Add three-state connection-status widget to UI dashboard #10

Closed
opened 2026-04-19 21:17:50 +00:00 by mahmoud · 4 comments
Owner

Context

The dashboard only renders a textual service_state field. Every Hero admin UI ships the standard three-state connection-status badge (green / yellow / red), polling rpc.health at a configurable interval. See hero_ui_connection_status.

Goals

  • Add the widget HTML + CSS + JS to the dashboard template.
  • Poll /<prefix>/rpc/ (rpc.health) every 5s.
  • States: connected (green) on OK, degraded (yellow) on slow/partial response, disconnected (red) on error or timeout.
  • Reflect state via aria-live polite region for accessibility.
  • Works under hero_router prefix (X-Forwarded-Prefix).

Related skills: hero_ui_connection_status, hero_ui_dashboard.

## Context The dashboard only renders a textual `service_state` field. Every Hero admin UI ships the standard three-state connection-status badge (green / yellow / red), polling `rpc.health` at a configurable interval. See `hero_ui_connection_status`. ## Goals - Add the widget HTML + CSS + JS to the dashboard template. - Poll `/<prefix>/rpc/` (`rpc.health`) every 5s. - States: connected (green) on OK, degraded (yellow) on slow/partial response, disconnected (red) on error or timeout. - Reflect state via `aria-live` polite region for accessibility. - Works under hero_router prefix (`X-Forwarded-Prefix`). Related skills: `hero_ui_connection_status`, `hero_ui_dashboard`.
Member

Implementation Spec for Issue #10

Objective

Add a reusable three-state connection-status widget to the hero_livekit UI dashboard. The widget displays a colored dot (green/yellow/red) in the navbar that polls the backend health every 5 seconds, with a clickable Bootstrap popover showing detailed status information.

Requirements

  • Copy the canonical connection-status.js from hero_embedder_ui (the shared Hero dashboard pattern)
  • Add CSS classes for the three connection states: connected (green), backend-down (yellow), disconnected (red) with pulse animation
  • Add aria-live="polite" wrapper for accessibility
  • Load connection-status.js in the base template so it is available on all pages
  • Initialize the poller in dashboard.js with proper options (healthUrl, pingFn, dot/label IDs)
  • Update the /health endpoint to return JSON instead of plain text for richer status data

Files to Modify/Create

  • crates/hero_livekit_ui/static/js/connection-status.js - New file: copy from hero_embedder_ui canonical widget
  • crates/hero_livekit_ui/static/css/dashboard.css - Add connection status classes (.disconnected, .backend-down, pulse animation)
  • crates/hero_livekit_ui/templates/index.html - Add aria-live wrapper around status elements
  • crates/hero_livekit_ui/templates/base.html - Add script tag for connection-status.js
  • crates/hero_livekit_ui/static/js/dashboard.js - Initialize startConnectionStatusPoller() and remove manual setServiceState polling
  • crates/hero_livekit_ui/src/main.rs - Update /health endpoint to return JSON

Implementation Plan

Step 1: Copy connection-status.js

Files: crates/hero_livekit_ui/static/js/connection-status.js

  • Copy the canonical IIFE from hero_embedder_ui verbatim
  • This provides window.startConnectionStatusPoller(options) with health polling, Bootstrap popover, visibility-change handling
    Dependencies: none

Step 2: Update dashboard.css

Files: crates/hero_livekit_ui/static/css/dashboard.css

  • Add .status-dot.disconnected class (red background with pulse animation)
  • Add .status-dot.backend-down class (yellow/amber background)
  • Add @keyframes pulse-red animation for disconnected state
  • Keep existing .running, .stopped, .unhealthy classes for backward compat with room status dots
    Dependencies: none

Step 3: Update index.html

Files: crates/hero_livekit_ui/templates/index.html

  • Wrap the #status-dot and #status-label elements in a <span aria-live="polite"> container
    Dependencies: none

Step 4: Update base.html

Files: crates/hero_livekit_ui/templates/base.html

  • Add <script src="{{ base_path }}/static/js/connection-status.js"></script> before the {% block scripts %} tag
    Dependencies: Step 1

Step 5: Update dashboard.js

Files: crates/hero_livekit_ui/static/js/dashboard.js

  • Remove the manual setServiceState() function and its calls
  • Add startConnectionStatusPoller() initialization with:
    • healthUrl: BASE + '/health'
    • pingFn: () => rpcOnce('LiveKitService.status', {})
    • dotId: 'status-dot'
    • labelId: 'status-label'
    • intervalMs: 5000
    • onStateChange: (state, detail) => { ... } to update UI behavior on state changes
      Dependencies: Steps 1, 4

