import { useState, useEffect } from 'react'; import { cn } from '@/lib/utils'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Cloud, Plus, MoreHorizontal, Server, Database, CheckCircle, AlertTriangle, XCircle, RefreshCw, Settings, Eye, Trash2, Search, Container, Download, } from 'lucide-react'; import { kubernetesApi } from '@/services/api'; import { toast } from 'sonner'; import type { KubernetesCluster } from '@/types'; import { KubernetesClusterDetail } from './KubernetesClusterDetail'; import { KubernetesClusterDialog } from './KubernetesClusterDialog'; import { KubectlTerminal } from './KubectlTerminal'; export function KubernetesClusters() { const [clusters, setClusters] = useState([]); const [loading, setLoading] = useState(true); const [selectedCluster, setSelectedCluster] = useState(null); const [searchQuery, setSearchQuery] = useState(''); // 创建/编辑集群弹窗 const [dialogOpen, setDialogOpen] = useState(false); const [editingCluster, setEditingCluster] = useState(null); // kubectl 终端 const [kubectlCluster, setKubectlCluster] = useState<{id: number; name: string} | null>(null); const [kubectlTerminalOpen, setKubectlTerminalOpen] = useState(false); // 导出资源弹窗 const [exportCluster, setExportCluster] = useState(null); const [exporting, setExporting] = useState(false); const loadData = async () => { try { setLoading(true); const res: any = await kubernetesApi.getClusters(); setClusters(res?.list || []); } catch (e) { console.error('加载集群列表失败:', e); setClusters([]); } finally { setLoading(false); } }; useEffect(() => { loadData(); }, []); // 新建后自动同步 const [pendingSyncId, setPendingSyncId] = useState(null); const handleSaved = () => { loadData().then(() => { if (!editingCluster) { // 新建集群后,等列表刷新完再取最新一条同步 setTimeout(async () => { const res: any = await kubernetesApi.getClusters(); const list = res?.list || []; if (list.length > 0) { handleSync(list[list.length - 1].id, true); } }, 500); } }); }; const handleDelete = async (id: string) => { try { await kubernetesApi.deleteCluster(id); loadData(); } catch (e) { console.error('删除集群失败:', e); } }; const handleSync = async (id: number | string, silent?: boolean) => { try { if (!silent) toast.info('正在同步集群资源...'); const res: any = await kubernetesApi.syncCluster(id); if (!silent) toast.success(res?.message || '同步完成'); loadData(); } catch (e: any) { if (!silent) toast.error(e?.message || '同步失败'); } }; const handleExport = async () => { if (!exportCluster) return; const code = exportCluster.code || String(exportCluster.id); try { setExporting(true); toast.info(`正在导出 ${exportCluster.name} 所有命名空间的资源...`); await kubernetesApi.exportAllResources(code); toast.success('导出完成,已开始下载'); setExportCluster(null); } catch (e: any) { toast.error(e?.message || '导出失败'); } finally { setExporting(false); } }; const filteredClusters = clusters.filter( (cluster) => (cluster.name || '').toLowerCase().includes(searchQuery.toLowerCase()) || (cluster.version || '').includes(searchQuery) ); const getStatusBadge = (status: KubernetesCluster['status']) => { switch (status) { case 'healthy': return ( 健康 ); case 'warning': return ( 警告 ); case 'error': return ( 异常 ); default: return null; } }; const clusterDetail = selectedCluster ? clusters.find((c) => c.id === selectedCluster) : null; return (
{/* Page Header */}

集群列表

管理Kubernetes集群资源

{/* Stats Cards */}

集群总数

{clusters.length}

健康集群

{clusters.filter((c) => c.status === 'healthy').length}

总节点数

{clusters.reduce((acc, c) => acc + (c.node_count || 0), 0)}

总Pod数

{clusters.reduce((acc, c) => acc + (c.pod_count || 0), 0)}

{/* Search */}
setSearchQuery(e.target.value)} className="pl-9 h-9 bg-slate-800/50 border-slate-700 text-slate-200 placeholder:text-slate-500" />
{/* Clusters Grid */}
{loading ? ( 加载中... ) : filteredClusters.length === 0 ? ( 暂无集群数据 ) : ( filteredClusters.map((cluster) => { const cpuUsage = Number(cluster.cpu_usage || 0); const memoryUsage = Number(cluster.memory_usage || 0); const hasMetrics = cpuUsage > 0 || memoryUsage > 0; return ( setSelectedCluster(cluster.id)} >
{cluster.name} {cluster.version}
{getStatusBadge(cluster.status)} 操作 查看详情 { e.stopPropagation(); setEditingCluster(cluster); setDialogOpen(true); }} > 配置 { e.stopPropagation(); handleSync(cluster.id); }} > 同步 { e.stopPropagation(); setKubectlCluster({ id: cluster.id, name: cluster.name }); setKubectlTerminalOpen(true); }} > kubectl { e.stopPropagation(); setExportCluster(cluster); }} > 导出资源 { e.stopPropagation(); handleDelete(cluster.id); }} > 删除
{cluster.node_count || 0}
节点
{cluster.pod_count || 0}
Pod
{cluster.service_count || 0}
Service
{cluster.deployment_count || 0}
Deployment
{hasMetrics ? ( <>
CPU
80 ? 'bg-red-400' : cpuUsage > 60 ? 'bg-amber-400' : 'bg-emerald-400' )} style={{ width: `${Math.min(cpuUsage, 100)}%` }} />
{cpuUsage.toFixed(1)}%
内存
80 ? 'bg-red-400' : memoryUsage > 60 ? 'bg-amber-400' : 'bg-emerald-400' )} style={{ width: `${Math.min(memoryUsage, 100)}%` }} />
{memoryUsage.toFixed(1)}%
) : (
已接入 {cluster.node_count || 0} 节点 · {cluster.pod_count || 0} Pod · 资源使用率待采集
)}
); }) )}
{/* Cluster Detail */} {clusterDetail && ( setSelectedCluster(null)} /> )} {/* 创建/编辑集群 Dialog */} {/* kubectl 终端 */} {kubectlCluster && ( { setKubectlTerminalOpen(open); if (!open) setKubectlCluster(null); }} clusterId={kubectlCluster.id} clusterName={kubectlCluster.name} /> )} {/* 导出资源弹窗 */} {exportCluster && (
setExportCluster(null)}>
e.stopPropagation()}>

导出集群资源

导出 {exportCluster.name} 所有命名空间的 Deployment、Service、Ingress、ConfigMap、Secret 为 tar.gz

)}
); }