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>
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
/**
|
|
* K8sPodStatusCard - Kubernetes Pod Status
|
|
* =========================================
|
|
* Phase 19.4a - GenUI K8s Pod 狀態卡
|
|
*
|
|
* @see ADR-032 GenUI Dynamic Rendering
|
|
* @author Claude Code (首席架構師)
|
|
* @version 1.0.0
|
|
* @date 2026-03-28 (台北時間)
|
|
*/
|
|
|
|
import React from 'react'
|
|
import { Box, CheckCircle, XCircle, RefreshCw } from 'lucide-react'
|
|
|
|
interface PodInfo {
|
|
name: string
|
|
status: 'Running' | 'Pending' | 'Failed' | 'CrashLoopBackOff' | 'Terminating'
|
|
restarts?: number
|
|
age?: string
|
|
}
|
|
|
|
interface K8sPodStatusCardProps {
|
|
data: {
|
|
namespace: string
|
|
pods: PodInfo[]
|
|
summary?: {
|
|
running: number
|
|
total: number
|
|
}
|
|
}
|
|
}
|
|
|
|
export const K8sPodStatusCard: React.FC<K8sPodStatusCardProps> = ({ data }) => {
|
|
const summary = data.summary || {
|
|
running: data.pods.filter(p => p.status === 'Running').length,
|
|
total: data.pods.length,
|
|
}
|
|
|
|
const isHealthy = summary.running === summary.total
|
|
|
|
const statusIcon = (status: PodInfo['status']) => {
|
|
switch (status) {
|
|
case 'Running':
|
|
return <CheckCircle className="text-green-500" size={14} />
|
|
case 'Pending':
|
|
return <RefreshCw className="text-yellow-500 animate-spin" size={14} />
|
|
case 'Failed':
|
|
case 'CrashLoopBackOff':
|
|
return <XCircle className="text-red-500" size={14} />
|
|
case 'Terminating':
|
|
return <RefreshCw className="text-gray-500" size={14} />
|
|
default:
|
|
return <Box className="text-gray-400" size={14} />
|
|
}
|
|
}
|
|
|
|
const statusColor = (status: PodInfo['status']) => {
|
|
switch (status) {
|
|
case 'Running':
|
|
return 'text-green-600'
|
|
case 'Pending':
|
|
return 'text-yellow-600'
|
|
case 'Failed':
|
|
case 'CrashLoopBackOff':
|
|
return 'text-red-600'
|
|
default:
|
|
return 'text-gray-600'
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={`w-full max-w-2xl bg-white border-2 ${isHealthy ? 'border-green-500' : 'border-yellow-500'} shadow-lg rounded-sm overflow-hidden mt-4 mb-2`}>
|
|
<div className="flex border-b-2 border-inherit bg-nothing-gray-50 p-3 items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Box className={isHealthy ? 'text-green-500' : 'text-yellow-500'} size={20} />
|
|
<span className="font-['VT323'] text-xl font-bold uppercase tracking-wider text-nothing-black">
|
|
K8s Pods // {data.namespace}
|
|
</span>
|
|
</div>
|
|
<div className={`px-2 py-1 text-xs font-body font-bold border-2 ${isHealthy ? 'border-green-500 text-green-600' : 'border-yellow-500 text-yellow-600'}`}>
|
|
{summary.running}/{summary.total} RUNNING
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-4">
|
|
<div className="space-y-2 max-h-48 overflow-y-auto">
|
|
{data.pods.map((pod, i) => (
|
|
<div
|
|
key={i}
|
|
className="flex items-center gap-3 p-2 bg-gray-50 rounded-sm text-sm font-body"
|
|
>
|
|
{statusIcon(pod.status)}
|
|
<span className="flex-1 truncate text-nothing-black">{pod.name}</span>
|
|
<span className={`text-xs ${statusColor(pod.status)}`}>{pod.status}</span>
|
|
{pod.restarts !== undefined && pod.restarts > 0 && (
|
|
<span className="text-xs text-red-500">R:{pod.restarts}</span>
|
|
)}
|
|
{pod.age && (
|
|
<span className="text-xs text-gray-400">{pod.age}</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|