Skip to content

Ops Portal — Architecture

Topology

                         INTERNET
                            |
                      [Caddy :443]
                            |
                    Core (152.53.192.33)
                            |
            +---------------+---------------+
            |               |               |
     /api/* :8090    /data/* :files     /static/*
            |               |               |
     [FastAPI app]    ops-status.json   [HTML/CSS/JS]
     server.py       /var/www/ops/     /opt/ops-portal/
            |            /data/           static/
            |
    +-------+-------+-------+-------+
    |       |       |       |       |
  S3 API  UISP   Wazuh  Bitdef  systemd
 (Wasabi) (FFW) (app1) (Cloud) (Core)

Data Flow

[Collector]                     [Dashboard]
     |                               |
     |-- python3 ops-data-           |
     |   collector.py                |
     |                               |
     v                               |
  S3 buckets ----+                   |
  UISP API ------+---> ops-status    |
  Wazuh API -----+     .json ------> GET /api/status
  Bitdefender ---+                   |
  systemd -------+                   |
  cron jobs -----+                   |
                                     v
                              [Browser renders
                               health grid,
                               widgets, alerts]

Components

1. Collector (/root/.hermes/scripts/ops-data-collector.py)

  • Runs every 5 min via cron
  • Gathers: S3 backup status (6 buckets), UISP devices (90), Wazuh agents/alerts, Bitdefender endpoints, systemd services, cron jobs, server health, disk/memory/CPU
  • Timeout: 90s (was 20s — too short for 94K-file S3 bucket)
  • Output: /var/www/ops/data/ops-status.json

2. Backend (/opt/ops-portal/server.py)

  • FastAPI on port 8090
  • 7 API endpoints (health, status, servers, servers/health, audit-log, ft360/status)
  • JWT auth from /root/.hermes/.env (ADMIN_USERNAME, ADMIN_PASSWORD, JWT_SECRET)
  • Critical service restart protection (hermes, caddy, ops-portal blocked)
  • Systemd: ops-portal.service

3. Frontend (/opt/ops-portal/static/)

  • 11 HTML pages with shared ops.css, app.js, utils.js
  • Auth: login overlay → localStorage JWT → all API calls Bearer
  • Auto-refresh: 60s interval + tab visibility API
  • Mobile: hamburger toggle with .nav-links.open CSS
  • Cache-busting: all assets versioned with timestamps

4. Proxy (Caddy on Core)

  • / and /*.html → static file server from /opt/ops-portal/static/
  • /api/* → reverse_proxy to 127.0.0.1:8090
  • /data/* → file server from /var/www/ops/data/
  • Domain: ops.itpropartner.com

Cross-Service Dependencies

Dependency Server Purpose Fallback
Wasabi S3 External Backup bucket status Shows "Issues"
UISP API unms.forefrontwireless.com Device/site count Shows 0 devices
Wazuh app1 (152.53.36.131) Agent count, alerts Shows "Offline"
Bitdefender External API Endpoint monitoring Shows "Offline"
Traccar app2 (152.53.39.202) FleetTracker data Dedicated endpoint
Core systemd Local Service health, disk, memory N/A (local)

Auth Flow

Browser                    Server
  |                          |
  |-- POST /api/auth/login ->|
  |   {username, password}   |
  |                          |-- Validate against ADMIN_USERNAME/ADMIN_PASSWORD
  |                          |-- Generate JWT with JWT_SECRET
  |<- {access_token} --------|
  |                          |
  |-- GET /api/status ------->|
  |   Authorization: Bearer   |
  |                          |-- Verify JWT
  |                          |-- Read ops-status.json
  |<- {full dashboard} ------|

Key Design Decisions

  1. Collector pattern over direct API calls: Dashboard fetches one JSON blob rather than 6 separate APIs. Single point of failure but fast rendering and offline-capable (shows last-cached data).

  2. Python/FastAPI over Node: Already have Python toolchain on Core. FastAPI is lightweight, async-native, and the ops portal is read-heavy with minimal write paths.

  3. Static HTML + vanilla JS over React/Vue: 11-page dashboard with no SPA routing. Auth via localStorage JWT. Zero build step, zero dependencies beyond ops.css.

  4. JWT over session cookies: Cross-page auth without server-side session state. Token survives page navigations and ops-portal restarts (persistent JWT_SECRET in .env).