[FEAT] Inventario Rilevanza NIS2: significato opzioni combo + dettaglio punteggio per criterio (ticket #431, #432)

- AssetScoringService::GRID: aggiunto campo 'desc' a tutte le 35 opzioni dei 6 criteri (significato operativo, additivo)
- assets.html: ogni combo mostra il significato dell'opzione selezionata + nuovo box 'Dettaglio punteggio per criterio' (live, anche alla riapertura di una valutazione salvata)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-23 22:43:43 +02:00
co-authored by Claude Opus 4.8
parent ea5966eaee
commit cf23be9c77
3 changed files with 60 additions and 40 deletions
+34 -34
View File
@@ -33,12 +33,12 @@ class AssetScoringService
'max' => 25,
'help' => 'Quanto il sistema e essenziale per l\'erogazione dei servizi core business.',
'options' => [
'critical' => ['label' => 'Critico', 'points' => 25],
'very_high' => ['label' => 'Molto Alto', 'points' => 20],
'high' => ['label' => 'Alto', 'points' => 15],
'medium' => ['label' => 'Medio', 'points' => 10],
'low' => ['label' => 'Basso', 'points' => 5],
'negligible' => ['label' => 'Trascurabile', 'points' => 0],
'critical' => ['label' => 'Critico', 'points' => 25, 'desc' => 'Sistema indispensabile: un suo fermo blocca subito i servizi core dell\'organizzazione.'],
'very_high' => ['label' => 'Molto Alto', 'points' => 20, 'desc' => 'Sistema primario: un fermo degrada gravemente i servizi core, con alternative molto limitate.'],
'high' => ['label' => 'Alto', 'points' => 15, 'desc' => 'Sistema importante: il fermo rallenta i servizi core ma esistono alternative parziali.'],
'medium' => ['label' => 'Medio', 'points' => 10, 'desc' => 'Sistema di supporto: il fermo ha impatto contenuto e gestibile sui servizi core.'],
'low' => ['label' => 'Basso', 'points' => 5, 'desc' => 'Sistema accessorio: il fermo incide poco sull\'operativita.'],
'negligible' => ['label' => 'Trascurabile', 'points' => 0, 'desc' => 'Sistema non essenziale: nessun impatto pratico sui servizi core.'],
],
],
'c2_disruption_impact' => [
@@ -46,12 +46,12 @@ class AssetScoringService
'max' => 25,
'help' => 'Conseguenze di un\'interruzione in termini di durata e utenti impattati.',
'options' => [
'gt24h_gt70' => ['label' => '>24h + >70% utenti', 'points' => 25],
'h8_24_50_70' => ['label' => '8-24h + 50-70% utenti', 'points' => 20],
'h4_8_30_50' => ['label' => '4-8h + 30-50% utenti', 'points' => 15],
'h1_4_10_30' => ['label' => '1-4h + 10-30% utenti', 'points' => 10],
'lt1h_lt10' => ['label' => '<1h + <10% utenti', 'points' => 5],
'none' => ['label' => 'Nessun impatto', 'points' => 0],
'gt24h_gt70' => ['label' => '>24h + >70% utenti', 'points' => 25, 'desc' => 'Interruzione oltre 24 ore che coinvolge piu del 70% degli utenti.'],
'h8_24_50_70' => ['label' => '8-24h + 50-70% utenti', 'points' => 20, 'desc' => 'Interruzione da 8 a 24 ore, dal 50 al 70% degli utenti coinvolti.'],
'h4_8_30_50' => ['label' => '4-8h + 30-50% utenti', 'points' => 15, 'desc' => 'Interruzione da 4 a 8 ore, dal 30 al 50% degli utenti coinvolti.'],
'h1_4_10_30' => ['label' => '1-4h + 10-30% utenti', 'points' => 10, 'desc' => 'Interruzione da 1 a 4 ore, dal 10 al 30% degli utenti coinvolti.'],
'lt1h_lt10' => ['label' => '<1h + <10% utenti', 'points' => 5, 'desc' => 'Interruzione sotto 1 ora, meno del 10% degli utenti coinvolti.'],
'none' => ['label' => 'Nessun impatto', 'points' => 0, 'desc' => 'Nessun impatto operativo apprezzabile in caso di interruzione.'],
],
],
'c3_data_processed' => [
@@ -59,11 +59,11 @@ class AssetScoringService
'max' => 20,
'help' => 'Sensibilita e criticita dei dati gestiti dal sistema.',
'options' => [
'gdpr_art9' => ['label' => 'Dati Sensibili Art.9 GDPR', 'points' => 20],
'personal_large' => ['label' => 'Dati Personali larga scala', 'points' => 15],
'personal_fin' => ['label' => 'Dati Personali + Finanziari', 'points' => 10],
'confidential' => ['label' => 'Dati Aziendali Riservati', 'points' => 5],
'public' => ['label' => 'Dati Pubblici', 'points' => 0],
'gdpr_art9' => ['label' => 'Dati Sensibili Art.9 GDPR', 'points' => 20, 'desc' => 'Categorie particolari di dati personali (salute, biometrici, ecc.) ai sensi dell\'Art.9 GDPR.'],
'personal_large' => ['label' => 'Dati Personali larga scala', 'points' => 15, 'desc' => 'Dati personali comuni trattati su larga scala.'],
'personal_fin' => ['label' => 'Dati Personali + Finanziari', 'points' => 10, 'desc' => 'Dati personali insieme a dati finanziari o di pagamento.'],
'confidential' => ['label' => 'Dati Aziendali Riservati', 'points' => 5, 'desc' => 'Dati aziendali riservati non personali (know-how, contratti).'],
'public' => ['label' => 'Dati Pubblici', 'points' => 0, 'desc' => 'Solo dati pubblici o gia liberamente divulgabili.'],
],
],
'c4_dependencies' => [
@@ -71,12 +71,12 @@ class AssetScoringService
'max' => 15,
'help' => 'Quanti altri sistemi critici dipendono da questo sistema.',
'options' => [
'ge5_critical' => ['label' => '>=5 sistemi critici', 'points' => 15],
'n3_4_critical' => ['label' => '3-4 sistemi critici', 'points' => 12],
'n2_critical' => ['label' => '2 sistemi critici', 'points' => 9],
'n1_critical' => ['label' => '1 sistema critico', 'points' => 6],
'noncritical' => ['label' => '1-2 sistemi non critici', 'points' => 3],
'none' => ['label' => 'Nessuna dipendenza', 'points' => 0],
'ge5_critical' => ['label' => '>=5 sistemi critici', 'points' => 15, 'desc' => 'Da questo sistema dipendono 5 o piu sistemi critici.'],
'n3_4_critical' => ['label' => '3-4 sistemi critici', 'points' => 12, 'desc' => 'Da questo sistema dipendono 3 o 4 sistemi critici.'],
'n2_critical' => ['label' => '2 sistemi critici', 'points' => 9, 'desc' => 'Da questo sistema dipendono 2 sistemi critici.'],
'n1_critical' => ['label' => '1 sistema critico', 'points' => 6, 'desc' => 'Da questo sistema dipende 1 sistema critico.'],
'noncritical' => ['label' => '1-2 sistemi non critici', 'points' => 3, 'desc' => 'Dipendono solo 1-2 sistemi non critici.'],
'none' => ['label' => 'Nessuna dipendenza', 'points' => 0, 'desc' => 'Nessun altro sistema dipende da questo.'],
],
],
'c5_exposure' => [
@@ -84,12 +84,12 @@ class AssetScoringService
'max' => 10,
'help' => 'Superficie di attacco ed esposizione del sistema.',
'options' => [
'internet_no_mfa' => ['label' => 'Internet pubblico senza MFA', 'points' => 10],
'internet_mfa' => ['label' => 'Internet con MFA', 'points' => 8],
'partner_net' => ['label' => 'Reti partner/fornitori', 'points' => 6],
'intranet' => ['label' => 'Rete aziendale intranet', 'points' => 4],
'mgmt_isolated' => ['label' => 'Rete gestione isolata', 'points' => 2],
'air_gapped' => ['label' => 'Completamente isolato', 'points' => 0],
'internet_no_mfa' => ['label' => 'Internet pubblico senza MFA', 'points' => 10, 'desc' => 'Esposto su Internet pubblico senza autenticazione a piu fattori.'],
'internet_mfa' => ['label' => 'Internet con MFA', 'points' => 8, 'desc' => 'Esposto su Internet ma protetto da autenticazione a piu fattori.'],
'partner_net' => ['label' => 'Reti partner/fornitori', 'points' => 6, 'desc' => 'Raggiungibile dalle reti di partner o fornitori.'],
'intranet' => ['label' => 'Rete aziendale intranet', 'points' => 4, 'desc' => 'Raggiungibile solo dalla rete aziendale interna.'],
'mgmt_isolated' => ['label' => 'Rete gestione isolata', 'points' => 2, 'desc' => 'Accessibile solo da una rete di gestione isolata.'],
'air_gapped' => ['label' => 'Completamente isolato', 'points' => 0, 'desc' => 'Completamente isolato, senza connettivita di rete.'],
],
],
'c6_regulatory' => [
@@ -97,11 +97,11 @@ class AssetScoringService
'max' => 5,
'help' => 'Se il sistema e soggetto a obblighi normativi o contrattuali specifici.',
'options' => [
'nis2_required' => ['label' => 'Richiesto da NIS2', 'points' => 5],
'mandatory_cert' => ['label' => 'Certificazioni obbligatorie', 'points' => 4],
'strict_sla' => ['label' => 'Obblighi SLA stringenti', 'points' => 3],
'external_audit' => ['label' => 'Audit esterni regolari', 'points' => 2],
'none' => ['label' => 'Nessun obbligo', 'points' => 0],
'nis2_required' => ['label' => 'Richiesto da NIS2', 'points' => 5, 'desc' => 'Sistema che rientra negli obblighi NIS2 dell\'organizzazione.'],
'mandatory_cert' => ['label' => 'Certificazioni obbligatorie', 'points' => 4, 'desc' => 'Soggetto a certificazioni obbligatorie (es. ISO o settoriali).'],
'strict_sla' => ['label' => 'Obblighi SLA stringenti', 'points' => 3, 'desc' => 'Vincolato a SLA contrattuali stringenti.'],
'external_audit' => ['label' => 'Audit esterni regolari', 'points' => 2, 'desc' => 'Sottoposto regolarmente ad audit esterni.'],
'none' => ['label' => 'Nessun obbligo', 'points' => 0, 'desc' => 'Nessun obbligo normativo o contrattuale specifico.'],
],
],
];
+25 -5
View File
@@ -634,16 +634,18 @@
const sel = (pv != null) ? (typeof pv === 'object' ? (pv.value || '') : pv) : '';
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>`;
opts += `<option value="${ov}" data-pts="${od.points}" data-lbl="${escapeHtml(od.label)}" data-desc="${escapeHtml(od.desc || '')}" ${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()" aria-label="${def.label}">${opts}</select>
<select class="form-select score-criterion" data-key="${key}" data-crit-label="${escapeHtml(def.label)}" data-max="${def.max}" onchange="updateScorePreview()" aria-label="${def.label}">${opts}</select>
<div class="score-meaning" id="meaning-${key}" style="font-size:0.78rem;color:var(--gray-600);margin-top:4px;min-height:1.1em;font-style:italic;"></div>
</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>`;
Totale: <span id="score-total">0</span>/100 — <span id="score-class">—</span></div>
<div id="score-breakdown" style="margin-top:0.6rem;font-size:0.8rem;color:var(--gray-700);"></div>`;
showModal(`Valuta Rilevanza NIS2 — ${escapeHtml(a.name)}`, body, {
size: 'lg',
@@ -658,9 +660,23 @@
function updateScorePreview() {
let total = 0, complete = true;
let rows = '';
document.querySelectorAll('.score-criterion').forEach(s => {
if (!s.value) { complete = false; return; }
total += parseInt(s.selectedOptions[0].dataset.pts || '0', 10);
const meaningEl = document.getElementById('meaning-' + s.dataset.key);
if (!s.value) {
complete = false;
if (meaningEl) meaningEl.textContent = '';
return;
}
const opt = s.selectedOptions[0];
const pts = parseInt(opt.dataset.pts || '0', 10);
total += pts;
// #432: mostra il significato dell'opzione selezionata sotto la combo
if (meaningEl) meaningEl.textContent = opt.dataset.desc || '';
// #431: dettaglio per criterio del punteggio raggiunto
rows += `<div style="display:flex;justify-content:space-between;gap:0.5rem;padding:3px 0;border-bottom:1px solid var(--gray-100);">
<span>${escapeHtml(s.dataset.critLabel)}: ${escapeHtml(opt.dataset.lbl || '')}</span>
<span style="font-weight:600;white-space:nowrap;">${pts}/${s.dataset.max}</span></div>`;
});
let cls = total >= 80 ? 'critico' : total >= 60 ? 'alto' : total >= 40 ? 'medio' : total >= 20 ? 'basso' : 'trascurabile';
const color = relevanceClassColors[cls];
@@ -668,6 +684,10 @@
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)';
const bdEl = document.getElementById('score-breakdown');
if (bdEl) bdEl.innerHTML = rows
? `<div style="font-weight:600;margin-bottom:4px;">Dettaglio punteggio per criterio</div>${rows}`
: '';
}
async function submitScoring(id) {
+1 -1
View File
@@ -1 +1 @@
{"version": "1.24.9", "build": "2026-06-23-v1.24.9", "date": "2026-06-23", "changelog": "Assistente ARIA — Ora ARIA conosce la barra laterale REALE dell'app e, quando ti spiega dove cliccare, usa i nomi esatti delle voci di menu (es. «sezione Operativo > Inventario») senza piu' inventare moduli o «fasi» inesistenti. Inoltre riceve la pagina da cui stai scrivendo, cosi' da darti indicazioni piu' pertinenti al punto in cui ti trovi. Risolto ticket #424."}
{"version": "1.24.10", "build": "2026-06-23-v1.24.10", "date": "2026-06-23", "changelog": "Valuta Rilevanza NIS2 (Inventario) — Ogni voce delle tendine ora mostra anche il suo SIGNIFICATO: scegliendo un'opzione compare sotto la combo una breve spiegazione di cosa indica (es. cosa vuol dire «Critico» per la Criticita Operativa). In piu' la finestra mostra il «Dettaglio punteggio per criterio»: vedi quanti punti hai ottenuto da ciascuna voce e come si compone il totale, anche riaprendo una valutazione gia' salvata. Risolti ticket #431 e #432."}