diff --git a/nodejs/plugins/discovery/nmap.js b/nodejs/plugins/discovery/nmap.js index 6f5b90b..d0eba1e 100644 --- a/nodejs/plugins/discovery/nmap.js +++ b/nodejs/plugins/discovery/nmap.js @@ -31,17 +31,19 @@ module.exports = { if (!targetRange) throw new Error("Missing targetRange for Nmap"); return new Promise((resolve, reject) => { - const scan = new nmap.OsAndPortScan(targetRange); + // OsAndPortScan requires root (for -O). NmapScan does a basic port scan (TCP connect if non-root). + const scan = new nmap.NmapScan(targetRange); scan.command.push('-Pn'); scan.on('complete', function(data) { const resources = []; const edges = []; for (const host of data) { - if (!host.mac || !host.ip) continue; - const hostSlug = `nmap-host-${host.mac.replace(/:/g, '')}`; + if (!host.ip) continue; + const hostId = host.mac ? host.mac.replace(/:/g, '') : host.ip.replace(/\\./g, '_'); + const hostSlug = `nmap-host-${hostId}`; - const interfaces = [{ mac: host.mac, ip: host.ip }]; + const interfaces = [{ mac: host.mac || null, ip: host.ip }]; resources.push({ kind: 'host', @@ -52,7 +54,7 @@ module.exports = { if (host.openPorts && host.openPorts.length > 0) { for (const port of host.openPorts) { - const svcSlug = `nmap-svc-${host.mac.replace(/:/g, '')}-${port.port}`; + const svcSlug = `nmap-svc-${hostId}-${port.port}`; resources.push({ kind: 'service', name: `${port.service} on ${port.port}`, diff --git a/nodejs/plugins/discovery/proxmox.js b/nodejs/plugins/discovery/proxmox.js index 25c691d..e59713d 100644 --- a/nodejs/plugins/discovery/proxmox.js +++ b/nodejs/plugins/discovery/proxmox.js @@ -43,6 +43,9 @@ module.exports = { const headers = { 'Authorization': `PVEAPIToken=${tokenId}=${tokenSecret}` }; + + // Ensure URL has no trailing slash + url = url.replace(/\\/+$/, ''); const resources = []; const edges = []; diff --git a/nodejs/views/discovery.ejs b/nodejs/views/discovery.ejs index 05e225b..2992eea 100644 --- a/nodejs/views/discovery.ejs +++ b/nodejs/views/discovery.ejs @@ -164,7 +164,7 @@ return; } $('.actionMessage').html('
Successfully promoted! Created groups: ' + res.groups.join(', ') + '
').show(); - renderTable(); + loadResources(); }); } diff --git a/nodejs/views/plugins.ejs b/nodejs/views/plugins.ejs index 3ce1724..ab691f3 100644 --- a/nodejs/views/plugins.ejs +++ b/nodejs/views/plugins.ejs @@ -57,6 +57,7 @@ + {{#lastError}}{{/lastError}} {{#enabled}}{{/enabled}} {{^enabled}}{{/enabled}} @@ -280,7 +281,7 @@ '
Secret fields are edited separately with the button.
', footer: { metaHtml: app.modal.formatAudit ? app.modal.formatAudit(p, { formatDate: function(ms){ return moment(ms).format('YYYY-MM-DD HH:mm'); } }) : '', - buttonsHtml: app.modal.footerButtons({ onSave: 'saveEdit("' + id + '")', saveLabel: 'Save' }) + buttonsHtml: app.modal.footerButtons({ onSave: 'saveEdit(\'' + id + '\')', saveLabel: 'Save' }) } }); } @@ -324,7 +325,7 @@ app.modal.open({ title: 'Edit Secrets — ' + p.name, bodyHtml: html, - footer: { buttonsHtml: app.modal.footerButtons({ onSave: 'saveSecrets("' + id + '")', saveLabel: 'Save Secrets' }) } + footer: { buttonsHtml: app.modal.footerButtons({ onSave: 'saveSecrets(\'' + id + '\')', saveLabel: 'Save Secrets' }) } }); } @@ -369,6 +370,16 @@ } } + function showLogs(id) { + var p = pluginsById[id]; + if (!p || !p.lastError) return; + app.modal.open({ + title: 'Logs — ' + p.name, + bodyHtml: '
' + String(p.lastError).replace(//g, '>') + '
', + footer: { buttonsHtml: '' } + }); + } + async function togglePlugin(id, enable) { try { await app.api.post('plugins/' + id + (enable ? '/load' : '/unload'), {});