From 6ac02408c0a1231c3e8c6dd97ec228af4aefafc5 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 20 May 2026 12:48:36 +0100 Subject: [PATCH] Normalize Proxmox drawer tables --- .../platformOverviewLayout.guardrails.test.ts | 21 ++ .../proxmox/ProxmoxCephClusterDrawer.tsx | 158 ++++++++----- .../proxmox/ProxmoxMailGatewayDrawer.tsx | 213 ++++++++++-------- 3 files changed, 237 insertions(+), 155 deletions(-) diff --git a/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts b/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts index c6fd5ebc3f..79118a1e81 100644 --- a/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts +++ b/frontend-modern/src/features/platformPage/__tests__/platformOverviewLayout.guardrails.test.ts @@ -7,7 +7,9 @@ import kubernetesDeploymentsTableSource from '@/features/kubernetes/KubernetesDe import kubernetesNodesTableSource from '@/features/kubernetes/KubernetesNodesTable.tsx?raw'; import kubernetesPageSurfaceSource from '@/features/kubernetes/KubernetesPageSurface.tsx?raw'; import proxmoxBackupsTableSource from '@/features/proxmox/ProxmoxBackupsTable.tsx?raw'; +import proxmoxCephClusterDrawerSource from '@/features/proxmox/ProxmoxCephClusterDrawer.tsx?raw'; import proxmoxCephTableSource from '@/features/proxmox/ProxmoxCephTable.tsx?raw'; +import proxmoxMailGatewayDrawerSource from '@/features/proxmox/ProxmoxMailGatewayDrawer.tsx?raw'; import proxmoxMailGatewayTableSource from '@/features/proxmox/ProxmoxMailGatewayTable.tsx?raw'; import proxmoxNodesTableSource from '@/features/proxmox/ProxmoxNodesTable.tsx?raw'; import proxmoxPageSurfaceSource from '@/features/proxmox/ProxmoxPageSurface.tsx?raw'; @@ -60,6 +62,11 @@ const proxmoxBespokeTableSources = [ proxmoxReplicationTableSource, ]; +const proxmoxInlineDetailTableSources = [ + proxmoxCephClusterDrawerSource, + proxmoxMailGatewayDrawerSource, +]; + describe('platform overview layout guardrails', () => { it('keeps platform inventory tables on the shared dense table styling contract', () => { expect(sharedPlatformPageSource).toContain('PLATFORM_TABLE_CARD_CLASS'); @@ -111,6 +118,20 @@ describe('platform overview layout guardrails', () => { } }); + it('keeps Proxmox inline detail tables on shared platform table primitives', () => { + for (const source of proxmoxInlineDetailTableSources) { + expect(source).toContain("from '@/components/shared/Table'"); + expect(source).toContain('PLATFORM_TABLE_HEADER_ROW_CLASS'); + expect(source).toContain('PLATFORM_TABLE_BODY_CLASS'); + expect(source).toContain('getPlatformTableHeadClassForKind'); + expect(source).toContain('getPlatformTableCellClassForKind'); + expect(source).not.toContain(' { for (const source of overviewSurfaceSources) { expect(source).toContain('
'); diff --git a/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx b/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx index b3bf0db42e..63baca9b1e 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx @@ -3,6 +3,20 @@ import XIcon from 'lucide-solid/icons/x'; import { Card } from '@/components/shared/Card'; import { ProgressBar } from '@/components/shared/ProgressBar'; import { StatusDot } from '@/components/shared/StatusDot'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/shared/Table'; +import { + PLATFORM_TABLE_BODY_CLASS, + PLATFORM_TABLE_HEADER_ROW_CLASS, + getPlatformTableCellClassForKind, + getPlatformTableHeadClassForKind, +} from '@/features/platformPage/sharedPlatformPage'; import type { StatusIndicatorVariant } from '@/utils/status'; import { formatBytes } from '@/utils/format'; import { getMetricColorClass } from '@/utils/metricThresholds'; @@ -48,11 +62,7 @@ function capacityToneFor(percent: number): string { return getMetricColorClass(percent, 'disk'); } -function CapacityBar(props: { - used: number; - total: number; - percent: number; -}) { +function CapacityBar(props: { used: number; total: number; percent: number }) { const clamped = Math.max(0, Math.min(100, props.percent)); return ( - - +
@@ -146,40 +151,58 @@ export const ProxmoxCephClusterDrawer: Component<{ when={pools().length > 0} fallback={

No pools reported.

} > - - - - - - - - - - - +
PoolObjectsStoredAvailUsed
+ + + Pool + + Objects + + + Stored + + + Avail + + + Used + + + + {(pool) => ( - - - - - - - + + )} - -
{pool.name || '—'} + + + {pool.name || '—'} + + {pool.objects.toLocaleString()} - + + {formatBytes(pool.storedBytes)} - + + {formatBytes(pool.availableBytes)} - + +
-
+ +
@@ -194,38 +217,59 @@ export const ProxmoxCephClusterDrawer: Component<{ when={services().length > 0} fallback={

No services reported.

} > - - - - - - - - - - +
ServiceStatusRunningTotal
+ + + Service + Status + + Running + + + Total + + + + {(svc) => { const cls = classifyService(svc); return ( - - - - - - + + + {svc.running} + + + {svc.total} + + ); }} - -
+ + {svc.type} - + +
- - {cls.label} + + + {cls.label} +
-
{svc.running}{svc.total}
+ +
diff --git a/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx b/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx index 18d4b969b4..cc88005856 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx @@ -1,23 +1,26 @@ -import { - For, - Show, - createMemo, - createResource, - type Component, -} from 'solid-js'; +import { For, Show, createMemo, createResource, type Component } from 'solid-js'; import XIcon from 'lucide-solid/icons/x'; import { Card } from '@/components/shared/Card'; import { StatusDot } from '@/components/shared/StatusDot'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/shared/Table'; +import { + PLATFORM_TABLE_BODY_CLASS, + PLATFORM_TABLE_HEADER_ROW_CLASS, + getPlatformTableCellClassForKind, + getPlatformTableHeadClassForKind, +} from '@/features/platformPage/sharedPlatformPage'; import type { StatusIndicatorVariant } from '@/utils/status'; import { formatBytes } from '@/utils/format'; import { asTrimmedString } from '@/utils/stringUtils'; import { apiFetch } from '@/utils/apiClient'; -import type { - PMGInstance, - PMGNodeStatus, - PMGQueueStatus, - PMGSpamBucket, -} from '@/types/api'; +import type { PMGInstance, PMGNodeStatus, PMGQueueStatus, PMGSpamBucket } from '@/types/api'; import type { Resource } from '@/types/resource'; // Inline drawer for a single Proxmox Mail Gateway instance. Drops the @@ -113,9 +116,7 @@ function StackedBar(props: { segments: StackedSegment[]; ariaLabel: string }) {
0} - fallback={ -
- } + fallback={
} >
- Math.max(0, ...props.buckets.map((b) => Math.max(0, b.count))), - ); + const maxCount = createMemo(() => Math.max(0, ...props.buckets.map((b) => Math.max(0, b.count)))); return ( -