/**
* NIS2 Agile - Modello Organizzativo SGSI (ISO 27001/27017/27018)
* Wizard 6 step: Contesto → Leadership → Risk → SoA → Documenti → Monitoraggio.
* Pattern coerente con acn-gap.html: 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 += ''+s[1]+' ';
});
return '';
}
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 Controllo Titolo Applicabile Stato % '+soa+'
'+
'');
w.document.close();
} catch(e){ alert((e&&e.message)||(lang()==='en'?'Start the SGSI first.':'Avvia prima il SGSI.')); }
}