import { useState, useEffect, useCallback } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { RefreshCw, Search, Boxes, Loader2, Container, Plus, Minus, RotateCw, Upload, FileText, Eye, } from 'lucide-react'; import { kubernetesApi } from '@/services/api'; import { useToast } from '@/hooks/use-toast'; import { DeploymentDetail } from './DeploymentDetail'; export function KubernetesWorkloads() { const { toast } = useToast(); const [clusters, setClusters] = useState([]); const [selectedCluster, setSelectedCluster] = useState(''); const [namespaces, setNamespaces] = useState([]); const [selectedNamespace, setSelectedNamespace] = useState('all'); const [deployments, setDeployments] = useState([]); const [pods, setPods] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(''); const [activeTab, setActiveTab] = useState('deployments'); const [actionLoading, setActionLoading] = useState>({}); // Detail view const [selectedDeployment, setSelectedDeployment] = useState<{ name: string; namespace: string } | null>(null); const loadClusters = useCallback(async () => { try { const res: any = await kubernetesApi.getClusters(); const list = res?.list || []; setClusters(list); if (list.length > 0 && !selectedCluster) setSelectedCluster(list[0].code || String(list[0].id)); } catch { setClusters([]); } }, []); useEffect(() => { loadClusters(); }, [loadClusters]); // Load namespaces when cluster changes const loadNamespaces = useCallback(async () => { if (!selectedCluster) return; try { const res: any = await kubernetesApi.getNamespaces(selectedCluster); const nsList: string[] = Array.isArray(res) ? res : (res?.list || res?.data || []); setNamespaces(nsList); } catch { setNamespaces([]); } }, [selectedCluster]); useEffect(() => { loadNamespaces(); }, [loadNamespaces]); const loadData = useCallback(async () => { if (!selectedCluster) return; setLoading(true); const clusterCode = selectedCluster; try { if (selectedNamespace !== 'all') { const [depRes, podRes] = await Promise.all([ kubernetesApi.getNamespaceDeployments(clusterCode, selectedNamespace).catch(() => []), kubernetesApi.getNamespacePods(clusterCode, selectedNamespace).catch(() => []), ]); const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || [])); setDeployments(toArray(depRes)); setPods(toArray(podRes)); } else { const [depRes, podRes] = await Promise.all([ kubernetesApi.getAllDeployments(clusterCode).catch(() => []), kubernetesApi.getAllPods(clusterCode).catch(() => []), ]); const toArray = (v: any) => (Array.isArray(v) ? v : (v?.list || [])); setDeployments(toArray(depRes)); setPods(toArray(podRes)); } } catch { setDeployments([]); setPods([]); } finally { setLoading(false); } }, [selectedCluster, selectedNamespace]); useEffect(() => { loadData(); }, [loadData]); const filteredDeps = deployments.filter((d) => (d.name || '').toLowerCase().includes(search.toLowerCase()) && (selectedNamespace === 'all' || d.namespace === selectedNamespace), ); const filteredPods = pods.filter((p) => (p.name || '').toLowerCase().includes(search.toLowerCase()) && (selectedNamespace === 'all' || p.namespace === selectedNamespace), ); const getPodStatusBadge = (status: string) => { const map: Record = { Running: 'bg-emerald-500/20 text-emerald-400 border-emerald-500/30', Pending: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30', Failed: 'bg-red-500/20 text-red-400 border-red-500/30', Succeeded: 'bg-blue-500/20 text-blue-400 border-blue-500/30', }; return {status || 'Unknown'}; }; const handleQuickScale = async (dep: any, delta: number) => { const key = `scale-${dep.name}`; setActionLoading((prev) => ({ ...prev, [key]: 'loading' })); try { const newReplicas = (dep.replicas || 0) + delta; if (newReplicas < 0) return; await kubernetesApi.scaleDeployment(selectedCluster, dep.namespace, dep.name, newReplicas); toast({ title: '扩缩容成功', description: `${dep.name} → ${newReplicas} 副本` }); loadData(); } catch (e: any) { toast({ title: '操作失败', description: e?.message || String(e), variant: 'destructive' }); } finally { setActionLoading((prev) => { const n = { ...prev }; delete n[key]; return n; }); } }; // Show detail view if a deployment is selected if (selectedDeployment) { return ( { setSelectedDeployment(null); loadData(); }} /> ); } return (

工作负载

Deployment 与 Pod 运行状态

{[ { label: 'Deployment', value: deployments.length, icon: , color: 'text-rose-400' }, { label: 'Pod 总数', value: pods.length, icon: , color: 'text-blue-400' }, { label: 'Running', value: pods.filter((p) => p.status === 'Running').length, icon: , color: 'text-emerald-400' }, { label: '异常', value: pods.filter((p) => ['Failed', 'Pending'].includes(p.status)).length, icon: , color: 'text-red-400' }, ].map((s) => (

{s.label}

{s.value}

{s.icon}
))}
setSearch(e.target.value)} className="pl-9 h-9 bg-slate-800/50 border-slate-700 text-slate-200" />
Deployment ({filteredDeps.length}) Pod ({filteredPods.length}) 名称 命名空间 副本 镜像 操作 {loading ? ( 加载中... ) : filteredDeps.length === 0 ? ( 暂无 Deployment ) : ( filteredDeps.map((d) => ( setSelectedDeployment({ name: d.name, namespace: d.namespace })}> {d.name} {d.namespace} {(() => { const ready = d.ready_replicas || 0; const total = d.replicas || 0; const pct = total > 0 ? (ready / total) * 100 : 0; const done = ready === total; return (
{ready}/{total}
); })()} {d.image || '-'} e.stopPropagation()}>
)) )}
名称 命名空间 状态 节点 CPU 内存 重启 镜像 {loading ? ( 加载中... ) : filteredPods.length === 0 ? ( 暂无 Pod ) : ( filteredPods.map((p) => ( {p.name} {p.namespace} {getPodStatusBadge(p.status)} {p.node_name || '-'} {p.cpu_usage_m || '-'} {p.mem_usage_mi || '-'} 0 ? 'text-yellow-400' : 'text-slate-500'}>{p.restart_count || 0} {p.image || '-'} )) )}
); }