feat: move toggle to toolbar, sort apps by name, overlay expand

- Move expand/collapse toggle from stat strip to dedicated toolbar
  below KPIs
- Sort app groups alphabetically by name
- Expanded card overlays from clicked card position instead of
  pushing other cards down
- Viewport constraint: overlay flips right-alignment and limits
  height when near edges

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-16 13:48:39 +02:00
parent 9f7951aa2b
commit 1fa897fbb5
2 changed files with 125 additions and 82 deletions

View File

@@ -289,7 +289,7 @@ export default function AgentHealth() {
const agentList = agents ?? [];
const groups = useMemo(() => groupByApp(agentList), [agentList]);
const groups = useMemo(() => groupByApp(agentList).sort((a, b) => a.appId.localeCompare(b.appId)), [agentList]);
// Aggregate stats
const totalInstances = agentList.length;
@@ -515,6 +515,10 @@ export default function AgentHealth() {
accent={deadCount > 0 ? 'error' : 'success'}
detail={deadCount > 0 ? 'requires attention' : 'all healthy'}
/>
</div>
{/* View toolbar */}
<div className={styles.viewToolbar}>
<div className={styles.viewToggle}>
<button
className={`${styles.viewToggleBtn} ${viewMode === 'compact' ? styles.viewToggleBtnActive : ''}`}
@@ -702,85 +706,99 @@ export default function AgentHealth() {
</div>
) : (
<div className={styles.compactGrid}>
{groups.map((group) =>
expandedApps.has(group.appId) ? (
<div
key={group.appId}
className={`${styles.compactGridExpanded} ${styles.expandWrapper} ${
animatingApps.get(group.appId) === 'expanding'
? styles.expandWrapperCollapsed
: animatingApps.get(group.appId) === 'collapsing'
? styles.expandWrapperCollapsed
: styles.expandWrapperExpanded
}`}
>
<GroupCard
title={group.appId}
accent={appHealth(group)}
headerRight={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Badge
label={`${group.liveCount}/${group.instances.length} LIVE`}
color={appHealth(group)}
variant="filled"
/>
<button
className={styles.collapseBtn}
onClick={() => animateToggle(group.appId)}
title="Collapse"
>
<ChevronDown size={14} />
</button>
</div>
}
meta={
<div className={styles.groupMeta}>
<span><strong>{group.totalTps.toFixed(1)}</strong> msg/s</span>
<span><strong>{group.totalActiveRoutes}</strong>/{group.totalRoutes} routes</span>
<span>
<StatusDot
variant={
appHealth(group) === 'success'
? 'live'
: appHealth(group) === 'warning'
? 'stale'
: 'dead'
}
/>
</span>
</div>
}
footer={
group.deadCount > 0 ? (
<div className={styles.alertBanner}>
<span className={styles.alertIcon}>&#9888;</span>
<span>
Single point of failure &mdash;{' '}
{group.deadCount === group.instances.length
? 'no redundancy'
: `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
</span>
</div>
) : undefined
}
>
<DataTable<AgentInstance & { id: string }>
columns={instanceColumns as Column<AgentInstance & { id: string }>[]}
data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
onRowClick={handleInstanceClick}
pageSize={50}
flush
/>
</GroupCard>
</div>
) : (
{groups.map((group) => (
<div key={group.appId} className={styles.compactGridCell}>
<CompactAppCard
key={group.appId}
group={group}
onExpand={() => animateToggle(group.appId)}
/>
),
)}
{expandedApps.has(group.appId) && (
<div
ref={(el) => {
if (!el) return;
// Constrain overlay within viewport
const rect = el.getBoundingClientRect();
const vw = document.documentElement.clientWidth;
if (rect.right > vw - 16) {
el.style.left = 'auto';
el.style.right = '0';
}
if (rect.bottom > document.documentElement.clientHeight) {
const overflow = rect.bottom - document.documentElement.clientHeight + 16;
el.style.maxHeight = `${rect.height - overflow}px`;
el.style.overflowY = 'auto';
}
}}
className={`${styles.compactGridExpanded} ${styles.expandWrapper} ${
animatingApps.get(group.appId) === 'expanding'
? styles.expandWrapperCollapsed
: animatingApps.get(group.appId) === 'collapsing'
? styles.expandWrapperCollapsed
: styles.expandWrapperExpanded
}`}
>
<GroupCard
title={group.appId}
accent={appHealth(group)}
headerRight={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Badge
label={`${group.liveCount}/${group.instances.length} LIVE`}
color={appHealth(group)}
variant="filled"
/>
<button
className={styles.collapseBtn}
onClick={() => animateToggle(group.appId)}
title="Collapse"
>
<ChevronDown size={14} />
</button>
</div>
}
meta={
<div className={styles.groupMeta}>
<span><strong>{group.totalTps.toFixed(1)}</strong> msg/s</span>
<span><strong>{group.totalActiveRoutes}</strong>/{group.totalRoutes} routes</span>
<span>
<StatusDot
variant={
appHealth(group) === 'success'
? 'live'
: appHealth(group) === 'warning'
? 'stale'
: 'dead'
}
/>
</span>
</div>
}
footer={
group.deadCount > 0 ? (
<div className={styles.alertBanner}>
<span className={styles.alertIcon}>&#9888;</span>
<span>
Single point of failure &mdash;{' '}
{group.deadCount === group.instances.length
? 'no redundancy'
: `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
</span>
</div>
) : undefined
}
>
<DataTable<AgentInstance & { id: string }>
columns={instanceColumns as Column<AgentInstance & { id: string }>[]}
data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
onRowClick={handleInstanceClick}
pageSize={50}
flush
/>
</GroupCard>
</div>
)}
</div>
))}
</div>
)}