// DeploymentView.tsx — 增强只读详情看板(对标 Kuboard) // 作者: Logan import { useState, Fragment } from 'react'; import { useNavigate } from 'react-router-dom'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Clock, Cpu, MemoryStick, HardDrive, Network, Activity, RotateCcw, FileCode, Server, Container, History, ChevronDown, ChevronRight, } from 'lucide-react'; import { kubernetesApi } from '@/services/api'; import { useToast } from '@/hooks/use-toast'; import { formatK8sTime, formatDuration, elapsedSeconds, statusColor, STATUS_COLORS, } from './k8s-yaml-utils'; interface PodItem { name: string; namespace?: string; status?: string; node_name?: string; node?: string; ip?: string; pod_ip?: string; restart_count?: number; age?: string; created_at?: string; start_time?: string; containers?: any[]; image?: string; images?: string[]; } interface ReplicaSetItem { name: string; namespace?: string; replicas?: number; ready_replicas?: number; available_replicas?: number; image?: string; revision?: number; status?: string; created_at?: string; controlled?: boolean; owner_deployment?: string; } interface DeploymentViewProps { clusterCode: string; namespace: string; deployment: any; pods: PodItem[]; replicaSets: ReplicaSetItem[]; revisions: any[]; onRefresh: () => void; onShowPodLogs: (pod: PodItem) => void; onShowPodTerminal: (pod: PodItem) => void; onShowPodYaml: (pod: PodItem) => void; } /** 计算 Pod 从调度到就绪的各阶段耗时(基于条件时间线,兼容扁平字段) */ function getPodTimeline(pod: PodItem) { const cond: any[] = (pod as any).conditions || (pod as any).pod_conditions || []; const find = (t: string) => cond.find((c: any) => (c.type || c.Type) === t); const scheduled = find('PodScheduled') || {}; const initialized = find('Initialized') || {}; const ready = find('Ready') || {}; const containersReady = find('ContainersReady') || {}; const ts = (c: any) => c?.lastTransitionTime || c?.LastTransitionTime || c?.last_transition_time; return { scheduled: ts(scheduled), initialized: ts(initialized), containersReady: ts(containersReady), ready: ts(ready), startTime: pod.start_time || (pod as any).startTime, }; } function StatCard({ label, value, sub, color }: { label: string; value: React.ReactNode; sub?: string; color?: string }) { return (

{label}

{value}

