Commit Graph
9 Commits
Author SHA1 Message Date
bonaminandClaude Opus 5.5 f1a7c5a7ca feat(devices): crash detail view and fleet Crash Report (F-070)
- CrashDetail (components/shared): headline is abort_msg when present,
  else the Xtensa exception name. For aborts, pc/exception/fault address are
  shown muted with a note that they describe abort() and that the first
  2-4 frames are abort internals. Shows task, uptime at crash, heap stats with
  a "likely heap exhaustion / fragmentation" badge (largest_block < 12000 or
  free_heap < 25000, F-055), PC + backtrace as 0x%08X in ESP serial format,
  a copyable addr2line command (PC, backtrace, then the PC parsed from
  "abort() was called at PC ..."), elf_sha256, and a backtrace-corrupted
  flag. When the server has the build's ELF and addr2line, a Decode button
  shows function + file:line per frame.
- Health tab boot history uses it, previews the headline in the row, and
  gets a "Sync from device" button (telemetry.get_boot_history, merged
  server-side).
- New /devices/crashes page (linked from the Fleet header): crashes grouped
  across devices with per-device counts and the latest occurrence.
- Firmware release modal: optional firmware.elf attachment.
- API reference: boot_report payload documents the F-070 fields.
- lib/crashReport.js holds the parsing/formatting rules; lib/clipboard.js
  a copy helper with a non-secure-context fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:28:41 +03:00
bonaminandClaude Opus 5.5 46c5c0a846 fix(mqtt): treat broker-replayed retained messages as stale state
The firmware publishes status/heartbeat, system/alerts, system/info and
status/playback with retain=true. On every backend (re)connect - every
restart and every uvicorn --reload - the broker replays the last message on
each of those topics for every device that ever connected. We handled those
replays as if they had just happened:

- heartbeats: a row with received_at=now() for every device, so devices
  that have been dead for months showed ONLINE for 90s after each restart
  and got pinged. ~770k such rows exist locally.
- boot_report: the last boot logged again as a new reboot (the phantom
  PANIC entries on the Health tab).
- alerts / other info events: logged again as new occurrences.

MQTT delivers retain=1 only for replays caused by a new subscription; live
publishes always arrive with retain=0. The flag is now passed through to the
handlers and the WS broadcast:

- heartbeat: replays are not stored. A live heartbeat is.
- {"state":"offline"} heartbeat (LWT / graceful disconnect) is no longer
  stored as a sign of life. It marks the device offline immediately in
  a small in-memory set (mqtt/presence.py) used by /mqtt/status and the ping
  loop; a later live heartbeat clears it. Replayed offline markers also mark
  offline, since a retained message is the device's last word.
- boot_report: live -> always a new boot. Replay -> stored only if it
  differs from the device's latest boot row (i.e. we missed it while down).
- alerts: replay still syncs the current-alert row; history gets a row on a
  live alert (even an identical repeat - faults recur) or on a replay that
  changes state. Replaces the 98dd16b rule that dropped identical live alerts.
- other info events: replays are not logged.
- Frontend (DeviceList, DeviceDetail, LogsTab) ignores retained WS messages
  for live updates, and flips a device offline on the offline marker instead
  of marking it online.

Verified locally after a backend restart: only the 7 actually-live devices
got new heartbeat rows (none from the replays), and no boot/alert/info rows
were created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:01:41 +03:00
bonaminandClaude Sonnet 5 0c0dd9d0e9 fix(devices): signal glyph polish, fix invisible copy button, even firmware row
Follow-up on 529e866 after review:

