--- name: ops-portal-pipeline version: 2.0.0 --- # Ops Portal — Architecture & Common Failure Modes Created 2026-07-08 for ops.itpropartner.com. Updated 2026-07-12 with JS architecture fixes. ## Data Pipeline Data Collector (every 5 min) → `/root/.hermes/scripts/ops-data-collector.py` → writes `/var/www/ops/data/ops-status.json` → dashboard pages fetch `/data/ops-status.json` ## JS Architecture The portal uses TWO JavaScript files loaded on every page: | File | Location | What it exports | |------|----------|----------------| | `utils.js` | `/opt/ops-portal/static/js/utils.js` | `window.initNav`, `window.fetchStatus`, `window.escapeHtml`, `window.statusBadge`, `window.fmtTime` | | `app.js` | `/opt/ops-portal/static/js/app.js` | `window.Ops` (login, logout, isAuthenticated, getStatus, etc.), `window.initNav`, `window.escapeHtml`, `window.fmtTime`, `window.fetchStatus` | **Both files MUST exist in the backend's static directory** (`/opt/ops-portal/static/js/`). A second copy lives at `/var/www/ops/js/` for Caddy's static file handler (controlled by `/etc/caddy/Caddyfile`). If either location goes out of sync, pages will 404 on one of the files. **Critical failure (Jul 12):** `utils.js` existed at `/var/www/ops/js/utils.js` but NOT at `/opt/ops-portal/static/js/utils.js` — all pages referencing `/js/utils.js` got a 404. Fix: copy `utils.js` to the backend's static directory. ## Auth Flow ``` Page loads → app.js checks localStorage.getItem('ops_token') → token exists? → hide login overlay, load page data → no token? → show login overlay, wait for credentials → user submits → POST /api/auth/login → store token in localStorage → load page ``` **Token storage:** `localStorage` (NOT `sessionStorage`). `localStorage` persists across tabs and browser restarts. **Credentials:** `germaine` / `itpp2026!` (stored in `/root/.hermes/.env` as `ADMIN_PASSWORD`). ## Nav Loading Pattern Pages either have inline nav (index.html) or fetch nav dynamically: **Fetched nav pattern (most pages):** ```html ... fetch('/nav.html').then(function(r) { return r.text(); }).then(function(h) { document.getElementById('nav').innerHTML = h; if (typeof Ops.initNav === 'function') Ops.initNav(); }).catch(function(e) { console.warn('Nav failed:', e); }); ``` **The hamburger toggle** has an inline `onclick` handler that toggles `classList.toggle('open')` on `#navLinks`. The `Ops.initNav()` function used to ALSO add an `addEventListener` that did the same toggle — causing both handlers to fire on every click, cancelling each other out. **Fixed Jul 12:** removed the duplicate toggle from `initNav()`. initNav now only handles: 1. Active-link highlighting (`.nav-link.active`) 2. Closing the menu when tapping outside (`document click` listener) ## Common Failure Modes & Fixes | Symptom | Root Cause | Fix | |---------|------------|-----| | Hamburger does nothing on mobile | Duplicate toggle handler (inline onclick + initNav addEventListener) | Remove addEventListener from initNav; inline onclick is sufficient | | Hamburger does nothing (2) | `window.initNav` is undefined | Export `window.initNav = initNav` in app.js | | Login overlay shows on every page load | `audit.html` uses `sessionStorage` not `localStorage` | Change to `localStorage` | | Services/servers/backups crash on load | `getElementById('logoutBtn')` called before nav fetch completes | Wrap in interval or move inside fetch callback | | cost.html blank on load | `window.fetchStatus` is undefined | Export `window.fetchStatus = fetchStatus` in app.js | | config.html renders nothing | `window.escapeHtml` is undefined | Export `window.escapeHtml = escapeHtml` in app.js | | JS/CSS files return 403 | File permissions are 600 | `chmod 644` on all static files | | JS returns wrong content-type | Backend FileResponse without explicit media_type | Set `media_type` explicitly or serve through Caddy `file_server` | | Pages load stale broken JS | Browser cached old version | Add `Cache-Control: no-cache` headers and `?v=N` on script tags | | API shows 6/7 with one failing | `cloudflare_zones` returns a list, not "ok" string | Filter it out of `api_checks` in collector | | Nav loaded but menu doesn't toggle | nav.html fetched but inline onclick not firing | Verify `onclick` attribute exists on toggle in nav.html | ## All Pages /, /services.html, /servers.html, /backups.html, /network.html, /cron.html, /config.html, /logs.html, /audit.html, /cost.html, /fleettracker360.html, /nav.html