argos-setup/setup.html

420 lines
26 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ARGOS SOC — Installazione</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Barlow:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#080a0e;--bg2:#0e1117;--bg3:#161b24;--bg4:#1c2333;
--border:#1e2840;--border2:#2a3a58;
--text:#e2e8f4;--text2:#8a9ab8;--text3:#445068;
--brand:#F09000;--brand2:#cc7a00;--brand-dim:#F0900018;
--ok:#10b981;--ok-dim:#10b98118;--err:#ef4444;--err-dim:#ef444418;--warn:#f59e0b;
--mono:'JetBrains Mono',monospace;--sans:'Barlow',sans-serif;--r:10px;
}
html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.6}
.shell{min-height:100vh;display:grid;grid-template-rows:auto 1fr auto}
.hdr{background:var(--bg2);border-bottom:1px solid var(--border);padding:16px 32px;display:flex;align-items:center;gap:16px}
.hdr-logo{font-size:22px;font-weight:800;color:var(--brand);letter-spacing:3px}
.hdr-sep{width:1px;height:24px;background:var(--border2)}
.hdr-sub{font-size:12px;color:var(--text3);font-family:var(--mono);text-transform:uppercase;letter-spacing:1.5px}
.hdr-badge{margin-left:auto;font-size:10px;font-family:var(--mono);background:var(--brand-dim);color:var(--brand);padding:4px 10px;border-radius:20px;border:1px solid #F0900033}
.main{display:flex;max-width:1000px;margin:0 auto;width:100%;padding:32px 24px;gap:28px}
.sidebar{width:200px;flex-shrink:0}
.sidebar-title{font-size:10px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:1.5px;padding:0 12px 12px;font-family:var(--mono)}
.tab-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:var(--r);cursor:pointer;transition:all .15s;color:var(--text2);font-size:13px;font-weight:500;margin-bottom:2px;border:1px solid transparent}
.tab-item:hover{background:var(--bg3);color:var(--text)}
.tab-item.active{background:var(--bg3);color:var(--brand);border-color:var(--border2)}
.tab-item.done .tab-num{background:var(--ok);color:#000}
.tab-item.active .tab-num{background:var(--brand);color:#000}
.tab-num{width:22px;height:22px;border-radius:50%;background:var(--bg4);color:var(--text3);font-size:10px;font-weight:700;font-family:var(--mono);display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .2s}
.content{flex:1;min-width:0}
.tab-panel{display:none}
.tab-panel.active{display:block}
.panel-title{font-size:22px;font-weight:700;margin-bottom:4px}
.panel-sub{font-size:13px;color:var(--text2);margin-bottom:24px}
.progress{display:flex;gap:6px;margin-bottom:28px}
.prog-step{flex:1;height:3px;border-radius:2px;background:var(--border2);transition:background .3s}
.prog-step.done{background:var(--ok)}
.prog-step.active{background:var(--brand)}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form-full{grid-column:1/-1}
.form-label{display:block;font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.8px;margin-bottom:6px}
.form-label span{color:var(--err);margin-left:2px}
.form-input,.form-select{width:100%;background:var(--bg3);border:1px solid var(--border2);border-radius:var(--r);padding:10px 14px;color:var(--text);font-family:var(--sans);font-size:13px;outline:none;transition:border-color .15s,box-shadow .15s}
.form-input:focus,.form-select:focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-dim)}
.form-input::placeholder{color:var(--text3)}
.mono-input{font-family:var(--mono);font-size:12px}
.form-hint{font-size:11px;color:var(--text3);margin-top:5px}
.sec-div{grid-column:1/-1;font-size:10px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:1.5px;font-family:var(--mono);border-top:1px solid var(--border);padding-top:18px;margin-top:4px}
.sec-div:first-child{border-top:none;padding-top:0;margin-top:0}
.ssl-toggle{grid-column:1/-1;display:flex;gap:8px}
.ssl-btn{flex:1;padding:12px 16px;background:var(--bg3);border:1px solid var(--border2);border-radius:var(--r);cursor:pointer;text-align:center;font-size:13px;font-weight:600;color:var(--text2);transition:all .15s}
.ssl-btn.active{background:var(--brand-dim);border-color:var(--brand);color:var(--brand)}
.ssl-btn small{display:block;font-weight:400;font-size:11px;opacity:.7;margin-top:2px}
.upload-area{grid-column:1/-1;border:2px dashed var(--border2);border-radius:var(--r);padding:20px;text-align:center;cursor:pointer;transition:all .15s}
.upload-area:hover,.upload-area.drag{border-color:var(--brand);background:var(--brand-dim)}
.upload-preview{display:none;align-items:center;justify-content:center;gap:10px;margin-top:10px}
.upload-preview img{max-height:50px;max-width:180px;border-radius:6px}
.alert{padding:12px 16px;border-radius:var(--r);font-size:13px;margin-bottom:0}
.alert-err{background:var(--err-dim);border:1px solid #ef444433;color:var(--err)}
.alert-ok{background:var(--ok-dim);border:1px solid #10b98133;color:var(--ok)}
.alert-warn{background:#f59e0b15;border:1px solid #f59e0b33;color:var(--warn)}
.nav-row{display:flex;justify-content:space-between;align-items:center;margin-top:28px;padding-top:20px;border-top:1px solid var(--border)}
.btn{padding:10px 22px;border-radius:var(--r);border:none;font-family:var(--sans);font-size:13px;font-weight:700;cursor:pointer;transition:all .15s;display:inline-flex;align-items:center;gap:8px}
.btn-brand{background:var(--brand);color:#000}
.btn-brand:hover{background:var(--brand2)}
.btn-ghost{background:transparent;color:var(--text2);border:1px solid var(--border2)}
.btn-ghost:hover{background:var(--bg3);color:var(--text)}
.btn-ok{background:var(--ok);color:#000;font-size:15px;padding:14px 36px}
.btn-ok:hover{filter:brightness(1.1)}
.btn:disabled{opacity:.4;cursor:not-allowed}
.summary-table{background:var(--bg3);border:1px solid var(--border);border-radius:var(--r);overflow:hidden;margin-bottom:16px}
.summary-section{font-size:10px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:1px;padding:10px 14px;font-family:var(--mono);background:var(--bg4);border-bottom:1px solid var(--border)}
.summary-row{display:flex;border-bottom:1px solid #1e284044}
.summary-row:last-child{border-bottom:none}
.summary-label{width:160px;padding:8px 14px;font-size:12px;color:var(--text3);flex-shrink:0}
.summary-value{padding:8px 14px;font-size:12px;color:var(--text);font-family:var(--mono);flex:1}
.summary-value.empty{color:var(--text3);font-style:italic;font-family:var(--sans)}
.footer{background:var(--bg2);border-top:1px solid var(--border);padding:12px 32px;text-align:center;font-size:11px;color:var(--text3);font-family:var(--mono)}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes pulse{0%,100%{opacity:.3;transform:scale(.8)}50%{opacity:1;transform:scale(1.2)}}
</style>
</head>
<body>
<div class="shell">
<header class="hdr">
<div class="hdr-logo">ARGOS</div>
<div class="hdr-sep"></div>
<div class="hdr-sub">Setup Wizard — Tecnotel Servizi SRL</div>
<div class="hdr-badge">v1.0.0</div>
</header>
<div class="main">
<aside class="sidebar">
<div class="sidebar-title">Configurazione</div>
<div class="tab-item active" onclick="goTab(0)" id="tab-0"><div class="tab-num">1</div> Cliente</div>
<div class="tab-item" onclick="goTab(1)" id="tab-1"><div class="tab-num">2</div> Rete & SSL</div>
<div class="tab-item" onclick="goTab(2)" id="tab-2"><div class="tab-num">3</div> SIEM</div>
<div class="tab-item" onclick="goTab(3)" id="tab-3"><div class="tab-num">4</div> Utente admin</div>
<div class="tab-item" onclick="goTab(4)" id="tab-4"><div class="tab-num">5</div> Installa</div>
</aside>
<div class="content">
<div class="progress">
<div class="prog-step active" id="ps-0"></div>
<div class="prog-step" id="ps-1"></div>
<div class="prog-step" id="ps-2"></div>
<div class="prog-step" id="ps-3"></div>
<div class="prog-step" id="ps-4"></div>
</div>
<!-- Tab 1: Cliente -->
<div class="tab-panel active" id="panel-0">
<div class="panel-title">👤 Informazioni cliente</div>
<div class="panel-sub">Dati dell'organizzazione che utilizzerà ARGOS SOC.</div>
<div class="form-grid">
<div><label class="form-label">Nome breve <span>*</span></label><input class="form-input" id="cliente_name" placeholder="es. NomeAzienda"><div class="form-hint">Usato nei titoli e intestazioni</div></div>
<div><label class="form-label">Tipo organizzazione</label><select class="form-select" id="cliente_type"><option value="enterprise">Azienda / Enterprise</option><option value="healthcare">Sanità / Healthcare</option><option value="pa">Pubblica Amministrazione</option><option value="education">Istruzione</option></select></div>
<div class="form-full"><label class="form-label">Nome completo</label><input class="form-input" id="cliente_full" placeholder="es. Nome Azienda Srl"></div>
<div><label class="form-label">Dominio email <span>*</span></label><input class="form-input" id="cliente_domain" placeholder="es. azienda.it"><div class="form-hint">Dominio utenti (es. nome@azienda.it)</div></div>
<div><label class="form-label">SharePoint tenant</label><input class="form-input" id="sp_tenant" placeholder="es. nomeazienda"><div class="form-hint">Solo se Microsoft 365. Vuoto se non usato.</div></div>
<div class="sec-div">Logo cliente (per i PDF report)</div>
<div class="upload-area form-full" id="logo-drop" ondragover="event.preventDefault();this.classList.add('drag')" ondragleave="this.classList.remove('drag')" ondrop="handleLogoDrop(event)" onclick="document.getElementById('logo-input').click()">
<input type="file" id="logo-input" accept="image/*" style="display:none" onchange="handleLogoFile(this)">
<div style="font-size:24px;margin-bottom:6px">🖼️</div>
<div style="font-size:13px;color:var(--text2)">Trascina il logo o clicca per selezionare</div>
<div style="font-size:11px;color:var(--text3);margin-top:3px">PNG, JPG — max 2MB — opzionale</div>
<div class="upload-preview" id="logo-preview"><img id="logo-img" src=""><div style="font-size:11px;color:var(--ok);font-family:var(--mono)" id="logo-fname"></div></div>
</div>
</div>
<div class="nav-row"><div></div><button class="btn btn-brand" onclick="goTab(1)">Avanti →</button></div>
</div>
<!-- Tab 2: Rete & SSL -->
<div class="tab-panel" id="panel-1">
<div class="panel-title">🌐 Rete & SSL</div>
<div class="panel-sub">Configurazione dominio e certificato SSL. Il DNS deve già puntare a questo server.</div>
<div class="form-grid">
<div class="form-full"><label class="form-label">Hostname principale <span>*</span></label><input class="form-input mono-input" id="domain" placeholder="es. soc.azienda.it"></div>
<div class="form-full"><label class="form-label">Nomi alternativi</label><input class="form-input mono-input" id="aliases" placeholder="es. argos.azienda.it soc2.azienda.it"><div class="form-hint">Separati da spazio — opzionale</div></div>
<div class="sec-div">Certificato SSL</div>
<div class="ssl-toggle">
<div class="ssl-btn active" id="ssl-le" onclick="setSsl('letsencrypt')">🔒 Let's Encrypt<small>Automatico e gratuito</small></div>
<div class="ssl-btn" id="ssl-manual" onclick="setSsl('manual')">📄 Certificato esistente<small>Carica .crt e .key</small></div>
</div>
<div id="le-block" class="form-full"><label class="form-label">Email amministratore <span>*</span></label><input class="form-input" id="admin_email" placeholder="admin@tecnotelsrl.com"><div class="form-hint">Per notifiche di scadenza certificato</div></div>
<div id="manual-block" class="form-full" style="display:none">
<div class="form-grid">
<div><label class="form-label">File .crt <span>*</span></label><input type="file" class="form-input" accept=".crt,.pem,.cer" onchange="uploadSsl(this,'cert')"><div class="form-hint" id="crt-status">Nessun file selezionato</div></div>
<div><label class="form-label">File .key <span>*</span></label><input type="file" class="form-input" accept=".key,.pem" onchange="uploadSsl(this,'key')"><div class="form-hint" id="key-status">Nessun file selezionato</div></div>
</div>
</div>
</div>
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(0)">← Indietro</button><button class="btn btn-brand" onclick="goTab(2)">Avanti →</button></div>
</div>
<!-- Tab 3: SIEM -->
<div class="tab-panel" id="panel-2">
<div class="panel-title">🔍 SIEM — OpenSearch</div>
<div class="panel-sub">Connessione al backend SIEM. Le altre integrazioni si configurano dall'interfaccia ARGOS dopo l'installazione.</div>
<div class="form-grid">
<div class="form-full"><label class="form-label">URL OpenSearch <span>*</span></label><input class="form-input mono-input" id="os_url" placeholder="https://10.0.0.30:9200"></div>
<div><label class="form-label">Username</label><input class="form-input" id="os_user" placeholder="admin" value="admin"></div>
<div><label class="form-label">Password <span>*</span></label><input class="form-input" type="password" id="os_pass" placeholder="••••••••"></div>
<div class="form-full"><div class="alert alert-warn"> Le integrazioni (ESET, FortiGate, Entra ID, ecc.) si configurano dopo il primo accesso dalla sezione <strong>Integrazioni</strong> di ARGOS.</div></div>
</div>
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(1)">← Indietro</button><button class="btn btn-brand" onclick="goTab(3)">Avanti →</button></div>
</div>
<!-- Tab 4: Admin -->
<div class="tab-panel" id="panel-3">
<div class="panel-title">🔑 Utente amministratore</div>
<div class="panel-sub">Crea il primo account admin per accedere a ARGOS SOC.</div>
<div class="form-grid">
<div><label class="form-label">Username <span>*</span></label><input class="form-input" id="admin_username" placeholder="admin" value="admin"></div>
<div><label class="form-label">Email</label><input class="form-input" id="admin_email_user" placeholder="admin@azienda.it"></div>
<div><label class="form-label">Password <span>*</span></label><input class="form-input" type="password" id="admin_password" placeholder="Min. 8 caratteri"></div>
<div><label class="form-label">Conferma password <span>*</span></label><input class="form-input" type="password" id="admin_password2" placeholder="Ripeti la password"></div>
<div class="form-full" id="pw-error" style="display:none"><div class="alert alert-err">Le password non corrispondono</div></div>
<div class="form-full"><div class="alert alert-ok">✓ Potrai creare altri utenti con ruoli diversi dall'interfaccia ARGOS dopo l'installazione.</div></div>
</div>
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(2)">← Indietro</button><button class="btn btn-brand" onclick="goToInstall()">Avanti →</button></div>
</div>
<!-- Tab 5: Installa -->
<div class="tab-panel" id="panel-4">
<div class="panel-title">🚀 Riepilogo & Installazione</div>
<div class="panel-sub">Verifica i dati e avvia l'installazione.</div>
<!-- Riepilogo -->
<div id="summary-box"></div>
<!-- Errori validazione -->
<div id="errors-box" style="display:none"></div>
<!-- Spinner installazione -->
<div id="spinner-box" style="display:none;text-align:center;padding:40px 20px">
<div style="font-size:40px;margin-bottom:16px">⚙️</div>
<div style="font-size:18px;font-weight:700;margin-bottom:8px">Installazione in corso...</div>
<div style="font-size:13px;color:var(--text2);margin-bottom:24px">Attendere il completamento. Richiede circa 5-10 minuti.</div>
<div style="display:flex;justify-content:center;gap:8px;margin-bottom:20px">
<div style="width:12px;height:12px;border-radius:50%;background:var(--brand);animation:pulse 1.2s ease-in-out 0s infinite"></div>
<div style="width:12px;height:12px;border-radius:50%;background:var(--brand);animation:pulse 1.2s ease-in-out 0.4s infinite"></div>
<div style="width:12px;height:12px;border-radius:50%;background:var(--brand);animation:pulse 1.2s ease-in-out 0.8s infinite"></div>
</div>
<div style="font-size:12px;color:var(--text3);font-family:var(--mono)" id="spinner-status">Avvio installazione...</div>
</div>
<!-- Successo con tasto -->
<div id="success-box" style="display:none;text-align:center;padding:40px 20px">
<div style="font-size:56px;margin-bottom:16px"></div>
<div style="font-size:24px;font-weight:800;color:var(--ok);margin-bottom:8px">Installazione completata!</div>
<div style="font-size:14px;color:var(--text2);margin-bottom:24px">ARGOS SOC è operativo e pronto all'uso.</div>
<button class="btn btn-ok" id="btn-open" onclick="openArgos()">Apri ARGOS SOC →</button>
</div>
<!-- Nav -->
<div class="nav-row" id="install-nav">
<button class="btn btn-ghost" onclick="goTab(3)">← Indietro</button>
<button class="btn btn-ok" id="btn-install" onclick="avviaInstallazione()">🚀 Avvia installazione</button>
</div>
</div>
</div>
</div>
<footer class="footer">ARGOS SOC — Tecnotel Servizi SRL &nbsp;·&nbsp; Web Installer v1.0.0 &nbsp;·&nbsp; La porta 8888 verrà chiusa al termine</footer>
</div>
<script>
let currentTab = 0;
let sslMode = 'letsencrypt';
let certUploaded = false;
let keyUploaded = false;
let installing = false;
let installDomain = '';
function goTab(n) {
if (n > currentTab) document.getElementById('tab-' + currentTab).classList.add('done');
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.prog-step').forEach((s,i) => {
s.classList.remove('active','done');
if (i < n) s.classList.add('done');
else if (i === n) s.classList.add('active');
});
document.getElementById('panel-' + n).classList.add('active');
document.getElementById('tab-' + n).classList.add('active');
currentTab = n;
}
function goToInstall() {
const pw1 = g('admin_password'), pw2 = g('admin_password2');
const err = document.getElementById('pw-error');
if (pw1 !== pw2) { err.style.display = 'block'; return; }
err.style.display = 'none';
goTab(4);
buildSummary();
}
function setSsl(mode) {
sslMode = mode;
document.getElementById('ssl-le').classList.toggle('active', mode === 'letsencrypt');
document.getElementById('ssl-manual').classList.toggle('active', mode === 'manual');
document.getElementById('le-block').style.display = mode === 'letsencrypt' ? 'block' : 'none';
document.getElementById('manual-block').style.display = mode === 'manual' ? 'block' : 'none';
}
async function uploadSsl(input, type) {
const file = input.files[0]; if (!file) return;
const sid = type === 'cert' ? 'crt-status' : 'key-status';
try {
const r = await fetch('/api/upload/' + type, { method: 'POST', body: await file.arrayBuffer() });
const d = await r.json();
if (d.ok) {
document.getElementById(sid).textContent = '✓ ' + file.name;
document.getElementById(sid).style.color = 'var(--ok)';
if (type === 'cert') certUploaded = true; else keyUploaded = true;
}
} catch(e) { document.getElementById(sid).textContent = 'Errore: ' + e.message; }
}
function handleLogoDrop(e) { e.preventDefault(); document.getElementById('logo-drop').classList.remove('drag'); processLogo(e.dataTransfer.files[0]); }
function handleLogoFile(input) { processLogo(input.files[0]); }
async function processLogo(file) {
if (!file || file.size > 2*1024*1024) return;
const reader = new FileReader();
reader.onload = e => {
document.getElementById('logo-img').src = e.target.result;
document.getElementById('logo-fname').textContent = file.name;
document.getElementById('logo-preview').style.display = 'flex';
};
reader.readAsDataURL(file);
fetch('/api/upload/logo', { method: 'POST', body: await file.arrayBuffer() });
}
function g(id) { return (document.getElementById(id)?.value || '').trim(); }
function collectData() {
return {
cliente_name: g('cliente_name'), cliente_full: g('cliente_full'),
cliente_domain: g('cliente_domain'), cliente_type: g('cliente_type'),
sp_tenant: g('sp_tenant'), domain: g('domain'), aliases: g('aliases'),
ssl_mode: sslMode, admin_email: g('admin_email'),
os_url: g('os_url'), os_user: g('os_user') || 'admin', os_pass: g('os_pass'),
admin_username: g('admin_username') || 'admin',
admin_email_user: g('admin_email_user'), admin_password: g('admin_password'),
};
}
function buildSummary() {
const d = collectData();
const errors = [];
if (!d.cliente_name) errors.push('Nome cliente obbligatorio');
if (!d.cliente_domain) errors.push('Dominio email obbligatorio');
if (!d.domain) errors.push('Hostname obbligatorio');
if (!d.os_url) errors.push('URL OpenSearch obbligatorio');
if (!d.os_pass) errors.push('Password OpenSearch obbligatoria');
if (!d.admin_password) errors.push('Password admin obbligatoria');
if (sslMode === 'letsencrypt' && !d.admin_email) errors.push("Email per Let's Encrypt obbligatoria");
if (sslMode === 'manual' && (!certUploaded || !keyUploaded)) errors.push('Certificato SSL (.crt e .key) non caricato');
const row = (l, v) => '<div class="summary-row"><div class="summary-label">' + l + '</div><div class="summary-value ' + (v ? '' : 'empty') + '">' + (v || 'non configurato') + '</div></div>';
document.getElementById('summary-box').innerHTML = '<div class="summary-table">' +
'<div class="summary-section">Cliente</div>' +
row('Nome', d.cliente_name) + row('Nome completo', d.cliente_full) +
row('Dominio email', d.cliente_domain) + row('Tipo', d.cliente_type) +
'<div class="summary-section">Rete & SSL</div>' +
row('Hostname', d.domain) + row('Alias', d.aliases) +
row('SSL', sslMode === 'letsencrypt' ? "Let's Encrypt" : 'Certificato manuale') +
'<div class="summary-section">SIEM</div>' +
row('OpenSearch URL', d.os_url) + row('Username', d.os_user) +
'<div class="summary-section">Utente admin</div>' +
row('Username', d.admin_username) + row('Email', d.admin_email_user) +
'</div>';
const errBox = document.getElementById('errors-box');
if (errors.length) {
errBox.style.display = 'block';
errBox.innerHTML = '<div class="alert alert-err" style="margin-bottom:16px">⚠️ <strong>Correggere prima di procedere:</strong><ul style="margin-top:8px;padding-left:16px">' + errors.map(e => '<li style="margin-top:4px">' + e + '</li>').join('') + '</ul></div>';
document.getElementById('btn-install').disabled = true;
} else {
errBox.style.display = 'none';
document.getElementById('btn-install').disabled = false;
}
}
async function avviaInstallazione() {
if (installing) return;
installing = true;
const data = collectData();
installDomain = data.domain;
document.getElementById('summary-box').style.display = 'none';
document.getElementById('errors-box').style.display = 'none';
document.getElementById('install-nav').style.display = 'none';
document.getElementById('spinner-box').style.display = 'block';
try {
const r = await fetch('/api/install', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const res = await r.json();
if (!res.ok) throw new Error(res.error || 'Errore sconosciuto');
pollStatus();
} catch(e) {
document.getElementById('spinner-status').textContent = 'ERRORE: ' + e.message;
document.getElementById('spinner-status').style.color = 'var(--err)';
installing = false;
}
}
function pollStatus() {
let errors = 0;
const statusEl = document.getElementById('spinner-status');
const steps = ['Generazione configurazione...','Build frontend React...','Installazione dipendenze...','Creazione utente admin...','Configurazione SSL...','Configurazione nginx...','Avvio servizi...','Finalizzazione...'];
let step = 0;
statusEl.textContent = steps[0];
const stepIv = setInterval(() => { if (step < steps.length-1) statusEl.textContent = steps[++step]; }, 20000);
const iv = setInterval(async () => {
try {
const r = await fetch('/api/status');
const d = await r.json();
errors = 0;
if (d.log && d.log.length) {
const last = d.log[d.log.length-1];
if (last.includes('frontend')) statusEl.textContent = 'Build frontend React...';
else if (last.includes('Python')) statusEl.textContent = 'Installazione dipendenze...';
else if (last.includes('admin')) statusEl.textContent = 'Creazione utente admin...';
else if (last.includes('SSL') || last.includes('certbot')) statusEl.textContent = 'Configurazione SSL...';
else if (last.includes('nginx')) statusEl.textContent = 'Configurazione nginx...';
else if (last.includes('avviato')) statusEl.textContent = 'Avvio servizi...';
else if (last.includes('COMPLETATA')) statusEl.textContent = 'Completato!';
}
if (d.done) { clearInterval(iv); clearInterval(stepIv); showSuccess(); }
if (d.error) { clearInterval(iv); clearInterval(stepIv); statusEl.textContent = 'Errore — controlla i log del server'; statusEl.style.color = 'var(--err)'; }
} catch(e) {
errors++;
if (errors >= 6) { clearInterval(iv); clearInterval(stepIv); showSuccess(); }
}
}, 2000);
}
function showSuccess() {
document.getElementById('spinner-box').style.display = 'none';
document.getElementById('success-box').style.display = 'block';
}
function openArgos() {
window.location.href = 'https://' + installDomain;
}
</script>
</body>
</html>