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>
116 lines
7.6 KiB
JavaScript
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 { '&': '&', '<': '<', '>': '>', '"': '"' }[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 };
|
|
})();
|