/report.html shows one company's backups over the last 7, 30 or 90
days, chosen in the page and kept in the URL so reports can be
bookmarked or linked. Reached from Reports in the top bar, or a
company's name in a job's detail.
- Summary (jobs, runs, success rate, warnings, failures, overdue now);
per job: run counts, success rate (successful / finished runs), last
result and last successful backup; a day-by-day strip; every warning
and failure with its message
- Print / Save as PDF: a print stylesheet prints light whatever the
screen theme, hides the controls, keeps rows and strips whole across
pages, and keeps the coloured cells and badges
- Days before a job was first known show as "Not monitored yet"
rather than as missed backups, so a 90-day report for a newly added
client doesn't look like 60 days of failures
- CSV of every run (full messages, times in the browser's time zone,
UTF-8 BOM for Excel, formula-like values defused with a leading ')
Backend: GET /api/v1/tenants, /api/v1/reports/tenant and
/api/v1/reports/tenant.csv; periods capped at retention. tzdata added
for systems without time zone data. Frontend status and day-strip
helpers moved out of app.js into status.js / history.js for reuse.
Tested: 9 new backend tests; in the browser against the API with 30
days of demo history (summary, issues, strips, 7/30/90 switching,
CSV download with BOM, print styles applied on screen), mock mode and
phone width.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Admins (Microsoft 365 sign-in names in BACKUP_DASH_ADMINS) get an
Admin link to /admin.html, where they can create, list and revoke the
API keys collectors use, instead of running manage.py on the server.
Backend
- /api/v1/admin/keys (list, create, revoke), admins only (403
otherwise); the user comes from the headers nginx sets after sign-in
- Changes need an X-Requested-By header, which another site's page
can't add, so a signed-in admin's browser can't be used to forge them
- A new key is returned once; only its hash is stored. Names are unique
for good, so revoked keys' history stays unambiguous
- Keys record who created/revoked them and the company they last
reported for (schema v2; databases migrate automatically on start)
- /api/v1/me reports is_admin; manage.py shares the key code and
records itself as creator/revoker
Frontend
- admin.html / admin.js: name validation shown after interaction
(:user-invalid, aria-invalid kept in sync), the one-time key with a
copy button (falls back to selecting it if clipboard access is
refused), a key table flagging keys quiet for over 24 hours, and
revoking confirmed in a <dialog>; results announced in a status region
- Shared el() and signed-in helpers moved to dom.js / signed-in.js
- Mock mode (?mock=1) includes sample keys
Tested: 15 new backend tests (access, forged requests, names,
duplicates, revoking, last-used company, v1 -> v2 upgrade); in the
browser, every admin flow in mock mode and at phone width; end to end
through the nginx config with the real API (a key created on the API
works for ingest; revoking it makes ingest 401; non-admins see an
explanation and no Admin link).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
nginx now checks every dashboard and API request with oauth2-proxy
(auth_request), which signs users in with Microsoft Entra ID. Who may
sign in is controlled in Entra: a single-tenant app registration with
"Assignment required", so only assigned users/groups get through.
- deploy/oauth2-proxy.cfg (entra-id provider, auth-only mode, secure
cookie, trusted proxy = localhost) and deploy/oauth2-proxy.service
- nginx: pages redirect to Microsoft sign-in and back to the same URL;
API calls get a plain 401 (the page already asks to reload);
/api/v1/ingest stays key-only for collectors; /oauth2/auth is
internal; the signed-in user is passed to the API in headers nginx
sets (so browsers can't spoof them); dashboard files are served with
Cache-Control: no-cache so updates show up at once
- GET /api/v1/me; the top bar shows the signed-in user and a Sign out
that also signs out of Microsoft (otherwise SSO signs straight back in)
- README: Entra and server setup steps
Tested locally with oauth2-proxy 7.15.5 and nginx 1.31 running the
deploy configs (TLS, self-signed): redirects to the tenant's sign-in
with the return URL kept, 401s, ingest bypass, sign-out allow-list,
cookie flags, security headers. The signed-in path was tested with a
stand-in for oauth2-proxy's auth answer; a real Entra sign-in wasn't.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The merge of add-backend-api into main committed unresolved conflicts
and corrupted uploads; the API could not import on main.
- backend/app/settings.py, deploy/backup-dashboard-api.service,
collectors/veeam/README.md: remove conflict markers, restoring the
add-backend-api versions
- backend/app/__init__.py, backend/tests/__init__.py: were 1024 null
bytes ("source code string cannot contain null bytes"); now empty
- Remove the accidental Backup-Dashboard gitlink at the repo root and a
duplicate collectors/veeam/MockVeeam.ps1 (identical to tests/)
- Restore the 60-minute collector interval from 17ee303, which the
merge reverted to 15, and update the docs to match
Co-Authored-By: Claude Opus 5.5 <[email protected]>
manage.py only found the database via BACKUP_DASH_DB, which is set in
the systemd unit and not in an admin's shell, so it silently created a
new backup-dashboard.db in the current directory. API keys created
there were never seen by the service.
- Settings now come from /etc/backup-dashboard/backup-dashboard.env,
which both the systemd unit (EnvironmentFile=) and manage.py read;
real environment variables still take precedence
- manage.py prints the database it uses and refuses to create a new
one unless --create is passed; --db overrides the path
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- FastAPI + SQLite backend: tenants, jobs and runs, with an idempotent
/api/v1/ingest endpoint authenticated by per-collector API keys
- Runs store started_at and finished_at; "Last Run" is the most recent
finish, and in-progress runs show as Running
- Job modal shows a 30-day daily strip, success rate, and a filterable
run list with per-run messages
- nginx: HTTPS plus basic auth, ingest endpoint exempt; systemd unit
- manage.py for API keys, job cleanup and demo data; pytest suite
Co-Authored-By: Claude Opus 5.5 <[email protected]>