feat: replace text labels with icons in runtime cards
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m20s
CI / docker (push) Successful in 1m38s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 53s

Use Activity, Cpu, and HeartPulse icons instead of "tps", "cpu", and
"ago" text in compact and expanded app cards. Bump design-system to
v0.1.55 for sidebar footer alignment fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-16 15:43:37 +02:00
parent 11ad769f59
commit a1ea112876
5 changed files with 37 additions and 40 deletions

View File

@@ -111,6 +111,12 @@
color: var(--text-muted);
}
.groupMeta > span {
display: inline-flex;
align-items: center;
gap: 4px;
}
.groupMeta strong {
font-family: var(--font-mono);
color: var(--text-secondary);
@@ -319,19 +325,31 @@
.compactCardTps {
font-family: var(--font-mono);
color: var(--text-muted);
display: inline-flex;
align-items: center;
gap: 3px;
}
.compactCardCpu {
font-family: var(--font-mono);
color: var(--text-muted);
display: inline-flex;
align-items: center;
gap: 3px;
}
.compactCardHeartbeat {
color: var(--text-muted);
display: inline-flex;
align-items: center;
gap: 3px;
}
.compactCardHeartbeatWarn {
color: var(--card-accent);
display: inline-flex;
align-items: center;
gap: 3px;
}
/* Wrapper for each compact grid cell — anchor for overlay */

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router';
import { ExternalLink, RefreshCw, Pencil, LayoutGrid, List, ChevronRight, ChevronDown, ArrowUp, ArrowDown, Search } from 'lucide-react';
import { ExternalLink, RefreshCw, Pencil, LayoutGrid, List, ChevronRight, ChevronDown, ArrowUp, ArrowDown, Search, Cpu, HeartPulse, Activity } from 'lucide-react';
import {
StatCard, StatusDot, Badge, MonoText,
GroupCard, DataTable, EventFeed,
@@ -141,15 +141,15 @@ function CompactAppCard({ group, onExpand, onNavigate }: { group: AppGroup; onEx
{group.liveCount}/{group.instances.length} live
</span>
<span className={styles.compactCardTps}>
{group.totalTps.toFixed(1)} tps
<Activity size={10} /> {group.totalTps.toFixed(1)}/s
</span>
{group.maxCpu >= 0 && (
<span className={styles.compactCardCpu}>
{(group.maxCpu * 100).toFixed(0)}% cpu
<Cpu size={10} /> {(group.maxCpu * 100).toFixed(0)}%
</span>
)}
<span className={isHealthy ? styles.compactCardHeartbeat : styles.compactCardHeartbeatWarn}>
{heartbeat ? timeAgo(heartbeat) : '\u2014'}
<HeartPulse size={10} /> {heartbeat ? timeAgo(heartbeat, true) : '\u2014'}
</span>
</div>
</div>
@@ -763,20 +763,9 @@ export default function AgentHealth() {
}
meta={
<div className={styles.groupMeta}>
<span><strong>{group.totalTps.toFixed(1)}</strong> msg/s</span>
<span><Activity size={12} /> <strong>{group.totalTps.toFixed(1)}</strong>/s</span>
<span><strong>{group.totalActiveRoutes}</strong>/{group.totalRoutes} routes</span>
{group.maxCpu >= 0 && <span><strong>{(group.maxCpu * 100).toFixed(0)}%</strong> cpu</span>}
<span>
<StatusDot
variant={
appHealth(group) === 'success'
? 'live'
: appHealth(group) === 'warning'
? 'stale'
: 'dead'
}
/>
</span>
{group.maxCpu >= 0 && <span><Cpu size={12} /> <strong>{(group.maxCpu * 100).toFixed(0)}%</strong></span>}
</div>
}
footer={
@@ -860,20 +849,9 @@ export default function AgentHealth() {
}
meta={
<div className={styles.groupMeta}>
<span><strong>{group.totalTps.toFixed(1)}</strong> msg/s</span>
<span><Activity size={12} /> <strong>{group.totalTps.toFixed(1)}</strong>/s</span>
<span><strong>{group.totalActiveRoutes}</strong>/{group.totalRoutes} routes</span>
{group.maxCpu >= 0 && <span><strong>{(group.maxCpu * 100).toFixed(0)}%</strong> cpu</span>}
<span>
<StatusDot
variant={
appHealth(group) === 'success'
? 'live'
: appHealth(group) === 'warning'
? 'stale'
: 'dead'
}
/>
</span>
{group.maxCpu >= 0 && <span><Cpu size={12} /> <strong>{(group.maxCpu * 100).toFixed(0)}%</strong></span>}
</div>
}
footer={

View File

@@ -24,16 +24,17 @@ export function statusLabel(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}
export function timeAgo(iso?: string): string {
export function timeAgo(iso?: string, short = false): string {
if (!iso) return '\u2014';
const diff = Date.now() - new Date(iso).getTime();
const secs = Math.floor(diff / 1000);
if (secs < 60) return `${secs}s ago`;
const suffix = short ? '' : ' ago';
if (secs < 60) return `${secs}s${suffix}`;
const mins = Math.floor(secs / 60);
if (mins < 60) return `${mins}m ago`;
if (mins < 60) return `${mins}m${suffix}`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
return `${Math.floor(hours / 24)}d ago`;
if (hours < 24) return `${hours}h${suffix}`;
return `${Math.floor(hours / 24)}d${suffix}`;
}
export function formatMetric(value: number, unit: string, decimals = 1): string {