Files
nis2-agile/public/organigramma.html
T
DevEnv nis2-agileandClaude Opus 4.8 30f4614b37 [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>
2026-06-16 19:59:35 +02:00

150 lines
9.9 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Organigramma - 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=20260617">
<style>
.org-intro { background:#eff6ff; border-left:4px solid var(--primary,#2563eb); padding:14px 18px; border-radius:8px; margin-bottom:18px; font-size:.92rem; line-height:1.6; }
.org-gov-note { font-size:.82rem; color:#92400e; background:#fffbeb; border:1px solid #fde68a; border-radius:8px; padding:10px 12px; margin:12px 0 18px; }
.org-stats { display:flex; flex-wrap:wrap; gap:10px; margin-bottom:18px; }
.org-stat { flex:1 1 150px; min-width:130px; background:#fff; border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; padding:12px 14px; }
.org-stat .n { font-size:1.5rem; font-weight:800; color:var(--primary,#2563eb); line-height:1; }
.org-stat .l { font-size:.78rem; color:var(--gray-500,#6b7280); margin-top:4px; }
/* Albero */
.org-tree { margin-top:6px; }
.org-children { margin-left:26px; border-left:2px solid var(--gray-100,#eef0f3); padding-left:16px; }
.org-node { margin:8px 0; }
.org-card { display:flex; align-items:flex-start; gap:12px; background:#fff; border:1px solid var(--gray-200,#e5e7eb); border-radius:10px; padding:12px 14px; }
.org-card.gov { border-color:#b45309; background:#fffdf7; }
.org-card-main { flex:1 1 auto; min-width:0; }
.org-role-name { font-weight:700; font-size:.98rem; }
.org-holder { font-size:.85rem; color:var(--gray-600,#4b5563); margin-top:2px; }
.org-holder.vacant { color:#9ca3af; font-style:italic; }
.org-desc { font-size:.82rem; color:var(--gray-500,#6b7280); margin-top:6px; line-height:1.5; }
.org-badge { display:inline-block; font-size:.68rem; font-weight:700; padding:2px 8px; border-radius:6px; margin-left:6px; vertical-align:middle; }
.org-badge.gov { background:#fef3c7; color:#92400e; }
.org-actions { display:flex; gap:6px; flex-wrap:wrap; flex-shrink:0; }
.org-actions .btn { padding:4px 8px; font-size:.74rem; }
.org-empty { text-align:center; padding:48px 16px; color:var(--gray-500,#6b7280); }
.org-empty .big { font-size:1.05rem; font-weight:700; color:var(--gray-700,#374151); margin-bottom:6px; }
/* Modale */
.om-overlay { display:none; position:fixed; inset:0; background:rgba(15,23,42,.5); z-index:1050; align-items:flex-start; justify-content:center; overflow:auto; padding:40px 16px; }
.om-overlay.open { display:flex; }
.om-dialog { background:#fff; border-radius:12px; width:100%; max-width:560px; box-shadow:0 20px 50px rgba(0,0,0,.25); }
.om-head { display:flex; justify-content:space-between; align-items:center; padding:16px 20px; border-bottom:1px solid var(--gray-100,#f3f4f6); }
.om-head h3 { margin:0; font-size:1.05rem; }
.om-body { padding:18px 20px; }
.om-foot { padding:14px 20px; border-top:1px solid var(--gray-100,#f3f4f6); display:flex; justify-content:flex-end; gap:10px; }
.om-field { margin-bottom:14px; }
.om-field label { display:block; font-weight:600; font-size:.86rem; margin-bottom:4px; }
.om-field input[type=text], .om-field select, .om-field textarea { width:100%; padding:9px 10px; border:1px solid var(--gray-200,#e5e7eb); border-radius:8px; font-size:.9rem; }
.om-field textarea { min-height:70px; resize:vertical; }
.om-check { display:flex; align-items:flex-start; gap:8px; background:#fffbeb; border:1px solid #fde68a; border-radius:8px; padding:10px 12px; font-size:.85rem; }
.om-check input { margin-top:3px; }
.om-err { color:#b91c1c; font-size:.82rem; min-height:18px; margin-top:2px; }
.om-close { background:none; border:none; font-size:1.4rem; line-height:1; cursor:pointer; color:var(--gray-400,#9ca3af); }
</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="org.title">Organigramma</h2>
<div class="content-header-actions">
<button class="btn btn-primary btn-sm" id="org-btn-new" onclick="orgOpenModal()" data-i18n="org.new_role">Nuovo ruolo</button>
</div>
</header>
<div class="content-body">
<div class="org-intro">
<strong>Chi è responsabile di cosa.</strong>
L'organigramma mappa i ruoli dell'organizzazione, la loro gerarchia e il titolare di ciascuno. È la base del modello relazionale NIS2: nelle fasi successive i ruoli si collegheranno a competenze, procedure, inventario e rischi.
</div>
<div class="org-gov-note">
<strong>Organi di amministrazione e direttivi (art. 23 D.Lgs. 138/2024)</strong>: marca con l'apposita spunta il ruolo che rappresenta gli organi di gestione. Su di essi ricade la responsabilità di approvare le misure di gestione del rischio e di vigilare sulla loro attuazione. Strumento di supporto organizzativo, non un parere legale.
</div>
<div class="org-stats" id="org-stats" style="display:none;">
<div class="org-stat"><div class="n" id="org-stat-total">0</div><div class="l">Ruoli definiti</div></div>
<div class="org-stat"><div class="n" id="org-stat-gov">0</div><div class="l">Organi di governance</div></div>
<div class="org-stat"><div class="n" id="org-stat-vacant">0</div><div class="l">Ruoli vacanti</div></div>
</div>
<div id="org-tree" class="org-tree" aria-live="polite"></div>
<div id="org-empty" class="org-empty" style="display:none;">
<div class="big">Nessun ruolo definito</div>
<div style="margin-bottom:16px;">Inizia a costruire l'organigramma della tua organizzazione.</div>
<button class="btn btn-primary" onclick="orgOpenModal()">Crea il primo ruolo</button>
<button class="btn btn-outline" onclick="orgScaffold()" style="margin-left:8px;">Crea struttura di base</button>
</div>
</div>
</main>
</div>
<!-- Modale crea/modifica ruolo -->
<div class="om-overlay" id="org-modal" role="dialog" aria-modal="true" aria-labelledby="org-modal-title">
<div class="om-dialog">
<div class="om-head">
<h3 id="org-modal-title">Nuovo ruolo</h3>
<button class="om-close" type="button" onclick="orgCloseModal()" aria-label="Chiudi">&times;</button>
</div>
<div class="om-body">
<input type="hidden" id="org-f-id">
<div class="om-field">
<label for="org-f-name">Nome del ruolo *</label>
<input type="text" id="org-f-name" maxlength="150" placeholder="es. Responsabile Sicurezza Informatica (CISO)">
</div>
<div class="om-field">
<label for="org-f-parent">Riporta a (ruolo padre)</label>
<select id="org-f-parent"><option value="">— Nessuno (nodo radice) —</option></select>
</div>
<div class="om-field">
<label for="org-f-holder">Titolare</label>
<select id="org-f-holder"><option value="">— Vacante —</option></select>
</div>
<div class="om-field">
<label for="org-f-desc">Responsabilità e poteri</label>
<textarea id="org-f-desc" maxlength="4000" placeholder="Descrivi cosa fa il ruolo, le sue responsabilità e i poteri di decisione/approvazione."></textarea>
</div>
<label class="om-check">
<input type="checkbox" id="org-f-gov">
<span>È un <strong>organo di amministrazione/direttivo</strong> (art. 23 D.Lgs. 138/2024): approva le misure di gestione del rischio e ne vigila l'attuazione.</span>
</label>
<div class="om-err" id="org-f-err" role="alert"></div>
</div>
<div class="om-foot">
<button class="btn btn-outline" type="button" onclick="orgCloseModal()">Annulla</button>
<button class="btn btn-primary" type="button" id="org-f-save" onclick="orgSaveRole()">Salva</button>
</div>
</div>
</div>
<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=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>