diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index f788f7d184..b09b3beb27 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -309,7 +309,10 @@ regression protection. `WorkloadPanel` owns the mutually exclusive host/guest drawer handoff: clicking a grouped host row while a guest drawer is open must clear the selected guest and keep/focus the host summary group so the node drawer can - replace the guest drawer. + replace the guest drawer. Platform pages that render a dedicated host + table above an embedded Workloads table may disable the grouped host drawer + through the explicit Workloads surface option, but standalone Workloads + must keep the inline grouped-row drawer as the default. Compact icon headers inside `WorkloadTableHeader.tsx` may stay visually dense for responsive workload tables, but the icon must be decorative and the column label must remain present through an `sr-only` label so the diff --git a/frontend-modern/src/components/Workloads/StackedDiskBar.tsx b/frontend-modern/src/components/Workloads/StackedDiskBar.tsx index 47c8da8a7e..6793e18121 100644 --- a/frontend-modern/src/components/Workloads/StackedDiskBar.tsx +++ b/frontend-modern/src/components/Workloads/StackedDiskBar.tsx @@ -14,6 +14,7 @@ export function StackedDiskBar(props: StackedDiskBarProps) { if (!Number.isFinite(parsed)) return '0'; return String(Math.max(0, Math.min(parsed, 100))); }; + const invertPercent = (value: number) => String(100 - Math.max(0, Math.min(value, 100))); return (
@@ -204,9 +205,25 @@ export function StackedDiskBar(props: StackedDiskBarProps) { >
+ class="absolute inset-0 rounded-sm brightness-100 transition-[filter] duration-100 group-hover/disk:brightness-125" + > + +
)} diff --git a/frontend-modern/src/components/Workloads/WorkloadPanel.tsx b/frontend-modern/src/components/Workloads/WorkloadPanel.tsx index 498e795167..3e19453f3f 100644 --- a/frontend-modern/src/components/Workloads/WorkloadPanel.tsx +++ b/frontend-modern/src/components/Workloads/WorkloadPanel.tsx @@ -43,6 +43,7 @@ type WorkloadPanelProps = Pick< | 'groupedWindowing' | 'guestMetadata' | 'guestParentNodeMap' + | 'groupNodeDrawerMode' | 'groupingMode' | 'handleCustomUrlUpdate' | 'handleTagClick' @@ -380,14 +381,17 @@ export function WorkloadPanel(props: WorkloadPanelProps) { ); const shouldShowNodeDrawer = createMemo( () => + props.groupNodeDrawerMode() === 'inline' && Boolean(node()) && props.focusedSummaryWorkloadGroupId() === groupKey() && props.selectedGuestId() === null, ); + const canOpenNodeDrawer = () => props.groupNodeDrawerMode() === 'inline'; const handleGroupHoverChange = (next: SummarySeriesGroupScope | null) => { props.setHoveredWorkloadGroupScope(next); }; const handleGroupFocusToggle = () => { + if (!canOpenNodeDrawer()) return; const scope = groupSummaryScope(); const selectedGuestId = props.selectedGuestId(); const nextFocusedScope = @@ -445,7 +449,9 @@ export function WorkloadPanel(props: WorkloadPanelProps) { node={node()!} renderAs="tr" colspan={props.totalColumns()} - columns={props.compactGroupHeaders() ? undefined : props.workloadTableVisibleColumns()} + columns={ + props.compactGroupHeaders() ? undefined : props.workloadTableVisibleColumns() + } columnCellClass={ props.compactGroupHeaders() ? undefined : getGroupNodeColumnCellClass } @@ -456,15 +462,23 @@ export function WorkloadPanel(props: WorkloadPanelProps) { !props.compactGroupHeaders() && !props.workloadTableVisibleColumnIds().includes('info') } - trClass="cursor-pointer select-none duration-150" + trClass={ + canOpenNodeDrawer() + ? 'cursor-pointer select-none duration-150' + : 'select-none duration-150' + } trProps={{ - 'aria-expanded': shouldShowNodeDrawer() ? 'true' : 'false', + 'aria-expanded': canOpenNodeDrawer() + ? shouldShowNodeDrawer() + ? 'true' + : 'false' + : undefined, 'data-summary-group-id': groupKey(), 'data-summary-group-series-count': String( groupSummaryScope()?.seriesIds.length ?? 0, ), 'data-summary-row-active': isSummaryGroupHighlighted() ? 'true' : 'false', - onClick: handleGroupFocusToggle, + onClick: canOpenNodeDrawer() ? handleGroupFocusToggle : undefined, ...groupRowInteraction, }} /> diff --git a/frontend-modern/src/components/Workloads/WorkloadsSurface.tsx b/frontend-modern/src/components/Workloads/WorkloadsSurface.tsx index 6142e0862d..19ae06d6e1 100644 --- a/frontend-modern/src/components/Workloads/WorkloadsSurface.tsx +++ b/frontend-modern/src/components/Workloads/WorkloadsSurface.tsx @@ -147,6 +147,7 @@ export function WorkloadsSurface(props: WorkloadsSurfaceProps) { groupedWindowing={state.groupedWindowing} guestMetadata={state.guestMetadata} guestParentNodeMap={state.guestParentNodeMap} + groupNodeDrawerMode={state.groupNodeDrawerMode} groupingMode={state.groupingMode} handleCustomUrlUpdate={state.handleCustomUrlUpdate} handleSort={state.handleSort} diff --git a/frontend-modern/src/components/Workloads/WorkloadsTable.tsx b/frontend-modern/src/components/Workloads/WorkloadsTable.tsx index 5c4632cda4..95a9f00f04 100644 --- a/frontend-modern/src/components/Workloads/WorkloadsTable.tsx +++ b/frontend-modern/src/components/Workloads/WorkloadsTable.tsx @@ -20,6 +20,7 @@ type WorkloadsTableProps = Pick< | 'groupedWindowing' | 'guestMetadata' | 'guestParentNodeMap' + | 'groupNodeDrawerMode' | 'groupingMode' | 'handleCustomUrlUpdate' | 'handleSort' @@ -103,6 +104,7 @@ export function WorkloadsTable(props: WorkloadsTableProps) { groupedWindowing={props.groupedWindowing} guestMetadata={props.guestMetadata} guestParentNodeMap={props.guestParentNodeMap} + groupNodeDrawerMode={props.groupNodeDrawerMode} groupingMode={props.groupingMode} handleCustomUrlUpdate={props.handleCustomUrlUpdate} handleTagClick={props.handleTagClick} diff --git a/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx b/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx index 7940c7327a..d0aa58ff3b 100644 --- a/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/WorkloadsSurface.performance.contract.test.tsx @@ -370,7 +370,9 @@ describe('Workloads platform-page embed contract', () => { expect(stateSource).toContain( 'onMetricDisplayModeChange?: (value: WorkloadsMetricDisplayMode) => void;', ); - expect(stateSource).toContain('metricHistoryRange?: Accessor;'); + expect(stateSource).toContain( + 'metricHistoryRange?: Accessor;', + ); expect(stateSource).toContain( 'onMetricHistoryRangeChange?: (value: WorkloadTableMetricHistoryRange) => void;', ); @@ -381,17 +383,12 @@ describe('Workloads platform-page embed contract', () => { // The controls layer must short-circuit to the page-provided accessor + // change handler when supplied; the internal persistent signal stays as // the fallback so standalone usage keeps working. - expect(controlsSource).toContain( - 'options.metricDisplayMode ?? internalMetricDisplayMode', - ); + expect(controlsSource).toContain('options.metricDisplayMode ?? internalMetricDisplayMode'); expect(controlsSource).toContain('options.onMetricDisplayModeChange'); - expect(controlsSource).toContain( - 'options.metricHistoryRange ?? internalMetricHistoryRange', - ); + expect(controlsSource).toContain('options.metricHistoryRange ?? internalMetricHistoryRange'); - const proxmoxSource = ( - await import('../../../features/proxmox/ProxmoxPageSurface.tsx?raw') - ).default; + const proxmoxSource = (await import('../../../features/proxmox/ProxmoxPageSurface.tsx?raw')) + .default; expect(proxmoxSource).toContain('STORAGE_KEYS.WORKLOADS_METRIC_DISPLAY_MODE'); expect(proxmoxSource).toContain('STORAGE_KEYS.WORKLOADS_METRIC_HISTORY_RANGE'); expect(proxmoxSource).toContain('metricDisplayMode={metricDisplayMode}'); @@ -399,9 +396,8 @@ describe('Workloads platform-page embed contract', () => { expect(proxmoxSource).toContain('metricHistoryRange={metricHistoryRange}'); expect(proxmoxSource).toContain('onMetricHistoryRangeChange={setMetricHistoryRange}'); - const nodesTableSource = ( - await import('../../../features/proxmox/ProxmoxNodesTable.tsx?raw') - ).default; + const nodesTableSource = (await import('../../../features/proxmox/ProxmoxNodesTable.tsx?raw')) + .default; expect(nodesTableSource).toContain('useWorkloadTableMetricHistory'); expect(nodesTableSource).toContain('MetricMiniSparkline'); expect(nodesTableSource).toContain('isSparklineMode'); @@ -410,9 +406,7 @@ describe('Workloads platform-page embed contract', () => { it('exposes compactGroupHeaders so platform pages can strip duplicate host stats from group rows', async () => { const stateSource = (await import('../useWorkloadsState.ts?raw')).default; expect(stateSource).toContain('compactGroupHeaders?: boolean;'); - expect(stateSource).toContain( - 'compactGroupHeaders: () => props.compactGroupHeaders === true,', - ); + expect(stateSource).toContain('compactGroupHeaders: () => props.compactGroupHeaders === true,'); const tableSource = (await import('../WorkloadsTable.tsx?raw')).default; expect(tableSource).toContain(`| 'compactGroupHeaders'`); @@ -430,11 +424,36 @@ describe('Workloads platform-page embed contract', () => { ); expect(panelSource).toContain('!props.compactGroupHeaders() &&'); - const proxmoxSource = ( - await import('../../../features/proxmox/ProxmoxPageSurface.tsx?raw') - ).default; + const proxmoxSource = (await import('../../../features/proxmox/ProxmoxPageSurface.tsx?raw')) + .default; expect(proxmoxSource).toContain('compactGroupHeaders'); }); + + it('lets platform pages move grouped host drawers to a dedicated host table owner', async () => { + const stateSource = (await import('../useWorkloadsState.ts?raw')).default; + expect(stateSource).toContain("groupNodeDrawerMode?: 'inline' | 'disabled';"); + expect(stateSource).toContain( + "groupNodeDrawerMode: () => props.groupNodeDrawerMode ?? 'inline',", + ); + + const tableSource = (await import('../WorkloadsTable.tsx?raw')).default; + expect(tableSource).toContain(`| 'groupNodeDrawerMode'`); + expect(tableSource).toContain('groupNodeDrawerMode={props.groupNodeDrawerMode}'); + + const panelSource = (await import('../WorkloadPanel.tsx?raw')).default; + expect(panelSource).toContain("props.groupNodeDrawerMode() === 'inline'"); + expect(panelSource).toContain( + 'onClick: canOpenNodeDrawer() ? handleGroupFocusToggle : undefined', + ); + + const proxmoxPageSource = (await import('../../../features/proxmox/ProxmoxPageSurface.tsx?raw')) + .default; + const proxmoxNodesSource = (await import('../../../features/proxmox/ProxmoxNodesTable.tsx?raw')) + .default; + expect(proxmoxPageSource).toContain('groupNodeDrawerMode="disabled"'); + expect(proxmoxNodesSource).toContain('NodeDrawer'); + expect(proxmoxNodesSource).toContain('data-inline-node-detail-for={node.id}'); + }); }); describe('Workloads performance contract', () => { diff --git a/frontend-modern/src/components/Workloads/useWorkloadsState.ts b/frontend-modern/src/components/Workloads/useWorkloadsState.ts index 22e3907532..97d744db77 100644 --- a/frontend-modern/src/components/Workloads/useWorkloadsState.ts +++ b/frontend-modern/src/components/Workloads/useWorkloadsState.ts @@ -79,6 +79,10 @@ export interface WorkloadsSurfaceProps { // `compactGroupHeaders` strips those stats from the NodeGroupHeader rows // in grouped mode so the section dividers don't duplicate the info. compactGroupHeaders?: boolean; + // Default Workloads behavior owns grouped host row drawers inline. Platform + // pages with a dedicated host table can disable that drawer so host details + // open from the host-owned table instead of the embedded guest table. + groupNodeDrawerMode?: 'inline' | 'disabled'; // When a platform page owns the metric display mode + sparkline range // (so the same toggle drives both the page's hosts table and this // embedded workloads surface), pass the accessors + change handlers. @@ -454,9 +458,7 @@ export function useWorkloadsState(props: WorkloadsSurfaceProps) { navigate, nodeByInstance, namespaceFilterConfig, - platformFilterConfig: props.suppressPlatformFilter - ? () => undefined - : platformFilterConfig, + platformFilterConfig: props.suppressPlatformFilter ? () => undefined : platformFilterConfig, platformOptions, reconnect, reconnectSurface, @@ -522,6 +524,7 @@ export function useWorkloadsState(props: WorkloadsSurfaceProps) { ws, groupingMode, compactGroupHeaders: () => props.compactGroupHeaders === true, + groupNodeDrawerMode: () => props.groupNodeDrawerMode ?? 'inline', } as const; } diff --git a/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx b/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx index 609806dac9..ddcb08be1f 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxNodesTable.tsx @@ -1,7 +1,16 @@ -import { For, Show, createMemo, type Accessor, type Component, type JSX } from 'solid-js'; +import { + For, + Show, + createMemo, + createSignal, + type Accessor, + type Component, + type JSX, +} from 'solid-js'; import { StatusDot } from '@/components/shared/StatusDot'; import { ResponsiveMetricCell } from '@/components/shared/responsive'; import { TableCard } from '@/components/shared/TableCard'; +import { NodeDrawer } from '@/components/Workloads/NodeDrawer'; import { StackedMemoryBar } from '@/components/Workloads/StackedMemoryBar'; import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar'; import { MetricMiniSparkline } from '@/components/Workloads/MetricMiniSparkline'; @@ -33,6 +42,7 @@ import { type WorkloadsMetricDisplayMode } from '@/components/Workloads/workload import { type WorkloadTableMetricHistoryRange } from '@/components/Workloads/workloadMetricHistoryModel'; import type { Disk, Node as LegacyNode } from '@/types/api'; import type { Resource } from '@/types/resource'; +import { nodeFromResource } from '@/utils/resourceStateAdapters'; import { getResourceClusterLabel, getResourceNodeName, @@ -128,6 +138,7 @@ export const ProxmoxNodesTable: Component<{ emptyDescription: string; }> = (props) => { const breakpoint = useBreakpoint(); + const [selectedNodeId, setSelectedNodeId] = createSignal(null); const layoutMode = createMemo(() => getWorkloadTableLayoutMode(breakpoint.width())); const visibleColumns = createMemo(() => getProxmoxHostVisibleColumnsForLayout(layoutMode())); const visibleColumnIds = createMemo(() => visibleColumns().map((column) => column.id)); @@ -183,6 +194,18 @@ export const ProxmoxNodesTable: Component<{ {(node) => { const name = () => asTrimmedString(node.name) || node.id; + const drawerNode = () => nodeFromResource(node); + const detailRowId = () => `proxmox-host-drawer-${node.id}`; + const isSelected = () => selectedNodeId() === node.id; + const toggleNodeDrawer = () => + setSelectedNodeId((current) => (current === node.id ? null : node.id)); + const handleActivationKey: JSX.EventHandler = ( + event, + ) => { + if (event.key !== 'Enter' && event.key !== ' ') return; + event.preventDefault(); + toggleNodeDrawer(); + }; const version = () => asTrimmedString(getResourceVersion(node)); const cluster = () => getResourceClusterLabel(node); const counts = () => countGuestsForNode(props.guests, getResourceNodeName(node)); @@ -397,9 +420,42 @@ export const ProxmoxNodesTable: Component<{ }; return ( - - {(column) => renderColumnCell(column)} - + <> + + {(column) => renderColumnCell(column)} + + + {(selectedNode) => ( + + +
event.stopPropagation()} + > + +
+
+
+ )} +
+ ); }}
diff --git a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx index f150ac83c3..d8d3e6178d 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx @@ -145,6 +145,7 @@ export function ProxmoxPageSurface() { suppressPlatformFilter forcedPlatform={PROXMOX_PLATFORM_FILTER} compactGroupHeaders + groupNodeDrawerMode="disabled" metricDisplayMode={metricDisplayMode} onMetricDisplayModeChange={setMetricDisplayMode} metricHistoryRange={metricHistoryRange} diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxNodesTable.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxNodesTable.test.tsx new file mode 100644 index 0000000000..f9dc680879 --- /dev/null +++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxNodesTable.test.tsx @@ -0,0 +1,102 @@ +import { cleanup, fireEvent, render, screen } from '@solidjs/testing-library'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import type { Resource } from '@/types/resource'; +import { ProxmoxNodesTable } from '../ProxmoxNodesTable'; + +const nodeDrawerMock = vi.hoisted(() => vi.fn()); + +vi.mock('@/hooks/useBreakpoint', () => ({ + useBreakpoint: () => ({ + width: () => 1280, + }), +})); + +vi.mock('@/components/shared/responsive', () => ({ + ResponsiveMetricCell: () =>
, +})); + +vi.mock('@/components/Workloads/StackedMemoryBar', () => ({ + StackedMemoryBar: () =>
, +})); + +vi.mock('@/components/Workloads/StackedDiskBar', () => ({ + StackedDiskBar: () =>
, +})); + +vi.mock('@/components/Workloads/MetricMiniSparkline', () => ({ + MetricMiniSparkline: () =>
, +})); + +vi.mock('@/components/shared/TemperatureGauge', () => ({ + TemperatureGauge: () =>
, +})); + +vi.mock('@/components/Workloads/useWorkloadTableMetricHistory', () => ({ + useWorkloadTableMetricHistory: () => ({ + getNodeMetricSeries: () => [], + }), +})); + +vi.mock('@/components/Workloads/NodeDrawer', () => ({ + NodeDrawer: (props: { node: { name: string } }) => { + nodeDrawerMock(props.node); + return
{props.node.name}
; + }, +})); + +const makeNodeResource = (overrides: Partial = {}): Resource => ({ + id: 'agent:pve-node-1', + name: 'pve-node-1', + displayName: 'pve-node-1', + platformId: 'homelab', + platformType: 'proxmox-pve', + sourceType: 'hybrid', + status: 'online', + type: 'agent', + lastSeen: 1_700_000_000_000, + cpu: { current: 42 }, + memory: { total: 8_000, used: 3_200, free: 4_800, current: 40 }, + disk: { total: 10_000, used: 4_500, free: 5_500, current: 45 }, + proxmox: { + clusterName: 'homelab', + nodeName: 'pve-node-1', + pveVersion: 'pve-manager/9.1.9/ee7bad0a3d1546c9', + }, + ...overrides, +}); + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +describe('ProxmoxNodesTable', () => { + it('opens the host details drawer from the host-owned top table row', async () => { + render(() => ( + } + emptyTitle="No Proxmox VE nodes" + emptyDescription="No nodes" + /> + )); + + const row = screen.getByText('pve-node-1').closest('tr'); + expect(row).toBeTruthy(); + expect(row).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByTestId('node-drawer')).not.toBeInTheDocument(); + + await fireEvent.click(row!); + + expect(row).toHaveAttribute('aria-expanded', 'true'); + expect(screen.getByTestId('node-drawer')).toHaveTextContent('pve-node-1'); + expect(nodeDrawerMock).toHaveBeenCalledWith(expect.objectContaining({ name: 'pve-node-1' })); + + await fireEvent.click(row!); + + expect(row).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByTestId('node-drawer')).not.toBeInTheDocument(); + }); +}); diff --git a/frontend-modern/src/features/proxmox/__tests__/proxmoxDrawerPlacement.contract.test.ts b/frontend-modern/src/features/proxmox/__tests__/proxmoxDrawerPlacement.contract.test.ts new file mode 100644 index 0000000000..311fe4dfda --- /dev/null +++ b/frontend-modern/src/features/proxmox/__tests__/proxmoxDrawerPlacement.contract.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; + +import workloadsStateSource from '@/components/Workloads/useWorkloadsState.ts?raw'; +import workloadPanelSource from '@/components/Workloads/WorkloadPanel.tsx?raw'; +import workloadsTableSource from '@/components/Workloads/WorkloadsTable.tsx?raw'; +import proxmoxNodesTableSource from '../ProxmoxNodesTable.tsx?raw'; +import proxmoxPageSurfaceSource from '../ProxmoxPageSurface.tsx?raw'; + +describe('Proxmox drawer placement contract', () => { + it('keeps host details owned by the Proxmox host table instead of the embedded guest table', () => { + expect(workloadsStateSource).toContain("groupNodeDrawerMode?: 'inline' | 'disabled';"); + expect(workloadsStateSource).toContain( + "groupNodeDrawerMode: () => props.groupNodeDrawerMode ?? 'inline',", + ); + expect(workloadsTableSource).toContain(`| 'groupNodeDrawerMode'`); + expect(workloadsTableSource).toContain('groupNodeDrawerMode={props.groupNodeDrawerMode}'); + expect(workloadPanelSource).toContain("props.groupNodeDrawerMode() === 'inline'"); + expect(workloadPanelSource).toContain( + 'onClick: canOpenNodeDrawer() ? handleGroupFocusToggle : undefined', + ); + expect(proxmoxPageSurfaceSource).toContain('groupNodeDrawerMode="disabled"'); + expect(proxmoxNodesTableSource).toContain('NodeDrawer'); + expect(proxmoxNodesTableSource).toContain('data-inline-node-detail-for={node.id}'); + }); +}); diff --git a/frontend-modern/src/features/proxmox/__tests__/proxmoxHostTableModel.test.ts b/frontend-modern/src/features/proxmox/__tests__/proxmoxHostTableModel.test.ts index 6ea6455da1..76a11d0fe6 100644 --- a/frontend-modern/src/features/proxmox/__tests__/proxmoxHostTableModel.test.ts +++ b/frontend-modern/src/features/proxmox/__tests__/proxmoxHostTableModel.test.ts @@ -13,10 +13,10 @@ describe('proxmoxHostTableModel', () => { expect(ids).toEqual(['node', 'cpu', 'memory', 'disk']); expect(getProxmoxHostTableMinWidthClass('mobile')).toBe('min-w-full'); - expect(getProxmoxHostColumnWidthStyle('node', 'mobile', ids)).toEqual({ width: '38%' }); - expect(getProxmoxHostColumnWidthStyle('cpu', 'mobile', ids)).toEqual({ width: '20%' }); - expect(getProxmoxHostColumnWidthStyle('memory', 'mobile', ids)).toEqual({ width: '21%' }); - expect(getProxmoxHostColumnWidthStyle('disk', 'mobile', ids)).toEqual({ width: '21%' }); + expect(getProxmoxHostColumnWidthStyle('node', 'mobile', ids)).toEqual({ width: '44%' }); + expect(getProxmoxHostColumnWidthStyle('cpu', 'mobile', ids)).toEqual({ width: '23%' }); + expect(getProxmoxHostColumnWidthStyle('memory', 'mobile', ids)).toEqual({ width: '23%' }); + expect(getProxmoxHostColumnWidthStyle('disk', 'mobile', ids)).toEqual({ width: '10%' }); }); it('adds temperature and guest counts before slower-changing metadata on tablet', () => { @@ -50,7 +50,7 @@ describe('proxmoxHostTableModel', () => { expect(getProxmoxHostTableMinWidthClass('compact')).toBe('min-w-[1080px]'); expect(getProxmoxHostTableMinWidthClass('wide')).toBe('min-w-[1080px]'); expect(getProxmoxHostColumnWidthStyle('cluster', 'compact', compactIds)).toEqual({ - width: '12%', + width: '13.0435%', }); }); });