Dashboard
The Dashboard is the default landing page when accessing the admin interface. It provides a real-time overview of system health, active zones, climate issues, security status, access denials, and upcoming scheduled events.
GEM is a single server that owns every device connection and all automation; clients connect to it for real-time control:
Status Bar
A persistent status bar spans the top of the dashboard, showing key system metrics at a glance:
- Version Badge — Current GEM version (e.g.,
v2.0.4359) - Site Mode Pill — Current effective Site Mode, color-coded by mode name (vacation = red, away = amber, sleep/night = indigo, home = green, party = purple). Click to manage modes. Hidden when no site modes are configured.
- CPU — Current CPU load percentage
- MEM — Memory usage percentage and free memory
- Uptime — System uptime formatted as
Xd Xh Xm - Clients — Number of connected UI clients (clickable, navigates to Clients page)
- Dark Mode Toggle — Switch between light and dark admin themes (persisted in browser local storage)
- Hostname — Server hostname (right-aligned)
Attention Pills
After the system metrics, the status bar displays color-coded attention pills that highlight items needing review:
- Red (error) — Offline devices, missing backups
- Amber (warning) — Access denials today, device alerts, stale backups (1-7 days)
- Green (ok) — Backup current, or "All systems operational" when nothing needs attention
Each pill is clickable and navigates to the relevant admin page (Devices, Access Report, Device Health, or Backup).
Refresh Rates: CPU every 5 seconds, memory every 10 seconds, client count every 10 seconds, attention data every 60 seconds
Widgets
The dashboard renders an ordered list of widget cards from a 3-tier layout config (your override → site default → built-in default). The default layout is six tiles in a responsive grid (3 columns on wide screens, 2 on medium, 1 on mobile). Each widget loads independently with its own loading spinner.
Customize Layout
A pencil icon to the left of the dark-mode toggle opens the Customize Dashboard modal. From here you can:
- Reorder widgets by dragging or using the up/down arrows
- Remove widgets you don't care about
- Add any widget marked
admin_only(or any user widget) from the right panel - Use built-in default — replace the working set with the factory-default layout
- Reset to default — delete your override and fall back to the site/built-in default
- Save — persist the layout for your user
A small dot on the customize icon indicates a custom layout is active for the current user. The modal header shows whether you're viewing your override, the site default, or the built-in default.
Layouts are stored as a JSON attribute (admin_dashboard_config) on auth_user (per-user override) or system (site-wide default). Adding a brand-new admin-only widget requires registering it in widget-metadata.js plus the dashboard's ADMIN_WIDGET_MAP.
Maximize / Restore
Every widget header has a maximize button (top-right). Click it to expand that widget across the full dashboard grid; the other widgets are hidden while one is maximized. Click the close button or press Escape to restore the normal grid.
Most widgets render in a compact summary form by default — a single hero metric (e.g. "3 Offline", "All Quiet", "ALL SECURE") with one supporting line. Clicking anywhere on the summary card maximises the widget and reveals the full detail view (per-zone lists, charts, controls). Interactive elements inside the summary (badges, links) call stopPropagation so they don't accidentally trigger the maximise.
:::note Built-in default widgets
The widgets below are the built-in default set. Other admin-only widgets — including Monitor Tag Rollups (live pill row of every Monitor Tag), Active Zones, Climate Issues, Device Health, Upcoming Schedules, Security & Access (a combined tile rolling physical security state + access denials), Integration Health (per-protocol DriverHealth rollup), Energy & Demand (live power draw + active OpenADR events), and Orchestrator Tunnel (live status of the reverse tunnel to the cloud) — are available from the customize modal's "Available Widgets" panel. End-user widgets registered in widget-metadata.js are also addable to the admin dashboard.
:::
All summary tiles render through a shared SummaryHero card that tints the headline number and footer band by severity (error / warning / ok / info / neutral), so empty / healthy / degraded states are visually consistent across every widget.
Device Health
Shows disconnected devices or confirms all devices are operational.
Header Badge:
X Offline(red) — When devices are disconnectedAll Online(green) — When all enabled devices are connected
All Online State: Displays a checkmark icon with "All devices operational" message
Offline Devices List: Each disconnected device shows:
- Red status dot
- Device name
- IP address and device ID
- Reload button — Re-runs the device's
connect()driver method without leaving the dashboard. The icon spins while the reload is in flight and a toast confirms success or failure. - Settings button — Navigates to the Devices admin page filtered to that device
Refresh Rate: Every 10 seconds
Climate Issues
Shows climate/HVAC zones whose current temperature has drifted from setpoint in the wrong direction for their system mode. Routine HVAC cycle swing is filtered out using a 2° tolerance.
Header Badge:
ON TARGET(green) — All climate zones within toleranceX OFF(red) — Number of zones outside tolerance
Empty State: "All zones at temperature"
Issue Detection by Mode:
- Cool — Flags when temperature exceeds setpoint by more than 2°
- Heat — Flags when temperature falls below setpoint by more than 2°
- Auto — Uses dual setpoints (
heat_setpoint/cool_setpoint) when available, otherwise falls back to a singlesetpointas both bounds - Off — Skipped entirely
Each Issue Row Shows:
- Mode glyph (fire icon for too hot, water icon for too cold) with red/blue accent
- Zone name
- Mode pill (
COOLINGorHEATING) - Target setpoint and humidity (if available)
- Current temperature with delta from setpoint (e.g.,
+3.2°)
Worst offenders sort to the top. Click a row to jump to that zone in the Zones page.
Refresh Rate: Every 30 seconds
Security & Access
Combined two-section widget. The top half mirrors physical security state (unlocked doors, open gates, disarmed security) — derived live from the same shared activeZones store the Zones of Interest widget publishes — and the bottom half lists the 10 most recent denied access attempts within the last 24 hours.
Header Badges:
SECURE(green) orX OPEN(red) — physical security summaryX DENIED(red, clickable) — denial count, opens Access Report
Physical Security section:
ALL SECUREstamp with "All doors locked, gates closed, security armed" plus today's access-event count when no items need attention- Otherwise: alert banner + per-row tiles for every unlocked/open/disarmed zone, click to jump to the zone
Access Denials section:
No denials in the last 24hempty state- Otherwise: pulsing red rows showing username, device, access type chip (
CARD/PIN/FACE), denial reason, and time ago. Click a row to open it in the Access Report.
Refresh Rate: Denials poll every 15 seconds; security state updates live via the shared zone subscription set.
Zones of Interest
Shows every enabled zone currently in a non-rest state, grouped by subsystem. The widget uses live socket subscriptions for in-flight attribute changes (capped at 50 concurrently subscribed zones) so tiles update immediately without waiting for the next poll.
Header Badge: X Active — Total active zone count
Empty State: "All quiet — nothing active"
Activity Detection:
| Subsystem | Active When |
|---|---|
| lights, fans, fire, water | state = on (or level > 0) |
| shades | state = open or position > 0 && < 100 |
| gates, garages, lifts | state = open |
| doors, locks | state = unlocked |
| security | state = disarmed |
| power | state = off (inverted — surfaces tripped or unplugged outlets) |
| AV | source > 0 (shows source device name) |
Subsystems cameras, climate, and location are excluded.
Group Headers: Each subsystem group shows an icon, label, and active count. Alert-style subsystems (doors, locks, gates, garages, security, power) sort to the top.
Tile Decorators:
- Lights — Circular dial showing
levelpercentage - Shades — Vertical fill bar showing
position - Doors / Locks — Pulsing open-lock glyph
- Gates / Garages — Pulsing subsystem glyph
- Security — Pulsing shield-broken glyph (amber)
- Power — Pulsing power-off glyph (red)
- Fans — Spinning fan glyph
- Other — Static subsystem glyph in accent color
Each tile shows zone name, current state (level %, position %, source name, or state), and time since the change in the corner. Click a tile to open that zone's live control inline in a modal (the same control component the zone renders in user UIs — dimmer, climate, gate, lock, etc.) so you can act on the zone without leaving the dashboard.
AV Quick-Off: AV tiles whose source is currently > 0 show a small power button. Click it to send source: 0 to the zone without leaving the dashboard.
Refresh Rate: Every 30 seconds for full re-evaluation; live socket subscriptions handle mid-interval changes for visible zones.
Upcoming Events
Shows the next 5 enabled macro schedules sorted by upcoming run time.
Header Badge: X Scheduled — Number of upcoming schedules shown
Empty State: "No scheduled events"
Schedule Timeline: Each entry shows:
- Time — Next run time (hour:minute format), or "Variable" for unresolved variable-based schedules
- Name — Schedule name
- Date — Next run date, or the raw rule expression for variable schedules
Variable Schedule Resolution: Schedules using variable references (e.g., [$sunrise-15]) are resolved by looking up the current variable value and applying any offset in minutes.
Click Action: Clicking a schedule navigates to the Macro Schedules admin page filtered to that schedule.
Refresh: Loaded once on page load
Monitor Tag Rollups
Optional widget — add from the customize modal. Renders a live pill row of every configured Monitor Tag with rollup state color-coded green/red/amber/grey, plus a dashed "inert" treatment for tags that aren't yet configured. Subscribes to live monitor_tag_state_change events so the row stays in sync without polling.
The tile header shows X DOWN or ALL UP depending on the current rollup set. Click any pill to jump to the Monitoring page. See Monitoring — Tags Tab for the underlying model.
Integration Health
Optional widget — add from the customize modal. Per-protocol rollup of every device grouped by driver (BACnet, Modbus, OPC UA, KNX, MQTT, Lutron, etc.) with online/offline counts and DriverHealth telemetry pulled live off each device's health.getSnapshot() (see Commercial Protocol Stack and the per-driver pages for what DriverHealth surfaces).
Severity per row:
- Red dot — at least one device in that protocol is offline
- Amber dot — consecutive operation failures have reached a driver's threshold, or the transport client has recycled since boot
- Green dot — fully healthy
Header Badge: X alerts (red), X warn (amber), or All Healthy (green) when nothing needs attention.
Per Row Shows:
- Driver
display_name(from each driver'sgetMetaData()) and device count - Recent client-recycle count when DriverHealth is supported
- Online / offline chip counts
Click a row to jump to the Devices page filtered to that driver.
Maximized view appends a per-driver DriverHealth detail table — device, online state, consecutive failures vs. threshold, last-success age, client recycles, and the last transport-level error message — for every protocol that exposes DriverHealth. Drivers without it still appear in the rollup with just online/offline counts.
Refresh Rate: Configurable via refresh_ms (default 15 seconds).
Energy & Demand
Optional widget — add from the customize modal. Live power-draw rollup across the device fleet plus a banner for any active OpenADR demand-response events.
Power Rollup: Sums any of these device attributes (first match per device wins, normalized to kW):
| Attribute | Unit assumed | Notes |
|---|---|---|
power_kw | kW | Canonical |
current_power_w | W | |
power_w | W | |
last_w | W | |
power | W | Fallback — assumed Watts |
The hero shows total kW, the body lists every reporting source (device name + which attribute it came from + its kW contribution), and negative values render green to mark exports / generation. Click a source row to jump to that device.
Demand-Response Banner: Whenever any openadr2 device has an active event, a pulsing color-graded banner (amber → red → purple as signal_level rises through 1 → 2 → 3) appears above the source list. Shows event count, end time, and an opted-out marker. Click to open the OpenADR admin page. See OpenADR.
Header Badge:
DR ACTIVE(red) — at least one OpenADR event is in progressX kW(blue) — current total demandNo meters(grey) — nothing reporting power
Summary Severity: error/warning when a DR event is active (severity follows the highest signal level), info otherwise, neutral when no meters report.
Refresh Rate: Configurable via refresh_ms (default 10 seconds).
Orchestrator Tunnel
Optional widget — add from the customize modal. Live status of the reverse tunnel that gives the cloud orchestrator remote access to this server. When the tunnel is enabled (the tunnel setting in gem.json), GEM makes a secure outbound connection — no inbound firewall ports are opened — and this widget surfaces whether that connection is currently up.
Header Badge / Hero State:
Connected(green) — tunnel is authenticated to the orchestratorConnecting/Reconnecting/Waiting(amber/info) — bound but not yet connected (waiting on a binding, dialing, or backing off between attempts)Disabled(grey) — the tunnel is turned offIdentity Fail(red) — the orchestrator failed the pinned-identity check; the tunnel refuses to serve and stops reconnecting
Detail rows (maximized view):
- Enabled — whether the tunnel is on, with the disable reason when off
- Orchestrator URL and Tenant — the bound destination
- Site / Install ID — when present
- Identity pinned — whether the orchestrator's public key is pinned (
no (unpinned)is flagged amber while running) - Active streams — proxied requests currently in flight
- Last connected / Last disconnected — age of the most recent transport events
- Reconnect in — backoff delay before the next attempt while disconnected
- Config source — whether the active config came from
gem.jsonor thetunnelsystem attribute - Last error / Reconcile error — most recent transport or lifecycle error
:::tip Enable or disable without a restart
The tunnel can be turned on or off at runtime by setting a tunnel system attribute (a JSON value such as {"enabled": true} or {"enabled": false}). This overrides the gem.json tunnel setting and takes effect immediately — no restart needed. The widget links straight to the System Attributes grid to add or edit it. The attribute replaces the whole gem.json tunnel object, so include every field you need (it may also carry a bootstrap url / tenant_id).
:::
Refresh Rate: Configurable via refresh_ms (default 15 seconds).
Auto-Refresh Behavior
All intervals are automatically stopped when navigating away from the dashboard and cleaned up when the component is destroyed. Live zone subscriptions are also unsubscribed on unmount. Intervals only run while the browser URL is on /admin or /admin/index.
| Widget / Metric | Refresh Interval |
|---|---|
| CPU Load | 5 seconds |
| Memory | 10 seconds |
| Connected Clients | 10 seconds |
| Dashboard Report | 60 seconds |
| Security & Access (denials half) | 15 seconds |
| Device Health | 10 seconds |
| Zones of Interest | 30 seconds (+ live socket updates) |
| Climate Issues | 30 seconds |
| Site Mode | 30 seconds |
| Upcoming Events | 60 seconds |
| Monitor Tag Rollups | Live (subscription only) |
| Integration Health | 15 seconds (configurable) |
| Energy & Demand | 10 seconds (configurable) |
| Orchestrator Tunnel | 15 seconds (configurable) |
Per-widget refresh intervals are tunable in the widget config block of admin_dashboard_config — for example, {"refresh_ms": 60000} on the Active Zones entry slows its backstop poll without affecting live subscriptions.
AI Assistant
The admin header includes an AI Assistant button (gem icon) that opens a slide-out drawer on the right side of the screen. The AI assistant can:
- Configure devices, zones, rooms, and automations using natural language
- Run system diagnostics to find disconnected devices, orphan zones, and empty macros
- Create triggers and schedules
- Bulk-create many Triggers in one atomic call ("alert me whenever ANY door opens after 10pm", "email me if any thermostat goes above 80") — pre-validates every entry and rolls back the batch if any insert fails
- Create user accounts with roles, notification profile, sites, and initial credentials in a single confirmation
- Bulk-commission many devices in one atomic call (driver, IP, port, credentials, and any extra attributes per entry — rolled back as a unit if any insert fails)
- Set the same Attribute on many targets in one atomic call ("disable history on every battery_level attribute", "set timezone=America/Chicago on all clients", "remove the deprecated old_address attribute everywhere") — scope by explicit id list or a server-side filter (driver / subsystem / name / label / enabled), capped at 200 targets, supports
dry_runto preview the resolved set, and rolls back every prior write if any single target fails - Clone an entire UI — duplicates the ui row plus every ui_zone, ui_control, ui_macro, and ui_page_ui link in one atomic call (optionally deep-copying ui_page rows when the new UI needs to diverge layout-wise)
- Clone a macro and its steps under one or many new names ("clone the goodnight macro for every kid's room") — deep-copies every macro_step verbatim, atomic rollback if any clone or step copy fails, hard-capped at 25 clones per call
- Answer "what's coming up?" / "what's scheduled today?" — lists the next macro_schedule fires inside a configurable window (default 24h, max 168h), parsed from each schedule's cron rule (sunset/sunrise variable rules are skipped)
- Navigate to any admin page
- Undo recent changes (update/attribute mutations are reversible — bulk creators like users, triggers, AV zones, notification profiles, UI zone wiring, UI clones, and bulk device creation require manual cleanup)
The assistant shows context-aware quick action buttons that change based on the current admin section. For example, on the System page you'll see "Find Offline" and "Configure Selected", while on the Automation page you'll see "Create Scene" and "Add Trigger".
The AI Assistant requires an Anthropic API key configured as the anthropic_api_key system attribute. The fastest way to enable it on a fresh install is the Setup page — when AI is unavailable, the AI Guided Setup card flips into an inline form that accepts your sk-ant-... key, validates it against /v1/messages with a 1-token probe, stores it as a secure system attribute, flips ai.enabled=true in gem.json (atomic temp+rename), and re-initializes the assistant without a server restart. If a key is already on file but the assistant is disabled (e.g. set during install with ai.enabled left false), the card instead shows the masked key (sk-ant-••••••••XXXX) with a one-click Enable AI Assistant button plus a Use a different key option. See Attributes to add or change the key later.
Responsive Layout
| Screen Width | Grid Layout |
|---|---|
| > 1200px | 3 columns, 2 rows |
| 768px - 1200px | 2 columns, 3 rows |
| < 768px | 1 column, scrollable |