From 95758b6efd73117b164ee7906fdc7ca2677ebfb5 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 17 May 2026 15:20:12 +0100 Subject: [PATCH] Show host disks as sub-rows under the Proxmox hosts table The disk column was cramming N inline lanes into a ~197x16 cell, which fell apart visually past three disks and forced label/percent collisions. Hosts table is small (a handful of rows), so use the vertical space: each multi-disk host gets one sub-row per disk beneath it, with the sub-row colspan'd from the disk column through cluster so name, bar, percent, and used/total all fit naturally. The host row's disk cell collapses to a neutral "N disks" pill with a worst-threshold dot, so nothing is hidden but no single disk is singled out by name. --- .../features/proxmox/ProxmoxNodesTable.tsx | 133 ++++++++++++++++-- 1 file changed, 125 insertions(+), 8 deletions(-) diff --git a/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx index 38085f53e..61f3e97bd 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx @@ -17,7 +17,8 @@ import { } from '@/components/shared/Table'; import { getSimpleStatusIndicator } from '@/utils/status'; import { asTrimmedString } from '@/utils/stringUtils'; -import { normalizeDiskArray } from '@/utils/format'; +import { formatBytes, formatPercent, normalizeDiskArray } from '@/utils/format'; +import { getMetricColorRgba } from '@/utils/metricThresholds'; import { buildMetricKeyForUnifiedResource } from '@/utils/metricsKeys'; import { useWorkloadTableMetricHistory } from '@/components/Workloads/useWorkloadTableMetricHistory'; import { getWorkloadTableLayoutMode } from '@/components/Workloads/guestRowModel'; @@ -118,6 +119,86 @@ const formatPercentLabel = (value: number | null | undefined): string => { return `${Math.round(Math.max(0, normalized))}%`; }; +const getDiskUsagePercent = (disk: Disk): number => { + if (disk.total > 0) return (disk.used / disk.total) * 100; + if (Number.isFinite(disk.usage)) return disk.usage <= 1 ? disk.usage * 100 : disk.usage; + return 0; +}; + +const getDiskShortLabel = (disk: Disk, index: number): string => { + const raw = (disk.mountpoint || disk.device || `Disk ${index + 1}`).trim(); + if (raw.startsWith('/dev/')) return raw.slice('/dev/'.length); + if (raw === '/') return '/'; + if (raw.startsWith('/')) { + const parts = raw.split('/').filter(Boolean); + if (parts.length > 0) return parts[parts.length - 1]; + } + return raw; +}; + +const getWorstDiskPercent = (disks: Disk[]): number => { + let worst = 0; + for (const disk of disks) { + const pct = getDiskUsagePercent(disk); + if (pct > worst) worst = pct; + } + return worst; +}; + +const DISK_COUNT_BADGE_BASE = + 'inline-flex items-center gap-1.5 rounded-md bg-surface-alt px-2 py-0.5 text-[11px] font-medium text-base-content'; + +const ProxmoxHostDiskSubRow: Component<{ + disk: Disk; + index: number; + visibleColumns: ProxmoxHostTableColumn[]; +}> = (subProps) => { + const percent = () => getDiskUsagePercent(subProps.disk); + const fillPercent = () => Math.min(percent(), 100); + const color = () => getMetricColorRgba(percent(), 'disk'); + const label = () => getDiskShortLabel(subProps.disk, subProps.index); + const fullPath = () => + subProps.disk.mountpoint || subProps.disk.device || `Disk ${subProps.index + 1}`; + const usedLabel = () => formatBytes(subProps.disk.used); + const totalLabel = () => + subProps.disk.total > 0 ? formatBytes(subProps.disk.total) : '—'; + + const diskIdx = () => subProps.visibleColumns.findIndex((c) => c.id === 'disk'); + const leadingColumns = () => subProps.visibleColumns.slice(0, diskIdx()); + const trailingSpan = () => subProps.visibleColumns.length - diskIdx(); + + return ( + + + {(column) => ( +   + )} + + +
+ + {label()} +
+
+
+ + {formatPercent(percent())} + + + {usedLabel()} / {totalLabel()} + +
+ + + ); +}; + export const ProxmoxNodesTable: Component<{ nodes: Resource[]; guests: Resource[]; @@ -205,6 +286,7 @@ export const ProxmoxNodesTable: Component<{ ? node.memory.current : undefined; const diskPercent = () => node.disk?.current ?? 0; + const diskList = (): Disk[] => normalizeDiskArray(node.agent?.disks) ?? []; const aggregateDisk = (): Disk | undefined => node.disk ? ({ @@ -333,10 +415,32 @@ export const ProxmoxNodesTable: Component<{
} > - + 1} + fallback={ + + } + > + + + } > @@ -392,9 +496,22 @@ export const ProxmoxNodesTable: Component<{ }; return ( - - {(column) => renderColumnCell(column)} - + <> + + {(column) => renderColumnCell(column)} + + 1}> + + {(disk, index) => ( + + )} + + + ); }}