Update installer and runtime setup flow
This commit is contained in:
1 parent
8603fb2bc2
commit
7540da04a8
5 files changed
+385
-54
No files matched your search
@@ -325,6 +325,7 @@ HTML = r"""<!DOCTYPE html>
|
||||
.alert-info { background: #EFF6FF; color: #1D4ED8; border: 1px solid #BFDBFE; }
|
||||
.alert-success { background: #ECFDF5; color: #065F46; border: 1px solid #A7F3D0; }
|
||||
.alert-error { background: #FEF2F2; color: #991B1B; border: 1px solid #FECACA; }
|
||||
.alert-warning { background: #FFFBEB; color: #92400E; border: 1px solid #FDE68A; }
|
||||
|
||||
.hidden { display: none !important; }
|
||||
#done-screen { text-align: center; padding: 48px 0; }
|
||||
@@ -336,10 +337,12 @@ HTML = r"""<!DOCTYPE html>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="wizard-banner" class="alert hidden" style="position:fixed;top:20px;right:20px;max-width:380px;z-index:1000;box-shadow:0 8px 24px rgba(0,0,0,.12)"></div>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<div class="logo">CEZEN AI SUITE</div>
|
||||
<div class="sub">Server Setup Wizard</div>
|
||||
<div class="logo">NEXUS ONE AI</div>
|
||||
<div class="sub">Powered by Cezen · Server Setup Wizard</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -448,25 +451,25 @@ HTML = r"""<!DOCTYPE html>
|
||||
<p class="desc">Choose the tier that matches your GPU hardware.</p>
|
||||
<div class="tier-grid">
|
||||
<div class="tier-card" id="tier-starter" onclick="selectTier('starter')">
|
||||
<div class="tier-name">Starter</div>
|
||||
<div class="tier-name">Server S</div>
|
||||
<div class="tier-gpu">1× RTX 5090 / 32GB VRAM</div>
|
||||
<div class="tier-users">Small team deployment</div>
|
||||
<div class="tier-note" id="tier-note-starter"></div>
|
||||
</div>
|
||||
<div class="tier-card" id="tier-basic" onclick="selectTier('basic')">
|
||||
<div class="tier-name">Entry</div>
|
||||
<div class="tier-name">Server M</div>
|
||||
<div class="tier-gpu">1× NVIDIA RTX Pro 6000 (96GB)</div>
|
||||
<div class="tier-users">Up to 20 concurrent users</div>
|
||||
<div class="tier-note" id="tier-note-basic"></div>
|
||||
</div>
|
||||
<div class="tier-card" id="tier-pro" onclick="selectTier('pro')">
|
||||
<div class="tier-name">Pro</div>
|
||||
<div class="tier-name">Server L</div>
|
||||
<div class="tier-gpu">2× RTX 5090 / RTX Pro class</div>
|
||||
<div class="tier-users">Up to 100 concurrent users</div>
|
||||
<div class="tier-note" id="tier-note-pro"></div>
|
||||
</div>
|
||||
<div class="tier-card" id="tier-max" onclick="selectTier('max')">
|
||||
<div class="tier-name">Max</div>
|
||||
<div class="tier-name">Server Max</div>
|
||||
<div class="tier-gpu">4–8× H100/H200/A100 class</div>
|
||||
<div class="tier-users">200+ concurrent users</div>
|
||||
<div class="tier-note" id="tier-note-max"></div>
|
||||
@@ -516,31 +519,64 @@ HTML = r"""<!DOCTYPE html>
|
||||
</div>
|
||||
|
||||
<div id="done-screen" class="hidden">
|
||||
<div class="done-icon">✅</div>
|
||||
<div class="done-icon">✓</div>
|
||||
<h2>Installation Complete!</h2>
|
||||
<p>Your Nexus One AI is ready.</p>
|
||||
<div class="services card" style="margin-top:24px;text-align:left">
|
||||
<div class="summary-row"><span class="key">Portal</span><span class="val badge">http://localhost</span></div>
|
||||
<div class="summary-row"><span class="key">Open WebUI</span><span class="val badge">:3001</span></div>
|
||||
<div class="summary-row"><span class="key">JupyterLab</span><span class="val badge">:8888</span></div>
|
||||
<div class="summary-row"><span class="key">MLflow</span><span class="val badge">:5000</span></div>
|
||||
<div class="summary-row"><span class="key">MinIO</span><span class="val badge">:9000</span></div>
|
||||
<div class="summary-row"><span class="key">Grafana</span><span class="val badge">:3000</span></div>
|
||||
</div>
|
||||
<div class="alert alert-info" style="max-width:420px;margin:20px auto 0;text-align:left">
|
||||
<strong>Admin login:</strong> admin / Cezen@2024!<br>
|
||||
You will be required to change this password on first login.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row" id="install-btn-row">
|
||||
<button class="btn btn-secondary" onclick="goStep(4)">← Back</button>
|
||||
<button class="btn btn-primary" id="install-btn" onclick="startInstall()">🚀 Start Installation</button>
|
||||
<button class="btn btn-primary" id="install-btn" onclick="startInstall()">Start Installation</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /container -->
|
||||
|
||||
<script>
|
||||
// ── Shared banner (replaces native alert() for validation/errors) ──
|
||||
let _bannerTimer = null;
|
||||
function showBanner(msg, type) {
|
||||
type = type || 'error';
|
||||
const el = document.getElementById('wizard-banner');
|
||||
el.className = 'alert alert-' + type;
|
||||
el.textContent = msg;
|
||||
el.classList.remove('hidden');
|
||||
clearTimeout(_bannerTimer);
|
||||
_bannerTimer = setTimeout(() => el.classList.add('hidden'), 6000);
|
||||
}
|
||||
|
||||
// ── State ──────────────────────────────────────────────────
|
||||
let netMode = 'dhcp';
|
||||
let selectedTier = 'basic';
|
||||
let tierPreview = null;
|
||||
const TIER_LABELS = { starter: 'Server S', basic: 'Server M', pro: 'Server L', max: 'Server Max' };
|
||||
function tierLabel(slug) { return TIER_LABELS[slug] || (slug ? slug.charAt(0).toUpperCase()+slug.slice(1) : 'Unknown'); }
|
||||
|
||||
// Raw codes from cezen_license.evaluate_license() (valid/missing/expired/
|
||||
// invalid_signature/not_yet_valid/machine_mismatch) aren't meant for an
|
||||
// end-user installer screen — translate them the same way the portal's
|
||||
// /api/health does, so a customer never sees a bare internal status code.
|
||||
const LICENSE_STATUS_LABELS = {
|
||||
valid: 'Valid',
|
||||
missing: 'No license installed (field-staging mode)',
|
||||
expired: 'Expired — contact support@cezentech.com',
|
||||
invalid_signature: 'Invalid signature — re-upload a valid signed license',
|
||||
not_yet_valid: 'Not yet in its valid date range',
|
||||
machine_mismatch: 'Bound to different hardware — contact support@cezentech.com',
|
||||
};
|
||||
function licenseStatusLabel(status) { return LICENSE_STATUS_LABELS[status] || (status || 'Unknown'); }
|
||||
let tools = {
|
||||
ollama: { name: 'Ollama + Open WebUI', desc: 'LLM inference & chat', icon: '🤖', on: true },
|
||||
jupyterlab: { name: 'JupyterLab', desc: 'Notebook environment', icon: '📓', on: true },
|
||||
@@ -563,7 +599,7 @@ window.onload = () => {
|
||||
|
||||
// ── Navigation ─────────────────────────────────────────────
|
||||
function goStep(n) {
|
||||
if (n === 4 && !selectedTier) { alert('Please select a tier.'); return; }
|
||||
if (n === 4 && !selectedTier) { showBanner('Please select a tier before continuing.', 'error'); return; }
|
||||
[1,2,3,4,5].forEach(i => {
|
||||
document.getElementById('step-'+i).classList.toggle('hidden', i !== n);
|
||||
const bar = document.getElementById('sbar-'+i);
|
||||
@@ -595,11 +631,11 @@ function applyStaticIP() {
|
||||
fetch('/api/network', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) })
|
||||
.then(r=>r.json()).then(d=>{
|
||||
if(!d.ok) {
|
||||
alert('Network config failed: ' + d.error);
|
||||
showBanner('Network config failed: ' + d.error, 'error');
|
||||
return;
|
||||
}
|
||||
if (d.warning) {
|
||||
alert(d.warning);
|
||||
showBanner(d.warning, 'warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -673,13 +709,13 @@ async function prepareTierStep() {
|
||||
});
|
||||
renderTierAvailability(res);
|
||||
document.getElementById('license-preview').innerHTML = `
|
||||
<strong>Status:</strong> ${esc(res.license?.status || 'missing')}<br>
|
||||
<strong>Status:</strong> ${esc(licenseStatusLabel(res.license?.status || 'missing'))}<br>
|
||||
<strong>Allowed tier:</strong> ${esc(res.license?.allowed_tier || 'basic')}<br>
|
||||
<strong>Hardware recommendation:</strong> ${esc(res.hardware?.recommended_tier || 'starter')}
|
||||
`;
|
||||
goStep(3);
|
||||
} catch (err) {
|
||||
alert('License preview failed: ' + err.message);
|
||||
showBanner('License preview failed: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,10 +730,10 @@ function renderSummary() {
|
||||
document.getElementById('summary-rows').innerHTML = `
|
||||
<div class="summary-row"><span class="key">Network</span><span class="val">${ip}</span></div>
|
||||
<div class="summary-row"><span class="key">Customer</span><span class="val">${license.customer_name || 'Not entered'}</span></div>
|
||||
<div class="summary-row"><span class="key">License</span><span class="val">${license.license_json ? (tierPreview?.license?.status || 'Provided') : 'Field staging / evaluation'}</span></div>
|
||||
<div class="summary-row"><span class="key">Allowed Tier</span><span class="val">${tierPreview?.license?.allowed_tier || 'basic'}</span></div>
|
||||
<div class="summary-row"><span class="key">Hardware Tier</span><span class="val">${tierPreview?.hardware?.recommended_tier || 'starter'}</span></div>
|
||||
<div class="summary-row"><span class="key">Tier</span><span class="val">${selectedTier.charAt(0).toUpperCase()+selectedTier.slice(1)}</span></div>
|
||||
<div class="summary-row"><span class="key">License</span><span class="val">${license.license_json ? licenseStatusLabel(tierPreview?.license?.status || 'missing') : 'Field staging / evaluation'}</span></div>
|
||||
<div class="summary-row"><span class="key">Allowed Tier</span><span class="val">${tierLabel(tierPreview?.license?.allowed_tier || 'basic')}</span></div>
|
||||
<div class="summary-row"><span class="key">Hardware Tier</span><span class="val">${tierLabel(tierPreview?.hardware?.recommended_tier || 'starter')}</span></div>
|
||||
<div class="summary-row"><span class="key">Tier</span><span class="val">${tierLabel(selectedTier)}</span></div>
|
||||
<div class="summary-row"><span class="key">Tools</span><span class="val" style="font-size:13px;text-align:right;max-width:60%">${onTools}</span></div>
|
||||
${offTools.length ? `<div class="summary-row"><span class="key">Skipped</span><span class="val" style="color:var(--muted);font-size:13px">${offTools.map(([,v])=>v.name).join(', ')}</span></div>` : ''}
|
||||
`;
|
||||
@@ -718,7 +754,7 @@ function startInstall() {
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.ok) throw new Error(data.error || 'Install start failed');
|
||||
}).catch(err => {
|
||||
alert(err.message);
|
||||
showBanner(err.message, 'error');
|
||||
document.getElementById('install-btn-row').classList.remove('hidden');
|
||||
document.getElementById('summary-card').classList.remove('hidden');
|
||||
document.getElementById('progress-wrap').classList.remove('show');
|
||||
@@ -799,23 +835,25 @@ function showRebootNotice() {
|
||||
document.getElementById('progress-wrap').style.display = 'none';
|
||||
document.getElementById('done-screen').classList.remove('hidden');
|
||||
document.getElementById('done-screen').innerHTML = `
|
||||
<div class="done-icon">🔄</div>
|
||||
<div class="done-icon">↻</div>
|
||||
<h2>Server is Rebooting</h2>
|
||||
<p style="margin-bottom:16px">NVIDIA drivers installed. Phase 2 (AI stack) is installing automatically after reboot.</p>
|
||||
<div class="alert alert-info" style="max-width:500px;margin:0 auto 24px">
|
||||
⏱ Phase 2 takes <strong>20–30 more minutes</strong>. You can monitor it via:<br>
|
||||
Phase 2 takes <strong>20–30 more minutes</strong>. You can monitor it via:<br>
|
||||
<code style="background:#E0F2FE;padding:2px 6px;border-radius:4px">ssh cezen@<server-ip></code>
|
||||
then
|
||||
<code style="background:#E0F2FE;padding:2px 6px;border-radius:4px">journalctl -fu cezen-phase2</code>
|
||||
</div>
|
||||
<div class="services card" style="margin:0 auto;max-width:400px;text-align:left">
|
||||
<p style="font-size:13px;color:var(--muted);margin-bottom:12px">Services will be available at:</p>
|
||||
<div class="summary-row"><span class="key">Portal</span><span class="val badge">http://localhost</span></div>
|
||||
<div class="summary-row"><span class="key">Open WebUI</span><span class="val badge">:3001</span></div>
|
||||
<div class="summary-row"><span class="key">JupyterLab</span><span class="val badge">:8888</span></div>
|
||||
<div class="summary-row"><span class="key">MLflow</span><span class="val badge">:5000</span></div>
|
||||
<div class="summary-row"><span class="key">MinIO</span><span class="val badge">:9000</span></div>
|
||||
<div class="summary-row"><span class="key">Grafana</span><span class="val badge">:3000</span></div>
|
||||
</div>
|
||||
<p style="font-size:13px;color:var(--muted);margin-top:16px">Admin login (admin / Cezen@2024!, change required on first login) will be ready once Phase 2 completes.</p>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
@@ -950,7 +988,7 @@ def show_console_banner(ip):
|
||||
|
||||
\033[1;36m╔══════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ CEZEN AI SUITE — SERVER SETUP ║
|
||||
║ NEXUS ONE AI — SERVER SETUP ║
|
||||
║ ║
|
||||
║ Open a browser on any computer on this network: ║
|
||||
║ ║
|
||||
|
||||
Reference in new issue
Block a user