- 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>
256 lines
16 KiB
HTML
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>
|