-
🏢
-
Azienda
-
Porto la mia organizzazione in compliance NIS2
+
+
+
+
CISO / Compliance Manager
+
Responsabile sicurezza e conformità NIS2 dell'organizzazione
-
-
👤
-
Consulente / CISO
-
Gestisco la compliance di più aziende clienti
+
+
+
Legale Rappresentante / Admin
+
Amministratore e legale rappresentante dell'organizzazione
+
+
+
+
Consulente Cybersecurity
+
Gestisco la compliance NIS2 per più aziende clienti
+
+
+
+
Auditor / Revisore
+
Verifico e valuto l'implementazione dei controlli di sicurezza
+
+
+
+
Board Member / DPO
+
Membro del consiglio o Responsabile della Protezione dei Dati con visibilità strategica
-
@@ -610,13 +953,48 @@ const COMMANDS = {$cmdsJson};
let es = null;
let timerInterval = null;
let t0 = null;
+let currentRun = null;
+
+const HISTORY_KEY = 'nis2_run_history';
+const MAX_HISTORY = 15;
+
+// ── History ──────────────────────────────────────────────────────────────────
+function loadHistory() {
+ try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch { return []; }
+}
+function saveHistory(runs) {
+ localStorage.setItem(HISTORY_KEY, JSON.stringify(runs.slice(0, MAX_HISTORY)));
+}
+function addHistory(entry) {
+ const runs = loadHistory();
+ runs.unshift(entry);
+ saveHistory(runs);
+ renderHistory();
+}
+function renderHistory() {
+ const list = document.getElementById('history-list');
+ const runs = loadHistory();
+ if (!runs.length) { list.innerHTML = '
Nessun run ancora
'; return; }
+ list.innerHTML = runs.map(r => {
+ const icon = r.status === 'ok' ? '✓' : r.status === 'err' ? '✗' : '~';
+ const cls = r.status === 'ok' ? 'h-ok' : r.status === 'err' ? 'h-err' : '';
+ const time = new Date(r.ts).toLocaleTimeString('it-IT', {hour:'2-digit',minute:'2-digit'});
+ const elapsed = r.elapsed ? ` (\${r.elapsed}s)` : '';
+ return `
+ \${icon}
+ \${r.label}
+ \${time}\${elapsed}
+
`;
+ }).join('');
+}
+renderHistory();
// ── API status check ─────────────────────────────────────────────────────────
async function checkApiStatus() {
try {
const r = await fetch('status', { signal: AbortSignal.timeout(6000) });
const d = await r.json();
- const el = document.getElementById('api-status');
+ const el = document.getElementById('api-status');
const lbl = document.getElementById('api-label');
if (d.api_up) {
el.className = 'badge-status up';
@@ -632,15 +1010,35 @@ async function checkApiStatus() {
checkApiStatus();
setInterval(checkApiStatus, 30000);
+// ── DB Stats ─────────────────────────────────────────────────────────────────
+async function loadDbStats() {
+ const grid = document.getElementById('stats-grid');
+ const totalEl = document.getElementById('stats-total');
+ grid.innerHTML = '
Caricamento...
';
+ try {
+ const r = await fetch('db-stats', { signal: AbortSignal.timeout(8000) });
+ const d = await r.json();
+ if (d.error) { grid.innerHTML = `
\${d.error}
`; return; }
+ const stats = d.stats || {};
+ const entries = Object.entries(stats).filter(([,v]) => v !== null);
+ grid.innerHTML = entries.map(([t, v]) =>
+ `
+
\${v.toLocaleString('it-IT')}
+
\${t}
+
`
+ ).join('');
+ totalEl.textContent = `Totale righe DB: \${(d.total || 0).toLocaleString('it-IT')}`;
+ } catch (e) {
+ grid.innerHTML = `
Errore: \${e.message}
`;
+ }
+}
+
// ── Tab switch ───────────────────────────────────────────────────────────────
function showTab(id) {
- document.querySelectorAll('.tab').forEach((t, i) => {
- const ids = ['test', 'sim', 'cred'];
- t.classList.toggle('active', ids[i] === id);
- });
- document.querySelectorAll('.tab-panel').forEach(p => {
- p.classList.toggle('active', p.id === 'tab-' + id);
- });
+ const tabIds = ['test', 'sim', 'cov', 'stats', 'cred'];
+ document.querySelectorAll('.tab').forEach((t, i) => t.classList.toggle('active', tabIds[i] === id));
+ document.querySelectorAll('.tab-panel').forEach(p => p.classList.toggle('active', p.id === 'tab-' + id));
+ if (id === 'stats') loadDbStats();
}
// ── Terminal ─────────────────────────────────────────────────────────────────
@@ -665,14 +1063,16 @@ function runCmd(id) {
const cmd = COMMANDS.find(c => c.id === id);
const label = cmd ? cmd.label : id;
+ currentRun = { id, label, ts: new Date().toISOString(), status: 'running', elapsed: null };
+
document.getElementById('term-title').textContent = '▶ ' + label;
document.getElementById('running-bar').classList.add('active');
document.getElementById('btn-stop').classList.add('visible');
document.querySelectorAll('.btn').forEach(b => b.disabled = true);
- appendLine('sep', '────────────────────────────────────────────────────────');
- appendLine('ts', new Date().toLocaleTimeString('it-IT') + ' Avvio: ' + label);
- appendLine('sep', '────────────────────────────────────────────────────────');
+ appendLine('sep', '────────────────────────────────────────────────────────────────────');
+ appendLine('ts', new Date().toLocaleTimeString('it-IT') + ' ▶ ' + label);
+ appendLine('sep', '────────────────────────────────────────────────────────────────────');
t0 = Date.now();
timerInterval = setInterval(() => {
@@ -682,26 +1082,41 @@ function runCmd(id) {
es = new EventSource('events?cmd=' + encodeURIComponent(id));
- es.addEventListener('stdout', e => appendLine('out', e.data));
+ es.addEventListener('stdout', e => {
+ const d = e.data;
+ if (d.includes('[OK]') || d.startsWith('✓')) appendLine('ok', d);
+ else if (d.includes('[WARN]')) appendLine('warn', d);
+ else if (d.startsWith('━━━') || d.startsWith('════')) appendLine('info', d);
+ else appendLine('out', d);
+ });
es.addEventListener('stderr', e => {
const d = e.data;
- if (d.includes('[OK]') || d.includes('✓')) appendLine('ok', d);
- else if (d.includes('[WARN]')) appendLine('warn', d);
- else appendLine('err', d);
+ if (d.includes('[OK]') || d.startsWith('✓')) appendLine('ok', d);
+ else if (d.includes('[WARN]') || d.includes('[INFO]')) appendLine('warn', d);
+ else if (d.includes('[SKIP]')) appendLine('warn', d);
+ else appendLine('err', d);
});
es.addEventListener('done', e => {
+ let elapsed = null;
try {
const d = JSON.parse(e.data);
- appendLine('ok', '✓ Completato in ' + d.elapsed + 's');
+ elapsed = d.elapsed;
+ appendLine('ok', '✓ Completato in ' + d.elapsed + 's');
} catch { appendLine('ok', '✓ Completato'); }
+ if (currentRun) { currentRun.status = 'ok'; currentRun.elapsed = elapsed; addHistory(currentRun); }
finish();
});
- es.onerror = () => { appendLine('err', '[ERRORE] Connessione SSE interrotta'); finish(); };
+ es.onerror = () => {
+ appendLine('err', '[ERRORE] Connessione SSE interrotta');
+ if (currentRun) { currentRun.status = 'err'; addHistory(currentRun); }
+ finish();
+ };
}
function stopCmd() {
if (es) { es.close(); es = null; }
appendLine('warn', '[STOP] Interrotto dall\'utente');
+ if (currentRun) { currentRun.status = 'err'; addHistory(currentRun); }
finish();
}
@@ -712,6 +1127,7 @@ function finish() {
document.getElementById('btn-stop').classList.remove('visible');
document.getElementById('term-title').textContent = 'Completato · ' + new Date().toLocaleTimeString('it-IT');
document.querySelectorAll('.btn').forEach(b => b.disabled = false);
+ currentRun = null;
}