Completa la copertura V2 al 100% delle pagine app autenticate (pre-swap).
Additivo: nessun file live HTML promosso ancora (lo swap e separato).
- 9 pagine app -bi: isms, acn-gap, normative, whistleblowing, kb, cross-analysis,
service-continuity (doc standalone), guida, companies
- admin/{index,organizations,users}-bi.html (path corretti, common-bi.js)
- help.js: +89 righe help contestuale pagine V2 (vincolo fonti certe)
- docs/kb/V2_UI_NAVIGAZIONE.md: doc UI/navigazione V2 per la KB AI (ARIA)
- Verifica: tutte servite 200, 0 CDN/FontAwesome, sprite validi, parita ID
Prodotto via Workflow a trittici (run wf_c4962fbe-93f) + fix manuale companies/sprite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
235 lines
18 KiB
HTML
235 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Standard Continuita di Servizio (BI) — NIS2 Agile</title>
|
|
<link rel="stylesheet" href="/vendor/bootstrap-italia/dist/css/bootstrap-italia.min.css">
|
|
<style>
|
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
body{font-family:'Titillium Web','Segoe UI',-apple-system,sans-serif;font-size:14.5px;line-height:1.7;color:#1A1A2E;background:#F5F7FA}
|
|
.icon-inline{vertical-align:-0.15em;margin-right:6px}
|
|
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
.page{max-width:960px;margin:0 auto;background:#fff;box-shadow:0 4px 40px rgba(0,0,0,.08)}
|
|
.doc-header{background:linear-gradient(135deg,#0D1B2A 0%,#06c 100%);color:#fff;padding:40px 48px}
|
|
.header-top{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;margin-bottom:22px}
|
|
.brand{display:flex;align-items:center;gap:10px}
|
|
.brand-icon{width:44px;height:44px;border-radius:10px;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,.85)}
|
|
.brand-icon .icon{fill:currentColor}
|
|
.brand-name{font-size:1rem;font-weight:800}
|
|
.brand-sub{font-size:.68rem;opacity:.6}
|
|
.doc-meta{text-align:right;font-size:.75rem;opacity:.6}
|
|
.doc-meta strong{display:block;color:rgba(255,255,255,.9);opacity:1;font-size:.85rem}
|
|
.doc-title{font-size:1.65rem;font-weight:800;letter-spacing:-.02em;line-height:1.2;margin-bottom:6px}
|
|
.doc-title span{color:rgba(255,255,255,.85)}
|
|
.doc-sub{font-size:.84rem;opacity:.7}
|
|
.tag-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:18px}
|
|
.tag{display:inline-flex;align-items:center;gap:5px;padding:4px 12px;border-radius:20px;font-size:.7rem;font-weight:600;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);color:#fff}
|
|
.tag .icon{fill:currentColor}
|
|
.content{padding:36px 48px}
|
|
h2{font-size:1.25rem;font-weight:800;color:#06c;margin:36px 0 14px;padding-bottom:6px;border-bottom:2px solid #eee;display:flex;align-items:center;gap:8px}
|
|
h2:first-child{margin-top:0}
|
|
h2 .icon{fill:currentColor;flex:none}
|
|
h3{font-size:1rem;font-weight:700;color:#06c;margin:22px 0 8px}
|
|
p{margin:8px 0}ul,ol{margin:8px 0 8px 20px}li{margin:4px 0}
|
|
.info-box{background:#E8F5E9;border-left:4px solid #2E7D32;padding:14px 18px;border-radius:0 8px 8px 0;margin:16px 0;font-size:.88rem;display:flex;gap:8px;align-items:flex-start}
|
|
.info-box .icon{fill:#2E7D32;flex:none;margin-top:2px}
|
|
.warn-box{background:#FFF3E0;border-left:4px solid #F57F17;padding:14px 18px;border-radius:0 8px 8px 0;margin:16px 0;font-size:.88rem;display:flex;gap:8px;align-items:flex-start}
|
|
.warn-box .icon{fill:#F57F17;flex:none;margin-top:2px}
|
|
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:.82rem}
|
|
th{background:#f5f5f5;padding:10px 12px;text-align:left;font-weight:700;border-bottom:2px solid #ddd;font-size:.75rem;text-transform:uppercase;letter-spacing:.5px;color:#333}
|
|
td{padding:9px 12px;border-bottom:1px solid #F0F2F5;vertical-align:top}
|
|
tr:hover td{background:#FAFBFC}
|
|
code{background:#f5f5f5;padding:2px 6px;border-radius:4px;font-size:.8rem;font-family:'Roboto Mono','Courier New',monospace;color:#333}
|
|
pre{background:#1A1A2E;color:#E0E4E8;padding:16px 20px;border-radius:8px;overflow-x:auto;font-size:.78rem;line-height:1.5;margin:12px 0}
|
|
.layer{display:grid;grid-template-columns:60px 1fr;gap:0;margin:16px 0}
|
|
.layer-num{background:#06c;color:#fff;font-weight:800;font-size:1.1rem;display:flex;align-items:center;justify-content:center;border-radius:8px 0 0 8px}
|
|
.layer-body{background:#f9f9f9;padding:16px 20px;border-radius:0 8px 8px 0;border:1px solid #eee;border-left:none}
|
|
.layer-body h4{margin:0 0 6px;color:#06c;font-size:.95rem;display:flex;align-items:center;gap:6px}
|
|
.layer-body h4 .icon{fill:currentColor;flex:none}
|
|
.badge-ok{display:inline-block;background:#2E7D32;color:#fff;padding:2px 8px;border-radius:10px;font-size:.68rem;font-weight:700}
|
|
.badge-warn{display:inline-block;background:#F57F17;color:#fff;padding:2px 8px;border-radius:10px;font-size:.68rem;font-weight:700}
|
|
.badge-crit{display:inline-block;background:#C62828;color:#fff;padding:2px 8px;border-radius:10px;font-size:.68rem;font-weight:700}
|
|
.doc-footer{background:#f5f5f5;padding:24px 48px;border-top:1px solid #ddd;font-size:.78rem;color:#5F6B7A;display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px}
|
|
a{color:#06c}
|
|
:focus-visible{outline:3px solid #06c;outline-offset:2px}
|
|
@media print{body{background:#fff}.page{box-shadow:none}}
|
|
@media(max-width:700px){.content,.doc-header,.doc-footer{padding:20px}.layer{grid-template-columns:1fr}.layer-num{border-radius:8px 8px 0 0;padding:8px}.layer-body{border-radius:0 0 8px 8px;border-left:1px solid #eee}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
<header class="doc-header">
|
|
<div class="header-top">
|
|
<div class="brand">
|
|
<div class="brand-icon"><svg class="icon icon-white" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-locked"></use></svg></div>
|
|
<div><div class="brand-name">NIS2 Agile</div><div class="brand-sub">Cybersecurity Compliance NIS2</div></div>
|
|
</div>
|
|
<div class="doc-meta"><strong>Standard Operativo</strong>Continuita di Servizio</div>
|
|
</div>
|
|
<h1 class="doc-title">Strategia di <span>Continuita di Servizio</span></h1>
|
|
<div class="doc-sub">Architettura di resilienza, monitoraggio, auto-recovery e protezione dei dati</div>
|
|
<div class="tag-row">
|
|
<span class="tag"><svg class="icon icon-xs icon-white" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-calendar"></use></svg> Aprile 2026</span>
|
|
<span class="tag"><svg class="icon icon-xs icon-white" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-software"></use></svg> PHP+MySQL+Qdrant</span>
|
|
<span class="tag"><svg class="icon icon-xs icon-white" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-pa"></use></svg> Hetzner CPX31</span>
|
|
<span class="tag"><svg class="icon icon-xs icon-white" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-chart-line"></use></svg> 99.9% SLA Target</span>
|
|
</div>
|
|
</header>
|
|
<main class="content">
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-pa"></use></svg> 1. Architettura di Servizio</h2>
|
|
<p><strong>NIS2 Agile</strong> e composto dai seguenti servizi gestiti sulla piattaforma Hetzner condivisa con gli altri prodotti della suite Agile Technology.</p>
|
|
<table><caption class="sr-only">Servizi, stack tecnologico e healthcheck</caption><thead><tr><th scope="col">Servizio</th><th scope="col">Stack</th><th scope="col">Healthcheck</th></tr></thead><tbody><tr><td><code>nis2-app</code></td><td>PHP+MySQL+Qdrant</td><td><span class="badge-ok">/health</span></td></tr><tr><td><code>MySQL/PostgreSQL</code></td><td>Database condiviso</td><td><span class="badge-ok">ping</span></td></tr><tr><td><code>Qdrant</code></td><td>Vector DB (embeddings)</td><td><span class="badge-ok">HTTP 200</span></td></tr><tr><td><code>AgileHub (NEXUS)</code></td><td>Ticket + AI + Notifiche</td><td><span class="badge-ok">/health</span></td></tr></tbody></table>
|
|
|
|
<div class="info-box">
|
|
<svg class="icon" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-info-circle"></use></svg>
|
|
<div><strong>Deploy a caldo</strong>: le modifiche al codice sorgente vengono applicate via bind mount Docker o PM2 reload. Nessun rebuild necessario per aggiornamenti ordinari.</div>
|
|
</div>
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-files"></use></svg> 2. Strategia di Resilienza a 7 Livelli</h2>
|
|
|
|
<div class="layer"><div class="layer-num">L1</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-refresh"></use></svg> Auto-Restart Container / PM2</h4>
|
|
<p>Ogni servizio ha <code>restart: unless-stopped</code> (Docker) o <code>pm2 --watch</code> (Node.js). Se un processo crasha, viene riavviato automaticamente entro 10-30 secondi.</p>
|
|
<p><strong>Tempo di recovery</strong>: < 30 secondi</p>
|
|
</div></div>
|
|
|
|
<div class="layer"><div class="layer-num">L2</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-chart-line"></use></svg> Healthcheck Nativo</h4>
|
|
<p>Ogni servizio espone un endpoint <code>/health</code> o <code>/api/health</code> verificato ogni 30 secondi. Se il check fallisce 3 volte consecutive, il container/processo viene ricreato automaticamente.</p>
|
|
<p><strong>Rileva</strong>: crash applicativo, memoria esaurita, deadlock, loop infinito.</p>
|
|
</div></div>
|
|
|
|
<div class="layer"><div class="layer-num">L3</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-inbox"></use></svg> Database Retry con Reconnect</h4>
|
|
<p>Tutti i servizi implementano retry automatico su errori transitori del database (MySQL/PostgreSQL):</p>
|
|
<ul>
|
|
<li><strong>2006</strong> — Server has gone away (reconnect)</li>
|
|
<li><strong>2013</strong> — Lost connection during query (retry)</li>
|
|
<li><strong>1213</strong> — Deadlock found (retry con backoff)</li>
|
|
</ul>
|
|
<p>Strategia: 2 retry con backoff esponenziale + reconnect automatico.</p>
|
|
</div></div>
|
|
|
|
<div class="layer"><div class="layer-num">L4</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-exchange-circle"></use></svg> Resilienza API Esterne</h4>
|
|
<p>Le chiamate ad API esterne (Claude AI, ElevenLabs TTS, Voyage embeddings) hanno protezione a 3 livelli:</p>
|
|
<ol>
|
|
<li><strong>Retry con backoff</strong>: 3 tentativi su errori transitori (429, 500, 502, 503)</li>
|
|
<li><strong>Degradazione graceful</strong>: se l'AI non risponde, il prodotto funziona con i dati locali</li>
|
|
<li><strong>Frontend fallback</strong>: messaggio "AI temporaneamente non disponibile" con suggerimenti alternativi</li>
|
|
</ol>
|
|
</div></div>
|
|
|
|
<div class="layer"><div class="layer-num">L5</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-horn"></use></svg> Error Alerting in Tempo Reale</h4>
|
|
<p>Ogni errore critico genera una notifica AgileHub (campanella) per l'admin e, se configurato, un'email di alert. L'admin vede l'errore entro 30 secondi via polling badge.</p>
|
|
</div></div>
|
|
|
|
<div class="layer"><div class="layer-num">L6</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-password-visible"></use></svg> Monitoraggio Esterno AgileHub</h4>
|
|
<p>Il cron AgileHub (<code>ticket-agent-cron.sh</code>) verifica lo stato di tutti i container ogni 2 minuti. Se un prodotto non e raggiungibile, viene segnalato nel log e inviata notifica.</p>
|
|
<p>Complementare ai healthcheck interni: rileva problemi di rete, proxy Apache, DNS.</p>
|
|
</div></div>
|
|
|
|
<div class="layer"><div class="layer-num">L7</div><div class="layer-body">
|
|
<h4><svg class="icon icon-sm icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-tool"></use></svg> Manutenzione Preventiva</h4>
|
|
<p>Script automatici prevengono il degrado:</p>
|
|
<table>
|
|
<caption class="sr-only">Operazioni di manutenzione preventiva</caption>
|
|
<thead><tr><th scope="col">Operazione</th><th scope="col">Frequenza</th><th scope="col">Funzione</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>Disk monitor</td><td>Ogni 6 ore</td><td>Alert email se disco > 85%</td></tr>
|
|
<tr><td>Cleanup</td><td>Settimanale</td><td>Elimina file temporanei, Docker prune</td></tr>
|
|
<tr><td>SSL renewal</td><td>Settimanale</td><td>Rinnovo automatico Let's Encrypt</td></tr>
|
|
<tr><td>Claude auth</td><td>Orario</td><td>Refresh token OAuth Claude Code</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div></div>
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-lock"></use></svg> 3. Protezione da Abuso</h2>
|
|
<h3>Rate Limiting</h3>
|
|
<table>
|
|
<caption class="sr-only">Limiti di rate limiting per endpoint</caption>
|
|
<thead><tr><th scope="col">Endpoint</th><th scope="col">Limite</th><th scope="col">Finestra</th><th scope="col">Tipo</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>Login</td><td>5 tentativi</td><td>15 min</td><td>Per IP + lockout</td></tr>
|
|
<tr><td>API generiche</td><td>100 req</td><td>1 ora</td><td>Per chiave/utente</td></tr>
|
|
<tr><td>Password reset</td><td>3 req</td><td>1 ora</td><td>Per email</td></tr>
|
|
<tr><td>Webhook esterni</td><td>50 req</td><td>1 min</td><td>Per IP</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-tool"></use></svg> 4. Modalita Manutenzione</h2>
|
|
<p>Quando il sistema AgileHub applica un fix approvato, il <strong>maintenance mode</strong> si attiva automaticamente:</p>
|
|
<ol>
|
|
<li>Il cron agent crea il flag <code>/tmp/maintenance-NIS2.flag</code></li>
|
|
<li>Apache rileva il flag e mostra la pagina di manutenzione a tutti gli utenti</li>
|
|
<li>Il fix viene applicato (Claude nel container DevEnv)</li>
|
|
<li>Il flag viene rimosso — il prodotto torna online</li>
|
|
<li>La pagina di manutenzione si auto-aggiorna e reindirizza gli utenti</li>
|
|
</ol>
|
|
<div class="warn-box">
|
|
<svg class="icon" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-warning-circle"></use></svg>
|
|
<div><strong>Nessun intervento manuale richiesto</strong>: il maintenance mode e completamente automatico. L'utente vede un messaggio professionale e il sistema torna online da solo.</div>
|
|
</div>
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-warning"></use></svg> 5. Matrice Rischi e Mitigazione</h2>
|
|
<table>
|
|
<caption class="sr-only">Matrice rischi, probabilita, impatto, mitigazione e recovery</caption>
|
|
<thead><tr><th scope="col">Scenario</th><th scope="col">Probabilita</th><th scope="col">Impatto</th><th scope="col">Mitigazione</th><th scope="col">Recovery</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>Crash singolo servizio</td><td><span class="badge-warn">Media</span></td><td><span class="badge-warn">Medio</span></td><td>Auto-restart Docker/PM2</td><td>< 30s</td></tr>
|
|
<tr><td>Database restart</td><td><span class="badge-ok">Bassa</span></td><td><span class="badge-crit">Alto</span></td><td>DB retry + reconnect</td><td>< 5s</td></tr>
|
|
<tr><td>AI non disponibile</td><td><span class="badge-warn">Media</span></td><td><span class="badge-ok">Basso</span></td><td>3 retry + graceful</td><td>Funzioni base intatte</td></tr>
|
|
<tr><td>Disco pieno</td><td><span class="badge-ok">Bassa</span></td><td><span class="badge-crit">Alto</span></td><td>Monitor 6h + cleanup</td><td>Alert prima del 85%</td></tr>
|
|
<tr><td>SSL scaduto</td><td><span class="badge-ok">Bassa</span></td><td><span class="badge-crit">Alto</span></td><td>Auto-renewal certbot</td><td>Rinnovo 30gg prima</td></tr>
|
|
<tr><td>Attacco brute force</td><td><span class="badge-warn">Media</span></td><td><span class="badge-warn">Medio</span></td><td>Rate limit + lockout</td><td>Blocco 15 min</td></tr>
|
|
<tr><td>Errore deployment</td><td><span class="badge-warn">Media</span></td><td><span class="badge-crit">Alto</span></td><td>Maintenance mode + git revert</td><td>< 2 min rollback</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-card"></use></svg> 6. Service Level Agreement</h2>
|
|
<table>
|
|
<caption class="sr-only">Metriche di Service Level Agreement</caption>
|
|
<thead><tr><th scope="col">Metrica</th><th scope="col">Target</th><th scope="col">Misurazione</th></tr></thead>
|
|
<tbody>
|
|
<tr><td>Uptime servizio</td><td><strong>99.9%</strong> (8.7h downtime/anno)</td><td>Health monitor + log</td></tr>
|
|
<tr><td>Recovery da crash</td><td>< 30 secondi</td><td>Docker restart + healthcheck</td></tr>
|
|
<tr><td>Recovery da aggiornamento</td><td>< 5 minuti</td><td>Maintenance mode + deploy</td></tr>
|
|
<tr><td>Rilevamento downtime</td><td>< 5 minuti</td><td>Cron monitor + email alert</td></tr>
|
|
<tr><td>Tempo risposta API (p95)</td><td>< 500ms</td><td>Health endpoint response time</td></tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2><svg class="icon icon-primary" aria-hidden="true"><use href="/vendor/bootstrap-italia/dist/svg/sprites.svg#it-check-circle"></use></svg> 7. Checklist Operativa</h2>
|
|
<h3>Verifica automatica (quotidiana)</h3>
|
|
<ul>
|
|
<li>Tutti i container/processi in stato healthy/online</li>
|
|
<li>Campanella AgileHub — nessuna notifica di errore sistema</li>
|
|
<li>Email — nessun alert da health monitor</li>
|
|
</ul>
|
|
<h3>Prima di un aggiornamento manuale</h3>
|
|
<ol>
|
|
<li>Verificare che non ci siano utenti attivi (o attivare maintenance mode)</li>
|
|
<li>Eseguire l'aggiornamento</li>
|
|
<li>Verificare tutti i servizi: <code>docker compose ps</code> o <code>pm2 list</code></li>
|
|
<li>Controllare i log per errori</li>
|
|
</ol>
|
|
|
|
</main>
|
|
<footer class="doc-footer">
|
|
<div>© 2026 NIS2 Agile — <a href="https://nis2.agile.software">nis2.agile.software</a></div>
|
|
<div>Standard Continuita di Servizio v1.0 • Aprile 2026 • Agile Technology SRL</div>
|
|
</footer>
|
|
</div>
|
|
<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>
|
|
</body>
|
|
</html>
|