Add URL-driven Agent Health page (/agents, /agents/:appId, /agents/:appId/:instanceId) that progressively narrows from all applications to a single instance with trend charts. Create generic GroupCard composite for grouping instances by application. Expand mock data to 8 instances across 4 apps with varied states. Split sidebar Agents header into navigable link + collapse chevron. Update agent tree paths to /agents/:appId/:instanceId. Add EventFeed with lifecycle events. Change SidebarAgent.tps from string to number. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
Agent Health Page — Progressive Filtering
Context
The Cameleer3 sidebar now has an "Agents" section with a tree of applications and their running instances. Clicking the "Agents" section header should navigate to a full Agent Health page showing all applications and their instances. Clicking an app in the tree narrows to that app's instances. Clicking an instance narrows to that single instance with charts.
The page follows the mockup at ui-mocks/mock-v3-agent-health.html: stat strip at top, application group cards in a 2-column grid, instance rows within each card, and an EventFeed at the bottom.
Domain Model
- Application (e.g., "order-service") = logical grouping for exchange executions
- Agent = the application's running binary code (synonymous with "application")
- Instance (e.g., "prod-1") = a running copy of the agent on a specific server
Routing
One page component handles three URL levels:
| URL | Scope | What shows |
|---|---|---|
/agents |
All | All applications, all instances |
/agents/:appId |
App | One application's instances (full-width card) |
/agents/:appId/:instanceId |
Instance | Single instance with expanded charts |
React Router config: replace both /agents and /agents/:id routes with a single <Route path="/agents/*" element={<AgentHealth />} />. Remove the AgentDetail import and route (the AgentHealth page now handles all /agents/* paths). The AgentDetail.tsx stub page becomes dead code and should be deleted.
URL parsing in the component:
const { '*': rest } = useParams()
const segments = rest?.split('/').filter(Boolean) ?? []
// segments.length === 0 → all
// segments.length === 1 → appId = segments[0]
// segments.length === 2 → appId = segments[0], instanceId = segments[1]
Sidebar Changes
Section header split
The "Agents" section header becomes both a navigation link and a collapse toggle:
- The text "Agents" is a
<Link to="/agents">(proper anchor semantics for right-click, screen readers) - The chevron (right side) is a
<button>that collapses/expands the tree - Both targets are on the same row, distinct click zones
The "Applications" section header remains collapse-only for now (no link).
Agent tree paths
buildAgentTreeNodes must update instance paths from /agents/${agent.id} to /agents/${app.id}/${agent.id}. App-level agent tree nodes keep /agents/${app.id}.
New Component: GroupCard
A generic composite component for the design system. Usable for agent groups now, application groups later.
Location: src/design-system/composites/GroupCard/
Props
interface GroupCardProps {
title: string
titleMono?: boolean // monospace title font (default: true)
headerRight?: ReactNode // right side of header (e.g., instance count badge)
meta?: ReactNode // aggregated stats row below header
footer?: ReactNode // bottom section (e.g., alert banner)
accent?: 'success' | 'warning' | 'error'
onClick?: () => void // optional click on header
className?: string
children: ReactNode // instance rows or any content
}
Structure
┌─────────────────────────────────────────────┐
│ [accent border-left] │
│ HEADER: title (mono) ····· headerRight │ ← bg-raised, border-bottom
├─────────────────────────────────────────────┤
│ META: aggregated stats row (optional) │ ← border-bottom
├─────────────────────────────────────────────┤
│ CHILDREN: instance rows │
├─────────────────────────────────────────────┤
│ FOOTER: alert banner (optional) │ ← colored bg
└─────────────────────────────────────────────┘
Styling
- Card chrome:
var(--bg-surface),var(--border-subtle),var(--shadow-card),var(--radius-lg) - Header:
var(--bg-raised)background, title invar(--font-mono)14px 600 - Accent: 3px left border in accent color (similar mechanism to Card, but positioned on the left edge instead of Card's top border)
- Hover:
var(--shadow-md)transition
Inventory
Add GroupCard to the Inventory composites section with a demo.
Page Layout
Breadcrumb
Updates per scope level:
/agents→System > Agents/agents/:appId→System > Agents > order-service/agents/:appId/:instanceId→System > Agents > order-service > prod-1
Stat Strip (top)
6 StatCard components in a row grid. Values recalculate based on scope:
| Card | All scope | App scope | Instance scope |
|---|---|---|---|
| Total Instances | count all | count app's | 1 |
| Live | live count | app's live | status badge |
| Stale | stale count | app's stale | — |
| Dead | dead count | app's dead | — |
| Total TPS | sum all | sum app's | instance TPS |
| Active Routes | sum all | sum app's | instance routes |
Application Group Cards
2-column grid (CSS Grid) of GroupCard components. Each card:
Header: Application name (mono) + instance count badge (e.g., "3 instances") Meta row: Aggregated TPS, total routes, overall health status Instance rows: Grid layout per row:
- Status dot (live/stale/dead)
- Instance name (mono, bold)
- Status badge (LIVE/STALE/DEAD)
- Uptime
- Throughput (msg/s)
- Error rate
- Last heartbeat
Footer (conditional): Alert banner when an app has dead instances ("Single point of failure — no redundancy")
Filtering behavior:
/agents→ all cards in 2-col grid/agents/:appId→ single card, full width, instance rows expandable with charts/agents/:appId/:instanceId→ single card, single instance row expanded with throughput + error rate charts (using LineChart)
Instance Expanded View
When viewing a single instance (/agents/:appId/:instanceId), the card shows:
- Instance header with all metrics
- Two LineChart panels side-by-side: Throughput (msg/s) and Error Rate (err/h)
- Built using the existing
LineChartcomposite with mock trend data
EventFeed (bottom)
Uses the existing EventFeed composite. Shows agent lifecycle events (started, stopped, stale, dead, config changes). Filtered to scope:
/agents→ all events/agents/:appId→ events for that app's instances/agents/:appId/:instanceId→ events for that instance only
Mock data: ~8-10 lifecycle events using new Date(Date.now() - offset) for fresh relative timestamps.
Mock Data Changes
src/mocks/agents.ts
Expand from 4 to ~10 instances across 4 applications. The new data is the source of truth — existing data inconsistencies (e.g., prod-2 appearing under two apps) are overwritten.
Add appId: string field to AgentHealth interface for clean grouping without cross-referencing sidebar data.
Change tps from string to number — format as string at display time. This also requires updating the SidebarAgent interface in Sidebar.tsx from tps: string to tps: number, and the sidebar mock data.
| Application | Instances | Status |
|---|---|---|
| order-service | ord-1, ord-2, ord-3 | All LIVE (ord-3 recently restarted) |
| payment-svc | pay-1, pay-2 | pay-1 LIVE, pay-2 STALE |
| shipment-svc | ship-1, ship-2 | Both LIVE |
| notification-hub | notif-1 | DEAD (single point of failure) |
Each instance: id, name, appId, service, version, tps (number), lastSeen, status, errorRate, uptime, memoryUsagePct, cpuUsagePct, activeRoutes, totalRoutes.
src/mocks/sidebar.ts
Update SIDEBAR_APPS agents arrays to match the expanded agent data. Also update SidebarAgent.tps to number.
src/mocks/agentEvents.ts (new)
Export agentEvents: FeedEvent[] with ~8-10 lifecycle events. Each event includes an appId and optional instanceId for filtering.
Files to Create
| File | Purpose |
|---|---|
src/design-system/composites/GroupCard/GroupCard.tsx |
Generic group card component |
src/design-system/composites/GroupCard/GroupCard.module.css |
GroupCard styles |
src/design-system/composites/GroupCard/GroupCard.test.tsx |
GroupCard tests |
src/mocks/agentEvents.ts |
Agent lifecycle event mock data |
Files to Modify
| File | Changes |
|---|---|
src/pages/AgentHealth/AgentHealth.tsx |
Full rewrite: URL-driven filtering, GroupCard usage, stat recalculation |
src/pages/AgentHealth/AgentHealth.module.css |
Updated styles for group grid, instance rows, expanded charts |
src/design-system/layout/Sidebar/Sidebar.tsx |
Split Agents header (Link + chevron), update agent tree instance paths to /agents/:appId/:instanceId, change SidebarAgent.tps to number |
src/design-system/layout/Sidebar/Sidebar.module.css |
Styles for split section header |
src/mocks/agents.ts |
Expand to ~10 instances, add appId field, change tps to number |
src/mocks/sidebar.ts |
Update SIDEBAR_APPS agents to match, change tps to number |
src/App.tsx |
Replace /agents + /agents/:id with /agents/*, remove AgentDetail import |
src/design-system/composites/index.ts |
Export GroupCard |
src/pages/Inventory/sections/CompositesSection.tsx |
Add GroupCard demo |
COMPONENT_GUIDE.md |
Add GroupCard to component index |
Files to Remove
| File | Reason |
|---|---|
src/pages/AgentDetail/AgentDetail.tsx |
Superseded by AgentHealth handling all /agents/* paths |
Implementation Order
- GroupCard component + styles + tests
- Expand mock data (agents.ts, sidebar.ts, agentEvents.ts)
- Update SidebarAgent.tps to number + sidebar agent tree paths
- Sidebar section header split (text=Link, chevron=button)
- App.tsx route change (
/agents/*), remove AgentDetail - AgentHealth page rewrite with progressive filtering
- Inventory demo for GroupCard
- Barrel exports + COMPONENT_GUIDE.md update
Verification
npx tsc --noEmit— zero errorsnpx vitest run— all tests passnpm run build— clean build- Manual:
/agentsshows all apps in 2-col grid with stat strip + EventFeed - Manual: click app in sidebar Agents tree → page narrows to that app's instances
- Manual: click instance → page narrows to single instance with charts
- Manual: sidebar "Agents" text navigates to
/agents, chevron collapses tree - Manual: breadcrumb updates per scope level
- Manual:
/inventoryshows GroupCard demo