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

8
ui/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"hasInstallScript": true,
"dependencies": {
"@cameleer/design-system": "^0.1.54",
"@cameleer/design-system": "^0.1.55",
"@tanstack/react-query": "^5.90.21",
"js-yaml": "^4.1.1",
"lucide-react": "^1.7.0",
@@ -281,9 +281,9 @@
}
},
"node_modules/@cameleer/design-system": {
"version": "0.1.54",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.54/design-system-0.1.54.tgz",
"integrity": "sha512-IX05JmY/JcxTndfDWBHF7uizrRSqJgEM/J5uv5vQerM+Zq02yUzVNcV4QufVYBevGdnI4acUScnDlmSOOb85Qg==",
"version": "0.1.55",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.55/design-system-0.1.55.tgz",
"integrity": "sha512-IdieQoRYnvxCWdj6PKJ6hnPmvTFlReWrimJcQ97URCv0bzkCaW9zJvXWISQ/r9b9kAKnz8xQ2HUNrJPBfYtw9g==",
"dependencies": {
"lucide-react": "^1.7.0",
"react": "^19.0.0",

View File

@@ -15,7 +15,7 @@
"postinstall": "node -e \"const fs=require('fs');fs.mkdirSync('public',{recursive:true});fs.copyFileSync('node_modules/@cameleer/design-system/assets/cameleer-logo.svg','public/favicon.svg')\""
},
"dependencies": {
"@cameleer/design-system": "^0.1.54",
"@cameleer/design-system": "^0.1.55",
"@tanstack/react-query": "^5.90.21",
"js-yaml": "^4.1.1",
"lucide-react": "^1.7.0",

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 {