[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:
co-authored by
Claude Opus 4.8
parent
896663d24c
commit
7c711b727a
@@ -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');
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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');
|
||||||
|
|||||||
@@ -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';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
@@ -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) {} } }
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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. ──
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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');
|
||||||
|
|||||||
@@ -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
@@ -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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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';
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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
@@ -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>."}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user