mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Align Proxmox totals with filtered workloads
This commit is contained in:
@@ -3902,6 +3902,10 @@ navigation (`pulse:filterbar:saved-views:<key>`); `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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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. */}
|
||||
<Show when={currentModel().summary.guestCount > 0}>
|
||||
{/* Keep this orientation strip aligned with the rows the operator can
|
||||
currently see rather than the unfiltered page-wide inventory. */}
|
||||
<Show when={visibleGuestStats().total > 0}>
|
||||
<div
|
||||
class="flex items-center gap-2 rounded border border-border bg-surface-alt px-2 py-1 text-xs text-muted"
|
||||
data-testid="proxmox-guest-totals"
|
||||
>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<StatusDot size="xs" variant="success" ariaHidden />
|
||||
{currentModel().summary.runningGuestCount} running
|
||||
{visibleGuestStats().running} running
|
||||
</span>
|
||||
<Show when={currentModel().summary.degradedGuestCount > 0}>
|
||||
<Show when={visibleGuestStats().degraded > 0}>
|
||||
<span aria-hidden="true">|</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<StatusDot size="xs" variant="warning" ariaHidden />
|
||||
{currentModel().summary.degradedGuestCount} attention
|
||||
{visibleGuestStats().degraded} attention
|
||||
</span>
|
||||
</Show>
|
||||
<span aria-hidden="true">|</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<StatusDot size="xs" variant="danger" ariaHidden />
|
||||
{currentModel().summary.stoppedGuestCount} stopped
|
||||
{visibleGuestStats().stopped} stopped
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -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}');
|
||||
|
||||
+25
-3
@@ -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<Resource> & Pick<Resource, 'id' | 'type'>): 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(() => <ProxmoxPageSurface />);
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user