[FEAT] UI frontend gap competitivi + help/i18n + OpenAPI + scorecard EVIX

UI delle 4 feature backend (ora usabili dagli utenti, non solo via API):
- risks.html: vista 'Quantitativo (FAIR)' con form + istogramma distribuzione ALE + registro portafoglio; vista 'KRI' con dashboard semafori green/amber/red + CRUD
- reports.html: tab 'Monitoraggio Continuo' con semafori freschezza controlli (healthy/warning/stale/failing) + copertura
- assets.html: bottone 'Importa' CSV/CMDB con parsing client + anteprima + scoring auto GV.OC-04
- api.js: metodi computeFair/getFairRegister/listKri/createKri/updateKri/importAssets/getControlsMonitoring

Trasversale:
- help.js: sezioni guida FAIR+KRI (risks), import CMDB (assets), monitoraggio continuo (reports)
- i18n.js: chiavi IT/EN (risks.fair_tab, risks.kri_tab, assets.import_btn, audit.monitoring_tab)
- ServicesController::openapi esteso con incidents-ingest/evidence-ingest/assets-ingest/controls-monitoring + securityScheme ApiKeyAuth
- AuditController::controlsMonitoring (versione JWT per la UI) + route audit/controlsMonitoring
- EVIX scorecard: gap P1/P2 marcati chiusi (backend), roadmap aggiornata

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-05-30 09:52:21 +02:00
co-authored by Claude Opus 4.8
parent edf0394616
commit 372ccb5ec1
7 changed files with 357 additions and 20 deletions
+51
View File
@@ -359,6 +359,7 @@
<button onclick="switchTab('controls', this)">Controlli</button>
<button onclick="switchTab('audit', this)">Audit Log</button>
<button onclick="switchTab('iso', this)">ISO 27001</button>
<button onclick="switchTab('monitoring', this)" data-i18n="audit.monitoring_tab">Monitoraggio Continuo</button>
</div>
<!-- Tab: Report Compliance -->
@@ -401,6 +402,16 @@
</div>
</div>
</div>
<!-- Tab: Continuous Control Monitoring -->
<div class="tab-panel" id="tab-monitoring">
<div id="monitoring-container">
<div class="loading-state">
<div class="spinner"></div>
<p>Caricamento monitoraggio...</p>
</div>
</div>
</div>
</div>
</main>
</div>
@@ -467,6 +478,46 @@
if (tabId === 'controls') loadControls();
if (tabId === 'audit') { auditPage = 1; loadAuditLogs(); }
if (tabId === 'iso') loadIsoMapping();
if (tabId === 'monitoring') loadMonitoring();
}
// ── Continuous Control Monitoring (P1) ───────────────────────
const MON_STATUS = {
healthy: ['#22c55e', 'Sano'],
warning: ['#eab308', 'Attenzione'],
stale: ['#f97316', 'Evidenza scaduta'],
failing: ['#ef4444', 'Non conforme'],
not_monitored: ['#9ca3af', 'Non monitorato']
};
async function loadMonitoring() {
const c = document.getElementById('monitoring-container');
try {
const res = await api.getControlsMonitoring();
if (!res.success) { c.innerHTML = '<div class="empty-state-box"><p>' + escapeHtml(res.message || 'Errore') + '</p></div>'; return; }
const d = res.data, s = d.summary || {};
const cards = ['healthy','warning','stale','failing','not_monitored'].map(k =>
`<div class="stat-box" style="border-left:4px solid ${MON_STATUS[k][0]};min-width:120px;">
<div class="stat-value">${s[k] || 0}</div><div class="stat-label">${MON_STATUS[k][1]}</div></div>`).join('');
const rows = (d.controls || []).map(ct => {
const st = MON_STATUS[ct.monitoring_status] || MON_STATUS.not_monitored;
return `<tr>
<td><span style="display:inline-block;width:11px;height:11px;border-radius:50%;background:${st[0]};vertical-align:middle;" title="${st[1]}"></span> ${st[1]}</td>
<td><code>${escapeHtml(ct.control_code)}</code></td>
<td>${escapeHtml(ct.title || '')}</td>
<td>${ct.last_checked_at || '<span style="color:var(--gray-400);">mai</span>'}</td>
<td>${ct.freshness_days || 30} gg</td></tr>`;
}).join('');
c.innerHTML = `
<p style="font-size:.85rem;color:var(--gray-600);margin-bottom:12px;">
Stato di freschezza dei controlli alimentato dalle <strong>evidenze automatiche</strong> dai connettori (Evidence Automation).
Copertura: <strong>${d.coverage_percent}%</strong> (${d.monitored}/${d.total_controls} controlli monitorati).</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px;">${cards}</div>
<table class="data-table"><thead><tr>
<th>Stato</th><th>Controllo</th><th>Titolo</th><th>Ultima evidenza</th><th>Freschezza</th>
</tr></thead><tbody>${rows || '<tr><td colspan="5" style="text-align:center;padding:20px;color:var(--gray-500);">Nessun controllo. Le evidenze arrivano via POST /api/services/evidence-ingest.</td></tr>'}</tbody></table>`;
} catch (e) {
c.innerHTML = '<div class="empty-state-box"><p>Errore di connessione</p></div>';
}
}
// ══════════════════════════════════════════════════════════