[FEAT] Epic C / C4 — inventario a 2 voci + sottoclassi configurabili (mig.050)

Simon C4: nell'inventario DUE sole voci principali; le sottoclassi le crea l'utente.
Riconduzione concordata (no perdita di dettaglio): hardware/network/facility -> ID.AM-01;
software/service/data -> ID.AM-02; personnel -> Organigramma (0 asset personnel presenti).

- Config: cfg_inventory_voci (2: ID.AM-01 Hardware, ID.AM-02 Software/Servizi/Sistemi),
  inventory_subclassi (12 default di sistema org NULL + sottoclassi org-scoped).
- assets.voce_code + subclass_id (FK). 20 asset esistenti MIGRATI (5 ID.AM-01 / 15 ID.AM-02).
- AssetController: list con label voce/sottoclasse; create/update validati (voce in whitelist,
  sottoclasse appartenente a org/default+voce); GET/POST /assets/subclasses; import (bulkUpsert)
  deriva la voce dal tipo legacy. asset_type resta come campo legacy (viste secondarie).
- assets.html: filtro per voce, form con selettore Voce + Sottoclasse (+ '+ aggiungi'),
  lista/dettaglio mostrano 'voce · sottoclasse', stat-chips per voce.
- FIX bug pre-esistente: apostrofo non escapato ('l'inventario') che ROMPEVA tutto il JS
  della pagina Inventario. + cache-buster common-bi.js (era a v=20260618, non bumpato dal fix C3).
Help + KB + i18n. Verifiche prod: subclasses 200, create voce/legacy OK, validazione 422,
JS valido (node --check), migrazione idempotente. version 1.18.4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-16 19:59:35 +02:00
co-authored by Claude Opus 4.8
parent e281aa4970
commit 30f4614b37
41 changed files with 370 additions and 173 deletions
+3 -3
View File
@@ -70,9 +70,9 @@
</script>
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/common-bi.js?v=20260618"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script src="js/common-bi.js?v=20260625"></script>
<script>
// Renderizza la sidebar BI (loadSidebar è stato ridefinito da common-bi.js)
document.addEventListener('DOMContentLoaded', function () {
+4 -4
View File
@@ -1088,10 +1088,10 @@
</main>
</div>
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/i18n.js?v=20260624"></script>
<script src="js/help.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script src="js/i18n.js?v=20260625"></script>
<script src="js/help.js?v=20260625"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
+5 -5
View File
@@ -154,8 +154,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -165,9 +165,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+67 -30
View File
@@ -331,14 +331,9 @@
<!-- Filters -->
<div class="filters-bar">
<select id="filter-type" onchange="applyFilters()" aria-label="Filtra per tipo">
<option value="">Tutti i tipi</option>
<option value="hardware">Hardware</option>
<option value="software">Software</option>
<option value="network">Rete</option>
<option value="data">Dati</option>
<option value="service">Servizio</option>
<option value="personnel">Personale</option>
<option value="facility">Struttura</option>
<option value="">Tutte le voci</option>
<option value="ID.AM-01">Hardware (ID.AM-01)</option>
<option value="ID.AM-02">Software/Servizi/Sistemi (ID.AM-02)</option>
</select>
<select id="filter-criticality" onchange="applyFilters()" aria-label="Filtra per criticità">
<option value="">Tutte le criticita'</option>
@@ -366,8 +361,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -377,15 +372,16 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
I18n.init();
HelpSystem.init();
loadSubclasses(); // C4 — voci+sottoclassi per i selettori (fire-and-forget, pronto al primo "Nuovo")
// ── Labels ──────────────────────────────────────────────
const typeLabels = {
@@ -411,6 +407,42 @@
decommissioned: 'Dismesso'
};
// ── Voci / sottoclassi (Epic C / C4) ────────────────────
const VOCE_LABELS = { 'ID.AM-01': 'Hardware', 'ID.AM-02': 'Software/Servizi/Sistemi' };
let SUBCLASSES = []; // [{voce_code,label,subclassi:[{id,label,is_default}]}]
function assetTipo(a) {
const v = a.voce_label || VOCE_LABELS[a.voce_code] || (typeLabels[a.asset_type] || a.asset_type || '-');
return a.subclass_label ? (v + ' · ' + a.subclass_label) : v;
}
async function loadSubclasses() {
try { const r = await api.listAssetSubclasses(); if (r && r.success) SUBCLASSES = (r.data && r.data.voci) || []; }
catch (e) { SUBCLASSES = []; }
}
function voceOptionsHtml(sel) {
return ['ID.AM-01', 'ID.AM-02'].map(v =>
`<option value="${v}" ${sel === v ? 'selected' : ''}>${v} — ${VOCE_LABELS[v]}</option>`).join('');
}
function subclassOptionsHtml(voce, sel) {
const grp = SUBCLASSES.find(g => g.voce_code === voce);
const subs = grp ? grp.subclassi : [];
return '<option value="">— nessuna —</option>' + subs.map(s =>
`<option value="${s.id}" ${String(sel) === String(s.id) ? 'selected' : ''}>${escapeHtml(s.label)}</option>`).join('');
}
function onVoceChange() {
const voce = document.getElementById('asset-voce').value;
document.getElementById('asset-subclass').innerHTML = subclassOptionsHtml(voce, '');
}
async function addSubclassPrompt() {
const voce = document.getElementById('asset-voce').value;
const label = (prompt('Nuova sottoclasse per «' + VOCE_LABELS[voce] + '»:') || '').trim();
if (!label) return;
try {
const r = await api.addAssetSubclass({ voce_code: voce, label: label });
if (r && r.success) { await loadSubclasses(); document.getElementById('asset-subclass').innerHTML = subclassOptionsHtml(voce, r.data.id); showNotification('Sottoclasse aggiunta.', 'success'); }
else showNotification((r && r.message) || 'Errore.', 'warning');
} catch (e) { showNotification('Errore aggiunta sottoclasse.', 'warning'); }
}
// ── State ───────────────────────────────────────────────
let allAssets = [];
@@ -423,7 +455,6 @@
const type = document.getElementById('filter-type').value;
const criticality = document.getElementById('filter-criticality').value;
const status = document.getElementById('filter-status').value;
if (type) params.asset_type = type;
if (criticality) params.criticality = criticality;
if (status) params.status = status;
@@ -431,13 +462,14 @@
const result = await api.listAssets(params);
if (result.success) {
allAssets = result.data || [];
updateStats(allAssets);
renderAssets(allAssets);
const view = type ? allAssets.filter(a => a.voce_code === type) : allAssets;
updateStats(view);
renderAssets(view);
} else {
container.innerHTML = '<div class="empty-state-box"><h4>Errore nel caricamento</h4><p>' + escapeHtml(result.message || '') + '</p></div>';
}
} catch (e) {
container.innerHTML = '<div class="empty-state-box"><h4>Errore di connessione</h4><p>Impossibile caricare l'inventario.</p></div>';
container.innerHTML = '<div class="empty-state-box"><h4>Errore di connessione</h4><p>Impossibile caricare l\'inventario.</p></div>';
}
}
@@ -455,7 +487,7 @@
let criticalCount = 0;
let decommCount = 0;
assets.forEach(a => {
const t = a.asset_type || 'unknown';
const t = a.voce_label || VOCE_LABELS[a.voce_code] || 'Non classificato';
typeCount[t] = (typeCount[t] || 0) + 1;
if (a.criticality === 'critical') criticalCount++;
if (a.status === 'decommissioned') decommCount++;
@@ -469,7 +501,7 @@
// Type chips
const chipsEl = document.getElementById('stat-type-chips');
chipsEl.innerHTML = Object.entries(typeCount).map(([type, count]) =>
`<span class="type-chip"><strong>${count}</strong> ${typeLabels[type] || type}</span>`
`<span class="type-chip"><strong>${count}</strong> ${escapeHtml(type)}</span>`
).join('');
}
@@ -508,7 +540,7 @@
html += `
<tr onclick="showAssetDetail(${asset.id})" title="Visualizza dettagli">
<td style="font-weight:500;">${escapeHtml(asset.name)}</td>
<td>${typeLabels[asset.asset_type] || asset.asset_type || '-'}</td>
<td>${escapeHtml(assetTipo(asset))}</td>
<td>${escapeHtml(asset.category || '-')}</td>
<td><span class="criticality-badge ${crit}">${criticalityLabels[crit] || crit}</span></td>
<td>${relevanceBadge(asset)}</td>
@@ -656,8 +688,8 @@
showModal(escapeHtml(a.name), `
<div class="detail-grid">
<div class="detail-field">
<div class="detail-field-label">Tipo</div>
<div class="detail-field-value">${typeLabels[a.asset_type] || a.asset_type || '-'}</div>
<div class="detail-field-label">Voce / sottoclasse</div>
<div class="detail-field-value">${escapeHtml(assetTipo(a))}</div>
</div>
<div class="detail-field">
<div class="detail-field-label">Categoria</div>
@@ -773,9 +805,9 @@
function showAssetModal(title, asset, onSave) {
const a = asset || {};
const typeOptions = Object.entries(typeLabels).map(([val, label]) =>
`<option value="${val}" ${a.asset_type === val ? 'selected' : ''}>${label}</option>`
).join('');
const voceSel = a.voce_code || 'ID.AM-01';
const voceOpts = voceOptionsHtml(a.voce_code);
const subOpts = subclassOptionsHtml(voceSel, a.subclass_id);
const critOptions = Object.entries(criticalityLabels).map(([val, label]) =>
`<option value="${val}" ${a.criticality === val ? 'selected' : ''}>${label}</option>`
@@ -795,14 +827,18 @@
</div>
<div class="form-grid-2">
<div class="form-group">
<label for="asset-type" class="form-label">Tipo *</label>
<select class="form-select" id="asset-type">${typeOptions}</select>
<label for="asset-voce" class="form-label">Voce inventario *</label>
<select class="form-select" id="asset-voce" onchange="onVoceChange()">${voceOpts}</select>
</div>
<div class="form-group">
<label for="asset-category" class="form-label">Categoria</label>
<input type="text" class="form-input" id="asset-category" value="${escapeHtml(a.category || '')}" placeholder="Es: Server, Database...">
<label for="asset-subclass" class="form-label">Sottoclasse <a href="#" onclick="addSubclassPrompt();return false;" style="font-weight:400;font-size:.78rem;margin-left:6px;">+ aggiungi</a></label>
<select class="form-select" id="asset-subclass">${subOpts}</select>
</div>
</div>
<div class="form-group">
<label for="asset-category" class="form-label">Categoria (etichetta libera)</label>
<input type="text" class="form-input" id="asset-category" value="${escapeHtml(a.category || '')}" placeholder="Opzionale: nota libera">
</div>
<div class="form-group">
<label for="asset-description" class="form-label">Descrizione</label>
<textarea class="form-input" id="asset-description" rows="2">${escapeHtml(a.description || '')}</textarea>
@@ -875,7 +911,8 @@
const data = {
name: name,
asset_type: document.getElementById('asset-type').value,
voce_code: document.getElementById('asset-voce').value,
subclass_id: document.getElementById('asset-subclass').value || null,
category: document.getElementById('asset-category').value.trim() || null,
description: document.getElementById('asset-description').value.trim() || null,
criticality: document.getElementById('asset-criticality').value,
+5 -5
View File
@@ -349,14 +349,14 @@
</main>
</div>
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle + override sidebar BI (common-bi.js dopo common.js: ridefinisce solo loadSidebar) -->
<script src="vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
<script>if (window.bootstrap && bootstrap.loadFonts) { bootstrap.loadFonts('vendor/bootstrap-italia/dist/fonts'); }</script>
<script src="js/common-bi.js?v=20260618"></script>
<script src="js/i18n.js?v=20260624"></script>
<script src="js/help.js?v=20260624"></script>
<script src="js/common-bi.js?v=20260625"></script>
<script src="js/i18n.js?v=20260625"></script>
<script src="js/help.js?v=20260625"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
+5 -5
View File
@@ -183,12 +183,12 @@
</div>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></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=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script src="/js/competenze.js?v=20260617"></script>
</body>
</html>
+4 -4
View File
@@ -382,8 +382,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -393,8 +393,8 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script>
const SPRITE = '/vendor/bootstrap-italia/dist/svg/sprites.svg';
function ico(name, cls) { return `<svg class="ico ${cls||''}" aria-hidden="true"><use href="${SPRITE}#${name}"></use></svg>`; }
+5 -5
View File
@@ -143,8 +143,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -154,9 +154,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -73,7 +73,7 @@
</script>
<!-- Stessa logica della forgot-password.html: ZERO modifiche backend -->
<script src="js/api.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script>
const form = document.getElementById('forgot-form');
const err = document.getElementById('err');
+5 -5
View File
@@ -1142,8 +1142,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (accordion) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI. -->
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
@@ -1152,9 +1152,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// La guida è accessibile anche da non autenticati (utile per onboarding)
// ma se sei loggato carichi sidebar + i18n normalmente. Stessa logica
+5 -5
View File
@@ -351,8 +351,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -362,9 +362,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ──────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+2
View File
@@ -421,6 +421,8 @@ $actionMap = [
'GET:scoringGrid' => 'scoringGrid',
'GET:relevantSystems' => 'relevantSystems',
'GET:dependencyMap' => 'dependencyMap',
'GET:subclasses' => 'subclasses', // Epic C / C4 — voci + sottoclassi
'POST:subclasses' => 'addSubclass', // Epic C / C4 — aggiunta sottoclasse org
'GET:{id}' => 'get',
'PUT:{id}' => 'update',
'DELETE:{id}' => 'delete',
+2 -2
View File
@@ -910,8 +910,8 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
</main>
<script src="js/common.js?v=20260624"></script>
<script src="js/i18n.js?v=20260624"></script>
<script src="js/common.js?v=20260625"></script>
<script src="js/i18n.js?v=20260625"></script>
<script>
function showTab(id) {
document.querySelectorAll('.tab-btn').forEach((b, i) => {
+5 -5
View File
@@ -184,8 +184,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -195,9 +195,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script src="/js/isms.js"></script>
</body>
</html>
+2
View File
@@ -384,6 +384,8 @@ class NIS2API {
scoreAsset(id, criteria) { return this.post(`/assets/${id}/score`, { criteria }); }
listRelevantSystems() { return this.get('/assets/relevantSystems'); }
importAssets(data) { return this.post('/assets/import', data); } // P2 import CMDB/CSV
listAssetSubclasses() { return this.get('/assets/subclasses'); } // C4 — voci + sottoclassi
addAssetSubclass(data) { return this.post('/assets/subclasses', data); } // C4 — nuova sottoclasse org
getControlsMonitoring() { return this.get('/audit/controlsMonitoring'); }
getAcnRequirements() { return this.get('/audit/acnRequirements'); } // requisiti ACN per org
updateAcnRequirement(id, status, note) { return this.put(`/audit/acnRequirements/${id}`, { status, evidence_note: note }); }
+5 -5
View File
@@ -689,12 +689,12 @@ const HelpSystem = (function () {
intro: 'Il modulo Inventario consente di catalogare e gestire i beni ICT (hardware e software) dell\'organizzazione, fondamentale per la gestione dei rischi e la sicurezza delle reti e dei sistemi informativi ai sensi dell\'art. 24 del D.Lgs. 138/2024 (che recepisce l\'art. 21 della Direttiva (UE) 2022/2555).',
sections: [
{
heading: 'Categorizzazione dei beni',
heading: 'Le due voci dell\'inventario',
items: [
'<strong>Hardware</strong> - server, workstation, dispositivi di rete, dispositivi mobili, apparati di sicurezza.',
'<strong>Software</strong> - applicativi, sistemi operativi, database, middleware, servizi cloud.',
'<strong>Rete</strong> - reti LAN/WAN, collegamenti internet, VPN, segmenti di rete.',
'<strong>Dati</strong> - archivi, database, backup, dati personali, dati critici per il business.'
'La normativa prevede <strong>due sole voci principali</strong>: <strong>ID.AM-01 Hardware</strong> (apparati fisici: dispositivi IT, IoT, OT e mobili) e <strong>ID.AM-02 Software, servizi e sistemi</strong> (applicazioni commerciali/open-source/custom, servizi, sistemi, API).',
'Il dettaglio si esprime con le <strong>sottoclassi</strong>: alcune di default (es. Server, Dispositivi di rete, Applicazioni software, Basi dati e archivi) e altre che <strong>puoi aggiungere tu</strong> con "+ aggiungi" accanto al campo Sottoclasse.',
'Le due voci principali non sono modificabili; le sottoclassi organizzano il tuo inventario sotto di esse.',
'I flussi di rete (ID.AM-03, solo soggetti essenziali) e i servizi dei fornitori (ID.AM-04) sono registri distinti, non voci dell\'inventario asset.'
]
},
{
+5 -5
View File
@@ -151,8 +151,8 @@
</div>
<!-- Stessa logica JS della pagina live (parita' funzionale assoluta, zero backend). -->
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260625"></script>
<script src="/js/api.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI. -->
<script src="/vendor/bootstrap-italia/dist/js/bootstrap-italia.bundle.min.js"></script>
@@ -161,9 +161,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script src="/js/kb.js"></script>
<script>
// Gate auth + chrome come le altre pagine -bi.
+1 -1
View File
@@ -515,7 +515,7 @@ body { background: var(--bg-main); }
<div id="toast"></div>
<script src="js/api.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script>
// ══════════════════════════════════════════════════════
// CONFIG
+2 -2
View File
@@ -94,8 +94,8 @@
</script>
<!-- Stessa logica della login attuale: ZERO modifiche backend -->
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script>
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
+6 -6
View File
@@ -6,7 +6,7 @@
<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=20260624">
<link rel="stylesheet" href="/css/style.css?v=20260625">
<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; }
@@ -162,12 +162,12 @@
</div>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></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=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
'use strict';
/*
+1 -1
View File
@@ -424,6 +424,6 @@ curl https://nis2.agile.software/api/services/status</pre>
</div>
</div>
<script src="js/common.js?v=20260624"></script>
<script src="js/common.js?v=20260625"></script>
</body>
</html>
+5 -5
View File
@@ -112,8 +112,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -123,9 +123,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
+2 -2
View File
@@ -494,8 +494,8 @@
</script>
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script>
// ── Auth check ──────────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+5 -5
View File
@@ -138,12 +138,12 @@
</div>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></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=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script src="/js/organigramma.js?v=20260617"></script>
</body>
</html>
+5 -5
View File
@@ -333,8 +333,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -344,9 +344,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+5 -5
View File
@@ -131,12 +131,12 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></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=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script src="/js/raci.js?v=20260619"></script>
</body>
</html>
+2 -2
View File
@@ -268,8 +268,8 @@
</script>
<!-- Stessa logica della register.html: ZERO modifiche backend -->
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script>
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
+5 -5
View File
@@ -443,8 +443,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -454,9 +454,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+5 -5
View File
@@ -141,12 +141,12 @@
</div>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></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=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
'use strict';
/*
+5 -5
View File
@@ -494,8 +494,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -505,9 +505,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ──────────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+5 -5
View File
@@ -672,8 +672,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -683,9 +683,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+2 -2
View File
@@ -165,8 +165,8 @@
</main>
</div>
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script>
// ── Auth check ───────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+5 -5
View File
@@ -124,12 +124,12 @@
</div>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></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=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
'use strict';
/*
+5 -5
View File
@@ -477,8 +477,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -488,9 +488,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -13,7 +13,7 @@
* Il nome cache include la release: va BUMPATO ad ogni rilascio (vedi version.json),
* cosi' activate elimina automaticamente la shell vecchia.
*/
const CACHE = 'nis2-shell-v1.18.3';
const CACHE = 'nis2-shell-v1.18.4';
const PRECACHE = [
'/offline.html',
+5 -5
View File
@@ -292,8 +292,8 @@
</main>
</div>
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -303,9 +303,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
// ── Auth & Init ─────────────────────────────────────────
if (!checkAuth()) throw new Error('Not authenticated');
+1 -1
View File
@@ -1 +1 @@
{"version": "1.18.3", "build": "2026-06-16-v1.18.3", "date": "2026-06-16", "changelog": "Epic C / C3 - rimosso il modulo 'Analisi GAP ACN' (superato da C1: elenco Misure/Requisiti + valutazione per requisito). Tabelle acn_assessments/acn_assessment_responses droppate (erano vuote, 0 dati persi). RaciController ripuntato su cfg_nis2_misure (ex acn_measures.json). Sidebar/help/i18n/api/routing/tour demo ripuliti; integrazione lg231 (services/gap-analysis) intatta. Additivo-sottrattivo, nessun dato utente perso."}
{"version": "1.18.4", "build": "2026-06-16-v1.18.4", "date": "2026-06-16", "changelog": "Epic C / C4 - inventario a DUE voci (ID.AM-01 Hardware, ID.AM-02 Software/Servizi/Sistemi) + sottoclassi configurabili (default di sistema + create dall'utente). mig.050: cfg_inventory_voci, inventory_subclassi, assets.voce_code/subclass_id; 20 asset migrati. AssetController voce/subclass + endpoint subclasses; assets.html selettori voce+sottoclasse. Fix: apostrofo non escapato che rompeva il JS dell'Inventario; cache-buster common-bi.js (fix sidebar C3). Additivo."}
+5 -5
View File
@@ -207,8 +207,8 @@
<!-- Report Detail Modal handled by common.js showModal -->
<script src="/js/api.js?v=20260624"></script>
<script src="/js/common.js?v=20260624"></script>
<script src="/js/api.js?v=20260625"></script>
<script src="/js/common.js?v=20260625"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
riusando tutti gli helper di common.js (zero backend). -->
@@ -218,9 +218,9 @@
bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts');
}
</script>
<script src="/js/common-bi.js?v=20260618"></script>
<script src="/js/i18n.js?v=20260624"></script>
<script src="/js/help.js?v=20260624"></script>
<script src="/js/common-bi.js?v=20260625"></script>
<script src="/js/i18n.js?v=20260625"></script>
<script src="/js/help.js?v=20260625"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();
+3 -3
View File
@@ -319,9 +319,9 @@
</main>
</div>
<script src="js/api.js?v=20260624"></script>
<script src="js/common.js?v=20260624"></script>
<script src="js/i18n.js?v=20260624"></script>
<script src="js/api.js?v=20260625"></script>
<script src="js/common.js?v=20260625"></script>
<script src="js/i18n.js?v=20260625"></script>
<script>
if (!checkAuth()) throw new Error('Not authenticated');
loadSidebar();