Step 6: Update /health endpoint in main.rs

Files: crates/hero_livekit_ui/src/main.rs

  • Change /health handler from returning plain "ok" to returning JSON: {"service": "hero_livekit_ui", "status": "ok"}
  • Use axum::Json response type
  • Keep /healthz as-is for backward compat (plain text)
    Dependencies: none

Acceptance Criteria

  • Green dot appears when both UI server and backend RPC are reachable
  • Yellow dot with "backend down" label when UI is up but RPC times out
  • Red dot with "disconnected" label when /health is unreachable
  • Clicking the dot opens a Bootstrap popover with detailed status
  • Poller pauses when tab is hidden, resumes when visible
  • aria-live attribute announces state changes to screen readers
  • /health returns JSON with service name and status
  • cargo build succeeds, all existing tests pass

Notes

  • The connection-status.js widget is a canonical Hero dashboard pattern used across hero_embedder_ui and other services
  • The widget uses an IIFE pattern and exposes a single global function for initialization
  • Existing .running/.stopped/.unhealthy CSS classes must be preserved since they are used by room card status dots
  • The setServiceState() function in dashboard.js can be fully replaced by the connection-status poller's onStateChange callback
## Implementation Spec for Issue #10 ### Objective Add a reusable three-state connection-status widget to the hero_livekit UI dashboard. The widget displays a colored dot (green/yellow/red) in the navbar that polls the backend health every 5 seconds, with a clickable Bootstrap popover showing detailed status information. ### Requirements - Copy the canonical `connection-status.js` from hero_embedder_ui (the shared Hero dashboard pattern) - Add CSS classes for the three connection states: connected (green), backend-down (yellow), disconnected (red) with pulse animation - Add `aria-live="polite"` wrapper for accessibility - Load connection-status.js in the base template so it is available on all pages - Initialize the poller in dashboard.js with proper options (healthUrl, pingFn, dot/label IDs) - Update the `/health` endpoint to return JSON instead of plain text for richer status data ### Files to Modify/Create - `crates/hero_livekit_ui/static/js/connection-status.js` - New file: copy from hero_embedder_ui canonical widget - `crates/hero_livekit_ui/static/css/dashboard.css` - Add connection status classes (.disconnected, .backend-down, pulse animation) - `crates/hero_livekit_ui/templates/index.html` - Add aria-live wrapper around status elements - `crates/hero_livekit_ui/templates/base.html` - Add script tag for connection-status.js - `crates/hero_livekit_ui/static/js/dashboard.js` - Initialize startConnectionStatusPoller() and remove manual setServiceState polling - `crates/hero_livekit_ui/src/main.rs` - Update /health endpoint to return JSON ### Implementation Plan #### Step 1: Copy connection-status.js Files: `crates/hero_livekit_ui/static/js/connection-status.js` - Copy the canonical IIFE from hero_embedder_ui verbatim - This provides `window.startConnectionStatusPoller(options)` with health polling, Bootstrap popover, visibility-change handling Dependencies: none #### Step 2: Update dashboard.css Files: `crates/hero_livekit_ui/static/css/dashboard.css` - Add `.status-dot.disconnected` class (red background with pulse animation) - Add `.status-dot.backend-down` class (yellow/amber background) - Add `@keyframes pulse-red` animation for disconnected state - Keep existing `.running`, `.stopped`, `.unhealthy` classes for backward compat with room status dots Dependencies: none #### Step 3: Update index.html Files: `crates/hero_livekit_ui/templates/index.html` - Wrap the `#status-dot` and `#status-label` elements in a `<span aria-live="polite">` container Dependencies: none #### Step 4: Update base.html Files: `crates/hero_livekit_ui/templates/base.html` - Add `<script src="{{ base_path }}/static/js/connection-status.js"></script>` before the `{% block scripts %}` tag Dependencies: Step 1 #### Step 5: Update dashboard.js Files: `crates/hero_livekit_ui/static/js/dashboard.js` - Remove the manual `setServiceState()` function and its calls - Add `startConnectionStatusPoller()` initialization with: - `healthUrl: BASE + '/health'` - `pingFn: () => rpcOnce('LiveKitService.status', {})` - `dotId: 'status-dot'` - `labelId: 'status-label'` - `intervalMs: 5000` - `onStateChange: (state, detail) => { ... }` to update UI behavior on state changes Dependencies: Steps 1, 4 #### Step 6: Update /health endpoint in main.rs Files: `crates/hero_livekit_ui/src/main.rs` - Change `/health` handler from returning plain `"ok"` to returning JSON: `{"service": "hero_livekit_ui", "status": "ok"}` - Use `axum::Json` response type - Keep `/healthz` as-is for backward compat (plain text) Dependencies: none ### Acceptance Criteria - [ ] Green dot appears when both UI server and backend RPC are reachable - [ ] Yellow dot with "backend down" label when UI is up but RPC times out - [ ] Red dot with "disconnected" label when /health is unreachable - [ ] Clicking the dot opens a Bootstrap popover with detailed status - [ ] Poller pauses when tab is hidden, resumes when visible - [ ] aria-live attribute announces state changes to screen readers - [ ] /health returns JSON with service name and status - [ ] cargo build succeeds, all existing tests pass ### Notes - The connection-status.js widget is a canonical Hero dashboard pattern used across hero_embedder_ui and other services - The widget uses an IIFE pattern and exposes a single global function for initialization - Existing `.running/.stopped/.unhealthy` CSS classes must be preserved since they are used by room card status dots - The `setServiceState()` function in dashboard.js can be fully replaced by the connection-status poller's `onStateChange` callback
Member

