Files
awoooi/apps/web/src/components/genui/K8sPodStatusCard.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

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>
)
}