// Setup Wizard - First-time install configuration UI. // // Multi-step slide-right form. Each step is a sibling div inside a track // that translates horizontally as the user advances. Submission POSTs to // /api/setup/save which fans out into separate tasks per app, then this UI // hands off to the tasks page focused on the first task. class SetupWizard { constructor() { this.container = null; this.suggestedName = ''; this.dnsCheckTimer = null; this.dnsCheckController = null; this.onComplete = null; this.currentStep = 0; // Step 0 (Experience) chooses Beginner or Advanced. Beginner shrinks // the wizard β€” the Metrics step is hidden and never asked β€” so the // visible step count is dynamic (4 for beginner, 5 for advanced). // installLevel defaults to 'beginner' so a user who races through // step 0 without touching anything gets the safe-default UX. this.stepNames = ['Experience', 'Identity', 'Domains', 'Recommended', 'Metrics']; this.stepIcons = ['🌱', 'πŸͺ', 'πŸ›°οΈ', 'πŸ›‘οΈ', 'πŸ“Š']; this.installLevel = 'beginner'; this.totalSteps = this._effectiveTotalSteps(); this.domainCount = 0; // tracked dynamically as the user adds rows this.devMode = false; // unlocked by the Advanced-card 10-tap easter egg } _effectiveTotalSteps() { // Metrics (last step) is advanced-only; beginner skips it entirely. return this.installLevel === 'advanced' ? this.stepNames.length : this.stepNames.length - 1; } initialize(setupDetector, onComplete = null) { this.onComplete = onComplete; this.create(); this.attach(); this.suggestName(); this.renderAppTiles(); this.preselectTimezone(); this.showStep(0); } getWizardApps() { return [ { slug: 'traefik', recommended: true, defaultChecked: true, fallback: { name: 'Traefik', description: 'Reverse proxy + automatic SSL via LetsEncrypt' } }, { slug: 'crowdsec', recommended: true, defaultChecked: true, fallback: { name: 'CrowdSec', description: 'Host-installed intrusion prevention' } } ]; } getMetricsApps() { return [ { slug: 'prometheus', defaultChecked: false, fallback: { name: 'Prometheus', description: 'Scrapes and stores time-series metrics from your apps' } }, { slug: 'grafana', defaultChecked: false, fallback: { name: 'Grafana', description: 'Dashboards and visualisations on top of Prometheus metrics' } } ]; } create() { this.container = document.createElement('div'); this.container.className = 'setup-wizard aurora-bg aurora-static'; this.container.innerHTML = `

Tuning your private universe before takeoff...

Step 1 of ${this.totalSteps} 0%

You can switch any time from the Advanced toggle inside the WebUI.

No domain? Skip this step β€” apps will be reachable by IP and Port on your LAN.

Recommended Apps

Pre-selected to give you a working install out of the box.

Metrics Apps

Optional. Install these to enable per-app "Export metrics to Grafana" later.

`; document.body.appendChild(this.container); document.body.classList.add('setup-wizard-open'); } attach() { const $ = (id) => this.container.querySelector(id); $('#sw-reroll').addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const btn = $('#sw-reroll'); btn.classList.add('manifesting'); setTimeout(() => btn.classList.remove('manifesting'), 700); this.suggestName(true); }); $('#sw-domain-add').addEventListener('click', () => this.addDomainRow()); // Seed with one empty row so the user has somewhere to type. They can // remove it if they truly want a local-only install. this.addDomainRow(); this.attachLiveValidation(); $('#sw-back').addEventListener('click', () => this.prev()); $('#sw-next').addEventListener('click', () => this.next()); // Experience radio β€” updates installLevel and the visible-step count // immediately so the progress bar reflects the choice (4 vs 5 steps). // Selection cards visually toggle via the wrapping