/** * NIS2 Agile - Modello Organizzativo SGSI (ISO 27001/27017/27018) * Wizard 6 step: Contesto → Leadership → Risk → SoA → Documenti → Monitoraggio. * Pattern coerente con le altre pagine: client api.* che ritorna `data` e lancia su errore. */ 'use strict'; const STEP_KEYS = ['isms.step1','isms.step2','isms.step3','isms.step4','isms.step5','isms.step6']; const STEP_FALLBACK = ['Contesto','Leadership','Risk','SoA','Documenti','Monitoraggio']; const THEME_LABELS = { organizational: { it:'Organizzativi', en:'Organizational' }, people: { it:'Persone', en:'People' }, physical: { it:'Fisici', en:'Physical' }, technological: { it:'Tecnologici', en:'Technological' }, privacy: { it:'Privacy (PII)', en:'Privacy (PII)' } }; let ISMS = { model:null, step:0, soaLoaded:false }; let soaSaveTimer = {}; function lang(){ try { return I18n.getLang ? I18n.getLang() : 'it'; } catch(e){ return 'it'; } } function el(id){ return document.getElementById(id); } function esc(s){ const d=document.createElement('div'); d.textContent=s==null?'':String(s); return d.innerHTML; } function hint(id, msg){ const e=el(id); if(e){ e.textContent=msg; } } 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(); renderSteps(); await loadModel(); }); function renderSteps(){ let html=''; STEP_FALLBACK.forEach(function(lbl, i){ const t = (window.I18n && I18n.t) ? I18n.t(STEP_KEYS[i]) : lbl; html += '
'+(i+1)+'
'+esc(t&&t!==STEP_KEYS[i]?t:lbl)+'
'; }); el('isms-steps').innerHTML = html; } function goStep(i){ ISMS.step = i; document.querySelectorAll('.isms-panel').forEach(function(p){ p.classList.remove('active'); }); const panel = el('panel-'+i); if (panel) panel.classList.add('active'); document.querySelectorAll('.isms-step').forEach(function(s,idx){ s.classList.toggle('active', idx===i); }); if (i===1) loadRoles(); if (i===3) loadSoa(); if (i===4) loadDocs(); if (i===5) loadReadiness(); window.scrollTo({top:0,behavior:'smooth'}); } async function loadModel(){ try { const res = await api.ismsGetModel(); ISMS.model = res.model; if (ISMS.model) fillStep1(ISMS.model); markDone(); } catch(e){ /* tabella non ancora migrata o nessun modello: si parte da zero */ } goStep(0); } function fillStep1(m){ el('f-scope').value = m.scope_statement||''; el('f-ctx-int').value = m.context_internal||''; el('f-ctx-ext').value = m.context_external||''; el('f-parties').value = (m.interested_parties||[]).join('\n'); el('f-boundaries').value = m.boundaries||''; el('f-exclusions').value = m.exclusions||''; el('f-uses-cloud').checked = !!m.uses_public_cloud; el('f-cloud-provider').checked = !!m.is_cloud_provider; el('f-pii-cloud').checked = !!m.processes_pii_in_cloud; if (m.risk_methodology!==undefined) el('f-method').value = m.risk_methodology||''; el('f-objectives').value = (m.isms_objectives||[]).join('\n'); } function linesToArr(v){ return (v||'').split('\n').map(function(s){return s.trim();}).filter(Boolean); } async function saveStep1(){ const data = { scope_statement: el('f-scope').value, context_internal: el('f-ctx-int').value, context_external: el('f-ctx-ext').value, interested_parties: linesToArr(el('f-parties').value), boundaries: el('f-boundaries').value, exclusions: el('f-exclusions').value, uses_public_cloud: el('f-uses-cloud').checked, is_cloud_provider: el('f-cloud-provider').checked, processes_pii_in_cloud: el('f-pii-cloud').checked }; try { await api.ismsSaveModel(data); hint('isms-savehint', lang()==='en'?'Saved':'Salvato'); ISMS.soaLoaded = false; // i flag cloud cambiano il perimetro SoA await loadModel(); goStep(1); } catch(e){ alert((e&&e.message)||'Errore'); } } async function saveStep3(){ try { await api.ismsSaveModel({ risk_methodology: el('f-method').value, isms_objectives: linesToArr(el('f-objectives').value) }); hint('isms-savehint', lang()==='en'?'Saved':'Salvato'); await loadModel(); goStep(3); } catch(e){ alert((e&&e.message)||'Errore'); } } // ── Ruoli (cl.5) ── async function loadRoles(){ try { const res = await api.ismsRoles(); let html=''; (res.roles||[]).forEach(function(r){ html += '
'+(r.raci||'-')+''+ ''+esc(r.role_name)+''+ ''+esc(r.responsibility||'')+''+ '
'; }); el('roles-list').innerHTML = html || '

'+(lang()==='en'?'No roles yet.':'Nessun ruolo definito.')+'

'; } catch(e){ el('roles-list').innerHTML='

'+esc((e&&e.message)||'')+'

'; } } async function addRole(){ const name = el('r-name').value.trim(); if(!name){ return; } try { await api.ismsSaveRole({ role_name:name, responsibility:el('r-resp').value, raci:el('r-raci').value }); el('r-name').value=''; el('r-resp').value=''; el('r-raci').value=''; loadRoles(); markDone(); } catch(e){ alert((e&&e.message)||'Errore'); } } async function delRole(id){ try { await api.ismsDeleteRole(id); loadRoles(); } catch(e){} } // ── SoA (cl.6.1.3) ── async function loadSoa(){ if (ISMS.soaLoaded) return; el('soa-groups').innerHTML = '

'+(lang()==='en'?'Loading…':'Caricamento…')+'

'; try { const res = await api.ismsGetSoa(); renderSoa(res); ISMS.soaLoaded = true; markDone(); } catch(e){ el('soa-groups').innerHTML = '

'+esc((e&&e.message)||'')+'

'; } } async function deriveSoa(){ try { await api.ismsDeriveSoa(); ISMS.soaLoaded=false; await loadSoa(); } catch(e){ alert((e&&e.message)||'Errore'); } } function renderSoa(res){ showSoaStats(res.stats); let html=''; (res.groups||[]).forEach(function(g, gi){ // raggruppa per tema const byTheme = {}; (g.controls||[]).forEach(function(c){ (byTheme[c.theme]=byTheme[c.theme]||[]).push(c); }); let inner=''; Object.keys(byTheme).forEach(function(th){ inner += '
'+esc(THEME_LABELS[th]?THEME_LABELS[th][lang()]:th)+'
'; byTheme[th].forEach(function(c){ inner += renderCtrl(c); }); }); html += '
'+ ''+esc(g.label)+''+(g.controls||[]).length+' '+(lang()==='en'?'controls':'controlli')+'
'+ '
'+inner+'
'; }); el('soa-groups').innerHTML = html || '

'+(lang()==='en'?'No controls.':'Nessun controllo.')+'

'; } function renderCtrl(c){ const title = lang()==='en' ? (c.title_en||c.title_it) : (c.title_it||c.title_en); const derived = (+c.derived_from_nis2) ? ''+(lang()==='en'?'from NIS2':'da NIS2')+'' : ''; const applicable = (+c.applicable)===1; const statuses = [ ['not_started', lang()==='en'?'Not started':'Da iniziare'], ['in_progress', lang()==='en'?'In progress':'In corso'], ['implemented', lang()==='en'?'Implemented':'Attuato'], ['verified', lang()==='en'?'Verified':'Verificato'] ]; let opts=''; statuses.forEach(function(s){ const sel = c.implementation_status===s[0] ? ('sel-'+s[0]) : ''; opts += ''; }); return '
'+ '
'+esc(c.control_code)+''+esc(title)+' '+derived+'
'+ '
'+ '
'+opts+'
'+ ''+ '
'; } function setSoaStatus(btn, code, status){ const wrap = btn.parentNode; Array.prototype.forEach.call(wrap.querySelectorAll('.soa-opt'), function(b){ b.className='soa-opt'; }); btn.className='soa-opt sel-'+status; const pct = {not_started:0,in_progress:50,implemented:100,verified:100}[status]; queueSoaSave(code, { control_code:code, implementation_status:status, implementation_pct:pct }); } function setSoaApplicable(code, applicable){ queueSoaSave(code, { control_code:code, applicable:applicable }); ISMS.soaLoaded=false; setTimeout(loadSoa, 400); } function setSoaJustif(code, val, applicable){ queueSoaSave(code, applicable ? { control_code:code, justification_inclusion:val } : { control_code:code, justification_exclusion:val }); } function queueSoaSave(code, payload){ hint('isms-savehint', lang()==='en'?'Saving…':'Salvataggio…'); if (soaSaveTimer[code]) clearTimeout(soaSaveTimer[code]); soaSaveTimer[code] = setTimeout(async function(){ try { const r = await api.ismsUpdateSoa(payload); showSoaStats(r.stats); hint('isms-savehint', lang()==='en'?'Saved':'Salvato'); } catch(e){ hint('isms-savehint', lang()==='en'?'Save pending':'Salvataggio in sospeso'); } }, 600); } function showSoaStats(s){ if(!s){ return; } const e = el('soa-stats'); if(e) e.textContent = (lang()==='en'?'Applicable: ':'Applicabili: ')+s.applicable+' · '+(lang()==='en'?'avg impl.: ':'impl. media: ')+s.avg_implementation_pct+'%'; } // ── Documenti (cl.7-8) ── async function loadDocs(){ try { const res = await api.ismsDocuments(); let html=''; (res.documents||[]).forEach(function(d){ const ai = (+d.ai_generated)?'AI':''; html += '
'+esc(d.title)+' '+ai+' ('+esc(d.doc_type)+')
'+ ''+esc(d.status)+'
'; }); el('docs-list').innerHTML = html || '

'+(lang()==='en'?'No documents yet.':'Nessun documento.')+'

'; } catch(e){ el('docs-list').innerHTML='

'+esc((e&&e.message)||'')+'

'; } } async function aiGenDoc(){ const btn = el('btn-aigen'); btn.disabled=true; hint('aigen-hint', lang()==='en'?'Generating draft…':'Generazione bozza in corso…'); try { await api.ismsAiGenerateDocument({ doc_type: el('d-type').value }); hint('aigen-hint', lang()==='en'?'Draft created (review required).':'Bozza creata (revisione obbligatoria).'); loadDocs(); markDone(); } catch(e){ hint('aigen-hint',''); alert((e&&e.message)||(lang()==='en'?'AI unavailable':'AI non disponibile')); } finally { btn.disabled=false; } } // ── Readiness (cl.9-10) ── async function loadReadiness(){ try { const res = await api.ismsReadiness(); el('readiness-pct').textContent = (res.overall_pct||0)+'%'; let html=''; (res.checklist||[]).forEach(function(c){ html += '
'+esc(c.clause)+''+esc(c.label)+''+ ''+(c.done?'✓':'—')+'
'; }); el('readiness-checklist').innerHTML = html; } catch(e){ el('readiness-checklist').innerHTML='

'+esc((e&&e.message)||'')+'

'; } } function markDone(){ // marca gli step "completati" in base allo stato del modello (best-effort). const m = ISMS.model; const done = [ m&&!!m.scope_statement, false, m&&!!m.risk_methodology, ISMS.soaLoaded, false, false ]; done.forEach(function(d,i){ const s=el('step-'+i); if(s) s.classList.toggle('done', !!d); }); } async function ismsExportView(){ try { const data = await api.ismsExport(); const w = window.open('', '_blank'); if(!w){ return; } const m = data.model||{}; let soa=''; (data.soa||[]).forEach(function(c){ soa += ''+esc(c.control_code)+''+esc(c.title_it||'')+''+((+c.applicable)?'Sì':'No')+''+esc(c.implementation_status||'')+''+(c.implementation_pct||0)+'%'; }); w.document.write('SGSI - '+esc((data.organization&&data.organization.name)||'')+''+ ''+ '

Modello Organizzativo SGSI (ISO/IEC 27001:2022)

'+ '

'+esc((data.organization&&data.organization.name)||'')+' — generato il '+esc(data.generated_at||'')+'

'+ '
'+esc(data.disclaimer||'')+'
'+ '

Ambito

'+esc(m.scope_statement||'—')+'

'+ '

Metodologia di risk assessment

'+esc(m.risk_methodology||'—')+'

'+ '

Statement of Applicability

'+soa+'
ControlloTitoloApplicabileStato%
'+ ''); w.document.close(); } catch(e){ alert((e&&e.message)||(lang()==='en'?'Start the SGSI first.':'Avvia prima il SGSI.')); } }