282 lines
12 KiB
HTML
Executable File
282 lines
12 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Vestibule Admin</title>
|
|
<link rel="stylesheet" href="admin.css">
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ════════════════════════════════════════════════════════════════
|
|
AUTH GATE — shown first
|
|
Two sub-views: setup (first run) or enter (returning)
|
|
════════════════════════════════════════════════════════════════ -->
|
|
<section id="view-auth-setup" class="auth-view" hidden>
|
|
<div class="auth-card">
|
|
<h1>Welcome to Vestibule</h1>
|
|
<p class="auth-sub">Choose a setup password. This password protects the admin configuration and cannot be recovered.</p>
|
|
|
|
<label>
|
|
<span>Setup password</span>
|
|
<input type="password" id="setup-password" autocomplete="new-password" autofocus>
|
|
</label>
|
|
<label>
|
|
<span>Confirm password</span>
|
|
<input type="password" id="setup-password-confirm" autocomplete="new-password">
|
|
</label>
|
|
|
|
<div class="auth-warn">
|
|
<strong>If you lose this password</strong>, you must reset Vestibule from the OS level
|
|
(delete the <code>vestibule-profile/storage</code> directory). There is no in-app recovery.
|
|
</div>
|
|
|
|
<div class="auth-actions">
|
|
<button id="setup-submit" class="primary">Set password & continue</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="view-auth-enter" class="auth-view" hidden>
|
|
<div class="auth-card">
|
|
<h1>Enter admin password</h1>
|
|
<p class="auth-sub">Required to view or modify the Vestibule configuration.</p>
|
|
|
|
<label>
|
|
<span>Admin password</span>
|
|
<input type="password" id="enter-password" autocomplete="current-password" autofocus>
|
|
</label>
|
|
|
|
<p id="enter-error" class="field-error" hidden></p>
|
|
|
|
<div class="auth-actions">
|
|
<button id="enter-submit" class="primary">Unlock</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<!-- ════════════════════════════════════════════════════════════════
|
|
WIZARD — shown after auth
|
|
════════════════════════════════════════════════════════════════ -->
|
|
<section id="view-wizard" hidden>
|
|
|
|
<header class="wizard-header">
|
|
<h1>Vestibule Configuration</h1>
|
|
<nav class="wizard-steps">
|
|
<ol id="step-list">
|
|
<li data-step="1" class="active">1. Kiosk identity</li>
|
|
<li data-step="2">2. URL policy</li>
|
|
<li data-step="3">3. Session behavior</li>
|
|
<li data-step="4">4. Power management</li>
|
|
<li data-step="5">5. Unlock method</li>
|
|
<li data-step="6">6. Review & save</li>
|
|
</ol>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="wizard-body">
|
|
|
|
<!-- ─── Step 1: Kiosk identity ─── -->
|
|
<section data-step="1" class="step active">
|
|
<h2>Kiosk identity</h2>
|
|
<p class="step-intro">Basic information about this kiosk. The name is shown in UI banners; the home URL is where the browser navigates after a session reset.</p>
|
|
|
|
<label>
|
|
<span>Kiosk name</span>
|
|
<input type="text" id="cfg-kiosk-name" placeholder="Vestibule Kiosk">
|
|
</label>
|
|
|
|
<label>
|
|
<span>Home URL</span>
|
|
<input type="url" id="cfg-home-url" placeholder="https://example.org/welcome">
|
|
<small>Navigated to on every session reset, wake, and idle timeout.</small>
|
|
</label>
|
|
|
|
<div id="home-domain-status" class="domain-status" hidden>
|
|
<span id="home-domain-text"></span>
|
|
<button id="btn-add-home-domain" class="secondary add-domain" hidden>Add domain to safelist</button>
|
|
</div>
|
|
|
|
<label>
|
|
<span>Attract URL <em>(optional)</em></span>
|
|
<input type="url" id="cfg-attract-url" placeholder="https://example.org/attract">
|
|
<small>Stored in the config schema; the attract-screen feature ships with the presence-detection work (Phase 5+). The field is harmless to leave empty.</small>
|
|
</label>
|
|
</section>
|
|
|
|
<!-- ─── Step 2: URL policy ─── -->
|
|
<section data-step="2" class="step" hidden>
|
|
<h2>URL policy</h2>
|
|
<p class="step-intro">Controls which URLs the kiosk can navigate to. This is the modern replacement for the 2001 VB6 trick of renaming <code>IEXPLORE.EXE</code> to <code>.bak</code>.</p>
|
|
|
|
<fieldset>
|
|
<legend>Mode</legend>
|
|
<label class="radio">
|
|
<input type="radio" name="url-mode" value="safelist">
|
|
<span><strong>Safelist</strong> — block every domain except the list below (default, recommended). Domain-based: an entry grants the domain and its subdomains, and nothing else.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="url-mode" value="open">
|
|
<span><strong>Open</strong> — all URLs allowed. Use only with OS-level lockdown.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="url-mode" value="blocklist">
|
|
<span><strong>Blocklist</strong> — all URLs allowed except those listed below.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="url-mode" value="allowlist">
|
|
<span><strong>Allowlist</strong> — only URLs containing a listed string are allowed. Legacy substring semantics; an empty list allows everything. Prefer safelist.</span>
|
|
</label>
|
|
</fieldset>
|
|
|
|
<label>
|
|
<span id="url-entries-label">Entries <em>(one per line)</em></span>
|
|
<textarea id="cfg-url-entries" rows="8" placeholder="example.org portal.example.org cdn.example.net"></textarea>
|
|
<small id="url-entries-help">Domains, one per line. An entry grants the domain and every subdomain; pasted URLs are normalized to their hostname. Include the domains of third-party resources (CDNs, SSO, analytics) the kiosk content needs — they are blocked too.</small>
|
|
</label>
|
|
</section>
|
|
|
|
<!-- ─── Step 3: Session behavior ─── -->
|
|
<section data-step="3" class="step" hidden>
|
|
<h2>Session behavior</h2>
|
|
<p class="step-intro">Controls how and when the kiosk session resets. All resets wipe browsing data — there is no "soft reset".</p>
|
|
|
|
<label>
|
|
<span>Idle timeout <em>(seconds)</em></span>
|
|
<input type="number" id="cfg-idle-timeout" min="30" max="3600" step="30" value="300">
|
|
<small>After this many seconds of no user input, the session resets. Default: 300 (5 min).</small>
|
|
</label>
|
|
|
|
<fieldset>
|
|
<legend>On reset</legend>
|
|
<label class="radio">
|
|
<input type="radio" name="on-reset" value="reset">
|
|
<span><strong>Reset only</strong> — clear all data, navigate to home.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="on-reset" value="lock">
|
|
<span><strong>Lock only</strong> — show unlock overlay, keep current tab visible behind it.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="on-reset" value="both">
|
|
<span><strong>Both</strong> — reset then show unlock overlay (default, recommended).</span>
|
|
</label>
|
|
</fieldset>
|
|
|
|
<label>
|
|
<span>Data persistence allowlist <em>(per-domain, advanced)</em></span>
|
|
<textarea id="cfg-persistence-allowlist" rows="4" placeholder="sso.example.org auth.example.org"></textarea>
|
|
<small>
|
|
Domains listed here keep cookies, localStorage, and indexedDB across session resets.
|
|
Use for SSO flows that require persistent auth (e.g., a patient-portal that takes
|
|
longer than <code>idle_timeout</code> to complete). <strong>Every persisted domain is
|
|
a potential data leak — list only what you absolutely need.</strong>
|
|
</small>
|
|
</label>
|
|
</section>
|
|
|
|
<!-- ─── Step 4: Power management ─── -->
|
|
<section data-step="4" class="step" hidden>
|
|
<h2>Power management</h2>
|
|
<p class="step-intro">How Vestibule handles device sleep. Vestibule detects wake and resets — it does <strong>not</strong> block sleep. The OS power profile decides whether the device sleeps; that's a deployment-time decision.</p>
|
|
|
|
<fieldset>
|
|
<legend>Mode</legend>
|
|
<label class="radio">
|
|
<input type="radio" name="power-mode" value="aware" checked>
|
|
<span><strong>Aware</strong> — detect wake from sleep, reset session on resume (default, recommended).</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="power-mode" value="always-on">
|
|
<span><strong>Always on</strong> — block idle sleep via OS inhibit APIs (Phase 3). Use only if OS power profile wasn't set at deploy time.</span>
|
|
</label>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend>On wake</legend>
|
|
<label class="radio">
|
|
<input type="radio" name="on-wake" value="reset">
|
|
<span><strong>Reset only</strong> — clear all data, navigate to home.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="on-wake" value="lock">
|
|
<span><strong>Lock only</strong> — show unlock overlay.</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="on-wake" value="both" checked>
|
|
<span><strong>Both</strong> — reset then show unlock overlay (default, recommended).</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="on-wake" value="nothing">
|
|
<span><strong>Nothing</strong> — ignore wake events (testing only; never use in production).</span>
|
|
</label>
|
|
</fieldset>
|
|
</section>
|
|
|
|
<!-- ─── Step 5: Unlock method ─── -->
|
|
<section data-step="5" class="step" hidden>
|
|
<h2>Unlock method</h2>
|
|
<p class="step-intro">How operators release the kiosk. This is separate from the admin password that protects this wizard.</p>
|
|
|
|
<fieldset>
|
|
<legend>Method</legend>
|
|
<label class="radio">
|
|
<input type="radio" name="unlock-method" value="password" checked>
|
|
<span><strong>Password</strong> — single password (Argon2id hash stored by usher at 0600).</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="unlock-method" value="pin">
|
|
<span><strong>PIN</strong> — numeric PIN (Argon2id hash stored by usher at 0600).</span>
|
|
</label>
|
|
<label class="radio">
|
|
<input type="radio" name="unlock-method" value="totp" disabled>
|
|
<span><strong>TOTP + password</strong> — time-based one-time password plus password (Phase 5+).</span>
|
|
</label>
|
|
</fieldset>
|
|
|
|
<label>
|
|
<span>Kiosk unlock password</span>
|
|
<input type="password" id="cfg-unlock-password" autocomplete="new-password">
|
|
<small>
|
|
Used to release the kiosk session (via the unlock button in the hidden menu).
|
|
usher stores the Argon2id hash at ~/.config/vestibule/unlock.hash (0600) and
|
|
verifies every attempt against it. The hash never touches browser storage.
|
|
</small>
|
|
</label>
|
|
|
|
<label>
|
|
<span>Confirm unlock password</span>
|
|
<input type="password" id="cfg-unlock-password-confirm" autocomplete="new-password">
|
|
</label>
|
|
</section>
|
|
|
|
<!-- ─── Step 6: Review & save ─── -->
|
|
<section data-step="6" class="step" hidden>
|
|
<h2>Review & save</h2>
|
|
<p class="step-intro">Confirm the configuration below. Saving overwrites any existing configuration.</p>
|
|
|
|
<pre id="review-output" class="review-output"></pre>
|
|
|
|
<div class="review-actions">
|
|
<button id="btn-save" class="primary">Save configuration</button>
|
|
<button id="btn-cancel" class="secondary">Cancel</button>
|
|
</div>
|
|
|
|
<p id="save-result" class="save-result" hidden></p>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<footer class="wizard-nav">
|
|
<button id="btn-prev" class="secondary">Previous</button>
|
|
<span class="wizard-progress" id="wizard-progress">Step 1 of 6</span>
|
|
<button id="btn-next" class="primary">Next</button>
|
|
</footer>
|
|
|
|
</section>
|
|
|
|
<script src="url-policy.js"></script>
|
|
<script src="admin.js"></script>
|
|
</body>
|
|
</html>
|