Local dashboard: plain-language status + listen locally without C2 (fixes analog, #12) #23

Merged
logan merged 2 commits from feat/local-ui-usable into main 2026-10-04 01:26:11 -04:00
Owner

Makes the edge node's local dashboard (port 80) usable by a first-time owner, and makes a node listenable locally with no C2 — Toughny's exact state (installed, not yet online).

What was broken

  • Unconfigured node showed only "configure it via the API (POST /api/config/system)" — and that route wrote the raw blob into active.cfg.json, which OP25 can't read.
  • No way on the dashboard to pick a system/frequency or start/stop OP25. The scanner's hidden S key could, but its system list is empty without C2, and its NBFM/DMR choices always failed.
  • Analog (NBFM) config generation in op25-container crashed (generator.config AttributeError + wrong ChannelConfig field names).
  • /op25/status said "running" after multi_rx died (closes #12).
  • A locally chosen system wasn't resumed after a reboot (resume was gated on C2's configured).

What changed

  • GET /api/health: a plain-language checklist (password, C2 approval, MQTT, SDR, what to listen to, OP25, decoding via trunk_update last_tsbk, audio, Icecast), each with what to do next. build_checks() is pure and unit-tested for each state.
  • POST /api/listen: P25 control channels / one analog frequency / a cached C2 system. Validated in MHz, stored as a local override, OP25 restarted, and the reason is shown if it fails to start. GET /api/op25/log shows multi_rx's tail.
  • op25-container: analog config fixed (fixed channel, centre +250 kHz off the DC spike, terminal on so the dashboard can see it). /start refuses with no config (409). /status checks for a live multi_rx process and reports crashed. The run script stops liquidsoap when multi_rx exits.
  • index.html rebuilt on UI_REDESIGN tokens (Plex Sans/Mono, light+dark, status shown as glyph+word+colour, no green). Scanner: manual entry goes through /api/listen, DMR removed, Hold mock removed, System key shown.
  • main.py: one line in the lifespan OP25-resume block (not the credential/MQTT wiring).

Verified

  • Edge suite: 217 pass (185 before, plus 32 new). flake8 clean.
  • Ran the real edge-node app locally against a fake OP25 API/terminal and drove it in Chrome: checklist, Listen-locally analog + P25, validation errors, OP25 log, revert.
  • op25-container generate-config was checked with an ad-hoc TestClient (P25/NBFM/DMR→400). Not tested on real SDR hardware.
  • Reviewed by drb-qa-review (findings fixed) and drb-audio-review (no boundary or attribution change).

Known gaps (filed)

  • #20: analog is live-listening only. Conventional channels carry no talkgroup, so recordings are discarded as orphans. The dashboard says so.
  • #21: needs mqtt_manager.last_error to tell "refused" from "unreachable". mqtt_manager.py is owned by the install/OTA work.
  • #22: override calls upload under the C2-assigned system_id (pre-existing bug).

🤖 Generated with Claude Code

Makes the edge node's local dashboard (port 80) usable by a first-time owner, and makes a node listenable locally with no C2 — Toughny's exact state (installed, not yet online). **What was broken** - Unconfigured node showed only "configure it via the API (POST /api/config/system)" — and that route wrote the raw blob into active.cfg.json, which OP25 can't read. - No way on the dashboard to pick a system/frequency or start/stop OP25. The scanner's hidden S key could, but its system list is empty without C2, and its NBFM/DMR choices always failed. - Analog (NBFM) config generation in op25-container crashed (`generator.config` AttributeError + wrong ChannelConfig field names). - `/op25/status` said "running" after multi_rx died (closes #12). - A locally chosen system wasn't resumed after a reboot (resume was gated on C2's `configured`). **What changed** - `GET /api/health`: a plain-language checklist (password, C2 approval, MQTT, SDR, what to listen to, OP25, decoding via trunk_update `last_tsbk`, audio, Icecast), each with what to do next. `build_checks()` is pure and unit-tested for each state. - `POST /api/listen`: P25 control channels / one analog frequency / a cached C2 system. Validated in MHz, stored as a local override, OP25 restarted, and the reason is shown if it fails to start. `GET /api/op25/log` shows multi_rx's tail. - op25-container: analog config fixed (fixed channel, centre +250 kHz off the DC spike, terminal on so the dashboard can see it). `/start` refuses with no config (409). `/status` checks for a live multi_rx process and reports `crashed`. The run script stops liquidsoap when multi_rx exits. - index.html rebuilt on UI_REDESIGN tokens (Plex Sans/Mono, light+dark, status shown as glyph+word+colour, no green). Scanner: manual entry goes through /api/listen, DMR removed, Hold mock removed, System key shown. - main.py: one line in the lifespan OP25-resume block (not the credential/MQTT wiring). **Verified** - Edge suite: 217 pass (185 before, plus 32 new). flake8 clean. - Ran the real edge-node app locally against a fake OP25 API/terminal and drove it in Chrome: checklist, Listen-locally analog + P25, validation errors, OP25 log, revert. - op25-container generate-config was checked with an ad-hoc TestClient (P25/NBFM/DMR→400). **Not tested on real SDR hardware.** - Reviewed by drb-qa-review (findings fixed) and drb-audio-review (no boundary or attribution change). **Known gaps (filed)** - #20: analog is live-listening only. Conventional channels carry no talkgroup, so recordings are discarded as orphans. The dashboard says so. - #21: needs `mqtt_manager.last_error` to tell "refused" from "unreachable". mqtt_manager.py is owned by the install/OTA work. - #22: override calls upload under the C2-assigned system_id (pre-existing bug). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
logan added 2 commits 2026-10-04 01:22:31 -04:00
- /api/health: ordered checklist (password, C2 approval, MQTT, SDR, source,
  OP25, decoding, audio, Icecast) with what's wrong and what to do; pure
  build_checks() so every state (incl. Toughny's unapproved node) is tested
- /api/listen: pick P25 control channels / one analog freq / a cached C2
  system, validated in MHz, stored as a local override; OP25 restarted with
  the start failure reason surfaced
- /api/config/system no longer writes a raw blob OP25 can't read
- OP25 resumes after reboot on a locally chosen system, not only C2-assigned
- op25-container: analog (NBFM) config generation fixed (AttributeError +
  wrong field names), /op25/log tail, start refuses without a config,
  status detects a dead multi_rx ("crashed") -- node-26#12
- index.html rebuilt on UI_REDESIGN tokens; scanner drops DMR, shows System key

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scanner manual entry uses validated /api/listen; drop Hold mock; flag analog as live-only (node-26#20)
CI / lint (push) Successful in 6s
CI / lint (pull_request) Successful in 6s
CI / test (push) Successful in 41s
CI / test (pull_request) Successful in 39s
797e14481b
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
logan merged commit 1e1dab66b1 into main 2026-10-04 01:26:11 -04:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/node-26#23