# Frontend Auth & Action Pattern — Ops Portal Created: 2026-07-09 Applies to: `/opt/ops-portal/static/` pages (index.html, servers.html, backups.html, services.html) ## Architecture Each page is a self-contained HTML file served by FastAPI with: 1. Inline login overlay (no separate login page) 2. Inline nav (not fetched via nav.html partial — auth-aware logout button is tightly coupled) 3. Single JS module: `/js/app.js` (exposed as `window.Ops`) ## JS Module (`/js/app.js`) ### Auth ```javascript // Login: stores JWT in localStorage['ops_token'] await Ops.login('username', 'password'); // Check auth state if (Ops.isAuthenticated()) { ... } // Logout — clears storage + page reload Ops.logout(); ``` ### API Client ```javascript // All calls auto-attach Authorization header from stored token var data = await Ops.apiFetch('/api/status'); var servers = await Ops.getServers(); var audit = await Ops.getAuditLog(10); // Service actions (all POST with JWT) await Ops.restartService('caddy'); await Ops.stopService('hermes'); await Ops.startService('ops-portal'); // Backup trigger await Ops.triggerBackup('hermes-vps-backups'); // Server reboot (by Hetzner server ID) await Ops.rebootServer(51140464); ``` **401 handling:** `apiFetch()` automatically clears localStorage and calls `window.location.reload()` on 401 responses, showing the login overlay again. ### UI Components ```javascript // Toast notification (auto-dismiss 5s) Ops.toast('Server reboot initiated', 'success'); // 'success' | 'error' | 'info' // Confirmation dialog (returns Promise) var ok = await Ops.confirmDialog( 'Reboot Server', 'Are you sure you want to reboot wphost02?', 'Reboot', 'Cancel' ); // Button loading state Ops.spinning(document.getElementById('refreshBtn'), true); Ops.spinning(document.getElementById('refreshBtn'), false); // Nav initialization (hamburger toggle + active link + logout) Ops.initNav(); // Formatting utilities Ops.fmtTime('2026-07-09T12:22:28-04:00'); // "Jul 9, 12:22 PM" Ops.fmtAge('...'); // "3h 12m ago" Ops.badgeHtml('ok'); // '✓ ok' Ops.dotColor('running'); // 'dot-green' Ops.escapeHtml(' ``` ## Files created Jul 9, 2026 | File | Size | Sections | |------|------|----------| | `/opt/ops-portal/static/index.html` | 19.3 KB | Health grid, Quick actions (restart/trigger), Servers summary, Cron jobs, Audit log | | `/opt/ops-portal/static/servers.html` | 15.0 KB | Quick specs (vCPUs/RAM), Sortable server table with click-to-expand details, Reboot button per server | | `/opt/ops-portal/static/backups.html` | 15.1 KB | Summary bar, Bucket grid with Trigger button, Schedule table | | `/opt/ops-portal/static/services.html` | 14.7 KB | Service control grid (Start/Stop/Restart per service), API health grid, Service audit log | | `/opt/ops-portal/static/css/ops.css` | 27.0 KB | Full theme with auth-overlay, confirm-dialog, toast, action-card, svc-card styles | | `/opt/ops-portal/static/js/app.js` | 11.6 KB | Auth module, API client, confirmation dialog, toast, utilities | ## CSS classes for auth UI (in ops.css) - `.login-overlay` — full-screen fixed overlay, `display: none` by default, `.show` sets `display: flex` - `.login-card` — centered dialog, max-width 400px, dark card with logo + form - `.login-error` — red alert box, hidden by default, `.show` reveals it - `.confirm-overlay` — modal backdrop with blur, `.show` = `display: flex` - `.confirm-dialog` — centered card with title + message + action buttons - `.toast-container` — fixed top-right notification area - `.toast`, `.toast-success`, `.toast-error`, `.toast-info` — slide-in animations ## Key quirks - **Login overlay is always in the HTML** — it's hidden by CSS `display: none`. The JS just toggles `.show` class. No page redirect on login; the overlay hides and the page stays. - **Auto-refresh stops when not authenticated** — every `setInterval` callback checks `Ops.isAuthenticated()` before fetching. Stale data stays visible until login. - **Caddy reload** after config change: `caddy reload --config /etc/caddy/Caddyfile` or `systemctl reload caddy`. Full restart (`systemctl stop/start`) only needed when adding new Caddy hosts, not modifying existing ones.