Files
nis2-agile/public/misure-requisiti.html
T
DevEnv nis2-agileandClaude Opus 4.8 92d101b6f4 [FEAT] Epic C / C1 — Elenco Misure/Requisiti consultabile (read-only) + FrameworkController
UI data-driven sulle tabelle di config cfg_nis2_* (mig.046):
- FrameworkController::catalog (GET /api/framework/catalog): 43 misure / 116 requisiti,
  procedura+rischio di default per requisito, flag importanti/essenziali, classe org.
- public/misure-requisiti.html (Bootstrap Italia/AGID): vista per misura / per area politica,
  selettore soggetto (auto/importante/essenziale/tutti), righe che si 'spengono' per classe,
  ricerca, sola lettura (codifica non modificabile). Voce sidebar in sezione Compliance.
- api.js frameworkCatalog(); routing index.php; i18n nav.framework IT/EN.
- help.js: guida 'misure-requisiti' (struttura, importanti/essenziali, riferimenti art.23/24 D.Lgs.).
- KB AI: doc SYSTEM ingestato (uuid 7871c1ea).
- Cache-buster ?v=20260621 (35 pagine), version 1.18.0, SW cache nis2-shell-v1.18.0.
- docs/simon/: file sorgente autoritativi (provenienza seed, codifica Simon non variata).

Smoke prod: /framework/catalog 200 (43/116, 6 misure solo-essenziali, GV.OC-04->Proc.01/Risk.01),
pagina 200, USR2 applicato. Additivo, nessuna modifica a dati esistenti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:29:08 +02:00

