Commit Graph

363 Commits

Author SHA1 Message Date
serversdown 5b70dcf071 feat: make Overview live tiles link to NRL detail
Each live monitoring tile is now a clickable link to its NRL detail page
(/projects/{id}/nrl/{location_id}) — same target as the NRL card name — with
a hover border + lift affordance so it reads as clickable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 22:01:12 +00:00
serversdown bb5b407c98 feat: live status chip on NRL list cards
Add a compact live-status chip to each NRL card in the location list, so the
inline list next to the map (Overview tab) and the Sound > NRLs tab show live
state alongside the new live tiles. Both surfaces share location_list.html, so
this lands in both.

- Chip shows "● <Leq> dB" when measuring (tinted green/amber/red at the same
  55/70 thresholds as the live tiles), else Stopped / Offline / Wedged.
  Hidden for cards with no assigned unit and for vibration locations.
- Painted by the existing 15s Overview poller (no extra requests). Repaints on
  htmx:afterSwap so the chips survive the NRL list reloading (e.g. the 30s
  dashboard swap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:30:54 +00:00
serversdown 76330f6137 feat: live monitoring section on internal project Overview
The client portal has a live dashboard but the internal project page only
showed static counts. Add a portal-style live section to the Overview tab
so operators can see real-time sound levels at a glance.

Backend:
- New GET /api/projects/{id}/live-stats — resolves each sound NRL to its
  active SLM unit and returns SLMM's cached /status snapshot (concurrent
  fetch). Internal-rich: includes battery/power/reachability the portal
  scrubs. Degrades to no_device/unreachable/no_data per location.

Frontend (project detail Overview tab):
- Rollup strip (live / offline / loudest-now) + a live tile per NRL with a
  Live/Stopped/Offline/Wedged badge, color-coded Leq (55/70 thresholds),
  Lp/Lmax, last-seen, and battery/power.
- Self-refreshes every 15s, pauses when the browser tab is hidden, and sits
  outside the 30s htmx dashboard swap so it never flickers. Polls only for
  projects with the sound module.

Reuses the same SLMM /status source as the portal; no SLMM changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 21:15:23 +00:00
serversdown c049ac8a41 fix: SLM control no longer shows false "Unknown error" on start
Starting a measurement could pop "Error: Unknown error" in the browser
even though the device started recording fine. Two causes: the proxy's
10s timeout was shorter than a real device start over cellular, and on
an httpx timeout str(e) is empty, so the relayed detail was "" -> the
frontend's `result.detail || 'Unknown error'` rendered "Unknown error".

- Raise the control proxy timeout to 30s so a healthy start isn't cut off.
- Surface SLMM's own error detail on non-200 responses.
- Add an explicit, honest timeout message.
- Never return an empty detail (which rendered as "Unknown error").

