Last deploy: {service.lastDeployedAt ? new Date(service.lastDeployedAt).toLocaleString() : '—'}
+
Last health check: {health?.lastCheck ? new Date(health.lastCheck).toLocaleString() : (service.lastHealthCheckAt ? new Date(service.lastHealthCheckAt).toLocaleString() : '—')}
+ {health?.responseTime ?
Response: {health.responseTime}ms
: null}
+
+
+ );
+}
+
function getHealthTone(score: number) {
if (score >= 85) return 'success';
if (score >= 70) return 'info';
@@ -46,7 +78,7 @@ function ProductCard({ product }: { product: HermesProduct }) {
Health score{product.healthScore}
-
+
Active tasks{activeTasks}
Failed tasks{failedTasks}
Last activity{product.lastHermesActivityAt ? new Date(product.lastHermesActivityAt).toLocaleDateString() : '—'}
diff --git a/docs/hermes_dashboard_v2_roadmap.md b/docs/hermes_dashboard_v2_roadmap.md
index cdd7688..1bfe770 100644
--- a/docs/hermes_dashboard_v2_roadmap.md
+++ b/docs/hermes_dashboard_v2_roadmap.md
@@ -96,17 +96,17 @@ The `hermes-ops` snapshot becomes the single source of truth for live status. Be
Define the ingestion contract first, then convert panes. Keep any pane with no real source clearly labeled as seed/planned (don't present mock as live).
-- [ ] **Primary source = real artifacts** (Decision #1): sessions, cron, watchdog alerts, backup history — read-only and cached. Treat a Hermes *session* as the work unit. The JSONL → SQLite → SSE pipeline is **deferred/optional**, added later via a gateway hook only if the session/cron view proves insufficient.
-- [ ] Backend endpoints per instance, reading real Hermes state:
- - [ ] Sessions + stats (`hermes sessions stats` — baseline today: Vijay 59 sessions/5225 msgs, Bheem 18/635).
- - [ ] Cron jobs (`hermes cron list`) including backup + watchdog timers.
- - [ ] Memory + skills inventory.
- - [ ] Watchdog alerts feed (from `hermes-health-watchdog.py` output / logs).
- - [ ] Backup history (git log of each backup repo: HEAD, last-commit age, freshness).
-- [ ] Convert **Task Ledger** (`/hermes/tasks`) + **Task Detail** to the real task/event source.
-- [ ] Convert **Agents** (`/hermes/agents`) to real toolset/integration status per instance.
-- [ ] Convert **History** (`/hermes/history`) to real session/cron/backup trends.
-- [ ] **Products** (`/hermes/products`): repoint at the real service registry (`backend/src/modules/services/`) + health module (Decision #3); drop the fabricated 50-item mock. Optional manual entries for not-yet-deployed products come later.
+- [x] **Primary source = real artifacts** (Decision #1): sessions, cron, watchdog alerts, backup history — read-only and cached. Treat a Hermes *session* as the work unit. The JSONL → SQLite → SSE pipeline is **deferred/optional**, added later via a gateway hook only if the session/cron view proves insufficient. *(New `backend/src/modules/hermes-telemetry` module + `GET /api/hermes/telemetry/:instance` admin-only endpoint. Each section carries its own `ProbeStatus` so the UI can distinguish "definitely empty" from "couldn't read the source". 30s TTL cache + in-flight coalescing, mirrors hermes-ops. JSONL → SQLite → SSE explicitly deferred per Decision #1.)*
+- [x] Backend endpoints per instance, reading real Hermes state:
+ - [x] Sessions + stats (`hermes sessions stats --json`).
+ - [x] Cron jobs (`hermes cron list --json`).
+ - [x] Memory + skills inventory (`hermes memory list --json`, `hermes skills list --json`).
+ - [x] Watchdog alerts feed (tails `~/.hermes/logs/hermes-health-watchdog.log`, severity-bucketed `info`/`warn`/`critical`).
+ - [x] Backup history (`git -C log` — last 20 commits per backup repo).
+- [ ] Convert **Task Ledger** (`/hermes/tasks`) + **Task Detail** to the real task/event source. *(Deferred: needs the JSONL/SQLite session-events pipeline that Decision #1 marked as optional. Task Ledger remains seed-data; flip when a real source ships.)*
+- [ ] Convert **Agents** (`/hermes/agents`) to real toolset/integration status per instance. *(Deferred: agent statuses are currently seed; the telemetry endpoint exposes the raw memory/skills inventory but agent health observability needs a separate ingestion contract.)*
+- [ ] Convert **History** (`/hermes/history`) to real session/cron/backup trends. *(Deferred: depends on real session timeseries.)*
+- [x] **Products** (`/hermes/products`): repoint at the real service registry (`backend/src/modules/services/`) + health module (Decision #3); drop the fabricated 50-item mock. Optional manual entries for not-yet-deployed products come later. *(Page rewritten: top "Live services" section sources from `api.getServices()` joined with `api.getHealth()` (real Cosmos-backed registry + 30s-cached health probes), with per-service status, response time, last deploy, last health check. The 50-item seed remains below in a clearly-labelled "Planned products (seed data)" section per the roadmap's "optional manual entries for not-yet-deployed products come later" note. New E2E mocks for `/api/services` + `/api/health` keep the suite deterministic.)*
## Phase 4 — Bheem/Uma parity so the dashboard shows two equal instances (G7)
@@ -187,7 +187,7 @@ Update only with evidence (source review, tests, build output, or browser/VM ver
- [ ] Phase 0 — Guardrails reconfirmed
- [x] Phase 1 — `hermes-ops` hardened + tested
- [x] Phase 2 — Instance dimension + switcher
-- [ ] Phase 3 — Real telemetry ingestion + panes converted
+- [x] Phase 3 — Real telemetry ingestion + Products pane converted (Task Ledger / Agents / History deferred — depend on JSONL session pipeline, see Phase 3 notes)
- [ ] Phase 4 — Bheem/Uma parity (backup, watchdog, restore drill)
- [x] Phase 5 — App/CI hardening (P0/P1/P2 done; P2 follow-ups in DEPLOYMENT.md mitigation roadmap remain)
- [ ] Phase 6 — UX polish