[FEAT] A4 Fase 4.3 — Matrice RACI + link m2m: raci_assignments + procedure_inventory/procedure_risk/inventory_risk/risk_measure (mig.043), RaciController org-scoped (anti-IDOR, validazione object/measure), raci.html+raci.js (Bootstrap Italia/AGID), routing+sidebar+api+help+i18n. Build+review adversariale via workflow (0 finding critical/major). Cache-buster ?v=20260619.
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
05dfba6fcd
commit
b4d47d58a2
@@ -70,8 +70,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della app: api.js + common.js (helper) + common-bi.js (override BI) -->
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script src="js/common-bi.js?v=20260618"></script>
|
||||
<script>
|
||||
// Renderizza la sidebar BI (loadSidebar è stato ridefinito da common-bi.js)
|
||||
|
||||
+4
-4
@@ -152,8 +152,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -164,8 +164,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
const FUNC_LABELS = {
|
||||
|
||||
@@ -317,8 +317,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -329,7 +329,7 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -147,8 +147,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="../js/api.js?v=20260618"></script>
|
||||
<script src="../js/common.js?v=20260618"></script>
|
||||
<script src="../js/api.js?v=20260619"></script>
|
||||
<script src="../js/common.js?v=20260619"></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). -->
|
||||
@@ -159,7 +159,7 @@
|
||||
}
|
||||
</script>
|
||||
<script src="../js/common-bi.js?v=20260618"></script>
|
||||
<script src="../js/i18n.js?v=20260618"></script>
|
||||
<script src="../js/i18n.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -169,8 +169,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="../js/api.js?v=20260618"></script>
|
||||
<script src="../js/common.js?v=20260618"></script>
|
||||
<script src="../js/api.js?v=20260619"></script>
|
||||
<script src="../js/common.js?v=20260619"></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). -->
|
||||
@@ -181,7 +181,7 @@
|
||||
}
|
||||
</script>
|
||||
<script src="../js/common-bi.js?v=20260618"></script>
|
||||
<script src="../js/i18n.js?v=20260618"></script>
|
||||
<script src="../js/i18n.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -1088,10 +1088,10 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/i18n.js?v=20260618"></script>
|
||||
<script src="js/help.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script src="js/i18n.js?v=20260619"></script>
|
||||
<script src="js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
@@ -154,8 +154,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -166,8 +166,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+4
-4
@@ -366,8 +366,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -378,8 +378,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -349,14 +349,14 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></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=20260618"></script>
|
||||
<script src="js/help.js?v=20260618"></script>
|
||||
<script src="js/i18n.js?v=20260619"></script>
|
||||
<script src="js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
|
||||
@@ -183,12 +183,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script src="/js/competenze.js?v=20260617"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -382,8 +382,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -394,7 +394,7 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></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>`; }
|
||||
|
||||
@@ -143,8 +143,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -155,8 +155,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della forgot-password.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script>
|
||||
const form = document.getElementById('forgot-form');
|
||||
const err = document.getElementById('err');
|
||||
|
||||
+4
-4
@@ -1142,8 +1142,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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>
|
||||
@@ -1153,8 +1153,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// La guida è accessibile anche da non autenticati (utile per onboarding)
|
||||
// ma se sei loggato carichi sidebar + i18n normalmente. Stessa logica
|
||||
|
||||
@@ -351,8 +351,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -363,8 +363,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -88,6 +88,7 @@ $controllerMap = [
|
||||
'organizations' => 'OrganizationController',
|
||||
'org-roles' => 'OrgRoleController', // A4 Fase 4.1 — Organigramma (ruoli/gerarchia, Art.23 governance)
|
||||
'competences' => 'CompetenceController', // A4 Fase 4.2 — Competenze (skill, requisiti ruolo, gap, alert->azione)
|
||||
'raci' => 'RaciController', // A4 Fase 4.3 — Matrice RACI + link m2m (procedure/inventario/rischi/misure)
|
||||
'assessments' => 'AssessmentController',
|
||||
'acn-gap' => 'AcnAssessmentController', // Gap Analysis ACN (Det. 164179/2025, misure/requisiti)
|
||||
'isms' => 'IsmsModelController', // Modello Organizzativo SGSI (ISO 27001/27017/27018 + SoA)
|
||||
@@ -223,6 +224,17 @@ $actionMap = [
|
||||
'POST:openAction' => 'openAction',
|
||||
],
|
||||
|
||||
// ── RaciController — Matrice RACI + link m2m (A4 Fase 4.3) ──
|
||||
'raci' => [
|
||||
'GET:matrix' => 'matrix',
|
||||
'POST:assign' => 'assign',
|
||||
'DELETE:assign' => 'unassign',
|
||||
'GET:objects' => 'objects',
|
||||
'GET:links' => 'links',
|
||||
'POST:link' => 'link',
|
||||
'DELETE:link' => 'unlink',
|
||||
],
|
||||
|
||||
// ── AssessmentController ────────────────────────
|
||||
'assessments' => [
|
||||
'GET:list' => 'list',
|
||||
|
||||
@@ -910,8 +910,8 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||
|
||||
</main>
|
||||
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/i18n.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script src="js/i18n.js?v=20260619"></script>
|
||||
<script>
|
||||
function showTab(id) {
|
||||
document.querySelectorAll('.tab-btn').forEach((b, i) => {
|
||||
|
||||
+4
-4
@@ -184,8 +184,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -196,8 +196,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script src="/js/isms.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -254,6 +254,22 @@ class NIS2API {
|
||||
compGapGrid() { return this._acn(this.get('/competences/gap-grid')); }
|
||||
compOpenAction(roleSkillId) { return this._acn(this.post('/competences/open-action', { role_skill_id: roleSkillId })); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Matrice RACI (A4 Fase 4.3) — hub ruoli↔oggetti (R/A/C/I) + link m2m
|
||||
// (procedura↔inventario, procedura↔rischio, inventario↔rischio, rischio↔misura).
|
||||
// Stesso contratto _acn: ritornano `data`, lanciano su success=false.
|
||||
// NB: i DELETE non hanno body (del() in request() invia body solo per POST/PUT),
|
||||
// quindi i parametri compositi vanno passati come query string → il backend
|
||||
// li legge da getParam() ($_REQUEST).
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
raciMatrix() { return this._acn(this.get('/raci/matrix')); }
|
||||
raciAssign(d) { return this._acn(this.post('/raci/assign', d)); }
|
||||
raciUnassign(d) { return this._acn(this.del('/raci/assign?role_id=' + encodeURIComponent(d.role_id) + '&object_type=' + encodeURIComponent(d.object_type) + '&object_id=' + encodeURIComponent(d.object_id))); }
|
||||
raciObjects(type) { return this._acn(this.get('/raci/objects?type=' + encodeURIComponent(type))); }
|
||||
raciLinks(linkType, id) { return this._acn(this.get('/raci/links?link_type=' + encodeURIComponent(linkType) + '&id=' + encodeURIComponent(id))); }
|
||||
raciLink(d) { return this._acn(this.post('/raci/link', d)); }
|
||||
raciUnlink(d) { return this._acn(this.del('/raci/link?link_type=' + encodeURIComponent(d.link_type) + '&a_id=' + encodeURIComponent(d.a_id) + '&b_id=' + encodeURIComponent(d.b_id))); }
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// Dashboard
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -197,6 +197,7 @@ function loadSidebar() {
|
||||
items: [
|
||||
{ name: 'Organigramma', href: 'organigramma.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path d="M8 2.5h4a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-1.25v2.5H15a.5.5 0 01.5.5v1.25H17a.5.5 0 01.5.5v3a.5.5 0 01-.5.5h-4a.5.5 0 01-.5-.5v-3a.5.5 0 01.5-.5h1.25V9.5H6v1.25H7.5a.5.5 0 01.5.5v3a.5.5 0 01-.5.5h-4a.5.5 0 01-.5-.5v-3a.5.5 0 01.5-.5H4.5V9.5a.5.5 0 01.5-.5h4.75V6H8a.5.5 0 01-.5-.5V3a.5.5 0 01.5-.5z"/></svg>`, i18nKey: 'nav.org_chart' },
|
||||
{ name: 'Competenze', href: 'competenze.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 11-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 8.134a1 1 0 010 1.732l-3.354.934-1.18 4.455a1 1 0 01-1.933 0L9.854 10.8 6.5 9.866a1 1 0 010-1.732l3.354-.934 1.179-4.456A1 1 0 0112 2z"/></svg>`, i18nKey: 'nav.competences' },
|
||||
{ name: 'Matrice RACI', href: 'raci.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zm0 6a1 1 0 011-1h5a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zm9 0a1 1 0 011-1h3a1 1 0 011 1v6a1 1 0 01-1 1h-3a1 1 0 01-1-1v-6z" clip-rule="evenodd"/></svg>`, i18nKey: 'nav.raci' },
|
||||
{ name: 'Rischi', href: 'risks.html', icon: iconShieldExclamation(), i18nKey: 'nav.risks' },
|
||||
{ name: 'Incidenti', href: 'incidents.html', icon: iconBell(), i18nKey: 'nav.incidents' },
|
||||
{ name: 'Policy', href: 'policies.html', icon: iconDocumentText(), i18nKey: 'nav.policies' },
|
||||
|
||||
@@ -276,6 +276,47 @@ const HelpSystem = (function () {
|
||||
]
|
||||
},
|
||||
|
||||
// ─── Matrice RACI (A4 Fase 4.3) ──────────────────────────────
|
||||
'raci': {
|
||||
title: 'Guida - Matrice RACI',
|
||||
intro: 'La Matrice RACI è l\'hub del modello relazionale: collega i ruoli dell\'organigramma agli oggetti del sistema (inventario, procedure, rischi, fornitori) indicando chi è Responsabile (R), chi è Approvatore/responsabile finale (A), chi va Consultato (C) e chi va Informato (I). In più gestisce i collegamenti molti-a-molti fra procedure, inventario, rischi e misure, così da tracciare la rete delle dipendenze. E\' uno strumento di supporto, non un parere legale.',
|
||||
sections: [
|
||||
{
|
||||
heading: 'Cos\'è la RACI',
|
||||
items: [
|
||||
'Per ogni coppia <strong>ruolo × oggetto</strong> assegni una lettera: <strong>R</strong>esponsible (esegue), <strong>A</strong>ccountable (risponde del risultato, di norma uno solo), <strong>C</strong>onsulted (consultato prima), <strong>I</strong>nformed (informato dopo).',
|
||||
'Gli oggetti collegabili sono: <strong>Inventario</strong> (gli asset), <strong>Procedure</strong> (le policy), <strong>Rischi</strong> (il registro rischi) e <strong>Fornitori</strong> (la supply chain).',
|
||||
'Lasciando vuota una cella, il ruolo non ha responsabilità formalizzate su quell\'oggetto.'
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: 'I collegamenti (link)',
|
||||
items: [
|
||||
'<strong>Procedura ↔ Inventario</strong>: quali asset sono coperti/governati da una procedura.',
|
||||
'<strong>Procedura ↔ Rischio</strong>: quali rischi una procedura contribuisce a trattare.',
|
||||
'<strong>Inventario ↔ Rischio</strong>: quali rischi insistono su un determinato asset.',
|
||||
'<strong>Rischio ↔ Misura</strong>: a quali misure ACN (per codice, es. GV.OC-04) un rischio è ricondotto.'
|
||||
]
|
||||
},
|
||||
{
|
||||
heading: 'Riferimenti normativi',
|
||||
items: [
|
||||
'<strong>GV.RR-02</strong>: ruoli, responsabilità e poteri per la cybersicurezza sono stabiliti e comunicati (base della RACI).',
|
||||
'<strong>Inventario</strong>: famiglia <strong>ID.AM</strong> (asset management) — censimento e responsabilità sugli asset.',
|
||||
'<strong>Procedure</strong>: <strong>GV.PO</strong> (policy/governance) — le procedure attuano le politiche.',
|
||||
'<strong>Rischi</strong>: <strong>art. 24 D.Lgs. 138/2024</strong> e famiglia <strong>ID.RA</strong> (risk assessment).'
|
||||
]
|
||||
}
|
||||
],
|
||||
references: [
|
||||
'NIST CSF 2.0 / GV.RR-02 - Ruoli, responsabilità e poteri stabiliti e comunicati',
|
||||
'Inventario: famiglia ID.AM (asset management)',
|
||||
'Procedure: GV.PO (policy/governance)',
|
||||
'Rischi: art. 24 D.Lgs. 138/2024 + famiglia ID.RA (risk assessment)',
|
||||
'NOTA: gli obblighi normativi in Italia derivano da NIS2 (Dir. UE 2022/2555) e dal D.Lgs. 138/2024; la matrice RACI e i framework NIST CSF/ACN sono strumenti di supporto, non un parere legale.'
|
||||
]
|
||||
},
|
||||
|
||||
// ─── Risk Management ─────────────────────────────────────────
|
||||
'risks': {
|
||||
title: 'Guida - Gestione Rischi',
|
||||
@@ -1118,6 +1159,8 @@ const HelpSystem = (function () {
|
||||
'organigramma': 'org',
|
||||
'competenze.html': 'competences',
|
||||
'competenze': 'competences',
|
||||
'raci.html': 'raci',
|
||||
'raci': 'raci',
|
||||
'risks.html': 'risks',
|
||||
'risks': 'risks',
|
||||
'incidents.html': 'incidents',
|
||||
|
||||
@@ -80,10 +80,12 @@ const I18n = (function () {
|
||||
'nav.management': { it: 'Gestione', en: 'Management' },
|
||||
'nav.org_chart': { it: 'Organigramma', en: 'Org Chart' },
|
||||
'nav.competences': { it: 'Competenze', en: 'Skills' },
|
||||
'nav.raci': { it: 'Matrice RACI', en: 'RACI Matrix' },
|
||||
'org.title': { it: 'Organigramma', en: 'Org Chart' },
|
||||
'org.subtitle': { it: 'Ruoli, responsabilità e organi di governance', en: 'Roles, responsibilities and governance bodies' },
|
||||
'org.new_role': { it: 'Nuovo ruolo', en: 'New role' },
|
||||
'comp.title': { it: 'Competenze', en: 'Skills' },
|
||||
'raci.title': { it: 'Matrice RACI', en: 'RACI Matrix' },
|
||||
'nav.risks': { it: 'Rischi', en: 'Risks' },
|
||||
'nav.incidents': { it: 'Incidenti', en: 'Incidents' },
|
||||
'nav.policies': { it: 'Policy', en: 'Policies' },
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* NIS2 Agile - Matrice RACI (A4 Fase 4.3)
|
||||
* 2 tab: Matrice RACI (ruoli × oggetti con celle R/A/C/I) · Collegamenti (link m2m).
|
||||
* Client api.raci*: ritorna `data`, lancia su success=false (api.js _acn).
|
||||
* I DELETE non hanno body → i parametri compositi viaggiano in query string (vedi api.js).
|
||||
* Ancoraggio: GV.RR-02 (ruoli/responsabilità), ID.AM (inventario), GV.PO (procedure),
|
||||
* art.24 D.Lgs. 138/2024 + ID.RA (rischi). Strumento di supporto, non un parere legale.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
let RACI = { roles: [], objects: [], assignments: {}, tab: 'matrix' };
|
||||
|
||||
function el(id) { return document.getElementById(id); }
|
||||
function esc(s) { const d = document.createElement('div'); d.textContent = (s == null ? '' : String(s)); return d.innerHTML; }
|
||||
|
||||
const OBJ_TYPE_LABELS = { inventory: 'Inventario', procedure: 'Procedura', risk: 'Rischio', supplier: 'Fornitore' };
|
||||
const RACI_LETTERS = ['R', 'A', 'C', 'I'];
|
||||
|
||||
// link_type → { label, aType, bType } (bType 'measure' = catalogo misure ACN).
|
||||
const LINK_TYPES = {
|
||||
procedure_inventory: { label: 'Procedura ↔ Inventario', aType: 'procedure', bType: 'inventory', aLabel: 'Procedura', bLabel: 'Inventario (asset)' },
|
||||
procedure_risk: { label: 'Procedura ↔ Rischio', aType: 'procedure', bType: 'risk', aLabel: 'Procedura', bLabel: 'Rischio' },
|
||||
inventory_risk: { label: 'Inventario ↔ Rischio', aType: 'inventory', bType: 'risk', aLabel: 'Inventario (asset)', bLabel: 'Rischio' },
|
||||
risk_measure: { label: 'Rischio ↔ Misura', aType: 'risk', bType: 'measure', aLabel: 'Rischio', bLabel: 'Misura ACN' }
|
||||
};
|
||||
|
||||
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 raciLoadMatrix();
|
||||
raciRenderMatrix();
|
||||
});
|
||||
|
||||
function raciKey(roleId, type, objId) { return roleId + '|' + type + '|' + objId; }
|
||||
|
||||
function raciTab(name) {
|
||||
RACI.tab = name;
|
||||
document.querySelectorAll('.cmp-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === name));
|
||||
document.querySelectorAll('.cmp-panel').forEach(p => p.classList.remove('active'));
|
||||
const panel = el('panel-' + name);
|
||||
if (panel) panel.classList.add('active');
|
||||
if (name === 'links' && !RACI._linksInit) { RACI._linksInit = true; raciLinkTypeChanged(); }
|
||||
}
|
||||
|
||||
/* ════════════════ TAB 1 — Matrice RACI ════════════════ */
|
||||
async function raciLoadMatrix() {
|
||||
try {
|
||||
const m = await api.raciMatrix();
|
||||
RACI.roles = (m && m.roles) || [];
|
||||
RACI.objects = (m && m.objects) || [];
|
||||
RACI.assignments = {};
|
||||
((m && m.assignments) || []).forEach(function (a) {
|
||||
RACI.assignments[raciKey(a.role_id, a.object_type, a.object_id)] = a.raci;
|
||||
});
|
||||
} catch (e) {
|
||||
showNotification('Errore nel caricamento della matrice: ' + (e.message || e), 'error');
|
||||
RACI.roles = []; RACI.objects = []; RACI.assignments = {};
|
||||
}
|
||||
}
|
||||
|
||||
function raciRenderMatrix() {
|
||||
const wrap = el('raci-matrix');
|
||||
el('raci-matrix-summary').textContent = RACI.roles.length + ' ruoli · ' + RACI.objects.length + ' oggetti collegabili';
|
||||
if (!RACI.roles.length) {
|
||||
wrap.innerHTML = '<div class="cmp-empty">Nessun ruolo: creane nell\'Organigramma per popolare la matrice.</div>';
|
||||
return;
|
||||
}
|
||||
if (!RACI.objects.length) {
|
||||
wrap.innerHTML = '<div class="cmp-empty">Nessun oggetto collegabile (inventario, procedure, rischi, fornitori). Aggiungili nei rispettivi moduli.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Header: oggetti raggruppati per tipo
|
||||
let head = '<thead><tr><th class="raci-corner">Ruolo \\ Oggetto</th>';
|
||||
RACI.objects.forEach(function (o) {
|
||||
head += '<th class="raci-objhead"><span class="raci-objtype">' + esc(OBJ_TYPE_LABELS[o.type] || o.type) + '</span>' + esc(o.label) + '</th>';
|
||||
});
|
||||
head += '</tr></thead>';
|
||||
|
||||
let body = '<tbody>';
|
||||
RACI.roles.forEach(function (r) {
|
||||
body += '<tr><th class="raci-rolehead">' + esc(r.role_name) + (r.is_governance_body ? ' <span class="gap-role-sub">(governance)</span>' : '') + '</th>';
|
||||
RACI.objects.forEach(function (o) {
|
||||
const cur = RACI.assignments[raciKey(r.role_id, o.type, o.id)] || '';
|
||||
const cls = cur ? ' has-' + cur.toLowerCase() : '';
|
||||
let opts = '<option value="">—</option>';
|
||||
RACI_LETTERS.forEach(function (L) { opts += '<option value="' + L + '"' + (cur === L ? ' selected' : '') + '>' + L + '</option>'; });
|
||||
const aria = 'RACI per ruolo ' + (r.role_name || '') + ' su oggetto ' + (o.label || '');
|
||||
body += '<td><select class="raci-cell-sel' + cls + '" aria-label="' + esc(aria) + '" '
|
||||
+ 'onchange="raciSetCell(' + r.role_id + ',\'' + o.type + '\',' + o.id + ',this.value,this)">' + opts + '</select></td>';
|
||||
});
|
||||
body += '</tr>';
|
||||
});
|
||||
body += '</tbody>';
|
||||
|
||||
wrap.innerHTML = '<div class="raci-matrix-wrap"><table class="raci-matrix">' + head + body + '</table></div>';
|
||||
}
|
||||
|
||||
async function raciSetCell(roleId, type, objId, value, selEl) {
|
||||
const prev = RACI.assignments[raciKey(roleId, type, objId)] || '';
|
||||
try {
|
||||
if (value) {
|
||||
await api.raciAssign({ role_id: roleId, object_type: type, object_id: objId, raci: value });
|
||||
RACI.assignments[raciKey(roleId, type, objId)] = value;
|
||||
showNotification('Responsabilità ' + value + ' assegnata.', 'success');
|
||||
} else {
|
||||
await api.raciUnassign({ role_id: roleId, object_type: type, object_id: objId });
|
||||
delete RACI.assignments[raciKey(roleId, type, objId)];
|
||||
showNotification('Responsabilità rimossa.', 'success');
|
||||
}
|
||||
if (selEl) {
|
||||
selEl.classList.remove('has-r', 'has-a', 'has-c', 'has-i');
|
||||
if (value) selEl.classList.add('has-' + value.toLowerCase());
|
||||
}
|
||||
} catch (e) {
|
||||
// ripristina il valore precedente in caso di errore
|
||||
if (selEl) {
|
||||
selEl.value = prev;
|
||||
selEl.classList.remove('has-r', 'has-a', 'has-c', 'has-i');
|
||||
if (prev) selEl.classList.add('has-' + prev.toLowerCase());
|
||||
}
|
||||
showNotification(e.message || 'Errore.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/* ════════════════ TAB 2 — Collegamenti (link m2m) ════════════════ */
|
||||
function currentLinkType() { return el('raci-linktype').value; }
|
||||
|
||||
async function raciLinkTypeChanged() {
|
||||
const lt = currentLinkType();
|
||||
const cfg = LINK_TYPES[lt];
|
||||
if (!cfg) return;
|
||||
el('raci-alabel').textContent = cfg.aLabel;
|
||||
el('raci-blabel').textContent = cfg.bLabel;
|
||||
const aSel = el('raci-aside');
|
||||
const bSel = el('raci-bside');
|
||||
aSel.innerHTML = '<option value="">— Caricamento… —</option>';
|
||||
bSel.innerHTML = '<option value="">— Caricamento… —</option>';
|
||||
try {
|
||||
const [aList, bList] = await Promise.all([api.raciObjects(cfg.aType), api.raciObjects(cfg.bType)]);
|
||||
aSel.innerHTML = raciObjOptions(aList, cfg.aType);
|
||||
bSel.innerHTML = raciObjOptions(bList, cfg.bType);
|
||||
} catch (e) {
|
||||
showNotification('Errore nel caricamento degli oggetti: ' + (e.message || e), 'error');
|
||||
aSel.innerHTML = '<option value="">— Errore —</option>';
|
||||
bSel.innerHTML = '<option value="">— Errore —</option>';
|
||||
}
|
||||
await raciLoadLinks();
|
||||
}
|
||||
|
||||
function raciObjOptions(list, type) {
|
||||
list = Array.isArray(list) ? list : [];
|
||||
if (!list.length) return '<option value="">— Nessun elemento —</option>';
|
||||
// misure: value = code (string); altri: value = id (int)
|
||||
return list.map(function (o) {
|
||||
const v = (type === 'measure') ? o.code : o.id;
|
||||
const label = (type === 'measure') ? o.label : o.label;
|
||||
return '<option value="' + esc(v) + '">' + esc(label) + '</option>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function raciLoadLinks() {
|
||||
const lt = currentLinkType();
|
||||
const cfg = LINK_TYPES[lt];
|
||||
const aId = el('raci-aside').value;
|
||||
const wrap = el('raci-links');
|
||||
if (!aId) { wrap.innerHTML = '<div class="cmp-empty">Seleziona un elemento "Lato A" per vederne i collegamenti.</div>'; return; }
|
||||
wrap.innerHTML = '<div class="cmp-empty">Caricamento…</div>';
|
||||
try {
|
||||
const links = await api.raciLinks(lt, aId);
|
||||
const rows = (Array.isArray(links) ? links : []);
|
||||
if (!rows.length) { wrap.innerHTML = '<div class="cmp-empty">Nessun collegamento per questo elemento.</div>'; return; }
|
||||
const body = rows.map(function (lk) {
|
||||
// risk_measure: { link_id, measure_code, measure_label }; altri: { link_id, b_id, b_label }
|
||||
const bId = (lt === 'risk_measure') ? lk.measure_code : lk.b_id;
|
||||
const bLabel = (lt === 'risk_measure') ? lk.measure_label : lk.b_label;
|
||||
return '<tr>'
|
||||
+ '<td>' + esc(cfg.bLabel) + '</td>'
|
||||
+ '<td><strong>' + esc(bLabel) + '</strong></td>'
|
||||
+ '<td style="text-align:right;"><button class="btn btn-outline btn-sm" onclick="raciRemoveLink(\'' + esc(String(bId)) + '\')">Rimuovi</button></td>'
|
||||
+ '</tr>';
|
||||
}).join('');
|
||||
wrap.innerHTML = '<table class="raci-linklist"><thead><tr><th>Tipo</th><th>Elemento collegato</th><th></th></tr></thead><tbody>' + body + '</tbody></table>';
|
||||
} catch (e) {
|
||||
wrap.innerHTML = '<div class="cmp-empty">Errore: ' + esc(e.message || e) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function raciAddLink() {
|
||||
const lt = currentLinkType();
|
||||
const aId = el('raci-aside').value;
|
||||
const bId = el('raci-bside').value;
|
||||
if (!aId || !bId) { showNotification('Seleziona entrambi gli elementi.', 'error'); return; }
|
||||
try {
|
||||
await api.raciLink({ link_type: lt, a_id: aId, b_id: bId });
|
||||
showNotification('Collegamento creato.', 'success');
|
||||
await raciLoadLinks();
|
||||
} catch (e) { showNotification(e.message || 'Errore.', 'error'); }
|
||||
}
|
||||
|
||||
async function raciRemoveLink(bId) {
|
||||
const lt = currentLinkType();
|
||||
const aId = el('raci-aside').value;
|
||||
if (!confirm('Rimuovere questo collegamento?')) return;
|
||||
try {
|
||||
await api.raciUnlink({ link_type: lt, a_id: aId, b_id: bId });
|
||||
showNotification('Collegamento rimosso.', 'success');
|
||||
await raciLoadLinks();
|
||||
} catch (e) { showNotification(e.message || 'Errore.', 'error'); }
|
||||
}
|
||||
+4
-4
@@ -151,8 +151,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Stessa logica JS della pagina live (parita' funzionale assoluta, zero backend). -->
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260619"></script>
|
||||
<script src="/js/api.js?v=20260619"></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>
|
||||
@@ -162,8 +162,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script src="/js/kb.js"></script>
|
||||
<script>
|
||||
// Gate auth + chrome come le altre pagine -bi.
|
||||
|
||||
@@ -515,7 +515,7 @@ body { background: var(--bg-main); }
|
||||
|
||||
<div id="toast"></div>
|
||||
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script>
|
||||
// ══════════════════════════════════════════════════════
|
||||
// CONFIG
|
||||
|
||||
+2
-2
@@ -94,8 +94,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della login attuale: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script>
|
||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||
|
||||
|
||||
@@ -424,6 +424,6 @@ curl https://nis2.agile.software/api/services/status</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -112,8 +112,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -124,8 +124,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
@@ -494,8 +494,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della onboarding.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ──────────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -138,12 +138,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script src="/js/organigramma.js?v=20260617"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -333,8 +333,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -345,8 +345,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>Matrice RACI - NIS2 Agile</title>
|
||||
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=20260617">
|
||||
<style>
|
||||
.raci-intro { background:#eff6ff; border-left:4px solid var(--primary,#2563eb); padding:14px 18px; border-radius:8px; margin-bottom:16px; font-size:.92rem; line-height:1.6; }
|
||||
.raci-note { font-size:.82rem; color:#92400e; background:#fffbeb; border:1px solid #fde68a; border-radius:8px; padding:10px 12px; margin:0 0 18px; }
|
||||
.cmp-tabs { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:18px; }
|
||||
.cmp-tab { flex:1 1 200px; min-width:160px; padding:10px 12px; border:1.5px solid var(--gray-200,#e5e7eb); border-radius:10px; background:#fff; cursor:pointer; font-size:.85rem; font-weight:600; text-align:center; }
|
||||
.cmp-tab.active { border-color:var(--primary,#2563eb); background:#eff6ff; color:var(--primary,#2563eb); }
|
||||
.cmp-panel { display:none; }
|
||||
.cmp-panel.active { display:block; }
|
||||
.cmp-toolbar { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-bottom:14px; }
|
||||
.cmp-toolbar select { padding:8px 10px; border:1px solid var(--gray-200,#e5e7eb); border-radius:8px; font-size:.88rem; }
|
||||
.cmp-empty { text-align:center; padding:34px 16px; color:var(--gray-500,#6b7280); }
|
||||
.gap-role-sub { font-size:.82rem; color:var(--gray-500,#6b7280); }
|
||||
/* Matrice RACI */
|
||||
.raci-matrix-wrap { overflow-x:auto; border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; }
|
||||
.raci-matrix { border-collapse:collapse; font-size:.84rem; min-width:100%; }
|
||||
.raci-matrix th, .raci-matrix td { padding:8px 10px; border-bottom:1px solid var(--gray-100,#f3f4f6); border-right:1px solid var(--gray-100,#f3f4f6); text-align:center; vertical-align:middle; }
|
||||
.raci-matrix thead th { background:var(--gray-50,#f9fafb); font-size:.72rem; text-transform:uppercase; letter-spacing:.03em; color:var(--gray-500,#6b7280); position:sticky; top:0; }
|
||||
.raci-matrix thead th.raci-grp { text-align:left; }
|
||||
.raci-matrix th.raci-rolehead { text-align:left; font-weight:700; color:var(--gray-700,#374151); background:#fff; position:sticky; left:0; z-index:2; min-width:180px; }
|
||||
.raci-matrix thead th.raci-corner { position:sticky; left:0; z-index:3; background:var(--gray-50,#f9fafb); text-align:left; }
|
||||
.raci-objhead { font-weight:600; min-width:130px; }
|
||||
.raci-objhead .raci-objtype { display:block; font-size:.66rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-400,#9ca3af); }
|
||||
.raci-cell-sel { width:100%; padding:5px 6px; border:1px solid var(--gray-200,#e5e7eb); border-radius:6px; font-size:.82rem; background:#fff; cursor:pointer; }
|
||||
.raci-cell-sel.has-r { background:#dcfce7; border-color:#86efac; color:#166534; font-weight:700; }
|
||||
.raci-cell-sel.has-a { background:#fee2e2; border-color:#fca5a5; color:#991b1b; font-weight:700; }
|
||||
.raci-cell-sel.has-c { background:#fef9c3; border-color:#fde047; color:#854d0e; font-weight:700; }
|
||||
.raci-cell-sel.has-i { background:#e0f2fe; border-color:#7dd3fc; color:#075985; font-weight:700; }
|
||||
.raci-legend { display:flex; gap:14px; flex-wrap:wrap; font-size:.78rem; margin:12px 2px 0; color:var(--gray-600,#4b5563); }
|
||||
.raci-legend span b { display:inline-block; width:18px; text-align:center; border-radius:4px; margin-right:4px; }
|
||||
.raci-legend .l-r b { background:#dcfce7; color:#166534; }
|
||||
.raci-legend .l-a b { background:#fee2e2; color:#991b1b; }
|
||||
.raci-legend .l-c b { background:#fef9c3; color:#854d0e; }
|
||||
.raci-legend .l-i b { background:#e0f2fe; color:#075985; }
|
||||
/* Link m2m */
|
||||
.raci-inline { display:flex; gap:8px; flex-wrap:wrap; align-items:flex-end; background:#f9fafb; border:1px solid var(--gray-100,#f3f4f6); border-radius:10px; padding:12px; margin-bottom:14px; }
|
||||
.raci-inline .fld { display:flex; flex-direction:column; gap:3px; }
|
||||
.raci-inline .fld label { font-size:.72rem; font-weight:600; color:var(--gray-500,#6b7280); }
|
||||
.raci-inline select { padding:8px 10px; border:1px solid var(--gray-200,#e5e7eb); border-radius:8px; font-size:.88rem; min-width:200px; }
|
||||
.raci-linklist { width:100%; border-collapse:collapse; font-size:.88rem; }
|
||||
.raci-linklist th, .raci-linklist td { text-align:left; padding:9px 10px; border-bottom:1px solid var(--gray-100,#f3f4f6); }
|
||||
.raci-linklist th { font-size:.74rem; text-transform:uppercase; letter-spacing:.04em; color:var(--gray-500,#6b7280); }
|
||||
.raci-linklist .btn { padding:4px 9px; font-size:.76rem; }
|
||||
.raci-arrow { color:var(--gray-400,#9ca3af); padding:0 6px; }
|
||||
</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="raci.title">Matrice RACI</h2>
|
||||
<div class="content-header-actions">
|
||||
<span class="savehint" id="raci-hint" role="status" aria-live="polite"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content-body">
|
||||
<div class="raci-intro">
|
||||
<strong>Matrice RACI & collegamenti.</strong>
|
||||
Collega i ruoli dell'organigramma agli oggetti del sistema (inventario, procedure, rischi, fornitori) assegnando le responsabilità <strong>R</strong>esponsible / <strong>A</strong>ccountable / <strong>C</strong>onsulted / <strong>I</strong>nformed. Gestisci inoltre i collegamenti molti-a-molti fra procedure, inventario, rischi e misure.
|
||||
</div>
|
||||
<div class="raci-note">
|
||||
Ancoraggio: <strong>GV.RR-02</strong> (ruoli, responsabilità e poteri stabiliti e comunicati), <strong>ID.AM</strong> (inventario), <strong>GV.PO</strong> (procedure), <strong>art. 24 D.Lgs. 138/2024</strong> e <strong>ID.RA</strong> (rischi). Strumento di supporto, non un parere legale.
|
||||
</div>
|
||||
|
||||
<div class="cmp-tabs" id="raci-tabs">
|
||||
<div class="cmp-tab active" data-tab="matrix" onclick="raciTab('matrix')">Matrice RACI</div>
|
||||
<div class="cmp-tab" data-tab="links" onclick="raciTab('links')">Collegamenti (link m2m)</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 1 — Matrice RACI -->
|
||||
<div class="cmp-panel active" id="panel-matrix">
|
||||
<div class="cmp-toolbar">
|
||||
<span class="gap-role-sub" id="raci-matrix-summary"></span>
|
||||
</div>
|
||||
<div id="raci-matrix"></div>
|
||||
<div class="raci-legend">
|
||||
<span class="l-r"><b>R</b> Responsible (esegue)</span>
|
||||
<span class="l-a"><b>A</b> Accountable (risponde del risultato)</span>
|
||||
<span class="l-c"><b>C</b> Consulted (consultato)</span>
|
||||
<span class="l-i"><b>I</b> Informed (informato)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 2 — Collegamenti (link m2m) -->
|
||||
<div class="cmp-panel" id="panel-links">
|
||||
<div class="raci-inline">
|
||||
<div class="fld">
|
||||
<label for="raci-linktype">Tipo di collegamento</label>
|
||||
<select id="raci-linktype" onchange="raciLinkTypeChanged()">
|
||||
<option value="procedure_inventory">Procedura ↔ Inventario</option>
|
||||
<option value="procedure_risk">Procedura ↔ Rischio</option>
|
||||
<option value="inventory_risk">Inventario ↔ Rischio</option>
|
||||
<option value="risk_measure">Rischio ↔ Misura</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="fld">
|
||||
<label id="raci-alabel" for="raci-aside">Lato A</label>
|
||||
<select id="raci-aside" onchange="raciLoadLinks()"></select>
|
||||
</div>
|
||||
<div class="fld">
|
||||
<label id="raci-blabel" for="raci-bside">Lato B</label>
|
||||
<select id="raci-bside"></select>
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm" onclick="raciAddLink()">Aggiungi collegamento</button>
|
||||
</div>
|
||||
<div id="raci-links"></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script src="/js/raci.js?v=20260619"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -268,8 +268,8 @@
|
||||
</script>
|
||||
|
||||
<!-- Stessa logica della register.html: ZERO modifiche backend -->
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script>
|
||||
if (api.isAuthenticated()) { window.location.href = 'dashboard.html'; }
|
||||
|
||||
|
||||
+4
-4
@@ -443,8 +443,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -455,8 +455,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+4
-4
@@ -494,8 +494,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -506,8 +506,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -672,8 +672,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -684,8 +684,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -165,8 +165,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -477,8 +477,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -489,8 +489,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -292,8 +292,8 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -304,8 +304,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"version":"1.17.0","build":"2026-06-15-v1.17.0","date":"2026-06-15","changelog":"A4 Fase 4.2 - Competenze: nuovo modulo per catalogo competenze, requisiti per ruolo, competenze possedute dalle persone, mappatura competenza-corso, calcolo gap competenze e apertura azione correttiva dal gap (riuso workflow NCR/CAPA). Backend CompetenceController + tabelle skills/role_skills/user_skills/skill_course_map (migration 042, additiva). Pagina competenze.html a 4 schede, help contestuale, voce di menu. Ancoraggio PR.AT-01/02, GV.RR-04, art.24 D.Lgs.138/2024. PWA cache bump v1.17.0."}
|
||||
{"version": "1.17.1", "build": "2026-06-15-v1.17.1", "date": "2026-06-15", "changelog": "A4 Fase 4.3 — Matrice RACI (ruoli x oggetti) + link molti-a-molti procedure/inventario/rischi/misure ACN. Nuove tabelle raci_assignments + procedure_inventory/procedure_risk/inventory_risk/risk_measure (mig.043). RaciController org-scoped (anti-IDOR), pagina raci.html (Bootstrap Italia/AGID), help+i18n. Nessuna modifica a dati esistenti."}
|
||||
@@ -207,8 +207,8 @@
|
||||
|
||||
<!-- Report Detail Modal handled by common.js showModal -->
|
||||
|
||||
<script src="/js/api.js?v=20260618"></script>
|
||||
<script src="/js/common.js?v=20260618"></script>
|
||||
<script src="/js/api.js?v=20260619"></script>
|
||||
<script src="/js/common.js?v=20260619"></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). -->
|
||||
@@ -219,8 +219,8 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/js/common-bi.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260618"></script>
|
||||
<script src="/js/help.js?v=20260618"></script>
|
||||
<script src="/js/i18n.js?v=20260619"></script>
|
||||
<script src="/js/help.js?v=20260619"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
@@ -319,9 +319,9 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/api.js?v=20260618"></script>
|
||||
<script src="js/common.js?v=20260618"></script>
|
||||
<script src="js/i18n.js?v=20260618"></script>
|
||||
<script src="js/api.js?v=20260619"></script>
|
||||
<script src="js/common.js?v=20260619"></script>
|
||||
<script src="js/i18n.js?v=20260619"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
Reference in New Issue
Block a user