fix: align AgentHealth page with mock design
Some checks failed
CI / build (push) Successful in 1m18s
CI / cleanup-branch (push) Has been skipped
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / docker (push) Has been cancelled

- DetailPanel: switch from tabs to flat children layout (fixes stale
  tab state bug), add position:fixed override, key on agent id
- Stat strip: colored status breakdown (live/stale/dead), msg/s detail
  on TPS, "requires attention" on dead count
- Scope trail: simplified to "X/Y live" label
- Event card header: rename "Event Log" to "Timeline" with count badge
- Remove unused Breadcrumb, scopeItems, groupHealth

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-23 21:50:16 +01:00
parent 6b750df1c4
commit a06808a2a2
2 changed files with 60 additions and 45 deletions

View File

@@ -244,3 +244,36 @@
font-size: 12px;
color: var(--text-muted);
}
/* Status breakdown in stat card */
.statusBreakdown {
display: flex;
gap: 8px;
font-size: 11px;
}
.statusLive { color: var(--success); }
.statusStale { color: var(--warning); }
.statusDead { color: var(--error); }
/* Scope trail */
.scopeLabel {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
}
/* DetailPanel override */
.detailPanelOverride {
position: fixed;
top: 0;
right: 0;
height: 100vh;
z-index: 100;
box-shadow: -4px 0 24px rgba(0, 0, 0, 0.12);
}
.panelDivider {
border-top: 1px solid var(--border-subtle);
margin: 16px 0;
}

View File

@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react';
import { useParams, useNavigate } from 'react-router';
import {
StatCard, StatusDot, Badge, MonoText,
GroupCard, EventFeed, Breadcrumb, Alert,
GroupCard, EventFeed, Alert,
DetailPanel, ProgressBar, LineChart,
} from '@cameleer/design-system';
import styles from './AgentHealth.module.css';
@@ -189,24 +189,6 @@ export default function AgentHealth() {
const activeRoutes = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.activeRoutes || 0), 0);
const totalTps = (agents || []).filter((a: any) => a.status === 'LIVE').reduce((sum: number, a: any) => sum + (a.tps || 0), 0);
const groupHealth: 'live' | 'stale' | 'dead' = useMemo(() => {
if (!appId) return 'live';
const groupAgents = agentsByApp[appId] || [];
if (groupAgents.some((a: any) => a.status === 'DEAD')) return 'dead';
if (groupAgents.some((a: any) => a.status === 'STALE')) return 'stale';
return 'live';
}, [appId, agentsByApp]);
const scopeItems = useMemo(() => {
const items: { label: string; href?: string }[] = [
{ label: 'Agent Health', href: '/agents' },
];
if (appId) {
items.push({ label: appId });
}
return items;
}, [appId]);
const feedEvents = useMemo(() =>
(events || []).map((e: any) => ({
id: String(e.id),
@@ -225,22 +207,25 @@ export default function AgentHealth() {
return (
<div>
<div className={styles.statStrip}>
<StatCard label="Total Agents" value={(agents || []).length} detail={`${liveCount} live / ${staleCount} stale / ${deadCount} dead`} />
<StatCard
label="Total Agents"
value={(agents || []).length}
detail={
<span className={styles.statusBreakdown}>
<span className={styles.statusLive}>{liveCount} live</span>
<span className={styles.statusStale}>{staleCount} stale</span>
<span className={styles.statusDead}>{deadCount} dead</span>
</span>
}
/>
<StatCard label="Applications" value={uniqueApps} />
<StatCard label="Active Routes" value={activeRoutes} />
<StatCard label="Total TPS" value={totalTps.toFixed(1)} />
<StatCard label="Dead" value={deadCount} accent={deadCount > 0 ? 'error' : undefined} />
<StatCard label="Total TPS" value={totalTps.toFixed(1)} detail="msg/s" />
<StatCard label="Dead" value={deadCount} accent={deadCount > 0 ? 'error' : undefined} detail={deadCount > 0 ? 'requires attention' : undefined} />
</div>
<div className={styles.scopeTrail}>
<Breadcrumb items={scopeItems} />
{!appId && <Badge label={`${liveCount} live`} variant="outlined" />}
{appId && (
<Badge
label={groupHealth}
color={groupHealth === 'live' ? 'success' : groupHealth === 'stale' ? 'warning' : 'error'}
/>
)}
<span className={styles.scopeLabel}>{liveCount}/{(agents || []).length} live</span>
</div>
<div className={styles.groupGrid}>
@@ -349,29 +334,26 @@ export default function AgentHealth() {
{feedEvents.length > 0 && (
<div className={styles.eventCard}>
<div className={styles.eventCardHeader}>Event Log</div>
<div className={styles.eventCardHeader}>
<span>Timeline</span>
<Badge label={`${feedEvents.length} events`} variant="outlined" />
</div>
<EventFeed events={feedEvents} maxItems={100} />
</div>
)}
{selectedAgent && (
<DetailPanel
open={!!selectedAgent}
key={selectedAgent.id}
open={true}
title={selectedAgent.name ?? selectedAgent.id}
onClose={() => setSelectedAgent(null)}
tabs={[
{
label: 'Overview',
value: 'overview',
content: <AgentOverviewContent agent={selectedAgent} />,
},
{
label: 'Performance',
value: 'performance',
content: <AgentPerformanceContent agent={selectedAgent} />,
},
]}
/>
className={styles.detailPanelOverride}
>
<AgentOverviewContent agent={selectedAgent} />
<div className={styles.panelDivider} />
<AgentPerformanceContent agent={selectedAgent} />
</DetailPanel>
)}
</div>
);