diff --git a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx index 8f68d1b92..7025f203b 100644 --- a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx +++ b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx @@ -1,6 +1,5 @@ -import { Component, Show } from 'solid-js'; +import { Component, Show, createEffect, createSignal, onCleanup } from 'solid-js'; import { Card } from '@/components/shared/Card'; -import { showTooltip, hideTooltip } from '@/components/shared/Tooltip'; interface DashboardFilterProps { search: () => string; @@ -18,6 +17,42 @@ interface DashboardFilterProps { } export const DashboardFilter: Component = (props) => { + const [showSearchHelp, setShowSearchHelp] = createSignal(false); + let helpPopoverRef: HTMLDivElement | undefined; + let helpButtonRef: HTMLButtonElement | undefined; + + const closeSearchHelp = () => setShowSearchHelp(false); + + createEffect(() => { + if (!showSearchHelp()) { + return; + } + + const handlePointerDown = (event: PointerEvent) => { + const target = event.target as Node; + const clickedInsidePopover = helpPopoverRef?.contains(target) ?? false; + const clickedHelpButton = helpButtonRef?.contains(target) ?? false; + + if (!clickedInsidePopover && !clickedHelpButton) { + closeSearchHelp(); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + closeSearchHelp(); + } + }; + + window.addEventListener('pointerdown', handlePointerDown); + window.addEventListener('keydown', handleKeyDown); + + onCleanup(() => { + window.removeEventListener('pointerdown', handlePointerDown); + window.removeEventListener('keydown', handleKeyDown); + }); + }); + return (
@@ -67,39 +102,91 @@ export const DashboardFilter: Component = (props) => {
- {/* Help Icon with Tooltip */} - + + +
(helpPopoverRef = el)} + id="dashboard-search-help" + role="dialog" + aria-label="Search tips" + class="absolute right-0 z-50 mt-2 w-72 overflow-hidden rounded-lg border border-gray-200 bg-white text-left shadow-xl dark:border-gray-600 dark:bg-gray-800" + > +
+ Search tips + +
+
+

Combine filters to narrow results

+
+
+ media + Guests with "media" in the name +
+
+ cpu>80 + Highlight guests using more than 80% CPU +
+
+ memory<20 + Find guests under 20% memory usage +
+
+ tags:prod + Filter by tag +
+
+ node:pve1 + Show guests on a specific node +
+
+
+ Try `node:pve1 cpu>60` to stack filters.
- `; - showTooltip(tooltipContent, rect.left, rect.top); - }} - onMouseLeave={() => hideTooltip()} - onClick={(e) => e.preventDefault()} - aria-label="Search help" - > - - - - +
+
+ {/* Filters */} diff --git a/frontend-modern/src/components/shared/NodeSummaryTable.tsx b/frontend-modern/src/components/shared/NodeSummaryTable.tsx index a13651a43..dd9c8c592 100644 --- a/frontend-modern/src/components/shared/NodeSummaryTable.tsx +++ b/frontend-modern/src/components/shared/NodeSummaryTable.tsx @@ -98,11 +98,11 @@ export const NodeSummaryTable: Component = (props) => { const node = item.data as Node; switch (props.currentTab) { case 'dashboard': - const vmCount = props.vms?.filter((vm) => vm.instance === node.id).length || 0; - const containerCount = props.containers?.filter((ct) => ct.instance === node.id).length || 0; + const vmCount = props.vms?.filter((vm) => vm.node === node.name).length || 0; + const containerCount = props.containers?.filter((ct) => ct.node === node.name).length || 0; return [vmCount, containerCount]; case 'storage': - const storageCount = props.storage?.filter((s) => s.instance === node.id).length || 0; + const storageCount = props.storage?.filter((s) => s.node === node.name).length || 0; const diskCount = state.physicalDisks?.filter((d) => d.node === node.name).length || 0; return [storageCount, diskCount]; case 'backups': diff --git a/frontend-modern/src/components/shared/UnifiedNodeSelector.tsx b/frontend-modern/src/components/shared/UnifiedNodeSelector.tsx index bd084cf84..cec662a3f 100644 --- a/frontend-modern/src/components/shared/UnifiedNodeSelector.tsx +++ b/frontend-modern/src/components/shared/UnifiedNodeSelector.tsx @@ -47,7 +47,7 @@ export const UnifiedNodeSelector: Component = (props) const backupCounts = createMemo(() => { const counts: Record = {}; - // Count PVE backups and snapshots by node instance ID (not hostname) + // Count PVE backups and snapshots by node name const nodes = props.nodes || state.nodes; if (nodes) { nodes.forEach((node) => { @@ -56,13 +56,13 @@ export const UnifiedNodeSelector: Component = (props) // Count storage backups (excluding PBS backups which are counted separately) if (state.pveBackups?.storageBackups) { count += state.pveBackups.storageBackups.filter( - (b) => b.instance === node.id && !b.isPBS, + (b) => b.node === node.name && !b.isPBS, ).length; } // Count snapshots if (state.pveBackups?.guestSnapshots) { - count += state.pveBackups.guestSnapshots.filter((s) => s.instance === node.id).length; + count += state.pveBackups.guestSnapshots.filter((s) => s.node === node.name).length; } counts[node.name] = count;