Files
nis2-agile/public/js/connettori-discovery.js
DevEnv nis2-agileandClaude Opus 4.8 01b016da50 [FEAT] Connettori Discovery — UI gestione per org (Fase 4)
Pagina connettori-discovery.html + connettori-discovery.js (DiscoveryUI): lista
connettori, crea (mostra la chiave API una sola volta + comando agente), dettaglio
con storico run, rigenera chiave, elimina. Solo org_admin.
- Voce sidebar V2 "Connettori Discovery" in Operativo (common-bi.js, data-nav auto).
- api.js: metodi list/create/get/update/delete/rotateDiscoveryKey.
- ARIA navigationMapBlock: aggiunta la voce (coerenza assistente↔menu).
- help.js: _pageMap + guida contestuale "Connettori Discovery" (multi-connettore,
  come funziona, sicurezza scope ingest:assets).
Smoke: pagina 200, /api/discovery-connectors/list 401 (auth). Cache-buster
common-bi/help/api -> 20260625d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:03:31 +02:00

116 lines
7.6 KiB
JavaScript

/**
* NIS2 Agile — Gestione Connettori Discovery (mappatura rete/cloud → auto-popola Inventario).
* Usa api.js (CRUD) + helper di common.js (escHtml/showModal/closeModal/showNotification).
*/
(function () {
'use strict';
function esc(s) {
s = (s == null) ? '' : String(s);
return (typeof escHtml === 'function') ? escHtml(s)
: s.replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; });
}
function notify(m, t) { if (typeof showNotification === 'function') showNotification(m, t || 'info'); else alert(m); }
function fmtDate(s) { if (!s) return '—'; try { return new Date(String(s).replace(' ', 'T')).toLocaleString('it-IT', { timeZone: 'Europe/Rome' }); } catch (e) { return s; } }
var INGEST_URL = location.origin + '/api/services/assets-ingest';
async function loadList() {
var el = document.getElementById('dc-list');
el.innerHTML = 'Caricamento…';
var r = await api.get('/discovery-connectors/list');
if (!r || !r.success) { el.innerHTML = '<div class="alert alert-danger">Errore nel caricamento.</div>'; return; }
var rows = (r.data && r.data.connectors) || [];
if (!rows.length) { el.innerHTML = '<p style="color:var(--gray-600)">Nessun connettore. Creane uno con «+ Nuovo connettore».</p>'; return; }
var html = '<table class="table"><thead><tr><th>Nome</th><th>Tipo</th><th>Stato</th><th>Ultimo run</th><th>Asset</th><th>Chiave</th><th></th></tr></thead><tbody>';
rows.forEach(function (c) {
var badge = c.status === 'active' ? 'success' : (c.status === 'paused' ? 'secondary' : 'danger');
var nm = esc(c.name).replace(/'/g, '');
html += '<tr>'
+ '<td><strong>' + esc(c.name) + '</strong></td>'
+ '<td>' + esc(c.connector_type) + '</td>'
+ '<td><span class="badge bg-' + badge + '">' + esc(c.status) + '</span></td>'
+ '<td>' + fmtDate(c.last_run_at) + (c.last_status ? (' <small>(' + esc(c.last_status) + ')</small>') : '') + '</td>'
+ '<td>' + (c.last_discovered != null ? esc(c.last_discovered) : '—') + '</td>'
+ '<td><code>' + esc(c.key_prefix || '—') + '…</code></td>'
+ '<td style="white-space:nowrap">'
+ '<button class="btn btn-sm btn-outline-primary" onclick="DiscoveryUI.detail(' + c.id + ')">Dettaglio</button> '
+ '<button class="btn btn-sm btn-outline-secondary" onclick="DiscoveryUI.rotate(' + c.id + ',\'' + nm + '\')">Rigenera chiave</button> '
+ '<button class="btn btn-sm btn-outline-danger" onclick="DiscoveryUI.remove(' + c.id + ',\'' + nm + '\')">Elimina</button>'
+ '</td></tr>';
});
html += '</tbody></table>';
el.innerHTML = html;
}
function keyModal(title, rawKey, name) {
var cmd = 'NIS2_API_KEY=' + rawKey + ' TARGETS=192.168.1.0/24 node discovery-agent.mjs';
var body = '<p>Copia ORA questa chiave: <strong>non sarà più visibile</strong>.</p>'
+ '<div class="form-group"><label>API key</label><input class="form-control" readonly value="' + esc(rawKey) + '" onclick="this.select()"></div>'
+ '<div class="form-group"><label>Comando agente (esempio)</label><textarea class="form-control" rows="3" readonly onclick="this.select()">' + esc(cmd) + '</textarea></div>'
+ '<p style="color:var(--gray-600);font-size:.9em">Endpoint ingest: <code>' + esc(INGEST_URL) + '</code> · scope <code>ingest:assets</code>. Agente: <code>scripts/discovery-agent.mjs</code>.</p>';
showModal(title, body, { size: 'lg', footer: '<button class="btn btn-primary" onclick="closeModal()">Ho copiato</button>' });
}
async function create() {
var name = (document.getElementById('dc-name').value || '').trim();
if (!name) { notify('Inserisci un nome', 'warning'); return; }
var data = {
name: name,
connector_type: document.getElementById('dc-type').value,
schedule: document.getElementById('dc-schedule').value,
config: { targets: (document.getElementById('dc-targets').value || '').split(',').map(function (s) { return s.trim(); }).filter(Boolean) }
};
var r = await api.post('/discovery-connectors/create', data);
if (r && r.success) {
document.getElementById('dc-form-card').style.display = 'none';
document.getElementById('dc-name').value = '';
document.getElementById('dc-targets').value = '';
keyModal('Connettore creato — chiave API', r.data.api_key, name);
loadList();
} else { notify('Errore: ' + ((r && r.message) || 'sconosciuto'), 'danger'); }
}
async function detail(id) {
var r = await api.get('/discovery-connectors/' + id);
if (!r || !r.success) { notify('Connettore non trovato', 'danger'); return; }
var c = r.data;
var runs = (c.runs || []).map(function (x) {
return '<tr><td>' + fmtDate(x.started_at) + '</td><td>' + esc(x.status) + '</td><td>' + esc(x.discovered_assets) + '</td><td>' + esc(x.discovered_flows) + '</td><td>' + esc(x.message || '') + '</td></tr>';
}).join('');
var targets = ((c.config && c.config.targets) || []).join(', ') || '—';
var body = '<p><strong>Tipo:</strong> ' + esc(c.connector_type) + ' · <strong>Stato:</strong> ' + esc(c.status) + ' · <strong>Schedulazione:</strong> ' + esc(c.schedule) + '</p>'
+ '<p><strong>Target:</strong> ' + esc(targets) + '</p>'
+ '<p><strong>Chiave:</strong> <code>' + esc(c.key_prefix || '—') + '…</code> · <strong>ultimo run:</strong> ' + fmtDate(c.last_run_at) + '</p>'
+ '<h4>Ultimi run</h4>'
+ (runs
? ('<table class="table table-sm"><thead><tr><th>Quando</th><th>Esito</th><th>Asset</th><th>Flussi</th><th>Note</th></tr></thead><tbody>' + runs + '</tbody></table>')
: '<p style="color:var(--gray-600)">Nessun run ancora. Esegui l\'agente di discovery con la chiave di questo connettore.</p>');
showModal('Connettore: ' + esc(c.name), body, { size: 'lg', footer: '<button class="btn btn-outline-primary" onclick="closeModal()">Chiudi</button>' });
}
async function rotate(id, name) {
if (!confirm('Rigenerare la chiave di «' + name + '»? La vecchia smetterà di funzionare.')) return;
var r = await api.post('/discovery-connectors/' + id + '/rotateKey', {});
if (r && r.success) { keyModal('Nuova chiave API', r.data.api_key, name); loadList(); }
else { notify('Errore nella rigenerazione', 'danger'); }
}
async function remove(id, name) {
if (!confirm('Eliminare il connettore «' + name + '»? La sua chiave verrà disattivata.')) return;
var r = await api.del('/discovery-connectors/' + id);
if (r && r.success) { notify('Connettore eliminato', 'success'); loadList(); }
else { notify('Errore nell\'eliminazione', 'danger'); }
}
function init() {
var b = document.getElementById('btn-dc-new');
if (b) b.onclick = function () { var f = document.getElementById('dc-form-card'); f.style.display = (f.style.display === 'none') ? 'block' : 'none'; };
var bc = document.getElementById('btn-dc-create'); if (bc) bc.onclick = create;
var bx = document.getElementById('btn-dc-cancel'); if (bx) bx.onclick = function () { document.getElementById('dc-form-card').style.display = 'none'; };
loadList();
}
window.DiscoveryUI = { init: init, detail: detail, rotate: rotate, remove: remove };
})();