Skip to content

EH/MU/FR-C1: live sensor-health KPIs, stale detection, refresh interval - #962

Open
Mustafa-1089 wants to merge 4 commits into
mainfrom
fr-c1-sensor-health
Open

EH/MU/FR-C1: live sensor-health KPIs, stale detection, refresh interval#962
Mustafa-1089 wants to merge 4 commits into
mainfrom
fr-c1-sensor-health

Conversation

@Mustafa-1089

Copy link
Copy Markdown

Summary

Turns the Sensor Health dashboard (FR-C1) from a static shell into a live view. The KPI cards, status pills, stale indicators, and refresh interval are now all driven by real data from the backend's /sensors/updates endpoint instead of hardcoded placeholder values.

Why

Before this change the page had the right structure but wasn't meeting the FR-C1 acceptance criteria:

The three summary cards showed fixed text ("Live", "MQTT / Seeded", "15s") that didn't reflect the actual sensors, so the KPIs and the table could say different things.
Nothing surfaced stale or offline sensors, the backend already derives a Degraded/Offline status, but the frontend had no pill, filter, or visual treatment for it.
The "Last Audio" column read a field (item.lastAudio) that the backend doesn't return, so it was always blank.
The refresh interval was hardcoded in three separate places that could drift apart.

What changed

Live KPIs: Replaced the 3 static cards with 5 (Total / Online / Degraded / Offline / Low Battery), computed from the same array that fills the table, so the cards can never disagree with the rows.
Stale detection: Degraded and Offline rows are now visually highlighted, each status pill shows a "seen Xm ago" subtext from lastSeenMinutesAgo, and a "Degraded" option was added to the status filter.
Refresh interval: Introduced a single REFRESH_MS constant driving both the auto-refresh timer and the displayed "auto-refresh every 15s" label, one source of truth.
Last Audio column: Now maps the real lastAudioMinutesAgo field instead of a non-existent one.
Low-battery highlight: Keys off the backend's derived Low Battery status rather than a hardcoded <20 threshold, so frontend and backend agree.

Testing

Verified against the real backend, not just a syntax check:

Seeded 5 of the existing topology nodes with health fields; GET :9000/sensors/updates returned all 14 nodes with the expected mix (2 Online, 1 Degraded, 1 Offline, 1 Low Battery, rest Offline/null).
Confirmed KPI counts sum correctly to the total and match the table.
Loaded the page and confirmed the new cards, filter, and layout render.

Not in scope / known follow-ups

  • The page fetches through the HMI proxy on :3000, which currently returns empty while the backend on :9000 returns data correctly. That's a separate HMI proxy/routing issue (FR-D1), not part of this change.
  • CPU / RAM / Disk / Uptime / GPS columns show "--" pending a backend telemetry source; the backend derivation doesn't return them yet.

- Replace static KPI cards with live Total/Online/Degraded/Offline/Low Battery counts computed from the same data as the table
- Surface backend Degraded/Offline status and last-seen time as stale indicators
- Add Degraded status pill and filter option
- Drive refresh interval from a single REFRESH_MS constant and display it
- Map lastAudioMinutesAgo into the Last Audio column
@Mustafa-1089 Mustafa-1089 self-assigned this Aug 6, 2026

@Vamshi-Gollapelly Vamshi-Gollapelly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through the changes and there are a couple of things that should be addressed before this is ready to merge. I left inline comments on the battery/status handling and KPI counting logic, as both could produce incorrect results in some scenarios. Once those are updated and verified, I'm happy to review it again.

return `<span class="pill pill-success">${s}</span>`;
}

if (s === "Degraded") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The batteryPct < 20 check above still runs before any status check, so a Degraded/Offline sensor with low battery will show "Low Battery" here even though the KPI card and row highlight correctly show Degraded/Offline. Since status is the source of truth everywhere else in this PR, can you fold it in here too and drop the raw threshold branch?
```js
function pillHtml(status) {
const s = String(status || "").trim();
if (s === "Online" || s === "Success") return <span class="pill pill-success">${s}</span>;
if (s === "Degraded" || s === "Low Battery") return <span class="pill pill-warning">${s}</span>;
if (s === "Offline" || s === "Failed") return <span class="pill pill-danger">${s}</span>;
return <span class="pill pill-warning">${s || "Unknown"}</span>;
}
```

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The battery check was overriding status. I've dropped the raw batteryPct threshold from pillHtml and it now trusts the backend status everywhere (pill, row highlight, and the filter). So a Degraded or Offline sensor with low battery now shows its correct status. Pushed in the latest commit.

try {
const data = await apiFetch("/sensors/updates");
lastItems = Array.isArray(data.items) ? data.items : [];
updateSensorKpis(lastItems);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When backend status is something other than Online/Degraded/Offline/Low Battery (your testing notes mention nodes coming back Offline/null), updateSensorKpis counts it toward Total but doesn't add it to any bucket. Can you confirm the backend always returns one of those four values, or should this have a fallback bucket so the cards can't silently under-count?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend derives exactly four statuses (Online / Degraded / Offline / Low Battery), so those are the full set today, but you are right that the cards shouldn't silently under-count if that ever changes. I've added an other fallback bucket so every sensor is counted and the buckets always reconcile with the total. Pushed in the latest commit.

style="margin-bottom: 12px;"
>
Last updated at: --
Last updated at: -- &middot; auto-refresh every <span id="refresh-interval-label">--</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interval is inside #last-updated-at, but updateLastUpdated() replaces the parent’s entire textContent at script.js (line 209). Nothing assigns REFRESH_MS to the span, so the required visible interval changes from -- to nothing

const tr = document.createElement("tr");

if (typeof item.batteryPct === "number" && item.batteryPct < 20) {
if (item.status === "Low Battery") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend overwrites Offline/Degraded with Low Battery when the battery is below its threshold.The frontend then adds the stale treatment only for Degraded or Offline. So a low-battery sensor whose heartbeat is old or missing appears only as low battery?

@AnDo081105 AnDo081105 changed the title FR-C1: live sensor-health KPIs, stale detection, refresh interval EH/MU/FR-C1: live sensor-health KPIs, stale detection, refresh interval Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants