mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 11:45:52 +00:00
Register FilterBar template governance
This commit is contained in:
@@ -202,6 +202,9 @@ FilterBar chip and "+ Filter" pattern for resource filtering, 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
|
||||
presentation is frontend-primitives-owned: severity filter leading dots must
|
||||
use `filterChipStatusDot` rather than alert-local span factories.
|
||||
Guest metric canonical state remains resource-backed and therefore node-scoped
|
||||
for Proxmox guests, so node moves must not strand active alert state on the
|
||||
previous resource ID. When a guest metric alert survives a node move, alerts
|
||||
|
||||
@@ -84,6 +84,7 @@ work extends shared components instead of creating new local variants.
|
||||
59. `frontend-modern/src/components/shared/FilterBar/index.ts`
|
||||
59a. `frontend-modern/src/components/shared/FilterBar/SavedViewsMenu.tsx`
|
||||
59b. `frontend-modern/src/components/shared/FilterBar/useSavedViews.ts`
|
||||
59c. `frontend-modern/src/components/shared/FilterBar/filterOptionPresentation.tsx`
|
||||
60. `frontend-modern/src/components/shared/TypeColumn.guardrails.test.ts`
|
||||
61. `frontend-modern/src/features/`
|
||||
62. `frontend-modern/src/components/SetupWizard/SetupWizard.tsx`
|
||||
@@ -2392,6 +2393,12 @@ Docker, Kubernetes, Proxmox, Standalone, TrueNAS, vSphere, and future platform
|
||||
feature tables; source-specific consumers own only the empty-state icon, title,
|
||||
description, and actions. Platform feature tables must not import
|
||||
`EmptyState` directly or recreate a `Card`-wrapped empty-state shell.
|
||||
Filter bars are registry-backed too. `FilterBar` owns resource-list filtering
|
||||
as a catalog of `FilterDef` entries, while `filterChipStatusDot` owns the
|
||||
small leading status-dot glyph used by filter options. Page and feature
|
||||
surfaces must not copy the chip-dot `<span>` factory or import the legacy
|
||||
`PageControls` deck for resource-list filtering; those drift checks live in
|
||||
`shared-template-registry.json` and run through `shared-template-audit.mjs`.
|
||||
Inline detail table rows are also registry-backed. `InlineDetailTableRow`
|
||||
owns the row/cell/content shell and row-click containment for platform,
|
||||
workload, and infrastructure inline drawers; callers may pass row-specific
|
||||
@@ -2496,10 +2503,11 @@ rail as view options, matching the v5 filter-bar pattern, and keeps longer or
|
||||
dynamic scope filters in the menu/chip path. Feature surfaces must not fork
|
||||
local filter rows or bury high-frequency Type, Status, or Group-by filters
|
||||
behind an extra menu just to regain one-click behavior.
|
||||
Pages that have not yet migrated (the alert-history filter card,
|
||||
Kubernetes deployments drawer) keep using `PageControls` and
|
||||
`LabeledFilterSelect`, but new resource-list filter surfaces should reach for
|
||||
`FilterBar` with a catalog rather than reintroducing a per-page select row.
|
||||
Legacy `PageControls` and labelled select/toggle primitives are not the
|
||||
resource-list filter shape. If a future surface needs a new filtering
|
||||
affordance, it should extend the FilterBar catalog model or add a new
|
||||
registry-backed shared primitive rather than reintroducing a per-page select
|
||||
row.
|
||||
|
||||
Pages may opt into saved views by passing `savedViewsKey` to
|
||||
`FilterBar`. The `useSavedViews` hook owns the localStorage IO + URL
|
||||
|
||||
@@ -374,6 +374,9 @@ regression protection.
|
||||
Non-Docker Workloads surfaces keep the global `disk` default unless they
|
||||
declare their own scoped contract.
|
||||
18. Extend workload filter active-count, reset semantics, and mobile toolbar state through `frontend-modern/src/components/Workloads/workloadsFilterModel.ts` (defaults, `countActiveWorkloadsFilters`, `hasActiveWorkloadsFilters`) rather than rebuilding filter-local state inside `frontend-modern/src/components/Workloads/WorkloadsFilter.tsx`. Workloads filter presentation now composes the shared `FilterBar` (`frontend-modern/src/components/shared/FilterBar/FilterBar.tsx`) with a per-page `FilterDef[]` catalog rather than the legacy `PageControls` structured control deck. High-frequency Type and Status filters stay in that catalog but render as inline compact segmented controls (`inline: true`), while longer or dynamic scope filters continue through the "+ Filter" menu and chip popovers. View options (grouped/list, charts, columns) sit in the shared `viewOptionsTrailing` slot.
|
||||
Workload filter option semantics stay workload-owned, but FilterBar chip
|
||||
presentation is frontend-primitives-owned: status and runtime leading dots
|
||||
must use `filterChipStatusDot` rather than workload-local span factories.
|
||||
WorkloadsFilter remains the shared interaction shell only; platform-owned
|
||||
embedded Workloads surfaces must pass page-owned aria labels, search
|
||||
placeholders, empty-search copy, status labels, and forced view-mode
|
||||
|
||||
@@ -979,6 +979,9 @@ recovery scope, or a storage/recovery-owned secret source.
|
||||
forking the toolbar. The source scope flows through
|
||||
`forcedSourceFilter` as a typed page input; the source filter remains
|
||||
available in the toolbar only when not forced.
|
||||
Storage filter option semantics stay storage-owned, but FilterBar chip
|
||||
presentation is frontend-primitives-owned: storage status leading dots must
|
||||
use `filterChipStatusDot` rather than storage-local span factories.
|
||||
41. Keep agent memory composition descriptive on the shared unified-resource
|
||||
contract. `internal/unifiedresources/types.go` carries the reclaimable
|
||||
page-cache split (`AgentMemoryMeta.cache`, holding used + cache + free
|
||||
|
||||
@@ -215,6 +215,10 @@ filter semantics, drawer handoffs, and resource projections, while
|
||||
Future platform tables must keep that split: row data and platform semantics
|
||||
stay in the unified-resource consumer, and the repeated table shell stays in the
|
||||
shared frontend primitive.
|
||||
Platform filter option semantics follow that split too: unified-resource
|
||||
consumers own the source-specific status buckets and labels, while the repeated
|
||||
FilterBar chip leading-dot presentation must use the frontend-primitives-owned
|
||||
`filterChipStatusDot` helper instead of page-local span factories.
|
||||
Platform table empty states use that same split. Unified-resource consumers own
|
||||
the source-specific empty-state vocabulary, action choice, and evidence rule
|
||||
that decides why a table is empty, but the table-card empty-state shell itself
|
||||
|
||||
@@ -113,6 +113,60 @@
|
||||
"src/components/shared/__tests__/InlineDetailTableRow.test.tsx",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filter-bar-catalog-shell",
|
||||
"category": "filter-bar",
|
||||
"summary": "Resource-list filter surfaces must compose the shared FilterBar catalog shell instead of recreating page-local filter rows or legacy PageControls decks.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/FilterBar/FilterBar.tsx",
|
||||
"export": "FilterBar"
|
||||
},
|
||||
"requiredConsumers": [
|
||||
{ "path": "src/components/Alerts/ThresholdsTable.tsx" },
|
||||
{ "path": "src/components/Settings/AuditLogPanel.tsx" },
|
||||
{ "path": "src/components/Storage/StoragePageControls.tsx" },
|
||||
{ "path": "src/components/Workloads/WorkloadsFilter.tsx" },
|
||||
{ "path": "src/features/alerts/AlertHistoryFiltersCard.tsx" },
|
||||
{ "path": "src/features/platformPage/sharedPlatformPage.tsx" },
|
||||
{ "path": "src/features/proxmox/ProxmoxBackupsTable.tsx" }
|
||||
],
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
"path": "src/components/Storage/StoragePageControls.tsx",
|
||||
"patterns": [
|
||||
"from '@/components/shared/PageControls'",
|
||||
"<PageControls",
|
||||
"<StorageControls",
|
||||
"<StorageFilter",
|
||||
"LabeledFilterSelect",
|
||||
"FilterSegmentedControl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Workloads/WorkloadsFilter.tsx",
|
||||
"patterns": [
|
||||
"from '@/components/shared/PageControls'",
|
||||
"<PageControls",
|
||||
"LabeledFilterSelect",
|
||||
"LabeledFilterToggleGroup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/proxmox/ProxmoxBackupsTable.tsx",
|
||||
"patterns": [
|
||||
"from '@/components/shared/PageControls'",
|
||||
"<PageControls",
|
||||
"LabeledFilterSelect",
|
||||
"LabeledFilterToggleGroup"
|
||||
]
|
||||
}
|
||||
],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/components/shared/FilterBar/FilterBar.test.tsx",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"patternGuards": [
|
||||
@@ -162,6 +216,43 @@
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filter-bar-consumer-legacy-page-controls",
|
||||
"category": "filter-bar",
|
||||
"summary": "Page and feature filter surfaces must not import the legacy PageControls deck when they should be expressed as a FilterBar catalog.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/FilterBar/FilterBar.tsx",
|
||||
"export": "FilterBar"
|
||||
},
|
||||
"scopes": ["src/components", "src/features"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["@/components/shared/PageControls"],
|
||||
"legacyReason": "Retired migration debt. Resource-list filter surfaces must compose FilterBar; no page-level filter exceptions are currently allowed.",
|
||||
"allowedPaths": [],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filter-chip-status-dot-local-factory",
|
||||
"category": "filter-bar",
|
||||
"summary": "Filter option status-dot leading icons must use the FilterBar presentation helper instead of page-local span factories.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/FilterBar/filterOptionPresentation.tsx",
|
||||
"export": "filterChipStatusDot"
|
||||
},
|
||||
"scopes": ["src/components", "src/features"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": ["h-2 w-2 rounded-full ${className}"],
|
||||
"legacyReason": "Retired migration debt. Filter chip leading-dot presentation is owned by the FilterBar primitive family.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/SharedPrimitives.guardrails.test.ts"],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
}
|
||||
],
|
||||
"requiredPatternGuards": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, JSX, Show, createMemo } from 'solid-js';
|
||||
import DatabaseIcon from 'lucide-solid/icons/database';
|
||||
import HardDriveIcon from 'lucide-solid/icons/hard-drive';
|
||||
import { FilterBar, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { FilterBar, filterChipStatusDot, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { Subtabs } from '@/components/shared/Subtabs';
|
||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
@@ -83,10 +83,6 @@ type StoragePageControlsProps = {
|
||||
savedViewsKey?: string;
|
||||
};
|
||||
|
||||
const storageStatusDot = (className: string) => (
|
||||
<span class={`h-2 w-2 rounded-full ${className}`} />
|
||||
);
|
||||
|
||||
export const StoragePageControls: Component<StoragePageControlsProps> = (props) => {
|
||||
const { isMobile } = useBreakpoint();
|
||||
const isPoolsView = () => props.view() === 'pools';
|
||||
@@ -222,13 +218,13 @@ export const StoragePageControls: Component<StoragePageControlsProps> = (props)
|
||||
label: option.label,
|
||||
leading:
|
||||
option.value === 'available'
|
||||
? storageStatusDot('bg-emerald-500')
|
||||
? filterChipStatusDot('bg-emerald-500')
|
||||
: option.value === 'warning' || option.value === 'attention'
|
||||
? storageStatusDot('bg-amber-500')
|
||||
? filterChipStatusDot('bg-amber-500')
|
||||
: option.value === 'critical'
|
||||
? storageStatusDot('bg-red-500')
|
||||
? filterChipStatusDot('bg-red-500')
|
||||
: option.value === 'offline' || option.value === 'unknown'
|
||||
? storageStatusDot('bg-slate-400')
|
||||
? filterChipStatusDot('bg-slate-400')
|
||||
: undefined,
|
||||
tone:
|
||||
option.value === 'available'
|
||||
|
||||
@@ -4,7 +4,12 @@ import BoxesIcon from 'lucide-solid/icons/boxes';
|
||||
import MonitorIcon from 'lucide-solid/icons/monitor';
|
||||
import XIcon from 'lucide-solid/icons/x';
|
||||
import { ColumnPicker } from '@/components/shared/ColumnPicker';
|
||||
import { FilterBar, type FilterDef, type FilterSelectOption } from '@/components/shared/FilterBar';
|
||||
import {
|
||||
FilterBar,
|
||||
filterChipStatusDot,
|
||||
type FilterDef,
|
||||
type FilterSelectOption,
|
||||
} from '@/components/shared/FilterBar';
|
||||
import { ChartVisibilityToggleButton, FilterActionButton } from '@/components/shared/FilterToolbar';
|
||||
import { GroupedTableModeSegmentedControl } from '@/components/shared/GroupedTableModeSegmentedControl';
|
||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||
@@ -24,8 +29,6 @@ import { WORKLOAD_STATUS_FILTER_OPTIONS, WORKLOAD_TYPE_OPTIONS } from './workloa
|
||||
|
||||
const PROXMOX_PLATFORM_FILTER = 'proxmox-pve';
|
||||
|
||||
const statusDot = (className: string) => <span class={`h-2 w-2 rounded-full ${className}`} />;
|
||||
|
||||
export const WorkloadsFilter: Component<WorkloadsFilterProps> = (props) => {
|
||||
const { isMobile } = useBreakpoint();
|
||||
|
||||
@@ -62,11 +65,11 @@ export const WorkloadsFilter: Component<WorkloadsFilterProps> = (props) => {
|
||||
label: option.label,
|
||||
leading:
|
||||
option.value === 'running'
|
||||
? statusDot('bg-emerald-500')
|
||||
? filterChipStatusDot('bg-emerald-500')
|
||||
: option.value === 'degraded'
|
||||
? statusDot('bg-amber-500')
|
||||
? filterChipStatusDot('bg-amber-500')
|
||||
: option.value === 'stopped'
|
||||
? statusDot('bg-red-500')
|
||||
? filterChipStatusDot('bg-red-500')
|
||||
: undefined,
|
||||
tone:
|
||||
option.value === 'running'
|
||||
@@ -86,8 +89,8 @@ export const WorkloadsFilter: Component<WorkloadsFilterProps> = (props) => {
|
||||
};
|
||||
|
||||
const runtimeChipDot = (value: string): JSX.Element | undefined => {
|
||||
if (value === 'docker') return statusDot('bg-sky-500');
|
||||
if (value === 'podman') return statusDot('bg-violet-500');
|
||||
if (value === 'docker') return filterChipStatusDot('bg-sky-500');
|
||||
if (value === 'podman') return filterChipStatusDot('bg-violet-500');
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, cleanup, fireEvent, within } from '@solidjs/testing-library';
|
||||
import { WorkloadsFilter } from '../WorkloadsFilter';
|
||||
import workloadsFilterSource from '../WorkloadsFilter.tsx?raw';
|
||||
import {
|
||||
DEFAULT_WORKLOADS_SORT_DIRECTION,
|
||||
DEFAULT_WORKLOADS_SORT_KEY,
|
||||
@@ -95,6 +96,12 @@ describe('WorkloadsFilter', () => {
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('uses the shared FilterBar status-dot presentation helper', () => {
|
||||
expect(workloadsFilterSource).toContain('filterChipStatusDot');
|
||||
expect(workloadsFilterSource).not.toContain('const statusDot = (className: string)');
|
||||
expect(workloadsFilterSource).not.toContain('h-2 w-2 rounded-full ' + '${className}');
|
||||
});
|
||||
|
||||
it('renders the search input', () => {
|
||||
render(() => <WorkloadsFilter {...makeProps()} />);
|
||||
expect(screen.getByTestId('search-input')).toBeInTheDocument();
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { JSX } from 'solid-js';
|
||||
|
||||
export const filterChipStatusDot = (className: string): JSX.Element => (
|
||||
<span class={`h-2 w-2 rounded-full ${className}`} aria-hidden="true" />
|
||||
);
|
||||
@@ -3,13 +3,7 @@ export { FilterChip } from './FilterChip';
|
||||
export { AddFilterMenu } from './AddFilterMenu';
|
||||
export { SavedViewsMenu } from './SavedViewsMenu';
|
||||
export { useSavedViews } from './useSavedViews';
|
||||
export type { SavedView, } from './useSavedViews';
|
||||
export {
|
||||
clearFilter,
|
||||
formatFilterChipValue,
|
||||
isFilterSet,
|
||||
} from './filterCatalog';
|
||||
export type {
|
||||
FilterDef,
|
||||
FilterSelectOption,
|
||||
} from './filterCatalog';
|
||||
export type { SavedView } from './useSavedViews';
|
||||
export { filterChipStatusDot } from './filterOptionPresentation';
|
||||
export { clearFilter, formatFilterChipValue, isFilterSet } from './filterCatalog';
|
||||
export type { FilterDef, FilterSelectOption } from './filterCatalog';
|
||||
|
||||
@@ -150,10 +150,12 @@ import filterBarSource from '@/components/shared/FilterBar/FilterBar.tsx?raw';
|
||||
import filterChipSource from '@/components/shared/FilterBar/FilterChip.tsx?raw';
|
||||
import addFilterMenuSource from '@/components/shared/FilterBar/AddFilterMenu.tsx?raw';
|
||||
import filterCatalogSource from '@/components/shared/FilterBar/filterCatalog.ts?raw';
|
||||
import filterBarOptionPresentationSource from '@/components/shared/FilterBar/filterOptionPresentation.tsx?raw';
|
||||
import filterBarIndexSource from '@/components/shared/FilterBar/index.ts?raw';
|
||||
import savedViewsMenuSource from '@/components/shared/FilterBar/SavedViewsMenu.tsx?raw';
|
||||
import useSavedViewsSource from '@/components/shared/FilterBar/useSavedViews.ts?raw';
|
||||
import storagePageControlsSource from '@/components/Storage/StoragePageControls.tsx?raw';
|
||||
import proxmoxBackupsTableSharedSource from '@/features/proxmox/proxmoxBackupsTableShared.tsx?raw';
|
||||
|
||||
const sharedSources = import.meta.glob(['./*.tsx', './cards/*.tsx', './responsive/*.tsx'], {
|
||||
query: '?raw',
|
||||
@@ -1057,7 +1059,7 @@ describe('shared primitive guardrails', () => {
|
||||
vsphereHostsTableSource,
|
||||
]) {
|
||||
expect(source).toContain('PlatformTableEmptyState');
|
||||
expect(source).not.toContain("@/components/shared/EmptyState");
|
||||
expect(source).not.toContain('@/components/shared/EmptyState');
|
||||
expect(source).not.toContain('<EmptyState');
|
||||
expect(source).not.toContain('<Card padding="lg">');
|
||||
}
|
||||
@@ -1405,14 +1407,71 @@ describe('shared primitive guardrails', () => {
|
||||
});
|
||||
|
||||
it('keeps the chip-based FilterBar on a catalog descriptor with shell, chip, and add-menu owners', () => {
|
||||
const registry = JSON.parse(sharedTemplateRegistrySource) as {
|
||||
rules?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
requiredConsumers?: Array<{ path?: string }>;
|
||||
}>;
|
||||
patternGuards?: Array<{
|
||||
id: string;
|
||||
canonical?: { path?: string; export?: string };
|
||||
allPatterns?: string[];
|
||||
allowedPaths?: string[];
|
||||
ignoredPaths?: string[];
|
||||
}>;
|
||||
};
|
||||
const filterBarRule = registry.rules?.find((rule) => rule.id === 'filter-bar-catalog-shell');
|
||||
const legacyPageControlsGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'filter-bar-consumer-legacy-page-controls',
|
||||
);
|
||||
const chipDotGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'filter-chip-status-dot-local-factory',
|
||||
);
|
||||
|
||||
expect(filterBarRule?.canonical?.path).toBe('src/components/shared/FilterBar/FilterBar.tsx');
|
||||
expect(filterBarRule?.canonical?.export).toBe('FilterBar');
|
||||
expect(filterBarRule?.requiredConsumers?.map((consumer) => consumer.path)).toEqual(
|
||||
expect.arrayContaining([
|
||||
'src/components/Alerts/ThresholdsTable.tsx',
|
||||
'src/components/Settings/AuditLogPanel.tsx',
|
||||
'src/components/Storage/StoragePageControls.tsx',
|
||||
'src/components/Workloads/WorkloadsFilter.tsx',
|
||||
'src/features/alerts/AlertHistoryFiltersCard.tsx',
|
||||
'src/features/platformPage/sharedPlatformPage.tsx',
|
||||
'src/features/proxmox/ProxmoxBackupsTable.tsx',
|
||||
]),
|
||||
);
|
||||
expect(legacyPageControlsGuard?.canonical?.path).toBe(
|
||||
'src/components/shared/FilterBar/FilterBar.tsx',
|
||||
);
|
||||
expect(legacyPageControlsGuard?.canonical?.export).toBe('FilterBar');
|
||||
expect(legacyPageControlsGuard?.allPatterns).toEqual(['@/components/shared/PageControls']);
|
||||
expect(legacyPageControlsGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(chipDotGuard?.canonical?.path).toBe(
|
||||
'src/components/shared/FilterBar/filterOptionPresentation.tsx',
|
||||
);
|
||||
expect(chipDotGuard?.canonical?.export).toBe('filterChipStatusDot');
|
||||
expect(chipDotGuard?.allPatterns).toEqual(['h-2 w-2 rounded-full ' + '${className}']);
|
||||
expect(chipDotGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(chipDotGuard?.ignoredPaths).toEqual([
|
||||
'src/components/shared/SharedPrimitives.guardrails.test.ts',
|
||||
]);
|
||||
|
||||
expect(filterBarIndexSource).toContain("export { FilterBar } from './FilterBar';");
|
||||
expect(filterBarIndexSource).toContain("export { FilterChip } from './FilterChip';");
|
||||
expect(filterBarIndexSource).toContain("export { AddFilterMenu } from './AddFilterMenu';");
|
||||
expect(filterBarIndexSource).toContain("export { SavedViewsMenu } from './SavedViewsMenu';");
|
||||
expect(filterBarIndexSource).toContain("export { useSavedViews } from './useSavedViews';");
|
||||
expect(filterBarIndexSource).toContain(
|
||||
"export { filterChipStatusDot } from './filterOptionPresentation';",
|
||||
);
|
||||
expect(filterBarIndexSource).toContain('isFilterSet');
|
||||
expect(filterBarIndexSource).toContain('clearFilter');
|
||||
expect(filterBarIndexSource).toContain('formatFilterChipValue');
|
||||
expect(filterBarOptionPresentationSource).toContain('export const filterChipStatusDot');
|
||||
expect(filterBarOptionPresentationSource).toContain('h-2 w-2 rounded-full');
|
||||
expect(filterBarOptionPresentationSource).toContain('aria-hidden="true"');
|
||||
|
||||
expect(filterCatalogSource).toContain('export interface FilterDef');
|
||||
expect(filterCatalogSource).toContain('defaultValue: string');
|
||||
@@ -1530,9 +1589,11 @@ describe('shared primitive guardrails', () => {
|
||||
// render as inline primary controls; Node/Platform/Namespace/Runtime
|
||||
// remain selector-backed catalog filters. The xl-breakpoint
|
||||
// segmented↔select swap retires here.
|
||||
expect(workloadsFilterSource).toContain(
|
||||
"import { FilterBar, type FilterDef, type FilterSelectOption } from '@/components/shared/FilterBar';",
|
||||
);
|
||||
expect(workloadsFilterSource).toContain("from '@/components/shared/FilterBar';");
|
||||
expect(workloadsFilterSource).toContain('FilterBar,');
|
||||
expect(workloadsFilterSource).toContain('filterChipStatusDot,');
|
||||
expect(workloadsFilterSource).toContain('type FilterDef,');
|
||||
expect(workloadsFilterSource).toContain('type FilterSelectOption,');
|
||||
expect(workloadsFilterSource).toContain('<FilterBar');
|
||||
expect(workloadsFilterSource).toContain("ariaLabel={props.ariaLabel ?? 'Workloads filters'}");
|
||||
expect(workloadsFilterSource).toContain("id: 'workloads-type'");
|
||||
@@ -1548,6 +1609,7 @@ describe('shared primitive guardrails', () => {
|
||||
expect(workloadsFilterSource).not.toContain('LabeledFilterToggleGroup');
|
||||
expect(workloadsFilterSource).not.toContain('LabeledFilterSelect');
|
||||
expect(workloadsFilterSource).not.toContain('useWorkloadsFilterState');
|
||||
expect(workloadsFilterSource).not.toContain('const statusDot = (className: string)');
|
||||
|
||||
// StoragePageControls — Subtabs sit above the FilterBar; sort key/sort
|
||||
// direction live in viewOptionsTrailing as raw view-options (not chips).
|
||||
@@ -1556,10 +1618,12 @@ describe('shared primitive guardrails', () => {
|
||||
// catalog. The legacy
|
||||
// 3-layer indirection (StoragePageControls → StorageControls →
|
||||
// StorageFilter) collapses to one component.
|
||||
expect(storagePageControlsSource).toContain(
|
||||
"import { FilterBar, type FilterDef } from '@/components/shared/FilterBar';",
|
||||
);
|
||||
expect(storagePageControlsSource).toContain("from '@/components/shared/FilterBar';");
|
||||
expect(storagePageControlsSource).toContain('FilterBar,');
|
||||
expect(storagePageControlsSource).toContain('filterChipStatusDot,');
|
||||
expect(storagePageControlsSource).toContain('type FilterDef');
|
||||
expect(storagePageControlsSource).toContain('<FilterBar');
|
||||
expect(storagePageControlsSource).toContain('filterChipStatusDot');
|
||||
expect(storagePageControlsSource).toContain('<Subtabs');
|
||||
expect(storagePageControlsSource).toContain(
|
||||
"ariaLabel={props.filterAriaLabel ?? 'Storage filters'}",
|
||||
@@ -1585,6 +1649,7 @@ describe('shared primitive guardrails', () => {
|
||||
expect(storagePageControlsSource).not.toContain('<StorageFilter');
|
||||
expect(storagePageControlsSource).not.toContain('LabeledFilterSelect');
|
||||
expect(storagePageControlsSource).not.toContain('FilterSegmentedControl');
|
||||
expect(storagePageControlsSource).not.toContain('storageStatusDot');
|
||||
// Storage's three-layer indirection retired — StoragePageControls no
|
||||
// longer imports the deleted StorageFilter / StorageControls modules,
|
||||
// and reads the canonical Storage types directly from storagePageState
|
||||
@@ -1598,5 +1663,15 @@ describe('shared primitive guardrails', () => {
|
||||
expect(storagePageControlsSource).toContain(
|
||||
"import type { StorageGroupKey, StorageSortKey } from './useStorageModel';",
|
||||
);
|
||||
expect(sharedPlatformPageSource).toContain('filterChipStatusDot');
|
||||
expect(sharedPlatformPageSource).not.toContain('platformChipStatusDot');
|
||||
for (const source of [
|
||||
proxmoxBackupsTableSharedSource,
|
||||
proxmoxCephTableSource,
|
||||
proxmoxReplicationTableSource,
|
||||
]) {
|
||||
expect(source).toContain('filterChipStatusDot');
|
||||
expect(source).not.toContain('const statusDot = (className: string)');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FilterBar, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { FilterBar, filterChipStatusDot, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { STORAGE_KEYS } from '@/utils/localStorage';
|
||||
import {
|
||||
ALERT_HISTORY_ALL_TIME_FILTER_LABEL,
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
import type { AlertHistoryRange, AlertSeverityFilter } from './alertHistoryModel';
|
||||
import type { AlertHistoryState } from './useAlertHistoryState';
|
||||
|
||||
const severityDot = (className: string) => <span class={`h-2 w-2 rounded-full ${className}`} />;
|
||||
|
||||
interface AlertHistoryFiltersCardProps {
|
||||
state: AlertHistoryState;
|
||||
isMobile: boolean;
|
||||
@@ -37,21 +35,20 @@ export function AlertHistoryFiltersCard(props: AlertHistoryFiltersCardProps) {
|
||||
group: 'status',
|
||||
inline: true,
|
||||
value: props.state.severityFilter,
|
||||
setValue: (value: string) =>
|
||||
props.state.setSeverityFilter(value as AlertSeverityFilter),
|
||||
setValue: (value: string) => props.state.setSeverityFilter(value as AlertSeverityFilter),
|
||||
defaultValue: 'all',
|
||||
options: () => [
|
||||
{ value: 'all', label: 'All' },
|
||||
{
|
||||
value: 'critical',
|
||||
label: 'Critical',
|
||||
leading: severityDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
tone: 'danger',
|
||||
},
|
||||
{
|
||||
value: 'warning',
|
||||
label: 'Warning',
|
||||
leading: severityDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
tone: 'warning',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -3,13 +3,13 @@ import XIcon from 'lucide-solid/icons/x';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import {
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableToolbar,
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -33,19 +33,19 @@ const DOCKER_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<DockerIncidentSe
|
||||
value: 'critical',
|
||||
label: 'Critical',
|
||||
tone: 'danger',
|
||||
leading: platformChipStatusDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
{
|
||||
value: 'warning',
|
||||
label: 'Warning',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'info',
|
||||
label: 'Info',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import XIcon from 'lucide-solid/icons/x';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import {
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableToolbar,
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -34,19 +34,19 @@ const KUBERNETES_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<KubernetesIn
|
||||
value: 'critical',
|
||||
label: 'Critical',
|
||||
tone: 'danger',
|
||||
leading: platformChipStatusDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
{
|
||||
value: 'warning',
|
||||
label: 'Warning',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'info',
|
||||
label: 'Info',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import TriangleAlertIcon from 'lucide-solid/icons/triangle-alert';
|
||||
import { For, Show, createMemo, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { type FilterOption as PlatformTableFilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import { FilterBar, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { FilterBar, filterChipStatusDot, type FilterDef } from '@/components/shared/FilterBar';
|
||||
import { type SearchInputProps } from '@/components/shared/SearchInput';
|
||||
import { Table, TableBody, TableHeader, TableRow } from '@/components/shared/Table';
|
||||
import { TableCard } from '@/components/shared/TableCard';
|
||||
@@ -174,11 +174,7 @@ export function PlatformErrorState(props: {
|
||||
// 'running' vs 'online', 'stopped' vs 'offline') collapse to one chip set.
|
||||
export type PlatformResourceStatusFilter = 'all' | 'online' | 'degraded' | 'offline';
|
||||
|
||||
export const platformChipStatusDot = (className: string) => (
|
||||
<span class={`h-2 w-2 rounded-full ${className}`} />
|
||||
);
|
||||
|
||||
const statusDot = platformChipStatusDot;
|
||||
const statusDot = filterChipStatusDot;
|
||||
|
||||
export const PLATFORM_STATUS_FILTER_OPTIONS: PlatformTableFilterOption<PlatformResourceStatusFilter>[] =
|
||||
[
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type Component,
|
||||
type JSX,
|
||||
} from 'solid-js';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import type { StatusIndicatorVariant } from '@/utils/status';
|
||||
@@ -38,13 +39,26 @@ import { ProxmoxCephClusterDrawer } from './ProxmoxCephClusterDrawer';
|
||||
|
||||
type CephStatusFilter = 'all' | 'healthy' | 'warning' | 'critical';
|
||||
|
||||
const statusDot = (className: string) => <span class={`h-2 w-2 rounded-full ${className}`} />;
|
||||
|
||||
const STATUS_FILTER_OPTIONS: PlatformTableFilterOption<CephStatusFilter>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'healthy', label: 'Healthy', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{ value: 'warning', label: 'Warning', tone: 'warning', leading: statusDot('bg-amber-500') },
|
||||
{ value: 'critical', label: 'Critical', tone: 'danger', leading: statusDot('bg-red-500') },
|
||||
{
|
||||
value: 'healthy',
|
||||
label: 'Healthy',
|
||||
tone: 'success',
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'warning',
|
||||
label: 'Warning',
|
||||
tone: 'warning',
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'critical',
|
||||
label: 'Critical',
|
||||
tone: 'danger',
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
];
|
||||
|
||||
function classify(resource: Resource): CephStatusFilter {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { For, Show, createMemo, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import ArrowRightIcon from 'lucide-solid/icons/arrow-right';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import type { StatusIndicatorVariant } from '@/utils/status';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
@@ -24,14 +25,27 @@ import type { ReplicationJob, ReplicationJobsResponse } from '@/types/api';
|
||||
|
||||
type ReplicationStatusFilter = 'all' | 'healthy' | 'failed' | 'pending' | 'disabled';
|
||||
|
||||
const statusDot = (className: string) => <span class={`h-2 w-2 rounded-full ${className}`} />;
|
||||
|
||||
const STATUS_FILTER_OPTIONS: PlatformTableFilterOption<ReplicationStatusFilter>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'healthy', label: 'Healthy', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{ value: 'failed', label: 'Failed', tone: 'danger', leading: statusDot('bg-red-500') },
|
||||
{ value: 'pending', label: 'Pending', tone: 'warning', leading: statusDot('bg-amber-500') },
|
||||
{ value: 'disabled', label: 'Disabled', tone: 'muted', leading: statusDot('bg-slate-400') },
|
||||
{
|
||||
value: 'healthy',
|
||||
label: 'Healthy',
|
||||
tone: 'success',
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{ value: 'failed', label: 'Failed', tone: 'danger', leading: filterChipStatusDot('bg-red-500') },
|
||||
{
|
||||
value: 'pending',
|
||||
label: 'Pending',
|
||||
tone: 'warning',
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'disabled',
|
||||
label: 'Disabled',
|
||||
tone: 'muted',
|
||||
leading: filterChipStatusDot('bg-slate-400'),
|
||||
},
|
||||
];
|
||||
|
||||
interface ReplicationStatusIndicator {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Show, type Accessor } from 'solid-js';
|
||||
import ArrowDownIcon from 'lucide-solid/icons/arrow-down';
|
||||
import ArrowUpIcon from 'lucide-solid/icons/arrow-up';
|
||||
import ArrowUpDownIcon from 'lucide-solid/icons/arrow-up-down';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { type FilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import { ProgressBar } from '@/components/shared/ProgressBar';
|
||||
import { TableHead } from '@/components/shared/Table';
|
||||
@@ -30,8 +31,6 @@ import type {
|
||||
// the orchestrating ProxmoxBackupsTable so each sub-view can import only what
|
||||
// it renders.
|
||||
|
||||
const statusDot = (className: string) => <span class={`h-2 w-2 rounded-full ${className}`} />;
|
||||
|
||||
export const PROXMOX_BACKUP_COLUMN_LABELS = {
|
||||
targetId: 'Target ID',
|
||||
created: 'Created',
|
||||
@@ -56,35 +55,75 @@ export const ARCHIVE_STATUS_FILTERS: FilterOption<
|
||||
'all' | 'protected' | 'verified' | 'unverified'
|
||||
>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'protected', label: 'Protected', tone: 'info', leading: statusDot('bg-blue-500') },
|
||||
{ value: 'verified', label: 'Verified', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{
|
||||
value: 'protected',
|
||||
label: 'Protected',
|
||||
tone: 'info',
|
||||
leading: filterChipStatusDot('bg-blue-500'),
|
||||
},
|
||||
{
|
||||
value: 'verified',
|
||||
label: 'Verified',
|
||||
tone: 'success',
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'unverified',
|
||||
label: 'Unverified',
|
||||
tone: 'warning',
|
||||
leading: statusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
];
|
||||
|
||||
export const PBS_STATUS_FILTERS: FilterOption<'all' | 'protected' | 'verified' | 'unverified'>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'protected', label: 'Protected', tone: 'info', leading: statusDot('bg-blue-500') },
|
||||
{ value: 'verified', label: 'Verified', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{ value: 'unverified', label: 'Unverified', tone: 'warning', leading: statusDot('bg-amber-500') },
|
||||
{
|
||||
value: 'protected',
|
||||
label: 'Protected',
|
||||
tone: 'info',
|
||||
leading: filterChipStatusDot('bg-blue-500'),
|
||||
},
|
||||
{
|
||||
value: 'verified',
|
||||
label: 'Verified',
|
||||
tone: 'success',
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'unverified',
|
||||
label: 'Unverified',
|
||||
tone: 'warning',
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
];
|
||||
|
||||
export const TASK_STATUS_FILTERS: FilterOption<'all' | 'ok' | 'failed' | 'running'>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'ok', label: 'OK', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{ value: 'failed', label: 'Failed', tone: 'danger', leading: statusDot('bg-red-500') },
|
||||
{ value: 'running', label: 'Running', tone: 'info', leading: statusDot('bg-blue-500') },
|
||||
{ value: 'ok', label: 'OK', tone: 'success', leading: filterChipStatusDot('bg-emerald-500') },
|
||||
{ value: 'failed', label: 'Failed', tone: 'danger', leading: filterChipStatusDot('bg-red-500') },
|
||||
{ value: 'running', label: 'Running', tone: 'info', leading: filterChipStatusDot('bg-blue-500') },
|
||||
];
|
||||
|
||||
export const COVERAGE_FILTERS: FilterOption<CoverageFilterValue>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'attention', label: 'Attention', tone: 'warning', leading: statusDot('bg-amber-500') },
|
||||
{ value: 'current', label: 'Current', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{ value: 'uncovered', label: 'Uncovered', tone: 'danger', leading: statusDot('bg-red-500') },
|
||||
{
|
||||
value: 'attention',
|
||||
label: 'Attention',
|
||||
tone: 'warning',
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'current',
|
||||
label: 'Current',
|
||||
tone: 'success',
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'uncovered',
|
||||
label: 'Uncovered',
|
||||
tone: 'danger',
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
];
|
||||
|
||||
const recoverableSourceFilterOption = (
|
||||
@@ -98,7 +137,7 @@ const recoverableSourceFilterOption = (
|
||||
compactLabel: presentation.compactFilterLabel,
|
||||
title: presentation.filterTitle,
|
||||
tone: 'info',
|
||||
leading: statusDot(presentation.timelineSwatchClassName),
|
||||
leading: filterChipStatusDot(presentation.timelineSwatchClassName),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -107,8 +146,18 @@ export const RECOVERABLE_FILTERS: FilterOption<RecoverableFilterValue>[] = [
|
||||
recoverableSourceFilterOption('pbs'),
|
||||
recoverableSourceFilterOption('archive'),
|
||||
recoverableSourceFilterOption('snapshot'),
|
||||
{ value: 'verified', label: 'Verified', tone: 'success', leading: statusDot('bg-emerald-500') },
|
||||
{ value: 'unverified', label: 'Unverified', tone: 'warning', leading: statusDot('bg-amber-500') },
|
||||
{
|
||||
value: 'verified',
|
||||
label: 'Verified',
|
||||
tone: 'success',
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'unverified',
|
||||
label: 'Unverified',
|
||||
tone: 'warning',
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
];
|
||||
|
||||
export const SNAPSHOT_FILTERS: FilterOption<SnapshotFilterValue>[] = [
|
||||
@@ -117,19 +166,19 @@ export const SNAPSHOT_FILTERS: FilterOption<SnapshotFilterValue>[] = [
|
||||
value: 'recent',
|
||||
label: 'Recent ≤30d',
|
||||
tone: 'success',
|
||||
leading: statusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'stale',
|
||||
label: 'Stale >30d',
|
||||
tone: 'warning',
|
||||
leading: statusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'with-ram',
|
||||
label: 'With RAM',
|
||||
tone: 'info',
|
||||
leading: statusDot('bg-violet-500'),
|
||||
leading: filterChipStatusDot('bg-violet-500'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { For, Show, createMemo, type Component, type JSX } from 'solid-js';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { ResponsiveMetricCell } from '@/components/shared/responsive';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { formatBytes } from '@/utils/format';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -40,19 +40,19 @@ const TRUENAS_STORAGE_STATUS_OPTIONS: PlatformTableFilterOption<TrueNASStorageSt
|
||||
value: 'healthy',
|
||||
label: 'Healthy',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'attention',
|
||||
label: 'Attention',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'offline',
|
||||
label: 'Offline',
|
||||
tone: 'danger',
|
||||
leading: platformChipStatusDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import XIcon from 'lucide-solid/icons/x';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import {
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableToolbar,
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -34,19 +34,19 @@ const VSPHERE_ACTIVITY_STATUS_OPTIONS: PlatformTableFilterOption<VmwareActivityS
|
||||
value: 'tasks',
|
||||
label: 'Tasks',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'events',
|
||||
label: 'Events',
|
||||
tone: 'info',
|
||||
leading: platformChipStatusDot('bg-blue-500'),
|
||||
leading: filterChipStatusDot('bg-blue-500'),
|
||||
},
|
||||
{
|
||||
value: 'failed',
|
||||
label: 'Failed',
|
||||
tone: 'danger',
|
||||
leading: platformChipStatusDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import XIcon from 'lucide-solid/icons/x';
|
||||
import { InlineDetailTableRow } from '@/components/shared/InlineDetailTableRow';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import {
|
||||
PlatformTableEmptyState,
|
||||
PlatformTableToolbar,
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -33,19 +33,19 @@ const VSPHERE_INCIDENT_STATUS_OPTIONS: PlatformTableFilterOption<VmwareIncidentS
|
||||
value: 'critical',
|
||||
label: 'Critical',
|
||||
tone: 'danger',
|
||||
leading: platformChipStatusDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
{
|
||||
value: 'warning',
|
||||
label: 'Warning',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'info',
|
||||
label: 'Info',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { For, Show, createMemo, type Component, type JSX } from 'solid-js';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -34,25 +34,25 @@ const VSPHERE_DATASTORE_STATUS_OPTIONS: PlatformTableFilterOption<VmwareDatastor
|
||||
value: 'accessible',
|
||||
label: 'Accessible',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'attention',
|
||||
label: 'Attention',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{
|
||||
value: 'inaccessible',
|
||||
label: 'Inaccessible',
|
||||
tone: 'danger',
|
||||
leading: platformChipStatusDot('bg-red-500'),
|
||||
leading: filterChipStatusDot('bg-red-500'),
|
||||
},
|
||||
{
|
||||
value: 'maintenance',
|
||||
label: 'Maintenance',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{ value: 'unknown', label: 'Unknown' },
|
||||
];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { For, Show, createMemo, type Component, type JSX } from 'solid-js';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TableCell, TableHead, TableRow } from '@/components/shared/Table';
|
||||
import { filterChipStatusDot } from '@/components/shared/FilterBar';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
createPlatformTableFilterState,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
platformChipStatusDot,
|
||||
type PlatformTableFilterOption,
|
||||
PlatformTableShell,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
@@ -33,13 +33,13 @@ const VSPHERE_NETWORK_STATUS_OPTIONS: PlatformTableFilterOption<VmwareNetworkSta
|
||||
value: 'healthy',
|
||||
label: 'Healthy',
|
||||
tone: 'success',
|
||||
leading: platformChipStatusDot('bg-emerald-500'),
|
||||
leading: filterChipStatusDot('bg-emerald-500'),
|
||||
},
|
||||
{
|
||||
value: 'attention',
|
||||
label: 'Attention',
|
||||
tone: 'warning',
|
||||
leading: platformChipStatusDot('bg-amber-500'),
|
||||
leading: filterChipStatusDot('bg-amber-500'),
|
||||
},
|
||||
{ value: 'unknown', label: 'Unknown' },
|
||||
];
|
||||
|
||||
@@ -465,6 +465,9 @@ describe('tab path helpers', () => {
|
||||
expect(alertHistoryFrequencyCardSource).toContain('getAlertBucketCountLabel');
|
||||
expect(alertHistoryFiltersCardSource).toContain('export function AlertHistoryFiltersCard');
|
||||
expect(alertHistoryFiltersCardSource).toContain('getAlertHistorySearchPlaceholder');
|
||||
expect(alertHistoryFiltersCardSource).toContain('filterChipStatusDot');
|
||||
expect(alertHistoryFiltersCardSource).not.toContain('severityDot');
|
||||
expect(alertHistoryFiltersCardSource).not.toContain('h-2 w-2 rounded-full ${className}');
|
||||
expect(alertResourceIncidentsPanelSource).toContain(
|
||||
'export function AlertResourceIncidentsPanel',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user