[FEAT] Gestione Rischi: tabella 12 colonne (inerente+residuo) — integrazione prototipo "Modulo Azioni" (Simon)
Integra la spec di Simon per la pagina Rischi con le correzioni normative (parere nis2-expert + iso-27001-expert) applicate come eccezioni motivate: - 12 colonne: Codice (link → Misure e Requisiti), Descrizione (RISCHIO DEFAULT = cfg_nis2_rischi.risk_descr), VALUTAZIONE RISCHIO INERENTE (P/I/PxI), Alert, Rischio Inerente, VALUTAZIONE RISCHIO RESIDUO (P/I/PxI), Esito residuo, Rischio Residuo. - Editing INLINE di Probabilità/Impatto (1-5), salvataggio automatico. - Alert a bande (15-25 Prioritaria / 9-14 Critica / 3-8 Necessaria / 1-2 Suggerita) → popup azioni + "crea azione" (tabella requisito_actions, seme Modulo Azioni). - Eccezione #1: il rischio RESIDUO NON aggiorna in automatico lo STATO DI CONFORMITÀ del requisito (piani distinti). Col.11 "Esito residuo" = flag Rivalutare/Adeguato; conformità esplicita. - Eccezione #3: banda 13-14 inclusa in "Critica" (9-14). "Danno €" → "Impatto" (1-5). Soglie = metodo interno (ISO 27001 §6.1.2), non obbligo (help/UI lo dichiarano). Backend: mig 067; derivedList (+risk_descr +residuo), setState (+residuo), requisitoActions list/create. help.js + i18n IT/EN. Cache-buster api/i18n/help 20260730a. Prossima mig=068. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cf224480cc
commit
8a0bc5ca39
@@ -70,7 +70,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
|
||||
<script src="js/api.js?v=20260729b"></script>
|
||||
<script src="js/api.js?v=20260730a"></script>
|
||||
<script src="js/common.js?v=20260729b"></script>
|
||||
<script src="js/common-bi.js?v=20260625d"></script>
|
||||
<script>
|
||||
|
||||
@@ -162,12 +162,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -85,12 +85,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -92,12 +92,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -1122,12 +1122,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar(); // = loadTopnavV3()
|
||||
|
||||
@@ -270,12 +270,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica
|
||||
pagina (wizard GAP) resta inline: usa gli stessi id/funzioni di prima. -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+3
-3
@@ -377,12 +377,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -190,12 +190,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help. La logica del
|
||||
calendario (aggregatore read-only) resta inline e usa gli stessi id/funzioni. -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -197,12 +197,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide → topnav-v3.js (chrome) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
|
||||
@@ -192,12 +192,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts V3. competenze.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/competenze.js?v=20260627"></script>
|
||||
<script>
|
||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
@@ -98,12 +98,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/connettori-discovery.js?v=20260627"></script>
|
||||
<script>
|
||||
if (typeof checkAuth === 'function' && !checkAuth()) {
|
||||
|
||||
@@ -155,12 +155,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
|
||||
|
||||
@@ -222,12 +222,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS.
|
||||
// Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ──
|
||||
|
||||
@@ -112,12 +112,12 @@
|
||||
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
||||
i18n → help → dashboard.js -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/dashboard.js?v=20260724a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della forgot-password.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260729b"></script>
|
||||
<script src="js/api.js?v=20260730a"></script>
|
||||
<script>
|
||||
const form = document.getElementById('forgot-form');
|
||||
const err = document.getElementById('err');
|
||||
|
||||
+3
-3
@@ -1177,12 +1177,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// La guida è accessibile anche da non autenticati (utile per onboarding)
|
||||
// ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend.
|
||||
|
||||
@@ -359,12 +359,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -344,6 +344,8 @@ $actionMap = [
|
||||
'GET:kri' => 'listKri', // P2: dashboard KRI
|
||||
'POST:kri' => 'createKri',
|
||||
'PUT:kri/{subId}' => 'updateKri',
|
||||
'GET:requisitoActions' => 'requisitoActionsList', // #500/Modulo Azioni: azioni sul rischio-da-requisito
|
||||
'POST:requisitoActions' => 'requisitoActionsCreate',
|
||||
],
|
||||
|
||||
// ── IncidentController ──────────────────────────
|
||||
|
||||
@@ -931,12 +931,12 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar(); // = loadTopnavV3()
|
||||
|
||||
@@ -160,12 +160,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+3
-3
@@ -199,12 +199,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → isms.js.
|
||||
isms.js si auto-inizializza su DOMContentLoaded (checkAuth/I18n/loadSidebar/Help/render). -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/isms.js?v=20260627v3"></script>
|
||||
<script>
|
||||
// Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring
|
||||
|
||||
@@ -451,6 +451,8 @@ class NIS2API {
|
||||
deleteRisk(id) { return this.del(`/risks/${id}`); }
|
||||
getRiskMatrix() { return this.get('/risks/matrix'); }
|
||||
derivedRisks() { return this.get('/risks/derived'); }
|
||||
requisitoActions(requisitoId) { return this.get('/risks/requisitoActions?requisito_id=' + encodeURIComponent(requisitoId)); }
|
||||
createRequisitoAction(d) { return this.post('/risks/requisitoActions', d || {}); }
|
||||
aiSuggestRisks() { return this.post('/risks/ai-suggest', {}); }
|
||||
// FAIR quantitativo + KRI (P2)
|
||||
computeFair(id, data) { return this.post(`/risks/${id}/fair`, data); }
|
||||
|
||||
+6
-3
@@ -606,9 +606,12 @@ const HelpSystem = (function () {
|
||||
items: [
|
||||
'La <strong>prima tabella</strong> elenca i rischi <strong>derivati dai requisiti</strong> del framework di sicurezza: ogni voce e\' il <strong>rischio di non conformita\'</strong> legato al mancato o parziale soddisfacimento di un requisito, <strong>non il requisito in se\'</strong>. E\' un elenco in <strong>sola lettura</strong>.',
|
||||
'Il codice <strong>RSK-‹misura›Com.‹n›</strong> e\' una <strong>codifica interna di NIS2 Agile</strong>; ‹misura› (es. GV.OC-04) e\' il codice della misura del Framework Nazionale richiamato dalla Determinazione ACN 164179/2025. <strong>Non</strong> e\' un codice o numero ufficiale ACN.',
|
||||
'La colonna <strong>Conformita\'</strong> mostra lo stato di valutazione del requisito (Da valutare, Non conforme, Parziale, Conforme). La tabella elenca <strong>solo i requisiti applicabili alla tua classe</strong> (importante → Allegato 1, essenziale → Allegato 2 della Determinazione ACN 164179/2025): quelli non pertinenti non vengono mostrati. Se l\'elenco e\' lungo, il riquadro <strong>scorre verticalmente</strong> con l\'intestazione fissa.',
|
||||
'La seconda tabella, <strong>Rischi aggiuntivi (approccio multirischio / all-hazard)</strong>, permette ai <strong>profili amministrativi</strong> (org_admin, super_admin) di aggiungere rischi non coperti da un singolo requisito (minacce fisiche, catena di fornitura, eventi naturali): copre l\'approccio multirischio richiesto dall\'art. 24 D.Lgs. 138/2024.',
|
||||
'I profili <strong>Amministratore</strong>, <strong>Compliance Manager</strong> e <strong>super admin</strong> possono <strong>valutare</strong> ogni rischio-da-requisito con il pulsante <strong>Valuta</strong>: si impostano lo <strong>stato di conformità</strong>, la <strong>Probabilità</strong> e l\'<strong>Impatto</strong> (scala 1–5). La <strong>Probabilità</strong> è la probabilità che la minaccia si concretizzi <strong>sfruttando la carenza</strong> del requisito; l\'<strong>Impatto</strong> è l\'impatto della <strong>non conformità o del conseguente incidente</strong> — non la "probabilità del requisito". Il punteggio Rischio (Probabilità × Impatto) compare nella tabella e alimenta la matrice.'
|
||||
'La tabella ha <strong>12 colonne</strong>: <strong>Codice rischio</strong> (cliccabile, apre la voce in Misure e Requisiti), <strong>Descrizione rischio</strong> (il rischio di default associato al requisito nel framework), e due valutazioni affiancate — <strong>RISCHIO INERENTE</strong> (prima del trattamento) e <strong>RISCHIO RESIDUO</strong> (dopo il trattamento). Ogni valutazione ha Probabilità, Impatto e Valutazione (= Probabilità × Impatto, scala 1–25).',
|
||||
'I profili <strong>Amministratore</strong>, <strong>Compliance Manager</strong> e <strong>super admin</strong> compilano <strong>direttamente in tabella</strong> Probabilità e Impatto (1–5): la Valutazione si calcola da sola e il salvataggio è automatico. La <strong>Probabilità</strong> è la probabilità che la minaccia sfrutti la carenza del requisito; l\'<strong>Impatto</strong> (scala 1–5, non euro) è la severità della non conformità o del conseguente incidente.',
|
||||
'La colonna <strong>Alert</strong> compare in base al punteggio inerente: <em>Azione Prioritaria</em> (15–25), <em>Azione Critica</em> (9–14), <em>Azione Necessaria</em> (3–8), <em>Azione Suggerita</em> (1–2). Le soglie sono una <strong>scelta metodologica</strong> dell\'organizzazione (rif. ISO/IEC 27001 §6.1.2), <strong>non un obbligo di legge</strong>. Cliccando l\'Alert si apre l\'elenco delle azioni passate su quel rischio e si può <strong>creare una nuova azione</strong>.',
|
||||
'La colonna <strong>Esito residuo</strong> segnala se il rischio residuo va <strong>rivalutato</strong> (punteggio 9–25) o è <strong>adeguato</strong> (1–8). <strong>Importante</strong>: il punteggio di rischio residuo <strong>non</strong> determina automaticamente lo <em>stato di conformità</em> del requisito. La <strong>conformità</strong> dipende dall\'attuazione della misura e dalle evidenze e si aggiorna in modo esplicito dal link "stato" nella colonna Rischio Residuo. Sono due piani distinti (punteggio di rischio ≠ conformità al requisito).',
|
||||
'La tabella elenca <strong>solo i requisiti applicabili alla tua classe</strong> (importante → Allegato 1, essenziale → Allegato 2 della Determinazione ACN 164179/2025). Se l\'elenco e\' lungo, il riquadro <strong>scorre verticalmente</strong> con l\'intestazione fissa.',
|
||||
'La seconda tabella, <strong>Rischi aggiuntivi (approccio multirischio / all-hazard)</strong>, permette ai <strong>profili amministrativi</strong> di aggiungere rischi non coperti da un singolo requisito (minacce fisiche, catena di fornitura, eventi naturali): copre l\'approccio multirischio richiesto dall\'art. 24 D.Lgs. 138/2024.'
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+8
-2
@@ -171,8 +171,14 @@ const I18n = (function () {
|
||||
'risks.avoid': { it: 'Evitare', en: 'Avoid' },
|
||||
'risks.derived_heading': { it: 'Rischi dai requisiti del framework di sicurezza', en: 'Risks from security framework requirements' },
|
||||
'risks.derived_code_legend': { it: 'RSK-‹misura›Com.‹n› è una codifica interna di NIS2 Agile; ‹misura› (es. GV.OC-04) è il codice della misura del Framework Nazionale richiamato dalla Determinazione ACN 164179/2025. Elenco in sola lettura.', en: 'RSK-‹measure›Com.‹n› is an internal NIS2 Agile code; ‹measure› (e.g. GV.OC-04) is the National Framework measure code referenced by ACN Determination 164179/2025. Read-only list.' },
|
||||
'risks.col_code': { it: 'Codice', en: 'Code' },
|
||||
'risks.col_risk': { it: 'Rischio', en: 'Risk' },
|
||||
'risks.col_code': { it: 'Codice rischio', en: 'Risk code' },
|
||||
'risks.col_risk': { it: 'Descrizione rischio', en: 'Risk description' },
|
||||
'risks.grp_inherent': { it: 'VALUTAZIONE RISCHIO INERENTE', en: 'INHERENT RISK ASSESSMENT' },
|
||||
'risks.grp_residual': { it: 'VALUTAZIONE RISCHIO RESIDUO', en: 'RESIDUAL RISK ASSESSMENT' },
|
||||
'risks.col_alert': { it: 'Alert', en: 'Alert' },
|
||||
'risks.col_inherent': { it: 'Rischio Inerente', en: 'Inherent risk' },
|
||||
'risks.col_res_outcome': { it: 'Esito residuo', en: 'Residual outcome' },
|
||||
'risks.col_residual': { it: 'Rischio Residuo', en: 'Residual risk' },
|
||||
'risks.col_measure': { it: 'Misura', en: 'Measure' },
|
||||
'risks.col_conformity': { it: 'Conformità', en: 'Conformity' },
|
||||
'risks.col_evaluation': { it: 'Valutazione', en: 'Evaluation' },
|
||||
|
||||
+3
-3
@@ -175,12 +175,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → kb.js -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/kb.js"></script>
|
||||
<script>
|
||||
// Gate auth + chrome come le altre pagine V3.
|
||||
|
||||
@@ -515,7 +515,7 @@ body { background: var(--bg-main); }
|
||||
|
||||
<div id="toast"></div>
|
||||
|
||||
<script src="js/api.js?v=20260729b"></script>
|
||||
<script src="js/api.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ══════════════════════════════════════════════════════
|
||||
// CONFIG
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della login attuale: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260729b"></script>
|
||||
<script src="js/api.js?v=20260730a"></script>
|
||||
<script src="js/common.js?v=20260729b"></script>
|
||||
<script>
|
||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||
|
||||
@@ -147,12 +147,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -175,12 +175,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -137,12 +137,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth + chrome + i18n + help ──────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -494,7 +494,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260729b"></script>
|
||||
<script src="js/api.js?v=20260730a"></script>
|
||||
<script src="js/common.js?v=20260729b"></script>
|
||||
<script>
|
||||
// ── Auth check ──────────────────────────────────────────────────
|
||||
|
||||
@@ -151,12 +151,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → organigramma.js.
|
||||
organigramma.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/organigramma.js?v=20260627"></script>
|
||||
<script>
|
||||
// Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero
|
||||
|
||||
@@ -340,12 +340,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+3
-3
@@ -141,12 +141,12 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts V3. raci.js si auto-inizializza su DOMContentLoaded (checkAuth/loadSidebar/I18n/Help). -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script src="/js/raci.js?v=20260627"></script>
|
||||
<script>
|
||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
@@ -268,7 +268,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della register.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260729b"></script>
|
||||
<script src="js/api.js?v=20260730a"></script>
|
||||
<script src="js/common.js?v=20260729b"></script>
|
||||
<script>
|
||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||
|
||||
+3
-3
@@ -232,12 +232,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -157,12 +157,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts V3 (ordine ESATTO come dashboard.html) -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+243
-62
@@ -306,10 +306,44 @@
|
||||
}
|
||||
#view-derived .table-container.scroll-box thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--gray-50);
|
||||
z-index: 2;
|
||||
}
|
||||
/* Tabella 12 colonne (spec Simon): 2 righe di intestazione, gruppi inerente/residuo */
|
||||
table.derived-12 thead tr:first-child th { top: 0; }
|
||||
table.derived-12 thead tr:nth-child(2) th { top: 30px; }
|
||||
table.derived-12 th, table.derived-12 td {
|
||||
font-size: 0.8rem; padding: 6px 8px; text-align: center; vertical-align: middle;
|
||||
}
|
||||
table.derived-12 th.grp {
|
||||
font-size: 0.66rem; letter-spacing: 0.04em; font-weight: 700; color: #fff;
|
||||
}
|
||||
table.derived-12 th.grp-inh { background: #b45309; }
|
||||
table.derived-12 th.grp-res { background: #0e7490; }
|
||||
table.derived-12 td.cell-code, table.derived-12 td.cell-desc { text-align: left; }
|
||||
.rk-eval { display:inline-flex; align-items:center; justify-content:center; min-width:26px; height:22px;
|
||||
padding:0 6px; border-radius:6px; font-weight:800; font-size:0.78rem; color:#fff; }
|
||||
.rk-low { background:#16a34a; } .rk-med { background:#d97706; }
|
||||
.rk-high { background:#ea580c; } .rk-crit { background:#dc2626; }
|
||||
.rk-sel { width:52px; padding:2px 4px; font-size:0.78rem; border:1px solid var(--gray-300);
|
||||
border-radius:6px; background:#fff; text-align:center; }
|
||||
.rk-sel:focus { border-color: var(--primary); outline:none; }
|
||||
.alert-btn { border:0; border-radius:16px; padding:4px 10px; font-size:0.7rem; font-weight:800;
|
||||
letter-spacing:.02em; cursor:pointer; white-space:nowrap; }
|
||||
.alert-pri { background:#dc2626; color:#fff; }
|
||||
.alert-cri { background:#f87171; color:#fff; }
|
||||
.alert-nec { background:#f59e0b; color:#fff; }
|
||||
.alert-sug { background:#16a34a; color:#fff; }
|
||||
.flag-riv { display:inline-block; padding:3px 9px; border-radius:14px; font-size:0.7rem; font-weight:800;
|
||||
background:#fee2e2; color:#b91c1c; }
|
||||
.flag-adeg { display:inline-block; padding:3px 9px; border-radius:14px; font-size:0.7rem; font-weight:800;
|
||||
background:#dcfce7; color:#15803d; }
|
||||
.rk-valutato { font-size:0.72rem; font-weight:700; color:#15803d; }
|
||||
.rk-empty { color: var(--gray-300); }
|
||||
.act-row { display:flex; align-items:flex-start; gap:10px; padding:10px 4px; border-bottom:1px solid var(--gray-100); }
|
||||
.act-row .act-when { font-size:0.72rem; color:var(--gray-500); white-space:nowrap; min-width:74px; }
|
||||
.act-row .act-desc { flex:1; font-size:0.82rem; color:var(--gray-800); }
|
||||
.act-row .act-st { font-size:0.68rem; font-weight:700; text-transform:uppercase; color:var(--gray-500); }
|
||||
</style>
|
||||
<!-- PWA:start -->
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
@@ -367,23 +401,30 @@
|
||||
</p>
|
||||
<div class="card">
|
||||
<div class="table-container scroll-box">
|
||||
<table>
|
||||
<table class="derived-12">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" data-i18n="risks.col_code">Codice</th>
|
||||
<th scope="col" data-i18n="risks.col_risk">Rischio</th>
|
||||
<th scope="col" data-i18n="risks.col_measure">Misura</th>
|
||||
<th scope="col" data-i18n="risks.col_conformity">Conformità</th>
|
||||
<th scope="col" rowspan="2" data-i18n="risks.col_code">Codice rischio</th>
|
||||
<th scope="col" rowspan="2" data-i18n="risks.col_risk">Descrizione rischio</th>
|
||||
<th scope="colgroup" colspan="3" class="grp grp-inh" data-i18n="risks.grp_inherent">VALUTAZIONE RISCHIO INERENTE</th>
|
||||
<th scope="col" rowspan="2" data-i18n="risks.col_alert">Alert</th>
|
||||
<th scope="col" rowspan="2" data-i18n="risks.col_inherent">Rischio Inerente</th>
|
||||
<th scope="colgroup" colspan="3" class="grp grp-res" data-i18n="risks.grp_residual">VALUTAZIONE RISCHIO RESIDUO</th>
|
||||
<th scope="col" rowspan="2" data-i18n="risks.col_res_outcome">Esito residuo</th>
|
||||
<th scope="col" rowspan="2" data-i18n="risks.col_residual">Rischio Residuo</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col" data-i18n="risks.col_probability">Probabilità</th>
|
||||
<th scope="col" data-i18n="risks.col_impact">Impatto</th>
|
||||
<th scope="col" data-i18n="risks.col_evaluation">Valutazione</th>
|
||||
<th scope="col" data-i18n="risks.col_probability">Probabilità</th>
|
||||
<th scope="col" data-i18n="risks.col_impact">Impatto</th>
|
||||
<th scope="col" data-i18n="risks.col_risk_level">Rischio (P×I)</th>
|
||||
<th scope="col" data-i18n="risks.col_evaluation">Valutazione</th>
|
||||
<th scope="col" id="derived-th-actions" data-i18n="risks.col_actions" style="display:none;">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="derived-table-body">
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<td colspan="12">
|
||||
<div class="spinner" style="margin:40px auto;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -499,12 +540,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
@@ -634,16 +675,44 @@
|
||||
return 'score-low';
|
||||
}
|
||||
|
||||
// ── Livello del punteggio 1-25 (classe colore del riquadro "Valutazione") ──
|
||||
function evalClass(p) {
|
||||
if (p >= 15) return 'rk-crit';
|
||||
if (p >= 9) return 'rk-high';
|
||||
if (p >= 3) return 'rk-med';
|
||||
return 'rk-low';
|
||||
}
|
||||
// ── Banda ALERT sul punteggio INERENTE (prodotto P×I). Eccezione #3: la banda
|
||||
// 13-14 (scoperta nella spec) è inclusa in "Critica" → 9-14 Critica, 15-25 Prioritaria. ──
|
||||
function alertBand(p) {
|
||||
if (p == null) return null;
|
||||
if (p >= 15) return { cls: 'alert-pri', label: 'Azione Prioritaria' };
|
||||
if (p >= 9) return { cls: 'alert-cri', label: 'Azione Critica' };
|
||||
if (p >= 3) return { cls: 'alert-nec', label: 'Azione Necessaria' };
|
||||
return { cls: 'alert-sug', label: 'Azione Suggerita' };
|
||||
}
|
||||
// ── Esito del RESIDUO (col.11). Eccezione #1: NON è un verdetto di conformità,
|
||||
// è un flag di rivalutazione. ≥9 → "Rivalutare"; 1-8 → "Adeguato". ──
|
||||
function residualOutcome(p) {
|
||||
if (p == null) return null;
|
||||
return p >= 9
|
||||
? { cls: 'flag-riv', label: 'Rivalutare' }
|
||||
: { cls: 'flag-adeg', label: 'Adeguato' };
|
||||
}
|
||||
function scoreSel(id, val, editable) {
|
||||
if (!editable) return (val >= 1 && val <= 5) ? (val + '/5') : '<span class="rk-empty">—</span>';
|
||||
let o = `<option value="">—</option>`;
|
||||
for (let v = 1; v <= 5; v++) o += `<option value="${v}" ${v == val ? 'selected' : ''}>${v}</option>`;
|
||||
return `<select class="rk-sel" id="${id}" onchange="onScoreChange(${id.split('-')[1]})">${o}</select>`;
|
||||
}
|
||||
|
||||
function renderDerivedTable(rows) {
|
||||
const tbody = document.getElementById('derived-table-body');
|
||||
const colspan = 8 + (canEvaluate ? 1 : 0);
|
||||
// Ticket #501/2: mostra SOLO i requisiti applicabili alla classe del soggetto
|
||||
// (importante → Allegato 1, essenziale → Allegato 2 della Det. ACN 164179/2025).
|
||||
// Le righe non applicabili non vengono più elencate (prima erano solo attenuate).
|
||||
// Ticket #501/2: mostra SOLO i requisiti applicabili alla classe del soggetto.
|
||||
rows = (rows || []).filter(isDerivedApplicable);
|
||||
if (!rows || rows.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="${colspan}">
|
||||
<tr><td colspan="12">
|
||||
<div class="empty-state">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944z" clip-rule="evenodd"/></svg>
|
||||
<h4>Nessun requisito applicabile</h4>
|
||||
@@ -655,66 +724,178 @@
|
||||
|
||||
let html = '';
|
||||
rows.forEach(r => {
|
||||
const applicable = isDerivedApplicable(r);
|
||||
const st = DERIVED_STATUS_BADGE[r.stato] || DERIVED_STATUS_BADGE.da_valutare;
|
||||
const reqId = r.requisito_id;
|
||||
const editable = canEvaluate && r.stato !== 'non_applicabile';
|
||||
const measureCode = r.misura_code || '-';
|
||||
const reqCode = r.requisito_code || '';
|
||||
const reqDescr = r.requisito_descr || '';
|
||||
const measureDescr = r.misura_descr || '';
|
||||
const evalVal = (r.valutazione_rischio !== null && r.valutazione_rischio !== undefined && r.valutazione_rischio !== '')
|
||||
? escapeHtml(String(r.valutazione_rischio)) : '—';
|
||||
const rowStyle = applicable ? '' : 'style="opacity:0.5;"';
|
||||
const riskTooltip = `Rischio derivante dal mancato o parziale soddisfacimento del requisito ${measureCode} ${reqCode}`.trim();
|
||||
const riskDescr = r.risk_descr || r.requisito_descr || '';
|
||||
const st = DERIVED_STATUS_BADGE[r.stato] || DERIVED_STATUS_BADGE.da_valutare;
|
||||
const riskTooltip = `Rischio di non conformità legato al requisito ${measureCode} ${reqCode}`.trim();
|
||||
|
||||
// Colonna "Rischio": etichetta + requisito come OGGETTO del rischio (mai il requisito nudo).
|
||||
const conformityCell = applicable
|
||||
? `<span class="badge ${st.cls}">${escapeHtml(st.label)}</span>`
|
||||
: `<span class="badge badge-neutral">Non applicabile alla tua classe</span>`;
|
||||
// INERENTE
|
||||
const iL = (r.likelihood >= 1 && r.likelihood <= 5) ? parseInt(r.likelihood) : null;
|
||||
const iI = (r.impact >= 1 && r.impact <= 5) ? parseInt(r.impact) : null;
|
||||
const iP = (iL != null && iI != null) ? iL * iI : null;
|
||||
// RESIDUO
|
||||
const rL = (r.residual_likelihood >= 1 && r.residual_likelihood <= 5) ? parseInt(r.residual_likelihood) : null;
|
||||
const rI = (r.residual_impact >= 1 && r.residual_impact <= 5) ? parseInt(r.residual_impact) : null;
|
||||
const rP = (rL != null && rI != null) ? rL * rI : null;
|
||||
|
||||
// Probabilità / Impatto / Rischio: solo per righe applicabili e non "non applicabili".
|
||||
let probCell = '<td>—</td>', impCell = '<td>—</td>', scoreCell = '<td>—</td>';
|
||||
if (applicable && r.stato !== 'non_applicabile') {
|
||||
const l = (r.likelihood >= 1 && r.likelihood <= 5) ? parseInt(r.likelihood) : null;
|
||||
const i = (r.impact >= 1 && r.impact <= 5) ? parseInt(r.impact) : null;
|
||||
probCell = `<td>${l != null ? l + '/5' : '—'}</td>`;
|
||||
impCell = `<td>${i != null ? i + '/5' : '—'}</td>`;
|
||||
if (r.score != null && r.score !== '' && !isNaN(r.score)) {
|
||||
const sc = parseInt(r.score);
|
||||
scoreCell = `<td><span class="risk-score ${getDerivedScoreClass(sc)}" title="P${l != null ? l : '?'} × I${i != null ? i : '?'} = ${sc}">${sc}</span></td>`;
|
||||
}
|
||||
}
|
||||
const iEval = iP != null ? `<span class="rk-eval ${evalClass(iP)}" id="iv-${reqId}">${iP}</span>` : `<span class="rk-empty" id="iv-${reqId}">—</span>`;
|
||||
const rEval = rP != null ? `<span class="rk-eval ${evalClass(rP)}" id="rv-${reqId}">${rP}</span>` : `<span class="rk-empty" id="rv-${reqId}">—</span>`;
|
||||
|
||||
// Colonna Azioni (solo per i ruoli che possono valutare).
|
||||
let actionsCell = '';
|
||||
if (canEvaluate) {
|
||||
actionsCell = applicable
|
||||
? `<td><button class="btn btn-ghost btn-sm" onclick="openEvaluateModal(${r.requisito_id})" title="Valuta rischio"><i data-lucide="sliders-horizontal" class="ic"></i> Valuta</button></td>`
|
||||
: '<td><span class="text-muted">—</span></td>';
|
||||
}
|
||||
const ab = alertBand(iP);
|
||||
const alertCell = ab
|
||||
? `<button class="alert-btn ${ab.cls}" onclick="openActionsPopup(${reqId})" title="Vedi/crea azioni">${ab.label}</button>`
|
||||
: `<span class="rk-empty" id="al-${reqId}">—</span>`;
|
||||
const inhOutcome = iP != null ? '<span class="rk-valutato">Valutato</span>' : '<span class="rk-empty">—</span>';
|
||||
|
||||
const ro = residualOutcome(rP);
|
||||
const resOutcomeCell = ro ? `<span class="${ro.cls}">${ro.label}</span>` : '<span class="rk-empty">—</span>';
|
||||
// Col.12 — Rischio Residuo: "Valutato" + stato requisito ESPLICITO (eccezione #1: non automatico).
|
||||
const resCell = rP != null
|
||||
? `<span class="rk-valutato">Valutato</span>${editable ? `<div style="margin-top:3px;"><a href="javascript:void(0)" onclick="openStateModal(${reqId})" style="font-size:0.68rem;color:var(--primary);">stato: ${escapeHtml(st.label)}</a></div>` : `<div style="font-size:0.68rem;color:var(--gray-500);margin-top:3px;">${escapeHtml(st.label)}</div>`}`
|
||||
: '<span class="rk-empty">—</span>';
|
||||
|
||||
html += `
|
||||
<tr ${rowStyle}>
|
||||
<td><code style="font-size:0.78rem;color:var(--primary);">${escapeHtml(r.risk_code || '-')}</code></td>
|
||||
<td title="${escapeHtml(riskTooltip)}">
|
||||
<div style="font-size:0.68rem;text-transform:uppercase;letter-spacing:0.04em;font-weight:600;color:var(--gray-500);margin-bottom:2px;">Rischio di non conformità</div>
|
||||
<div style="font-size:0.86rem;color:var(--gray-800);">${escapeHtml(reqDescr)}</div>
|
||||
<tr>
|
||||
<td class="cell-code">
|
||||
<a href="misure-requisiti.html?req=${reqId}" title="Apri in Misure e Requisiti"><code style="font-size:0.75rem;color:var(--primary);">${escapeHtml(r.risk_code || '-')}</code></a>
|
||||
<div style="font-size:0.66rem;color:var(--gray-500);margin-top:2px;">${escapeHtml(measureCode)} ${escapeHtml(reqCode)}</div>
|
||||
</td>
|
||||
<td>
|
||||
<code style="font-size:0.75rem;color:var(--gray-600);">${escapeHtml(measureCode)}</code>
|
||||
${measureDescr ? `<div style="font-size:0.78rem;color:var(--gray-500);margin-top:2px;">${escapeHtml(measureDescr)}</div>` : ''}
|
||||
<td class="cell-desc" title="${escapeHtml(riskTooltip)}">
|
||||
<div style="font-size:0.64rem;text-transform:uppercase;letter-spacing:0.04em;font-weight:600;color:var(--gray-500);">Rischio di non conformità</div>
|
||||
<div style="font-size:0.82rem;color:var(--gray-800);">${escapeHtml(riskDescr)}</div>
|
||||
</td>
|
||||
<td>${conformityCell}</td>
|
||||
${probCell}
|
||||
${impCell}
|
||||
${scoreCell}
|
||||
<td>${evalVal}</td>
|
||||
${actionsCell}
|
||||
<td>${scoreSel('il-' + reqId, iL, editable)}</td>
|
||||
<td>${scoreSel('ii-' + reqId, iI, editable)}</td>
|
||||
<td>${iEval}</td>
|
||||
<td>${alertCell}</td>
|
||||
<td>${inhOutcome}</td>
|
||||
<td>${scoreSel('rl-' + reqId, rL, editable)}</td>
|
||||
<td>${scoreSel('ri-' + reqId, rI, editable)}</td>
|
||||
<td>${rEval}</td>
|
||||
<td>${resOutcomeCell}</td>
|
||||
<td>${resCell}</td>
|
||||
</tr>`;
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} }
|
||||
}
|
||||
|
||||
// ── Editing inline: legge i 4 punteggi della riga, salva l'intera valutazione, ri-render. ──
|
||||
async function onScoreChange(reqId) {
|
||||
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||
if (!r) return;
|
||||
const rd = id => { const el = document.getElementById(id); if (!el) return null; const v = el.value; return v === '' ? null : parseInt(v); };
|
||||
r.likelihood = rd('il-' + reqId);
|
||||
r.impact = rd('ii-' + reqId);
|
||||
r.score = (r.likelihood && r.impact) ? r.likelihood * r.impact : null;
|
||||
r.residual_likelihood = rd('rl-' + reqId);
|
||||
r.residual_impact = rd('ri-' + reqId);
|
||||
r.residual_score = (r.residual_likelihood && r.residual_impact) ? r.residual_likelihood * r.residual_impact : null;
|
||||
renderDerivedTable(derivedData);
|
||||
loadMatrix();
|
||||
try {
|
||||
await api.frameworkSetState({
|
||||
requisito_id: parseInt(reqId),
|
||||
stato: r.stato || 'da_valutare',
|
||||
valutazione_rischio: r.valutazione_rischio || '',
|
||||
likelihood: r.likelihood, impact: r.impact,
|
||||
residual_likelihood: r.residual_likelihood, residual_impact: r.residual_impact,
|
||||
});
|
||||
} catch (e) {
|
||||
showNotification(e.message || 'Errore nel salvataggio', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Popup Azioni (col.6 Alert): elenco azioni passate + "crea azione" ──
|
||||
async function openActionsPopup(reqId) {
|
||||
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||
if (!r) return;
|
||||
let actions = [];
|
||||
try {
|
||||
const res = await api.requisitoActions(reqId);
|
||||
if (res && res.success && res.data) actions = res.data.actions || [];
|
||||
} catch (e) { /* mostra comunque il popup */ }
|
||||
|
||||
const today = new Date().toLocaleDateString('it-IT');
|
||||
let list = `
|
||||
<div class="act-row" style="background:var(--gray-50);border-radius:8px;">
|
||||
<div class="act-when"><b>${today}</b></div>
|
||||
<div class="act-desc"><input type="text" class="form-input" id="new-act-descr" placeholder="Descrivi la nuova azione…" style="width:100%;"></div>
|
||||
<button class="btn btn-primary btn-sm" onclick="createReqAction(${reqId})">Crea azione</button>
|
||||
</div>`;
|
||||
if (actions.length) {
|
||||
list += actions.map(a => {
|
||||
const when = a.created_at ? new Date(a.created_at.replace(' ', 'T')).toLocaleDateString('it-IT') : '—';
|
||||
return `<div class="act-row"><div class="act-when">${when}</div><div class="act-desc">${escapeHtml(a.description || '')}</div><div class="act-st">${escapeHtml(a.status || '')}</div></div>`;
|
||||
}).join('');
|
||||
} else {
|
||||
list += `<div style="padding:14px 4px;color:var(--gray-500);font-size:0.82rem;">Nessuna azione precedente su questo rischio.</div>`;
|
||||
}
|
||||
|
||||
const content = `
|
||||
<div style="padding:10px 12px;background:var(--gray-50);border:1px solid var(--gray-200);border-radius:8px;margin-bottom:14px;">
|
||||
<code style="font-size:0.78rem;color:var(--primary);">${escapeHtml(r.risk_code || '-')}</code>
|
||||
<div style="font-size:0.82rem;color:var(--gray-800);margin-top:3px;">${escapeHtml(r.risk_descr || r.requisito_descr || '')}</div>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size:0.74rem;margin:0 0 10px;">La soglia di trattamento è una scelta metodologica dell'organizzazione (rif. ISO/IEC 27001 §6.1.2), non un obbligo di legge.</p>
|
||||
${list}`;
|
||||
showModal('Azioni sul rischio', content, {
|
||||
size: 'lg',
|
||||
footer: `<button class="btn btn-secondary" onclick="closeModal()">Chiudi</button>`
|
||||
});
|
||||
}
|
||||
|
||||
async function createReqAction(reqId) {
|
||||
const el = document.getElementById('new-act-descr');
|
||||
const descr = (el && el.value || '').trim();
|
||||
if (!descr) { showNotification('Inserisci una descrizione', 'error'); return; }
|
||||
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||
try {
|
||||
const res = await api.createRequisitoAction({ requisito_id: parseInt(reqId), description: descr, risk_code: r ? r.risk_code : null });
|
||||
if (res && res.success) {
|
||||
showNotification('Azione creata', 'success');
|
||||
openActionsPopup(reqId);
|
||||
} else {
|
||||
showNotification((res && res.message) || 'Errore', 'error');
|
||||
}
|
||||
} catch (e) { showNotification(e.message || 'Errore', 'error'); }
|
||||
}
|
||||
|
||||
// ── Aggiorna ESPLICITAMENTE lo stato di conformità del requisito (eccezione #1) ──
|
||||
function openStateModal(reqId) {
|
||||
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||
if (!r || !canEvaluate) return;
|
||||
const opts = DERIVED_STATUS_OPTIONS.map(([v, l]) =>
|
||||
`<option value="${v}" ${v === (r.stato || 'da_valutare') ? 'selected' : ''}>${escapeHtml(l)}</option>`).join('');
|
||||
const content = `
|
||||
<p class="text-muted" style="font-size:0.8rem;line-height:1.5;">Lo stato di <b>conformità</b> dipende dall'<b>attuazione della misura e dalle evidenze</b>, non dal punteggio di rischio. Aggiornalo qui in modo esplicito.</p>
|
||||
<div class="form-group"><label class="form-label">Stato del requisito</label><select class="form-select" id="st-sel">${opts}</select></div>`;
|
||||
showModal('Stato del requisito', content, {
|
||||
size: 'md',
|
||||
footer: `<button class="btn btn-secondary" onclick="closeModal()">Annulla</button><button class="btn btn-primary" onclick="saveState(${reqId})">Salva stato</button>`
|
||||
});
|
||||
}
|
||||
async function saveState(reqId) {
|
||||
const r = (derivedData || []).find(x => String(x.requisito_id) === String(reqId));
|
||||
if (!r) return;
|
||||
const stato = document.getElementById('st-sel').value;
|
||||
try {
|
||||
await api.frameworkSetState({
|
||||
requisito_id: parseInt(reqId), stato: stato,
|
||||
valutazione_rischio: r.valutazione_rischio || '',
|
||||
likelihood: r.likelihood, impact: r.impact,
|
||||
residual_likelihood: r.residual_likelihood, residual_impact: r.residual_impact,
|
||||
});
|
||||
r.stato = stato;
|
||||
closeModal();
|
||||
showNotification('Stato aggiornato', 'success');
|
||||
renderDerivedTable(derivedData);
|
||||
} catch (e) { showNotification(e.message || 'Errore', 'error'); }
|
||||
}
|
||||
|
||||
// ── Modale "Valuta" (opzione B) — valutazione rischio-da-requisito ──
|
||||
const DERIVED_STATUS_OPTIONS = [
|
||||
['da_valutare', 'Da valutare'],
|
||||
|
||||
@@ -735,12 +735,12 @@
|
||||
<!-- Scripts V3: api.js (client REST, prima) → common.js (helper+FAB reali) →
|
||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) →
|
||||
i18n → help → init pagina (inline, invariato). -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -215,12 +215,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -210,12 +210,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -542,12 +542,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -190,12 +190,12 @@
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali+showModal/showNotification) →
|
||||
Lucide self-host → topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help.
|
||||
La logica della pagina e' inline (nessun training.js): preservata invariata. -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth & Init (ordine V3) ─────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -258,12 +258,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
@@ -143,12 +143,12 @@
|
||||
|
||||
<!-- Scripts V3: api.js → common.js (helper+FAB reali) → Lucide self-host →
|
||||
topnav-v3.js (chrome, ridefinisce loadSidebar) → i18n → help → init pagina. -->
|
||||
<script src="/js/api.js?v=20260729b"></script>
|
||||
<script src="/js/api.js?v=20260730a"></script>
|
||||
<script src="/js/common.js?v=20260729b"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260729"></script>
|
||||
<script src="/js/i18n.js?v=20260729"></script>
|
||||
<script src="/js/help.js?v=20260729b"></script>
|
||||
<script src="/js/i18n.js?v=20260730a"></script>
|
||||
<script src="/js/help.js?v=20260730a"></script>
|
||||
<script>
|
||||
// ── Auth + chrome + i18n + help ──────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
Reference in New Issue
Block a user