[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>
This commit is contained in:
DevEnv nis2-agile
2026-06-26 12:03:31 +02:00
co-authored by Claude Opus 4.8
parent 3d0bec1f7f
commit 01b016da50
40 changed files with 367 additions and 94 deletions
+1 -1
View File
@@ -72,7 +72,7 @@
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
<script src="js/api.js?v=20260631"></script>
<script src="js/common.js?v=20260624g"></script>
<script src="js/common-bi.js?v=20260624f"></script>
<script src="js/common-bi.js?v=20260625d"></script>
<script>
// Renderizza la sidebar BI (loadSidebar è stato ridefinito da common-bi.js)
document.addEventListener('DOMContentLoaded', function () {
+2 -2
View File
@@ -317,7 +317,7 @@
</main>
</div>
<script src="/js/api.js?v=20260620"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -328,7 +328,7 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260620"></script>
<script>
// ── Auth check ───────────────────────────────────────────
+2 -2
View File
@@ -147,7 +147,7 @@
</main>
</div>
<script src="../js/api.js?v=20260620"></script>
<script src="../js/api.js?v=20260625d"></script>
<script src="../js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -158,7 +158,7 @@
bootstrap.loadFonts('../vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="../js/common-bi.js?v=20260624f"></script>
<script src="../js/common-bi.js?v=20260625d"></script>
<script src="../js/i18n.js?v=20260620"></script>
<script>
// ── Auth check ───────────────────────────────────────────
+2 -2
View File
@@ -169,7 +169,7 @@
</main>
</div>
<script src="../js/api.js?v=20260620"></script>
<script src="../js/api.js?v=20260625d"></script>
<script src="../js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -180,7 +180,7 @@
bootstrap.loadFonts('../vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="../js/common-bi.js?v=20260624f"></script>
<script src="../js/common-bi.js?v=20260625d"></script>
<script src="../js/i18n.js?v=20260620"></script>
<script>
// ── Auth check ───────────────────────────────────────────
+2 -2
View File
@@ -1093,9 +1093,9 @@
<script src="js/api.js?v=20260631"></script>
<script src="js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="js/i18n.js?v=20260631"></script>
<script src="js/help.js?v=20260625c"></script>
<script src="js/help.js?v=20260625d"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
+3 -3
View File
@@ -154,7 +154,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -165,9 +165,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -370,7 +370,7 @@
</main>
</div>
<script src="/js/api.js?v=20260623g"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -381,9 +381,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -151,12 +151,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+2 -2
View File
@@ -354,9 +354,9 @@
<!-- Bootstrap Italia bundle + override sidebar BI (common-bi.js dopo common.js: ridefinisce solo loadSidebar) -->
<script src="vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script>if (window.bootstrap && bootstrap.loadFonts) { bootstrap.loadFonts('vendor/bootstrap-italia/dist/fonts'); }</script>
<script src="js/common-bi.js?v=20260624f"></script>
<script src="js/common-bi.js?v=20260625d"></script>
<script src="js/i18n.js?v=20260631"></script>
<script src="js/help.js?v=20260625c"></script>
<script src="js/help.js?v=20260625d"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -183,12 +183,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script src="/js/competenze.js?v=20260617"></script>
</body>
</html>
+109
View File
@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>Connettori Discovery - NIS2 Agile</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
<link rel="stylesheet" href="/css/style.css?v=20260618">
<meta name="theme-color" content="#0066CC">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<script src="/js/pwa.js?v=20260614" defer></script>
</head>
<body>
<div class="app-layout">
<aside class="sidebar" id="sidebar"></aside>
<main class="main-content">
<header class="content-header">
<h1 class="content-title">Connettori Discovery</h1>
<div class="content-header-actions">
<button class="btn btn-primary" id="btn-dc-new">+ Nuovo connettore</button>
</div>
</header>
<div class="content-body">
<div class="card" style="margin-bottom:16px">
<div class="card-body">
<p style="margin:0;color:var(--gray-600)">
I connettori mappano automaticamente la tua <strong>rete/cloud</strong> e <strong>popolano l'Inventario</strong>
(asset + rilevanza NIS2 + Mappa Dipendenze + flussi di rete ID.AM-03). Puoi creare
<strong>più connettori per azienda</strong> (es. uno per sede). Ogni connettore ha una <strong>chiave</strong>
che usi nell'<em>agente di discovery</em> (vedi <code>scripts/discovery-agent.mjs</code>).
</p>
</div>
</div>
<div class="card" id="dc-form-card" style="display:none;margin-bottom:16px">
<div class="card-header"><h3 style="margin:0">Nuovo connettore</h3></div>
<div class="card-body">
<div class="form-row" style="display:flex;gap:12px;flex-wrap:wrap">
<div class="form-group" style="flex:1;min-width:200px">
<label>Nome</label>
<input id="dc-name" class="form-control" placeholder="Es. Rete sede Modena" maxlength="120">
</div>
<div class="form-group" style="min-width:160px">
<label>Tipo</label>
<select id="dc-type" class="form-control">
<option value="network">Rete (scan IP)</option>
<option value="aws">AWS</option>
<option value="azure">Azure</option>
<option value="gcp">GCP</option>
<option value="cmdb">CMDB</option>
<option value="agent">Agente generico</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-group" style="min-width:160px">
<label>Schedulazione</label>
<select id="dc-schedule" class="form-control">
<option value="manual">Manuale</option>
<option value="daily">Giornaliera</option>
<option value="hourly">Oraria</option>
<option value="weekly">Settimanale</option>
</select>
</div>
</div>
<div class="form-group">
<label>Target (CIDR/IP, separati da virgola) — descrittivo / usato dall'agente</label>
<input id="dc-targets" class="form-control" placeholder="192.168.1.0/24, 10.0.0.5">
</div>
<div style="display:flex;gap:8px;margin-top:8px">
<button class="btn btn-primary" id="btn-dc-create">Crea connettore</button>
<button class="btn btn-outline-primary" id="btn-dc-cancel">Annulla</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div id="dc-list">Caricamento…</div>
</div>
</div>
</div>
</main>
</div>
<script src="/js/common.js?v=20260624g"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script>
if (window.bootstrap && bootstrap.loadFonts) { bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); }
</script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625d"></script>
<script src="/js/connettori-discovery.js?v=20260625"></script>
<script>
if (typeof checkAuth === 'function' && !checkAuth()) {
// checkAuth reindirizza a login
} else {
if (typeof loadSidebar === 'function') loadSidebar();
if (typeof I18n !== 'undefined' && I18n.init) I18n.init();
if (typeof HelpSystem !== 'undefined' && HelpSystem.init) HelpSystem.init();
if (window.DiscoveryUI) DiscoveryUI.init();
}
</script>
</body>
</html>
+3 -3
View File
@@ -136,12 +136,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
+2 -2
View File
@@ -382,7 +382,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -393,7 +393,7 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script>
const SPRITE = '/vendor/bootstrap-italia/dist/svg/sprites.svg';
+3 -3
View File
@@ -143,7 +143,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -154,9 +154,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -1142,7 +1142,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (accordion) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI. -->
@@ -1152,9 +1152,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// La guida è accessibile anche da non autenticati (utile per onboarding)
// ma se sei loggato carichi sidebar + i18n normalmente. Stessa logica
+3 -3
View File
@@ -351,7 +351,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -362,9 +362,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ──────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -139,12 +139,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+3 -3
View File
@@ -184,7 +184,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -195,9 +195,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script src="/js/isms.js?v=20260620b"></script>
</body>
</html>
+8
View File
@@ -558,6 +558,14 @@ class NIS2API {
getFeedback(id) { return this.get(`/feedback/${id}`); }
updateFeedback(id, data) { return this.put(`/feedback/${id}`, data); }
resolveFeedback(id, password) { return this.post(`/feedback/${id}/resolve`, { password }); }
// ── Connettori Discovery (mappatura rete/cloud → auto-popola Inventario) ──
listDiscoveryConnectors() { return this.get('/discovery-connectors/list'); }
createDiscoveryConnector(data) { return this.post('/discovery-connectors/create', data); }
getDiscoveryConnector(id) { return this.get(`/discovery-connectors/${id}`); }
updateDiscoveryConnector(id, d){ return this.put(`/discovery-connectors/${id}`, d); }
deleteDiscoveryConnector(id) { return this.del(`/discovery-connectors/${id}`); }
rotateDiscoveryKey(id) { return this.post(`/discovery-connectors/${id}/rotateKey`, {}); }
}
// Singleton globale
+1
View File
@@ -91,6 +91,7 @@
items: [
{ name: 'Formazione', href: 'training.html', icon: iconAcademicCap(), i18nKey: 'nav.training' },
{ name: 'Inventario', href: 'assets.html', icon: iconServer(), i18nKey: 'nav.assets' },
{ name: 'Connettori Discovery', href: 'connettori-discovery.html', icon: '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"/><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"/></svg>' },
{ name: 'Audit interni', href: 'internal-audits.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm5.707 6.707a1 1 0 00-1.414-1.414L7 10.586l-.293-.293a1 1 0 00-1.414 1.414l1 1a1 1 0 001.414 0l2-2zM10 13a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/></svg>`, i18nKey: 'nav.internal_audits' },
{ name: 'Controlli periodici', href: 'controlli-periodici.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.5 2.5a1 1 0 001.414-1.414L11 9.586V6z" clip-rule="evenodd"/></svg>`, i18nKey: 'nav.periodic_controls' },
{ name: 'Riesame di Direzione', href: 'management-review.html', icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>`, i18nKey: 'nav.management_review' },
+115
View File
@@ -0,0 +1,115 @@
/**
* 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 };
})();
+40
View File
@@ -980,6 +980,44 @@ const HelpSystem = (function () {
]
},
// ─── Connettori Discovery ────────────────────────────────────
'connettori-discovery': {
title: 'Guida - Connettori Discovery',
intro: 'I connettori di discovery mappano automaticamente la rete o il cloud dell\'organizzazione e popolano l\'Inventario con gli asset rilevati, la rilevanza NIS2, le dipendenze e i flussi di rete (ID.AM-03), riducendo l\'inserimento manuale.',
sections: [
{
heading: 'Più connettori per azienda',
items: [
'Puoi creare <strong>più connettori</strong> per la stessa organizzazione, uno per ciascuna sorgente (es. "Rete sede Roma", "Rete sede Modena", "AWS prod").',
'Ogni connettore ha un <strong>tipo</strong> (rete, AWS, Azure, GCP, CMDB, agente, custom) e una propria <strong>chiave API</strong>.',
'Stato (attivo/in pausa/errore) e storico delle esecuzioni sono nel dettaglio del connettore.'
]
},
{
heading: 'Come funziona',
items: [
'Crea un connettore con "+ Nuovo connettore": ottieni una <strong>chiave API</strong> mostrata una sola volta (copiala subito).',
'Esegui l\'<strong>agente di discovery</strong> (<code>scripts/discovery-agent.mjs</code>) dentro la rete da mappare con quella chiave: scansiona gli host e invia gli asset a NIS2.',
'NIS2 fa <strong>dedup</strong> per indirizzo, calcola il <strong>punteggio di rilevanza NIS2</strong> e aggiorna Inventario + Mappa Dipendenze + flussi di rete.',
'Ogni esecuzione aggiorna "ultimo run" e il numero di asset scoperti.'
]
},
{
heading: 'Sicurezza',
items: [
'La chiave del connettore ha <strong>solo</strong> lo scope <code>ingest:assets</code>: può aggiungere asset/flussi solo alla tua organizzazione, non leggere altri dati.',
'Se una chiave è compromessa usa <strong>Rigenera chiave</strong>: la vecchia smette di funzionare.',
'La gestione dei connettori è riservata agli <strong>amministratori dell\'organizzazione</strong>.'
]
}
],
references: [
'Art. 24 D.Lgs. 138/2024 - Gestione degli attivi e dei sistemi informativi e di rete',
'NIST CSF ID.AM-01/02 - Inventario hardware/software; ID.AM-03 - Flussi di rete',
'Identificazione e classificazione dei sistemi rilevanti - scoring 0-100 (GV.OC-04)'
]
},
// ─── Reports / Audit ─────────────────────────────────────────
'reports': {
title: 'Guida - Audit & Report',
@@ -1509,6 +1547,8 @@ const HelpSystem = (function () {
'training': 'training',
'assets.html': 'assets',
'assets': 'assets',
'connettori-discovery.html': 'connettori-discovery',
'connettori-discovery': 'connettori-discovery',
'reports.html': 'reports',
'audit.html': 'reports',
'reports': 'reports',
+3 -3
View File
@@ -152,7 +152,7 @@
<!-- Stessa logica JS della pagina live (parita' funzionale assoluta, zero backend). -->
<script src="/js/common.js?v=20260624g"></script>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI. -->
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
@@ -161,9 +161,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script src="/js/kb.js"></script>
<script>
// Gate auth + chrome come le altre pagine -bi.
+3 -3
View File
@@ -126,12 +126,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+3 -3
View File
@@ -167,12 +167,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+3 -3
View File
@@ -112,7 +112,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -123,9 +123,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
+3 -3
View File
@@ -138,12 +138,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script src="/js/organigramma.js?v=20260617"></script>
</body>
</html>
+3 -3
View File
@@ -333,7 +333,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -344,9 +344,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -131,12 +131,12 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script src="/js/raci.js?v=20260619"></script>
</body>
</html>
+3 -3
View File
@@ -443,7 +443,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -454,9 +454,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -141,12 +141,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+3 -3
View File
@@ -494,7 +494,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -505,9 +505,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ──────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -688,7 +688,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -699,9 +699,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -194,12 +194,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+3 -3
View File
@@ -191,12 +191,12 @@
</div>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
'use strict';
/*
+3 -3
View File
@@ -477,7 +477,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -488,9 +488,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260621sp"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -292,7 +292,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -303,9 +303,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+3 -3
View File
@@ -207,7 +207,7 @@
<!-- Report Detail Modal handled by common.js showModal -->
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260625d"></script>
<script src="/js/common.js?v=20260624g"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -218,9 +218,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="/js/i18n.js?v=20260631"></script>
<script src="/js/help.js?v=20260625c"></script>
<script src="/js/help.js?v=20260625d"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
+2 -2
View File
@@ -324,9 +324,9 @@
<script src="js/api.js?v=20260631"></script>
<script src="js/common.js?v=20260624g"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260624f"></script>
<script src="/js/common-bi.js?v=20260625d"></script>
<script src="js/i18n.js?v=20260631"></script>
<script src="js/help.js?v=20260625c"></script>
<script src="js/help.js?v=20260625d"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();