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>
152 lines
5.0 KiB
TypeScript
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>
|
|
)
|