Files

3.3 KiB

Nav Consistency Fix — July 12, 2026

Problem

Multiple ops portal pages had erratic behavior on mobile — hamburger menu didn't work, nav links were missing or stale, some pages showed no data.

Root Causes Found (3 issues)

1. Mixed nav strategies — inline vs fetched

Some pages had the nav hardcoded inline in the HTML, while others fetched /nav.html dynamically. The inline navs were stale — missing Audit, Costs, and Grafana links. The fetched navs were always up to date.

Fix: Every page should use:

<div id="nav"></div>

And fetch nav at the bottom of the page script:

fetch('/nav.html').then(r => r.text()).then(html => {
    document.getElementById('nav').innerHTML = html;
    if (typeof window.initNav === 'function') window.initNav();
});

Pages affected: backups.html, servers.html, services.html (all had inline navs that were replaced)

2. Ops.initNav() vs window.initNav() — TypeError kills nav

The initNav function is defined in utils.js as window.initNav, NOT as Ops.initNav. Pages calling Ops.initNav() throw a ReferenceError that kills ALL JavaScript execution on the page — including the data loading and rendering code.

Fix: Replace all Ops.initNav() with window.initNav():

// BROKEN:
Ops.initNav();
// FIXED:
if (typeof window.initNav === 'function') window.initNav();

Pages affected: index.html, backups.html, servers.html, services.html

3. CSS/JS MIME types — wrong content-type kills scripts

The backend (FastAPI FileResponse) and Caddy reverse proxy served JS and CSS files with Content-Type: application/json. Browsers refuse to execute scripts or apply stylesheets with the wrong MIME type.

Root causes:

  • FastAPI's FileResponse() without explicit media_type parameter sometimes resolves .js files as application/json
  • Caddy handle_path /js/* block was nested inside another handle_path block by accident (sed nesting bug)
  • File permissions were 600 (root-only) — Caddy returned 403, browser silently failed

Fix — Caddy approach (most reliable):

ops.itpropartner.com {
    handle_path /data/* {
        root * /var/www/ops/data/
        file_server
    }
    handle_path /js/* {           # ← TOP LEVEL
        root * /opt/ops-portal/static/js
        file_server
    }
    handle_path /css/* {          # ← TOP LEVEL
        root * /opt/ops-portal/static/css
        file_server
    }
    reverse_proxy 127.0.0.1:8090
}

Plus fix file permissions: chmod 644 /opt/ops-portal/static/js/*.js /opt/ops-portal/static/css/*.css

Verification:

curl -sI https://ops.itpropartner.com/js/app.js | grep -i content-type
# Expected: text/javascript; charset=utf-8

for page in / /services.html /servers.html /backups.html /audit.html /cost.html; do
    echo "$page$(curl -sS -o /dev/null -w '%{http_code}' https://ops.itpropartner.com$page)"
done
# All should return 200

Prevention Checklist

  • Every new page: use <div id="nav"></div> + fetch pattern, never inline nav
  • Every new page: call window.initNav() (not Ops.initNav()) inside the fetch callback
  • After any Caddy config change: verify handle_path blocks are top-level, not nested
  • After adding any new static file: chmod 644
  • After any static file change: test with curl -sI to verify MIME type