diff --git a/ui/src/pages/AgentHealth/AgentHealth.tsx b/ui/src/pages/AgentHealth/AgentHealth.tsx
index c7bcfcbe..0087b644 100644
--- a/ui/src/pages/AgentHealth/AgentHealth.tsx
+++ b/ui/src/pages/AgentHealth/AgentHealth.tsx
@@ -80,6 +80,16 @@ function appHealth(group: AppGroup): 'success' | 'warning' | 'error' {
return 'success';
}
+function latestHeartbeat(group: AppGroup): string | undefined {
+ let latest: string | undefined;
+ for (const inst of group.instances) {
+ if (inst.lastHeartbeat && (!latest || inst.lastHeartbeat > latest)) {
+ latest = inst.lastHeartbeat;
+ }
+ }
+ return latest;
+}
+
// ── Detail sub-components ────────────────────────────────────────────────────
const LOG_LEVEL_ITEMS: ButtonGroupItem[] = [
@@ -96,6 +106,40 @@ const LOG_SOURCE_ITEMS: ButtonGroupItem[] = [
{ value: 'container', label: 'Container' },
];
+function CompactAppCard({ group, onExpand }: { group: AppGroup; onExpand: () => void }) {
+ const health = appHealth(group);
+ const heartbeat = latestHeartbeat(group);
+ const isHealthy = health === 'success';
+
+ const variantClass =
+ health === 'success' ? styles.compactCardSuccess
+ : health === 'warning' ? styles.compactCardWarning
+ : styles.compactCardError;
+
+ return (
+
{ if (e.key === 'Enter' || e.key === ' ') onExpand(); }}
+ >
+
+ {group.appId}
+
+
+
+
+ {group.liveCount}/{group.instances.length} live
+
+
+ {heartbeat ? timeAgo(heartbeat) : '\u2014'}
+
+
+
+ );
+}
+
// ── AgentHealth page ─────────────────────────────────────────────────────────
export default function AgentHealth() {
@@ -550,60 +594,135 @@ export default function AgentHealth() {
{/* Group cards grid */}
-
- {groups.map((group) => (
-
- }
- meta={
-
- {group.totalTps.toFixed(1)} msg/s
- {group.totalActiveRoutes}/{group.totalRoutes} routes
-
-
-
-
- }
- footer={
- group.deadCount > 0 ? (
-
-
⚠
+ {viewMode === 'expanded' || isFullWidth ? (
+
+ {groups.map((group) => (
+
+ }
+ meta={
+
+ {group.totalTps.toFixed(1)} msg/s
+ {group.totalActiveRoutes}/{group.totalRoutes} routes
- Single point of failure —{' '}
- {group.deadCount === group.instances.length
- ? 'no redundancy'
- : `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
+
- ) : undefined
- }
- >
-
- columns={instanceColumns as Column[]}
- data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
- onRowClick={handleInstanceClick}
- pageSize={50}
- flush
- />
-
- ))}
-
+ }
+ footer={
+ group.deadCount > 0 ? (
+
+ ⚠
+
+ Single point of failure —{' '}
+ {group.deadCount === group.instances.length
+ ? 'no redundancy'
+ : `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
+
+
+ ) : undefined
+ }
+ >
+
+ columns={instanceColumns as Column[]}
+ data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
+ onRowClick={handleInstanceClick}
+ pageSize={50}
+ flush
+ />
+
+ ))}
+
+ ) : (
+
+ {groups.map((group) =>
+ expandedApps.has(group.appId) ? (
+
+
+
+
+
+ }
+ meta={
+
+ {group.totalTps.toFixed(1)} msg/s
+ {group.totalActiveRoutes}/{group.totalRoutes} routes
+
+
+
+
+ }
+ footer={
+ group.deadCount > 0 ? (
+
+ ⚠
+
+ Single point of failure —{' '}
+ {group.deadCount === group.instances.length
+ ? 'no redundancy'
+ : `${group.deadCount} dead instance${group.deadCount > 1 ? 's' : ''}`}
+
+
+ ) : undefined
+ }
+ >
+
+ columns={instanceColumns as Column[]}
+ data={group.instances.map(i => ({ ...i, id: i.instanceId }))}
+ onRowClick={handleInstanceClick}
+ pageSize={50}
+ flush
+ />
+
+
+ ) : (
+
toggleAppExpanded(group.appId)}
+ />
+ ),
+ )}
+
+ )}
{/* Log + Timeline side by side */}