feat(nodes): 节点列表新增 Request 占用列;服务器列表分页改为 10/30/50/100
节点管理原表格只有"使用率"一列(CPU/内存双条),缺少 Request 占用。 - ListNodes 一次性拉取 Pod 并按节点聚合 CPU/内存 Request, 计算 cpu_alloc_pct / mem_alloc_pct(已申请 / 可分配), 新增 pod_count / cpu_request_m / mem_request_mi 字段 - 前端新增"Request 占用"双条列,阈值 60%/85% 变色, tooltip 展示"已申请 / 可分配"绝对值 - 不含磁盘/分区:K8s 原生 API 不提供磁盘用量, 后续接入 Prometheus 后由监控侧承担 服务器列表分页选项由 10/20/30 调整为 10/30/50/100。 // Logan.Li
This commit is contained in:
1 parent
a210163387
commit
c69d5479b3
3 files changed
+89
-3
No files matched your search
@@ -49,7 +49,44 @@ func (h *KubernetesHandler) ListNodes(c *gin.Context) {
|
|||||||
// Metrics absolute (for detail pages)
|
// Metrics absolute (for detail pages)
|
||||||
CPUUsageM string `json:"cpu_usage_m,omitempty"` // e.g. "122m"
|
CPUUsageM string `json:"cpu_usage_m,omitempty"` // e.g. "122m"
|
||||||
MemUsageMi string `json:"mem_usage_mi,omitempty"` // e.g. "2105Mi"
|
MemUsageMi string `json:"mem_usage_mi,omitempty"` // e.g. "2105Mi"
|
||||||
|
|
||||||
|
// Request 占用(分配率 = 该节点 Pod 已申请 / 可分配)// Logan.Li
|
||||||
|
PodCount int `json:"pod_count"`
|
||||||
|
CPURequestM int `json:"cpu_request_m"`
|
||||||
|
MemRequestMi int64 `json:"mem_request_mi"`
|
||||||
|
CPUAllocPct float64 `json:"cpu_alloc_pct"`
|
||||||
|
MemAllocPct float64 `json:"mem_alloc_pct"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 一次性拉取 Pod 并按节点聚合 Request:列表页展示"Request 占用"所需,
|
||||||
|
// 避免每行单独查一次 Pod 列表。 // Logan.Li
|
||||||
|
type reqAgg struct {
|
||||||
|
cpuM int
|
||||||
|
memMi int64
|
||||||
|
pods int
|
||||||
|
}
|
||||||
|
podReqs := map[string]*reqAgg{}
|
||||||
|
if pods, perr := client.ListPods(); perr == nil && pods != nil {
|
||||||
|
for _, p := range pods.Items {
|
||||||
|
if p.Spec.NodeName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
agg := podReqs[p.Spec.NodeName]
|
||||||
|
if agg == nil {
|
||||||
|
agg = &reqAgg{}
|
||||||
|
podReqs[p.Spec.NodeName] = agg
|
||||||
|
}
|
||||||
|
agg.pods++
|
||||||
|
for _, ctr := range p.Spec.Containers {
|
||||||
|
if ctr.Resources.Requests == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
agg.cpuM += parseCpuToMilli(ctr.Resources.Requests["cpu"])
|
||||||
|
agg.memMi += parseMemToMi(ctr.Resources.Requests["memory"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result := []NodeSummary{}
|
result := []NodeSummary{}
|
||||||
for _, n := range nodes.Items {
|
for _, n := range nodes.Items {
|
||||||
role := "worker"
|
role := "worker"
|
||||||
@@ -100,6 +137,20 @@ func (h *KubernetesHandler) ListNodes(c *gin.Context) {
|
|||||||
s.MemoryUsage = float64(memMi) / float64(memCapacity) * 100
|
s.MemoryUsage = float64(memMi) / float64(memCapacity) * 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Request 占用:本节点 Pod 已申请 / 可分配。 // Logan.Li
|
||||||
|
if agg := podReqs[n.Metadata.Name]; agg != nil {
|
||||||
|
allocCPU := parseIntResource(n.Status.Allocatable["cpu"])
|
||||||
|
allocMem := parseMemResource(n.Status.Allocatable["memory"])
|
||||||
|
s.PodCount = agg.pods
|
||||||
|
s.CPURequestM = agg.cpuM
|
||||||
|
s.MemRequestMi = agg.memMi
|
||||||
|
if allocCPU > 0 {
|
||||||
|
s.CPUAllocPct = float64(agg.cpuM) / float64(allocCPU*1000) * 100
|
||||||
|
}
|
||||||
|
if allocMem > 0 {
|
||||||
|
s.MemAllocPct = float64(agg.memMi) / float64(allocMem) * 100
|
||||||
|
}
|
||||||
|
}
|
||||||
result = append(result, s)
|
result = append(result, s)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: result})
|
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "success", Data: result})
|
||||||
|
|||||||
@@ -116,15 +116,17 @@ export function KubernetesNodes() {
|
|||||||
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('system.terminalStatus')} </TableHead>
|
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('system.terminalStatus')} </TableHead>
|
||||||
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('deploy.detailDialog.config')} </TableHead>
|
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('deploy.detailDialog.config')} </TableHead>
|
||||||
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('server.usage')} </TableHead>
|
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('server.usage')} </TableHead>
|
||||||
|
{/* Request 占用:已申请 / 可分配,与"使用率"分列,避免一列塞四个条 // Logan.Li */}
|
||||||
|
<TableHead className="text-[var(--dg-text-secondary)]"> {i18n.t('k8s.node.requestUsage', { defaultValue: 'Request 占用' })} </TableHead>
|
||||||
<TableHead className="text-[var(--dg-text-secondary)]">Pods</TableHead>
|
<TableHead className="text-[var(--dg-text-secondary)]">Pods</TableHead>
|
||||||
<TableHead className="text-[var(--dg-text-secondary)]">Kubelet</TableHead>
|
<TableHead className="text-[var(--dg-text-secondary)]">Kubelet</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" /> {i18n.t('monitor.logPanel.logResults.loading')} </TableCell></TableRow>
|
<TableRow><TableCell colSpan={9} className="text-center text-[var(--dg-text-muted)] py-12"><Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" /> {i18n.t('monitor.logPanel.logResults.loading')} </TableCell></TableRow>
|
||||||
) : filtered.length === 0 ? (
|
) : filtered.length === 0 ? (
|
||||||
<TableRow><TableCell colSpan={8} className="text-center text-[var(--dg-text-muted)] py-12"><Server className="h-8 w-8 mx-auto mb-2 opacity-50" />{selectedCluster ? i18n.t('k8s.empty.nodes') : i18n.t('kubernetes.kubernetesnodes.sf8c38834')}</TableCell></TableRow>
|
<TableRow><TableCell colSpan={9} className="text-center text-[var(--dg-text-muted)] py-12"><Server className="h-8 w-8 mx-auto mb-2 opacity-50" />{selectedCluster ? i18n.t('k8s.empty.nodes') : i18n.t('kubernetes.kubernetesnodes.sf8c38834')}</TableCell></TableRow>
|
||||||
) : (
|
) : (
|
||||||
filtered.map((n) => (
|
filtered.map((n) => (
|
||||||
<TableRow key={n.id} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
<TableRow key={n.id} className="border-[var(--dg-border)] hover:bg-[var(--dg-bg-tertiary)]/30">
|
||||||
@@ -161,6 +163,38 @@ export function KubernetesNodes() {
|
|||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{(() => {
|
||||||
|
// Request 占用 = 该节点 Pod 已申请 / 可分配。
|
||||||
|
// 百分比给直观判断,tooltip 给绝对值(分子/分母一起看才有意义)。 // Logan.Li
|
||||||
|
const cpuPct = n.cpu_alloc_pct || 0;
|
||||||
|
const memPct = n.mem_alloc_pct || 0;
|
||||||
|
const cpuReq = n.cpu_request_m || 0;
|
||||||
|
const memReq = n.mem_request_mi || 0;
|
||||||
|
const bar = (val: number) => val >= 85 ? 'bg-[var(--dg-red)]' : val >= 60 ? 'bg-[var(--dg-orange)]' : 'bg-[var(--dg-blue)]';
|
||||||
|
const txt = (val: number) => val >= 85 ? 'text-[var(--dg-red)]' : val >= 60 ? 'text-[var(--dg-orange)]' : 'text-[var(--dg-text-secondary)]';
|
||||||
|
const cpuCap = (n.cpu || 0) * 1000;
|
||||||
|
const memCap = n.memory || 0;
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1 min-w-[120px]">
|
||||||
|
<div className="flex items-center gap-2" title={`CPU 已申请 ${cpuReq}m / 可分配 ${cpuCap}m`}>
|
||||||
|
<span className="text-xs text-[var(--dg-text-muted)] w-7">CPU</span>
|
||||||
|
<div className="flex-1 h-1.5 rounded-full bg-[var(--dg-bg-tertiary)]/50 overflow-hidden">
|
||||||
|
<div className={`h-full rounded-full ${bar(cpuPct)}`} style={{ width: `${Math.min(cpuPct, 100)}%` }} />
|
||||||
|
</div>
|
||||||
|
<span className={`text-xs w-9 text-right ${txt(cpuPct)}`}>{cpuPct.toFixed(0)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2" title={`内存 已申请 ${(memReq / 1024).toFixed(1)}Gi / 可分配 ${(memCap / 1024).toFixed(1)}Gi`}>
|
||||||
|
<span className="text-xs text-[var(--dg-text-muted)] w-7">Mem</span>
|
||||||
|
<div className="flex-1 h-1.5 rounded-full bg-[var(--dg-bg-tertiary)]/50 overflow-hidden">
|
||||||
|
<div className={`h-full rounded-full ${bar(memPct)}`} style={{ width: `${Math.min(memPct, 100)}%` }} />
|
||||||
|
</div>
|
||||||
|
<span className={`text-xs w-9 text-right ${txt(memPct)}`}>{memPct.toFixed(0)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{n.pod_count || 0} / {n.max_pods || '-'}</TableCell>
|
<TableCell className="text-sm text-[var(--dg-text-secondary)]">{n.pod_count || 0} / {n.max_pods || '-'}</TableCell>
|
||||||
<TableCell className="text-xs text-[var(--dg-text-muted)]">{n.kubelet_version || '-'}</TableCell>
|
<TableCell className="text-xs text-[var(--dg-text-muted)]">{n.kubelet_version || '-'}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -583,8 +583,9 @@ export function ServerManager() {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]">
|
<SelectContent className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]">
|
||||||
<SelectItem value="10">10</SelectItem>
|
<SelectItem value="10">10</SelectItem>
|
||||||
<SelectItem value="20">20</SelectItem>
|
|
||||||
<SelectItem value="30">30</SelectItem>
|
<SelectItem value="30">30</SelectItem>
|
||||||
|
<SelectItem value="50">50</SelectItem>
|
||||||
|
<SelectItem value="100">100</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<span>{t("server.items")}</span>
|
<span>{t("server.items")}</span>
|
||||||
|
|||||||
Reference in new issue
Block a user