2026-10-05 03:58:42 +00:00
2026-10-05 03:58:42 +00:00
2026-10-05 03:57:48 +00:00
2026-10-05 03:57:48 +00:00

Backup-Dashboard

Proof of concept backup dashboard, intended to display backup jobs from multiple sources.

collectors ──POST /api/v1/ingest (API key)──▶ ┌──────────────┐
                                              │ FastAPI +    │
browser ──nginx (basic auth, TLS)──▶ /api/* ─▶│ SQLite       │
        └─ static files: public/              └──────────────┘
  • public/: static frontend (no build step). Add ?mock=1 to the URL to use built-in sample data.
  • backend/: FastAPI API backed by a single SQLite file.
  • deploy/: nginx site and systemd unit.

Data model

  • Tenant (company) → Job → Run.
  • A job is keyed by (tenant, source_system, job.external_id), so renaming a job at the source updates it instead of creating a duplicate.
  • A run has status (running | success | warning | failed), started_at and finished_at. The dashboard's Last Run is the most recent finished_at. While a job is running it shows Running, and Last Run still shows when the previous run finished.
  • Runs older than BACKUP_DASH_RETENTION_DAYS (default 90, minimum 30) are purged every 6 hours.

Reporting results (collectors)

Each collector (a script on a Veeam server, a webhook relay, etc.) gets its own API key:

cd backend && python manage.py create-key veeam-acme

Then it POSTs one report per run. Send running when a run starts (optional) and the final status when it ends. Reports with the same started_at (or run.external_id) update the same run, and retries are safe.

curl -X POST https://backups.example.com/api/v1/ingest \
  -H "Authorization: Bearer bdk_..." -H "Content-Type: application/json" \
  -d '{
    "tenant": "Acme Corp",
    "source_system": "Veeam B&R",
    "job": { "external_id": "4f1c…", "name": "File Server – Nightly", "next_run_at": "2026-10-05T22:00:00-04:00" },
    "run": {
      "status": "success",
      "started_at": "2026-10-04T22:00:05-04:00",
      "finished_at": "2026-10-04T22:47:12-04:00",
      "message": "Job completed successfully.",
      "size_bytes": 128849018880,
      "details": { "repository": "REPO-01" }
    }
  }'
  • All timestamps must include a timezone offset (Get-Date -Format o in PowerShell does this).
  • job.next_run_at controls the Overdue flag. If you omit it, the stored value is kept. If you send null, it's cleared.
  • run can be omitted to only register or update a job.
  • Unknown fields are rejected (HTTP 422), so typos in collector scripts fail loudly.

API (read)

Endpoint Returns
GET /api/v1/jobs All jobs with current status, running_since, last_run_at, next_run_at
GET /api/v1/jobs/{id} One job, plus the last finished run's duration, size, message and details
GET /api/v1/jobs/{id}/runs?days=30 Run history (no messages), newest first
GET /api/v1/runs/{id} One run including message and details
GET /api/v1/health {"status": "ok"}

The OpenAPI schema is at /api/v1/openapi.json.

Authentication

  • Dashboard users: nginx basic auth over HTTPS (deploy/nginx.conf). Every user can see every tenant. Planned: Microsoft 365 sign-in through oauth2-proxy with the Entra ID provider and nginx auth_request. That's an nginx-only change. The frontend already tells the user to reload when a session expires (HTTP 401).
  • Collectors: per-collector bearer keys, checked by the app. Only a SHA-256 hash is stored. To revoke one, run python manage.py revoke-key NAME.

Development

cd backend
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
pytest
python manage.py seed-demo                     # 30 days of fake history
BACKUP_DASH_SERVE_STATIC=1 uvicorn --factory app.main:create_app --reload
# → http://localhost:8000

Deployment (Linux, nginx)

  1. Copy the repo to /opt/backup-dashboard, create a venv at /opt/backup-dashboard/venv, and run pip install -r backend/requirements.txt.
  2. Create a backupdash system user, then install deploy/backup-dashboard-api.service into /etc/systemd/system/ and enable it.
  3. Copy public/ to /var/www/backup-dashboard/public, install deploy/nginx.conf, get a certificate with certbot, and create users with htpasswd -B.
  4. Run the manage.py commands as the service user with BACKUP_DASH_DB=/var/lib/backup-dashboard/backup-dashboard.db.

Back up the SQLite file with sqlite3 backup-dashboard.db ".backup copy.db". Don't use a plain cp while the service is running.

S
Description
Proof of concept backup dashboard, intended to display backup jobs from multiple sources
Readme
395 KiB
Languages
PowerShell 52.5%
Python 19.1%
JavaScript 18.1%
CSS 5.9%
HTML 4.4%