From bacff3e2ebbb53dfa54881132d971e11e50d452e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 2 Aug 2026 21:55:18 +0100 Subject: [PATCH] Align Proxmox totals with filtered workloads --- .../subsystems/frontend-primitives.md | 4 +++ .../internal/subsystems/storage-recovery.md | 5 +++ frontend-modern/browser-verification.json | 36 ++++++++----------- .../features/proxmox/ProxmoxPageSurface.tsx | 15 ++++---- .../__tests__/ProxmoxBackupsTable.test.tsx | 12 +++++++ .../ProxmoxPageSurface.contract.test.tsx | 28 +++++++++++++-- 6 files changed, 68 insertions(+), 32 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index d0fe4fcec..679ce24ef 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -3902,6 +3902,10 @@ navigation (`pulse:filterbar:saved-views:`); `SavedViewsMenu` owns the dropdown chrome. A "view" is the page's URL query string at save time, so saved views double as shareable links: copying the bar URL after applying a view gives someone else the exact filtered state. +Counts or orientation strips presented as part of a filtered resource table +must derive from that table's canonical filtered collection as well. They must +not continue showing page-wide inventory totals after FilterBar state, saved +views, or search has narrowed the rows the operator can see. Implicit "remember last filters" is intentionally not added — defaulting to yesterday's filter state on a monitoring page hides real problems. That same shared filter-toolbar boundary also owns controlled select continuity diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 42180bbb6..7e8cbc7e6 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -468,6 +468,11 @@ presentation only. Comparing a VM or LXC's used bytes with its parent-node memory total may change the memory bar and memory sort order, but it must not change Backup-column freshness, protection coverage, restore-point correlation, storage-health state, or recovery authority. +The adjacent Proxmox overview guest-totals strip is likewise presentation-only +and must consume the embedded Workloads surface's filtered statistics. It must +not fall back to page-wide guest summary counts when search, type, status, or +node filters have narrowed the visible workload inventory; backup and recovery +authority remain unchanged by either set of presentation counts. That overview Backup signal belongs to Proxmox VMs and LXCs. If the embedded Workloads table demotes Docker-in-LXC `app-container` rows out of peer membership, the backup surface must still receive the page model's Proxmox diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index fac590dae..f04dd39c9 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,13 +1,13 @@ { "version": 1, - "base_sha": "b3dbc26d545b7526f62ed466884c4006f07cf8cb", - "verified_at": "2026-08-02T20:27:26Z", + "base_sha": "c143a7cf322b407e8091ca4e27a16e92a5178cef", + "verified_at": "2026-08-02T20:53:27Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/shared/searchInputEnhancementsModel.ts" + "frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx" ], "content_sha256": { - "frontend-modern/src/components/shared/searchInputEnhancementsModel.ts": "cd34929cd632fcf6eda163b34def13df0666d91f279e5df9e329ee8b0b05ee33" + "frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx": "3dafdf4fec6e6843082f347258c7913f96bc1bf9fc23b10e2461d87ffa90110f" }, "routes": [ "/proxmox/overview" @@ -17,31 +17,23 @@ "width": 1280, "height": 900 }, - { - "width": 640, - "height": 800 - }, { "width": 390, "height": 844 } ], "states": [ - "Desktop default filter toolbar", - "Desktop empty search-history menu", - "Desktop populated search-history menu", - "Desktop active search with history closed", - "Tablet-width empty search-history menu", - "Phone-width populated search-history menu", - "Restored empty search and cleared history" + "Desktop filtered saved view with one stopped VM", + "Phone-width filtered saved view with one stopped VM", + "Desktop zero-result Attention filter", + "Desktop unfiltered workload inventory", + "Clean unfiltered state after saved-view removal and reload" ], "interactions": [ - "Opened the empty search-history menu and confirmed it remained bounded to 512px on the 1280px desktop viewport", - "Confirmed the same menu used the full available search width at 640px and 390px", - "Measured zero document-level horizontal overflow at all three verified widths", - "Entered edge, committed it with Enter, and confirmed the search filtered the workload table", - "Reopened history and confirmed the populated entry and clear-history action rendered within the bounded menu", - "Closed history and confirmed the active-search filter toolbar remained usable", - "Cleared history and the active search, then confirmed the overview returned to its clean default state" + "Confirmed the one-row VMs, Stopped, and Node pve1 view rendered 0 running and 1 stopped instead of estate-wide totals", + "Inspected the filtered totals strip at 1280x900 and 390x844 for alignment, clipping, and horizontal overflow", + "Cleared the saved filters and confirmed the totals returned to the full visible workload collection", + "Applied the Attention filter and confirmed the totals strip was absent beside the canonical zero-result state", + "Removed the temporary saved view, cleared filters, reloaded the route, and confirmed no saved default or filter state remained" ] } diff --git a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx index c4e57a2ad..01b1cee6f 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx @@ -299,6 +299,7 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) { workloadsState.surfaceInitialDataReceived() && workloadsState.allGuests().length > 0, ); + const visibleGuestStats = createMemo(() => workloadsState.totalStats()); const filteredNodes = createMemo(() => filterProxmoxNodesForSearch( currentModel().pveNodes, @@ -373,28 +374,28 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) { emptyStateTitle="No Proxmox workloads" emptyStateDescription="Proxmox VMs and LXCs appear here when inventory is available." /> - {/* v5 dashboard closed with a running/stopped totals strip; the counts - are platform-wide, matching the status-chip vocabulary above. */} - 0}> + {/* Keep this orientation strip aligned with the rows the operator can + currently see rather than the unfiltered page-wide inventory. */} + 0}>
- {currentModel().summary.runningGuestCount} running + {visibleGuestStats().running} running - 0}> + 0}> - {currentModel().summary.degradedGuestCount} attention + {visibleGuestStats().degraded} attention - {currentModel().summary.stoppedGuestCount} stopped + {visibleGuestStats().stopped} stopped
diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx index 8fdb28a7d..52934e17e 100644 --- a/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx +++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxBackupsTable.test.tsx @@ -377,6 +377,18 @@ describe('ProxmoxBackupsTable', () => { expect(proxmoxPageSurfaceSource).not.toContain('workloads={workloadsState.allGuests'); }); + it('keeps Overview guest totals aligned with the filtered Workloads collection', () => { + expect(proxmoxPageSurfaceSource).toContain( + 'const visibleGuestStats = createMemo(() => workloadsState.totalStats())', + ); + expect(proxmoxPageSurfaceSource).not.toContain( + 'currentModel().summary.runningGuestCount} running', + ); + expect(proxmoxPageSurfaceSource).not.toContain( + 'currentModel().summary.stoppedGuestCount} stopped', + ); + }); + it('keeps the shared storage surface scoped to the whole Proxmox product family', () => { expect(proxmoxPageSurfaceSource).toContain("const PROXMOX_PLATFORM_FILTER = 'proxmox-all';"); expect(proxmoxPageSurfaceSource).toContain('forcedSourceFilter={PROXMOX_PLATFORM_FILTER}'); diff --git a/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx b/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx index 2bcec806a..973e60099 100644 --- a/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx +++ b/frontend-modern/src/features/proxmox/__tests__/ProxmoxPageSurface.contract.test.tsx @@ -7,6 +7,7 @@ const mockUseUnifiedResources = vi.fn(); const mockPathname = vi.hoisted(() => vi.fn(() => '/proxmox/overview')); const mockVersionInfo = vi.hoisted(() => vi.fn()); const mockStorageProps = vi.hoisted(() => vi.fn()); +const mockTotalStats = vi.hoisted(() => vi.fn()); const makeResource = (resource: Partial & Pick): Resource => ({ @@ -81,6 +82,7 @@ vi.mock('@/components/Workloads/useWorkloadsState', () => ({ surfaceConnected: () => false, surfaceInitialDataReceived: () => false, allGuests: () => [], + totalStats: mockTotalStats, search: () => '', setSearch: vi.fn(), }), @@ -129,6 +131,16 @@ describe('ProxmoxPageSurface contract', () => { beforeEach(() => { mockPathname.mockReturnValue('/proxmox/overview'); mockVersionInfo.mockReturnValue(null); + mockTotalStats.mockReturnValue({ + total: 3, + running: 1, + degraded: 1, + stopped: 1, + vms: 3, + containers: 0, + appContainers: 0, + pods: 0, + }); }); afterEach(() => { @@ -186,7 +198,7 @@ describe('ProxmoxPageSurface contract', () => { ); }); - it('renders the v5-style guest totals strip from the page summary', () => { + it('renders guest totals from the filtered workload collection', () => { setResources([ makeResource({ id: 'agent:pve-1', @@ -212,12 +224,22 @@ describe('ProxmoxPageSurface contract', () => { proxmox: { nodeName: 'pve-1', vmid: 102 }, }), ]); + mockTotalStats.mockReturnValue({ + total: 1, + running: 0, + degraded: 0, + stopped: 1, + vms: 1, + containers: 0, + appContainers: 0, + pods: 0, + }); render(() => ); const totals = screen.getByTestId('proxmox-guest-totals'); - expect(totals).toHaveTextContent('1 running'); - expect(totals).toHaveTextContent('1 attention'); + expect(totals).toHaveTextContent('0 running'); + expect(totals).not.toHaveTextContent('attention'); expect(totals).toHaveTextContent('1 stopped'); });