[FEAT] Rischi: matrice sempre visibile, rimosse viste FAIR e KRI (ticket #500 fase 1)

Ticket #500 (Simon Fattori) - semplificazione pagina Gestione Rischi:
- matrice 5x5 ora sempre visibile sotto la tabella (rimosso bottone 'Matrice' e il view-toggle)
- rimosse le viste 'Quantitativo (FAIR)' e 'KRI' con relativi bottoni, blocchi HTML e JS
- help.js allineato (rimosse sezioni FAIR/KRI), cache-buster help.js -> 20260727a
- version.json -> 1.25.2
Rimandato a fase 2 (richiede modello dati): rischi derivati dai requisiti LEX&ISO
+ codifica RSK-<misura>Com.N + rimozione bottoni 'Nuovo Rischio'/'AI Suggerisci'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-07-27 12:15:22 +02:00
co-authored by Claude Opus 4.8
parent 896663d24c
commit 7c711b727a
37 changed files with 46 additions and 295 deletions
+1 -1
View File
@@ -167,7 +167,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth check ─────────────────────────────────────────── // ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -90,7 +90,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth check ─────────────────────────────────────────── // ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -97,7 +97,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth check ─────────────────────────────────────────── // ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -1127,7 +1127,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar(); // = loadTopnavV3() loadSidebar(); // = loadTopnavV3()
+1 -1
View File
@@ -275,7 +275,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth check ─────────────────────────────────────────── // ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -382,7 +382,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init ───────────────────────────────────────── // ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -195,7 +195,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+1 -1
View File
@@ -144,7 +144,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -197,7 +197,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/competenze.js?v=20260627"></script> <script src="/js/competenze.js?v=20260627"></script>
<script> <script>
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } } function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
+1 -1
View File
@@ -103,7 +103,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/connettori-discovery.js?v=20260627"></script> <script src="/js/connettori-discovery.js?v=20260627"></script>
<script> <script>
if (typeof checkAuth === 'function' && !checkAuth()) { if (typeof checkAuth === 'function' && !checkAuth()) {
+1 -1
View File
@@ -160,7 +160,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */ /* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
+1 -1
View File
@@ -227,7 +227,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS. // ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS.
// Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ── // Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ──
+1 -1
View File
@@ -117,7 +117,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/dashboard.js?v=20260724a"></script> <script src="/js/dashboard.js?v=20260724a"></script>
<script> <script>
// ── Auth check ─────────────────────────────────────────── // ── Auth check ───────────────────────────────────────────
+1 -1
View File
@@ -1182,7 +1182,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// La guida è accessibile anche da non autenticati (utile per onboarding) // La guida è accessibile anche da non autenticati (utile per onboarding)
// ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend. // ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend.
+1 -1
View File
@@ -364,7 +364,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init ────────────────────────────────────────────── // ── Auth & Init ──────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -936,7 +936,7 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar(); // = loadTopnavV3() loadSidebar(); // = loadTopnavV3()
+1 -1
View File
@@ -165,7 +165,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+1 -1
View File
@@ -204,7 +204,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/isms.js?v=20260627v3"></script> <script src="/js/isms.js?v=20260627v3"></script>
<script> <script>
// Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring // Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring
+1 -19
View File
@@ -634,30 +634,12 @@ const HelpSystem = (function () {
'Ogni rischio registrato include: descrizione, categoria, beni coinvolti, owner e piano di trattamento.', 'Ogni rischio registrato include: descrizione, categoria, beni coinvolti, owner e piano di trattamento.',
'Lo stato di ogni rischio e\' tracciato nel tempo con lo storico delle modifiche.', 'Lo stato di ogni rischio e\' tracciato nel tempo con lo storico delle modifiche.',
'I rischi possono essere collegati a beni (inventario), incidenti e controlli di compliance.', 'I rischi possono essere collegati a beni (inventario), incidenti e controlli di compliance.',
'La <strong>Matrice Rischi</strong> si aggiorna in tempo reale: eliminando un rischio dalla vista dettaglio, la matrice si ricalcola automaticamente senza richiedere un refresh manuale.' 'La <strong>Matrice Rischi</strong> è <strong>sempre visibile</strong> sotto la tabella e si aggiorna in tempo reale: eliminando un rischio dalla vista dettaglio, la matrice si ricalcola automaticamente senza richiedere un refresh manuale.'
]
},
{
heading: 'Analisi Quantitativa FAIR (vista "Quantitativo")',
items: [
'Affianca alla matrice qualitativa la stima economica della <strong>Perdita Annua Attesa (ALE)</strong> in EUR.',
'Inserisci la frequenza eventi minaccia (TEF min/probabile/max), la vulnerabilita\' (0-1) e la magnitudo di perdita (min/probabile/max).',
'Il motore esegue una simulazione <strong>Monte Carlo</strong> e restituisce i percentili P10/P50/P90 + l\'istogramma della distribuzione delle perdite.',
'Il <strong>Registro Quantitativo</strong> aggrega l\'ALE di portafoglio per prioritizzare gli investimenti di sicurezza sul ritorno economico.'
]
},
{
heading: 'Key Risk Indicators - KRI (vista "KRI")',
items: [
'Definisci indicatori misurabili (es. % endpoint senza MFA, n. vulnerabilita\' critiche aperte) con soglie di warning e critica.',
'Lo stato semaforo (<strong>verde/ambra/rosso</strong>) e\' calcolato automaticamente confrontando il valore corrente con le soglie e la direzione (valori alti o bassi = peggio).',
'La dashboard KRI offre alla Direzione una vista sintetica dell\'esposizione al rischio in tempo reale.'
] ]
} }
], ],
references: [ references: [
'Art. 24 D.Lgs. 138/2024 - Approccio basato sulla valutazione dei rischi (risk-based approach) (recepisce l\'art. 21, par. 1, della Direttiva (UE) 2022/2555)', 'Art. 24 D.Lgs. 138/2024 - Approccio basato sulla valutazione dei rischi (risk-based approach) (recepisce l\'art. 21, par. 1, della Direttiva (UE) 2022/2555)',
'FAIR (Factor Analysis of Information Risk) - The Open Group, standard O-RA per il rischio quantitativo',
'Art. 24 D.Lgs. 138/2024 - Politiche di analisi dei rischi e di sicurezza dei sistemi informatici (recepisce l\'art. 21, par. 2, lett. a, della Direttiva (UE) 2022/2555)', 'Art. 24 D.Lgs. 138/2024 - Politiche di analisi dei rischi e di sicurezza dei sistemi informatici (recepisce l\'art. 21, par. 2, lett. a, della Direttiva (UE) 2022/2555)',
'Art. 24 D.Lgs. 138/2024 - Politiche e procedure per valutare l\'efficacia delle misure (recepisce l\'art. 21, par. 2, lett. f, della Direttiva (UE) 2022/2555)', 'Art. 24 D.Lgs. 138/2024 - Politiche e procedure per valutare l\'efficacia delle misure (recepisce l\'art. 21, par. 2, lett. f, della Direttiva (UE) 2022/2555)',
'Considerando 79 - Proporzionalita\' delle misure rispetto ai rischi' 'Considerando 79 - Proporzionalita\' delle misure rispetto ai rischi'
+1 -1
View File
@@ -180,7 +180,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/kb.js"></script> <script src="/js/kb.js"></script>
<script> <script>
// Gate auth + chrome come le altre pagine V3. // Gate auth + chrome come le altre pagine V3.
+1 -1
View File
@@ -152,7 +152,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+1 -1
View File
@@ -180,7 +180,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+1 -1
View File
@@ -142,7 +142,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth + chrome + i18n + help ────────────────────────── // ── Auth + chrome + i18n + help ──────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -156,7 +156,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/organigramma.js?v=20260627"></script> <script src="/js/organigramma.js?v=20260627"></script>
<script> <script>
// Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero // Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero
+1 -1
View File
@@ -345,7 +345,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init ───────────────────────────────────────── // ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -146,7 +146,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script src="/js/raci.js?v=20260627"></script> <script src="/js/raci.js?v=20260627"></script>
<script> <script>
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } } function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
+1 -1
View File
@@ -237,7 +237,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init ───────────────────────────────────────── // ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -162,7 +162,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+10 -241
View File
@@ -325,15 +325,9 @@
<div class="page-head"> <div class="page-head">
<div> <div>
<h1 data-i18n="risks.title">Gestione Rischi</h1> <h1 data-i18n="risks.title">Gestione Rischi</h1>
<p class="sub">Registro rischi, matrice 5×5, analisi quantitativa FAIR e KRI</p> <p class="sub">Registro rischi e matrice 5×5</p>
</div> </div>
<div class="content-header-actions" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap"> <div class="content-header-actions" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<div class="view-toggle" id="view-toggle">
<button class="active" onclick="switchView('table')">Tabella</button>
<button onclick="switchView('matrix')">Matrice</button>
<button onclick="switchView('fair')" data-i18n="risks.fair_tab">Quantitativo (FAIR)</button>
<button onclick="switchView('kri')" data-i18n="risks.kri_tab">KRI</button>
</div>
<button class="btn-ghost" id="btn-ai-suggest" onclick="aiSuggest()"> <button class="btn-ghost" id="btn-ai-suggest" onclick="aiSuggest()">
<i data-lucide="sparkles" class="ic"></i>AI Suggerisci <i data-lucide="sparkles" class="ic"></i>AI Suggerisci
</button> </button>
@@ -391,8 +385,8 @@
</div> </div>
</div> </div>
<!-- Matrix View --> <!-- Matrix View (sempre visibile) -->
<div id="view-matrix" class="hidden"> <div id="view-matrix" style="margin-top:16px;">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3>Matrice di Rischio 5x5</h3> <h3>Matrice di Rischio 5x5</h3>
@@ -436,65 +430,6 @@
</div> </div>
</div> </div>
<!-- FAIR Quantitative View -->
<div id="view-fair" class="hidden">
<div class="card" style="margin-bottom:16px;">
<div class="card-header">
<h3>Analisi Quantitativa del Rischio (FAIR)</h3>
<span class="text-muted" style="font-size:0.8rem;">Perdita Annua Attesa (ALE) in EUR — simulazione Monte Carlo</span>
</div>
<div class="card-body">
<div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:12px; margin-bottom:16px;">
<div><label for="fair-risk-select" class="form-label">Rischio</label>
<select class="form-select" id="fair-risk-select"><option value="">— seleziona —</option></select></div>
<div><label for="fair-tef-min" class="form-label">TEF min <small>(ev/anno)</small></label><input type="number" step="0.1" class="form-input" id="fair-tef-min" value="0.5"></div>
<div><label for="fair-tef-ml" class="form-label">TEF probabile</label><input type="number" step="0.1" class="form-input" id="fair-tef-ml" value="1"></div>
<div><label for="fair-tef-max" class="form-label">TEF max</label><input type="number" step="0.1" class="form-input" id="fair-tef-max" value="2"></div>
<div><label for="fair-vuln" class="form-label">Vulnerabilita <small>(0-1)</small></label><input type="number" step="0.05" min="0" max="1" class="form-input" id="fair-vuln" value="0.3"></div>
<div><label for="fair-lm-min" class="form-label">Perdita min <small>(EUR)</small></label><input type="number" step="1000" class="form-input" id="fair-lm-min" value="50000"></div>
<div><label for="fair-lm-ml" class="form-label">Perdita probabile</label><input type="number" step="1000" class="form-input" id="fair-lm-ml" value="300000"></div>
<div><label for="fair-lm-max" class="form-label">Perdita max</label><input type="number" step="1000" class="form-input" id="fair-lm-max" value="2000000"></div>
</div>
<button class="btn btn-primary" onclick="runFair()" id="btn-run-fair">Calcola ALE</button>
<div id="fair-result" class="hidden" style="margin-top:20px;">
<div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; margin-bottom:16px;">
<div class="stat-box"><div class="stat-label">ALE P10 (ottimistico)</div><div class="stat-value" id="fair-p10">—</div></div>
<div class="stat-box" style="border-color:var(--primary);"><div class="stat-label">ALE Mediana (P50)</div><div class="stat-value" id="fair-p50">—</div></div>
<div class="stat-box"><div class="stat-label">ALE P90 (pessimistico)</div><div class="stat-value" id="fair-p90">—</div></div>
<div class="stat-box"><div class="stat-label">ALE Media</div><div class="stat-value" id="fair-mean">—</div></div>
</div>
<div style="font-size:0.8rem; color:var(--gray-600); margin-bottom:8px;">Distribuzione delle perdite annue simulate (LEF medio <span id="fair-lef">—</span> eventi/anno):</div>
<div id="fair-histogram" style="display:flex; align-items:flex-end; gap:2px; height:160px; border-bottom:2px solid var(--gray-300); padding:4px;"></div>
<div id="fair-histogram-labels" style="display:flex; gap:2px; font-size:0.65rem; color:var(--gray-500); margin-top:4px;"></div>
</div>
</div>
</div>
<div class="card">
<div class="card-header"><h3>Registro Quantitativo</h3>
<span class="text-muted" style="font-size:0.8rem;">ALE di portafoglio: <strong id="fair-portfolio">—</strong></span></div>
<div class="table-container">
<table class="table-clickable"><thead><tr>
<th scope="col">Codice</th><th scope="col">Titolo</th><th scope="col">Categoria</th><th scope="col">ALE Min</th><th scope="col">ALE Mediana</th><th scope="col">ALE Max</th><th scope="col">ALE Media</th>
</tr></thead><tbody id="fair-register-body"><tr><td colspan="7" class="text-muted" style="text-align:center;padding:20px;">Nessun rischio quantificato</td></tr></tbody></table>
</div>
</div>
</div>
<!-- KRI Dashboard View -->
<div id="view-kri" class="hidden">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<div id="kri-summary" style="display:flex; gap:12px;"></div>
<button class="btn btn-primary" onclick="showKriModal()">+ Nuovo KRI</button>
</div>
<div class="card">
<div class="table-container">
<table class="table-clickable"><thead><tr>
<th scope="col">Stato</th><th scope="col">Indicatore</th><th scope="col">Categoria</th><th scope="col">Valore</th><th scope="col">Soglia Warning</th><th scope="col">Soglia Critica</th><th scope="col">Azioni</th>
</tr></thead><tbody id="kri-table-body"><tr><td colspan="7"><div class="spinner" style="margin:30px auto;"></div></td></tr></tbody></table>
</div>
</div>
</div>
<!-- Detail View --> <!-- Detail View -->
<div id="view-detail" class="hidden"></div> <div id="view-detail" class="hidden"></div>
</div> </div>
@@ -507,7 +442,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init ────────────────────────────────────────────── // ── Auth & Init ──────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
@@ -557,6 +492,7 @@
// ── Load ───────────────────────────────────────────────────── // ── Load ─────────────────────────────────────────────────────
loadRisks(); loadRisks();
loadMatrix();
async function loadRisks() { async function loadRisks() {
const params = {}; const params = {};
@@ -650,171 +586,6 @@
return `<span class="badge ${cls}">${escapeHtml(label)}</span>`; return `<span class="badge ${cls}">${escapeHtml(label)}</span>`;
} }
// ── View Switching ───────────────────────────────────────────
function switchView(view) {
currentView = view;
document.getElementById('view-table').classList.toggle('hidden', view !== 'table');
document.getElementById('view-matrix').classList.toggle('hidden', view !== 'matrix');
document.getElementById('view-fair').classList.toggle('hidden', view !== 'fair');
document.getElementById('view-kri').classList.toggle('hidden', view !== 'kri');
document.getElementById('view-detail').classList.toggle('hidden', true);
const btns = document.querySelectorAll('#view-toggle button');
btns.forEach(b => b.classList.remove('active'));
const idx = { table: 0, matrix: 1, fair: 2, kri: 3 }[view] ?? 0;
if (btns[idx]) btns[idx].classList.add('active');
if (view === 'matrix') loadMatrix();
if (view === 'fair') loadFair();
if (view === 'kri') loadKri();
}
// ── FAIR quantitativo ────────────────────────────────────────
const fmtEur = n => '€ ' + Number(n || 0).toLocaleString('it-IT', { maximumFractionDigits: 0 });
async function loadFair() {
// popola select rischi + registro
try {
const [risksRes, regRes] = await Promise.all([api.listRisks({ per_page: 200 }), api.getFairRegister()]);
const sel = document.getElementById('fair-risk-select');
if (risksRes.success) {
const risks = (risksRes.data && risksRes.data.items) || risksRes.data.risks || [];
sel.innerHTML = '<option value="">— seleziona rischio —</option>' +
risks.map(r => `<option value="${r.id}">${escapeHtml((r.risk_code ? r.risk_code + ' · ' : '') + r.title)}</option>`).join('');
}
if (regRes.success) renderFairRegister(regRes.data);
} catch (e) { showNotification('Errore caricamento FAIR', 'error'); }
}
function renderFairRegister(data) {
document.getElementById('fair-portfolio').textContent = fmtEur(data.portfolio_ale_mean);
const body = document.getElementById('fair-register-body');
const risks = data.risks || [];
if (!risks.length) { body.innerHTML = '<tr><td colspan="7" class="text-muted" style="text-align:center;padding:20px;">Nessun rischio quantificato. Calcola l\'ALE di un rischio qui sopra.</td></tr>'; return; }
body.innerHTML = risks.map(r => `<tr>
<td>${escapeHtml(r.risk_code || '—')}</td><td>${escapeHtml(r.title)}</td>
<td>${escapeHtml(CATEGORY_LABELS[r.category] || r.category || '')}</td>
<td>${fmtEur(r.ale_min)}</td><td><strong>${fmtEur(r.ale_ml)}</strong></td>
<td>${fmtEur(r.ale_max)}</td><td>${fmtEur(r.ale_mean)}</td></tr>`).join('');
}
async function runFair() {
const riskId = document.getElementById('fair-risk-select').value;
if (!riskId) { showNotification('Seleziona un rischio', 'warning'); return; }
const btn = document.getElementById('btn-run-fair');
setButtonLoading(btn, true);
const payload = {
tef_min: +document.getElementById('fair-tef-min').value,
tef_ml: +document.getElementById('fair-tef-ml').value,
tef_max: +document.getElementById('fair-tef-max').value,
vuln: +document.getElementById('fair-vuln').value,
lm_min: +document.getElementById('fair-lm-min').value,
lm_ml: +document.getElementById('fair-lm-ml').value,
lm_max: +document.getElementById('fair-lm-max').value,
};
try {
const res = await api.computeFair(riskId, payload);
if (res.success) {
renderFairResult(res.data.result);
showNotification('Analisi FAIR calcolata', 'success');
api.getFairRegister().then(r => { if (r.success) renderFairRegister(r.data); });
} else { showNotification(res.message || 'Errore calcolo FAIR', 'error'); }
} catch (e) { showNotification('Errore di connessione', 'error'); }
finally { setButtonLoading(btn, false); }
}
function renderFairResult(r) {
document.getElementById('fair-result').classList.remove('hidden');
document.getElementById('fair-p10').textContent = fmtEur(r.ale_min);
document.getElementById('fair-p50').textContent = fmtEur(r.ale_ml);
document.getElementById('fair-p90').textContent = fmtEur(r.ale_max);
document.getElementById('fair-mean').textContent = fmtEur(r.ale_mean);
document.getElementById('fair-lef').textContent = (r.lef_mean ?? 0).toFixed(3);
const bars = document.getElementById('fair-histogram');
const labels = document.getElementById('fair-histogram-labels');
const h = r.histogram || [];
const maxCount = Math.max(1, ...h.map(b => b.count));
bars.innerHTML = h.map(b => {
const pct = Math.round(b.count / maxCount * 100);
return `<div title="${fmtEur(b.from)} – ${fmtEur(b.to)}: ${b.count}" style="flex:1; background:var(--primary); opacity:.8; height:${pct}%; min-height:2px; border-radius:2px 2px 0 0;"></div>`;
}).join('');
labels.innerHTML = h.map((b, i) => i % 3 === 0 ? `<div style="flex:3; text-align:left;">${fmtEur(b.from)}</div>` : '').join('');
}
// ── KRI dashboard ────────────────────────────────────────────
const KRI_STATUS = { green: ['#22c55e', 'OK'], amber: ['#eab308', 'Attenzione'], red: ['#ef4444', 'Critico'], unknown: ['#9ca3af', 'N/D'] };
async function loadKri() {
try {
const res = await api.listKri();
if (res.success) renderKri(res.data);
else showNotification(res.message || 'Errore KRI', 'error');
} catch (e) { showNotification('Errore di connessione', 'error'); }
}
function renderKri(data) {
const sum = data.summary || {};
document.getElementById('kri-summary').innerHTML = ['red', 'amber', 'green', 'unknown'].map(s =>
`<div class="stat-box" style="border-left:4px solid ${KRI_STATUS[s][0]};"><div class="stat-value">${sum[s] || 0}</div><div class="stat-label">${KRI_STATUS[s][1]}</div></div>`).join('');
const body = document.getElementById('kri-table-body');
const kris = data.kris || [];
if (!kris.length) { body.innerHTML = '<tr><td colspan="7" class="text-muted" style="text-align:center;padding:20px;">Nessun KRI definito. Creane uno per monitorare gli indicatori di rischio.</td></tr>'; return; }
body.innerHTML = kris.map(k => {
const st = KRI_STATUS[k.status] || KRI_STATUS.unknown;
const val = k.current_value !== null ? `${(+k.current_value).toLocaleString('it-IT')} ${k.unit || ''}` : '—';
return `<tr>
<td><span style="display:inline-block;width:12px;height:12px;border-radius:50%;background:${st[0]};" title="${st[1]}"></span></td>
<td><strong>${escapeHtml(k.name)}</strong>${k.description ? `<br><small class="text-muted">${escapeHtml(k.description)}</small>` : ''}</td>
<td>${escapeHtml(CATEGORY_LABELS[k.category] || k.category || '')}</td>
<td><strong>${escapeHtml(val)}</strong></td>
<td>${k.threshold_warning ?? '—'}</td><td>${k.threshold_critical ?? '—'}</td>
<td><button class="btn btn-sm btn-secondary" onclick='showKriModal(${JSON.stringify(k)})'>Modifica</button></td></tr>`;
}).join('');
}
function showKriModal(kri = null) {
const k = kri || {};
const body = `
<div class="form-group"><label for="kri-name" class="form-label">Nome indicatore *</label><input class="form-input" id="kri-name" value="${escapeHtml(k.name || '')}"></div>
<div class="form-group"><label for="kri-desc" class="form-label">Descrizione</label><input class="form-input" id="kri-desc" value="${escapeHtml(k.description || '')}"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<div class="form-group"><label for="kri-cat" class="form-label">Categoria</label><select class="form-select" id="kri-cat">
${['cyber', 'operational', 'compliance', 'supply_chain', 'physical', 'human'].map(c => `<option value="${c}" ${k.category === c ? 'selected' : ''}>${CATEGORY_LABELS[c] || c}</option>`).join('')}</select></div>
<div class="form-group"><label for="kri-unit" class="form-label">Unita (%, count, EUR...)</label><input class="form-input" id="kri-unit" value="${escapeHtml(k.unit || '')}"></div>
<div class="form-group"><label for="kri-cur" class="form-label">Valore corrente</label><input type="number" step="any" class="form-input" id="kri-cur" value="${k.current_value ?? ''}"></div>
<div class="form-group"><label for="kri-target" class="form-label">Obiettivo</label><input type="number" step="any" class="form-input" id="kri-target" value="${k.target_value ?? ''}"></div>
<div class="form-group"><label for="kri-warn" class="form-label">Soglia Warning</label><input type="number" step="any" class="form-input" id="kri-warn" value="${k.threshold_warning ?? ''}"></div>
<div class="form-group"><label for="kri-crit" class="form-label">Soglia Critica</label><input type="number" step="any" class="form-input" id="kri-crit" value="${k.threshold_critical ?? ''}"></div>
<div class="form-group" style="grid-column:span 2;"><label for="kri-dir" class="form-label">Direzione</label><select class="form-select" id="kri-dir">
<option value="higher_worse" ${k.direction === 'higher_worse' ? 'selected' : ''}>Valori alti = peggio</option>
<option value="lower_worse" ${k.direction === 'lower_worse' ? 'selected' : ''}>Valori bassi = peggio</option></select></div>
</div>`;
showModal(k.id ? 'Modifica KRI' : 'Nuovo KRI', body, {
footer: `<button class="btn btn-secondary" onclick="closeModal()">Annulla</button>
<button class="btn btn-primary" onclick="saveKri(${k.id || 'null'})">Salva</button>`
});
}
async function saveKri(id) {
const data = {
name: document.getElementById('kri-name').value.trim(),
description: document.getElementById('kri-desc').value.trim(),
category: document.getElementById('kri-cat').value,
unit: document.getElementById('kri-unit').value.trim(),
current_value: document.getElementById('kri-cur').value,
target_value: document.getElementById('kri-target').value,
threshold_warning: document.getElementById('kri-warn').value,
threshold_critical: document.getElementById('kri-crit').value,
direction: document.getElementById('kri-dir').value,
};
if (!data.name) { showNotification('Nome obbligatorio', 'warning'); return; }
try {
const res = id ? await api.updateKri(id, data) : await api.createKri(data);
if (res.success) { closeModal(); showNotification('KRI salvato', 'success'); loadKri(); }
else showNotification(res.message || 'Errore salvataggio', 'error');
} catch (e) { showNotification('Errore di connessione', 'error'); }
}
// ── Matrix ─────────────────────────────────────────────────── // ── Matrix ───────────────────────────────────────────────────
async function loadMatrix() { async function loadMatrix() {
try { try {
@@ -897,8 +668,6 @@
function renderDetail(risk) { function renderDetail(risk) {
document.getElementById('view-table').classList.add('hidden'); document.getElementById('view-table').classList.add('hidden');
document.getElementById('view-matrix').classList.add('hidden'); document.getElementById('view-matrix').classList.add('hidden');
document.getElementById('view-fair').classList.add('hidden');
document.getElementById('view-kri').classList.add('hidden');
const container = document.getElementById('view-detail'); const container = document.getElementById('view-detail');
container.classList.remove('hidden'); container.classList.remove('hidden');
@@ -1070,9 +839,9 @@
function backToList() { function backToList() {
document.getElementById('view-detail').classList.add('hidden'); document.getElementById('view-detail').classList.add('hidden');
// Ripristina la vista corrente tra le 4 (table/matrix/fair/kri), non solo table/matrix. // Tabella e matrice sono entrambe sempre visibili.
const view = ['table', 'matrix', 'fair', 'kri'].includes(currentView) ? currentView : 'table'; document.getElementById('view-table').classList.remove('hidden');
document.getElementById('view-' + view).classList.remove('hidden'); document.getElementById('view-matrix').classList.remove('hidden');
} }
// ── Create/Edit Risk Modal ─────────────────────────────────── // ── Create/Edit Risk Modal ───────────────────────────────────
@@ -1227,6 +996,7 @@
closeModal(); closeModal();
showNotification(riskId ? 'Rischio aggiornato' : 'Rischio creato con successo', 'success'); showNotification(riskId ? 'Rischio aggiornato' : 'Rischio creato con successo', 'success');
loadRisks(); loadRisks();
loadMatrix();
// If detail view is visible, reload detail // If detail view is visible, reload detail
if (!document.getElementById('view-detail').classList.contains('hidden')) { if (!document.getElementById('view-detail').classList.contains('hidden')) {
viewRiskDetail(riskId || result.data.id); viewRiskDetail(riskId || result.data.id);
@@ -1261,10 +1031,9 @@
if (result.success) { if (result.success) {
closeModal(); closeModal();
showNotification('Rischio eliminato', 'success'); showNotification('Rischio eliminato', 'success');
const wasMatrix = currentView === 'matrix';
backToList(); backToList();
loadRisks(); loadRisks();
if (wasMatrix) loadMatrix(); loadMatrix();
} else { } else {
showNotification(result.message || 'Errore nella eliminazione', 'error'); showNotification(result.message || 'Errore nella eliminazione', 'error');
} }
+1 -1
View File
@@ -740,7 +740,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth check ─────────────────────────────────────────── // ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -220,7 +220,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+1 -1
View File
@@ -215,7 +215,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
'use strict'; 'use strict';
/* /*
+1 -1
View File
@@ -547,7 +547,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init ───────────────────────────────────────── // ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -195,7 +195,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth & Init (ordine V3) ───────────────────────────── // ── Auth & Init (ordine V3) ─────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -1 +1 @@
{"version": "1.25.1", "build": "2026-07-24-v1.25.1", "date": "2026-07-24", "changelog": "Dashboard - card 'Documenti istituzionali' ora funzionante: puoi aggiungere documenti (ognuno con una descrizione obbligatoria) e trovi sempre presenti le 5 voci standard Codice Etico, Mission, Vision, Statuto e Piano Sanzionatorio da compilare."} {"version": "1.25.2", "build": "2026-07-27-v1.25.2", "date": "2026-07-27", "changelog": "Gestione Rischi - pagina semplificata: la matrice 5x5 e' ora sempre visibile sotto la tabella dei rischi (niente piu' bottone 'Matrice'); rimosse le viste 'Quantitativo (FAIR)' e 'KRI' e i relativi bottoni. Prossimo passo: i rischi verranno derivati dai requisiti LEX&ISO con codifica RSK-<misura>."}
+1 -1
View File
@@ -263,7 +263,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar(); loadSidebar();
+1 -1
View File
@@ -148,7 +148,7 @@
<script src="/vendor/lucide/lucide.min.js"></script> <script src="/vendor/lucide/lucide.min.js"></script>
<script src="/js/topnav-v3.js?v=20260627t"></script> <script src="/js/topnav-v3.js?v=20260627t"></script>
<script src="/js/i18n.js?v=20260627p"></script> <script src="/js/i18n.js?v=20260627p"></script>
<script src="/js/help.js?v=20260627n"></script> <script src="/js/help.js?v=20260727a"></script>
<script> <script>
// ── Auth + chrome + i18n + help ────────────────────────────── // ── Auth + chrome + i18n + help ──────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated'); if (!checkAuth()) throw new Error('Not authenticated');