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¶
-
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).
-
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.
-
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.
-
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).