Pairs with the SLMM-side fix that makes /start confirm promptly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:22:52 +00:00
serversdown c9bb25e7e1 chore(release): 0.15.0 — operator authentication (version, CHANGELOG split, SW cache, test isolation) 2026-06-18 22:04:16 +00:00
serversdown fdcb1ca532 Merge pull request 'Operator-Auth full implementation.' (#70) from feat/operator-auth into dev
Reviewed-on: #70
2026-06-18 16:35:59 -04:00
serversdown c5ffa5c8ea docs(deploy): add .env.example documenting SECRET_KEY / COOKIE_SECURE / OPERATOR_AUTH_ENABLED 2026-06-18 20:03:35 +00:00
serversdown 68161298a4 fix(auth): hide /admin/users when flag off; pass OPTIONS preflight through gate
- operator_users router now depends on _require_auth_enabled, which raises
  404 when OPERATOR_AUTH_ENABLED is false — prevents world-open pre-seeding
  of a superadmin while the flag is off (the default).  Flag is read as a
  live module attribute (operator_auth.OPERATOR_AUTH_ENABLED) so monkeypatching
  in tests and a runtime flip both take effect.
- operator_gate passes OPTIONS requests through immediately before the exempt-
  path check, so CORS preflight reaches CORSMiddleware rather than being
  303/401'd by the gate.
- Two new tests: test_admin_surface_404s_when_flag_off (test_operator_users)
  and test_options_preflight_passes_through_gate (test_operator_gate).
  Full suite: 90 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:27:01 +00:00
serversdown 054eebe68b chore(auth): wire OPERATOR_AUTH_ENABLED into compose + changelog 2026-06-17 20:17:01 +00:00
serversdown dc95a59dfa test(auth): regression guard — gate never blocks machine endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:14:30 +00:00
serversdown 7a4453108a feat(auth): operator admin/break-glass CLI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:50:37 +00:00
serversdown bff9a4af4a feat(auth): superadmin user-management page + CRUD
/admin/users page and /api/admin/users/* JSON CRUD endpoints, all behind
require_role("superadmin"). Temp passwords are returned once on create/reset
and never stored in plaintext. Admins get 403; password_hash is never leaked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:48:13 +00:00
serversdown 41ab900c33 feat(auth): login/logout/change-password routes + pages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:38:55 +00:00
serversdown 2879abb355 feat(auth): deny-by-default gate middleware + require_role
Adds operator_gate Starlette HTTP middleware that gates every route
except an explicit allow-list. Flag defaults OFF so all existing
behaviour and tests are unchanged. wire_operator_auth helper in
conftest lets tests monkeypatch the module-global SessionLocal and
flag, keeping the gate's own DB session pointed at the test engine.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:27:41 +00:00
serversdown e8fe4845aa feat(auth): authenticate + lockout + operator data helpers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:19:36 +00:00
serversdown a6e1cb4f87 feat(auth): operator session cookie + current_operator DB re-validation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:11:41 +00:00
serversdown 4abfcbc293 feat(auth): OperatorUser model + role ladder
Add OperatorUser SQLAlchemy model (operator_users table, auto-created by
create_all) with email uniqueness, default active/must_change_password/
failed_login_count, and sessions_valid_from truncated to whole seconds.
Add backend/operator_auth.py with feature flag, cookie constants, _ROLE_RANK
map, role_at_least(), and _norm_email() helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:08:18 +00:00
serversdown 8e817ec48d feat(auth): generic HMAC signed-cookie module for operator auth
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 19:05:56 +00:00
serversdown 37e6ca55c1 docs: operator-auth implementation plan (10 TDD tasks) 2026-06-17 18:57:26 +00:00
serversdown 59c19291ca docs: operator-auth design spec (v1 password login + roles + easy reset; 2FA/operator-role deferred)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:39:49 +00:00
serversdown abdb3869bd docs: consolidate changelog into 0.14.0 + refresh README
Cut [0.14.0] consolidating SLM live monitoring, the FTP night-report
pipeline (was missing from the changelog entirely), the client portal,
and portal auth Phase 1 under one entry. Bump VERSION + README to 0.14.0
and add the sound-monitoring / night-report / client-portal features to
the README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 01:29:32 +00:00
serversdown 0beba9bfbc Merge pull request 'Sound night-report pipeline (v1): automated FTP capture → ingest → morning report' (#66) from feat/ftp-report-pipeline into dev
Reviewed-on: #66
2026-06-16 20:17:09 -04:00
serversdown b0e5dcdc52 Merge branch 'dev' into feat/ftp-report-pipeline 2026-06-16 21:52:06 +00:00
serversdown 4ca167de5e Merge pull request 'chore: wire SECRET_KEY + COOKIE_SECURE env pass-throughs in compose; flesh out changelog upgrade notes' (#64) from feat/portal-auth into dev
Reviewed-on: #64
2026-06-16 16:55:23 -04:00
serversdown 98bbbcfa86 Merge pull request 'Client portal auth (Phase 1): per-project link + password gate' (#63) from feat/portal-auth into dev
Reviewed-on: #63
2026-06-16 14:59:57 -04:00
serversdown b536877566 chore: wire SECRET_KEY + COOKIE_SECURE env pass-throughs in compose; flesh out changelog upgrade notes 2026-06-16 18:53:42 +00:00
serversdown 3c5e830f9c feat(reports): one safe restart-retry in the cycle before alerting
If the post-restart DOD check shows the meter isn't measuring, retry once with start_recording (a plain start that does NOT re-index, unlike start_cycle) and re-verify before raising the schedule-failed alert. Retry fires only on a confident not-measuring reading — never on a failed/inconclusive DOD read — so a flaky read can't disrupt an already-running measurement or split the night across two store folders. Turns a transient restart hiccup into a self-heal instead of a meter left stopped overnight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 02:54:37 +00:00
serversdown 766f64f35f refactor: final-review cleanup
- delete dead magic-link helpers (resolve_token, ensure_project_client,
  mint_link_token, provision_preview_session) + now-unused datetime import
- key brute-force lockout on link_token alone (IP term only enabled a
  source-IP-rotation bypass; behind the proxy all clients share one IP)
- drop unused PORTAL_BASE_URL from the retired CLI
- add WebSocket ownership tests (unauth + cross-project both close 1008)
2026-06-16 00:28:23 +00:00
serversdown da128f6173 docs: changelog + portal-auth Phase 1 notes 2026-06-16 00:19:33 +00:00
serversdown 76f16a2aba docs(adr): establish device data ownership principle (ADR 0001)
Modules own raw device data; Terra-View owns fleet/project/session/report context. Documents the SFM (read-through) vs SLMM (Terra-View-stored) asymmetry, the rule new modules must follow, and grandfathers SLMM as a deliberate-future-realignment exception. Establishes the docs/adr/ convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 00:17:00 +00:00
serversdown 20f62a5c0a feat: env-driven Secure flag on portal session cookie
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 00:16:54 +00:00
serversdown 01180d5725 fix: retire portal_admin mint-link (dead /portal/enter URL); refresh docstrings; assert revoke route gone 2026-06-16 00:15:09 +00:00
serversdown f0a13ea2ff refactor: retire interim magic-link/open-link in favor of password gate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 00:06:02 +00:00
serversdown 0394f4b0c8 fix: error handling + robust state in Portal access panel JS (per review) 2026-06-16 00:02:33 +00:00
serversdown eb91441904 feat: operator Portal access panel (enable + password + link)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:59:41 +00:00
serversdown 25a4a28433 feat: operator portal-access endpoints (enable/password/disable/state)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:55:10 +00:00
serversdown b8e4718318 fix: link project to its portal client (project.client_id) so the portal isn't empty
Caught by adversarial review of the scope test: portal_client_for_project minted a
dedicated client but never set project.client_id, so the client-scoped routes found
no projects — every location 404'd, including the client's own (empty portal). Now
links the project + adds a positive-case test.
2026-06-15 23:53:19 +00:00
serversdown c3eb900b7e test: portal session is isolated to its own project (404 on others)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:48:08 +00:00
serversdown c74dada8b3 fix: treat enabled-but-passwordless portal as inactive (no dead form / self-lockout) 2026-06-15 23:46:14 +00:00
serversdown d75f405857 feat: per-project portal password gate (/portal/p/{token}) + lockout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:41:37 +00:00
serversdown 446d8704f9 refactor: hoist Project import to top; drop unused test import 2026-06-15 23:39:14 +00:00
serversdown c04830a0ad feat: per-project portal session mint + link-token resolve + lockout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:35:48 +00:00
serversdown b11e1a554f feat: add per-project portal gate columns + migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:32:41 +00:00
serversdown ad6de946b5 refactor: simplify verify_password except clause; drop unused import 2026-06-15 23:31:14 +00:00
serversdown d44625374d feat: argon2 password hashing helpers for the portal 2026-06-15 23:29:26 +00:00
serversdown 33069a070d test: tidy conftest fixtures per review (drop dead try/finally, scope override cleanup, rm unused import) 2026-06-15 23:28:16 +00:00
serversdown ec5d986ac5 test: stand up pytest harness + add argon2-cffi
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 23:23:41 +00:00
serversdown 0888da32b4 docs: portal-auth Phase 1 implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:11:33 +00:00
serversdown 694980c869 refactor(reports): namespace the FTP browser partial behind window.FtpBrowser
Proper fix superseding the fb* prefix band-aid (1801d4e): wrap ftp_browser.html's script in an IIFE and expose only window.FtpBrowser. Its ~11 helpers no longer leak to global scope, so the partial is safe to co-load with other FTP-browsing partials (e.g. slm_live_view's Command Center) without name collisions in either direction. Inline onclick handlers call FtpBrowser.*; showFTPSettings stays global (it's from the included settings modal). Behaviour unchanged — verified full Jinja render + balanced delimiters.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:14:27 +00:00
serversdown 1801d4eb74 fix(reports): resolve loadFTPFiles collision breaking Browse Files on the NRL tab
ftp_browser.html and slm_live_view.html are both loaded on the per-NRL detail page (Data Files + Command Center tabs) and each defined loadFTPFiles / downloadToServer / downloadFTPFile / enableFTP / formatFileSize as globals — last to load won. 'Browse Files' then called slm_live_view's loadFTPFiles, which renders into the hidden Command Center's #ftp-files-list, so the FTP request fired but nothing appeared. Prefix ftp_browser's five colliding functions with fb* so each partial keeps its own. (Element IDs don't collide: per-unit vs fixed.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:58:34 +00:00