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.
Quick navigation: press Ctrl+K (Cmd+K on Mac) anywhere in the admin interface — or click the search icon in the top toolbar — to open the command palette. Type to jump to any admin page or straight to an entity by name (zones, devices, macros, triggers, schedules, UIs, subsystems, spaces, users). Entities open in place in an editor modal where supported; pages navigate directly.
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 admin dashboard is a real UI page (admin_dashboard), seeded at boot and rendered through the shared UI Page component — the same path user dashboards take. It renders an ordered set of widget tiles (six by default) that flow in a responsive grid, roughly 3 across on wide screens, 2 on medium, 1 on mobile. Each widget loads independently with its own loading spinner.
If the admin_dashboard page hasn't been seeded (an older deployment, or a fresh boot before the widget scan completes), the dashboard falls back to a built-in grid resolved from a legacy 3-tier admin_dashboard_config attribute chain (your override → site default → built-in default).
Customize Layout
Because the dashboard is a real UI page, you edit it exactly like any other. A pencil icon to the left of the dark-mode toggle opens the UI Pages editor, deep-linked to the admin dashboard (?page=admin_dashboard). From there you can:
- Add any admin-only widget (or any end-user widget) to the page
- Remove widgets you don't care about
- Reorder widgets and set per-widget size and container config
- Preview the result before saving
Changes take effect on the dashboard the next time it loads. A small dot on the customize icon indicates a legacy per-user admin_dashboard_config override is still in effect on the fallback path.
Details View
Each widget header has a details button (the three-dot icon, top-right). Click it to open that widget's full detail view in a centered modal overlay; click the close button or press Escape to dismiss it. The overlay floats above the page, so the rest of the dashboard stays in place underneath.
Most widgets render a compact summary by default — a single hero metric (e.g. "3 Offline", "All Quiet", "ALL SECURE") with one supporting line. Opening the details view reveals the full detail (per-zone lists, charts, controls).
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) — can be added from the UI Pages editor. 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
Alarm Feed
Live feed of open alarms from the alarm center — active and latched-unacknowledged events with severity, message, and source. Shelved alarms are hidden from the feed.
Header Badge:
CLEAR(green) — no open alarmsX UNACKEDorX OPEN(red) — open alarms needing attention
The summary tile shows the open count tinted by severity; the details view lists each alarm with a per-alarm acknowledge button. Settings: minimum severity filter, only-unacknowledged filter, max rows, and an Allow Acknowledge toggle.
Refresh: Live (alarm event subscription only)
Monitor Tag Rollups
Optional widget — add from the UI Pages editor. 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 UI Pages editor. 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.
Details 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 UI Pages editor. Live power-draw rollup across the device fleet plus a banner for any active OpenADR demand-response events.
Power Rollup: Power devices are discovered generically through the attribute registry — the same metric-role path the Power Flow widget uses. Any driver tagged with power metric roles (Tesla Powerwall, Enphase, Fronius, SolarEdge, Sense, Cordex and others) contributes its live load (site demand) automatically, with no per-driver wiring here; when a driver doesn't report load directly it's derived from grid + solar + battery. All values are normalized to kW.
The hero shows total kW, the body lists every reporting device with 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 is tracking a demand-response event — including a scheduled event that has not reached its start time yet — 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 UI Pages editor. 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 (details 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
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 |
| Alarm Feed | Live (subscription only) |
| 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 each widget's config block from the UI Pages editor — for example, setting refresh_ms to 60000 on the Active Zones widget 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 |