- SignalIndicator: offline now renders a diagonal slash (standard
  "no signal" convention) instead of an X. Added a distinct amber "?"
  state for devices that are online but whose firmware never reports rssi
  at all (legacy v1 heartbeats predate the rssi/state/ok fields — see API
  Reference's v2 migration notes) — previously this looked identical to
  "not loaded yet". DeviceListCardView's online block now opts into this
  by passing isOnline explicitly.

- Copy-to-clipboard for the serial number was invisible: the icon button
  had opacity:0 as an inline style, which beats a CSS hover rule at equal
  specificity, so the reveal-on-hover code paths never fired. Replaced
  with a shared CopyableSerial component (components/shared/) where the
  whole serial text is clickable, not just a trailing icon, and the icon
  sits at partial opacity at rest instead of fully hidden. Wired into both
  DeviceDetail's header and OverviewTab's hero Serial Number field.

- GeneralTab firmware hero: all stat columns are now equal-width via a
  grid instead of ad-hoc flex gaps, and backup_version's "Unknown" sentinel
  (the device's own placeholder when no second OTA slot has ever been
  flashed) is no longer rendered as the literal string "vUnknown".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:45:01 +03:00
bonaminandClaude Sonnet 5 529e866fff fix(devices): offline signal glyph, plus firmware UI across Device Detail tabs
SignalIndicator now takes an isOnline prop — when a device is offline it
renders a dimmed amber glyph with an X instead of showing the last-known
(possibly full-bars) RSSI reading, which was misleading on the Device List
table view. Card view already special-cased offline devices and is
unaffected.

Also, while working the device details surface:
- DeviceDetail: hover-to-reveal copy button next to the serial number
  subtitle in the page header.
- OverviewTab hero: swapped the redundant "Location" field (already shown
  in detail on the General tab's map) for live Firmware Version.
- GeneralTab: new full-width, compact Firmware hero row (version, channel,
  validation state, boot count, backup slot) sourced from firmware.status.
- ControlTab: max width brought in line with every other tab (2000, was
  1400), and added a 4th "Firmware" section with channel selection,
  Update Now, custom-URL flash, and commit/rollback controls, wired to the
  firmware.status / ota.* / firmware.commit / firmware.rollback commands
  documented in the API Reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 11:47:10 +03:00
bonaminandClaude Sonnet 5 acd1896788 feat(devices): add Health tab, log explorer, and fleet health indicators
Surfaces the new device-health telemetry (boot events, ping RTT,
diagnostics reports, alert events) across the console:

- HealthTab: boot/diagnostics timeline, CPU temp and RSSI charts
  (LineChart), current alert status, and a Settings sub-tab for
  DeviceHealthSettings thresholds
- LogsTab: dedicated log explorer embedded in the Health tab, with
  level/source filtering and fmtLogTimestamp for dense timestamp rows
- OverviewTab: a "Latest Device Issue" card showing the most recent
  alert event, colour-coded by severity and fading with age, plus a
  modal to inspect the surrounding log lines
- DeviceList/DeviceListCardView/DeviceListMapView: fleet list gains a
  SignalIndicator-based RSSI display in place of the plain online dot

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 18:32:06 +03:00
bonaminandClaude Sonnet 5 6e0a291228 feat(devices): add console-only display name (console_name)
Lets staff set a friendly name for a device independent of whatever
name the end user gave it in the app. console_name is never shown to
app users and never synced from/to device_name; every display label
across search, equipment/helpdesk name resolution, device search, and
the Manage tab's issue linker now falls back through console_name ->
device_name -> serial rather than device_name alone.

Also includes an incidental one-line fix in devices/service.py: the
nested-struct deep-merge in update_device() was missing the newly
added device_health_settings key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 18:31:10 +03:00
bonaminandClaude Sonnet 5 cc138e3d82 feat(devices): push General/Clock/Bells/Alerts config live over MQTT
Adopts useDeviceCommand across DeviceDetail and its tabs so config
changes (log levels, clock settings, bell outputs, alert thresholds,
backlight, attributes) are sent to the device immediately via
control commands and only persisted to Firestore once the device
acks success, instead of writing Firestore first and hoping the
device eventually picks it up.

- GeneralTab: log-level sliders now call log.set_serial/sd/mqtt
  directly and revert on failure; a background log.get_config +
  network.info pull reconciles Firestore against the device's actual
  state once per mount
- ClockTab, BellsTab, ControlTab, and the Edit* modals: same
  live-command-then-persist pattern
- EditLoggingModal is removed — its job (log level editing) moved
  inline into GeneralTab's sliders, so a modal round-trip is no
  longer needed
- DeviceDetail wires the shared useDeviceCommand connection through
  to each tab and adds a Health tab entry

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 18:25:30 +03:00
bonamin 6a958a8d7d update: Add Global Search on Header, Add Global Audit log for all actions. 2026-04-19 15:41:29 +03:00
bonamin 0a8a42d69b Initial Switch to V2. Completely Overhauled Backend, Frontend and General Structure. 2026-04-17 14:45:30 +03:00