mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-25 04:33:03 +00:00
Keep a guest's host node visible when searching the Proxmox overview
The Proxmox overview's shared search box is a VM/LXC filter, but filteredNodes applied the same term to the nodes table independently. Searching a guest name (e.g. 'debian-go') matched no node name, so the nodes table collapsed to the 'No Proxmox VE nodes' empty state — which reads as 'you have no Proxmox infrastructure' even though the matching guest was listed right below it on its host node. Extract filterProxmoxNodesForSearch: keep a node when it matches the term directly OR when it hosts a guest that matches the term. A guest search now keeps that guest's host node visible for context; a node-name search still narrows to the matching node; an empty term still shows every node. Regression tests cover all three cases; verified live (searching 'debian-go' now shows the minipc host node instead of the empty state).
This commit is contained in:
@@ -23,7 +23,6 @@ import { getPlatformIcon } from '@/features/platformPage/platformIcon';
|
||||
import { PlatformOutdatedAgentNotice } from '@/features/platformPage/PlatformOutdatedAgentNotice';
|
||||
import { usePersistentSignal } from '@/hooks/usePersistentSignal';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
import { resourceMatchesSearch } from '@/utils/resourceSearchMatch';
|
||||
import {
|
||||
PlatformErrorState,
|
||||
PlatformSectionTabs,
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
PROXMOX_TAB_SPECS,
|
||||
buildProxmoxPageModel,
|
||||
buildVisibleProxmoxTabSpecs,
|
||||
filterProxmoxNodesForSearch,
|
||||
type ProxmoxPageModel,
|
||||
type ProxmoxPageTabId,
|
||||
} from './proxmoxPageModel';
|
||||
@@ -247,11 +247,13 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) {
|
||||
workloadsState.surfaceInitialDataReceived() &&
|
||||
workloadsState.allGuests().length > 0,
|
||||
);
|
||||
const filteredNodes = createMemo(() => {
|
||||
const term = workloadsState.search();
|
||||
if (!term.trim()) return props.model().pveNodes;
|
||||
return props.model().pveNodes.filter((node) => resourceMatchesSearch(node, term));
|
||||
});
|
||||
const filteredNodes = createMemo(() =>
|
||||
filterProxmoxNodesForSearch(
|
||||
props.model().pveNodes,
|
||||
props.model().guests,
|
||||
workloadsState.search(),
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
PROXMOX_TAB_SPECS,
|
||||
buildProxmoxPageModel,
|
||||
buildVisibleProxmoxTabSpecs,
|
||||
filterProxmoxNodesForSearch,
|
||||
getResourceVersion,
|
||||
resolveProxmoxPlatformScope,
|
||||
} from '../proxmoxPageModel';
|
||||
@@ -31,6 +32,44 @@ describe('proxmoxPageModel', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe('filterProxmoxNodesForSearch', () => {
|
||||
const minipc = makeResource({
|
||||
id: 'minipc',
|
||||
type: 'agent',
|
||||
proxmox: { nodeName: 'minipc', clusterName: 'homelab' },
|
||||
});
|
||||
const delly = makeResource({
|
||||
id: 'delly',
|
||||
type: 'agent',
|
||||
proxmox: { nodeName: 'delly', clusterName: 'homelab' },
|
||||
});
|
||||
const debianGo = makeResource({
|
||||
id: 'system-container-112',
|
||||
type: 'system-container',
|
||||
name: 'debian-go',
|
||||
proxmox: { vmid: 112, nodeName: 'minipc' },
|
||||
});
|
||||
|
||||
it('returns every node when the search term is empty', () => {
|
||||
expect(filterProxmoxNodesForSearch([minipc, delly], [debianGo], '')).toEqual([
|
||||
minipc,
|
||||
delly,
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps the host node of a matching guest so a guest search does not empty the nodes table', () => {
|
||||
// Regression: searching a guest name used to filter the nodes table to
|
||||
// nothing, surfacing the "No Proxmox VE nodes" empty state even though the
|
||||
// guest's host node exists and the guest is listed below.
|
||||
const result = filterProxmoxNodesForSearch([minipc, delly], [debianGo], 'debian-go');
|
||||
expect(result).toEqual([minipc]);
|
||||
});
|
||||
|
||||
it('still matches a node by its own name', () => {
|
||||
expect(filterProxmoxNodesForSearch([minipc, delly], [debianGo], 'delly')).toEqual([delly]);
|
||||
});
|
||||
});
|
||||
|
||||
it('builds a Proxmox-first estate model from canonical v6 resources', () => {
|
||||
const model = buildProxmoxPageModel([
|
||||
makeResource({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Resource, ResourceMetric, ResourceType } from '@/types/resource';
|
||||
import type { ResourceChange } from '@/types/resource';
|
||||
import { formatProxmoxVersion } from '@/utils/proxmoxVersion';
|
||||
import { resourceMatchesSearch } from '@/utils/resourceSearchMatch';
|
||||
|
||||
export type ProxmoxPageTabId = 'overview' | 'storage' | 'replication' | 'backups' | 'ceph' | 'mail';
|
||||
|
||||
@@ -175,6 +176,32 @@ export function getResourceNodeName(resource: Resource): string {
|
||||
);
|
||||
}
|
||||
|
||||
// filterProxmoxNodesForSearch narrows the nodes table to match the shared
|
||||
// workload search box. Because that box is a VM/LXC filter, filtering nodes by
|
||||
// the raw term alone collapses the table to its empty state whenever the term
|
||||
// matches a guest but not a node name — which misreads as "no Proxmox nodes"
|
||||
// while the matching guest is listed right below. Keep a node when it matches
|
||||
// the term directly OR when it hosts a guest that matches, so a guest search
|
||||
// still shows that guest's host node for context.
|
||||
export function filterProxmoxNodesForSearch(
|
||||
nodes: Resource[],
|
||||
guests: Resource[],
|
||||
term: string,
|
||||
): Resource[] {
|
||||
if (!term.trim()) return nodes;
|
||||
const matchingGuestNodeNames = new Set(
|
||||
guests
|
||||
.filter((guest) => resourceMatchesSearch(guest, term))
|
||||
.map((guest) => getResourceNodeName(guest))
|
||||
.filter(Boolean),
|
||||
);
|
||||
return nodes.filter(
|
||||
(node) =>
|
||||
resourceMatchesSearch(node, term) ||
|
||||
matchingGuestNodeNames.has(getResourceNodeName(node)),
|
||||
);
|
||||
}
|
||||
|
||||
export function getResourceVmid(resource: Resource): string {
|
||||
const vmid = resource.proxmox?.vmid;
|
||||
if (typeof vmid === 'number' && Number.isFinite(vmid)) {
|
||||
|
||||
Reference in New Issue
Block a user