[FIX] UI V3 — allineamento help/i18n/KB/ARIA al menu top-nav
Dopo il revamp V3, allineati i layer che descrivevano ancora la sidebar V2: - topnav-v3.js: aggiunte le pagine orfane in nav (Matrice RACI → Struttura interna, Scadenziario → Monitoraggio, Compliance Journey → Guida) + cablato data-i18n su gruppi e voci del mega-menu (traduzione IT/EN come in V2). - i18n.js: nuove chiavi nav V3 (gruppi mega-menu + connettori/whistleblowing/ normative/cross-analysis/kb/architettura/simulazione/integrazioni/workflow/ audit ACN/registra incidente). - AIService.php navigationMapBlock: riscritta sul menu IN ALTO V3 (9 gruppi + pulsante "Registra incidente"), sync con topnav-v3.js NAV_GROUPS; ARIA non parla più di "barra laterale". - help.js: corrette le 3 menzioni "sidebar" → barra/menu in alto. - KB nis2_kb: nuovo docs/kb/V3_UI_NAVIGAZIONE.md + scripts/ingest-v3-ui-doc.php (ingerito host-side: 3 chunk SYSTEM, rimossi i chunk V2; ARIA RAG ora guida sul top-nav). - Cache-buster ?v=20260627n sui 3 JS modificati su tutte le pagine. Deploy: JS/HTML statici live via bind-mount; php-fpm reload (host + nis2-app) per AIService. 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
a4150b5389
commit
58b4bdbe5e
@@ -61,22 +61,29 @@ class AIService
|
||||
* system prompt dell'assistente ARIA cosi' che le indicazioni di navigazione
|
||||
* usino i nomi ESATTI del menu e non vengano inventati moduli/fasi inesistenti.
|
||||
*
|
||||
* ⚠️ TENERE SINCRONIZZATA con la fonte di verita' della sidebar:
|
||||
* public/js/common-bi.js (array navItems). Se aggiungi/rinomini una voce
|
||||
* ⚠️ TENERE SINCRONIZZATA con la fonte di verita' del menu V3:
|
||||
* public/js/topnav-v3.js (array NAV_GROUPS). Se aggiungi/rinomini una voce
|
||||
* di menu li', aggiorna anche questo elenco.
|
||||
* (Ticket #424, 2026-06-23: ARIA citava voci di menu inesistenti.)
|
||||
* (Ticket #424, 2026-06-23: ARIA citava voci di menu inesistenti.
|
||||
* Aggiornato 2026-06-27 al menu IN ALTO V3 — prima era la sidebar V2.)
|
||||
*/
|
||||
private function navigationMapBlock(): string
|
||||
{
|
||||
return "\n## MAPPA DELLA NAVIGAZIONE (barra laterale REALE dell'app)\n"
|
||||
. "Sezione \"Principale\": Dashboard · Calendario · Compliance Journey · Gap Analysis · Misure e Requisiti · Modello SGSI (ISO 27001).\n"
|
||||
. "Sezione \"Gestione\": Organigramma · Competenze · Rischi · Incidenti · Policy · Supply Chain · Stakeholder · Attivita' stakeholder · Segnalazioni · Normative · AI Cross-Analysis · Knowledge Base.\n"
|
||||
. "Sezione \"Operativo\": Formazione · Inventario · Connettori Discovery · Audit interni · Controlli periodici · Riesame di Direzione · Audit & Report.\n"
|
||||
. "Sezione \"Sistema\": Guida all'uso · Impostazioni · Architettura · Simulazione Demo · Integrazioni Esterne.\n"
|
||||
return "\n## MAPPA DELLA NAVIGAZIONE (menu IN ALTO dell'app — interfaccia V3)\n"
|
||||
. "L'app ha una BARRA IN ALTO (non piu' una barra laterale): a sinistra logo e ricerca, a destra il pulsante ROSSO \"Registra incidente\", il menu utente (Impostazioni, Esci) e il selettore lingua IT/EN. Sotto, una barra di NAVIGAZIONE con voci a tendina (mega-menu).\n"
|
||||
. "Voci dirette: \"Dashboard\" · \"Calendario\".\n"
|
||||
. "Menu \"Stakeholders\": Stakeholders · Supply-Chain · Attivita' su Stakeholders.\n"
|
||||
. "Menu \"Struttura interna\": Organigramma · Competenze · GAP Analysis · Matrice RACI · Formazione.\n"
|
||||
. "Menu \"Rischi\": Mappa dei rischi · Inventario · Policy · Connettori Discovery.\n"
|
||||
. "Menu \"Monitoraggio\": Segnalazioni ricevute · Audit interni · Audit ACN · Controlli periodici · Riesame di Direzione · Scadenziario.\n"
|
||||
. "Menu \"Lex e ISO\": Normative · Modello SGSI (ISO 27001) · Misure e requisiti ACN.\n"
|
||||
. "Menu \"AI\": AI Cross-Analysis · Knowledge Base.\n"
|
||||
. "Menu \"Guida\": Guida all'uso · Compliance Journey · Integrazioni Esterne · Impostazioni · Architettura · Simulazione Demo.\n"
|
||||
. "Incidenti: si raggiungono dal pulsante ROSSO \"Registra incidente\" in alto a destra.\n"
|
||||
. "\nREGOLE DI NAVIGAZIONE (vincolanti):\n"
|
||||
. "1. Quando indichi DOVE cliccare o COME raggiungere una funzione, usa ESCLUSIVAMENTE i nomi delle voci qui sopra, esattamente come scritti, indicando la sezione (es. \"nella sezione Operativo, voce Inventario\").\n"
|
||||
. "2. NON inventare nomi di menu, \"fasi\", codici (es. \"A4\", \"Fase 4.1\") o moduli che NON compaiono in questo elenco.\n"
|
||||
. "3. L'inventario degli asset si trova in: sezione Operativo > Inventario. La valutazione del rischio in: sezione Gestione > Rischi. Misure e requisiti in: sezione Principale > Misure e Requisiti.\n"
|
||||
. "1. Quando indichi DOVE cliccare o COME raggiungere una funzione, usa ESCLUSIVAMENTE i nomi delle voci qui sopra, esattamente come scritti, indicando il menu (es. \"nel menu in alto Rischi, voce Inventario\").\n"
|
||||
. "2. NON inventare nomi di menu, \"fasi\", codici (es. \"A4\", \"Fase 4.1\") o moduli che NON compaiono in questo elenco. NON parlare di \"barra laterale\"/\"sidebar\": il menu e' IN ALTO.\n"
|
||||
. "3. L'inventario degli asset si trova in: menu Rischi > Inventario. La valutazione del rischio in: menu Rischi > Mappa dei rischi. Misure e requisiti in: menu Lex e ISO > Misure e requisiti ACN. Per registrare un incidente: pulsante rosso \"Registra incidente\" in alto.\n"
|
||||
. "4. Se l'utente cerca una funzione che non corrisponde ad alcuna voce, dillo chiaramente e suggerisci la voce piu' vicina, senza inventarne una.\n"
|
||||
. "5. NON citare MAI nomi di file (es. \"rischi.html\", \"assets.html\") nE URL interni: l'utente naviga dalle voci di menu qui sopra, non dai file.\n"
|
||||
. "6. NON inventare pulsanti, campi o passaggi che non esistono. Se non sei certo che una funzione/pagina esista, NON darla per scontata: dillo e invita ad aprire la guida \"?\" della pagina; usa solo le funzioni descritte nel contesto/guida della pagina, se forniti.\n";
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
FONTE: Documentazione interfaccia prodotto NIS2 Agile (interfaccia "V3" — menu in alto, tema navy/oro, font Inter, icone Lucide).
|
||||
AUTORITA: Team prodotto NIS2 Agile — descrizione UI/navigazione (NON contenuto normativo).
|
||||
SCOPO: guida operativa per l'assistente AI ARIA, così da indirizzare l'utente nell'interfaccia dell'app. Descrive SOLO struttura e navigazione; nessuna affermazione normativa.
|
||||
|
||||
> ⚠️ AGGIORNATO 2026-06-27 — questo documento SOSTITUISCE le versioni precedenti che descrivevano la **sidebar a sinistra** (interfaccia "V2" Bootstrap Italia). Dal 27/6 l'interfaccia è la **V3**: il menu è una **barra IN ALTO** (top-nav a mega-menu), NON più una barra laterale. Gli URL delle pagine restano normali (`dashboard.html`, `risks.html`, …).
|
||||
|
||||
# NIS2 Agile — Interfaccia V3: struttura e navigazione
|
||||
|
||||
## Com'è fatta l'interfaccia V3
|
||||
|
||||
L'app è **multi-pagina** (non SPA): ogni modulo è una pagina a sé. La navigazione avviene da una **barra in alto** (chrome) presente in tutte le pagine applicative; **non esiste più la barra laterale a sinistra**. Tema **navy + oro**, font **Inter**, icone **Lucide**.
|
||||
|
||||
La barra in alto ha due righe:
|
||||
1. **Riga superiore**: logo "N2" e nome "NIS2 Agile" a sinistra; un **campo di ricerca** al centro; a destra il **selettore lingua IT/EN**, il pulsante **rosso "Registra incidente"**, e il **menu utente** (avatar + nome → apre Impostazioni, versione/changelog, Esci). Per i consulenti multi-organizzazione, a destra compare anche un **selettore azienda** (org-switcher).
|
||||
2. **Riga di navigazione**: voci con **menu a tendina (mega-menu)**. La voce/gruppo della pagina corrente è evidenziata (bordo oro, `aria-current`).
|
||||
|
||||
In basso a destra restano i **3 pulsanti flottanti (FAB)**: assistente **ARIA** (chat), **segnalazione** bug/supporto, **notifiche** (campana con badge contatore).
|
||||
|
||||
## Voci del menu in alto (mega-menu)
|
||||
|
||||
Voci dirette (senza tendina):
|
||||
- **Dashboard** → panoramica conformità.
|
||||
- **Calendario** → scadenze e adempimenti in vista mese/lista.
|
||||
|
||||
Gruppi a tendina:
|
||||
- **Stakeholders**: *Stakeholders* (soggetti interni/esterni) · *Supply-Chain* (rischio fornitori) · *Attività su Stakeholders* (interazioni e impegni).
|
||||
- **Struttura interna**: *Organigramma* (ruoli e deleghe) · *Competenze* (skill matrix) · *GAP Analysis* (assessment di conformità) · *Matrice RACI* (responsabilità R/A/C/I) · *Formazione* (piani formativi).
|
||||
- **Rischi**: *Mappa dei rischi* (registro rischi e trattamenti) · *Inventario* (asset e perimetro tecnico) · *Policy* (politiche) · *Connettori Discovery* (scansione rete/cloud che auto-popola l'Inventario).
|
||||
- **Monitoraggio**: *Segnalazioni ricevute* (whistleblowing) · *Audit interni* · *Audit ACN* (verifiche/report dell'autorità) · *Controlli periodici* · *Riesame di Direzione* (§9.3) · *Scadenziario* (revisioni ricorrenti).
|
||||
- **Lex e ISO**: *Normative* (Direttiva e decreto) · *Modello SGSI (ISO 27001)* · *Misure e requisiti ACN* (misure tecniche/organizzative).
|
||||
- **AI**: *AI Cross-Analysis* (analisi trasversale) · *Knowledge Base* (procedure e linee guida).
|
||||
- **Guida**: *Guida all'uso* · *Compliance Journey* (percorso guidato) · *Integrazioni Esterne* · *Impostazioni* · *Architettura* · *Simulazione Demo*.
|
||||
|
||||
## Dove trovo… (mappa funzioni → menu)
|
||||
|
||||
- **Registrare un incidente** → pulsante **rosso "Registra incidente"** in alto a destra (porta alla pagina Incidenti).
|
||||
- **Inventario degli asset** → menu **Rischi → Inventario**.
|
||||
- **Valutare la rilevanza NIS2 di un asset** → menu Rischi → Inventario, pulsante "Valuta Rilevanza NIS2" sulla riga dell'asset.
|
||||
- **Valutazione del rischio / matrice** → menu **Rischi → Mappa dei rischi**.
|
||||
- **Misure e requisiti ACN** → menu **Lex e ISO → Misure e requisiti ACN**.
|
||||
- **Gap analysis / assessment** → menu **Struttura interna → GAP Analysis**.
|
||||
- **Modello SGSI / SoA / 111 controlli ISO 27001** → menu **Lex e ISO → Modello SGSI (ISO 27001)**.
|
||||
- **Fornitori / supply chain** → menu **Stakeholders → Supply-Chain**.
|
||||
- **Segnalazioni whistleblowing** → menu **Monitoraggio → Segnalazioni ricevute**.
|
||||
- **Impostazioni, membri, API key, branding** → menu utente (in alto a destra) → **Impostazioni**, oppure menu **Guida → Impostazioni**.
|
||||
- **Cambiare lingua** → selettore **IT/EN** in alto a destra.
|
||||
- **Aiuto contestuale** → pulsante **"?"** presente nelle pagine (apre la guida della pagina); chat **ARIA** dal FAB in basso a destra.
|
||||
|
||||
## Pagine pubbliche (senza menu in alto)
|
||||
|
||||
Login, registrazione, onboarding, recupero password, landing e la pagina di **segnalazione anonima** NON hanno la barra di navigazione: sono pagine pubbliche. Le pagine standalone `simulate`/`simulate-b2b` (demo) e i portali esterni (questionario fornitore, portale stakeholder) non usano la chrome dell'app.
|
||||
|
||||
## Regole per l'assistente
|
||||
|
||||
- Indica sempre **il menu in alto** e il gruppo (es. "menu **Rischi → Inventario**"). **Non** dire "barra laterale" o "sidebar": non esiste più.
|
||||
- Usa **solo** i nomi delle voci elencate qui sopra; non inventare moduli, "fasi" o codici.
|
||||
- Non citare nomi di file o URL interni: l'utente naviga dalle voci di menu.
|
||||
@@ -165,9 +165,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -88,9 +88,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -95,9 +95,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -1125,9 +1125,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar(); // = loadTopnavV3()
|
||||
|
||||
@@ -273,9 +273,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+3
-3
@@ -380,9 +380,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -193,9 +193,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -142,9 +142,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
|
||||
@@ -195,9 +195,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/competenze.js?v=20260627"></script>
|
||||
<script>
|
||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
@@ -101,9 +101,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/connettori-discovery.js?v=20260627"></script>
|
||||
<script>
|
||||
if (typeof checkAuth === 'function' && !checkAuth()) {
|
||||
|
||||
@@ -158,9 +158,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/* Controlli periodici (control monitoring §9.1/A.8.16): controlli ricorrenti + esecuzioni (evidenza) + NC/AC. */
|
||||
|
||||
@@ -225,9 +225,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Icone inline (Lucide stroke) usate nelle stringhe HTML generate dal JS.
|
||||
// Renderizzate immediatamente (no createIcons re-run), self-host, niente sprite BI. ──
|
||||
|
||||
@@ -115,9 +115,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/dashboard.js?v=20260627"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
|
||||
+3
-3
@@ -1180,9 +1180,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// La guida è accessibile anche da non autenticati (utile per onboarding)
|
||||
// ma se sei loggato carichi chrome + i18n normalmente. ZERO modifiche backend.
|
||||
|
||||
@@ -362,9 +362,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -934,9 +934,9 @@ curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar(); // = loadTopnavV3()
|
||||
|
||||
@@ -163,9 +163,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+3
-3
@@ -202,9 +202,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/isms.js?v=20260627v3"></script>
|
||||
<script>
|
||||
// Icone Lucide della chrome + crumb/page-head statici. L'init dati e il wiring
|
||||
|
||||
+3
-3
@@ -1134,7 +1134,7 @@ const HelpSystem = (function () {
|
||||
'<strong>Tema</strong>: chiaro, scuro o automatico (segue le impostazioni del sistema operativo).',
|
||||
'<strong>Timezone</strong>: usata per visualizzare date e ore. Default Europe/Rome (CET/CEST). Cambia se lavori da un altro fuso.',
|
||||
'<strong>Notifiche email</strong>: ricevi via email gli avvisi per incidenti, scadenze e training.',
|
||||
'<strong>Notifiche in-app</strong>: mostra badge contatori nella sidebar per nuove notifiche.'
|
||||
'<strong>Notifiche in-app</strong>: mostra un badge contatore per le nuove notifiche.'
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1150,7 +1150,7 @@ const HelpSystem = (function () {
|
||||
{
|
||||
heading: 'Cambio organizzazione (multi-tenant)',
|
||||
items: [
|
||||
'Se sei membro di piu\' organizzazioni (es. consulente), nella sidebar appare un <strong>dropdown azienda</strong> in alto.',
|
||||
'Se sei membro di piu\' organizzazioni (es. consulente), nella barra in alto appare un <strong>selettore azienda</strong>.',
|
||||
'Click sul dropdown per vedere tutte le organizzazioni a cui hai accesso e quale è quella attualmente attiva (✓).',
|
||||
'Selezionando un\'altra organizzazione il sistema emette un nuovo token con il contesto aggiornato e ricarica la pagina.',
|
||||
'Il cambio è registrato nell\'audit log come <em>context_switch</em>.'
|
||||
@@ -1161,7 +1161,7 @@ const HelpSystem = (function () {
|
||||
items: [
|
||||
'Il tab <strong>Branding</strong> consente a uno studio di consulenza di personalizzare l\'interfaccia che vedranno i suoi clienti.',
|
||||
'<strong>Nome prodotto custom</strong>: sostituisce "NIS2 Agile" con il branding dello studio (es. "Studio Rossi NIS2 Suite").',
|
||||
'<strong>URL Logo</strong>: link assoluto al logo dello studio (es. https://www.tuosito.it/logo.svg). Verra\' mostrato nella sidebar e nelle pagine di login.',
|
||||
'<strong>URL Logo</strong>: link assoluto al logo dello studio (es. https://www.tuosito.it/logo.svg). Verra\' mostrato nell\'intestazione e nelle pagine di login.',
|
||||
'<strong>Colori</strong>: il primario sostituisce il blu di default (intestazioni, pulsanti principali); il secondario sostituisce il ciano (badge, accenti).',
|
||||
'Le modifiche vengono applicate automaticamente a tutti gli utenti del firm al ricaricamento della pagina.'
|
||||
]
|
||||
|
||||
@@ -96,6 +96,26 @@ const I18n = (function () {
|
||||
'nav.settings': { it: 'Impostazioni', en: 'Settings' },
|
||||
'nav.help': { it: 'Guida', en: 'Help' },
|
||||
'nav.logout': { it: 'Esci', en: 'Logout' },
|
||||
// ── Voci nav UI V3 (top-nav mega-menu) ──
|
||||
'nav.register_incident': { it: 'Registra incidente', en: 'Report incident' },
|
||||
'nav.connectors': { it: 'Connettori Discovery', en: 'Discovery Connectors' },
|
||||
'nav.whistleblowing': { it: 'Segnalazioni ricevute', en: 'Reports received' },
|
||||
'nav.normative': { it: 'Normative', en: 'Regulations' },
|
||||
'nav.cross_analysis': { it: 'AI Cross-Analysis', en: 'AI Cross-Analysis' },
|
||||
'nav.kb': { it: 'Knowledge Base', en: 'Knowledge Base' },
|
||||
'nav.architecture': { it: 'Architettura', en: 'Architecture' },
|
||||
'nav.simulate': { it: 'Simulazione Demo', en: 'Demo Simulation' },
|
||||
'nav.integrations': { it: 'Integrazioni Esterne', en: 'External Integrations' },
|
||||
'nav.workflow': { it: 'Compliance Journey', en: 'Compliance Journey' },
|
||||
'nav.audit_acn': { it: 'Audit ACN', en: 'ACN Audit' },
|
||||
// ── Etichette gruppi mega-menu V3 ──
|
||||
'nav.grp_stakeholders': { it: 'Stakeholders', en: 'Stakeholders' },
|
||||
'nav.grp_internal': { it: 'Struttura interna', en: 'Internal structure' },
|
||||
'nav.grp_risks': { it: 'Rischi', en: 'Risks' },
|
||||
'nav.grp_monitoring': { it: 'Monitoraggio', en: 'Monitoring' },
|
||||
'nav.grp_lexiso': { it: 'Lex e ISO', en: 'Law & ISO' },
|
||||
'nav.grp_ai': { it: 'AI', en: 'AI' },
|
||||
'nav.grp_guide': { it: 'Guida', en: 'Guide' },
|
||||
|
||||
// ── Dashboard ──────────────────────────────────
|
||||
'dashboard.title': { it: 'Dashboard', en: 'Dashboard' },
|
||||
|
||||
+24
-4
@@ -38,6 +38,7 @@
|
||||
{ b: 'Organigramma', s: 'Ruoli, deleghe e responsabilità', href: 'organigramma.html', ic: 'network' },
|
||||
{ b: 'Competenze', s: 'Skill matrix e copertura dei ruoli', href: 'competenze.html', ic: 'award' },
|
||||
{ b: 'GAP Analysis', s: 'Scostamento tra stato attuale e atteso', href: 'assessment.html', ic: 'git-compare' },
|
||||
{ b: 'Matrice RACI', s: 'Responsabilità per attività (R/A/C/I)', href: 'raci.html', ic: 'grid-3x3' },
|
||||
{ b: 'Formazione', s: 'Piani formativi e completamento', href: 'training.html', ic: 'graduation-cap' }
|
||||
]}]
|
||||
},
|
||||
@@ -57,7 +58,8 @@
|
||||
{ b: 'Audit interni', s: 'Programma e esiti degli audit', href: 'internal-audits.html', ic: 'clipboard-check' },
|
||||
{ b: 'Audit ACN', s: 'Verifiche e ispezioni dell’autorità', href: 'reports.html', ic: 'file-check' },
|
||||
{ b: 'Controlli periodici', s: 'Verifiche ricorrenti e check-list', href: 'controlli-periodici.html', ic: 'list-checks' },
|
||||
{ b: 'Riesame di Direzione', s: 'Esiti del riesame §9.3', href: 'management-review.html', ic: 'gauge' }
|
||||
{ b: 'Riesame di Direzione', s: 'Esiti del riesame §9.3', href: 'management-review.html', ic: 'gauge' },
|
||||
{ b: 'Scadenziario', s: 'Revisioni e adempimenti ricorrenti', href: 'review-schedule.html', ic: 'calendar-clock' }
|
||||
]}]
|
||||
},
|
||||
{
|
||||
@@ -80,6 +82,7 @@
|
||||
cols: [
|
||||
{ head: 'GUIDA', links: [
|
||||
{ b: 'Guida all’uso', s: 'Manuale e istruzioni della piattaforma', href: 'guida.html', ic: 'life-buoy' },
|
||||
{ b: 'Compliance Journey', s: 'Percorso guidato di conformità', href: 'workflow.html', ic: 'route' },
|
||||
{ b: 'Integrazioni Esterne', s: 'Connettori e servizi collegati', href: 'integrazioniext.html', ic: 'plug' }
|
||||
]},
|
||||
{ head: 'SISTEMA', links: [
|
||||
@@ -91,6 +94,23 @@
|
||||
}
|
||||
];
|
||||
|
||||
// ── i18n: traduzione voci nav (IT/EN) come nel V2. Chiave per href/gruppo. ──
|
||||
var I18N_BY_HREF = {
|
||||
'dashboard.html': 'nav.dashboard', 'calendario.html': 'nav.calendar',
|
||||
'stakeholders.html': 'nav.stakeholders', 'supply-chain.html': 'nav.supply_chain', 'stakeholder-activities.html': 'nav.stk_activities',
|
||||
'organigramma.html': 'nav.org_chart', 'competenze.html': 'nav.competences', 'assessment.html': 'nav.gap_analysis', 'raci.html': 'nav.raci', 'training.html': 'nav.training',
|
||||
'risks.html': 'nav.risks', 'assets.html': 'nav.assets', 'policies.html': 'nav.policies', 'connettori-discovery.html': 'nav.connectors',
|
||||
'whistleblowing.html': 'nav.whistleblowing', 'internal-audits.html': 'nav.internal_audits', 'reports.html': 'nav.audit_acn', 'controlli-periodici.html': 'nav.periodic_controls', 'management-review.html': 'nav.management_review', 'review-schedule.html': 'nav.review_schedule',
|
||||
'normative.html': 'nav.normative', 'isms.html': 'nav.isms', 'misure-requisiti.html': 'nav.framework',
|
||||
'cross-analysis.html': 'nav.cross_analysis', 'kb.html': 'nav.kb',
|
||||
'guida.html': 'nav.help', 'workflow.html': 'nav.workflow', 'integrazioniext.html': 'nav.integrations', 'settings.html': 'nav.settings', 'architecture.html': 'nav.architecture', 'simulate.html': 'nav.simulate'
|
||||
};
|
||||
var GROUP_I18N = {
|
||||
'Stakeholders': 'nav.grp_stakeholders', 'Struttura interna': 'nav.grp_internal', 'Rischi': 'nav.grp_risks',
|
||||
'Monitoraggio': 'nav.grp_monitoring', 'Lex e ISO': 'nav.grp_lexiso', 'AI': 'nav.grp_ai', 'Guida': 'nav.grp_guide'
|
||||
};
|
||||
function i18nAttr(key) { return key ? ' data-i18n="' + key + '"' : ''; }
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
||||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
||||
@@ -118,7 +138,7 @@
|
||||
var links = col.links.map(function (l) {
|
||||
return '<a class="mega-link" href="' + esc(l.href) + '">' +
|
||||
'<span class="mega-ic"><i data-lucide="' + esc(l.ic) + '" class="ic"></i></span>' +
|
||||
'<span><b>' + esc(l.b) + '</b><span>' + esc(l.s) + '</span></span></a>';
|
||||
'<span><b' + i18nAttr(I18N_BY_HREF[l.href]) + '>' + esc(l.b) + '</b><span>' + esc(l.s) + '</span></span></a>';
|
||||
}).join('');
|
||||
return '<div class="mega-col"><p class="mega-head">' + esc(col.head) + '</p>' + links + '</div>';
|
||||
}).join('');
|
||||
@@ -129,7 +149,7 @@
|
||||
var active = isGroupActive(g, page);
|
||||
var cls = 'nav-link' + (active ? ' active' : '');
|
||||
var aria = active ? ' aria-current="page"' : '';
|
||||
var i18n = g.i18n ? ' data-i18n="' + g.i18n + '"' : '';
|
||||
var i18n = i18nAttr(g.i18n || GROUP_I18N[g.label]);
|
||||
|
||||
if (g.href && !g.cols) {
|
||||
// Voce diretta (Dashboard, Calendario)
|
||||
@@ -183,7 +203,7 @@
|
||||
'<button class="lang-btn ' + (lang === 'it' ? 'active' : '') + '" onclick="switchLang(\'it\')" title="Italiano" aria-label="Italiano">IT</button>' +
|
||||
'<button class="lang-btn ' + (lang === 'en' ? 'active' : '') + '" onclick="switchLang(\'en\')" title="English" aria-label="English">EN</button>' +
|
||||
'</span>' +
|
||||
'<a class="btn-incident" href="incidents.html"><i data-lucide="siren" class="ic"></i><span>Registra incidente</span></a>' +
|
||||
'<a class="btn-incident" href="incidents.html"><i data-lucide="siren" class="ic"></i><span data-i18n="nav.register_incident">Registra incidente</span></a>' +
|
||||
// User menu (avatar+nome+ruolo via _loadUserInfo; click = menu)
|
||||
'<span class="user" id="topnav-user" tabindex="0" role="button" aria-haspopup="true" aria-expanded="false">' +
|
||||
'<span class="avatar" id="sidebar-user-avatar">--</span>' +
|
||||
|
||||
+3
-3
@@ -178,9 +178,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/kb.js"></script>
|
||||
<script>
|
||||
// Gate auth + chrome come le altre pagine V3.
|
||||
|
||||
@@ -150,9 +150,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -178,9 +178,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -140,9 +140,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth + chrome + i18n + help ──────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -154,9 +154,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/organigramma.js?v=20260627"></script>
|
||||
<script>
|
||||
// Render icone Lucide (chrome + crumb + bottoni). organigramma.js fa render albero
|
||||
|
||||
@@ -343,9 +343,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
+3
-3
@@ -144,9 +144,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script src="/js/raci.js?v=20260627"></script>
|
||||
<script>
|
||||
function v3Icons() { if (window.lucide && lucide.createIcons) { try { lucide.createIcons(); } catch (e) {} } }
|
||||
|
||||
+3
-3
@@ -235,9 +235,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -160,9 +160,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
+3
-3
@@ -505,9 +505,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init ──────────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -738,9 +738,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth check ───────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -218,9 +218,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -213,9 +213,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
/*
|
||||
|
||||
@@ -545,9 +545,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init ─────────────────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -193,9 +193,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth & Init (ordine V3) ─────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -261,9 +261,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
loadSidebar();
|
||||
|
||||
@@ -146,9 +146,9 @@
|
||||
<script src="/js/api.js?v=20260625d"></script>
|
||||
<script src="/js/common.js?v=20260627v3"></script>
|
||||
<script src="/vendor/lucide/lucide.min.js"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627"></script>
|
||||
<script src="/js/i18n.js?v=20260631"></script>
|
||||
<script src="/js/help.js?v=20260625d"></script>
|
||||
<script src="/js/topnav-v3.js?v=20260627n"></script>
|
||||
<script src="/js/i18n.js?v=20260627n"></script>
|
||||
<script src="/js/help.js?v=20260627n"></script>
|
||||
<script>
|
||||
// ── Auth + chrome + i18n + help ──────────────────────────────
|
||||
if (!checkAuth()) throw new Error('Not authenticated');
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - Ingest documento "Guida UI V3 (top-nav)" nella KB (RAG)
|
||||
* ----------------------------------------------------------------------------
|
||||
* Indicizza docs/kb/V3_UI_NAVIGAZIONE.md nella collection Qdrant `nis2_kb` con
|
||||
* scope SYSTEM, così che ARIA guidi l'utente nella nuova interfaccia V3 (menu in
|
||||
* alto / mega-menu). SOSTITUISCE i chunk della guida UI V2 (Bootstrap Italia).
|
||||
*
|
||||
* ESEGUIRE dove Qdrant (172.21.0.5) + Voyage sono raggiungibili.
|
||||
* Path proven (host Hetzner): il container nis2-app monta SOLO application/ e
|
||||
* public/ (NON scripts/), quindi copiare temporaneamente script+doc sotto
|
||||
* application/ ed eseguire via docker exec:
|
||||
* cp scripts/ingest-v3-ui-doc.php application/_ingest-v3.php
|
||||
* cp docs/kb/V3_UI_NAVIGAZIONE.md application/_v3_ui_doc.md
|
||||
* docker exec -i nis2-app php /var/www/nis2-agile/application/_ingest-v3.php
|
||||
* rm application/_ingest-v3.php application/_v3_ui_doc.md
|
||||
* (lo script cerca il doc in più path: BASE_PATH/docs/kb, __DIR__, APP_PATH.)
|
||||
*
|
||||
* Idempotente: cancella i chunk SYSTEM della fonte V3 *e* della vecchia fonte V2
|
||||
* prima del re-upsert. Opzioni: --dry-run (estrae e mostra le stat, niente upsert)
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') { fwrite(STDERR, "Solo CLI\n"); exit(1); }
|
||||
|
||||
if (!defined('BASE_PATH')) define('BASE_PATH', dirname(__DIR__));
|
||||
if (!defined('APP_PATH')) define('APP_PATH', BASE_PATH . '/application');
|
||||
|
||||
require_once APP_PATH . '/config/env.php';
|
||||
require_once APP_PATH . '/config/config.php';
|
||||
require_once APP_PATH . '/config/database.php';
|
||||
require_once APP_PATH . '/services/EmbedService.php';
|
||||
require_once APP_PATH . '/services/VectorService.php';
|
||||
|
||||
$opts = getopt('', ['dry-run']);
|
||||
$dryRun = isset($opts['dry-run']);
|
||||
|
||||
// Path doc: prova più candidati (root repo, accanto allo script copiato in application/)
|
||||
$CANDIDATES = [
|
||||
BASE_PATH . '/docs/kb/V3_UI_NAVIGAZIONE.md',
|
||||
__DIR__ . '/V3_UI_NAVIGAZIONE.md',
|
||||
__DIR__ . '/_v3_ui_doc.md',
|
||||
APP_PATH . '/_v3_ui_doc.md',
|
||||
];
|
||||
$DOC_FILE = '';
|
||||
foreach ($CANDIDATES as $c) { if (is_file($c)) { $DOC_FILE = $c; break; } }
|
||||
|
||||
$SOURCE = 'NIS2 Agile — Guida interfaccia V3 (top-nav)';
|
||||
$TITLE = 'Interfaccia V3 NIS2 Agile — navigazione e moduli (menu in alto)';
|
||||
$SOURCE_OLD = 'NIS2 Agile — Guida interfaccia V2 (Bootstrap Italia)'; // da rimpiazzare
|
||||
|
||||
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
|
||||
|
||||
function chunkText(string $text, int $size = 2000, int $overlap = 200): array
|
||||
{
|
||||
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
|
||||
$chunks = []; $len = mb_strlen($text, 'UTF-8'); $start = 0;
|
||||
while ($start < $len) {
|
||||
$take = min($size, $len - $start);
|
||||
$piece = mb_substr($text, $start, $take, 'UTF-8');
|
||||
if (trim($piece) !== '') $chunks[] = $piece;
|
||||
if ($start + $take >= $len) break;
|
||||
$start += ($size - $overlap);
|
||||
}
|
||||
return $chunks;
|
||||
}
|
||||
|
||||
function uuid(): string
|
||||
{
|
||||
$b = random_bytes(16);
|
||||
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
|
||||
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
|
||||
}
|
||||
|
||||
logln('=== Ingest Guida UI V3 nella KB (scope SYSTEM) ===');
|
||||
if ($dryRun) logln('MODALITA DRY-RUN: nessun upsert.');
|
||||
|
||||
if ($DOC_FILE === '') { logln('ERRORE: doc V3 non trovato in nessun path candidato.'); exit(1); }
|
||||
logln(' doc: ' . $DOC_FILE);
|
||||
$text = (string) file_get_contents($DOC_FILE);
|
||||
$text = preg_replace('/[ \t]+/', ' ', $text);
|
||||
$text = preg_replace('/\n{3,}/', "\n\n", trim($text));
|
||||
if (strlen($text) < 200) { logln('ERRORE: testo troppo breve.'); exit(1); }
|
||||
|
||||
$header = "DOCUMENTO: Guida all'interfaccia V3 di NIS2 Agile (menu in alto / mega-menu).\n"
|
||||
. "USO: aiutare l'utente a navigare la UI, trovare moduli e funzioni.\n\n";
|
||||
$chunks = chunkText($header . $text, 2000, 200);
|
||||
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
|
||||
|
||||
if ($dryRun) { logln('=== DRY-RUN completato ==='); exit(0); }
|
||||
|
||||
$embed = new EmbedService();
|
||||
$vector = new VectorService();
|
||||
$vector->ensureCollection($embed->dims);
|
||||
|
||||
// Idempotenza + supersede: rimuovi i chunk SYSTEM della fonte V3 e della vecchia V2
|
||||
foreach ([$SOURCE, $SOURCE_OLD] as $src) {
|
||||
try {
|
||||
$vector->deleteByFilter(['must' => [
|
||||
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
|
||||
['key' => 'source', 'match' => ['value' => $src]],
|
||||
]]);
|
||||
logln(" rimossi chunk precedenti per source: {$src}");
|
||||
} catch (Exception $e) { logln(' (warning) delete ' . $src . ': ' . $e->getMessage()); }
|
||||
}
|
||||
|
||||
$docUuid = uuid();
|
||||
$points = [];
|
||||
foreach ($chunks as $i => $chunk) {
|
||||
$vec = null;
|
||||
for ($try = 1; $try <= 5; $try++) {
|
||||
try { $vec = $embed->embed($chunk); break; }
|
||||
catch (Throwable $e) {
|
||||
if ($try === 5) { logln(" ERRORE embed chunk {$i} dopo 5 tentativi: " . $e->getMessage()); throw $e; }
|
||||
logln(" retry embed chunk {$i} (tentativo {$try})");
|
||||
sleep($try);
|
||||
}
|
||||
}
|
||||
$points[] = [
|
||||
'id' => uuid(),
|
||||
'vector' => $vec,
|
||||
'payload' => [
|
||||
'doc_uuid' => $docUuid,
|
||||
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
|
||||
'chunk' => $chunk,
|
||||
'entity_type' => 'guida_prodotto',
|
||||
'source' => $SOURCE,
|
||||
'lang' => 'it',
|
||||
'scope' => 'SYSTEM',
|
||||
'consulting_firm_id' => null,
|
||||
'organization_id' => null,
|
||||
'shared_with_orgs' => [],
|
||||
'uploaded_by' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
foreach (array_chunk($points, 64) as $batch) {
|
||||
$vector->upsertBatch($batch);
|
||||
}
|
||||
|
||||
// Tracking MySQL (best-effort)
|
||||
try {
|
||||
$stmt = Database::getInstance()->prepare(
|
||||
"INSERT INTO kb_uploaded_documents
|
||||
(qdrant_doc_uuid, scope, consulting_firm_id, organization_id, uploaded_by, title, entity_type, source, lang, chunk_count, shared_with_orgs, status)
|
||||
VALUES (?, 'SYSTEM', NULL, NULL, 0, ?, 'guida_prodotto', ?, 'it', ?, '[]', 'ready')"
|
||||
);
|
||||
$stmt->execute([$docUuid, $TITLE, $SOURCE, count($chunks)]);
|
||||
} catch (Exception $e) { logln(' (warning) tracking insert: ' . $e->getMessage()); }
|
||||
|
||||
logln("=== OK indicizzato: doc_uuid={$docUuid}, " . count($chunks) . " chunk ===");
|
||||
Reference in New Issue
Block a user