/** * Cluster Monitor Card Component * 显示单个集群的监控信息 */ import React, { useState } from "react"; import { Activity, CheckCircle, AlertTriangle, XCircle, HelpCircle, Clock, Cpu, Database, Server as ServerIcon, ChevronDown, ChevronUp, TrendingUp, Users } from "lucide-react"; import { Card, Badge } from "@/shared/components"; import type { ClusterMetrics } from "@/core/types"; import { NodeMetricCard } from "./NodeMetricCard"; interface ClusterMonitorCardProps { cluster: ClusterMetrics; } export const ClusterMonitorCard: React.FC = ({ cluster }) => { const [showNodes, setShowNodes] = useState(false); const status = cluster.status ?? "unknown"; const uptime = cluster.uptime ?? "N/A"; const nodeCount = cluster.nodeCount ?? 0; const podCount = cluster.podCount ?? 0; const totalGpu = cluster.totalGpu ?? 0; const usedGpu = cluster.usedGpu ?? 0; const allocatedGpu = firstNumber(cluster.gpuAllocated, cluster.allocatedGpu, cluster.gpuAllocation, usedGpu); const usedGpuMemory = firstDisplayValue(cluster.allocatedGpuMemoryMb, cluster.allocatedGpuMemoryMB, cluster.gpuMemoryRequestsMb, cluster.usedGpuMemory, cluster.gpuMemoryUsed, cluster.usedGpuMem); const totalGpuMemory = firstDisplayValue(cluster.totalGpuMemory, cluster.totalGpuMem); const cpuUsage = cluster.cpuUsage ?? 0; const memoryUsage = cluster.memoryUsage ?? 0; const gpuUsage = cluster.gpuUsage ?? 0; const usedCpu = cluster.usedCpu ?? "N/A"; const totalCpu = cluster.totalCpu ?? "N/A"; const usedMemory = cluster.usedMemory ?? "N/A"; const totalMemory = cluster.totalMemory ?? "N/A"; const cpuRequestText = firstDisplayValue(cluster.cpuRequests, usedCpu); const memoryRequestText = firstDisplayValue(cluster.memoryRequests, usedMemory); const hasClusterTotals = Boolean(cluster.totalCpu || cluster.totalMemory || cluster.nodeCount); const lastCheckedText = cluster.lastCheck ? new Date(cluster.lastCheck).toLocaleString() : "N/A"; const userResourceRows = getUserResourceRows(cluster); const getStatusBadge = () => { switch (status) { case "healthy": return Healthy; case "warning": case "unknown": return Warning; case "error": case "unhealthy": return Error; default: return Unknown; } }; const getStatusIcon = () => { switch (status) { case "healthy": return ; case "warning": case "unknown": return ; case "error": case "unhealthy": return ; default: return ; } }; return (
{/* Status Icon */}
{getStatusIcon()}
{/* Cluster Info */}

{cluster.clusterName || "Unnamed Cluster"}

{getStatusBadge()}
{/* Metrics Grid */}

Uptime

{uptime}

{hasClusterTotals ? "Nodes" : "Visible Nodes"}

{nodeCount}

Pods

{podCount}

GPU

{hasClusterTotals ? `${usedGpu}/${totalGpu || "N/A"}` : `${allocatedGpu} allocated`}

GPU Mem

{usedGpuMemory || "N/A"}{totalGpuMemory ? ` / ${totalGpuMemory}` : ""}

{/* Resource Usage */}

{hasClusterTotals ? "CPU (Cluster Total)" : "CPU Requests"}

{hasClusterTotals ? `${usedCpu} / ${totalCpu}` : cpuRequestText || "0 cores"}

{hasClusterTotals ? `${cpuUsage.toFixed(1)}%` : "self-scoped allocation"}

{cluster.maxNodeCpu && (

Max per node

{cluster.maxNodeCpu}

{cluster.maxNodeCpuUsage && cluster.maxNodeCpuUsage > 0 && (

Peak: {cluster.maxNodeCpuUsage.toFixed(1)}%

)}
)}

{hasClusterTotals ? "Memory (Cluster Total)" : "Memory Requests"}

{hasClusterTotals ? `${usedMemory} / ${totalMemory}` : memoryRequestText || "0 B"}

{hasClusterTotals ? `${memoryUsage.toFixed(1)}%` : "self-scoped allocation"}

{cluster.maxNodeMemory && (

Max per node

{cluster.maxNodeMemory}

{cluster.maxNodeMemUsage && cluster.maxNodeMemUsage > 0 && (

Peak: {cluster.maxNodeMemUsage.toFixed(1)}%

)}
)}
{(totalGpu > 0 || allocatedGpu > 0) && (

GPU Allocation

{allocatedGpu} / {totalGpu || "N/A"}

{hasClusterTotals ? `${gpuUsage.toFixed(1)}%` : "self-scoped allocation"}

{cluster.maxNodeGpu && cluster.maxNodeGpu > 0 && (

Max per node

{cluster.maxNodeGpu} GPUs

{cluster.maxNodeGpuUsage && cluster.maxNodeGpuUsage > 0 && (

Peak: {cluster.maxNodeGpuUsage.toFixed(1)}%

)}
)}
)} {(usedGpuMemory || totalGpuMemory) && (

GPU Mem

{usedGpuMemory || "0"}{totalGpuMemory ? ` / ${totalGpuMemory}` : ""}

requests.nvidia.com/gpumem

)}
{userResourceRows.length > 0 && (

User Resources

{userResourceRows.map((row, index) => ( ))}
User Namespace CPU Memory GPU GPU Mem Pods
{row.username || row.userName || shortId(row.userId) || "-"} {row.namespace || "-"} {firstDisplayValue(row.cpuRequests, row.usedCpu, row.cpuUsed, row.cpuRequest, row.cpuLimits, row.cpuLimit) || "-"} {firstDisplayValue(row.memoryRequests, row.usedMemory, row.memoryUsed, row.memoryRequest, row.memoryLimits, row.memoryLimit) || "-"} {firstNumber(row.gpuRequests, row.gpuAllocated, row.gpuAllocation, row.usedGpu, row.gpuUsed) ?? 0} {firstDisplayValue(row.gpuMemoryRequestsMb, row.gpuMemoryAllocated, row.gpuMemAllocated, row.usedGpuMemory, row.gpuMemoryUsed, row.gpuMemUsed) || "0"} {row.podCount ?? "-"}
)}
Last checked: {lastCheckedText}
{/* Actions */}
{cluster.nodes && cluster.nodes.length > 0 && ( )}
{/* Nodes List */} {showNodes && cluster.nodes && cluster.nodes.length > 0 && (

Cluster Nodes ({cluster.nodes.length})

{cluster.nodes.map((node) => ( ))}
)} ); }; const firstNumber = (...values: Array): number => { for (const value of values) { if (typeof value === "number" && Number.isFinite(value)) { return value; } } return 0; }; const firstDisplayValue = (...values: Array): string => { for (const value of values) { if (typeof value === "number" && Number.isFinite(value)) { return String(value); } if (typeof value === "string" && value.trim()) { return value.trim(); } } return ""; }; const getUserResourceRows = (cluster: ClusterMetrics) => cluster.resourceUsageByUser || cluster.userResources || cluster.userResourceUsage || cluster.resourcesByUser || cluster.userResourceRows || []; const shortId = (value?: string): string => { const id = value?.trim(); if (!id) return ""; if (id.length <= 12) return id; return `${id.slice(0, 8)}...${id.slice(-4)}`; };