feat(k8s): M1 SyncCluster 增加 metrics 采集
后端: - SyncCluster 中调 GetNodeMetrics() 获取 metrics-server 数据 - 节点 INSERT 增加 cpu_usage/memory_usage 百分比字段 - 新增 parseCpuNanoToMilli / parseMemKiToMi 辅助函数 - 采集 Pod metrics(为 M2 准备) 前端: - KubernetesClusterDetail 删除硬编码 resourceData mock - 改为从节点 metrics 实时计算集群 CPU/内存使用率 - 图表 Y 轴固定 0-100 域,底部显示百分比数值 新增文档: - ROADMAP.md / TDD_PLAN.md / PROJECT_EVALUATION.md
This commit is contained in:
1 parent
3b112ee813
commit
b3d2011243
5 files changed
+289
-729
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
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';
|
||||
@@ -15,16 +15,8 @@ import {
|
||||
ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
|
||||
// 模拟资源使用趋势数据
|
||||
const resourceData = [
|
||||
{ time: '00:00', cpu: 45, memory: 52 },
|
||||
{ time: '04:00', cpu: 38, memory: 48 },
|
||||
{ time: '08:00', cpu: 62, memory: 68 },
|
||||
{ time: '12:00', cpu: 78, memory: 82 },
|
||||
{ time: '16:00', cpu: 72, memory: 76 },
|
||||
{ time: '20:00', cpu: 58, memory: 64 },
|
||||
{ time: '24:00', cpu: 48, memory: 56 },
|
||||
];
|
||||
// 资源使用趋势数据 — 从节点 metrics 实时计算(不再用硬编码 mock) // Logan.Li
|
||||
// 由于当前无 time-series 存储,用当前快照展示一个合理的汇总值
|
||||
|
||||
export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; onClose: () => void }) {
|
||||
const [nodes, setNodes] = useState<any[]>([]);
|
||||
@@ -33,6 +25,32 @@ export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; on
|
||||
const [services, setServices] = useState<any[]>([]);
|
||||
const [ingresses, setIngresses] = useState<any[]>([]);
|
||||
|
||||
// 从节点数据计算集群级 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) {
|
||||
// ClusterOverviewPage 读的是 cpu_usage_m / mem_usage_mi(实时路径)
|
||||
// ClusterDetail 的 nodes 从 getNodes API 获取,也有这些字段
|
||||
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 : (n.cpu_usage || 0);
|
||||
const memMb = n.memory_mb || n.memory || 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 : (n.memory_usage || 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})
|
||||
@@ -120,7 +138,9 @@ export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; on
|
||||
<CardContent>
|
||||
<div className="h-[200px]">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<AreaChart data={resourceData}>
|
||||
<AreaChart data={[
|
||||
{ time: 'CPU', cpu: Number(clusterMetrics.cpuPct.toFixed(1)), memory: Number(clusterMetrics.memPct.toFixed(1)) },
|
||||
]}>
|
||||
<defs>
|
||||
<linearGradient id="colorCpu" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
|
||||
@@ -133,7 +153,7 @@ export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; on
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
|
||||
<XAxis dataKey="time" stroke="#475569" fontSize={12} />
|
||||
<YAxis stroke="#475569" fontSize={12} />
|
||||
<YAxis stroke="#475569" fontSize={12} domain={[0, 100]} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: '#0f172a',
|
||||
@@ -148,7 +168,7 @@ export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; on
|
||||
stroke="#3b82f6"
|
||||
fillOpacity={1}
|
||||
fill="url(#colorCpu)"
|
||||
name="CPU"
|
||||
name="CPU(%)"
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
@@ -156,11 +176,15 @@ export function KubernetesClusterDetail({ cluster, onClose }: { cluster: any; on
|
||||
stroke="#10b981"
|
||||
fillOpacity={1}
|
||||
fill="url(#colorMemory)"
|
||||
name="内存"
|
||||
name="内存(%)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="flex justify-center gap-6 mt-2 text-xs text-slate-400">
|
||||
<span>CPU: <span className="text-blue-400 font-bold">{clusterMetrics.cpuPct.toFixed(1)}%</span></span>
|
||||
<span>内存: <span className="text-emerald-400 font-bold">{clusterMetrics.memPct.toFixed(1)}%</span></span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in new issue
Block a user