Files
awoooi/apps/web/src/components/genui/MetricsSummaryCard.tsx
OG T 4d46e6b9a7
Some checks failed
E2E Health Check / e2e-health (push) Successful in 17s
CD Pipeline / build-and-deploy (push) Has been cancelled
style(web): 全站 font-mono → font-body (DM Mono 設計系統套用)
45 個 component + 6 個 page 統一從舊 font-mono 遷移到
font-body (DM Mono),確保設計系統一致性。

font-body = DM Mono (等寬),視覺效果相同但走新設計 token。
保留: font-heading (Syne)、font-dot-matrix (VT323/DSEG7)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 09:37:03 +08:00

152 lines
5.0 KiB
TypeScript

/**
* MetricsSummaryCard - SignOz Metrics Summary
* ============================================
* Phase 19.4a - GenUI 指標摘要卡
*
* 顯示 SignOz 即時指標:
* - RPS (Requests Per Second)
* - Error Rate
* - P99 Latency
*
* @see ADR-032 GenUI Dynamic Rendering
* @author Claude Code (首席架構師)
* @version 1.0.0
* @date 2026-03-28 (台北時間)
*/
import React from 'react'
import { Activity, AlertTriangle, Clock, TrendingUp } from 'lucide-react'
interface MetricsSummaryCardProps {
data: {
rps?: number
errorRate?: string
p99Latency?: string
status?: 'healthy' | 'warning' | 'critical'
// Legacy props (fallback)
cpu?: number
memory?: number
pods?: { running: number; total: number }
}
}
export const MetricsSummaryCard: React.FC<MetricsSummaryCardProps> = ({ data }) => {
const status = data.status || 'healthy'
const statusColors = {
healthy: 'border-green-500 bg-green-50',
warning: 'border-yellow-500 bg-yellow-50',
critical: 'border-red-500 bg-red-50',
}
const statusTextColors = {
healthy: 'text-green-600',
warning: 'text-yellow-600',
critical: 'text-red-600',
}
return (
<div className={`w-full max-w-2xl border-2 ${statusColors[status]} shadow-lg rounded-sm overflow-hidden mt-4 mb-2`}>
{/* Header */}
<div className="flex border-b-2 border-inherit bg-white/50 p-3 items-center justify-between">
<div className="flex items-center gap-2">
<Activity className={statusTextColors[status]} size={20} />
<span className="font-['VT323'] text-xl font-bold uppercase tracking-wider text-nothing-black">
SignOz Metrics // Real-time
</span>
</div>
<div className={`px-2 py-1 text-xs font-body font-bold border-2 ${statusTextColors[status]} border-current uppercase`}>
{status}
</div>
</div>
{/* Metrics Grid */}
<div className="p-4 grid grid-cols-3 gap-4 bg-white/80">
{/* RPS */}
<MetricBox
icon={<TrendingUp size={16} />}
label="RPS"
value={data.rps !== undefined ? `${data.rps}` : '-'}
unit="req/s"
/>
{/* Error Rate */}
<MetricBox
icon={<AlertTriangle size={16} />}
label="Error Rate"
value={data.errorRate || '-'}
unit=""
highlight={data.errorRate ? parseFloat(data.errorRate) > 5 : false}
/>
{/* P99 Latency */}
<MetricBox
icon={<Clock size={16} />}
label="P99 Latency"
value={data.p99Latency || '-'}
unit=""
/>
</div>
{/* Legacy CPU/Memory display (fallback) */}
{(data.cpu !== undefined || data.memory !== undefined) && (
<div className="p-4 pt-0 grid grid-cols-2 gap-4 bg-white/80">
{data.cpu !== undefined && (
<div className="flex items-center gap-2">
<span className="text-xs font-body text-gray-500">CPU:</span>
<div className="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full ${data.cpu > 80 ? 'bg-red-500' : data.cpu > 60 ? 'bg-yellow-500' : 'bg-green-500'}`}
style={{ width: `${data.cpu}%` }}
/>
</div>
<span className="text-xs font-body font-bold">{data.cpu}%</span>
</div>
)}
{data.memory !== undefined && (
<div className="flex items-center gap-2">
<span className="text-xs font-body text-gray-500">MEM:</span>
<div className="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full ${data.memory > 80 ? 'bg-red-500' : data.memory > 60 ? 'bg-yellow-500' : 'bg-green-500'}`}
style={{ width: `${data.memory}%` }}
/>
</div>
<span className="text-xs font-body font-bold">{data.memory}%</span>
</div>
)}
</div>
)}
{/* Pods status */}
{data.pods && (
<div className="px-4 pb-4 bg-white/80">
<div className="text-xs font-body text-gray-500">
Pods: <span className="font-bold text-nothing-black">{data.pods.running}/{data.pods.total}</span> running
</div>
</div>
)}
</div>
)
}
// Helper component for metric display
const MetricBox: React.FC<{
icon: React.ReactNode
label: string
value: string
unit: string
highlight?: boolean
}> = ({ icon, label, value, unit, highlight }) => (
<div className={`text-center p-3 rounded-sm border ${highlight ? 'border-red-300 bg-red-50' : 'border-gray-200 bg-gray-50'}`}>
<div className="flex items-center justify-center gap-1 text-gray-500 mb-1">
{icon}
<span className="text-xs font-body uppercase">{label}</span>
</div>
<div className={`font-['VT323'] text-2xl ${highlight ? 'text-red-600' : 'text-nothing-black'}`}>
{value}
{unit && <span className="text-sm text-gray-400 ml-1">{unit}</span>}
</div>
</div>
)