238 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Misure e Requisiti - NIS2 Agile</title>
<!-- Bootstrap Italia v2.18.1 self-hostato (regola: MAI CDN). Caricato PRIMA di style.css. -->
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
<link rel="stylesheet" href="/css/style.css?v=20260621">
<style>
.mr-intro { background:#eff6ff; border-left:4px solid var(--primary,#2563eb); padding:14px 18px; border-radius:8px; margin-bottom:14px; font-size:.92rem; line-height:1.6; }
.mr-note { font-size:.82rem; color:#92400e; background:#fffbeb; border:1px solid #fde68a; border-radius:8px; padding:10px 12px; margin:0 0 16px; }
.mr-ro { font-size:.8rem; color:#3730a3; background:#eef2ff; border:1px solid #c7d2fe; border-radius:8px; padding:8px 12px; margin:0 0 16px; display:flex; gap:8px; align-items:center; }
.mr-stats { display:flex; flex-wrap:wrap; gap:10px; margin-bottom:16px; }
.mr-stat { flex:1 1 140px; min-width:120px; background:#fff; border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; padding:12px 14px; }
.mr-stat .n { font-size:1.5rem; font-weight:800; line-height:1; }
.mr-stat .l { font-size:.76rem; color:var(--gray-500,#6b7280); margin-top:4px; }
.mr-toolbar { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-bottom:14px; }
.mr-toolbar label { font-weight:600; font-size:.82rem; }
.mr-toolbar select, .mr-toolbar input[type=search] { padding:8px 10px; border:1px solid var(--gray-200,#e5e7eb); border-radius:8px; font-size:.88rem; }
.mr-toolbar .grow { flex:1 1 auto; }
.mr-group { border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; margin-bottom:14px; overflow:hidden; }
.mr-group-head { background:#f8fafc; padding:11px 14px; border-bottom:1px solid var(--gray-100,#f3f4f6); }
.mr-group-head .code { font-weight:800; color:#1e3a8a; font-size:.95rem; }
.mr-group-head .area { font-size:.78rem; color:var(--gray-500,#6b7280); }
.mr-group-head .descr { font-size:.86rem; color:#334155; margin-top:3px; line-height:1.5; }
.mr-tag { display:inline-block; font-size:.66rem; font-weight:800; padding:2px 8px; border-radius:20px; margin-left:6px; vertical-align:middle; }
.mr-tag.ess { background:#fef3c7; color:#92400e; }
.mr-table { width:100%; border-collapse:collapse; font-size:.85rem; }
.mr-table th, .mr-table td { text-align:left; padding:9px 11px; border-bottom:1px solid var(--gray-100,#f3f4f6); vertical-align:top; }
.mr-table th { font-size:.72rem; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-500,#6b7280); background:#fff; position:sticky; top:0; }
.mr-req-code { white-space:nowrap; font-weight:700; color:#475569; }
.mr-code-pill { display:inline-block; font-size:.7rem; font-weight:700; color:#3730a3; background:#eef2ff; border-radius:6px; padding:1px 6px; }
.mr-yn { text-align:center; font-weight:800; }
.mr-yn.y { color:#166534; }
.mr-yn.n { color:#cbd5e1; }
.mr-row.spento { opacity:.4; }
.mr-row.spento td { font-style:italic; }
.mr-empty { text-align:center; padding:34px 16px; color:var(--gray-500,#6b7280); }
</style>
<!-- PWA:start -->
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#0066CC">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="NIS2 Agile">
<meta name="application-name" content="NIS2 Agile">
<script src="/js/pwa.js?v=20260614" defer></script>
<!-- PWA:end -->
</head>
<body>
<div class="app-layout">
<aside class="sidebar" id="sidebar"></aside>
<main class="main-content">
<header class="content-header">
<h2 data-i18n="mr.title">Misure e Requisiti</h2>
<div class="content-header-actions">
<span class="savehint" id="mr-hint" role="status" aria-live="polite"></span>
</div>
</header>
<div class="content-body">
<div class="mr-intro">
<strong>L'elenco canonico delle misure di sicurezza di base e dei relativi requisiti.</strong>
Ogni requisito riporta la <strong>procedura di default</strong> che lo soddisfa e il <strong>rischio di default</strong> associato. È la base su cui si costruiscono procedure, rischi e attività: le righe si "spengono" quando non sono applicabili alla classe del tuo soggetto.
</div>
<div class="mr-ro">
🔒 <span>Elenco di riferimento, <strong>sola lettura</strong>: la codifica (misure, requisiti, ordine) non è modificabile. Procedure e rischi qui elencati vengono proposti come <em>default</em> nei rispettivi moduli, dove potrai personalizzarli.</span>
</div>
<div class="mr-note">
Fonte: <strong>Determinazione ACN n. 164179 del 14 aprile 2025</strong> (misure di base, Allegati 1–2). Le misure applicabili dipendono dalla classe del soggetto: <strong>importanti</strong> 37 misure / 92 requisiti, <strong>essenziali</strong> 43 / 116. Strumento di supporto, non un parere legale.
</div>
<div class="mr-stats" id="mr-stats" style="display:none;">
<div class="mr-stat"><div class="n" id="mr-stat-mis">0</div><div class="l">Misure</div></div>
<div class="mr-stat"><div class="n" id="mr-stat-req">0</div><div class="l">Requisiti</div></div>
<div class="mr-stat"><div class="n" id="mr-stat-active">0</div><div class="l">Applicabili al soggetto</div></div>
</div>
<div class="mr-toolbar">
<label for="mr-view">Vista:</label>
<select id="mr-view" onchange="mrRender()">
<option value="misura">Per misura</option>
<option value="area">Per area politica</option>
</select>
<label for="mr-class">Soggetto:</label>
<select id="mr-class" onchange="mrRender()">
<option value="auto">Automatico (dalla mia org)</option>
<option value="important">Importante</option>
<option value="essential">Essenziale</option>
<option value="all">Tutti (mostra tutto)</option>
</select>
<label style="font-weight:500;"><input type="checkbox" id="mr-hide" onchange="mrRender()"> nascondi non applicabili</label>
<span class="grow"></span>
<input type="search" id="mr-q" placeholder="Cerca codice o testo…" oninput="mrRender()" style="min-width:200px;">
</div>
<div id="mr-list" aria-live="polite"><div class="mr-empty">Caricamento…</div></div>
</div>
</main>
</div>
<script src="/js/api.js?v=20260621"></script>
<script src="/js/common.js?v=20260621"></script>
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260621"></script>
<script src="/js/help.js?v=20260621"></script>
<script>
'use strict';
/*
* NIS2 Agile - Elenco canonico Misure/Requisiti (Epic C / C1) - SOLA LETTURA.
* Dati da api.frameworkCatalog() (tabelle cfg_nis2_*). Codifica non modificabile.
* Le righe si "spengono" per la classe del soggetto (importante/essenziale).
*/
let MR = { measures: [], org_class: null };
function el(id) { return document.getElementById(id); }
function esc(s) { const d = document.createElement('div'); d.textContent = (s == null ? '' : String(s)); return d.innerHTML; }
document.addEventListener('DOMContentLoaded', async function () {
if (typeof checkAuth === 'function' && !checkAuth()) return;
if (window.I18n && I18n.init) I18n.init('it');
if (typeof loadSidebar === 'function') loadSidebar();
if (window.HelpSystem && HelpSystem.init) HelpSystem.init();
await mrLoad();
});
async function mrLoad() {
try {
const data = await api.frameworkCatalog();
MR.measures = (data && data.measures) || [];
MR.org_class = (data && data.org_class) || null;
// se conosco la classe dell'org, parto in modalità "auto"; altrimenti mostro tutto
if (!MR.org_class) el('mr-class').value = 'all';
el('mr-stat-mis').textContent = (data && data.totals && data.totals.misure) || MR.measures.length;
el('mr-stat-req').textContent = (data && data.totals && data.totals.requisiti) || 0;
el('mr-stats').style.display = 'flex';
mrRender();
} catch (e) {
el('mr-list').innerHTML = '<div class="mr-empty">Impossibile caricare l\'elenco. Riprova più tardi.</div>';
}
}
// classe effettiva selezionata (risolve "auto" sulla classe dell'org)
function mrEffectiveClass() {
const sel = el('mr-class').value;
if (sel === 'auto') return MR.org_class; // può essere null => tutto attivo
if (sel === 'all') return null;
return sel; // important | essential
}
// un requisito è "attivo" per la classe effettiva?
function reqActive(req, cls) {
if (!cls) return true;
return cls === 'important' ? !!req.applies_important : !!req.applies_essential;
}
function matchQuery(m, req, q) {
if (!q) return true;
q = q.toLowerCase();
return (m.misura_code + ' ' + m.misura_descr + ' ' + m.area_politica + ' ' +
req.requisito_code + ' ' + req.requisito_descr + ' ' +
(req.proc_descr || '') + ' ' + (req.risk_descr || '')).toLowerCase().indexOf(q) >= 0;
}
function mrRender() {
const cls = mrEffectiveClass();
const hide = el('mr-hide').checked;
const q = (el('mr-q').value || '').trim();
const view = el('mr-view').value;
// costruisco le righe filtrate (misura -> requisiti visibili)
let activeCount = 0;
const groupsByKey = {}; // key -> {label, descr, area, code, essOnly, rows:[]}
MR.measures.forEach(function (m) {
const essOnly = !m.applies_important;
m.requisiti.forEach(function (req) {
if (!matchQuery(m, req, q)) return;
const active = reqActive(req, cls);
if (active) activeCount++;
if (hide && !active) return;
let key, head;
if (view === 'area') {
key = m.area_politica || '—';
head = { code: m.area_politica || '—', descr: '', area: '', essOnly: false };
} else {
key = m.misura_code;
head = { code: m.misura_code + ' — ' + (m.nist_code || ''), descr: m.misura_descr, area: m.area_politica, essOnly: essOnly };
}
if (!groupsByKey[key]) groupsByKey[key] = Object.assign({ rows: [] }, head);
groupsByKey[key].rows.push({ m: m, req: req, active: active });
});
});
el('mr-stat-active').textContent = activeCount;
const keys = Object.keys(groupsByKey);
if (!keys.length) { el('mr-list').innerHTML = '<div class="mr-empty">Nessun risultato per i filtri selezionati.</div>'; return; }
let html = '';
keys.forEach(function (key) {
const g = groupsByKey[key];
html += '<div class="mr-group">';
html += '<div class="mr-group-head"><div><span class="code">' + esc(g.code) + '</span>' +
(g.essOnly ? '<span class="mr-tag ess">solo essenziali</span>' : '') + '</div>';
if (g.area) html += '<div class="area">' + esc(g.area) + '</div>';
if (g.descr) html += '<div class="descr">' + esc(g.descr) + '</div>';
html += '</div>';
html += '<table class="mr-table"><thead><tr>' +
'<th>Requisito</th><th>Descrizione</th><th>Procedura (default)</th><th>Rischio (default)</th>' +
'<th style="text-align:center">Imp.</th><th style="text-align:center">Ess.</th></tr></thead><tbody>';
g.rows.forEach(function (r) {
const req = r.req, m = r.m;
html += '<tr class="mr-row' + (r.active ? '' : ' spento') + '">';
if (view === 'area') {
html += '<td class="mr-req-code"><span class="mr-code-pill">' + esc(m.misura_code) + '</span> ' + esc(req.requisito_code) + '</td>';
} else {
html += '<td class="mr-req-code">' + esc(req.requisito_code) + '</td>';
}
html += '<td>' + esc(req.requisito_descr) + '</td>';
html += '<td>' + (req.proc_code ? '<span class="mr-code-pill">' + esc(req.proc_code) + '</span> ' + esc(req.proc_descr || '') : '—') + '</td>';
html += '<td>' + (req.risk_code ? '<span class="mr-code-pill">' + esc(req.risk_code) + '</span> ' + esc(req.risk_descr || '') : '—') + '</td>';
html += '<td class="mr-yn ' + (req.applies_important ? 'y' : 'n') + '">' + (req.applies_important ? '✓' : '—') + '</td>';
html += '<td class="mr-yn ' + (req.applies_essential ? 'y' : 'n') + '">' + (req.applies_essential ? '✓' : '—') + '</td>';
html += '</tr>';
});
html += '</tbody></table></div>';
});
el('mr-list').innerHTML = html;
}
</script>
</body>
</html>