[FEAT] Rischi #500 fase 2 (frontend): elenco rischi = requisiti Lex&ISO
La pagina Gestione Rischi mostra come tabella primaria (read-only) i requisiti del framework Lex&ISO come rischi, codice RSK-<misura>Com.N via /api/risks/derived, filtrati per classe (importante All.1 / essenziale All.2). Framing normativo (validato nis2-expert): ogni voce = "rischio derivante dal mancato/parziale soddisfacimento del requisito", mai "requisito = rischio"; RSK e "92" NON come codici/numeri ufficiali ACN; matrice = strumento di rappresentazione (art.24). - Rischi CUSTOM mantenuti in sezione "Rischi aggiuntivi (all-hazard)". - Tasti "Nuovo Rischio"/"AI Suggerisci" limitati a super_admin/org_admin (non rimossi): l'inserimento libero serve al multirischio (art.24 D.Lgs.138/2024). - api.js derivedRisks(); help.js + i18n aggiornati; version 1.25.4; SW v1.25.4. - Cache-buster api/help/i18n ?v=20260727r. Backend/DB non toccati. 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
571e151305
commit
4e7e75582a
+155
-6
@@ -331,12 +331,57 @@
|
||||
<button class="btn-ghost" id="btn-ai-suggest" onclick="aiSuggest()">
|
||||
<i data-lucide="sparkles" class="ic"></i>AI Suggerisci
|
||||
</button>
|
||||
<button class="btn-primary" onclick="showRiskModal()"><i data-lucide="plus" class="ic"></i>Nuovo Rischio</button>
|
||||
<button class="btn-primary" id="btn-new-risk" onclick="showRiskModal()"><i data-lucide="plus" class="ic"></i>Nuovo Rischio</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-body">
|
||||
<!-- Table View -->
|
||||
<!-- ── Nota introduttiva (framing normativo) ───────────── -->
|
||||
<div class="risk-intro-note" style="display:flex;gap:12px;align-items:flex-start;padding:16px 18px;background:var(--gray-50);border:1px solid var(--gray-200);border-left:4px solid var(--primary);border-radius:var(--border-radius);margin-bottom:24px;">
|
||||
<i data-lucide="info" class="ic" style="flex-shrink:0;color:var(--primary);margin-top:2px;"></i>
|
||||
<p style="font-size:0.85rem;color:var(--gray-700);line-height:1.55;margin:0;">
|
||||
In questa pagina i rischi corrispondono ai requisiti del framework di sicurezza applicabili alla tua organizzazione e alla loro valutazione. I requisiti derivano dalle misure di sicurezza di base della Determinazione ACN n. 164179 del 14/4/2025 (Allegato 1 per i soggetti importanti, Allegato 2 per gli essenziali). Ogni voce rappresenta il rischio derivante dal mancato o parziale soddisfacimento del requisito, non il requisito in sé. La valutazione e la matrice 5×5 sono uno strumento di rappresentazione: la normativa (art. 24 D.Lgs. 138/2024) richiede un'analisi dei rischi proporzionata e con approccio multirischio, senza imporre una metodologia specifica.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ── SEZIONE 1: Rischi derivati dai requisiti (PRIMARIA, read-only) ── -->
|
||||
<div id="view-derived" style="margin-bottom:32px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:8px;margin-bottom:10px;">
|
||||
<h2 style="font-size:1.05rem;margin:0;" data-i18n="risks.derived_heading">Rischi dai requisiti del framework di sicurezza</h2>
|
||||
<span class="text-muted" style="font-size:0.78rem;" id="derived-source-note"></span>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size:0.78rem;margin:0 0 12px;">
|
||||
<span data-i18n="risks.derived_code_legend">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.</span>
|
||||
</p>
|
||||
<div class="card">
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<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" data-i18n="risks.col_evaluation">Valutazione</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="derived-table-body">
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="spinner" style="margin:40px auto;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── SEZIONE 2: Rischi aggiuntivi (multirischio / all-hazard) ── -->
|
||||
<h2 style="font-size:1.05rem;margin:0 0 6px;" data-i18n="risks.custom_heading">Rischi aggiuntivi (approccio multirischio / all-hazard)</h2>
|
||||
<p class="text-muted" style="font-size:0.82rem;margin:0 0 16px;line-height:1.5;" data-i18n="risks.custom_help">Oltre ai rischi derivati dai requisiti, i profili amministrativi possono aggiungere rischi non coperti da un singolo requisito (es. minacce fisiche, catena di fornitura, eventi naturali): copre l'approccio multirischio/all-hazard (art. 24 D.Lgs. 138/2024).</p>
|
||||
|
||||
<!-- Table View (rischi custom) -->
|
||||
<div id="view-table">
|
||||
<div class="filters-bar">
|
||||
<select class="form-select" id="filter-category" onchange="loadRisks()" aria-label="Filtra per categoria">
|
||||
@@ -426,6 +471,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size:0.78rem;text-align:center;margin:16px auto 0;max-width:640px;line-height:1.5;" data-i18n="risks.matrix_caption">La matrice 5×5 è uno strumento di rappresentazione, non un obbligo normativo (art. 24 D.Lgs. 138/2024).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -437,12 +483,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=20260627u"></script>
|
||||
<script src="/js/api.js?v=20260727r"></script>
|
||||
<script src="/js/common.js?v=20260627t"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627t"></script>
|
||||
<script src="/js/i18n.js?v=20260627p"></script>
|
||||
<script src="/js/help.js?v=20260727a"></script>
|
||||
<script src="/js/i18n.js?v=20260727r"></script>
|
||||
<script src="/js/help.js?v=20260727r"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
@@ -489,11 +535,112 @@
|
||||
// ── State ────────────────────────────────────────────────────
|
||||
let currentView = 'table';
|
||||
let risksData = [];
|
||||
let derivedData = [];
|
||||
let orgClass = null;
|
||||
|
||||
// ── Gating ruolo: creazione/AI riservate ai profili amministrativi ──
|
||||
const ADMIN_ROLES = ['super_admin', 'org_admin'];
|
||||
(function gateAdminActions() {
|
||||
const role = (api.getUserRole && api.getUserRole()) || '';
|
||||
if (!ADMIN_ROLES.includes(role)) {
|
||||
['btn-ai-suggest', 'btn-new-risk'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.display = 'none';
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// ── Load ─────────────────────────────────────────────────────
|
||||
loadDerivedRisks();
|
||||
loadRisks();
|
||||
loadMatrix();
|
||||
|
||||
// ── Derived Risks (requisiti del framework) ──────────────────
|
||||
// Badge conformità per lo stato di valutazione del requisito.
|
||||
const DERIVED_STATUS_BADGE = {
|
||||
da_valutare: { cls: 'badge-neutral', label: 'Da valutare' },
|
||||
non_applicabile: { cls: 'badge-neutral', label: 'Non applicabile' },
|
||||
non_conforme: { cls: 'badge-danger', label: 'Non conforme' },
|
||||
parziale: { cls: 'badge-warning', label: 'Parziale' },
|
||||
conforme: { cls: 'badge-success', label: 'Conforme' },
|
||||
};
|
||||
|
||||
function isDerivedApplicable(r) {
|
||||
// important → applicabili solo i requisiti Allegato 1; essential → Allegato 2;
|
||||
// classe non determinata / adesione volontaria → tutti applicabili.
|
||||
if (orgClass === 'important') return r.applies_important == 1;
|
||||
if (orgClass === 'essential') return r.applies_essential == 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function loadDerivedRisks() {
|
||||
try {
|
||||
const result = await api.derivedRisks();
|
||||
if (result && result.success && result.data) {
|
||||
orgClass = result.data.org_class || null;
|
||||
derivedData = result.data.risks || [];
|
||||
const src = result.data.source || '';
|
||||
const note = document.getElementById('derived-source-note');
|
||||
if (note && src) note.textContent = 'Fonte: ' + src;
|
||||
renderDerivedTable(derivedData);
|
||||
} else {
|
||||
renderDerivedTable([]);
|
||||
}
|
||||
} catch (e) {
|
||||
renderDerivedTable([]);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDerivedTable(rows) {
|
||||
const tbody = document.getElementById('derived-table-body');
|
||||
if (!rows || rows.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr><td colspan="5">
|
||||
<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>
|
||||
<p>Non risultano requisiti del framework di sicurezza per questa organizzazione.</p>
|
||||
</div>
|
||||
</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
rows.forEach(r => {
|
||||
const applicable = isDerivedApplicable(r);
|
||||
const st = DERIVED_STATUS_BADGE[r.stato] || DERIVED_STATUS_BADGE.da_valutare;
|
||||
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();
|
||||
|
||||
// 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>`;
|
||||
|
||||
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>
|
||||
</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>
|
||||
<td>${conformityCell}</td>
|
||||
<td>${evalVal}</td>
|
||||
</tr>`;
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
}
|
||||
|
||||
async function loadRisks() {
|
||||
const params = {};
|
||||
const cat = document.getElementById('filter-category').value;
|
||||
@@ -666,6 +813,7 @@
|
||||
}
|
||||
|
||||
function renderDetail(risk) {
|
||||
document.getElementById('view-derived').classList.add('hidden');
|
||||
document.getElementById('view-table').classList.add('hidden');
|
||||
document.getElementById('view-matrix').classList.add('hidden');
|
||||
const container = document.getElementById('view-detail');
|
||||
@@ -839,7 +987,8 @@
|
||||
|
||||
function backToList() {
|
||||
document.getElementById('view-detail').classList.add('hidden');
|
||||
// Tabella e matrice sono entrambe sempre visibili.
|
||||
// Sezione derivati, tabella custom e matrice sono sempre visibili.
|
||||
document.getElementById('view-derived').classList.remove('hidden');
|
||||
document.getElementById('view-table').classList.remove('hidden');
|
||||
document.getElementById('view-matrix').classList.remove('hidden');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user