Sync Proxmox node table with workload search

This commit is contained in:
rcourtman
2026-08-23 19:31:34 +01:00
parent 35b8c0a4c2
commit 771d583f4a
5 changed files with 45 additions and 31 deletions
@@ -1613,6 +1613,11 @@ Agent`), with the plain-language source phrase available through accessible
and responsive/mobile rows must present the same preferred label while
retaining native diagnostics; page-local formatting such as
`cluster (node)` must not replace the backend-authored presentation.
On the Proxmox overview, the committed Workloads search is shared state for
both the guest table and the node table: guest matches retain their owning
node, direct node matches retain that node, and unrelated nodes are hidden.
The node table must use the same provider-scoped search vocabulary rather
than receiving an unfiltered copy of the estate.
8. Keep summary chart interaction identity on one shared helper. Summary surfaces that expose row-hover, group-hover, chart-hover, or route-focus-driven chart emphasis must derive page/group/entity scope through `frontend-modern/src/components/shared/summaryCardInteraction.ts` and pass that same resolved scope into card-state, sparkline, and density-map primitives, rather than letting cards read `hovered || focused` while charts listen to a different page-local ID source. Hovering one summary chart must promote that series into the shared active entity so sibling cards highlight the same object instead of keeping chart-local hover islands, and hovering or pinning a workload group header, infrastructure cluster header, or storage pool-group header must scope the matching summary cards through that same shared contract instead of forking a page-local summary filter path. Sibling cards should surface that synchronized hover as one compact header readout through the shared summary-card contract, while the chart under the pointer keeps the only floating tooltip. Recovery is explicitly outside this interaction dialect: its retired posture-card strip must not return with row/group/chart hover behavior without a separate governed product decision.
9. Keep page summaries page-scoped when table rows enter contextual focus. Route-backed row selection may add a focused label and shared series emphasis, but infrastructure, workloads, and storage summary cards must continue to render the page-level series set instead of collapsing the summary down to the selected row or replacing the global trend view with row-local empty states.
10. Keep contextual row focus on the shared summary primitive. Summary surfaces and same-route table drill-ins must reuse `frontend-modern/src/components/shared/contextualFocus.ts` for interactive-series filtering, focused-name lookup, active-series derivation, local scroll preservation, and deliberate inline-detail reveal instead of rebuilding page-local `Set` filters, focused-label scans, drawer-aware scroll math, or ad hoc scroll restoration in each surface.
@@ -437,10 +437,13 @@ That bounded node preview remains before the virtual guest inventory at every
viewport; narrow layouts must not move it after the full guest list's virtual
scroll extent, where the node estate would become effectively undiscoverable.
The workload filter belongs immediately before the workload table it controls,
after the node preview; it must not filter the node table. The nodes table must
always receive the real already-loaded PVE node collection, so committed or
draft workload terms can never manufacture the estate-empty `No Proxmox VE
nodes` state. That empty state is reserved for genuinely absent node inventory.
after the node preview, and its committed search term must also flow into the
node table. The node table must use the shared Proxmox search model so a
matching guest retains its owning node while unrelated nodes disappear; a
node search must likewise retain the directly matching node. The table still
receives the real already-loaded PVE node collection as its source, so the
estate-empty `No Proxmox VE nodes` state remains reserved for genuinely absent
node inventory, while a narrowed result uses the distinct no-match state.
Those counts and layout controls must not imply
protection, verification, or restore readiness beyond the evidence held by the
workflow-owned Storage, Backups, Ceph, and Mail surfaces.
+11 -18
View File
@@ -1,21 +1,16 @@
{
"version": 1,
"base_sha": "ae9cfb1da87235e81359e251ec5f0a6670398ad9",
"verified_at": "2026-08-23T18:06:00Z",
"base_sha": "35b8c0a4c2ccbc11a1bb67116cb2d1e8f6d72391",
"verified_at": "2026-08-23T18:28:00Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx",
"frontend-modern/src/features/proxmox/proxmoxPageModel.ts",
"frontend-modern/src/routing/resourceLinks.ts"
"frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx"
],
"content_sha256": {
"frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx": "050b7e4336a467a71376b471f3e53cbcf65f11de088cd076efe0afe0943ecf31",
"frontend-modern/src/features/proxmox/proxmoxPageModel.ts": "09cebaf862d67def8e560b49acf50bdbcdc13f814fec15ad7fc8b1ae412a7fc1",
"frontend-modern/src/routing/resourceLinks.ts": "8a22520911eb1b84cee0ab4ef4388ec8dbfd40380db93dbac451d26f8031db3c"
"frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx": "5fbcb11d536ff657dcbffbe25c3b749bcf8bd4ff545dbdd0c02d3414c703c759"
},
"routes": [
"/proxmox/backups/date",
"/proxmox/backups/coverage"
"/proxmox/overview"
],
"viewports": [
{
@@ -28,15 +23,13 @@
}
],
"states": [
"Desktop direct navigation to /proxmox/backups/date renders the By date section with its chronological backup table and Coverage link",
"Desktop navigation to /proxmox/backups/coverage selects Coverage and renders posture filters and coverage rows",
"Narrow Coverage renders the route-backed section rail, responsive columns, and no horizontal overflow",
"The running app reported no console errors during direct navigation and section switching"
"Desktop direct navigation to /proxmox/overview rendered the connected Proxmox app shell and its overview loading state while the large mock estate hydrated",
"The running app reported a connected backend/live data stream and no browser console errors during the overview navigation",
"The large-estate narrowed node result is covered by the focused Proxmox page-surface and node-table tests"
],
"interactions": [
"Loaded /proxmox/backups/date at the desktop viewport and inspected the active section rail and date table",
"Selected Coverage through the canonical section link and verified /proxmox/backups/coverage with active navigation",
"Loaded Coverage at 390x844 and verified responsive section navigation, posture table content, and overflow state",
"Reset the temporary narrow viewport after verification"
"Loaded /proxmox/overview at the desktop viewport and inspected the connected overview shell",
"Confirmed the page exposes the Proxmox overview route used by the shared workload search",
"Verified the guest-aware node narrowing contract through the focused page-surface and node-table test suites"
]
}
@@ -333,6 +333,7 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
<ProxmoxNodesTable
nodes={currentModel().pveNodes}
guests={currentModel().guests}
search={workloadsState.search}
metricDisplayMode={props.metricDisplayMode}
metricHistoryRange={props.metricHistoryRange}
layoutWidth={overviewWidth.width}
@@ -122,9 +122,15 @@ vi.mock('../ProxmoxMailGatewayTable', () => ({
}));
vi.mock('../ProxmoxNodesTable', () => ({
ProxmoxNodesTable: (props: { nodes: Resource[]; topology?: unknown }) => {
ProxmoxNodesTable: (props: { nodes: Resource[]; search?: () => string; topology?: unknown }) => {
mockNodesTableProps(props);
return <div data-testid="nodes-table" data-rows={props.nodes.length} />;
return (
<div
data-testid="nodes-table"
data-rows={props.nodes.length}
data-search={props.search?.() ?? ''}
/>
);
},
}));
@@ -212,7 +218,7 @@ describe('ProxmoxPageSurface contract', () => {
);
});
it('keeps the node inventory independent from committed workload search terms', () => {
it('passes committed workload search terms to the node table', () => {
mockWorkloadSearch.mockReturnValue('reporting-api-01, wireguard-edge-01');
setResources([
makeResource({
@@ -242,11 +248,17 @@ describe('ProxmoxPageSurface contract', () => {
renderSurface();
expect(screen.getByTestId('nodes-table')).toHaveAttribute('data-rows', '2');
expect(mockNodesTableProps).toHaveBeenCalledWith(
expect.objectContaining({ nodes: expect.arrayContaining([expect.any(Object)]) }),
expect(screen.getByTestId('nodes-table')).toHaveAttribute(
'data-search',
'reporting-api-01, wireguard-edge-01',
);
expect(proxmoxPageSurfaceSource).toContain('nodes={currentModel().pveNodes}');
expect(proxmoxPageSurfaceSource).not.toContain('filterProxmoxNodesForSearch');
expect(mockNodesTableProps).toHaveBeenCalledWith(
expect.objectContaining({
nodes: expect.arrayContaining([expect.any(Object)]),
search: mockWorkloadSearch,
}),
);
expect(proxmoxPageSurfaceSource).toContain('search={workloadsState.search}');
});
it('does not call a retained version from a stopped Proxmox agent currently running', () => {
@@ -312,7 +324,7 @@ describe('ProxmoxPageSurface contract', () => {
);
});
it('keeps workload search scoped to guests instead of hiding the node inventory', () => {
it('shares workload search with the node inventory', () => {
mockWorkloadSearch.mockReturnValue('pve-1');
setResources([
makeResource({
@@ -325,7 +337,7 @@ describe('ProxmoxPageSurface contract', () => {
renderSurface();
expect(mockNodesTableProps).toHaveBeenLastCalledWith(
expect.not.objectContaining({ search: expect.anything() }),
expect.objectContaining({ search: mockWorkloadSearch }),
);
});