[FEAT] Integrazione analisi docs/nis2 v1.7.0 — scoring asset, tassonomia incidenti, PIR, NIST CSF, fonti certe
Fase 1 - Asset Relevance Scoring NIS2 (GV.OC-04): metodologia 0-100 a 6 criteri, AssetScoringService + endpoint scoringGrid/score/relevantSystems + UI assets.html + registro stampabile. Fase 2 - Tassonomia incidenti Determina ACN 164179/2025: IS-1..4 + regime essenziale/importante (Allegati 3/4). Fase 3 - Post-Incident Review (5-Whys) + metriche TTD/TTC/TTR + timestamp di fase. Fase 4 - Mapping NIST CSF 2.0 (43 controlli) reference-only. Fonti certe: registry config/nis2_sources.php + grounding AI (vieta riferimenti inventati) + citazioni help.js + ingest PDF normativi nella KB RAG (scripts/ingest-nis2-sources.php). Migrazioni 020/021/022 (additive idempotenti). Fix VectorService IP Qdrant (drift .5->.3). Analisi concorrenza Evix (docs/EVIX_ANALISI_CONCORRENZA.html, gap-driven). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a7a21faa82
commit
5c545ea3d0
+107
-1
@@ -466,6 +466,7 @@
|
||||
<th>Tipo</th>
|
||||
<th>Categoria</th>
|
||||
<th>Criticita'</th>
|
||||
<th>Rilevanza NIS2</th>
|
||||
<th>Owner</th>
|
||||
<th>Stato</th>
|
||||
<th>Azioni</th>
|
||||
@@ -482,9 +483,13 @@
|
||||
<td>${typeLabels[asset.asset_type] || asset.asset_type || '-'}</td>
|
||||
<td>${escapeHtml(asset.category || '-')}</td>
|
||||
<td><span class="criticality-badge ${crit}">${criticalityLabels[crit] || crit}</span></td>
|
||||
<td>${relevanceBadge(asset)}</td>
|
||||
<td>${escapeHtml(asset.owner_name || '-')}</td>
|
||||
<td><span class="status-badge ${st}">${statusLabels[st] || st}</span></td>
|
||||
<td>
|
||||
<td style="white-space:nowrap;">
|
||||
<button class="btn-icon" onclick="event.stopPropagation(); showScoringModal(${asset.id})" title="Valuta rilevanza NIS2">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M3 3a1 1 0 011 1v12h12a1 1 0 110 2H3a1 1 0 01-1-1V4a1 1 0 011-1zm14.707 4.707a1 1 0 00-1.414-1.414L12 10.586 9.707 8.293a1 1 0 00-1.414 0L4.586 12 6 13.414l2.293-2.293L10.586 13l5.121-5.293z" clip-rule="evenodd"/></svg>
|
||||
</button>
|
||||
<button class="btn-icon" onclick="event.stopPropagation(); showEditAssetModal(${asset.id})" title="Modifica">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg>
|
||||
</button>
|
||||
@@ -496,6 +501,106 @@
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// ── Rilevanza NIS2 (GV.OC-04) ───────────────────────────
|
||||
const relevanceClassColors = {
|
||||
critico: '#dc2626', alto: '#ea580c', medio: '#ca8a04',
|
||||
basso: '#2563eb', trascurabile: '#6b7280'
|
||||
};
|
||||
|
||||
function relevanceBadge(asset) {
|
||||
if (asset.relevance_score === null || asset.relevance_score === undefined || asset.relevance_score === '') {
|
||||
return '<span style="color:var(--gray-400); font-size:0.8rem;">Da valutare</span>';
|
||||
}
|
||||
const cls = asset.relevance_class || 'trascurabile';
|
||||
const color = relevanceClassColors[cls] || '#6b7280';
|
||||
const rel = Number(asset.is_nis2_relevant) ? ' ✓' : '';
|
||||
return `<span style="display:inline-flex;align-items:center;gap:6px;font-size:0.8rem;font-weight:600;color:${color};">
|
||||
<span style="display:inline-block;min-width:30px;text-align:center;padding:2px 6px;border-radius:6px;background:${color}1a;">${asset.relevance_score}</span>
|
||||
${cls.charAt(0).toUpperCase() + cls.slice(1)}${rel}</span>`;
|
||||
}
|
||||
|
||||
let _scoringGrid = null;
|
||||
async function loadScoringGrid() {
|
||||
if (_scoringGrid) return _scoringGrid;
|
||||
const r = await api.getScoringGrid();
|
||||
if (r.success) _scoringGrid = r.data;
|
||||
return _scoringGrid;
|
||||
}
|
||||
|
||||
async function showScoringModal(id) {
|
||||
try {
|
||||
const [assetRes, grid] = await Promise.all([api.getAsset(id), loadScoringGrid()]);
|
||||
if (!assetRes.success || !grid) { showNotification('Errore caricamento dati.', 'error'); return; }
|
||||
const a = assetRes.data;
|
||||
let prev = a.relevance_criteria;
|
||||
if (typeof prev === 'string') { try { prev = JSON.parse(prev); } catch (e) { prev = null; } }
|
||||
|
||||
let body = `<p style="font-size:0.85rem;color:var(--gray-600);margin-bottom:1rem;">
|
||||
Metodologia di scoring rilevanza NIS2 (requisito <strong>GV.OC-04</strong>). Soglia rilevanza: <strong>≥${grid.threshold} punti</strong>.
|
||||
Il punteggio aggiorna automaticamente anche la criticita dell'asset.</p>`;
|
||||
|
||||
for (const [key, def] of Object.entries(grid.grid)) {
|
||||
const sel = prev && prev[key] ? prev[key].value : '';
|
||||
let opts = `<option value="">— seleziona —</option>`;
|
||||
for (const [ov, od] of Object.entries(def.options)) {
|
||||
opts += `<option value="${ov}" data-pts="${od.points}" ${ov === sel ? 'selected' : ''}>${od.label} (${od.points})</option>`;
|
||||
}
|
||||
body += `<div class="form-group" style="margin-bottom:0.75rem;">
|
||||
<label class="form-label" style="font-weight:600;">${def.label} <span style="color:var(--gray-400);font-weight:400;">(max ${def.max})</span></label>
|
||||
<div style="font-size:0.78rem;color:var(--gray-500);margin-bottom:4px;">${def.help}</div>
|
||||
<select class="form-select score-criterion" data-key="${key}" onchange="updateScorePreview()">${opts}</select>
|
||||
</div>`;
|
||||
}
|
||||
body += `<div id="score-preview" style="margin-top:1rem;padding:0.9rem;border-radius:10px;background:var(--gray-50);font-weight:600;text-align:center;">
|
||||
Totale: <span id="score-total">0</span>/100 — <span id="score-class">—</span></div>`;
|
||||
|
||||
showModal(`Valuta Rilevanza NIS2 — ${escapeHtml(a.name)}`, body, {
|
||||
size: 'lg',
|
||||
footer: `<button class="btn btn-secondary" onclick="closeModal()">Annulla</button>
|
||||
<button class="btn btn-primary" onclick="submitScoring(${id})">Calcola e Salva</button>`
|
||||
});
|
||||
updateScorePreview();
|
||||
} catch (e) {
|
||||
showNotification('Errore nell\'apertura della valutazione.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function updateScorePreview() {
|
||||
let total = 0, complete = true;
|
||||
document.querySelectorAll('.score-criterion').forEach(s => {
|
||||
if (!s.value) { complete = false; return; }
|
||||
total += parseInt(s.selectedOptions[0].dataset.pts || '0', 10);
|
||||
});
|
||||
let cls = total >= 80 ? 'critico' : total >= 60 ? 'alto' : total >= 40 ? 'medio' : total >= 20 ? 'basso' : 'trascurabile';
|
||||
const color = relevanceClassColors[cls];
|
||||
document.getElementById('score-total').textContent = total;
|
||||
const clsEl = document.getElementById('score-class');
|
||||
clsEl.textContent = complete ? `${cls.charAt(0).toUpperCase() + cls.slice(1)}${total >= 40 ? ' — Rilevante NIS2 ✓' : ''}` : '(completa tutti i criteri)';
|
||||
clsEl.style.color = complete ? color : 'var(--gray-400)';
|
||||
}
|
||||
|
||||
async function submitScoring(id) {
|
||||
const criteria = {};
|
||||
let complete = true;
|
||||
document.querySelectorAll('.score-criterion').forEach(s => {
|
||||
if (!s.value) complete = false;
|
||||
criteria[s.dataset.key] = s.value;
|
||||
});
|
||||
if (!complete) { showNotification('Compila tutti i 6 criteri.', 'warning'); return; }
|
||||
try {
|
||||
const r = await api.scoreAsset(id, criteria);
|
||||
if (r.success) {
|
||||
showNotification(`Rilevanza calcolata: ${r.data.score}/100 (${r.data.class}).`, 'success');
|
||||
closeModal();
|
||||
loadAssets();
|
||||
} else {
|
||||
showNotification(r.message || 'Errore nel calcolo.', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Errore di connessione.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Asset Detail View ───────────────────────────────────
|
||||
async function showAssetDetail(id) {
|
||||
try {
|
||||
@@ -581,6 +686,7 @@
|
||||
size: 'lg',
|
||||
footer: `
|
||||
<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal(); showScoringModal(${a.id})">Valuta Rilevanza NIS2</button>
|
||||
<button class="btn btn-primary" onclick="closeModal(); showEditAssetModal(${a.id})">Modifica</button>
|
||||
`
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user