import { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { RefreshCw, Search, Server, Loader2 } from 'lucide-react'; import { kubernetesApi } from '@/api'; import { PageShell } from '@/components/layout/PageShell'; import { ContentPanel } from '@/components/layout/ContentPanel'; import type { JsonData } from '@/types/common'; import i18n from '@/i18n'; export function KubernetesNodes() { const [clusters, setClusters] = useState([]); const [selectedCluster, setSelectedCluster] = useState(''); const [nodes, setNodes] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(''); const [roleFilter, setRoleFilter] = useState('all'); const loadClusters = useCallback(async () => { try { const res: JsonData = await kubernetesApi.getClusters(); const list = res?.list || []; setClusters(list); if (list.length > 0 && !selectedCluster) setSelectedCluster(String(list[0].code || list[0].id)); } catch { setClusters([]); } }, []); useEffect(() => { loadClusters(); }, [loadClusters]); const loadNodes = useCallback(async () => { if (!selectedCluster) return; setLoading(true); try { const res: JsonData = await kubernetesApi.getNodes(selectedCluster); setNodes(Array.isArray(res) ? res : (res?.list || [])); } catch { setNodes([]); } finally { setLoading(false); } }, [selectedCluster]); useEffect(() => { loadNodes(); }, [loadNodes]); const filtered = nodes.filter((n) => (n.name || '').toLowerCase().includes(search.toLowerCase()) && (roleFilter === 'all' || n.role === roleFilter), ); const getRoleBadge = (role: string) => role === 'master' ? Master : Worker; const getStatusBadge = (status: string) => status === 'Ready' ? {status} : {status || 'Unknown'}; const tableToolbar = (
setSearch(e.target.value)} className="pl-9 h-9 dg-input" />
); const clusterLabel = clusters.find((c) => String(c.code || c.id) === selectedCluster)?.name; return ( } actions={( <> )} > {i18n.t('kubernetes.node.nodeName')} IP {i18n.t('system.userRole')} {i18n.t('system.terminalStatus')} {i18n.t('deploy.detailDialog.config')} {i18n.t('server.usage')} {/* Request 占用:已申请 / 可分配,与"使用率"分列,避免一列塞四个条 // Logan.Li */} {i18n.t('k8s.node.requestUsage', { defaultValue: 'Request 占用' })} Pods Kubelet {loading ? ( {i18n.t('monitor.logPanel.logResults.loading')} ) : filtered.length === 0 ? ( {selectedCluster ? i18n.t('k8s.empty.nodes') : i18n.t('kubernetes.kubernetesnodes.sf8c38834')} ) : ( filtered.map((n) => ( {n.name} {n.ip || '-'} {getRoleBadge(n.role)} {getStatusBadge(n.status)} {n.cpu ? i18n.t('k8s.capacity.cpuCores', { count: n.cpu }) : '-'} / {n.memory ? `${Math.round(n.memory / 1024)}GB` : '-'} {(() => { const cpu = n.cpu_usage || 0; const mem = n.memory_usage || 0; const bar = (val: number) => val >= 85 ? 'bg-[var(--dg-red)]' : val >= 60 ? 'bg-[var(--dg-orange)]' : 'bg-[var(--dg-green)]'; const txt = (val: number) => val >= 85 ? 'text-[var(--dg-red)]' : val >= 60 ? 'text-[var(--dg-orange)]' : 'text-[var(--dg-green)]'; return (
CPU
{cpu.toFixed(0)}%
Mem
{mem.toFixed(0)}%
); })()} {(() => { // Request 占用 = 该节点 Pod 已申请 / 可分配。 // 百分比给直观判断,tooltip 给绝对值(分子/分母一起看才有意义)。 // Logan.Li const cpuPct = n.cpu_alloc_pct || 0; const memPct = n.mem_alloc_pct || 0; const cpuReq = n.cpu_request_m || 0; const memReq = n.mem_request_mi || 0; const bar = (val: number) => val >= 85 ? 'bg-[var(--dg-red)]' : val >= 60 ? 'bg-[var(--dg-orange)]' : 'bg-[var(--dg-blue)]'; const txt = (val: number) => val >= 85 ? 'text-[var(--dg-red)]' : val >= 60 ? 'text-[var(--dg-orange)]' : 'text-[var(--dg-text-secondary)]'; const cpuCap = (n.cpu || 0) * 1000; const memCap = n.memory || 0; return (
CPU
{cpuPct.toFixed(0)}%
Mem
{memPct.toFixed(0)}%
); })()} {n.pod_count || 0} / {n.max_pods || '-'} {n.kubelet_version || '-'} )) )}
); }