Commit Graph
4 Commits
Author SHA1 Message Date
kyleandClaude Opus 5.5 2297187f88 Add per-company backup reports (printable page and CSV)
/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]>
2026-10-08 14:56:54 -06:00
kyleandClaude Opus 5.5 d4e821799c Add an admin page for managing collector API keys
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]>
2026-10-08 13:52:29 -06:00
kyleandClaude Opus 5.5 2fa9eea274 Replace basic auth with Microsoft 365 sign-in
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]>
2026-10-08 11:31:35 -06:00
kyle 2df1a75fd5 Update public/css/styles.css 2026-10-05 04:00:56 +00:00