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>
238 lines
11 KiB
Markdown
238 lines
11 KiB
Markdown
# 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:**
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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 in `var(--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 `LineChart` composite 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
|
|
|
|
1. GroupCard component + styles + tests
|
|
2. Expand mock data (agents.ts, sidebar.ts, agentEvents.ts)
|
|
3. Update SidebarAgent.tps to number + sidebar agent tree paths
|
|
4. Sidebar section header split (text=Link, chevron=button)
|
|
5. App.tsx route change (`/agents/*`), remove AgentDetail
|
|
6. AgentHealth page rewrite with progressive filtering
|
|
7. Inventory demo for GroupCard
|
|
8. Barrel exports + COMPONENT_GUIDE.md update
|
|
|
|
## Verification
|
|
|
|
1. `npx tsc --noEmit` — zero errors
|
|
2. `npx vitest run` — all tests pass
|
|
3. `npm run build` — clean build
|
|
4. Manual: `/agents` shows all apps in 2-col grid with stat strip + EventFeed
|
|
5. Manual: click app in sidebar Agents tree → page narrows to that app's instances
|
|
6. Manual: click instance → page narrows to single instance with charts
|
|
7. Manual: sidebar "Agents" text navigates to `/agents`, chevron collapses tree
|
|
8. Manual: breadcrumb updates per scope level
|
|
9. Manual: `/inventory` shows GroupCard demo
|