Matches settings' new multi-topic-per-client support: the create form's
Topic scope field is now a textarea (one pattern per line), the list shows
all of a client's scopes, and a '+ Topic' button lets an admin grant an
existing client more topic access without recreating it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces settings' new service-client flag in the MQTT Broker Clients tab —
a checkbox that stores the password encrypted so a stack app (e.g.
hvac-backend) can fetch its own login at runtime, plus a 'service' badge in
the client list. Device logins are unchanged (leave the box off).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AppIcon.js was a stale, tracked, out-of-sync compiled duplicate of
AppIcon.tsx (missing Zap/Thermometer too) sitting next to the real
source. Since imports use bare 'from ./AppIcon' with no extension,
Vite's default resolve order picks .js before .tsx, so it may have
been silently shadowing the real source. Removed it so there's a
single source of truth, and added Gauge (plant's icon) there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the flat scrolling capture log with a structured message buffer
(mqttMessages) rendered two ways: a collapsible topic tree showing each
topic's latest value (MQTT-Explorer-ish), and a chronological feed. An
"Auto-refresh" toggle repeats the existing 8s /deploy/exec capture every
~9s and merges results in, rather than requiring a manual click each time.
Still not a genuine live stream (no WebSocket listener on the broker) —
just polling dressed up to feel closer to one, without touching the
broker or NPM config.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AdminMonitor: new "MQTT" tab reuses the existing Shell tab's /deploy/exec
mechanism to run a bounded (8s) mosquitto_sub capture against the broker
LXC via a read-only "mqtt-inspector" identity — a one-shot capture, not a
live stream, since /deploy/exec is request/response. Retained messages
(most device telemetry) show immediately regardless.
AdminSettings: new "MQTT Clients" tab — create/list/revoke broker
identities via the new settings API, matching forecasting's API Keys
show-once-password UX. Removes the need to hand-run mosquitto_ctrl for
every new device or app that needs broker access.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A 7-day window meant "next week"-type events could miss both the Mine
fetch and the All backfill entirely, since they queried the same range.
The Upcoming list is still capped by UPCOMING_LIMIT, so this doesn't
flood the dashboard — it just means events further out get a chance.
When the Mine tab has fewer than 3 relevant events in range, top it up with
the earliest not-mine events from All (Today + Upcoming combined), rendered
in italics so it's clear they aren't personally assigned.
Dashboard now shows today's and upcoming events (mine or all, toggle) for
users with calendar app access. Also gives the portal shell a 1-hour
inactivity default for devices that have never been configured, instead of
following child apps into an unlimited 30-day session — that's why the
dashboard could sit logged in for days without prompting. An explicit "Off"
choice in Admin Settings still overrides it (now written as mins=0 instead
of clearing the cookie, so it's distinguishable from "never configured").
Login.tsx only read the return path from router state, which a hard
cross-document navigation (a child app bouncing the whole shell here
on session expiry) can never carry — so re-authenticating always
landed on the Dashboard instead of back in whatever app you were
using. Login.tsx now also accepts a ?from= query param for that case.
Also wired up AuthGate's inactivity auto-logout timer, which was
defined (getInactivityMs) but never actually called — so the
per-device timeout configurable in Admin Settings had no effect.
Disabled for installed PWAs as intended.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Admin clicks 'Auth secret', re-enters their password, and sees CENTRAL_AUTH_SECRET
with a copy button. Secret auto-clears after 60 seconds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On every iframe load, mirror its current path to the portal URL hash
(e.g. /app/cashup#/reports). On mount, initialSrc reads that hash and
restores the correct page after a refresh. Hash is cleared when
switching apps so it doesn't bleed across slugs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If a different user logs into a child app while the portal tab is open,
the portal cookie changes but the in-memory user state goes stale. Now
AuthGate re-fetches /api/auth/me on visibilitychange and focus events,
updating state only when the user identity actually changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Displays a gold badge next to the latest commit hash when the backend
returns a commitsBehind count greater than zero.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Backup Directory field in the Nextcloud integration card gains a
Browse button that opens an inline panel with: breadcrumb navigation,
scrollable directory list, and a Select button to pick the current path.
Falls back to manual text entry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backups tab shows last-run status, expandable run history with per-item
breakdown (db/vol, size, status), and a Run Now trigger button.
Settings Nextcloud card gains a Backup Directory field.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Portal sidebar was rendering nothing for the history app entry because
History was missing from the Lucide icon map.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Maintenance (Wrench) and Reports (BarChart2) icons were missing from
the hardcoded iconMap, causing those app tiles to show no icon.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>