364 lines
98 KiB
HTML
364 lines
98 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<meta name="theme-color" content="#0b1220">
|
||
<title>Site Gateway</title>
|
||
<meta name="description" content="Host sites, proxy services, and manage HTTPS from one simple dashboard.">
|
||
<link rel="icon" type="image/png" href="/site-gateway-icon-approved.png">
|
||
<link rel="stylesheet" href="/styles.css?v=0.11.82">
|
||
</head>
|
||
<body>
|
||
<div id="login" class="login-shell hidden">
|
||
<form id="login-form" class="login-card">
|
||
<div class="brand-stack">
|
||
<img class="brand-icon-lg" src="/site-gateway-icon-approved.png" alt="">
|
||
<img class="brand-wordmark-lg" src="/site-gateway-wordmark-approved.png" alt="Site Gateway">
|
||
</div>
|
||
<p class="eyebrow">Host. Proxy. Secure.</p>
|
||
<h1 id="login-title">Welcome back</h1>
|
||
<p id="login-copy" class="muted">Sign in to manage your sites.</p>
|
||
<label>Username<input name="username" autocomplete="username" required></label>
|
||
<label>Password<input name="password" type="password" autocomplete="current-password" required></label>
|
||
<p id="login-error" class="error" role="alert"></p>
|
||
<button class="button primary wide">Sign in</button>
|
||
</form>
|
||
<form id="mfa-login-form" class="login-card hidden">
|
||
<div class="brand-stack">
|
||
<img class="brand-icon-lg" src="/site-gateway-icon-approved.png" alt="">
|
||
<img class="brand-wordmark-lg" src="/site-gateway-wordmark-approved.png" alt="Site Gateway">
|
||
</div>
|
||
<p class="eyebrow">Two-factor authentication</p>
|
||
<h1>Enter your code</h1>
|
||
<p class="muted">Enter the 6-digit code from your authenticator app, or one of your recovery codes.</p>
|
||
<label>Code<input name="code" autocomplete="one-time-code" inputmode="numeric" maxlength="11" required autofocus></label>
|
||
<p id="mfa-login-error" class="error" role="alert"></p>
|
||
<button class="button primary wide">Verify</button>
|
||
<button type="button" id="mfa-login-cancel" class="button secondary wide">Back to sign in</button>
|
||
</form>
|
||
</div>
|
||
|
||
<dialog id="setup-dialog" class="setup-dialog">
|
||
<form id="setup-form" class="dialog-card setup-card">
|
||
<div class="brand-stack">
|
||
<img class="brand-icon-lg" src="/site-gateway-icon-approved.png" alt="">
|
||
<img class="brand-wordmark-lg" src="/site-gateway-wordmark-approved.png" alt="Site Gateway">
|
||
</div>
|
||
<p class="eyebrow">First-time setup</p>
|
||
<h1>Secure your administrator account</h1>
|
||
<p class="muted">Confirm or change the administrator details below. The credentials supplied during installation were used only to bootstrap this account.</p>
|
||
<label>Display name<input name="displayName" value="Administrator" maxlength="80" autocomplete="name" required></label>
|
||
<label>Administrator username<input name="username" minlength="3" maxlength="64" pattern="[A-Za-z0-9][A-Za-z0-9._-]{2,63}" autocomplete="username" required></label>
|
||
<label>New password<input name="password" type="password" minlength="8" autocomplete="new-password" required><small>Use at least 8 characters and a password unique to Site Gateway.</small></label>
|
||
<label>Confirm password<input name="confirmPassword" type="password" minlength="8" autocomplete="new-password" required></label>
|
||
<p id="setup-error" class="error" role="alert"></p>
|
||
<button class="button primary wide">Save administrator account</button>
|
||
</form>
|
||
</dialog>
|
||
|
||
<div id="dashboard" class="app-shell hidden">
|
||
<aside>
|
||
<button type="button" class="brand" data-view="overview" aria-label="Go to dashboard"><img class="brand-icon" src="/site-gateway-icon-approved.png" alt=""><img class="brand-wordmark" src="/site-gateway-wordmark-approved.png" alt="Site Gateway"></button>
|
||
<nav aria-label="Publishing types">
|
||
<button class="nav-active" data-view="overview">Dashboard</button>
|
||
<button data-view="hosted">Hosted sites <span id="hosted-count">0</span></button>
|
||
<button data-view="proxies">Proxy hosts <span id="proxy-count">0</span></button>
|
||
<button data-view="streaming">Streaming hosts <span id="streaming-count">0</span></button>
|
||
<button data-view="redirects">Redirect hosts <span id="redirect-count">0</span></button>
|
||
<button data-view="certificates">Certificates <span id="certificate-count">0</span></button>
|
||
<button data-view="access">Access Lists <span id="access-count">0</span></button>
|
||
<button data-view="performance">Performance</button>
|
||
<button data-view="logs">Logs</button>
|
||
</nav>
|
||
<div class="aside-utilities"><button class="admin-only" data-view="administration">Administration</button><button data-view="account">My Account</button><button data-view="documentation">Documentation</button></div>
|
||
<div class="aside-footer"><span>Installed version</span><strong id="version-label">v—</strong></div>
|
||
</aside>
|
||
<main>
|
||
<div class="utility-bar" aria-label="Account and appearance">
|
||
<label class="theme-control" for="theme-select"><span>Theme</span><select id="theme-select" aria-label="Color theme"><option value="system">System</option><option value="dark">Dark</option><option value="light">Light</option></select></label>
|
||
<div class="account-control"><span>Signed in as <strong id="user-label">admin</strong></span><button id="logout" class="text-button">Sign out</button></div>
|
||
</div>
|
||
<nav class="mobile-nav" aria-label="Dashboard sections">
|
||
<button class="nav-active" data-view="overview">Dashboard</button>
|
||
<button data-view="hosted">Hosted</button>
|
||
<button data-view="proxies">Proxies</button>
|
||
<button data-view="redirects">Redirects</button>
|
||
<button data-view="certificates">TLS</button>
|
||
<button data-view="performance">Perf</button>
|
||
<button data-view="logs">Logs</button>
|
||
<button class="admin-only" data-view="administration">Admin</button><button data-view="account">Account</button><button data-view="documentation">Docs</button>
|
||
</nav>
|
||
<header>
|
||
<div><p class="eyebrow">Gateway control</p><h1 id="page-title">Dashboard</h1><p id="page-subtitle" class="muted">Health, activity, and system status at a glance.</p></div>
|
||
<button id="open-create" class="button primary">+ New hosted site</button><button id="check-health" class="button primary hidden">Run certificate check</button><button id="refresh-logs" class="button primary hidden">Refresh logs</button>
|
||
</header>
|
||
<section id="dashboard-view" class="dashboard-view" aria-label="Gateway dashboard">
|
||
<div class="metric-grid">
|
||
<button class="metric-card accent-green" data-target="hosted"><span class="metric-icon">↗</span><span class="metric-label">Hosted sites</span><strong id="dash-hosted-total">0</strong><span id="dash-hosted-detail">None configured</span></button>
|
||
<button class="metric-card accent-blue" data-target="proxies"><span class="metric-icon">⇌</span><span class="metric-label">Proxy hosts</span><strong id="dash-proxy-total">0</strong><span id="dash-proxy-detail">None configured</span></button>
|
||
<button class="metric-card accent-blue" data-target="certificates"><span class="metric-icon">▣</span><span class="metric-label">Certificates</span><strong id="dash-tls-total">0</strong><span id="dash-tls-detail">No TLS domains</span></button>
|
||
</div>
|
||
<div class="metric-strip">
|
||
<button class="metric-chip accent-amber" data-target="redirects"><span class="chip-icon">↪</span><span class="chip-copy"><span class="metric-label">Redirect hosts</span><strong id="dash-redirect-total">0</strong></span></button>
|
||
<button class="metric-chip accent-purple" data-target="streaming"><span class="chip-icon">⇄</span><span class="chip-copy"><span class="metric-label">Streaming hosts</span><strong id="dash-stream-total">0</strong></span></button>
|
||
<div class="metric-chip" id="dash-attention-chip"><span class="chip-icon" id="dash-attention-icon">◈</span><span class="chip-copy"><span class="metric-label">Needs attention</span><strong id="dash-attention-total">0</strong><small id="dash-attention-detail">No current issues</small></span></div>
|
||
<button class="metric-chip accent-blue" data-target="performance"><span class="chip-icon">∿</span><span class="chip-copy"><span class="metric-label">Throughput</span><strong id="dash-throughput-total">0</strong><small id="dash-throughput-detail">requests / min</small></span></button>
|
||
</div>
|
||
<div class="dashboard-columns">
|
||
<section class="dashboard-panel health-panel status-healthy" id="health-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Live health</p><h2>Services</h2></div><div class="health-actions"><span id="overall-health" class="health-badge healthy">Healthy</span><button id="refresh-health" class="icon-button" aria-label="Refresh health checks" title="Refresh health checks">↻</button></div></div>
|
||
<div class="health-grid">
|
||
<div class="health-tile"><span id="gateway-health-dot" class="status-dot running"></span><span class="health-tile-copy"><strong>Gateway</strong><small id="gateway-health-copy">Configuration valid</small></span></div>
|
||
<div class="health-tile"><span id="http-health-dot" class="status-dot running"></span><span class="health-tile-copy"><strong>HTTP · Port 80</strong><small id="http-health-copy">Ready and responding</small></span></div>
|
||
<div class="health-tile"><span id="https-health-dot" class="status-dot inactive"></span><span class="health-tile-copy"><strong>HTTPS · Port 443</strong><small id="https-health-copy">Not configured</small></span></div>
|
||
<div class="health-tile"><span id="storage-health-dot" class="status-dot running"></span><span class="health-tile-copy"><strong>Persistent storage</strong><small id="storage-health-copy">Data directory writable</small></span></div>
|
||
<div class="health-tile"><span id="streaming-health-dot" class="status-dot inactive"></span><span class="health-tile-copy"><strong>Streaming ports</strong><small id="streaming-health-copy">No streaming hosts configured</small></span></div>
|
||
<div class="health-tile"><span id="upstream-health-dot" class="status-dot inactive"></span><span class="health-tile-copy"><strong>Upstreams</strong><small id="upstream-health-copy">No proxy hosts configured</small></span></div>
|
||
</div>
|
||
<p id="health-checked" class="checked-time"><span class="live-dot" id="health-live-dot"></span>Last checked —</p>
|
||
</section>
|
||
<section class="dashboard-panel system-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Runtime</p><h2>System</h2></div></div>
|
||
<dl class="system-grid">
|
||
<div class="system-tile"><dt>Uptime</dt><dd id="system-uptime">—</dd></div>
|
||
<div class="system-tile"><dt>Memory</dt><dd id="system-memory">—</dd></div>
|
||
<div class="system-tile"><dt>Site Gateway data</dt><dd id="system-data">—</dd><small>Used by sites and configuration</small></div>
|
||
<div class="system-tile"><dt>Storage available</dt><dd id="system-disk">—</dd><small>Available on the /data volume</small></div>
|
||
<div class="system-tile"><dt>Site Gateway</dt><dd id="system-app-version">—</dd></div>
|
||
<div class="system-tile"><dt>Caddy</dt><dd id="system-caddy-version">—</dd></div>
|
||
<div class="system-tile"><dt>Database</dt><dd id="system-database">—</dd><small id="system-database-detail">SQLite storage</small></div>
|
||
<div class="system-tile"><dt>Public IP</dt><dd id="system-public-ip">—</dd><small id="system-public-ip-detail">Not yet checked</small></div>
|
||
</dl>
|
||
</section>
|
||
</div>
|
||
<div id="dashboard-jobs-slot" class="dashboard-jobs-slot"></div>
|
||
<div class="dashboard-columns lower" id="dashboard-lower-columns">
|
||
<section class="dashboard-panel attention-panel" id="attention-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Action required</p><h2>Needs attention</h2></div></div>
|
||
<div id="attention-list" class="dashboard-list"><div class="all-clear"><span class="status-dot running"></span><span>Everything looks good — no issues to review.</span></div></div>
|
||
</section>
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Recent activity</p><h2>Recent activity</h2></div><button class="text-button" data-view="logs">View all logs →</button></div>
|
||
<div id="activity-list" class="dashboard-list"><p class="quiet-state">No recent activity.</p></div>
|
||
</section>
|
||
</div>
|
||
</section>
|
||
<section id="certificates-view" class="feature-view hidden">
|
||
<p id="cert-last-checked" class="certificate-status muted checked-time">Last checked —</p>
|
||
<div class="feature-summary">
|
||
<div><strong id="cert-healthy">0</strong><span>Healthy</span></div><div><strong id="cert-30">0</strong><span>Within 30 days</span></div><div><strong id="cert-7">0</strong><span>Within 7 days</span></div><div><strong id="cert-warning">0</strong><span>Needs attention</span></div><div><strong id="cert-pending">0</strong><span>Not detected</span></div>
|
||
</div>
|
||
<div class="diagnostic-section-heading"><p class="eyebrow">Certificate inventory</p><h2>Certificates</h2><p class="muted">Managed and uploaded certificates assigned to configured domains.</p></div><div id="certificate-list" class="data-list diagnostic-list"><p class="quiet-state">Loading certificates…</p></div>
|
||
<section class="dashboard-panel readiness-panel"><div class="panel-heading"><div><p class="eyebrow">Guided diagnostics</p><h2>Domain readiness</h2><p class="muted">DNS, listener, TLS, and upstream checks for every configured domain.</p></div></div><div id="readiness-list" class="dashboard-list diagnostic-list"><p class="quiet-state">Checking configured domains…</p></div></section>
|
||
</section>
|
||
<section id="logs-view" class="feature-view hidden">
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Access logs</p><h2>Access requests</h2><p class="muted">Requests handled by configured domains. Sensitive headers are never displayed.</p></div></div>
|
||
<div class="log-filters"><label>Domain<select id="log-host"><option value="">All domains</option></select></label><label>Response status<select id="log-status"><option value="">All responses</option><option value="2">Successful · 2xx</option><option value="3">Redirects · 3xx</option><option value="4">Client errors · 4xx</option><option value="5">Server errors · 5xx</option></select></label></div>
|
||
<p id="log-summary" class="muted feature-note">No requests in the current view. <span id="log-last-checked">Not checked yet.</span></p>
|
||
<div class="table-wrap log-table-wrap"><table class="log-table"><thead><tr><th>Time</th><th>Domain</th><th>Request</th><th>Status</th><th>Duration</th></tr></thead><tbody id="log-rows"></tbody></table></div>
|
||
</section>
|
||
<section class="dashboard-panel log-activity"><div class="panel-heading"><div><p class="eyebrow">Gateway events</p><h2>Activity and errors</h2><p class="muted">Configuration, certificate, and health events recorded by Site Gateway.</p></div></div><div class="event-filters"><label>Severity<select id="event-severity"><option value="">All severities</option><option value="ok">Normal</option><option value="warning">Warnings</option><option value="error">Errors</option></select></label><label>Category<select id="event-category"><option value="">All categories</option><option value="configuration">Configuration</option><option value="certificate">Certificates / TLS</option><option value="health">Upstream health</option><option value="authentication">Authentication</option><option value="backup">Backups</option><option value="system">System</option></select></label></div><div id="gateway-log-list" class="dashboard-list event-list diagnostic-list"></div></section>
|
||
</section>
|
||
<section id="performance-view" class="feature-view hidden">
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Trend</p><h2 id="performance-trend-title">Requests · last 6 hours</h2></div></div>
|
||
<div class="log-filters"><label>Domain<select id="performance-host"><option value="">All domains</option></select></label><label>Range<select id="performance-range"><option value="1">Last hour</option><option value="3">Last 3 hours</option><option value="6" selected>Last 6 hours</option><option value="12">Last 12 hours</option><option value="24">Last 24 hours</option><option value="72">Last 3 days</option><option value="168">Last 7 days</option></select></label></div>
|
||
<p id="performance-summary" class="muted feature-note">No requests recorded yet. <span id="performance-last-checked">Not checked yet.</span></p>
|
||
<div class="performance-sparkline-wrap">
|
||
<svg id="performance-sparkline" class="performance-sparkline" viewBox="0 0 600 140" preserveAspectRatio="none" aria-label="Request volume trend"></svg>
|
||
<div id="performance-sparkline-labels" class="performance-sparkline-labels"></div>
|
||
</div>
|
||
</section>
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Per-route</p><h2>Throughput by domain</h2></div></div>
|
||
<p class="muted feature-note">Requests, error rate, and average response time for each configured domain.</p>
|
||
<div class="table-wrap performance-table-wrap"><table class="performance-table"><thead><tr><th>Domain</th><th>Last hour</th><th>Last 24h</th><th>Avg. response</th></tr></thead><tbody id="performance-rows"></tbody></table></div>
|
||
</section>
|
||
</section>
|
||
<section id="users-view" class="feature-view hidden">
|
||
<div class="admin-tabs"><button class="tab-active" data-admin-tab="users">Users</button><button data-admin-tab="defaults">Gateway defaults</button><button data-admin-tab="backups">Backup & restore</button><button data-admin-tab="security">Security & updates</button><button data-admin-tab="danger" class="danger-tab">Danger Zone</button></div>
|
||
<section data-admin-panel="users">
|
||
<div id="user-summary" class="summary user-summary" aria-label="User summary"></div>
|
||
<div id="user-list" class="user-grid"><p class="quiet-state">Loading users…</p></div>
|
||
</section>
|
||
<section data-admin-panel="defaults" class="hidden settings-panel">
|
||
<h2>Default site</h2><p class="muted">Choose what visitors receive when no configured host matches their request.</p>
|
||
<div class="callout"><strong>HTTP fallback</strong><span>This response is used for unknown HTTP hostnames. Unknown HTTPS hostnames are rejected unless a matching certificate and route exist, preventing misleading certificate warnings.</span></div>
|
||
<form id="default-site-form" class="settings-form"><div class="form-section"><p class="eyebrow">Response</p><label>Response<select name="mode"><option value="themed404">Themed route-not-found page (404)</option><option value="welcome">Gateway ready page (200)</option><option value="abort">No response — close connection</option><option value="redirect">Redirect elsewhere</option><option value="custom">Custom HTML</option></select></label></div><div class="form-section"><p class="eyebrow">Page content</p><label>Page heading<input name="title" maxlength="100" placeholder="Route not found"></label><label>Explanation<textarea name="message" maxlength="500" placeholder="The gateway is responding, but this address has not been configured."></textarea></label></div><div class="form-section"><p class="eyebrow">Redirect behavior</p><label>Redirect destination<input name="redirectUrl" type="url" placeholder="https://www.example.com"></label><label>Redirect code<select name="redirectCode"><option>302</option><option>301</option><option>307</option><option>308</option></select></label><label class="check-control"><input name="preservePath" type="checkbox" checked><span>Preserve the requested path and query</span></label></div><div class="form-section form-section-wide"><p class="eyebrow">Custom response</p><label>Custom HTML<textarea name="customHtml" class="code-input" placeholder="<!doctype html>..."></textarea><small>Administrator-authored HTML only. Used when Custom HTML is selected.</small></label></div><div class="dialog-actions"><button class="button primary">Save & apply</button></div><p id="default-error" class="error"></p></form>
|
||
</section>
|
||
<section data-admin-panel="backups" class="hidden settings-panel"><div class="panel-heading"><div><h2>Backup & restore</h2><p class="muted">Create a copy, restore a previous version, or schedule automatic backups.</p></div><div class="row-actions"><button id="import-backup" class="button secondary">Import backup</button><button id="create-backup" class="button primary">Create backup</button></div></div><input id="backup-upload" type="file" accept=".sgbackup,application/zip" hidden><form id="backup-settings-form" class="settings-form backup-settings"><div class="form-section form-section-wide"><p class="eyebrow">Scheduled backups</p><div class="form-grid"><label class="check-control"><input name="enabled" type="checkbox"><span>Enable scheduled backups</span></label><label>Backup type<select name="type"><option value="configuration">Configuration only</option><option value="complete">Complete — configuration + hosted files</option></select><small id="backup-type-help">Configuration only includes settings and metadata, not uploaded Hosted Site files.</small></label><label>Schedule<select name="frequency"><option value="daily">Daily</option><option value="weekly">Weekly</option><option value="monthly">Monthly</option></select></label><label>Hour<select name="hour"></select></label><label>Keep<input name="retention" type="number" min="1" max="100" value="7"></label><label class="check-control"><input name="includeLogs" type="checkbox"><span>Include logs</span></label></div></div><div class="form-section form-section-wide"><p class="eyebrow">Encryption</p><div class="form-grid encryption-grid"><label class="backup-password-field"><span class="field-label">Backup encryption password <span class="optional">Optional</span></span><input name="backupPassword" type="password" autocomplete="new-password"><small>Used for manually created backups and required when restoring an encrypted archive. It is not stored by Site Gateway.</small></label><label class="check-control encryption-toggle"><input name="encrypt" type="checkbox"><span>Encrypt scheduled backups<small>Uses the container’s <code>BACKUP_PASSWORD</code> value. Enable only after configuring that value.</small></span></label></div></div><div class="dialog-actions"><button class="button secondary">Save schedule</button></div></form><div class="callout"><strong>Storage guidance</strong><span id="backup-path">Backups are stored in /data/backups. Mount /backups separately to protect against appdata disk failure.</span></div><div id="backup-list" class="data-list"></div></section>
|
||
<section data-admin-panel="security" class="hidden settings-panel"><h2>Security, health & updates</h2><section class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Reliability</p><h2>Configuration safety & updates</h2></div></div><dl class="system-grid"><div class="system-tile"><dt>Configuration safety</dt><dd>Validates generated Caddy configuration before every reload and retains the active configuration when validation fails.</dd></div><div class="system-tile"><dt>Container updates</dt><dd>Installed by pulling a new pinned image. Create a backup before changing versions.</dd></div></dl></section><section class="dashboard-panel"><div class="panel-heading"><div><p class="eyebrow">Troubleshooting & support</p><h2>Gateway diagnostics</h2><p class="muted">Run checks and download a redacted report when you need to investigate a gateway issue.</p></div><button id="download-support" class="button secondary admin-only">Download support report</button></div><p class="muted feature-note">The report includes version, configuration health, certificate readiness, upstream checks, and recent events. Passwords, private keys, session secrets, cookies, and certificate contents are excluded.</p></section><form id="health-settings-form" class="settings-form"><label>Renewing-soon warning<input name="warningDays" type="number" min="8" max="120" value="30"><small>Days remaining before a certificate is highlighted.</small></label><label>Critical warning<input name="criticalDays" type="number" min="1" max="119" value="7"><small>Must be lower than the renewing-soon threshold.</small></label><label>Stale health data<input name="staleMinutes" type="number" min="2" max="1440" value="10"><small>Minutes before a displayed check is considered old.</small></label><div class="dialog-actions"><button class="button primary">Save health settings</button></div></form></section>
|
||
<section data-admin-panel="danger" class="hidden settings-panel danger-zone"><h2>Danger Zone</h2><p class="muted">These actions can permanently remove Site Gateway data. Review each warning carefully before continuing.</p><div class="danger-card"><p class="eyebrow">Restore defaults</p><h3>Reset gateway preferences</h3><p>Restore default site behavior, backup scheduling, certificate thresholds, and interface preferences. Your users, routes, certificates, logs, and backups remain intact.</p><button id="restore-defaults" class="button secondary">Restore default settings</button></div><div class="danger-card destructive"><p class="eyebrow">Permanent action</p><h3>Factory reset</h3><p>Deletes all Site Gateway data under <code>/data</code>, including users, routes, certificates, logs, backups, and settings. Docker-mounted files outside <code>/data</code> are not affected. The container restarts at first-install setup.</p><form id="factory-reset-form" class="danger-form"><label>Administrator username<input name="username" autocomplete="username" required></label><label>Administrator password<input name="password" type="password" autocomplete="current-password" required></label><label>Type <strong>FACTORY RESET</strong> to confirm<input name="confirmation" required autocomplete="off"></label><p id="factory-reset-error" class="error"></p><div class="danger-actions"><button class="button secondary" type="button" id="factory-reset-cancel">Cancel</button><button class="button danger" type="submit">Erase all data and reset</button></div></form></div></section>
|
||
</section>
|
||
<section id="account-view" class="feature-view hidden">
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Profile</p><h2>Your account</h2></div></div>
|
||
<dl class="system-grid">
|
||
<div class="system-tile"><dt>Display name</dt><dd id="account-display-name">—</dd></div>
|
||
<div class="system-tile"><dt>Username</dt><dd id="account-username">—</dd></div>
|
||
<div class="system-tile"><dt>Role</dt><dd id="account-role">—</dd></div>
|
||
</dl>
|
||
</section>
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Password</p><h2>Change your password</h2></div></div>
|
||
<form id="account-password-form" class="settings-form">
|
||
<label>Current password<input name="currentPassword" type="password" autocomplete="current-password" required></label>
|
||
<label>New password<input name="newPassword" type="password" minlength="8" autocomplete="new-password" required></label>
|
||
<label style="grid-column:1/-1;max-width:calc(50% - 9px)">Confirm new password<input name="confirmPassword" type="password" minlength="8" autocomplete="new-password" required></label>
|
||
<p id="account-password-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button class="button primary" type="submit">Change password</button></div>
|
||
</form>
|
||
</section>
|
||
<section class="dashboard-panel">
|
||
<div class="panel-heading"><div><p class="eyebrow">Security</p><h2>Two-factor authentication</h2></div><span id="account-mfa-status" class="status-pill"><span class="status-dot inactive"></span>Off</span></div>
|
||
<p class="muted feature-note">Require a 6-digit code from an authenticator app, in addition to your password, when signing in.</p>
|
||
<div id="account-mfa-actions" class="dialog-actions">
|
||
<button id="account-mfa-enable" class="button primary">Enable two-factor authentication</button>
|
||
<button id="account-mfa-disable" class="button secondary danger-text hidden">Disable two-factor authentication</button>
|
||
<button id="account-mfa-recovery" class="button secondary hidden">Regenerate recovery codes</button>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<dialog id="mfa-setup-dialog" class="setup-dialog">
|
||
<div class="dialog-card">
|
||
<p class="eyebrow">Two-factor authentication</p>
|
||
<h1>Scan this code</h1>
|
||
<p class="muted">Scan with an authenticator app (Google Authenticator, 1Password, Authy, etc.), or enter the key manually.</p>
|
||
<div id="mfa-setup-qr" class="mfa-qr"></div>
|
||
<p class="muted">Manual entry key: <code id="mfa-setup-secret"></code></p>
|
||
<form id="mfa-setup-confirm-form">
|
||
<label>Enter the 6-digit code to confirm<input name="code" autocomplete="one-time-code" inputmode="numeric" maxlength="6" required></label>
|
||
<p id="mfa-setup-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" id="mfa-setup-cancel" class="button secondary">Cancel</button><button class="button primary" type="submit">Confirm and enable</button></div>
|
||
</form>
|
||
</div>
|
||
</dialog>
|
||
<dialog id="mfa-password-dialog" class="setup-dialog">
|
||
<form id="mfa-password-form" class="dialog-card">
|
||
<p class="eyebrow" id="mfa-password-title">Confirm your password</p>
|
||
<h1 id="mfa-password-heading">Confirm it's you</h1>
|
||
<label>Current password<input name="password" type="password" autocomplete="current-password" required></label>
|
||
<p id="mfa-password-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" id="mfa-password-cancel" class="button secondary">Cancel</button><button class="button primary" type="submit">Continue</button></div>
|
||
</form>
|
||
</dialog>
|
||
<dialog id="mfa-recovery-dialog" class="setup-dialog">
|
||
<div class="dialog-card">
|
||
<p class="eyebrow">Save these now</p>
|
||
<h1>Your recovery codes</h1>
|
||
<p class="muted">Each code can be used once to sign in if you lose access to your authenticator app. Store them somewhere safe — they won't be shown again.</p>
|
||
<pre id="mfa-recovery-codes" class="mfa-recovery-codes"></pre>
|
||
<div class="dialog-actions"><button type="button" id="mfa-recovery-done" class="button primary">I've saved these codes</button></div>
|
||
</div>
|
||
</dialog>
|
||
<section id="management-summary" class="summary hidden" aria-label="Site summary"><div><span id="running-dot" class="status-dot inactive"></span><strong id="running-count">0</strong><span id="running-label">No sites running</span></div><div><span id="disabled-dot" class="status-dot inactive"></span><strong id="disabled-count">0</strong><span id="disabled-label">No disabled sites</span></div><div><span id="error-dot" class="status-dot inactive"></span><strong id="error-count">0</strong><span id="error-label">No issues</span></div><div class="port-note">Ports <strong id="port-range">9000–9099</strong></div></section>
|
||
<section id="streaming-view" class="feature-view hidden"><div id="stream-list" class="site-grid"></div><section id="stream-empty" class="empty hidden"><div class="empty-icon">⇄</div><h2>Create your first streaming host</h2><p>Forward raw TCP or UDP traffic on a specific port straight to another host and port — no domain, no HTTPS.</p><button class="button primary create-trigger">Create a streaming host</button></section></section>
|
||
<section id="redirects-view" class="feature-view hidden"><div id="redirect-list" class="site-grid"></div><section id="redirect-empty" class="empty hidden"><div class="empty-icon">↪</div><h2>Create your first redirect</h2><p>Send an old domain to a new destination while preserving its path if you choose.</p><button class="button primary create-trigger">Create a redirect host</button></section></section>
|
||
<section id="access-view" class="feature-view hidden"><div id="access-list" class="data-list"></div></section>
|
||
<section id="documentation-view" class="feature-view hidden docs"><div class="docs-intro"><p class="eyebrow">Site Gateway manual</p><h2>Every section, every field, explained</h2><p>A complete reference for the Dashboard, Hosted Sites, Proxy Hosts, Redirect Hosts, Streaming Hosts, Certificates, Access Lists, My Account & two-factor authentication, Administration, Performance monitoring, and every advanced control between them. Start with an area’s overview, then use its field reference when you need to know what one specific setting does.</p><div class="docs-search-panel"><label class="doc-search"><span>Search the complete manual</span><input id="doc-search" type="search" placeholder="Search “upstream TLS”, “Minecraft”, “CIDR”, “backup”, or any field name"></label><small>Searches every heading, field name, and explanation on this page.</small></div></div><div class="docs-layout"><aside class="docs-nav" aria-label="Documentation sections"><p class="eyebrow">Contents</p><button data-doc-jump="introduction">Introduction</button><button data-doc-jump="getting">Getting started</button><button data-doc-jump="dashboard">Dashboard</button><button data-doc-jump="hosted">Hosted Sites</button><button data-doc-jump="proxy">Proxy Hosts</button><button data-doc-jump="redirect">Redirect Hosts</button><button data-doc-jump="streaming">Streaming Hosts</button><button data-doc-jump="certificates">Certificates</button><button data-doc-jump="access lists overview">Access Lists</button><button data-doc-jump="users">Users & Groups</button><button data-doc-jump="my account">My Account</button><button data-doc-jump="defaults">Gateway Defaults</button><button data-doc-jump="backup">Backup & Restore</button><button data-doc-jump="security">Security & Health</button><button data-doc-jump="danger">Danger Zone</button><button data-doc-jump="access logs">Logs</button><button data-doc-jump="performance throughput">Performance</button><button data-doc-jump="common">Common Controls</button><button data-doc-jump="icons">Icons</button><button data-doc-jump="troubleshooting">Troubleshooting</button></aside><div id="docs-content"><article data-doc="introduction why built philosophy caddy novice expert overview"><p class="eyebrow">Introduction</p><h2>Why Site Gateway exists</h2><p>Reverse proxies often expose powerful settings without explaining what they change. Site Gateway provides a visual, Caddy-powered control plane for static sites, proxy routes, redirects, raw TCP/UDP streams, HTTPS, health checks, access control, and recovery — covering everything from a single ZIP upload to a full homelab of proxied applications.</p><h3>How this manual is organized</h3><p>Each area below has an <strong>overview</strong> article (what it’s for and how to configure the common case) followed by a <strong>field reference</strong> article covering every advanced control, one at a time. Use the sidebar to jump straight to a section, or search for any field name, error message, or setting — search matches headings, body text, and field names together.</p><h3>Novice path</h3><p>Create one route, test it locally, then add a domain and TLS. Keep defaults until you have a reason to change them.</p><h3>Expert note</h3><p>Configuration is stored in SQLite under <code>/data</code> and every generated Caddy configuration is validated before reload — if validation fails, the previous working configuration stays active and the failure is explained in Gateway Events.</p></article><article data-doc="getting started install first login setup administrator"><p class="eyebrow">Getting started</p><h2>From installation to your first route</h2><p>Install the container with persistent <code>/data</code> storage, open the management port (<code>8080</code> by default), and complete the administrator setup screen shown on first launch. Before publishing public domains, confirm DNS already points to this server and that ports 80 and 443 are free for Site Gateway to use.</p><h3>Choosing your first route type</h3><ul><li><strong>Hosted Site:</strong> you have static files (a ZIP or a single <code>index.html</code>) and want Site Gateway to serve them directly.</li><li><strong>Proxy Host:</strong> you already have an application running somewhere — another container, a LAN device, another server — and want a domain and HTTPS in front of it.</li><li><strong>Redirect Host:</strong> you want a domain to simply forward visitors to a different address.</li><li><strong>Streaming Host:</strong> you need to forward a raw TCP or UDP port (SSH, Minecraft, a game server) with no domain and no HTTPS involved.</li></ul><h3>Example</h3><p>Publish a ZIP on a direct port first so you can confirm it works over LAN, then add <code>www.example.com</code> once DNS and port forwarding are ready and Automatic HTTPS can issue a certificate.</p></article><article data-doc="dashboard overview metric strip live health runtime system scheduled jobs needs attention recent activity public ip throughput"><p class="eyebrow">Dashboard</p><h2>What each panel is telling you</h2><p>The Dashboard is a glance-and-go summary — every number and status on it links back to a full section elsewhere, so you never have to act on the Dashboard itself.</p><h3>Metric strip</h3><p>Counts of Hosted Sites, Proxy Hosts, and Certificates needing attention, plus a <strong>Throughput</strong> chip showing live requests in the last minute across every configured domain — click it to open the full Performance tab.</p><h3>Live Health</h3><ul><li><strong>Reverse proxy / Certificates / Persistent storage:</strong> the original health tiles.</li><li><strong>Streaming ports:</strong> how many enabled Streaming Hosts are actually bound and listening, versus configured.</li><li><strong>Upstreams:</strong> how many Proxy Hosts are currently passing their health check.</li></ul><h3>Runtime & System</h3><p>Container and version facts, plus a <strong>Public IP</strong> tile showing your gateway's current public address, rechecked automatically every 60 minutes — prep work for future Dynamic DNS support. This is a status fact, not a job you configure.</p><h3>Scheduled Jobs</h3><p>Background jobs shown as status tiles — proxy health checks, backups, log pruning, certificate/domain readiness checks, and the Public IP check — each showing its schedule and last result.</p><h3>Needs Attention</h3><p>Lists anything that needs a decision (a failing health check, an expiring certificate). Clicking an item jumps straight to it. When nothing needs attention, the panel collapses to a single all-clear banner instead of an empty list.</p><h3>Recent Activity</h3><p>The last several configuration and operational events, with relative timestamps (“4 hours ago”). This is a shortcut, not a separate log — the same events are searchable in full under Logs → Gateway Events.</p></article><article data-doc="hosted sites overview static zip index upload configure create name domain port tls hsts"><p class="eyebrow">Hosted Sites</p><h2>Overview: publish a static website</h2><p>A Hosted Site serves files you upload directly — no separate application or container required. Use it for a static site, a single-page app build, documentation, or anything that’s just HTML/CSS/JS.</p><h3>Creating a Hosted Site</h3><ul><li><strong>Name:</strong> the label shown in Site Gateway; it doesn’t need to match the domain.</li><li><strong>Primary domain</strong> <span class="optional">Optional</span>: leave blank for port-only LAN access, or set a hostname to serve the same files there once DNS is ready.</li><li><strong>Additional domains:</strong> one alias per line — every alias serves the same files under the same TLS settings as the primary domain.</li><li><strong>Port:</strong> the direct LAN port this site answers on. Must fall inside the configured port range (shown as a hint on the form, default <code>9000–9099</code>) and not already be used by another site.</li><li><strong>TLS:</strong> <em>Automatic public HTTPS</em> (default, requires a working domain and open ports 80/443), <em>Internal HTTPS for trusted local devices</em>, or <em>HTTP only</em>.</li><li><strong>Enable HSTS after HTTPS is verified:</strong> only turn this on once you’ve confirmed HTTPS works for every client — HSTS tells browsers to refuse HTTP entirely for this domain going forward, and that’s hard to undo quickly.</li><li><strong>Website files:</strong> upload a ZIP whose root contains <code>index.html</code> (a single top-level folder inside the ZIP is automatically flattened), or upload a bare <code>index.html</code> file directly. Up to 250 MB.</li></ul><h3>Updating an existing site</h3><p>Use <strong>Replace files</strong> from the card’s menu to upload a new ZIP or HTML file without recreating the site or losing its domain/TLS/health settings. Use <strong>Domain & TLS</strong> to change the domain, TLS mode, or any advanced setting below.</p><h3>Expected behavior</h3><p>Files are served immediately on the chosen port and, once configured, through the domain as well.</p></article><article data-doc="hosted sites advanced access list compression headers hsts subdomains custom caddy configuration health monitor"><p class="eyebrow">Hosted Sites</p><h2>Advanced options and health monitoring</h2><p>Expand <strong>Advanced options</strong> on the create or edit form for these controls:</p><ul><li><strong>Access List:</strong> applies a reusable network or login restriction before any visitor reaches this site. Choose <em>Public — no Access List</em> to leave it open.</li><li><strong>Compression:</strong> <em>Automatic zstd + gzip</em> (default), <em>gzip only</em>, or <em>Off</em>.</li><li><strong>Request headers</strong> / <strong>Response headers:</strong> one <code>Name: value</code> pair per line, up to 30 each. Request headers are added before Caddy processes the request; response headers are added to what visitors receive.</li><li><strong>Apply HSTS to subdomains:</strong> adds <code>includeSubDomains</code> to the HSTS header; only takes effect when HSTS itself is on and TLS isn’t HTTP only.</li><li><strong>Custom Caddy configuration:</strong> expert-only raw Caddyfile lines appended to this site’s block, validated before reload. Lines that would touch the global config block, the admin API, storage, or <code>import</code>/<code>persist_config</code> directives are rejected outright, since those could affect every other route on the gateway.</li></ul><h3>Monitoring this site</h3><p>Hosted Sites can be health-checked the same way Proxy Hosts are: a <strong>Health-check path</strong> (default <code>/</code>), <strong>method</strong> (GET or HEAD), <strong>expected status</strong> (a code, list, or range such as <code>200</code>, <code>200,204</code>, or <code>200-499</code>), a <strong>timeout</strong> in seconds (1–60, default 4), up to 3 <strong>retries</strong>, and a <strong>Monitor this site</strong> toggle. Turning monitoring off shows the card as “Monitoring paused” instead of running a periodic check.</p></article><article data-doc="proxy hosts overview jellyfin vaultwarden plex forward upstream configure name domain target tls"><p class="eyebrow">Proxy Hosts</p><h2>Overview: connect a local application</h2><p>A Proxy Host puts a domain and HTTPS in front of something already running elsewhere — another container, a LAN device, or a remote service.</p><h3>Creating a Proxy Host</h3><ul><li><strong>Name:</strong> the label shown in Site Gateway.</li><li><strong>Primary domain</strong> and <strong>Additional domains:</strong> same behavior as Hosted Sites.</li><li><strong>Forward to:</strong> the upstream address, e.g. <code>http://192.168.1.20:8123</code>. Use the container name, LAN address, or application URL — no path or query string beyond a trailing slash.</li><li><strong>Upstream pool</strong> <span class="optional">Optional</span>: additional <code>http(s)://</code> targets, one per line, up to 10. When present, Caddy load-balances across every healthy target instead of using the single Forward-to address alone.</li><li><strong>TLS:</strong> Automatic public HTTPS, Internal HTTPS, <em>Custom uploaded certificate</em>, or HTTP only.</li><li><strong>Enable HSTS after HTTPS is verified.</strong></li></ul><h3>Example</h3><p>For Jellyfin at <code>192.168.1.20:8096</code>, use domain <code>jellyfin.example.com</code> and forward target <code>http://192.168.1.20:8096</code>. Site Gateway checks the upstream continuously and Caddy manages an eligible public HTTPS certificate automatically.</p></article><article data-doc="proxy hosts advanced access list health expected status timeout retries compression block common exploits security custom locations headers upstream tls server name insecure hsts caddy configuration"><p class="eyebrow">Proxy Hosts</p><h2>Advanced options, field by field</h2><p>Most applications only need a domain, Forward-to target, and TLS choice. These controls are for applications with unusual paths, authentication needs, response codes, headers, or performance requirements.</p><ul><li><strong>Access List:</strong> applies reusable login and network rules before the upstream is reached.</li><li><strong>Health-check path</strong> and <strong>method:</strong> the request Site Gateway makes when checking this application (path default <code>/</code>; GET or HEAD).</li><li><strong>Expected status:</strong> accepts a single code, a comma list, or a range — <code>200</code>, <code>200,204</code>, or <code>200-399</code>.</li><li><strong>Timeout in seconds:</strong> 1–60, default 4.</li><li><strong>Monitor this upstream:</strong> when off, the status shows “Monitoring paused” and no periodic request is made.</li><li><strong>Compression:</strong> Automatic zstd + gzip, gzip only, or Off.</li><li><strong>Block common exploits:</strong> when enabled, Site Gateway rejects requests that match common exploit-probe patterns — path traversal, WordPress/PHP admin and scanner paths, <code>.env</code>/<code>.git</code> exposure attempts, and SQL-injection-style query strings — with a 403, before the request ever reaches the upstream. This is a static, general-purpose ruleset, not a full web application firewall; it will not catch every attack and should not replace keeping the upstream application patched.</li><li><strong>Custom Locations:</strong> send specific paths to a different upstream. One per line: <code>/api/* | http://192.168.1.20:3001 | strip</code> or <code>preserve</code> — <em>strip</em> removes the matched prefix before forwarding, <em>preserve</em> keeps it. Up to 20 entries.</li><li><strong>Request headers</strong> / <strong>Response headers:</strong> same <code>Name: value</code>-per-line format as Hosted Sites.</li><li><strong>Upstream TLS server name:</strong> an optional SNI name expected by the upstream certificate. Only meaningful when every upstream target uses <code>https://</code> — the field is disabled otherwise.</li><li><strong>Ignore upstream TLS certificate errors:</strong> use only for a trusted internal HTTPS service with a self-signed or hostname-mismatched certificate; also requires an HTTPS upstream to take effect.</li><li><strong>Apply HSTS to subdomains.</strong></li><li><strong>Custom Caddy configuration:</strong> same validation rules as Hosted Sites’ custom configuration. NGINX syntax is not supported — Site Gateway generates Caddyfile syntax.</li></ul><h3>How to verify a change</h3><p>Save one setting at a time, watch the card’s upstream status line, check Access Logs, and compare against a direct request to the application. If Caddy rejects a custom configuration, Site Gateway keeps the last known-good configuration active and shows the rejection reason.</p></article><article data-doc="proxy custom certificate upload pem private key mismatch"><p class="eyebrow">Proxy Hosts</p><h2>Custom uploaded certificates</h2><p>Choosing <strong>Custom uploaded certificate</strong> as the TLS mode reveals two file fields: <strong>Certificate PEM</strong> and <strong>Private key PEM</strong>. Both are required together — Site Gateway verifies the private key’s public key actually matches the certificate before accepting the pair, and confirms the certificate’s Subject Alternative Names cover every domain configured on that Proxy Host. A certificate that doesn’t cover every alias is rejected rather than silently applied to only some of them.</p><p>Accepted files are stored under <code>/data/certificates/custom</code> and included in complete backups. Replacing a custom certificate later works the same way — upload both files again through the same field.</p></article><article data-doc="redirect hosts overview permanent temporary path query preserve configure"><p class="eyebrow">Redirect Hosts</p><h2>Overview: move an address safely</h2><p>A Redirect Host sends visitors from one domain straight to another, with no files hosted and no application behind it.</p><h3>Fields</h3><ul><li><strong>Name.</strong></li><li><strong>Source domain:</strong> the old address people currently use.</li><li><strong>Additional source domains:</strong> further aliases that redirect the same way.</li><li><strong>Destination:</strong> a complete <code>http://</code> or <code>https://</code> URL to send visitors to.</li><li><strong>Redirect type:</strong> <code>302</code> Temporary (default), <code>301</code> Permanent, <code>307</code> Temporary — preserve method, <code>308</code> Permanent — preserve method.</li><li><strong>TLS:</strong> Automatic HTTPS, HTTP only, or Internal HTTPS — this governs the source domain, not the destination.</li><li><strong>Preserve path and query</strong> (on by default): when enabled, <code>old.example.com/library?id=2</code> becomes <code>new.example.com/library?id=2</code> instead of always landing on the destination’s root.</li><li><strong>Access List</strong> <span class="optional">Optional</span>: protects the source domain the same way it protects Hosted Sites and Proxy Hosts.</li></ul><h3>Choosing a redirect type</h3><p>Use 301 or 308 only when the move is meant to be permanent — browsers and search engines cache these aggressively. Use 302 or 307 while you’re still testing the new destination.</p></article><article data-doc="streaming hosts overview tcp udp port forward raw minecraft ssh game server configure port target"><p class="eyebrow">Streaming Hosts</p><h2>Overview: raw TCP/UDP port forwarding</h2><p>A Streaming Host forwards a raw TCP or UDP port straight to another host and port — there’s no domain, no HTTPS, and no HTTP layer involved at all, unlike every other route type in Site Gateway. Use it for services that speak their own protocol directly over a port: SSH, a Minecraft or other game server, a VPN endpoint, or any similar TCP/UDP service.</p><h3>The one prerequisite: publish the port first</h3><p>Streaming Hosts listen for connections from <em>inside</em> the Site Gateway container itself — they are not routed through Caddy the way domains are. That means the incoming port must already be published on the container (added to your <code>docker-compose.yaml</code> or Unraid port mappings, with the container recreated) <strong>before</strong> you create the matching Streaming Host in the UI, or nothing outside the container will ever reach it.</p><h3>Example docker-compose port mapping</h3><p>To forward SSH (22) and a Minecraft server (25565):</p><p><code>ports:<br> - "22:22/tcp"<br> - "25565:25565/tcp"</code></p><h3>Creating a Streaming Host</h3><ul><li><strong>Name.</strong></li><li><strong>Incoming port:</strong> 1–65535. Cannot be the admin port, 80, 443, a port already inside the Hosted Sites port range, or a port already used by another Streaming Host.</li><li><strong>Forward to:</strong> a plain <code>host:port</code> address, e.g. <code>192.168.1.20:25565</code>. No <code>http://</code> — this is a raw socket forward, not a web address.</li><li><strong>TCP</strong> / <strong>UDP:</strong> enable one or both. At least one must stay checked.</li><li><strong>Monitor this target:</strong> runs a TCP reachability probe against the forward address every check cycle, even for UDP-only streams (UDP itself has no reliable way to “ping” a service).</li></ul></article><article data-doc="streaming hosts how it works tcp relay udp relay session multiplex health probe examples"><p class="eyebrow">Streaming Hosts</p><h2>How the forwarding actually works</h2><p><strong>TCP</strong> is a full one-to-one relay: every inbound connection gets its own fresh outbound connection to the forward target, and the two are piped together in both directions. Closing or erroring either side tears down the other.</p><p><strong>UDP</strong> is connectionless, so Site Gateway multiplexes many remote clients over one listening port by tracking a short-lived “session” per unique client address, each with its own outbound socket to the forward target. An idle session is cleaned up automatically after about a minute of inactivity.</p><h3>Health monitoring</h3><p>The monitor always performs a plain TCP reachability check against the forward address, with a 4-second timeout — this confirms the target host and port are reachable, not that the specific game or service protocol is fully healthy. Disabling monitoring shows the card as “Monitoring paused” rather than unreachable.</p><h3>Practical examples</h3><ul><li><strong>SSH:</strong> incoming port <code>2222</code>, forward to <code>192.168.1.30:22</code>, TCP only.</li><li><strong>Minecraft:</strong> incoming port <code>25565</code>, forward to <code>192.168.1.20:25565</code>, TCP (and UDP if the specific server/mod needs it — vanilla Minecraft is TCP-only).</li></ul><h3>Editing a Streaming Host</h3><p>Changing the port, target, or protocol checkboxes restarts the listener immediately to apply the change; toggling Enable/Disable does the same.</p></article><article data-doc="certificates overview automatic https prerequisites dns ports check now readiness"><p class="eyebrow">Certificates</p><h2>Overview: automatic HTTPS prerequisites</h2><p>For Automatic HTTPS to succeed, a domain must resolve to your public address, inbound ports 80 and 443 must reach Site Gateway, and no other service (including another reverse proxy) can already own those ports on this host.</p><h3>Domain readiness checks</h3><ul><li><strong>DNS:</strong> confirms the domain actually resolves, and to what address.</li><li><strong>HTTP / HTTPS:</strong> confirms Caddy itself is listening on ports 80/443 inside the container — this does not by itself prove the internet can reach you, only that the gateway is ready to answer if it can.</li><li><strong>TLS:</strong> reflects the certificate status for that domain (healthy, renewing soon, critical, expired, or “Waiting for Caddy,” meaning issuance hasn’t completed yet — not that anything is broken).</li><li><strong>Upstream</strong> (Proxy Hosts only): the latest health-check result for that route.</li></ul><h3>Check now</h3><p>Forces an immediate re-check of every certificate, domain readiness result, and health probe instead of waiting for the periodic background check.</p><h3>Novice workflow</h3><p>Confirm DNS, forward ports 80 and 443, stop any competing proxy, then run Check now. Don’t troubleshoot an upstream application until the domain and HTTPS checks themselves are healthy.</p></article><article data-doc="certificates field reference issuer expiration warning critical thresholds serial fingerprint"><p class="eyebrow">Certificates</p><h2>Field reference and thresholds</h2><p>Expanding a certificate’s details shows: status, valid-from date, issuer, every domain it covers, serial number, SHA-256 fingerprint, and when it was last detected — deliberately excluding private key material.</p><h3>Status thresholds</h3><p>Set from Administration → Security & Health:</p><ul><li><strong>Renewing-soon warning:</strong> days remaining before a certificate is flagged as renewing soon (8–120, default 30).</li><li><strong>Critical warning:</strong> days remaining before it’s flagged critical; must stay lower than the renewing-soon threshold (default 7).</li><li><strong>Stale health data:</strong> minutes before a displayed check is considered old and worth re-running (2–1440, default 10).</li></ul><p>A status of <strong>mismatch</strong> means a custom certificate was uploaded for that route but its coverage doesn’t include every configured domain — upload a replacement covering all of them.</p></article><article data-doc="access lists overview protect lan authentication networks denied login groups assignment group enabled disabled toggle"><p class="eyebrow">Access Lists</p><h2>Overview: protect a route</h2><p>An Access List restricts who can reach a Hosted Site, Proxy Host, or Redirect Host, by network address, by login, or both. Assign a saved list from that route’s Advanced options.</p><h3>Fields</h3><ul><li><strong>Name.</strong></li><li><strong>Allowed networks:</strong> one IP, CIDR range, or the literal <code>private_ranges</code> per line. When set, every network <em>not</em> listed is denied — this is an allow-list, not a suggestion.</li><li><strong>Denied networks</strong> <span class="optional">Optional</span>: evaluated <em>before</em> allowed networks and logins, so a denied entry always wins even if it would otherwise be allowed.</li><li><strong>Logins:</strong> add one or more username/password pairs directly on the Access List (passwords need at least 8 characters; leaving a password blank while editing an existing login keeps it unchanged).</li><li><strong>Groups:</strong> alternatively, let members of a Group (Administration → Users & Groups) authenticate with their own Site Gateway username and password instead of a separate Access-List-only login. A disabled Group (Administration → Users & Groups) stops granting access immediately, even though it still appears assigned here — see Users & Groups for details.</li></ul><p>At least one rule — a network rule, a denied-network rule, or a login — is required before the list can be saved.</p><h3>Assigning and inspecting</h3><p>Use a route’s Advanced options to assign an Access List to it, or use the Access List’s own <strong>View assigned hosts</strong> menu action to see everywhere it’s currently in use. An Access List can’t be deleted while anything still references it — unassign it from every host first.</p></article><article data-doc="users groups overview administrator standard viewer role reset password archive audit group enable disable toggle access"><p class="eyebrow">Users & Groups</p><h2>Overview: control who can change the gateway</h2><p>Every account has a role:</p><ul><li><strong>Administrator:</strong> full access, including Users, Groups, Settings, and Backups.</li><li><strong>Standard User:</strong> can manage Hosted Sites, Proxy Hosts, Redirect Hosts, Streaming Hosts, and Access Lists, but not Users, Groups, Settings, or Backups.</li><li><strong>Viewer:</strong> read-only — can inspect everything but change nothing.</li></ul><h3>Creating a user</h3><ul><li><strong>Display name.</strong></li><li><strong>Username:</strong> 3–64 characters, letters/numbers/<code>._-</code>, must be unique.</li><li><strong>Role.</strong></li><li><strong>Temporary password:</strong> at least 8 characters — share it securely and have the person change it after their first sign-in.</li></ul><h3>Managing an existing user</h3><p>An administrator can change a user’s role from the card’s role dropdown, reset their password, archive them (a reversible soft-disable distinct from deleting), or delete them outright. Delete and Change icon live under the card’s “•••” menu, matching Hosted Sites and Proxy Hosts; role, password reset, and archive stay as visible buttons. You cannot disable, archive, delete, or change the role of your own account — another administrator has to do that — and Site Gateway always keeps at least one active Administrator, refusing any action that would leave zero. Resetting a user’s password here does not disable their two-factor authentication if they have it enabled — see My Account for how 2FA works. There is currently no administrator-side way to turn off another user’s 2FA; only that user can, from their own My Account page.</p><h3>Groups</h3><p>A Group is simply a named set of users. Its only purpose is authentication: assign a Group to an Access List so its members can log in with their own Site Gateway credentials instead of a separate Access-List-only login.</p><p>Every Group has its own enable/disable toggle, separate from deleting it. <strong>Disabling a Group immediately stops it from granting access through any Access List it’s assigned to</strong> — the assignment itself is untouched and still shows as assigned, but its members can no longer authenticate through it until the Group is re-enabled. This is a common surprise: disabling a Group is not the same as archiving it for later cleanup, it has an immediate access-control effect. If the Group is currently assigned to at least one enabled Access List, disabling it now asks for confirmation first, naming exactly which Access List(s) will stop authenticating that Group’s members — the same pattern already used when disabling an Access List that protects active hosts.</p><h3>Audit history</h3><p>Administration includes an Audit log — a searchable, filterable, immutable record of who did what, filterable by outcome (success/failed) and free-text search across the user, action, and target. Audit entries can’t be edited or deleted.</p></article><article data-doc="my account profile password change two-factor authentication mfa totp authenticator recovery codes self-service"><p class="eyebrow">My Account</p><h2>Managing your own profile, password, and two-factor authentication</h2><p>My Account is available to every role — Administrator, Standard User, and Viewer alike — and only ever affects your own account. It sits in the sidebar next to Administration and Documentation.</p><h3>Profile</h3><p>Shows your display name, username, and role. These are read-only here; an administrator changes them from Administration → Users.</p><h3>Changing your password</h3><p>Enter your current password once, then your new password twice. This works for every role — previously, only an administrator could change a user’s password (Administration → Users → Reset password), which meant every routine password change had to go through an admin. My Account closes that gap for everyone’s own account.</p><h3>Two-factor authentication (2FA)</h3><p>Optional and off by default for every account. When enabled, signing in requires your password plus a 6-digit code from an authenticator app (or a recovery code), using the standard TOTP algorithm (RFC 6238) — any authenticator app works, not a Site Gateway-specific one.</p><h3>Enabling 2FA</h3><ol><li>Choose Enable two-factor authentication.</li><li>Scan the QR code with your authenticator app, or enter the shown key manually.</li><li>Enter the 6-digit code it generates to confirm setup.</li><li>Save the 10 recovery codes shown immediately afterward — each works once, and this is the only time they’re shown in full.</li></ol><h3>Signing in with 2FA enabled</h3><p>After your username and password are accepted, you’re prompted for a code. Enter the current 6-digit code from your authenticator app, or one of your unused recovery codes if you don’t have the app available. A wrong code shows an inline error without sending you back to the username/password screen.</p><h3>Disabling 2FA, or regenerating recovery codes</h3><p>Both require re-entering your current password as confirmation. Regenerating recovery codes immediately invalidates the previous set.</p><p>An administrator resetting your password does not disable your 2FA — you’ll still need your authenticator app or a recovery code at your next sign-in. Only you can turn off your own 2FA, from My Account.</p></article><article data-doc="gateway defaults default site welcome themed 404 abort redirect custom html unknown hostnames"><p class="eyebrow">Administration</p><h2>Gateway Defaults: handling unknown addresses</h2><p>Controls what happens when a visitor reaches Site Gateway on HTTP using a hostname that isn’t configured. (Unknown HTTPS hostnames are always rejected outright, regardless of this setting, since serving anything else would need a certificate Site Gateway doesn’t have — and issuing a misleading one would be worse.)</p><h3>Response modes</h3><ul><li><strong>Themed route-not-found page (404)</strong> — the safest public default.</li><li><strong>Gateway ready page (200)</strong> — useful while confirming HTTP routing during initial setup.</li><li><strong>No response — close connection.</strong></li><li><strong>Redirect elsewhere:</strong> set a destination URL, redirect code, and whether to preserve the requested path and query.</li><li><strong>Custom HTML:</strong> administrator-authored markup, served exactly as written with no sanitization — up to 250,000 characters.</li></ul><p>The themed page’s heading and explanation text are also editable here, independent of which mode is active.</p></article><article data-doc="backup restore schedule retention encryption complete configuration import restore checklist"><p class="eyebrow">Administration</p><h2>Backup & Restore</h2><p>A <strong>Configuration only</strong> backup contains a consistent SQLite snapshot of every route, user, group, Access List, and setting, plus a portable JSON export of the same data. A <strong>Complete</strong> backup adds uploaded Hosted Site files, icons, the default-site page, and certificates (both managed and custom).</p><h3>Scheduled backups</h3><ul><li><strong>Enable scheduled backups.</strong></li><li><strong>Type:</strong> Configuration only or Complete.</li><li><strong>Schedule:</strong> Daily, Weekly, or Monthly, plus the hour of day to run.</li><li><strong>Keep:</strong> how many scheduled backups to retain (1–100, default 7) — older ones beyond this count are deleted automatically after each run.</li><li><strong>Include logs.</strong></li><li><strong>Encrypt scheduled backups:</strong> uses the container’s <code>BACKUP_PASSWORD</code> environment value — enable this only after that value is actually configured, or scheduled runs will fail.</li></ul><h3>Manual backup, encryption password</h3><p>The optional backup password field on this page is used only for manually created backups and for restoring an encrypted archive — it is never stored by Site Gateway.</p><h3>Restore checklist</h3><ol><li>Download or import the <code>.sgbackup</code> archive (importing just stages the file — restoring is a separate, explicit action).</li><li>Supply its password if it’s encrypted.</li><li>Choose Restore and allow validation to finish.</li><li>Confirm hosts, certificates, and upstream health afterward.</li></ol><p>Site Gateway verifies every file’s checksum and automatically creates a safety backup of the current state before restoring anything. If the restored configuration turns out to be invalid, it automatically rolls back to that safety backup rather than leaving the gateway in a broken state.</p></article><article data-doc="security health updates thresholds support report logs retention pruning warning critical stale"><p class="eyebrow">Administration</p><h2>Security, Health & Updates</h2><p>Site Gateway validates every generated Caddy configuration before reload and keeps the previous working configuration active if validation fails. Container updates are installed by pulling a new pinned image — create a backup first.</p><h3>Certificate health thresholds</h3><p>See Certificates → Field reference for what Renewing-soon warning, Critical warning, and Stale health data actually control.</p><h3>Support report</h3><p>Downloadable by administrators only. Includes version info, Caddy/Node versions, storage integrity-check result, gateway health, a redacted route summary, full certificate and domain readiness output, and recent activity. It deliberately excludes passwords, private keys, session secrets, cookies, and certificate contents — safe to share when asking for help.</p><h3>Logs & Retention</h3><p>Set how many days of Access, Activity, Audit, Certificate, and Security records to keep (7–3650 days each) and whether automatic pruning is enabled. <strong>Prune Now</strong> shows exactly how many records in each category are eligible before you confirm, and <strong>Download Logs</strong> exports them.</p></article><article data-doc="danger zone restore defaults factory reset credentials confirmation countdown"><p class="eyebrow">Administration</p><h2>Danger Zone</h2><p>Two separate, deliberately distinct destructive actions — kept apart so a routine preference correction is never confused with a full rebuild. Both require re-entering your own administrator username and password, typing an exact confirmation phrase, and then confirming a second themed dialog by typing <strong>YES</strong>.</p><h3>Restore Defaults</h3><p>Confirmation phrase: <code>RESTORE DEFAULT</code>. Resets Gateway Defaults, backup schedule settings, and certificate-health thresholds back to their starting values. It does <strong>not</strong> remove any hosts, uploaded files, users, groups, Access Lists, certificates, logs, or backups.</p><h3>Factory Reset</h3><p>Confirmation phrase: <code>FACTORY RESET</code>. Deletes everything under <code>/data</code> — every host of every kind, uploaded files, certificates (managed and custom), logs, backups, icons, users, and settings. Docker-mounted files outside <code>/data</code> are untouched. The container returns to the initial setup screen without needing a manual restart. Because backups themselves live under <code>/data/backups</code>, they’re deleted too — recovery is only possible from a backup taken beforehand and stored elsewhere (downloaded, or on separately mounted storage).</p><h3>When to use a backup instead</h3><p>If you want to undo a recent change while keeping the rest of the installation intact, restore a backup — Factory Reset is not a rollback tool.</p></article><article data-doc="logs access logs gateway events requests response status domain filters severity category redacted"><p class="eyebrow">Logs</p><h2>Access Logs and Gateway Events</h2><h3>Access Logs</h3><p>Every request Caddy handles, with domain, path, response status, latency, and upstream outcome. Filter by host or by status-code range (2xx/3xx/4xx/5xx). Sensitive query-string values — tokens, secrets, passwords, session identifiers, API keys, credentials — are redacted before they’re ever stored, regardless of filter settings.</p><h3>Gateway Events</h3><p>Configuration and operational changes, filterable by severity (Normal/Warnings/Errors) and by category (certificate, health, authentication, backup, configuration, or system).</p><h3>Example</h3><p>Filter Access Logs for a 502, then compare the target address against a direct LAN request to the same upstream to isolate whether the problem is the gateway or the application itself.</p></article><article data-doc="performance throughput trend sparkline per-route domain range last hour last 24h errors average response time"><p class="eyebrow">Performance</p><h2>Throughput by domain, over time</h2><p>Built entirely from the same request data already collected for Access Logs — no new logging or extra overhead, just a different view of it.</p><h3>Filters</h3><p><strong>Domain</strong> narrows the Trend chart to one host (default: all domains combined). <strong>Range</strong> sets the Trend window — Last hour, 3, 6 (default), 12, or 24 hours, or 3 or 7 days.</p><h3>Trend</h3><p>Request volume over the selected range, bucketed into 15-minute (up to 24h) or hourly (3–7 days) points.</p><h3>Per-route table</h3><p>One row per domain that has received traffic, sorted by 24-hour volume:</p><ul><li><strong>Last hour / Last 24h:</strong> request count for that window; a red count after the divider is how many of those responses were 4xx or 5xx. This includes routine noise (expired-token 401s, bots probing by raw IP, scanners hitting invalid hosts) as well as genuine failures — it isn’t a health verdict by itself.</li><li><strong>Avg. response:</strong> the mean response time across every request to that domain in the last 24 hours — a fixed 24h window regardless of the Range filter above, and a straight average, so a handful of slow outliers can pull it up more than most visitors actually experience.</li></ul></article><article data-doc="common interface controls menus three dots edit disable delete enable toggle role gating viewer standard administrator"><p class="eyebrow">Common Interface Controls</p><h2>Menus, toggles, and role gating</h2><p>The same card language is used throughout Hosted Sites, Proxy Hosts, Redirect Hosts, Streaming Hosts, Access Lists, Groups, and Users, so learning one area transfers directly to the next.</p><h3>The three-dot menu</h3><p>Contains actions that change or inspect a card: <strong>Edit</strong> opens the full form, kind-specific extras appear where relevant (Replace files on Hosted Sites, View assigned hosts on Access Lists), and <strong>Delete</strong> removes the record after a confirmation.</p><h3>The toggle switch</h3><p>A slide switch, separate from the menu, turns a route or account on or off without deleting its saved configuration — useful during maintenance or testing when you expect to reuse the exact same settings shortly after.</p><h3>Who can use each control</h3><p>Administrators can manage everything. Standard Users can manage Hosted Sites, Proxy Hosts, Redirect Hosts, Streaming Hosts, and Access Lists, but not Users, Groups, Settings, or Backups. Viewers can inspect information but cannot create, edit, disable, assign, or delete anything — their menus and toggles are hidden entirely. This is enforced on the server independently of what the interface shows, so hiding a button is a convenience, not the actual security boundary.</p><h3>Update notifications</h3><p>While signed in, Site Gateway checks every 60 seconds whether a newer version has been deployed. If so, a small banner appears with Refresh and Dismiss — Refresh reloads the page to pick up the new version; Dismiss hides the banner, but it reappears on the next check if you’re still on the old version. This only matters for a tab left open across a deploy; closing and reopening the tab, or signing in fresh, always loads the current version automatically.</p></article><article data-doc="icons change icon picker search catalog custom upload url fallback initials"><p class="eyebrow">Icons</p><h2>Changing a card’s icon</h2><p>Every Hosted Site, Proxy Host, Redirect Host, Streaming Host, Access List, Group, and User can have its own icon. Open the picker from a card’s icon tile or its “Change icon” menu action.</p><ul><li><strong>Search by service name:</strong> type at least 2 characters (e.g. <code>Jellyfin</code>, <code>Plex</code>) to search a large built-in icon catalog and pick a match.</li><li><strong>Upload a custom image:</strong> PNG, JPEG, WebP, GIF, or SVG, up to 2 MB, stored locally under <code>/data/icons</code>. Uploaded SVGs are checked for embedded scripts or external references before being accepted.</li><li><strong>Image URL:</strong> paste a direct <code>https://</code> image link instead of uploading a file.</li><li><strong>Use two-letter fallback:</strong> clears any icon and reverts to initials derived from the name.</li></ul><p>If a custom icon URL ever stops loading, the card automatically falls back to showing initials instead of a broken image.</p></article><article data-doc="troubleshooting dns ports certificate caddy nginx conflict https not detected"><p class="eyebrow">Troubleshooting</p><h2>When HTTPS is not detected</h2><p>Confirm public DNS actually points to this server, that router/firewall forwarding reaches ports 80 and 443, and that NGINX Proxy Manager or another reverse proxy isn’t still holding those ports. Then check Certificates and Logs → Gateway Events for the specific rejection reason. Site Gateway cannot request a public certificate while another gateway is receiving the ACME challenge on its behalf.</p><h3>A route rejected its configuration</h3><p>Check the error shown on save — it names the specific problem (duplicate address, invalid upstream, malformed custom configuration, certificate/TLS issue) rather than a generic failure, and the previous working configuration stays active while you fix it.</p><h3>A Streaming Host isn’t reachable from outside</h3><p>This is almost always the port not being published on the container yet — see Streaming Hosts → Overview for the docker-compose/Unraid port mapping requirement.</p><h3>A user is locked out after enabling two-factor authentication</h3><p>If they still have an unused recovery code, they can sign in with it in place of the 6-digit code. If not, there is currently no administrator-side way to disable another user’s 2FA — only the affected user can turn it off, from My Account, which itself requires signing in first. Keep this in mind before encouraging 2FA adoption broadly.</p></article></div></div><p id="doc-empty" class="quiet-state hidden">No guide matched that search.</p></section>
|
||
<div id="management-view" class="hidden">
|
||
<section id="empty" class="empty hidden">
|
||
<div class="empty-icon">↗</div><h2>Publish your first site</h2>
|
||
<p>Drop in a ZIP containing an <code>index.html</code> and choose a port. That’s it.</p>
|
||
<button class="button primary create-trigger">Create a site</button>
|
||
</section>
|
||
<section id="site-grid" class="site-grid" aria-live="polite"></section>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<dialog id="create-dialog">
|
||
<form id="create-form" class="dialog-card">
|
||
<div class="dialog-heading"><div><p class="eyebrow">New destination</p><h2>Create a site</h2></div><button type="button" class="icon-button close-dialog" aria-label="Close">×</button></div>
|
||
<label>Site name<input name="name" placeholder="Portfolio" maxlength="80" required></label>
|
||
<label>Port<input name="port" type="number" required><small id="port-help"></small></label>
|
||
<label>Domain <span class="optional">Optional</span><input name="domain" placeholder="www.example.com"><small>Leave blank for port-only LAN access.</small></label><label>Additional domains <span class="optional">Optional</span><textarea name="domains" placeholder="www.example.com example.net"></textarea><small>One alias per line. All domains use the same hosted files and TLS settings.</small></label>
|
||
<label>TLS<select name="tls"><option value="automatic">Automatic public HTTPS</option><option value="internal">Internal HTTPS for trusted local devices</option><option value="http">HTTP only</option></select></label>
|
||
<label class="check-control"><input name="hsts" type="checkbox" value="true"><span>Enable HSTS after HTTPS is verified</span></label>
|
||
<details><summary>Advanced options</summary><div class="details-body"><label>Access List<select name="accessListId"><option value="">Public — no Access List</option></select><small>Reusable network or login protection.</small></label><label>Compression<select name="compression"><option value="automatic">Automatic zstd + gzip</option><option value="gzip">gzip only</option><option value="off">Off</option></select></label><label>Request headers<textarea name="requestHeadersText" placeholder="X-Robots-Tag: noindex"></textarea><small>One Name: value pair per line.</small></label><label>Response headers<textarea name="responseHeadersText" placeholder="X-Frame-Options: SAMEORIGIN"></textarea><small>One Name: value pair per line.</small></label><label class="check-control"><input name="hstsSubdomains" type="checkbox"><span>Apply HSTS to subdomains</span></label><label>Custom Caddy configuration<textarea name="customConfig" class="code-input" placeholder="# Expert use only"></textarea><small>Validated before Caddy reload.</small></label></div></details>
|
||
<label class="dropzone">Website files<input name="files" type="file" accept=".zip,.html,text/html,application/zip" required><span class="upload-icon">⇧</span><strong>Choose a ZIP or index.html</strong><small>ZIP files must contain index.html · Up to 250 MB</small></label>
|
||
<p id="create-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Create & publish</button></div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="proxy-dialog">
|
||
<form id="proxy-form" class="dialog-card">
|
||
<div class="dialog-heading"><div><p class="eyebrow">New route</p><h2>Create a proxy host</h2></div><button type="button" class="icon-button close-dialog" aria-label="Close">×</button></div>
|
||
<label>Name<input name="name" placeholder="Home Assistant" maxlength="80" required></label>
|
||
<label>Primary domain<input name="domain" placeholder="home.example.com" required></label>
|
||
<label>Additional domains <span class="optional">Optional</span><textarea name="domainsText" placeholder="www.home.example.com home.example.net"></textarea><small>One alias per line. All domains use this proxy host’s upstream and TLS settings.</small></label>
|
||
<label>Forward to<input name="target" type="url" placeholder="http://192.168.1.20:8123" required><small>Use the container name, LAN address, or application URL.</small></label>
|
||
<label>Upstream pool <span class="optional">Optional</span><textarea name="upstreamsText" placeholder="http://192.168.1.20:53 http://192.168.1.21:53"></textarea><small>One HTTP/HTTPS target per line. Caddy distributes requests across healthy targets.</small></label>
|
||
<label>TLS<select name="tls"><option value="automatic">Automatic public HTTPS</option><option value="internal">Internal HTTPS for trusted local devices</option><option value="custom">Custom uploaded certificate</option><option value="http">HTTP only</option></select></label>
|
||
<label class="check-control"><input name="hsts" type="checkbox"><span>Enable HSTS after HTTPS is verified</span></label>
|
||
<div id="custom-certificate-fields"><label>Certificate PEM<input name="certificateFile" type="file" accept=".pem,.crt,application/x-pem-file"></label><label>Private key PEM<input name="privateKeyFile" type="file" accept=".pem,.key,application/x-pem-file"></label><small>Both files are required when installing or replacing a custom certificate.</small></div>
|
||
<details><summary>Advanced options</summary><div class="details-body"><label>Access List<select name="accessListId"><option value="">Public — no Access List</option></select><small>Reusable network or login protection.</small></label><label>Health-check path<input name="healthPath" value="/"></label><label>Health-check method<select name="healthMethod"><option value="GET">GET — retrieve a response</option><option value="HEAD">HEAD — headers only</option></select></label><label>Expected status<input name="healthExpected" value="200-499"><small>Examples: 200, 200,204, or 200-399.</small></label><label>Timeout in seconds<input name="healthTimeoutSeconds" type="number" min="1" max="60" value="4"></label><label class="check-control"><input name="healthEnabled" type="checkbox" checked><span>Monitor this upstream</span></label><label>Compression<select name="compression"><option value="automatic">Automatic zstd + gzip</option><option value="gzip">gzip only</option><option value="off">Off</option></select></label><label class="check-control"><input name="blockCommonExploits" type="checkbox"><span>Block common exploits</span><small>Rejects requests matching common exploit-probe patterns (path traversal, WordPress/PHP scanner paths, SQL injection attempts) with a 403 before they reach the upstream.</small></label><h3>Custom locations <span class="optional">Optional</span></h3><label>Locations<textarea name="customLocationsText" placeholder="/api/* | http://192.168.1.20:3001 | strip /media/* | http://192.168.1.21:8080 | preserve"></textarea><small>One per line: path | destination | strip or preserve.</small></label><h3>Headers and upstream TLS</h3><label>Request headers<textarea name="requestHeadersText" placeholder="X-Forwarded-Host: {host}"></textarea><small>One Name: value pair per line.</small></label><label>Response headers<textarea name="responseHeadersText" placeholder="X-Frame-Options: SAMEORIGIN"></textarea></label><label>Upstream TLS server name<input name="upstreamTlsServerName" placeholder="service.internal"><small>Optional SNI name expected by the upstream certificate.</small></label><label class="check-control"><input name="upstreamTlsInsecure" type="checkbox"><span>Ignore upstream TLS certificate errors</span><small>Use only for a trusted internal HTTPS service with a self-signed or hostname-mismatched certificate.</small></label><label class="check-control"><input name="hstsSubdomains" type="checkbox"><span>Apply HSTS to subdomains</span></label><label>Custom Caddy configuration<textarea name="customConfig" class="code-input" placeholder="# Expert use only"></textarea><small>Validated before Caddy reload. NGINX syntax is not supported.</small></label></div></details>
|
||
<p id="proxy-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Create & publish</button></div>
|
||
</form>
|
||
</dialog>
|
||
<dialog id="stream-dialog"><form id="stream-form" class="dialog-card"><div class="dialog-heading"><div><p class="eyebrow">New route</p><h2 id="stream-title">Create a streaming host</h2></div><button type="button" class="icon-button close-dialog">×</button></div><label>Name<input name="name" required placeholder="Minecraft server"></label><label>Incoming port<input name="port" type="number" min="1" max="65535" required placeholder="25565"><small>Must already be published on the container. See Documentation → Streaming hosts.</small></label><label>Forward to<input name="target" required placeholder="192.168.1.20:25565"><small>A host and port, such as 192.168.1.20:25565. No http:// — this is raw TCP/UDP, not a web address.</small></label><div class="form-grid"><label class="check-control"><input name="tcp" type="checkbox" checked><span>TCP</span></label><label class="check-control"><input name="udp" type="checkbox"><span>UDP</span></label></div><label class="check-control"><input name="healthEnabled" type="checkbox" checked><span>Monitor this target</span></label><p id="stream-error" class="error"></p><div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Create streaming host</button></div></form></dialog>
|
||
<dialog id="redirect-dialog"><form id="redirect-form" class="dialog-card"><div class="dialog-heading"><div><p class="eyebrow">New route</p><h2>Create a redirect host</h2></div><button type="button" class="icon-button close-dialog">×</button></div><label>Name<input name="name" required placeholder="Old website"></label><label>Source domain<input name="domain" required placeholder="old.example.com"></label><label>Destination<input name="target" type="url" required placeholder="https://new.example.com"></label><label>Redirect type<select name="code"><option value="302">302 · Temporary</option><option value="301">301 · Permanent</option><option value="307">307 · Temporary, preserve method</option><option value="308">308 · Permanent, preserve method</option></select></label><label>TLS<select name="tls"><option value="automatic">Automatic HTTPS</option><option value="http">HTTP only</option><option value="internal">Internal HTTPS</option></select></label><label class="check-control"><input name="preservePath" type="checkbox" checked><span>Preserve path and query</span></label><p id="redirect-error" class="error"></p><div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Create redirect</button></div></form></dialog>
|
||
<dialog id="access-dialog"><form id="access-form" class="dialog-card"><div class="dialog-heading"><div><p class="eyebrow">Reusable protection</p><h2>Create an Access List</h2></div><button type="button" class="icon-button close-dialog">×</button></div><label>Name<input name="name" required placeholder="LAN and family"></label><label>Allowed networks<textarea name="networks" placeholder="private_ranges 192.168.50.0/24"></textarea><small>When supplied, every other network is denied. Use one IP, CIDR range, or private_ranges per line.</small></label><label>Denied networks <span class="optional">Optional</span><textarea name="deniedNetworks" placeholder="203.0.113.0/24"></textarea><small>These rules are evaluated before allowed networks and logins.</small></label><div id="access-credential-editor" class="credential-editor"></div><div id="access-assignment-summary" class="callout hidden"></div><p id="access-error" class="error"></p><div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Save Access List</button></div></form></dialog>
|
||
|
||
<dialog id="settings-dialog">
|
||
<form id="settings-form" class="dialog-card">
|
||
<div class="dialog-heading"><div><p class="eyebrow">Gateway settings</p><h2 id="settings-title">Edit route</h2></div><button type="button" class="icon-button close-dialog" aria-label="Close">×</button></div>
|
||
<label id="settings-name-wrap">Name<input name="name" maxlength="80"></label>
|
||
<label>Primary domain<input name="domain" placeholder="www.example.com"></label>
|
||
<label>Additional domains <span class="optional">Optional</span><textarea name="domainsText" placeholder="www.example.com example.net"></textarea><small>One alias per line. All domains use the same route and TLS settings.</small></label>
|
||
<label id="settings-target-wrap">Forward to<input name="target" type="url" placeholder="http://192.168.1.20:3000"></label>
|
||
<label>TLS<select name="tls"><option value="automatic">Automatic public HTTPS</option><option value="internal">Internal HTTPS for trusted local devices</option><option value="custom">Custom uploaded certificate</option><option value="http">HTTP only</option></select></label>
|
||
<label class="check-control"><input name="hsts" type="checkbox"><span>Enable HSTS after HTTPS is verified</span></label>
|
||
<div class="custom-certificate-fields"><label>Certificate PEM<input name="certificateFile" type="file" accept=".pem,.crt,application/x-pem-file"></label><label>Private key PEM<input name="privateKeyFile" type="file" accept=".pem,.key,application/x-pem-file"></label><small>Both files are required when installing or replacing a custom certificate.</small></div>
|
||
<details id="settings-hosted-advanced"><summary>Advanced options</summary><div class="details-body"><label>Access List<select name="accessListId"><option value="">Public — no Access List</option></select><small>Reusable network or login protection.</small></label><label>Compression<select name="compression"><option value="automatic">Automatic zstd + gzip</option><option value="gzip">gzip only</option><option value="off">Off</option></select></label><label>Request headers<textarea name="requestHeadersText" placeholder="Name: value"></textarea></label><label>Response headers<textarea name="responseHeadersText" placeholder="Name: value"></textarea></label><label class="check-control"><input name="hstsSubdomains" type="checkbox"><span>Apply HSTS to subdomains</span></label><label>Custom Caddy configuration<textarea name="customConfig" class="code-input"></textarea></label></div></details>
|
||
<details id="settings-advanced"><summary>Advanced options</summary><div class="details-body"><label>Access List<select name="accessListId"><option value="">Public — no Access List</option></select></label><label>Health-check path<input name="healthPath" value="/"></label><label>Health-check method<select name="healthMethod"><option value="GET">GET — retrieve a response</option><option value="HEAD">HEAD — headers only</option></select></label><label>Expected status<input name="healthExpected" value="200-499"></label><label>Timeout in seconds<input name="healthTimeoutSeconds" type="number" min="1" max="60" value="4"></label><label class="check-control"><input name="healthEnabled" type="checkbox" checked><span>Monitor this upstream</span></label><label>Compression<select name="compression"><option value="automatic">Automatic zstd + gzip</option><option value="gzip">gzip only</option><option value="off">Off</option></select></label><label class="check-control"><input name="blockCommonExploits" type="checkbox"><span>Block common exploits</span><small>Rejects requests matching common exploit-probe patterns (path traversal, WordPress/PHP scanner paths, SQL injection attempts) with a 403 before they reach the upstream.</small></label><label>Custom locations<textarea name="customLocationsText" placeholder="/api/* | http://192.168.1.20:3001 | strip"></textarea><small>One per line: path | destination | strip or preserve.</small></label><label>Request headers<textarea name="requestHeadersText" placeholder="Name: value"></textarea></label><label>Response headers<textarea name="responseHeadersText" placeholder="Name: value"></textarea></label><label>Upstream TLS server name<input name="upstreamTlsServerName"><small>Optional SNI name expected by the upstream certificate.</small></label><label class="check-control"><input name="upstreamTlsInsecure" type="checkbox"><span>Ignore upstream TLS certificate errors</span><small>Use only for a trusted internal HTTPS service with a self-signed or hostname-mismatched certificate.</small></label><label class="check-control"><input name="hstsSubdomains" type="checkbox"><span>Apply HSTS to subdomains</span></label><label>Custom Caddy configuration<textarea name="customConfig" class="code-input"></textarea></label></div></details>
|
||
<p id="settings-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Save & apply</button></div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="confirm-dialog">
|
||
<form method="dialog" class="dialog-card compact"><h2 id="confirm-title">Delete this site?</h2><p id="confirm-copy" class="muted">Its uploaded files will be permanently removed.</p><div class="dialog-actions"><button value="cancel" class="button secondary">Cancel</button><button value="confirm" class="button danger">Delete</button></div></form>
|
||
</dialog>
|
||
<dialog id="icon-dialog" class="icon-dialog">
|
||
<form method="dialog" class="dialog-card icon-picker">
|
||
<div class="dialog-heading"><div><p class="eyebrow">Appearance</p><h2>Choose an icon</h2></div><button value="cancel" class="icon-button" aria-label="Close">×</button></div>
|
||
<p class="muted">Search Dashboard Icons. Selected icons are validated and stored locally in <code>/data/icons</code>.</p>
|
||
<label>Search icons<input id="icon-search" type="search" placeholder="Jellyfin" autocomplete="off"></label>
|
||
<label>Upload a custom icon<input id="icon-upload" type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml"><small>PNG, JPEG, WebP, GIF, or SVG · up to 2 MB. Stored locally in <code>/data/icons</code>.</small></label>
|
||
<label>Or use an image URL <span class="optional">Optional</span><input id="icon-url" type="url" placeholder="https://example.com/icon.png"><small>Use a trusted HTTPS URL. The two-letter fallback remains available.</small></label>
|
||
<div id="icon-results" class="icon-results" aria-live="polite"><p class="quiet-state">Enter at least two characters to search.</p></div>
|
||
<p id="icon-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button id="save-icon-url" type="button" class="button secondary">Save URL</button><button id="reset-icon" value="none" class="button secondary">Use two-letter fallback</button><button value="cancel" class="button secondary">Cancel</button></div>
|
||
</form>
|
||
</dialog>
|
||
<dialog id="user-dialog">
|
||
<form id="user-form" class="dialog-card">
|
||
<div class="dialog-heading"><div><p class="eyebrow">Administration</p><h2>Create a user</h2></div><button type="button" class="icon-button close-dialog" aria-label="Close">×</button></div>
|
||
<label>Display name<input name="displayName" placeholder="Marvin Wade" maxlength="80" required></label>
|
||
<label>Username<input name="username" placeholder="marvin" minlength="3" maxlength="64" pattern="[A-Za-z0-9][A-Za-z0-9._-]{2,63}" autocomplete="off" required></label>
|
||
<label>Role<select name="role"><option value="standard">Standard User</option><option value="viewer">Viewer</option><option value="administrator">Administrator</option></select><small>Viewer accounts can inspect gateway data. Standard Users and Administrators retain their assigned management capabilities.</small></label>
|
||
<label>Temporary password<input name="password" type="password" minlength="8" autocomplete="new-password" required><small>At least 8 characters. Share it securely.</small></label>
|
||
<p id="user-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Create user</button></div>
|
||
</form>
|
||
</dialog>
|
||
<dialog id="password-dialog">
|
||
<form id="password-form" class="dialog-card">
|
||
<div class="dialog-heading"><div><p class="eyebrow">Credentials</p><h2 id="password-title">Reset password</h2></div><button type="button" class="icon-button close-dialog" aria-label="Close">×</button></div>
|
||
<label>New password<input name="password" type="password" minlength="8" autocomplete="new-password" required><small>At least 8 characters.</small></label>
|
||
<p id="password-error" class="error" role="alert"></p>
|
||
<div class="dialog-actions"><button type="button" class="button secondary close-dialog">Cancel</button><button class="button primary">Save password</button></div>
|
||
</form>
|
||
</dialog>
|
||
<input id="replace-files" type="file" accept=".zip,.html,text/html,application/zip" hidden>
|
||
<div id="toast" class="toast" role="status"></div>
|
||
<div id="update-banner" class="update-banner hidden" role="status"><span>A new version of Site Gateway is available.</span><div class="update-banner-actions"><button id="update-banner-refresh" class="button primary">Refresh</button><button id="update-banner-dismiss" class="text-button">Dismiss</button></div></div>
|
||
<script src="/app.js?v=0.11.84" defer></script><script src="/features.js?v=0.11.86" defer></script>
|
||
</body>
|
||
</html>
|