import { useState, useEffect, useMemo } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Cloud, XCircle } from 'lucide-react'; import { kubernetesApi } from '@/api'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts'; import type { JsonData } from '@/types/common'; // 资源使用趋势数据 — 从节点 metrics 实时计算(不再用硬编码 mock) // Logan.Li // 由于当前无 time-series 存储,用当前快照展示一个合理的汇总值 export function KubernetesClusterDetail({ cluster, onClose }: { cluster?: JsonData; onClose: () => void }) { const [nodes, setNodes] = useState([]); const [pods, setPods] = useState([]); const [deployments, setDeployments] = useState([]); const [services, setServices] = useState([]); const [ingresses, setIngresses] = useState([]); // 从节点数据计算集群级 CPU/内存使用率(实时快照) // Logan.Li const clusterMetrics = useMemo(() => { if (!nodes || nodes.length === 0) return { cpuPct: 0, memPct: 0 }; let totalCpuPct = 0, totalMemPct = 0, validNodes = 0; for (const n of nodes) { // 后端 ListNodes 返回 cpu_usage_m (如 "122m"), mem_usage_mi (如 "2105Mi"), cpu (核数), memory_mb (MB) const cpuCores = n.cpu || 0; const cpuUsageM = n.cpu_usage_m ? parseFloat(String(n.cpu_usage_m).replace('m', '')) : 0; const cpuPct = cpuCores > 0 ? (cpuUsageM / 1000 / cpuCores) * 100 : 0; const memMb = n.memory_mb || 0; const memUsageMi = n.mem_usage_mi ? parseFloat(String(n.mem_usage_mi).replace('Mi', '').replace('Ki', '')) : 0; const memUsageNorm = n.mem_usage_mi?.includes?.('Ki') ? memUsageMi / 1024 : n.mem_usage_mi?.includes?.('Gi') ? memUsageMi * 1024 : memUsageMi; const memPct = memMb > 0 ? (memUsageNorm / memMb) * 100 : 0; if (cpuPct > 0 || memPct > 0) { totalCpuPct += cpuPct; totalMemPct += memPct; validNodes++; } } return { cpuPct: validNodes > 0 ? totalCpuPct / validNodes : 0, memPct: validNodes > 0 ? totalMemPct / validNodes : 0, }; }, [nodes]); const loadClusterDetail = async (clusterId: string) => { try { // 后端 ListNodes/ListPods/etc 返回 data 直接为数组(非 {list,total}) const [nodesRes, podsRes, depRes, svcRes, ingRes] = await Promise.all([ kubernetesApi.getNodes(clusterId).catch(() => []), kubernetesApi.getPods(clusterId).catch(() => []), kubernetesApi.getDeployments(clusterId).catch(() => []), kubernetesApi.getServices(clusterId).catch(() => []), kubernetesApi.getIngresses(clusterId).catch(() => []), ]); const toArray = (v: JsonData) => (Array.isArray(v) ? v : (v?.list || [])); setNodes(toArray(nodesRes)); setPods(toArray(podsRes)); setDeployments(toArray(depRes)); setServices(toArray(svcRes)); setIngresses(toArray(ingRes)); } catch (e) { console.error('加载集群详情失败:', e); } }; useEffect(() => { if (cluster?.id) { loadClusterDetail(cluster.id); } }, [cluster?.id]); return (
{cluster.name} {cluster.version} · {cluster.node_count || 0} 节点 · 创建于{' '} {cluster.created_at ? new Date(cluster.created_at).toLocaleDateString() : '-'}
概览 节点 ({nodes.length}) Pod ({pods.length}) Deployment ({deployments.length}) Service ({services.length}) Ingress ({ingresses.length}) 事件
资源使用趋势
CPU: {clusterMetrics.cpuPct.toFixed(1)}% 内存: {clusterMetrics.memPct.toFixed(1)}%
资源分布
{cluster.pod_count || 0}
Pod
{cluster.service_count || 0}
Service
{cluster.deployment_count || 0}
Deployment
{cluster.node_count || 0}
节点
{nodes.length === 0 ? ( ) : ( nodes.map((node) => ( )) )}
名称 角色 状态 CPU 内存 Pod 操作系统
暂无节点数据
{node.name} {node.role === 'master' ? 'Master' : 'Worker'} {(node.status || '').toLowerCase() === 'ready' ? 'Ready' : 'NotReady'} {node.cpu_usage_m || '0m'} / {node.cpu}核 {node.mem_usage_mi || '0Mi'} / {node.memory_mb ? (node.memory_mb / 1024).toFixed(0) + 'GB' : '-'} {node.max_pods ?? 0} {node.os}
{pods.length === 0 ? ( ) : ( pods.map((pod) => ( )) )}
名称 命名空间 状态 重启次数 CPU 内存 节点
暂无Pod数据
{pod.name} {pod.namespace} {pod.status} {pod.restart_count ?? 0} {pod.cpu_usage_m || '0m'} {pod.mem_usage_mi || '0Mi'} {pod.node_name}
{deployments.length === 0 ? ( ) : ( deployments.map((deployment) => ( )) )}
名称 命名空间 副本数 可用副本 镜像 创建时间
暂无Deployment数据
{deployment.name} {deployment.namespace} {deployment.replicas} {deployment.available_replicas ?? deployment.availableReplicas} / {deployment.replicas} {deployment.image} {deployment.created_at || deployment.createdAt}
{services.length === 0 ? ( ) : ( services.map((service) => ( )) )}
名称 命名空间 类型 ClusterIP 端口 Selector
暂无Service数据
{service.name} {service.namespace} {service.type} {service.cluster_ip ?? service.clusterIP} {service.ports} {service.selector}
{ingresses.length === 0 ? ( ) : ( ingresses.map((ingress) => ( )) )}
名称 命名空间 Hosts 后端服务 TLS
暂无Ingress数据
{ingress.name} {ingress.namespace} {ingress.hosts} {ingress.backend} {ingress.tls ? '已启用' : '未启用'}
{[ { type: 'Normal', reason: 'Started', message: 'Started container nginx', time: '2分钟前' }, { type: 'Normal', reason: 'Created', message: 'Created pod nginx-deployment-xxx', time: '5分钟前' }, { type: 'Warning', reason: 'FailedScheduling', message: '0/3 nodes are available', time: '10分钟前' }, { type: 'Normal', reason: 'Pulling', message: 'Pulling image "nginx:latest"', time: '15分钟前' }, { type: 'Normal', reason: 'Pulled', message: 'Successfully pulled image', time: '12分钟前' }, ].map((event, index) => (
{event.type} {event.reason} {event.message} {event.time}
))}
); }