argos-setup/setup.html

558 lines
32 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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> Licenza</div>
<div class="tab-item" onclick="goTab(1)" id="tab-1"><div class="tab-num">2</div> Cliente</div>
<div class="tab-item" onclick="goTab(2)" id="tab-2"><div class="tab-num">3</div> Rete & SSL</div>
<div class="tab-item" onclick="goTab(3)" id="tab-3"><div class="tab-num">4</div> SIEM</div>
<div class="tab-item" onclick="goTab(4)" id="tab-4"><div class="tab-num">5</div> Utente admin</div>
<div class="tab-item" onclick="goTab(5)" id="tab-5"><div class="tab-num">6</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 class="prog-step" id="ps-5"></div>
</div>
<!-- Tab 1: Licenza ARGOS -->
<div class="tab-panel active" id="panel-0">
<div class="panel-title">🔑 Licenza ARGOS</div>
<div class="panel-sub">ARGOS SOC richiede una licenza valida emessa da Tecnotel Servizi SRL,
vincolata all'identificativo hardware di questo server.</div>
<div class="form-grid">
<div class="sec-div" style="grid-column:1/-1">1. Identificativo hardware del server</div>
<div class="form-full">
<label class="form-label">Machine ID (SHA256 hex)</label>
<div style="display:flex;gap:8px;align-items:stretch">
<input class="form-input mono-input" id="machine-id-display" value="Calcolo in corso..." readonly style="flex:1">
<button class="btn btn-ghost" onclick="copyMachineId()" id="btn-copy-mid" style="padding:10px 16px">📋 Copia</button>
</div>
<div class="form-hint">Invia questo ID a <strong>Tecnotel Servizi SRL</strong> per ricevere la licenza.
Email: <strong>info@tecnotelsrl.com</strong></div>
</div>
<div class="sec-div" style="grid-column:1/-1">2. Carica license.json ricevuta</div>
<div class="upload-area form-full" id="lic-drop"
ondragover="event.preventDefault();this.classList.add('drag')"
ondragleave="this.classList.remove('drag')"
ondrop="handleLicenseDrop(event)"
onclick="document.getElementById('lic-input').click()">
<input type="file" id="lic-input" accept=".json,application/json" style="display:none" onchange="handleLicenseFile(this)">
<div style="font-size:24px;margin-bottom:6px">📜</div>
<div style="font-size:13px;color:var(--text2)">Trascina license.json o clicca per selezionare</div>
<div style="font-size:11px;color:var(--text3);margin-top:3px">File JSON firmato Ed25519 — max 10KB</div>
</div>
<div id="lic-error" class="form-full" style="display:none;background:var(--err-dim);border:1px solid var(--err);border-radius:var(--r);padding:12px 16px;color:var(--err);font-size:13px"></div>
<div id="lic-summary" class="form-full" style="display:none;background:var(--ok-dim);border:1px solid var(--ok);border-radius:var(--r);padding:16px 20px">
<div style="font-size:14px;font-weight:700;color:var(--ok);margin-bottom:10px">✅ Licenza valida</div>
<div style="display:grid;grid-template-columns:auto 1fr;gap:6px 14px;font-size:12px;font-family:var(--mono)">
<div style="color:var(--text3)">Cliente:</div> <div id="lic-customer"></div>
<div style="color:var(--text3)">Tier:</div> <div id="lic-tier"></div>
<div style="color:var(--text3)">Hostname:</div> <div id="lic-hostname"></div>
<div style="color:var(--text3)">Emessa:</div> <div id="lic-issued"></div>
<div style="color:var(--text3)">Scadenza:</div> <div id="lic-expires"></div>
<div style="color:var(--text3)">Token Gitea:</div><div id="lic-gitea"></div>
</div>
</div>
</div>
<div class="nav-row">
<div></div>
<button class="btn btn-brand" id="btn-after-license" onclick="goTab(1)" disabled style="opacity:.4;cursor:not-allowed">Avanti →</button>
</div>
</div>
<!-- Tab 2: Cliente -->
<div class="tab-panel" id="panel-1">
<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(2)">Avanti →</button></div>
</div>
<!-- Tab 3: Rete & SSL -->
<div class="tab-panel" id="panel-2">
<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(1)">← Indietro</button><button class="btn btn-brand" onclick="goTab(3)">Avanti →</button></div>
</div>
<!-- Tab 4: SIEM -->
<div class="tab-panel" id="panel-3">
<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(2)">← Indietro</button><button class="btn btn-brand" onclick="goTab(4)">Avanti →</button></div>
</div>
<!-- Tab 4: Admin -->
<div class="tab-panel" id="panel-4">
<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(3)">← Indietro</button><button class="btn btn-brand" onclick="goToInstall()">Avanti →</button></div>
</div>
<!-- Tab 6: Installa -->
<div class="tab-panel" id="panel-5">
<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(4)">← 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 = '';
// ── Licenza ARGOS: fetch machine_id e gestione upload ────────────────────────
let licenseValid = false;
async function loadMachineId() {
try {
const r = await fetch('/api/machine-id');
const d = await r.json();
const el = document.getElementById('machine-id-display');
if (d && d.machine_id) {
el.value = d.machine_id;
} else {
el.value = 'Errore caricamento';
}
} catch (e) {
document.getElementById('machine-id-display').value = 'Errore: ' + e.message;
}
}
function copyMachineId() {
const el = document.getElementById('machine-id-display');
el.select();
navigator.clipboard.writeText(el.value).then(() => {
const btn = document.getElementById('btn-copy-mid');
const orig = btn.textContent;
btn.textContent = '✅ Copiato';
setTimeout(() => btn.textContent = orig, 1500);
}).catch(() => {});
}
function handleLicenseDrop(ev) {
ev.preventDefault();
ev.currentTarget.classList.remove('drag');
if (ev.dataTransfer.files.length) uploadLicense(ev.dataTransfer.files[0]);
}
function handleLicenseFile(inp) {
if (inp.files.length) uploadLicense(inp.files[0]);
}
async function uploadLicense(file) {
const errEl = document.getElementById('lic-error');
const sumEl = document.getElementById('lic-summary');
const btnNext = document.getElementById('btn-after-license');
errEl.style.display = 'none';
sumEl.style.display = 'none';
try {
const text = await file.text();
const r = await fetch('/api/license/upload', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: text,
});
const d = await r.json();
if (!d.ok) {
errEl.textContent = 'Licenza non valida: ' + (d.error || 'errore sconosciuto');
errEl.style.display = 'block';
licenseValid = false;
btnNext.disabled = true;
btnNext.style.opacity = '.4';
btnNext.style.cursor = 'not-allowed';
return;
}
const s = d.summary || {};
document.getElementById('lic-customer').textContent = s.customer || '—';
document.getElementById('lic-tier').textContent = (s.tier || '—').toUpperCase();
document.getElementById('lic-hostname').textContent = s.issued_to || '—';
document.getElementById('lic-issued').textContent = s.issued_at || '—';
document.getElementById('lic-expires').textContent = s.expires_at || '—';
document.getElementById('lic-gitea').textContent = s.has_gitea ? '✓ Incluso' : '— Non incluso';
sumEl.style.display = 'block';
licenseValid = true;
btnNext.disabled = false;
btnNext.style.opacity = '1';
btnNext.style.cursor = 'pointer';
} catch (e) {
errEl.textContent = 'Errore upload: ' + e.message;
errEl.style.display = 'block';
}
}
// Al caricamento pagina: fetch machine_id
window.addEventListener('DOMContentLoaded', loadMachineId);
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(5);
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>