mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Separate threshold platform navigation from filters
This commit is contained in:
@@ -430,9 +430,12 @@ perform notification work. Canonical lifecycle and stateful cooldown refires
|
||||
consume resolved state through the shared lock-order-aware helper, preserve the
|
||||
original alert `StartTime`, and keep the five-minute refire/history semantics.
|
||||
The browser thresholds surface is also platform-shaped: Proxmox, Docker,
|
||||
Kubernetes, TrueNAS, vSphere, PBS, PMG, and Systems. It must use the shared
|
||||
FilterBar chip and "+ Filter" pattern for resource filtering, and alert tables
|
||||
must use the canonical platform table column-kind alignment helpers from
|
||||
Kubernetes, TrueNAS, vSphere, PBS, PMG, and Systems. Route-backed platform
|
||||
choices must use the shared `Subtabs` navigation above the shared `FilterBar`;
|
||||
they are not resource-filter facets and must remain visible when the mobile
|
||||
filter shell is collapsed. Resource filtering must use the shared FilterBar
|
||||
chip and "+ Filter" pattern, and alert tables must use the canonical platform
|
||||
table column-kind alignment helpers from
|
||||
`frontend-modern/src/features/platformPage/` rather than hard-coded table
|
||||
alignment classes.
|
||||
Alert filter option semantics stay alert-owned, but FilterBar chip
|
||||
@@ -1021,6 +1024,10 @@ links like `/alerts/thresholds/infrastructure`,
|
||||
`/alerts/thresholds/containers`, and `/alerts/thresholds/mail-gateway` must
|
||||
redirect to the matching platform-shaped route. Legacy
|
||||
`/alerts/thresholds/agents` links must continue to resolve to Systems.
|
||||
`frontend-modern/src/components/Alerts/ThresholdsTable.tsx` must expose those
|
||||
peer platform routes through the shared `Subtabs` primitive rather than an
|
||||
inline FilterBar group. Clearing search or override filters must preserve the
|
||||
active platform route.
|
||||
Within the Proxmox tab, render-heavy ownership now further routes through
|
||||
`frontend-modern/src/components/Alerts/ThresholdsTableProxmoxNodesSection.tsx`,
|
||||
`frontend-modern/src/components/Alerts/ThresholdsTableProxmoxPBSSection.tsx`,
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "b2e5ad606176c0e08185e7da95b33a4ae4e39318",
|
||||
"verified_at": "2026-08-03T00:21:48Z",
|
||||
"base_sha": "1b2e9866a843386916fe58a9f73be1700ca6f7d6",
|
||||
"verified_at": "2026-08-03T07:52:17Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Storage/StoragePageControls.tsx",
|
||||
"frontend-modern/src/components/shared/FilterBar/FilterBar.tsx",
|
||||
"frontend-modern/src/components/shared/FilterBar/ViewOptionsMenu.tsx"
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTable.tsx"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Storage/StoragePageControls.tsx": "f3ea9419ff3b7de9f21deba51add5485e15751ec58842eee532fcf30e9d28782",
|
||||
"frontend-modern/src/components/shared/FilterBar/FilterBar.tsx": "97c31217224436d7326b34629ad26d7e8fbafa9c50033a2faaf9877af49e3d9d",
|
||||
"frontend-modern/src/components/shared/FilterBar/ViewOptionsMenu.tsx": "a4af7c1e398bab745109d332dff808f273b85067e80cf5c53c11abf6f1fe2f98"
|
||||
"frontend-modern/src/components/Alerts/ThresholdsTable.tsx": "3c2eb5f190d916a16ff9f03e247939726e36957f0ebd5f8b8f0647fb57d99be1"
|
||||
},
|
||||
"routes": [
|
||||
"/proxmox/storage",
|
||||
"/docker/overview"
|
||||
"/alerts/thresholds/proxmox",
|
||||
"/alerts/thresholds/docker",
|
||||
"/alerts/thresholds/systems"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
@@ -28,20 +25,18 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Desktop Proxmox Storage toolbar with Status as the only permanent segmented filter and Saved, Clear filters, and View aligned on the shared rail",
|
||||
"Desktop Storage View preferences with Layout/Group by and Order controls fully visible",
|
||||
"Phone-width Storage filter rail expanded with Saved, Clear filters, and View together in the labelled Filter actions row",
|
||||
"Phone-width Storage View preferences fully contained within the viewport",
|
||||
"Phone-width Docker filter rail expanded with Saved, View, and the container count in the shared action row",
|
||||
"Phone-width Docker View preferences fully contained despite the trailing container count"
|
||||
"Desktop Proxmox threshold page with the route-backed platform rail above the resource FilterBar",
|
||||
"Desktop Docker threshold page with Docker selected and only Overrides available from Add filter",
|
||||
"Phone-width Docker threshold page with Filters collapsed and the platform rail still visible",
|
||||
"Phone-width Systems threshold page after the platform rail scrolled Machines into view",
|
||||
"Phone-width Systems resource filters expanded with only the Overrides choices present"
|
||||
],
|
||||
"interactions": [
|
||||
"Changed Storage Group by from Flat to By node and confirmed grouped host rows plus the canonical group=node URL parameter",
|
||||
"Reloaded Storage and confirmed the By node selection and grouped result persisted from URL state",
|
||||
"Pressed Escape and confirmed the View dialog closed with focus restored to its trigger",
|
||||
"Reopened View and clicked the Storage tab outside it, confirming outside-click dismissal",
|
||||
"Expanded the phone-width Storage filter rail and confirmed Group by no longer consumes an inline filter row",
|
||||
"Opened View on phone-width Storage and Docker routes and confirmed the action-rail-relative panel stayed inside both viewport edges",
|
||||
"Confirmed the verified routes produced no browser warning or error console entries"
|
||||
"Selected Docker from the threshold platform rail and confirmed navigation to the canonical Docker threshold route",
|
||||
"Applied Overrides: Custom only, cleared filters, and confirmed Docker remained selected with no route change",
|
||||
"Reloaded at phone width with Filters collapsed and confirmed platform navigation remained available",
|
||||
"Selected Machines from the horizontally scrollable phone-width platform rail and confirmed navigation to Systems",
|
||||
"Expanded and collapsed phone-width Filters and confirmed the platform choices were never duplicated as filter options",
|
||||
"Confirmed the phone-width document had no horizontal overflow and the final verified routes produced no browser warning or error console entries"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Show } from 'solid-js';
|
||||
import { getPlatformIcon } from '@/features/platformPage/platformIcon';
|
||||
import { FilterBar, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { Subtabs, type SubtabOption } from '@/components/shared/Subtabs';
|
||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||
import { BulkEditDialog } from './BulkEditDialog';
|
||||
import { ThresholdsTableAgentsTab } from './ThresholdsTableAgentsTab';
|
||||
@@ -13,28 +14,33 @@ import type { ThresholdsTableProps } from '@/features/alerts/thresholds/types';
|
||||
import type { ThresholdsActiveTab } from '@/features/alerts/thresholds/tableTypes';
|
||||
import { useThresholdsTableState } from '@/features/alerts/thresholds/hooks/useThresholdsTableState';
|
||||
|
||||
const thresholdPlatformDefinitions = [
|
||||
{ value: 'proxmox', label: 'Proxmox', icon: getPlatformIcon('proxmox') },
|
||||
{ value: 'docker', label: 'Docker', icon: getPlatformIcon('docker') },
|
||||
{ value: 'kubernetes', label: 'Kubernetes', icon: getPlatformIcon('kubernetes') },
|
||||
{ value: 'truenas', label: 'TrueNAS', icon: getPlatformIcon('truenas') },
|
||||
{ value: 'vmware', label: 'vSphere', icon: getPlatformIcon('vmware') },
|
||||
{ value: 'systems', label: 'Machines', icon: getPlatformIcon('systems') },
|
||||
] as const;
|
||||
|
||||
export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
const state = useThresholdsTableState(props);
|
||||
const { isMobile } = useBreakpoint();
|
||||
const platformTabs: SubtabOption[] = thresholdPlatformDefinitions.map(
|
||||
({ value, label, icon: Icon }) => ({
|
||||
value,
|
||||
label: (
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<span aria-hidden="true">
|
||||
<Icon class="h-4 w-4" />
|
||||
</span>
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
const filters = (): FilterDef[] => [
|
||||
{
|
||||
id: 'alerts-platform',
|
||||
label: 'Platform',
|
||||
group: 'scope',
|
||||
inline: true,
|
||||
value: state.activeTab,
|
||||
setValue: (value) => state.handleTabClick(value as ThresholdsActiveTab),
|
||||
defaultValue: 'proxmox',
|
||||
options: () => [
|
||||
{ value: 'proxmox', label: 'Proxmox', icon: getPlatformIcon('proxmox') },
|
||||
{ value: 'docker', label: 'Docker', icon: getPlatformIcon('docker') },
|
||||
{ value: 'kubernetes', label: 'Kubernetes', icon: getPlatformIcon('kubernetes') },
|
||||
{ value: 'truenas', label: 'TrueNAS', icon: getPlatformIcon('truenas') },
|
||||
{ value: 'vmware', label: 'vSphere', icon: getPlatformIcon('vmware') },
|
||||
{ value: 'systems', label: 'Machines', icon: getPlatformIcon('systems') },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'alerts-overrides',
|
||||
label: 'Overrides',
|
||||
@@ -52,6 +58,13 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<Subtabs
|
||||
value={state.activeTab()}
|
||||
onChange={(value) => state.handleTabClick(value as ThresholdsActiveTab)}
|
||||
tabs={platformTabs}
|
||||
ariaLabel="Threshold platform"
|
||||
/>
|
||||
|
||||
<FilterBar
|
||||
role="group"
|
||||
ariaLabel="Alert threshold filters"
|
||||
@@ -65,14 +78,11 @@ export function ThresholdsTable(props: ThresholdsTableProps) {
|
||||
}}
|
||||
filters={filters()}
|
||||
showClearAll={() =>
|
||||
state.searchTerm().trim().length > 0 ||
|
||||
state.overrideFilter() !== 'all' ||
|
||||
state.activeTab() !== 'proxmox'
|
||||
state.searchTerm().trim().length > 0 || state.overrideFilter() !== 'all'
|
||||
}
|
||||
onClearAll={() => {
|
||||
state.setSearchTerm('');
|
||||
state.setOverrideFilter('all');
|
||||
state.handleTabClick('proxmox');
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { render, fireEvent, screen, cleanup, waitFor } from '@solidjs/testing-library';
|
||||
import { render, fireEvent, screen, cleanup, waitFor, within } from '@solidjs/testing-library';
|
||||
import { createSignal } from 'solid-js';
|
||||
|
||||
import { ThresholdsTable } from '../ThresholdsTable';
|
||||
@@ -361,6 +361,18 @@ describe('ThresholdsTable basics', () => {
|
||||
});
|
||||
|
||||
describe('ThresholdsTable navigation and redirection', () => {
|
||||
it('renders route-backed platform choices as persistent navigation', () => {
|
||||
render(() => <ThresholdsTable {...(baseProps() as any)} />);
|
||||
|
||||
const platformTabs = screen.getByRole('tablist', { name: 'Threshold platform' });
|
||||
expect(within(platformTabs).getAllByRole('tab')).toHaveLength(6);
|
||||
expect(within(platformTabs).getByRole('tab', { name: 'Docker' })).toHaveAttribute(
|
||||
'aria-selected',
|
||||
'true',
|
||||
);
|
||||
expect(screen.queryByText('Platform')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('redirects from base path to Proxmox', () => {
|
||||
setPathname('/alerts/thresholds');
|
||||
render(() => <ThresholdsTable {...(baseProps() as any)} />);
|
||||
@@ -405,24 +417,30 @@ describe('ThresholdsTable navigation and redirection', () => {
|
||||
it('navigates to correct route when tabs are clicked', () => {
|
||||
render(() => <ThresholdsTable {...(baseProps() as any)} />);
|
||||
|
||||
const proxmoxTab = screen
|
||||
.getAllByRole('button')
|
||||
.find((el) => el.textContent?.includes('Proxmox'));
|
||||
if (proxmoxTab) fireEvent.click(proxmoxTab);
|
||||
const platformTabs = screen.getByRole('tablist', { name: 'Threshold platform' });
|
||||
fireEvent.click(within(platformTabs).getByRole('tab', { name: 'Proxmox' }));
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/alerts/thresholds/proxmox');
|
||||
|
||||
const machinesTab = screen
|
||||
.getAllByRole('button')
|
||||
.find((el) => el.textContent?.includes('Machines'));
|
||||
if (machinesTab) fireEvent.click(machinesTab);
|
||||
fireEvent.click(within(platformTabs).getByRole('tab', { name: 'Machines' }));
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/alerts/thresholds/systems');
|
||||
|
||||
const vmwareTab = screen
|
||||
.getAllByRole('button')
|
||||
.find((el) => el.textContent?.includes('vSphere'));
|
||||
if (vmwareTab) fireEvent.click(vmwareTab);
|
||||
fireEvent.click(within(platformTabs).getByRole('tab', { name: 'vSphere' }));
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/alerts/thresholds/vmware');
|
||||
});
|
||||
|
||||
it('clears resource filters without changing the active platform', () => {
|
||||
render(() => <ThresholdsTable {...(baseProps() as any)} />);
|
||||
|
||||
const search = screen.getByPlaceholderText(/Search resources/i);
|
||||
fireEvent.input(search, { target: { value: 'api' } });
|
||||
mockNavigate.mockClear();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Clear filters' }));
|
||||
|
||||
expect(search).toHaveValue('');
|
||||
expect(mockNavigate).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('tab', { name: 'Docker' })).toHaveAttribute('aria-selected', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ThresholdsTable Resource Rendering', () => {
|
||||
|
||||
Reference in New Issue
Block a user