2026-04-20 10:28:40 +02:00
<!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 >
2026-04-20 14:27:10 +02:00
< 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 >
2026-04-20 10:28:40 +02:00
< / 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 >
2026-04-20 14:27:10 +02:00
< div class = "prog-step" id = "ps-5" > < / div >
2026-04-20 10:28:40 +02:00
< / div >
2026-04-20 14:27:10 +02:00
<!-- Tab 1: Licenza ARGOS -->
2026-04-20 10:28:40 +02:00
< div class = "tab-panel active" id = "panel-0" >
2026-04-20 14:27:10 +02:00
< 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" >
2026-04-20 10:28:40 +02:00
< 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 >
2026-04-20 14:27:10 +02:00
< div class = "nav-row" > < div > < / div > < button class = "btn btn-brand" onclick = "goTab(2)" > Avanti →< / button > < / div >
2026-04-20 10:28:40 +02:00
< / div >
2026-04-20 14:27:10 +02:00
<!-- Tab 3: Rete & SSL -->
< div class = "tab-panel" id = "panel-2" >
2026-04-20 10:28:40 +02:00
< 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 >
2026-04-20 17:06:37 +02:00
< 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 >
2026-04-20 10:28:40 +02:00
< / div >
2026-04-20 14:27:10 +02:00
<!-- Tab 4: SIEM -->
< div class = "tab-panel" id = "panel-3" >
2026-04-20 10:28:40 +02:00
< 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 >
2026-04-20 17:06:37 +02:00
< 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 >
2026-04-20 10:28:40 +02:00
< / div >
<!-- Tab 4: Admin -->
2026-04-20 14:27:10 +02:00
< div class = "tab-panel" id = "panel-4" >
2026-04-20 10:28:40 +02:00
< 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 >
2026-04-20 17:06:37 +02:00
< div class = "nav-row" > < button class = "btn btn-ghost" onclick = "goTab(3)" > ← Indietro< / button > < button class = "btn btn-brand" onclick = "goToInstall()" > Avanti →< / button > < / div >
2026-04-20 10:28:40 +02:00
< / div >
2026-04-20 14:27:10 +02:00
<!-- Tab 6: Installa -->
< div class = "tab-panel" id = "panel-5" >
2026-04-20 10:28:40 +02:00
< 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" >
2026-04-20 17:06:37 +02:00
< button class = "btn btn-ghost" onclick = "goTab(4)" > ← Indietro< / button >
2026-04-20 10:28:40 +02:00
< 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 · Web Installer v1.0.0 · 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 = '';
2026-04-20 14:27:10 +02:00
// ── 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);
2026-04-20 10:28:40 +02:00
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';
2026-04-20 14:27:10 +02:00
goTab(5);
2026-04-20 10:28:40 +02:00
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 >