{sub &&

{sub}

}
); } export function DeploymentView({ clusterCode, namespace, deployment, pods, replicaSets, revisions, onRefresh, onShowPodLogs, onShowPodTerminal, onShowPodYaml, }: DeploymentViewProps) { const navigate = useNavigate(); const { toast } = useToast(); const [expandedPod, setExpandedPod] = useState(null); const [rollbackLoading, setRollbackLoading] = useState(null); const ready = deployment.ready_replicas || deployment.readyReplicas || 0; const total = deployment.replicas || 0; const available = deployment.available_replicas || deployment.availableReplicas || 0; const updated = deployment.updated_replicas || deployment.updatedReplicas || 0; const isHealthy = ready === total && total > 0; const createdAt = deployment.created_at || deployment.creationTimestamp || deployment.created_at; const updatedAt = deployment.updated_at || deployment.last_updated; const handleRollback = async (rev: any) => { const revision = rev.revision ?? rev.revision_number ?? rev.number; if (!confirm(`确认回滚到修订版本 #${revision}?\n镜像: ${rev.image || '-'}`)) return; setRollbackLoading(revision); try { await kubernetesApi.rollbackDeployment(clusterCode, namespace, deployment.name, Number(revision)); toast({ title: '回滚指令已下发', description: `正在回滚到 revision #${revision}` }); onRefresh(); } catch (e: any) { toast({ title: '回滚失败', description: e?.message || String(e), variant: 'destructive' }); } finally { setRollbackLoading(null); } }; const nodeClick = (nodeName: string | undefined) => { if (!nodeName) return; navigate(`/kubernetes/${clusterCode}`); }; return (
{/* ============ 1. 状态总览 ============ */}
{formatK8sTime(createdAt)}} color="#94a3b8" /> {formatK8sTime(updatedAt)}} color="#94a3b8" />
{/* ============ 2. 容器详情(CPU/内存/镜像/端口) ============ */} 容器模板(Pod Template) {/* ============ 3. Pod 状态时间线 + 详情列表 ============ */} Pod 实例 ({pods.length}) Pod 名称 状态 所在节点 Pod IP 重启 调度→就绪 启动时间 操作 {pods.length === 0 ? ( 暂无 Pod ) : pods.map((pod) => { const tl = getPodTimeline(pod); const schedToReady = elapsedSeconds(tl.scheduled, tl.ready); const expanded = expandedPod === pod.name; const nodeName = pod.node_name || pod.node; return ( {pod.name} {pod.status || 'Unknown'} {pod.ip || pod.pod_ip || '-'} 0 ? 'text-yellow-400' : 'text-slate-500'}>{pod.restart_count || 0} {formatDuration(schedToReady)} {formatK8sTime(tl.startTime || pod.created_at)}
{expanded && ( )}
); })}
{/* ============ 4. 历史版本(可回滚) ============ */} 历史版本(Revision / Rollout) Revision 镜像 状态 更新时间 操作 {revisions.length === 0 ? ( 后端 /revisions 接口尚未就绪,等待后端联调 ) : revisions.map((rev, idx) => { const revision = rev.revision ?? rev.revision_number ?? rev.number ?? idx + 1; const isLatest = idx === 0; return ( #{revision} {rev.image || rev.images?.[0] || '-'} {isLatest ? 当前 : 历史} {formatK8sTime(rev.created_at || rev.time)} ); })}
{/* ============ 5. ReplicaSet 列表 ============ */} ReplicaSet 列表 ({replicaSets.length}) 名称 副本 镜像 Revision 状态 创建时间 {replicaSets.length === 0 ? ( 后端 /replicasets 接口尚未就绪,等待后端联调 ) : replicaSets.map((rs) => { const r: any = rs; const rsReady = r.ready_replicas ?? r.readyReplicas ?? 0; const rsTotal = r.replicas ?? 0; const ok = rsReady === rsTotal && rsTotal > 0; return ( {r.name} {r.controlled && 当前} {rsReady}/{rsTotal} {r.image || '-'} #{r.revision || '-'} {r.status || (ok ? 'Ready' : 'Updating')} {formatK8sTime(r.created_at)} ); })}
); } // ============ 容器模板表(镜像/端口/资源/探针) ============ function ContainersTable({ deployment }: { deployment: any }) { const containers: any[] = deployment.containers || deployment.container_specs || deployment.spec_containers || []; if (containers.length === 0) { return

无容器详情数据(扁平字段缺少 containers,可查看 YAML)

; } return (
{containers.map((c, idx) => { const cpuReq = c.resources?.requests?.cpu || c.cpu_request; const cpuLim = c.resources?.limits?.cpu || c.cpu_limit; const memReq = c.resources?.requests?.memory || c.memory_request; const memLim = c.resources?.limits?.memory || c.memory_limit; const ports: any[] = c.ports || c.container_ports || []; return (
{c.name || `container-${idx}`}
{c.image}
CPU: {cpuReq || '-'}/{cpuLim || '-'}
内存: {memReq || '-'}/{memLim || '-'}
端口: {ports.map((p: any) => typeof p === 'object' ? (p.containerPort || p.port) : p).join(', ') || '-'}
挂载: {(c.volume_mounts || c.volumeMounts || []).length || 0}
{/* 探针 */}
{c.liveness_probe || c.livenessProbe ? ( ● 存活探针 ) : ○ 无存活探针} {c.readiness_probe || c.readinessProbe ? ( ● 就绪探针 ) : ○ 无就绪探针} {c.startup_probe || c.startupProbe ? ( ● 启动探针 ) : null}
); })}
); } // ============ Pod 状态时间线(调度→初始化→容器就绪→就绪) ============ function PodTimeline({ pod }: { pod: PodItem }) { const tl = getPodTimeline(pod); const stages = [ { label: '调度', ts: tl.scheduled, color: STATUS_COLORS.blue }, { label: '初始化', ts: tl.initialized, color: STATUS_COLORS.orange }, { label: '容器就绪', ts: tl.containersReady, color: STATUS_COLORS.green }, { label: 'Pod 就绪', ts: tl.ready, color: STATUS_COLORS.green }, ]; return (

状态时间线

{stages.map((s, i) => { const prev = stages[i - 1]; const dur = elapsedSeconds(prev?.ts, s.ts); return (
{s.label} {s.ts ? formatK8sTime(s.ts) : '-'}
{i < stages.length - 1 && (
{dur != null ? formatDuration(dur) : ''}
)}
); })}
); }