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, Eye, } from 'lucide-react'; import { kubernetesApi } from '@/api'; import { useToast } from '@/hooks/use-toast'; import { DeploymentDetail } from './DeploymentDetail'; import type { JsonData } from '@/types/common'; import { useTranslation } from 'react-i18next'; export function KubernetesWorkloads() { const { t } = useTranslation(); 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 [, setActionLoading] = useState>({}); // Detail view const [selectedDeployment, setSelectedDeployment] = useState<{ name: string; namespace: string } | null>(null); const loadClusters = useCallback(async () => { try { const res: JsonData = 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: JsonData = 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: JsonData) => (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: JsonData) => (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 dg-text-green border-emerald-500/30', Pending: 'bg-yellow-500/20 dg-text-yellow border-yellow-500/30', Failed: 'bg-red-500/20 dg-text-red border-red-500/30', Succeeded: 'bg-blue-500/20 dg-text-blue border-blue-500/30', }; return {status || 'Unknown'}; }; const handleQuickScale = async (dep: JsonData, 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: t('kubernetes.kubernetesworkloads.s0b3ea1'), description: t('kubernetes.kubernetesworkloads.scf7f1c', { dep.name: dep.name, newReplicas: newReplicas }) }); loadData(); } catch (e) { toast({ title: t('common.operationFailed'), 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 (

{t('menu.workloads')}

{t('kubernetes.kubernetesworkloads.sf90ced')}

{[ { label: 'Deployment', value: deployments.length, icon: , color: 'text-rose-400' }, { label: t('kubernetes.kubernetesworkloads.s83481f'), value: pods.length, icon: , color: 'dg-text-blue' }, { label: 'Running', value: pods.filter((p) => p.status === 'Running').length, icon: , color: 'dg-text-green' }, { label: t('common.abnormal'), value: pods.filter((p) => ['Failed', 'Pending'].includes(p.status)).length, icon: , color: 'dg-text-red' }, ].map((s) => (

{s.label}

{s.value}

{s.icon}
))}
setSearch(e.target.value)} className="pl-9 h-9 dg-input" />
Deployment ({filteredDeps.length}) Pod ({filteredPods.length}) {t('common.name')} {t('k8s.column.namespace')} {t('kubernetes.kubernetesworkloads.s0428b3')} {t('k8s.column.image')} {t('common.actions')} {loading ? ( {t('common.loading')} ) : filteredDeps.length === 0 ? ( {t('kubernetes.kubernetesworkloads.s5689d3')} ) : ( 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()}>
)) )}
{t('common.name')} {t('k8s.column.namespace')} {t('common.status')} {t('k8s.tab.nodes')} CPU {t('k8s.pods.memory')} {t('common.reboot')} {t('k8s.column.image')} {loading ? ( {t('common.loading')} ) : filteredPods.length === 0 ? ( {t('k8s.deploymentDetail.noPod')} ) : ( filteredPods.map((p) => ( {p.name} {p.namespace} {getPodStatusBadge(p.status)} {p.node_name || '-'} {p.cpu_usage_m || '-'} {p.mem_usage_mi || '-'} 0 ? 'dg-text-yellow' : 'dg-text-muted'}>{p.restart_count || 0} {p.image || '-'} )) )}
); }