import { useState, useEffect, useCallback } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { Card, CardContent } 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 { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Legend, PieChart, Pie, Cell, } from 'recharts'; import { RefreshCw, Cloud, Cpu, Container, Boxes, Activity, Server, Shield, HardDrive, Layers, Network, ChevronRight, TrendingUp, } from 'lucide-react'; import { kubernetesApi } from '@/services/api'; const CHART_COLORS = ['#3B82F6', '#207060', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316']; export default function ClusterComparePage() { const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [error, setError] = useState(''); const loadData = useCallback(async () => { setLoading(true); setError(''); try { const res: any = await kubernetesApi.getMultiClusterComparison(); setData(res || null); } catch (e: any) { setError(e?.message || '加载失败'); setData(null); } finally { setLoading(false); } }, []); useEffect(() => { loadData(); }, [loadData]); const clusters: any[] = data?.clusters || []; const totals: any = data?.totals || {}; // 图表数据准备 const resourceBarData = clusters.map((c: any) => ({ name: c.code || c.name, 节点: c.node_count || 0, Pod: c.pod_count || 0, Deployment: c.deployment_count || 0, Service: c.service_count || 0, })); const usageBarData = clusters.map((c: any) => ({ name: c.code || c.name, 'CPU %': Number((c.cpu_usage || 0).toFixed(1)), '内存 %': Number((c.memory_usage || 0).toFixed(1)), })); const radarData = clusters.length > 0 ? [ { metric: '节点数', ...Object.fromEntries(clusters.map((c: any) => [c.code || c.name, c.node_count || 0])) }, { metric: 'Pod数', ...Object.fromEntries(clusters.map((c: any) => [c.code || c.name, Math.min(c.pod_count || 0, 100)])) }, { metric: '部署数', ...Object.fromEntries(clusters.map((c: any) => [c.code || c.name, c.deployment_count || 0])) }, { metric: 'CPU使用', ...Object.fromEntries(clusters.map((c: any) => [c.code || c.name, Number((c.cpu_usage || 0).toFixed(1))])) }, { metric: '内存使用', ...Object.fromEntries(clusters.map((c: any) => [c.code || c.name, Number((c.memory_usage || 0).toFixed(1))])) }, { metric: 'CPU核数', ...Object.fromEntries(clusters.map((c: any) => [c.code || c.name, c.total_cpu_cores || 0])) }, ] : []; const nodeRolePie = clusters.flatMap((c: any) => [ { name: `${c.code}-Master`, value: c.master_count || 0, fill: CHART_COLORS[1] }, { name: `${c.code}-Worker`, value: c.worker_count || 0, fill: CHART_COLORS[0] }, ]).filter(d => d.value > 0); const statusColorMap: Record = { healthy: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30', warning: 'bg-amber-500/15 text-amber-400 border-amber-500/30', error: 'bg-red-500/15 text-red-400 border-red-500/30', }; // 格式化内存 (MB → GB) const fmtMem = (mb: number | undefined) => { if (!mb || mb <= 0) return '-'; return `${(mb / 1024).toFixed(1)} GB`; }; // 格式化同步时间 const fmtTime = (t: string | null | undefined) => { if (!t) return '从未同步'; try { const d = new Date(t); return d.toLocaleString('zh-CN', { hour12: false }); } catch { return t; } }; return (
{/* Header */}

多集群资源对比看板

跨集群资源用量、节点分布、健康状态对比

{error && ( ⚠ {error} )} {/* 概览卡片 */}
{[ { label: '集群总数', value: totals.cluster_count || 0, icon: Cloud, color: 'text-blue-400', bg: 'bg-blue-500/10' }, { label: '节点总数', value: totals.node_count || 0, icon: Cpu, color: 'text-emerald-400', bg: 'bg-emerald-500/10' }, { label: 'Pod 总数', value: totals.pod_count || 0, icon: Container, color: 'text-purple-400', bg: 'bg-purple-500/10' }, { label: 'Deployment 总数', value: totals.deployment_count || 0, icon: Boxes, color: 'text-amber-400', bg: 'bg-amber-500/10' }, { label: 'Service 总数', value: totals.service_count || 0, icon: Network, color: 'text-cyan-400', bg: 'bg-cyan-500/10' }, ].map((s, i) => (

{s.label}

{s.value}

))}
{clusters.length === 0 && !loading && (

暂无集群数据,请先添加 K8s 集群并同步

)} {clusters.length > 0 && ( <> {/* 资源对比柱状图 + 使用率柱状图 */}

资源数量对比

CPU / 内存使用率对比

{/* 雷达图 + 节点角色饼图 */} {clusters.length >= 2 && (

资源雷达对比

{clusters.map((c: any, i: number) => ( ))}

节点角色分布

{nodeRolePie.length > 0 ? ( `${e.name}: ${e.value}`} labelLine={{ stroke: '#475569' }} > {nodeRolePie.map((_, i) => ( ))} ) : (
无节点角色数据
)}
)} {/* 详细对比表格 */}

详细对比

点击行可进入集群概览
集群 状态 节点 Pod Deployment Service CPU 核数 内存 CPU 使用率 内存使用率 角色 同步时间 {clusters.map((c: any) => ( navigate(`/kubernetes/${c.code}`)} >
{c.name}
{c.code} · {c.api_server || '-'}
{c.status === 'healthy' ? '✓ 健康' : c.status === 'warning' ? '⚠ 警告' : '✗ 异常'} {c.node_count} {c.pod_count} {c.deployment_count} {c.service_count} {c.total_cpu_cores || '-'} {fmtMem(c.total_memory_mb)}
{(c.cpu_usage || 0).toFixed(1)}%
{(c.memory_usage || 0).toFixed(1)}%
{(c.master_count > 0) && ( M:{c.master_count} )} {(c.worker_count > 0) && ( W:{c.worker_count} )} {!c.master_count && !c.worker_count && -}
{fmtTime(c.last_sync_at)} ))}
)}
); }