Files

2.4 KiB

Ops Dashboard Pattern

When to use

Build an infrastructure operations dashboard when you need a single pane of glass showing cron health, service status, backup freshness, API reachability, server inventory, and system resource usage -- all updating automatically.

Architecture

Python data collector (every 5m via no_agent cron)
        |
        v writes JSON to
   /var/www/<app>/data/ops-status.json
        |
        v served by Caddy
   <domain>/data/ops-status.json
        |
        v fetched by
   index.html (dark theme dashboard)
        |
        v auto-refresh every 30s
   dashboard stays live

Key pieces

1. Data collector script

  • Self-contained (stdlib only: subprocess, urllib, json, pathlib, os, datetime, socket)
  • Each data source wrapped in safe() -- one failure never crashes the whole cycle
  • Sources: Hermes cron jobs, systemd services, disk/memory, S3 backups (aws CLI), API health checks, versions, Hetzner server list, netcup server info
  • Output: /var/www/ops/data/ops-status.json (~6KB JSON)
  • Error log: /var/log/ops-collector.log
  • Runs via cron: --schedule "*/5 * * * *" --no_agent --script ops-data-collector.py

2. Dashboard HTML

  • Dark theme: bg #0f172a, cards #1e293b, amber accents #f59e0b
  • NO external dependencies (no Tailwind, no CDN, no frameworks) -- inline CSS + JS
  • Mobile-first responsive, PWA meta tags for iPhone Safari
  • Sections: Overall Health Bar, Scheduled Jobs, Services, S3 Backups, API Health, Server Inventory, Routers (placeholder), Versions
  • Fetch /data/ops-status.json on load, auto-refresh every 30s
  • If fetch fails: red banner "Dashboard data not available -- collector may be offline"

3. Caddy serving

ops.itpropartner.com {
    root * /var/www/ops
    encode gzip
    file_server
    log { output file /var/log/caddy/ops.log }
}

4. CORS for the data file

header /data/* {
    Access-Control-Allow-Origin "*"
}

Pitfalls

  • S3 auth requires endpoint-url for Wasabi: aws s3 ls --recursive --endpoint-url https://s3.us-east-1.wasabisys.com
  • Datetime timezone mismatch: S3 ls returns naive datetimes. The collector must .replace(tzinfo=timezone.utc) before subtracting.
  • Python 3.13 deprecation: datetime.utcnow() is deprecated. Use datetime.now(timezone.utc) instead.
  • AWS CLI path: must activate /opt/awscli-venv/bin/activate before calling aws commands
  • Placeholder sections: Show amber cards with "Coming soon" for empty sections