Test Results

  • Total: 34
  • Passed: 34
  • Failed: 0
  • Ignored: 4 (doc-tests)

All tests pass. cargo build and cargo test both succeed with no errors or warnings.

## Test Results - Total: 34 - Passed: 34 - Failed: 0 - Ignored: 4 (doc-tests) All tests pass. cargo build and cargo test both succeed with no errors or warnings.
Member

Implementation Summary

Changes Made

New file:

  • crates/hero_livekit_ui/static/js/connection-status.js -- Canonical three-state connection-status widget (copied from hero_embedder_ui). Provides startConnectionStatusPoller() with health polling, Bootstrap popover, and visibility-change handling.

Modified files:

  • crates/hero_livekit_ui/static/css/dashboard.css -- Added .disconnected (red + pulse animation) and .backend-down (yellow) status-dot classes. Existing .running/.stopped/.unhealthy classes preserved for room card dots.
  • crates/hero_livekit_ui/templates/index.html -- Wrapped status-dot and status-label in aria-live="polite" container for screen reader accessibility.
  • crates/hero_livekit_ui/templates/base.html -- Added <script> tag for connection-status.js before page-specific scripts block.
  • crates/hero_livekit_ui/static/js/dashboard.js -- Removed manual setServiceState()/runStatus() functions. Added startConnectionStatusPoller() initialization with healthUrl, pingFn, and 5-second polling interval. Updated refreshAll() to trigger connection-status recheck.
  • crates/hero_livekit_ui/src/main.rs -- Changed /health endpoint from plain text "ok" to JSON {"service":"hero_livekit_ui","status":"ok"}. /healthz unchanged for backward compat.

Test Results

All 34 tests pass, 0 failures.

## Implementation Summary ### Changes Made **New file:** - `crates/hero_livekit_ui/static/js/connection-status.js` -- Canonical three-state connection-status widget (copied from hero_embedder_ui). Provides `startConnectionStatusPoller()` with health polling, Bootstrap popover, and visibility-change handling. **Modified files:** - `crates/hero_livekit_ui/static/css/dashboard.css` -- Added `.disconnected` (red + pulse animation) and `.backend-down` (yellow) status-dot classes. Existing `.running/.stopped/.unhealthy` classes preserved for room card dots. - `crates/hero_livekit_ui/templates/index.html` -- Wrapped status-dot and status-label in `aria-live="polite"` container for screen reader accessibility. - `crates/hero_livekit_ui/templates/base.html` -- Added `<script>` tag for connection-status.js before page-specific scripts block. - `crates/hero_livekit_ui/static/js/dashboard.js` -- Removed manual `setServiceState()`/`runStatus()` functions. Added `startConnectionStatusPoller()` initialization with healthUrl, pingFn, and 5-second polling interval. Updated `refreshAll()` to trigger connection-status recheck. - `crates/hero_livekit_ui/src/main.rs` -- Changed `/health` endpoint from plain text "ok" to JSON `{"service":"hero_livekit_ui","status":"ok"}`. `/healthz` unchanged for backward compat. ### Test Results All 34 tests pass, 0 failures.
Member

Pull request opened: #19

This PR implements the changes discussed in this issue.

Pull request opened: https://forge.ourworld.tf/lhumina_code/hero_livekit/pulls/19 This PR implements the changes discussed in this issue.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_livekit#10
No description provided.