nis2-agile/public/simulate-b2b.html
DevEnv nis2-agile 8b9a617fd5 [FEAT] Simulatore B2B licenze + registrazione ridotta
- simulate-nis2-b2b.php: 6 scenari autonomi (SIM-B1→B6):
  mktg login, invito con recipient data, validazione pubblica,
  registrazione ridotta, provision org, login con org, API Key M2M
- public/simulate-b2b.html: UI terminale dark con flow diagram e SSE streaming
- public/register.html:
  - Registrazione ridotta: con invito che ha recipient data mostra banner
    "Ciao [Nome]!" + campi pre-compilati read-only + solo password richiesta
  - Post-register con inviteToken: chiama provision automaticamente,
    salva nis2_org_id in localStorage, redirect a dashboard.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 15:26:23 +01:00

256 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simulazione B2B — NIS2 Agile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
:root { --bg:#0f172a;--bg2:#1e293b;--bg3:#0f2d3d;--primary:#06B6D4;--border:#1e3a5f;
--text:#e2e8f0;--muted:#64748b;--ok:#22d3ee;--warn:#fbbf24;--err:#f87171;--phase:#38bdf8; }
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--text);font-family:'Segoe UI',system-ui,sans-serif;min-height:100vh}
.header{background:var(--bg2);border-bottom:1px solid var(--border);padding:14px 24px;display:flex;align-items:center;gap:16px}
.header-logo{display:flex;align-items:center;gap:10px;font-weight:800;font-size:1.1rem;color:var(--primary)}
.header-logo svg{width:28px;height:28px}
.header-badge{background:#164e63;color:var(--primary);font-size:.72rem;font-weight:700;padding:3px 10px;border-radius:20px;border:1px solid var(--primary)}
.header-back{margin-left:auto;color:var(--muted);font-size:.82rem;text-decoration:none}
.header-back:hover{color:var(--primary)}
.container{max-width:960px;margin:0 auto;padding:24px 20px}
.flow-diagram{display:flex;align-items:center;justify-content:center;gap:0;margin-bottom:24px;flex-wrap:wrap;
background:var(--bg2);border-radius:12px;padding:16px 20px;border:1px solid var(--border)}
.flow-step{display:flex;flex-direction:column;align-items:center;gap:5px;min-width:80px}
.flow-icon{width:40px;height:40px;border-radius:10px;background:var(--bg3);border:1.5px solid var(--border);
display:flex;align-items:center;justify-content:center;font-size:.95rem;color:var(--muted);transition:all .3s}
.flow-icon.active{border-color:var(--primary);color:var(--primary);background:rgba(6,182,212,.1)}
.flow-icon.done{border-color:var(--ok);color:var(--ok);background:rgba(34,211,238,.1)}
.flow-label{font-size:.62rem;color:var(--muted);text-align:center;font-weight:600}
.flow-arrow{color:var(--border);font-size:.75rem;padding:0 4px;margin-bottom:18px}
.btn{padding:10px 20px;border:none;border-radius:8px;font-size:.875rem;font-weight:700;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:8px}
.btn-primary{background:var(--primary);color:#0f172a}
.btn-primary:hover:not(:disabled){background:#0891b2;color:#fff}
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
.btn-danger{background:#7f1d1d;color:#fca5a5;border:1px solid #991b1b}
.btn-danger:hover:not(:disabled){background:#991b1b}
.btn-gray{background:var(--bg2);color:#94a3b8;border:1px solid var(--border)}
.btn-gray:hover{background:#334155}
.controls{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px}
.stats-bar{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap}
.stat-pill{display:flex;align-items:center;gap:6px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:6px 12px;font-size:.8rem;font-weight:700}
.stat-pass{color:var(--ok)}.stat-warn{color:var(--warn)}.stat-fail{color:var(--err)}.stat-skip{color:var(--muted)}
.progress-wrap{margin-bottom:16px}
.progress-bar{height:5px;background:var(--bg2);border-radius:3px;overflow:hidden}
.progress-fill{height:100%;width:0%;background:linear-gradient(90deg,var(--primary),#0ea5e9);transition:width .4s ease;border-radius:3px}
.progress-label{font-size:.72rem;color:var(--muted);margin-top:4px;text-align:right}
.terminal{background:#080f1a;border:1px solid var(--border);border-radius:12px;height:460px;overflow-y:auto;
font-family:'JetBrains Mono','Fira Code',monospace;font-size:.78rem;line-height:1.6;padding:14px 16px}
.term-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.term-dot{width:10px;height:10px;border-radius:50%}
.term-title{color:var(--muted);font-size:.72rem;margin-left:auto}
.log-line{padding:1px 0}
.log-ok{color:var(--ok)}.log-warn{color:var(--warn)}.log-error{color:var(--err)}
.log-info{color:#94a3b8}
.log-phase{color:var(--phase);font-weight:700;border-top:1px solid var(--border);padding-top:8px;margin-top:6px}
.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:20px}
.info-card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px 16px}
.info-card-title{font-size:.72rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px}
.info-row{display:flex;justify-content:space-between;font-size:.78rem;padding:3px 0;border-bottom:1px solid rgba(30,58,95,.5)}
.info-row:last-child{border-bottom:none}
.info-key{color:var(--muted)}.info-val{color:var(--text);font-family:monospace;font-size:.75rem}
.done-banner{display:none;background:linear-gradient(135deg,#0a2638,#0c3a50);border:1px solid var(--primary);
border-radius:12px;padding:16px 20px;margin-top:16px;text-align:center}
.done-banner.visible{display:block}
.done-title{font-size:1rem;font-weight:800;color:var(--primary);margin-bottom:6px}
.done-sub{font-size:.82rem;color:#94a3b8}
</style>
</head>
<body>
<div class="header">
<div class="header-logo">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/>
</svg>
NIS2 Agile
</div>
<div class="header-badge">B2B SIM</div>
<span style="color:var(--muted);font-size:.82rem;">Simulatore Flusso Licenze B2B</span>
<a href="simulate.html" class="header-back"><i class="fas fa-arrow-left"></i> Sim Standard</a>
<a href="dashboard.html" class="header-back" style="margin-left:8px;"><i class="fas fa-home"></i> Dashboard</a>
</div>
<div class="container">
<div class="flow-diagram">
<div class="flow-step"><div class="flow-icon" id="fi-1"><i class="fas fa-bullhorn"></i></div><div class="flow-label">SIM-B1<br>Mktg Invito</div></div>
<div class="flow-arrow"></div>
<div class="flow-step"><div class="flow-icon" id="fi-2"><i class="fas fa-check-circle"></i></div><div class="flow-label">SIM-B2<br>Valida</div></div>
<div class="flow-arrow"></div>
<div class="flow-step"><div class="flow-icon" id="fi-3"><i class="fas fa-user-plus"></i></div><div class="flow-label">SIM-B3<br>Registrazione</div></div>
<div class="flow-arrow"></div>
<div class="flow-step"><div class="flow-icon" id="fi-4"><i class="fas fa-building"></i></div><div class="flow-label">SIM-B4<br>Provision Org</div></div>
<div class="flow-arrow"></div>
<div class="flow-step"><div class="flow-icon" id="fi-5"><i class="fas fa-sign-in-alt"></i></div><div class="flow-label">SIM-B5<br>Login</div></div>
<div class="flow-arrow"></div>
<div class="flow-step"><div class="flow-icon" id="fi-6"><i class="fas fa-key"></i></div><div class="flow-label">SIM-B6<br>API Key M2M</div></div>
</div>
<div class="controls">
<button id="btnRun" class="btn btn-primary" onclick="runSim()">
<i class="fas fa-play"></i><span id="btnLabel">Avvia Simulazione B2B</span>
</button>
<button id="btnStop" class="btn btn-danger" onclick="stopSim()" disabled>
<i class="fas fa-stop"></i> Interrompi
</button>
<button class="btn btn-gray" onclick="resetSim()">
<i class="fas fa-trash-alt"></i> Reset Dati Demo
</button>
<button class="btn btn-gray" onclick="clearLog()">
<i class="fas fa-eraser"></i> Pulisci
</button>
</div>
<div class="stats-bar">
<div class="stat-pill stat-pass"><i class="fas fa-check-circle"></i> <span id="cnt-pass">0</span> Pass</div>
<div class="stat-pill stat-warn"><i class="fas fa-exclamation-triangle"></i> <span id="cnt-warn">0</span> Warn</div>
<div class="stat-pill stat-fail"><i class="fas fa-times-circle"></i> <span id="cnt-fail">0</span> Fail</div>
<div class="stat-pill stat-skip"><i class="fas fa-arrow-right"></i> <span id="cnt-skip">0</span> Skip</div>
</div>
<div class="progress-wrap">
<div class="progress-bar"><div class="progress-fill" id="progFill"></div></div>
<div class="progress-label" id="progLabel">Pronto</div>
</div>
<div class="terminal" id="terminal">
<div class="term-header">
<div class="term-dot" style="background:#ef4444;"></div>
<div class="term-dot" style="background:#fbbf24;"></div>
<div class="term-dot" style="background:#22d3ee;"></div>
<span class="term-title">NIS2 B2B Simulator</span>
</div>
<div id="logContainer">
<div class="log-line log-info">Pronto. Clicca "Avvia Simulazione B2B" per testare il ciclo completo Mktg → Invito → Registrazione → Provision → Login → API Key.</div>
</div>
</div>
<div class="info-grid">
<div class="info-card">
<div class="info-card-title"><i class="fas fa-bullhorn"></i> Operatore Mktg</div>
<div class="info-row"><span class="info-key">Email</span><span class="info-val">sim-b2b-mktg@nis2agile.it</span></div>
<div class="info-row"><span class="info-key">Ruolo</span><span class="info-val">super_admin</span></div>
<div class="info-row"><span class="info-key">Canale invito</span><span class="info-val">sim-b2b</span></div>
</div>
<div class="info-card">
<div class="info-card-title"><i class="fas fa-user"></i> Cliente B2B</div>
<div class="info-row"><span class="info-key">Email</span><span class="info-val">laura.bianchi@techstart.b2b.demo</span></div>
<div class="info-row"><span class="info-key">Azienda</span><span class="info-val">TechStart S.r.l.</span></div>
<div class="info-row"><span class="info-key">P.IVA</span><span class="info-val">03456789012</span></div>
</div>
<div class="info-card">
<div class="info-card-title"><i class="fas fa-ticket-alt"></i> Invito B2B</div>
<div class="info-row"><span class="info-key">Piano</span><span class="info-val">professional · 12 mesi</span></div>
<div class="info-row"><span class="info-key">Dati pre-fill</span><span class="info-val">nome, email, P.IVA, azienda</span></div>
<div class="info-row"><span class="info-key">Scadenza</span><span class="info-val">30 giorni</span></div>
</div>
<div class="info-card">
<div class="info-card-title"><i class="fas fa-shield-alt"></i> Registrazione Ridotta</div>
<div class="info-row"><span class="info-key">Campo richiesto</span><span class="info-val">solo password</span></div>
<div class="info-row"><span class="info-key">Provision</span><span class="info-val">automatico post-register</span></div>
<div class="info-row"><span class="info-key">Redirect</span><span class="info-val">dashboard (org pronta)</span></div>
</div>
</div>
<div class="done-banner" id="doneBanner">
<div class="done-title"><i class="fas fa-check-circle"></i> Flusso B2B completato con successo</div>
<div class="done-sub" id="doneSub"></div>
</div>
</div>
<script>
let evtSrc = null;
let phaseN = 0;
const LABELS = ['','Mktg Invito','Valida Invito','Reg. Ridotta','Provision Org','Login + Org','API Key M2M'];
function upProg(pct, lbl) {
document.getElementById('progFill').style.width = pct + '%';
document.getElementById('progLabel').textContent = lbl || pct + '%';
}
function appendLog(msg, type) {
const el = document.createElement('div');
el.className = 'log-line log-' + (type || 'info');
el.textContent = msg;
document.getElementById('logContainer').appendChild(el);
const t = document.getElementById('terminal');
t.scrollTop = t.scrollHeight;
}
function clearLog() {
document.getElementById('logContainer').innerHTML = '';
upProg(0, 'Pronto');
document.getElementById('doneBanner').classList.remove('visible');
for (let i=1;i<=6;i++) { const e=document.getElementById('fi-'+i); e.classList.remove('active','done'); }
['pass','warn','fail','skip'].forEach(k => document.getElementById('cnt-'+k).textContent='0');
}
function runSim() {
if (evtSrc) { evtSrc.close(); evtSrc=null; }
clearLog();
const run=document.getElementById('btnRun'), stop=document.getElementById('btnStop');
run.disabled=true; stop.disabled=false;
document.getElementById('btnLabel').textContent='Simulazione in corso...';
upProg(2,'Avvio...');
evtSrc = new EventSource('simulate-nis2-b2b.php?t=' + Date.now());
phaseN = 0;
evtSrc.onmessage = (e) => {
const d=JSON.parse(e.data), t=d.t||'info', m=d.m||'';
if (t === 'done') {
const st=d.stats||{};
['pass','warn','fail','skip'].forEach(k => document.getElementById('cnt-'+k).textContent=st[k]??0);
upProg(100,'Completato');
for(let i=1;i<=6;i++){const el=document.getElementById('fi-'+i);el.classList.remove('active');el.classList.add('done');}
document.getElementById('doneBanner').classList.add('visible');
document.getElementById('doneSub').textContent=`${st.pass||0} pass ⚠${st.warn||0} warn ✗${st.fail||0} fail →${st.skip||0} skip`;
run.disabled=false; stop.disabled=true;
document.getElementById('btnLabel').textContent='Avvia di Nuovo';
evtSrc.close(); evtSrc=null; return;
}
appendLog(m, t);
if (t === 'phase') {
phaseN++;
if (phaseN>=1&&phaseN<=6){
for(let i=1;i<phaseN;i++){const el=document.getElementById('fi-'+i);if(!el.classList.contains('done')){el.classList.add('done');el.classList.remove('active');}}
const cur=document.getElementById('fi-'+phaseN);
if(cur){cur.classList.add('active');cur.classList.remove('done');}
}
upProg(Math.min(5+phaseN*16,93), LABELS[phaseN]||'');
}
};
evtSrc.onerror = () => {
appendLog('Connessione SSE interrotta.','error');
run.disabled=false; stop.disabled=true;
document.getElementById('btnLabel').textContent='Avvia Simulazione B2B';
if(evtSrc){evtSrc.close();evtSrc=null;}
};
}
function stopSim() {
if(evtSrc){evtSrc.close();evtSrc=null;}
appendLog('Simulazione interrotta.','warn');
document.getElementById('btnRun').disabled=false;
document.getElementById('btnStop').disabled=true;
document.getElementById('btnLabel').textContent='Avvia Simulazione B2B';
}
function resetSim() {
if(!confirm('Eliminare tutti i dati demo B2B (utenti *.b2b.demo, inviti sim-b2b)?')) return;
clearLog(); appendLog('Reset B2B in corso...','info');
const src=new EventSource('simulate-nis2-b2b.php?reset=1&t='+Date.now());
src.onmessage=(e)=>{const d=JSON.parse(e.data);appendLog(d.m||'',d.t||'info');if(d.t==='done'){src.close();appendLog('Reset completato.','ok');}};
src.onerror=()=>{src.close();appendLog('Reset completato.','ok');};
}
</script>
</body>
</html>