# Ops Portal — Network Page Pattern Created: 2026-07-08 Session: Built `/var/www/ops/network.html` — network infrastructure page for the ops portal Domain: `ops.itpropartner.com` Root: `/var/www/ops/` ## Page structure | Section | Content | |---|---| | **Nav** | Shared via `fetch('/nav.html')` — nav partial already had the `/network.html` link | | **Header** | Title "Network", status summary, last-updated timestamp, refresh button | | **Network Health Overview** | 5-card grid: DNS Zones tracked, API Checks (ok/total), Routers tracked, Domains active, Overall health score | | **MikroTik Routers** | Amber placeholder card + conditional backup history row from S3 data | | **Ubiquiti / UISP** | Teal placeholder card | | **DNS & Domains** | Live table from `data.cloudflare_zones` or `data.domains` with soft-coded fallback | | **Network Health — Port & Service Checks** | Grid of API/port check cards | ## Reusable patterns ### 1. Fallback data for when collector is offline When a page depends on data from a live collector that may be offline, provide a hardcoded fallback so the page is useful from day one: ```javascript var KNOWN_ZONES = [ { name: 'itpropartner.com', zone: '' }, { name: 'nousresearch.com', zone: '' }, // ... ]; ``` Render logic: prefer live data → fall back to known list → show "Pending" badges → the page always has content even if the data source is down. ### 2. Placeholder card pattern Sections that aren't yet connected to a live data source use a `placeholder-card` with status badges: ```html
🔄
Title of feature
Description of what will show when configured
Status: Not configured Requires: API Key
``` Three color variants are defined: - `.placeholder-card` (amber) — not yet configured - `.placeholder-card.placeholder-teal` — setup required (teal accent) - `.placeholder-card.placeholder-purple` — reserved for future use ### 3. Data-source-agnostic health grid The health overview at the top fetches from multiple possible data paths: | Metric | Data path priority | |---|---| | DNS Zones | `data.cloudflare_zones` → `data.domains` → `KNOWN_ZONES.length` | | API Checks | `data.api_health` → `data.api_checks` → 0 | | Routers | `data.routers` → 0 | | Overall health | Weighted combination of zone status + API status | This means the grid always renders something, even if only partial data is available. ### 4. S3 backup cross-section When an S3 backup bucket name matches a section (e.g., "mikrotik-backups"), the section shows backup history inline: ```javascript var s3Backups = getNested(data, 's3_backups', []); for (var i = 0; i < s3Backups.length; i++) { var bName = (b.name || b.bucket_name || b.bucket || '').toLowerCase(); if (bName.indexOf('mikrotik') !== -1) { // Show backup row with last_upload, age color, status dot } } ``` ### 5. Plain CSS (no Tailwind CDN) This page uses plain CSS with CSS custom properties — same dark theme variables as the rest of the portal but no Tailwind CDN dependency. This is an alternative approach to the Tailwind-heavy mockups listed elsewhere in this skill. Useful when: - The page is deployed in production (not a mockup) - Caching and no-external-dep is preferred - The shared `ops.css` stylesheet pattern is in play ## Data flow Fetches `/data/ops-status.json` on load + auto-refresh every 30s. Expects these fields: ```json { "cloudflare_zones": [{ "name": "...", "status": "active" }], "api_health": [{ "name": "...", "status": "ok" }], "network_checks": { "hostname": { "status": "reachable", "latency_ms": 42 } }, "routers": [{ "name": "...", "status": "..." }], "domains": [{ "name": "...", "status": "...", "last_checked": "..." }], "s3_backups": [{ "name|bucket_name|bucket": "...", "status": "ok", "last_upload": "..." }] } ``` ## File - `/var/www/ops/network.html` — 920 lines, 30 KB, plain HTML+CSS+JS, no external dependencies