From 47d898cd503f8b99981c3c8fdbd03e85a4ad655f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 28 Apr 2026 21:51:50 +0100 Subject: [PATCH] Normalize frontend product labels --- .../v6/internal/subsystems/alerts.md | 6 +++++- .../internal/subsystems/frontend-primitives.md | 11 ++++++++++- .../subsystems/performance-and-scalability.md | 4 ++++ .../v6/internal/subsystems/storage-recovery.md | 10 +++++++++- .../internal/subsystems/unified-resources.md | 7 ++++--- .../components/Dashboard/DashboardFilter.tsx | 2 +- .../Dashboard.performance.contract.test.tsx | 2 +- .../__tests__/DashboardFilter.test.tsx | 1 + .../Recovery/RecoveryHistorySection.tsx | 3 ++- .../RecoveryProtectedInventorySection.tsx | 4 ++-- .../Recovery/__tests__/Recovery.test.tsx | 2 +- .../src/components/Storage/StorageFilter.tsx | 6 +++--- .../Storage/__tests__/Storage.test.tsx | 18 +++++++++--------- .../Storage/__tests__/StorageControls.test.tsx | 12 ++++++------ .../Storage/__tests__/storagePageState.test.ts | 6 +++--- .../__tests__/storageSourceOptions.test.ts | 2 +- .../__tests__/useStorageFilterState.test.ts | 8 ++++---- .../useStorageFilterToolbarModel.test.ts | 8 ++++---- .../src/components/Storage/storagePageState.ts | 8 ++++---- .../alerts/AlertOverviewStatsCards.tsx | 11 ++++++++--- .../InfrastructurePageSurface.tsx | 2 +- ...nfrastructurePageSurface.guardrails.test.ts | 2 ++ .../__tests__/alertConfigPresentation.test.ts | 6 +++--- .../alertOverviewPresentation.test.ts | 9 +++++++++ .../alertThresholdsPresentation.test.ts | 18 +++++++++--------- .../frontendResourceTypeBoundaries.test.ts | 2 +- .../src/utils/alertConfigPresentation.ts | 8 ++++---- .../src/utils/alertOverviewPresentation.ts | 3 +++ .../src/utils/alertThresholdsPresentation.ts | 16 ++++++++-------- frontend-modern/src/utils/storageSources.ts | 2 +- 30 files changed, 123 insertions(+), 76 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md index 16e519808..5b233b896 100644 --- a/docs/release-control/v6/internal/subsystems/alerts.md +++ b/docs/release-control/v6/internal/subsystems/alerts.md @@ -209,7 +209,11 @@ canonical owners for alert enablement copy, history administration wording, bulk-edit labels, schedule/configuration text, email-destination field labels, frequency chips, grouping card styling, history source and resource badges, severity badges, tab labels, thresholds empty states, and thresholds section -status labels. Future alert configuration or history presentation work should +status labels. Overview stat-card labels must also route through the alert +overview presentation helper, and user-facing configuration or thresholds copy +must use workload, VM, and container vocabulary instead of exposing internal +guest override/filter names unless the UI is naming a backend field directly. +Future alert configuration or history presentation work should extend those helpers instead of rebuilding alert-specific semantics in pages, dashboard surfaces, feature hooks, or thresholds shells. diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 4acd0064a..8c9752f10 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -264,7 +264,16 @@ work extends shared components instead of creating new local variants. inherits that same boundary: shared data-grid shells must route scrollbar hiding and table-width sizing through shared classes plus HTML attributes, not inline overflow or min-width styles. -3. Add feature-specific presentation only when no shared primitive should own it +3. Add feature-specific presentation only when no shared primitive should own it. + Feature surfaces under `frontend-modern/src/features/` that display + product labels must consume the owning subsystem's presentation utilities + rather than hard-coding divergent page-local copy. Shared primitives and + feature shells may compose those labels, but they must not become a second + source of truth for alert, storage, recovery, infrastructure, workload, or + adjacent product vocabulary. Table-mode segmented controls that expose a + grouping accessible label must use the shared `Group by` casing, while + visible options describe the mode itself (`Grouped`, `List`, or the + owning surface's equivalent) instead of resource-specific concepts. 4. Add guardrail tests when a new shared pattern is introduced. Shared monitored-system warning primitives must prove their admission-freeze posture through the canonical `frontend-modern/src/utils/monitoredSystemPresentation.ts` diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 05b3accdc..08b511f5e 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -779,6 +779,10 @@ stays in `frontend-modern/src/components/Dashboard/DashboardFilter.tsx`, while toolbar defaults, active-filter counting, and reset semantics live in `frontend-modern/src/components/Dashboard/dashboardFilterModel.ts` and `frontend-modern/src/components/Dashboard/useDashboardFilterState.ts`. +Dashboard table-mode controls must also keep their accessible group name aligned +with the shared table presentation contract by using `Group by` for the +grouped/list selector instead of reintroducing local `Group By` casing or +platform-specific cluster wording. The dashboard-owned filter-config assembly now lives in `frontend-modern/src/components/Dashboard/useDashboardState.ts`, so future filter runtime changes must extend through those owners instead of diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index ab3ea6183..e18055062 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -847,6 +847,11 @@ contract in `frontend-modern/src/utils/storageSources.ts`: storage pages and cross-surface storage links must reuse one canonical ordering, label, tone, and default-option model for sources like PVE, PBS, Ceph, and TrueNAS instead of re-sorting or re-presenting those source options locally. +Storage filter option labels for grouped views, node/host filters, sort +controls, and source selectors are also canonical presentation contracts: +storage surfaces must consume `frontend-modern/src/components/Storage/storagePageState.ts` +and `frontend-modern/src/utils/storageSources.ts` rather than re-declaring +page-local title casing or alternate all-option labels. That same storage ownership also includes the physical-disk detail identity contract in `frontend-modern/src/components/Storage/` and `frontend-modern/src/features/storageBackups/`: historical disk charts must @@ -1441,7 +1446,10 @@ The recovery table presentation helper now owns the canonical subject-type label fallback for recovery rows and delegates its title-casing to the shared `frontend-modern/src/utils/textPresentation.ts` helper rather than keeping a local recovery-only formatter, so subject and outcome labels stay aligned with -the shared frontend label contract. +the shared frontend label contract. Protected-inventory and recovery-event +filters, table headers, and column-picker labels must use that helper for +artifact fields such as `Item Type`, so the recovery tabs do not drift into +near-identical page-local casing. That same recovery drill-in surface now also keeps provider-specific metadata inside a provider-neutral detail shell through `frontend-modern/src/components/Recovery/RecoveryPointDetails.tsx`, so PBS diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 981f7295e..054b933e3 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -139,9 +139,10 @@ cross-source deduplication. is collection-method detail when a provider/API platform is also present. Infrastructure table presentation controls must describe the table mode rather than a platform-specific resource concept: the grouped/flat toggle - uses operator-facing `Grouped` and `List` wording, while Proxmox, - Kubernetes, and other platform clusters stay reserved for actual resource - identity, filters, and detail surfaces. + uses operator-facing `Grouped` and `List` wording with a `Group by` + accessible group label, while Proxmox, Kubernetes, and other platform + clusters stay reserved for actual resource identity, filters, and detail + surfaces. Resource detail mappers now reuse the shared `frontend-modern/src/utils/textPresentation.ts` title-case helper for sensor diff --git a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx index f07245f79..fee418d48 100644 --- a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx +++ b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx @@ -183,7 +183,7 @@ export const DashboardFilter: Component = (props) => { props.setGroupingMode(value as 'grouped' | 'flat')} - aria-label="Group By" + aria-label="Group by" options={[ { value: 'grouped', diff --git a/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx b/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx index c2a02d5ba..e8ad80ac9 100644 --- a/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx +++ b/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx @@ -744,7 +744,7 @@ describe('Dashboard performance contract', () => { expect(dashboardWorkloadViewportSyncSource).toContain('groupedWindowing.onScroll'); expect(dashboardWorkloadRouteStateSource).not.toContain("from './workloadTopology'"); expect(dashboardWorkloadRouteModelSource).toContain("from './workloadTopology'"); - expect(dashboardWorkloadRouteModelSource).toContain('workloadNodeScopeId'); + expect(dashboardWorkloadRouteModelSource).toContain('workloadHostScopeId'); expect(dashboardWorkloadRouteModelSource).toContain('getKubernetesContextKey'); expect(dashboardSelectionStateSource).toContain( 'const [selectedGuestId, setSelectedGuestIdRaw]', diff --git a/frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx b/frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx index 6f46c0388..67ecc7882 100644 --- a/frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx +++ b/frontend-modern/src/components/Dashboard/__tests__/DashboardFilter.test.tsx @@ -104,6 +104,7 @@ describe('DashboardFilter', () => { it('renders Grouped and List buttons', () => { const props = makeProps(); render(() => ); + expect(screen.getByLabelText('Group by')).toBeInTheDocument(); expect(screen.getByText('Grouped')).toBeInTheDocument(); expect(screen.getByText('List')).toBeInTheDocument(); }); diff --git a/frontend-modern/src/components/Recovery/RecoveryHistorySection.tsx b/frontend-modern/src/components/Recovery/RecoveryHistorySection.tsx index d156eed0a..6a3fba8e3 100644 --- a/frontend-modern/src/components/Recovery/RecoveryHistorySection.tsx +++ b/frontend-modern/src/components/Recovery/RecoveryHistorySection.tsx @@ -35,6 +35,7 @@ import { } from '@/utils/recoveryLocationPresentation'; import { normalizeRecoveryModeQueryValue } from '@/utils/recoveryRecordPresentation'; import { + getRecoveryArtifactColumnLabel, getRecoveryHistorySearchPlaceholder, getRecoverySearchHistoryEmptyMessage, RECOVERY_ADVANCED_FILTER_FIELD_CLASS, @@ -403,7 +404,7 @@ export const RecoveryHistorySection: Component = (p { props.setItemTypeFilter( diff --git a/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx b/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx index eb9be2657..c773f2d86 100644 --- a/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx +++ b/frontend-modern/src/components/Recovery/RecoveryProtectedInventorySection.tsx @@ -291,7 +291,7 @@ export const RecoveryProtectedInventorySection: Component< > props.setItemTypeFilter( @@ -419,7 +419,7 @@ export const RecoveryProtectedInventorySection: Component< {( [ ['item', getRecoveryArtifactColumnLabel('item', 'Item')], - ['type', 'Item Type'], + ['type', getRecoveryArtifactColumnLabel('type', 'Item Type')], ['platform', getRecoveryArtifactColumnLabel('platform', 'Platform')], ['lastBackup', 'Latest Point'], ['outcome', 'Protection State'], diff --git a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx index b1f50485c..91bb770e4 100644 --- a/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx +++ b/frontend-modern/src/components/Recovery/__tests__/Recovery.test.tsx @@ -422,7 +422,7 @@ describe('Recovery', () => { ); expect(screen.getAllByText(/^1 event$/i)).toHaveLength(1); expect(within(historyControls).queryByText(/day group/i)).not.toBeInTheDocument(); - expect(within(historyControls).getByLabelText('Item type').className).not.toContain('min-w-['); + expect(within(historyControls).getByLabelText('Item Type').className).not.toContain('min-w-['); expect(within(historyControls).getByLabelText('Platform').className).not.toContain('min-w-['); expect(within(historyControls).getByLabelText('Status').className).not.toContain('min-w-['); const recoverySummaryPanel = screen.getByTestId('recovery-summary'); diff --git a/frontend-modern/src/components/Storage/StorageFilter.tsx b/frontend-modern/src/components/Storage/StorageFilter.tsx index 6eb9d5908..6f6c7f9f4 100644 --- a/frontend-modern/src/components/Storage/StorageFilter.tsx +++ b/frontend-modern/src/components/Storage/StorageFilter.tsx @@ -180,7 +180,7 @@ export const StorageFilter: Component = (props) => { props.setGroupBy!(value as StorageGroupByFilter)} - aria-label="Group By" + aria-label="Group by" options={STORAGE_GROUP_BY_OPTIONS} /> @@ -267,7 +267,7 @@ export const StorageFilter: Component = (props) => { value={props.sortKey()} onChange={(e) => props.setSortKey(e.currentTarget.value)} disabled={props.sortDisabled} - aria-label="Sort By" + aria-label="Sort by" class={STORAGE_FILTER_SORT_SELECT_CLASS} > {sortOptions().map((option) => ( @@ -279,7 +279,7 @@ export const StorageFilter: Component = (props) => { title={sortDirectionTitle()} onClick={toggleSortDirection} disabled={props.sortDisabled} - aria-label="Sort Direction" + aria-label="Sort direction" class={STORAGE_FILTER_SORT_DIRECTION_BUTTON_CLASS} > { .some((element) => element.getAttribute('title') === 'Pool redundancy is reduced.'), ).toBe(true); - fireEvent.change(screen.getByLabelText('Sort By'), { + fireEvent.change(screen.getByLabelText('Sort by'), { target: { value: 'usage' }, }); - fireEvent.click(screen.getByRole('button', { name: 'Sort Direction' })); + fireEvent.click(screen.getByRole('button', { name: 'Sort direction' })); await waitFor(() => { const orderedRowIds = Array.from(document.querySelectorAll('tr[data-row-id]')).map((row) => @@ -668,7 +668,7 @@ describe('Storage', () => { expect(orderedRowIds.slice(0, 2)).toEqual(['storage-1', 'storage-2']); }); - fireEvent.click(screen.getByRole('button', { name: 'By Status' })); + fireEvent.click(screen.getByRole('button', { name: 'By status' })); // Group headers now show just the key name (without prefix) expect(screen.getAllByText('degraded').length).toBeGreaterThan(0); @@ -710,18 +710,18 @@ describe('Storage', () => { 'true', ); expect((screen.getByLabelText('Node') as HTMLSelectElement).value).toBe('node-2'); - expect((screen.getByLabelText('Sort By') as HTMLSelectElement).value).toBe('usage'); + expect((screen.getByLabelText('Sort by') as HTMLSelectElement).value).toBe('usage'); expect(getStorageSourceSelect().value).toBe('proxmox-pve'); expect((screen.getByLabelText('Status') as HTMLSelectElement).value).toBe('warning'); // Grouping controls are only shown on the Pools view. fireEvent.click(screen.getByRole('tab', { name: 'Pools' })); - expect(screen.getByRole('button', { name: 'By Status' })).toHaveAttribute( + expect(screen.getByRole('button', { name: 'By status' })).toHaveAttribute( 'aria-pressed', 'true', ); - fireEvent.click(screen.getByRole('button', { name: 'By Type' })); + fireEvent.click(screen.getByRole('button', { name: 'By type' })); await waitFor(() => { const [nextPath] = navigateSpy.mock.calls.at(-1) as [string]; @@ -791,7 +791,7 @@ describe('Storage', () => { expect((screen.getByLabelText('Node') as HTMLSelectElement).value).toBe('node-2'); expect(getStorageSourceSelect().value).toBe('proxmox-pve'); expect((screen.getByLabelText('Status') as HTMLSelectElement).value).toBe('available'); - expect((screen.getByLabelText('Sort By') as HTMLSelectElement).value).toBe('usage'); + expect((screen.getByLabelText('Sort by') as HTMLSelectElement).value).toBe('usage'); }); it('canonicalizes source aliases in storage URL params back to owned option values', async () => { @@ -1550,7 +1550,7 @@ describe('Storage', () => { label: option.textContent, })), ).toEqual([ - { value: 'all', label: 'All Sources' }, + { value: 'all', label: 'All sources' }, { value: 'proxmox-pve', label: 'PVE' }, { value: 'truenas', label: 'TrueNAS' }, ]); @@ -1728,7 +1728,7 @@ describe('Storage', () => { .getAllByRole('option') .map((option) => option.textContent) .filter(Boolean); - expect(options).toContain('All Disk Hosts'); + expect(options).toContain('All disk hosts'); expect(options).toContain('pve1'); expect(options).not.toContain('mini'); }); diff --git a/frontend-modern/src/components/Storage/__tests__/StorageControls.test.tsx b/frontend-modern/src/components/Storage/__tests__/StorageControls.test.tsx index 1468193d6..faf64bac9 100644 --- a/frontend-modern/src/components/Storage/__tests__/StorageControls.test.tsx +++ b/frontend-modern/src/components/Storage/__tests__/StorageControls.test.tsx @@ -15,7 +15,7 @@ describe('StorageControls', () => { createSignal<'all' | 'warning' | 'critical'>('all'); const [sourceFilter, setSourceFilter] = createSignal('all'); const [sourceOptions] = createSignal([ - { key: 'all', label: 'All Sources', tone: 'slate' as const }, + { key: 'all', label: 'All sources', tone: 'slate' as const }, { key: 'proxmox-pve', label: 'PVE', tone: 'orange' as const }, ]); const [selectedNodeId, setSelectedNodeId] = createSignal('all'); @@ -38,7 +38,7 @@ describe('StorageControls', () => { setSourceFilter={setSourceFilter} sourceOptions={sourceOptions} nodeFilterOptions={[ - { value: 'all', label: 'All Nodes' }, + { value: 'all', label: 'All nodes' }, { value: 'node-1', label: 'pve1' }, ]} selectedNodeId={selectedNodeId} @@ -65,7 +65,7 @@ describe('StorageControls', () => { createSignal<'all' | 'warning' | 'critical'>('all'); const [sourceFilter, setSourceFilter] = createSignal('truenas'); const [sourceOptions, setSourceOptions] = createSignal([ - { key: 'all', label: 'All Sources', tone: 'slate' as const }, + { key: 'all', label: 'All sources', tone: 'slate' as const }, ]); const [selectedNodeId, setSelectedNodeId] = createSignal('all'); @@ -86,14 +86,14 @@ describe('StorageControls', () => { sourceFilter={sourceFilter} setSourceFilter={setSourceFilter} sourceOptions={sourceOptions} - nodeFilterOptions={[{ value: 'all', label: 'All Nodes' }]} + nodeFilterOptions={[{ value: 'all', label: 'All nodes' }]} selectedNodeId={selectedNodeId} setSelectedNodeId={setSelectedNodeId} /> )); setSourceOptions([ - { key: 'all', label: 'All Sources', tone: 'slate' as const }, + { key: 'all', label: 'All sources', tone: 'slate' as const }, { key: 'proxmox-pve', label: 'PVE', tone: 'orange' as const }, { key: 'truenas', label: 'TrueNAS', tone: 'blue' as const }, ]); @@ -104,7 +104,7 @@ describe('StorageControls', () => { label: option.textContent, })), ).toEqual([ - { value: 'all', label: 'All Sources' }, + { value: 'all', label: 'All sources' }, { value: 'proxmox-pve', label: 'PVE' }, { value: 'truenas', label: 'TrueNAS' }, ]); diff --git a/frontend-modern/src/components/Storage/__tests__/storagePageState.test.ts b/frontend-modern/src/components/Storage/__tests__/storagePageState.test.ts index 5ffb7b483..07c4e46c4 100644 --- a/frontend-modern/src/components/Storage/__tests__/storagePageState.test.ts +++ b/frontend-modern/src/components/Storage/__tests__/storagePageState.test.ts @@ -117,8 +117,8 @@ describe('storagePageState', () => { expect(getStorageStatusFilterValue('attention')).toBe('attention'); expect(toStorageHealthFilterValue('available')).toBe('healthy'); expect(toStorageHealthFilterValue('attention')).toBe('attention'); - expect(getStorageNodeFilterLabel('pools')).toBe('All Nodes'); - expect(getStorageNodeFilterLabel('disks')).toBe('All Disk Hosts'); + expect(getStorageNodeFilterLabel('pools')).toBe('All nodes'); + expect(getStorageNodeFilterLabel('disks')).toBe('All disk hosts'); expect(DEFAULT_STORAGE_VIEW).toBe('pools'); expect(DEFAULT_STORAGE_SOURCE_FILTER).toBe('all'); expect(DEFAULT_STORAGE_DISK_ROLE_FILTER).toBe('all'); @@ -171,7 +171,7 @@ describe('storagePageState', () => { expect(coerceSelectedStorageNodeId('node-1', nodeOptions)).toBe('node-1'); expect(coerceSelectedStorageNodeId('missing', nodeOptions)).toBe('all'); expect(buildStorageNodeFilterOptions('disks', nodeOptions)).toEqual([ - { value: 'all', label: 'All Disk Hosts' }, + { value: 'all', label: 'All disk hosts' }, { value: 'node-1', label: 'pve1' }, { value: 'node-2', label: 'pve2' }, ]); diff --git a/frontend-modern/src/components/Storage/__tests__/storageSourceOptions.test.ts b/frontend-modern/src/components/Storage/__tests__/storageSourceOptions.test.ts index f1066273c..416057052 100644 --- a/frontend-modern/src/components/Storage/__tests__/storageSourceOptions.test.ts +++ b/frontend-modern/src/components/Storage/__tests__/storageSourceOptions.test.ts @@ -52,7 +52,7 @@ describe('storageSourceOptions', () => { makeStorage({ id: '4', type: 'custom-backend' }), ]); - expect(options[0]).toMatchObject({ key: 'all', label: 'All Sources' }); + expect(options[0]).toMatchObject({ key: 'all', label: 'All sources' }); expect(options.map((option) => option.key)).toEqual([ 'all', 'proxmox-pve', diff --git a/frontend-modern/src/components/Storage/__tests__/useStorageFilterState.test.ts b/frontend-modern/src/components/Storage/__tests__/useStorageFilterState.test.ts index 8725aa4a0..465a7e8a8 100644 --- a/frontend-modern/src/components/Storage/__tests__/useStorageFilterState.test.ts +++ b/frontend-modern/src/components/Storage/__tests__/useStorageFilterState.test.ts @@ -39,13 +39,13 @@ describe('useStorageFilterState', () => { ); expect(result.sourceFilterOptions()).toEqual([ - { key: 'all', label: 'All Sources', tone: 'slate' }, + { key: 'all', label: 'All sources', tone: 'slate' }, { key: 'proxmox-pve', label: 'PVE', tone: 'orange' }, { key: 'truenas', label: 'TrueNAS', tone: 'blue' }, { key: 'agent', label: 'Agent', tone: 'slate' }, ]); expect(result.nodeFilterOptions()).toEqual([ - { value: 'all', label: 'All Nodes' }, + { value: 'all', label: 'All nodes' }, { value: 'node-1', label: 'pve1' }, ]); expect(result.storageFilterGroupBy()).toBe('node'); @@ -56,9 +56,9 @@ describe('useStorageFilterState', () => { it('coerces stale selected nodes and disk facets, and maps status setters', () => { const [view] = createSignal<'pools' | 'disks'>('disks'); - const [nodeOptions] = createSignal([{ id: 'all', label: 'All Nodes' }]); + const [nodeOptions] = createSignal([{ id: 'all', label: 'All nodes' }]); const [diskNodeOptions] = createSignal([ - { id: 'all', label: 'All Nodes' }, + { id: 'all', label: 'All nodes' }, ]); const [selectedNodeId, setSelectedNodeId] = createSignal('missing'); const [sourceOptions] = createSignal(['all']); diff --git a/frontend-modern/src/components/Storage/__tests__/useStorageFilterToolbarModel.test.ts b/frontend-modern/src/components/Storage/__tests__/useStorageFilterToolbarModel.test.ts index 1e53109ed..15e6cdbd7 100644 --- a/frontend-modern/src/components/Storage/__tests__/useStorageFilterToolbarModel.test.ts +++ b/frontend-modern/src/components/Storage/__tests__/useStorageFilterToolbarModel.test.ts @@ -67,7 +67,7 @@ describe('useStorageFilterToolbarModel', () => { const [sortKey, setSortKey] = createSignal('priority'); const [sortDirection, setSortDirection] = createSignal<'asc' | 'desc'>('desc'); const [sourceOptions, setSourceOptions] = createSignal([ - { key: 'all', label: 'All Sources', tone: 'slate' as const }, + { key: 'all', label: 'All sources', tone: 'slate' as const }, ]); const { result } = renderHook(() => @@ -82,16 +82,16 @@ describe('useStorageFilterToolbarModel', () => { }), ); - expect(result.sourceOptions()).toEqual([{ key: 'all', label: 'All Sources', tone: 'slate' }]); + expect(result.sourceOptions()).toEqual([{ key: 'all', label: 'All sources', tone: 'slate' }]); setSourceOptions([ - { key: 'all', label: 'All Sources', tone: 'slate' }, + { key: 'all', label: 'All sources', tone: 'slate' }, { key: 'proxmox-pve', label: 'PVE', tone: 'orange' }, { key: 'truenas', label: 'TrueNAS', tone: 'blue' }, ]); expect(result.sourceOptions()).toEqual([ - { key: 'all', label: 'All Sources', tone: 'slate' }, + { key: 'all', label: 'All sources', tone: 'slate' }, { key: 'proxmox-pve', label: 'PVE', tone: 'orange' }, { key: 'truenas', label: 'TrueNAS', tone: 'blue' }, ]); diff --git a/frontend-modern/src/components/Storage/storagePageState.ts b/frontend-modern/src/components/Storage/storagePageState.ts index 105cb5416..c342fb9f5 100644 --- a/frontend-modern/src/components/Storage/storagePageState.ts +++ b/frontend-modern/src/components/Storage/storagePageState.ts @@ -75,9 +75,9 @@ export const STORAGE_STATUS_FILTER_OPTIONS: StorageOption[] = [ export const STORAGE_GROUP_BY_OPTIONS: StorageOption[] = [ { value: 'none', label: 'Flat' }, - { value: 'node', label: 'By Node' }, - { value: 'type', label: 'By Type' }, - { value: 'status', label: 'By Status' }, + { value: 'node', label: 'By node' }, + { value: 'type', label: 'By type' }, + { value: 'status', label: 'By status' }, ]; export const normalizeStorageHealthFilter = (value: string): StorageHealthFilter => { @@ -196,7 +196,7 @@ export const hasActiveStorageFilters = (state: StorageFilterActivityState): bool DEFAULT_STORAGE_DISK_GROUP_FILTER; export const getStorageNodeFilterLabel = (view: StorageView): string => - view === 'disks' ? 'All Disk Hosts' : 'All Nodes'; + view === 'disks' ? 'All disk hosts' : 'All nodes'; export const readStorageRouteValue = (value: string | undefined, defaultValue: string): string => { const normalized = (value || '').trim(); diff --git a/frontend-modern/src/features/alerts/AlertOverviewStatsCards.tsx b/frontend-modern/src/features/alerts/AlertOverviewStatsCards.tsx index 8d3f96668..10b7f354a 100644 --- a/frontend-modern/src/features/alerts/AlertOverviewStatsCards.tsx +++ b/frontend-modern/src/features/alerts/AlertOverviewStatsCards.tsx @@ -1,4 +1,9 @@ import { Card } from '@/components/shared/Card'; +import { + ALERT_OVERVIEW_ACKNOWLEDGED_LABEL, + ALERT_OVERVIEW_LAST_24_HOURS_LABEL, + ALERT_OVERVIEW_WORKLOAD_OVERRIDES_LABEL, +} from '@/utils/alertOverviewPresentation'; import type { AlertOverviewState } from './useAlertOverviewState'; @@ -13,7 +18,7 @@ export function AlertOverviewStatsCards(props: AlertOverviewStatsCardsProps) {

- Acknowledged + {ALERT_OVERVIEW_ACKNOWLEDGED_LABEL}

{props.state.alertStats().acknowledged} @@ -40,7 +45,7 @@ export function AlertOverviewStatsCards(props: AlertOverviewStatsCardsProps) {

- Last 24 Hours + {ALERT_OVERVIEW_LAST_24_HOURS_LABEL}

{props.state.alertStats().total24h} @@ -67,7 +72,7 @@ export function AlertOverviewStatsCards(props: AlertOverviewStatsCardsProps) {

- Guest Overrides + {ALERT_OVERVIEW_WORKLOAD_OVERRIDES_LABEL}

{props.state.alertStats().overrides} diff --git a/frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx b/frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx index fd1a256e0..70ead4b06 100644 --- a/frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx +++ b/frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx @@ -232,7 +232,7 @@ export function InfrastructurePageSurface() { setGroupingMode(value as GroupingMode)} - aria-label="Group By" + aria-label="Group by" options={[ { value: 'grouped', diff --git a/frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts b/frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts index 9cadf1f12..7c6328c9a 100644 --- a/frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts +++ b/frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts @@ -81,6 +81,8 @@ describe('InfrastructurePageSurface guardrails', () => { 'data-testid="infrastructure-interaction-surface"', ); expect(infrastructurePageSurfaceSource).toContain('data-summary-clear-ignore'); + expect(infrastructurePageSurfaceSource).toContain('aria-label="Group by"'); + expect(infrastructurePageSurfaceSource).not.toContain('aria-label="Group By"'); expect(infrastructurePageSurfaceSource).toContain("title: 'Grouped table view'"); expect(infrastructurePageSurfaceSource).toContain('Grouped'); expect(infrastructurePageSurfaceSource).not.toContain("title: 'Group by cluster'"); diff --git a/frontend-modern/src/utils/__tests__/alertConfigPresentation.test.ts b/frontend-modern/src/utils/__tests__/alertConfigPresentation.test.ts index 617d8944c..c76d8d0db 100644 --- a/frontend-modern/src/utils/__tests__/alertConfigPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/alertConfigPresentation.test.ts @@ -100,7 +100,7 @@ describe('alertConfigPresentation', () => { ); expect(ALERT_CONFIG_COOLDOWN_MAX_ALERTS_LABEL).toBe('Max alerts / hour'); expect(ALERT_CONFIG_COOLDOWN_MAX_ALERTS_SUFFIX).toBe('alerts'); - expect(ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP).toBe('Per guest/metric combination'); + expect(ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP).toBe('Per workload/metric combination'); expect(ALERT_CONFIG_GROUPING_TITLE).toBe('Smart grouping'); expect(ALERT_CONFIG_GROUPING_DESCRIPTION).toBe('Bundle similar alerts together.'); expect(ALERT_CONFIG_GROUPING_WINDOW_LABEL).toBe('Grouping window'); @@ -108,8 +108,8 @@ describe('alertConfigPresentation', () => { 'Alerts within this window are grouped together. Set to 0 to send immediately.', ); expect(ALERT_CONFIG_GROUPING_STRATEGY_LABEL).toBe('Grouping strategy'); - expect(ALERT_CONFIG_GROUPING_BY_NODE).toBe('By Node'); - expect(ALERT_CONFIG_GROUPING_BY_GUEST).toBe('By Guest'); + expect(ALERT_CONFIG_GROUPING_BY_NODE).toBe('By node'); + expect(ALERT_CONFIG_GROUPING_BY_GUEST).toBe('By workload'); expect(getAlertConfigQuietHourSuppressOptions()).toEqual([ { key: 'performance', diff --git a/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts b/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts index 295a2e95a..d3790a944 100644 --- a/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts @@ -5,6 +5,9 @@ import { ALERT_HISTORY_EMPTY_STATE, ALERT_HISTORY_LOADING_STATE, ALERT_HISTORY_SEARCH_PLACEHOLDER, + ALERT_OVERVIEW_ACKNOWLEDGED_LABEL, + ALERT_OVERVIEW_LAST_24_HOURS_LABEL, + ALERT_OVERVIEW_WORKLOAD_OVERRIDES_LABEL, ALERTS_EMPTY_STATE, ALERTS_PAGE_DEFAULT_TITLE, ALERTS_PAGE_DEFAULT_DESCRIPTION, @@ -62,6 +65,12 @@ describe('alertOverviewPresentation', () => { expect(getAlertListEmptyState(false)).toBe('No unacknowledged alerts'); }); + it('returns canonical alert overview stat labels', () => { + expect(ALERT_OVERVIEW_ACKNOWLEDGED_LABEL).toBe('Acknowledged'); + expect(ALERT_OVERVIEW_LAST_24_HOURS_LABEL).toBe('Last 24 Hours'); + expect(ALERT_OVERVIEW_WORKLOAD_OVERRIDES_LABEL).toBe('Workload Overrides'); + }); + it('returns canonical alert history search and empty-state copy', () => { expect(ALERT_HISTORY_SEARCH_PLACEHOLDER).toBe('Search alerts...'); expect(getAlertHistorySearchPlaceholder()).toBe('Search alerts...'); diff --git a/frontend-modern/src/utils/__tests__/alertThresholdsPresentation.test.ts b/frontend-modern/src/utils/__tests__/alertThresholdsPresentation.test.ts index c82282121..ba1d9bc3e 100644 --- a/frontend-modern/src/utils/__tests__/alertThresholdsPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/alertThresholdsPresentation.test.ts @@ -64,7 +64,7 @@ describe('alertThresholdsPresentation', () => { ); expect(PBS_THRESHOLDS_FILTER_EMPTY_STATE).toBe('No PBS servers match the current filters.'); expect(GUEST_THRESHOLDS_FILTER_EMPTY_STATE).toBe('No VMs or containers match the current filters.'); - expect(GUEST_FILTERING_EMPTY_STATE).toBe('Configure guest filtering rules.'); + expect(GUEST_FILTERING_EMPTY_STATE).toBe('Configure VM and container filtering rules.'); expect(BACKUP_THRESHOLDS_EMPTY_STATE).toBe('Configure recovery alert thresholds.'); expect(SNAPSHOT_THRESHOLDS_EMPTY_STATE).toBe('Configure snapshot age thresholds.'); expect(STORAGE_THRESHOLDS_EMPTY_STATE).toBe('No storage devices found.'); @@ -100,19 +100,19 @@ describe('alertThresholdsPresentation', () => { expect(ALERT_THRESHOLDS_DOCKER_IGNORED_PREFIXES_PLACEHOLDER).toBe('runner-'); expect(getAlertThresholdsGuestFilterPresentation()).toEqual({ ignoredPrefixes: { - title: 'Ignored Prefixes', - description: 'Skip metrics for guests starting with:', + title: 'Ignored prefixes', + description: 'Skip metrics for VMs and containers starting with:', placeholder: 'dev-', }, tagWhitelist: { - title: 'Tag Whitelist', + title: 'Required tags', description: - 'Only monitor guests with at least one of these tags (leave empty to disable whitelist):', + 'Only monitor VMs and containers with at least one of these tags (leave empty to disable this filter):', placeholder: 'production', }, tagBlacklist: { - title: 'Tag Blacklist', - description: 'Ignore guests with any of these tags:', + title: 'Ignored tags', + description: 'Ignore VMs and containers with any of these tags:', placeholder: 'maintenance', }, }); @@ -172,7 +172,7 @@ describe('alertThresholdsPresentation', () => { expect(ALERT_THRESHOLDS_SECTION_TITLE_NODES).toBe('Virtualization Hosts'); expect(ALERT_THRESHOLDS_SECTION_TITLE_PBS).toBe('PBS Servers'); expect(ALERT_THRESHOLDS_SECTION_TITLE_GUESTS).toBe('VMs & Containers'); - expect(ALERT_THRESHOLDS_SECTION_TITLE_GUEST_FILTERING).toBe('Guest Filtering'); + expect(ALERT_THRESHOLDS_SECTION_TITLE_GUEST_FILTERING).toBe('VM and Container Filtering'); expect(ALERT_THRESHOLDS_SECTION_TITLE_BACKUPS).toBe('Recovery'); expect(ALERT_THRESHOLDS_SECTION_TITLE_SNAPSHOTS).toBe('Snapshot Age'); expect(ALERT_THRESHOLDS_SECTION_TITLE_STORAGE).toBe('Storage Devices'); @@ -185,7 +185,7 @@ describe('alertThresholdsPresentation', () => { nodes: 'Virtualization Hosts', pbs: 'PBS Servers', guests: 'VMs & Containers', - guestFiltering: 'Guest Filtering', + guestFiltering: 'VM and Container Filtering', backups: 'Recovery', snapshots: 'Snapshot Age', storage: 'Storage Devices', diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index 10341ded1..f0ae3de43 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -839,7 +839,7 @@ describe('frontend resource type boundaries', () => { expect(dashboardWorkloadDerivedStateSource).toContain('buildGuestParentNodeMap('); expect(dashboardWorkloadRouteStateSource).not.toContain("from './workloadTopology'"); expect(dashboardWorkloadRouteModelSource).toContain("from './workloadTopology'"); - expect(dashboardWorkloadRouteModelSource).toContain('workloadNodeScopeId'); + expect(dashboardWorkloadRouteModelSource).toContain('workloadHostScopeId'); expect(dashboardWorkloadRouteModelSource).toContain('getKubernetesContextKey'); expect(dashboardWorkloadRouteStateSource).toContain('isWorkloadsRoute,'); expect(dashboardSelectionStateSource).toContain( diff --git a/frontend-modern/src/utils/alertConfigPresentation.ts b/frontend-modern/src/utils/alertConfigPresentation.ts index e1abc4251..2c85cf752 100644 --- a/frontend-modern/src/utils/alertConfigPresentation.ts +++ b/frontend-modern/src/utils/alertConfigPresentation.ts @@ -22,15 +22,15 @@ export const ALERT_CONFIG_COOLDOWN_PERIOD_HELP = 'Minimum time between alerts for the same issue'; export const ALERT_CONFIG_COOLDOWN_MAX_ALERTS_LABEL = 'Max alerts / hour'; export const ALERT_CONFIG_COOLDOWN_MAX_ALERTS_SUFFIX = 'alerts'; -export const ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP = 'Per guest/metric combination'; +export const ALERT_CONFIG_COOLDOWN_MAX_ALERTS_HELP = 'Per workload/metric combination'; export const ALERT_CONFIG_GROUPING_TITLE = 'Smart grouping'; export const ALERT_CONFIG_GROUPING_DESCRIPTION = 'Bundle similar alerts together.'; export const ALERT_CONFIG_GROUPING_WINDOW_LABEL = 'Grouping window'; export const ALERT_CONFIG_GROUPING_WINDOW_HELP = 'Alerts within this window are grouped together. Set to 0 to send immediately.'; export const ALERT_CONFIG_GROUPING_STRATEGY_LABEL = 'Grouping strategy'; -export const ALERT_CONFIG_GROUPING_BY_NODE = 'By Node'; -export const ALERT_CONFIG_GROUPING_BY_GUEST = 'By Guest'; +export const ALERT_CONFIG_GROUPING_BY_NODE = 'By node'; +export const ALERT_CONFIG_GROUPING_BY_GUEST = 'By workload'; export const ALERT_CONFIG_QUIET_HOUR_SUPPRESS_OPTIONS = [ { key: 'performance', @@ -129,7 +129,7 @@ export function getAlertConfigSummaryGrouping( byNode: boolean, byGuest: boolean, ) { - const groupingTargets = [byNode && 'node', byGuest && 'guest'].filter(Boolean).join(' and '); + const groupingTargets = [byNode && 'node', byGuest && 'workload'].filter(Boolean).join(' and '); return groupingTargets ? `${ALERT_CONFIG_SUMMARY_GROUPING_PREFIX} ${windowMinutes} minute windows by ${groupingTargets}` : `${ALERT_CONFIG_SUMMARY_GROUPING_PREFIX} ${windowMinutes} minute windows`; diff --git a/frontend-modern/src/utils/alertOverviewPresentation.ts b/frontend-modern/src/utils/alertOverviewPresentation.ts index 4c7fead3c..c8b10b66c 100644 --- a/frontend-modern/src/utils/alertOverviewPresentation.ts +++ b/frontend-modern/src/utils/alertOverviewPresentation.ts @@ -11,6 +11,9 @@ export const ALERT_HISTORY_EMPTY_STATE = 'No alerts found'; export const ALERT_HISTORY_EMPTY_DESCRIPTION = 'Try adjusting your filters or check back later'; export const ALERT_BUCKET_EMPTY_LABEL = 'No alerts'; export const ALERT_HISTORY_LOADING_STATE = 'Loading alert history...'; +export const ALERT_OVERVIEW_ACKNOWLEDGED_LABEL = 'Acknowledged'; +export const ALERT_OVERVIEW_LAST_24_HOURS_LABEL = 'Last 24 Hours'; +export const ALERT_OVERVIEW_WORKLOAD_OVERRIDES_LABEL = 'Workload Overrides'; export const ALERTS_PAGE_DEFAULT_TITLE = 'Alerts'; export const ALERTS_PAGE_OVERVIEW_TITLE = 'Alerts Overview'; export const ALERTS_PAGE_THRESHOLDS_TITLE = 'Alert Thresholds'; diff --git a/frontend-modern/src/utils/alertThresholdsPresentation.ts b/frontend-modern/src/utils/alertThresholdsPresentation.ts index 59a2d60a8..a49499d09 100644 --- a/frontend-modern/src/utils/alertThresholdsPresentation.ts +++ b/frontend-modern/src/utils/alertThresholdsPresentation.ts @@ -3,7 +3,7 @@ export const GUEST_THRESHOLDS_EMPTY_STATE = 'No VMs or containers found.'; export const NODE_THRESHOLDS_FILTER_EMPTY_STATE = 'No virtualization hosts match the current filters.'; export const PBS_THRESHOLDS_FILTER_EMPTY_STATE = 'No PBS servers match the current filters.'; export const GUEST_THRESHOLDS_FILTER_EMPTY_STATE = 'No VMs or containers match the current filters.'; -export const GUEST_FILTERING_EMPTY_STATE = 'Configure guest filtering rules.'; +export const GUEST_FILTERING_EMPTY_STATE = 'Configure VM and container filtering rules.'; export const BACKUP_THRESHOLDS_EMPTY_STATE = 'Configure recovery alert thresholds.'; export const SNAPSHOT_THRESHOLDS_EMPTY_STATE = 'Configure snapshot age thresholds.'; export const STORAGE_THRESHOLDS_EMPTY_STATE = 'No storage devices found.'; @@ -20,17 +20,17 @@ export const CONTAINER_RUNTIMES_FILTER_EMPTY_STATE = export const CONTAINERS_FILTER_EMPTY_STATE = 'No containers match the current filters.'; export const ALERT_THRESHOLDS_SEARCH_PLACEHOLDER = 'Search resources...'; export const ALERT_THRESHOLDS_HELP_DISMISS_LABEL = 'Dismiss tips'; -export const ALERT_THRESHOLDS_GUEST_IGNORED_PREFIXES_TITLE = 'Ignored Prefixes'; +export const ALERT_THRESHOLDS_GUEST_IGNORED_PREFIXES_TITLE = 'Ignored prefixes'; export const ALERT_THRESHOLDS_GUEST_IGNORED_PREFIXES_DESCRIPTION = - 'Skip metrics for guests starting with:'; + 'Skip metrics for VMs and containers starting with:'; export const ALERT_THRESHOLDS_GUEST_IGNORED_PREFIXES_PLACEHOLDER = 'dev-'; -export const ALERT_THRESHOLDS_GUEST_TAG_WHITELIST_TITLE = 'Tag Whitelist'; +export const ALERT_THRESHOLDS_GUEST_TAG_WHITELIST_TITLE = 'Required tags'; export const ALERT_THRESHOLDS_GUEST_TAG_WHITELIST_DESCRIPTION = - 'Only monitor guests with at least one of these tags (leave empty to disable whitelist):'; + 'Only monitor VMs and containers with at least one of these tags (leave empty to disable this filter):'; export const ALERT_THRESHOLDS_GUEST_TAG_WHITELIST_PLACEHOLDER = 'production'; -export const ALERT_THRESHOLDS_GUEST_TAG_BLACKLIST_TITLE = 'Tag Blacklist'; +export const ALERT_THRESHOLDS_GUEST_TAG_BLACKLIST_TITLE = 'Ignored tags'; export const ALERT_THRESHOLDS_GUEST_TAG_BLACKLIST_DESCRIPTION = - 'Ignore guests with any of these tags:'; + 'Ignore VMs and containers with any of these tags:'; export const ALERT_THRESHOLDS_GUEST_TAG_BLACKLIST_PLACEHOLDER = 'maintenance'; export const ALERT_THRESHOLDS_BACKUP_ORPHANED_TITLE = 'Orphaned backups'; export const ALERT_THRESHOLDS_BACKUP_ORPHANED_DESCRIPTION = @@ -64,7 +64,7 @@ export const ALERT_THRESHOLDS_DOCKER_SERVICES_GAP_VALIDATION_MESSAGE = export const ALERT_THRESHOLDS_SECTION_TITLE_NODES = 'Virtualization Hosts'; export const ALERT_THRESHOLDS_SECTION_TITLE_PBS = 'PBS Servers'; export const ALERT_THRESHOLDS_SECTION_TITLE_GUESTS = 'VMs & Containers'; -export const ALERT_THRESHOLDS_SECTION_TITLE_GUEST_FILTERING = 'Guest Filtering'; +export const ALERT_THRESHOLDS_SECTION_TITLE_GUEST_FILTERING = 'VM and Container Filtering'; export const ALERT_THRESHOLDS_SECTION_TITLE_BACKUPS = 'Recovery'; export const ALERT_THRESHOLDS_SECTION_TITLE_SNAPSHOTS = 'Snapshot Age'; export const ALERT_THRESHOLDS_SECTION_TITLE_STORAGE = 'Storage Devices'; diff --git a/frontend-modern/src/utils/storageSources.ts b/frontend-modern/src/utils/storageSources.ts index 138e47a77..b1a0670df 100644 --- a/frontend-modern/src/utils/storageSources.ts +++ b/frontend-modern/src/utils/storageSources.ts @@ -19,7 +19,7 @@ export interface StorageSourceOption { const ALL_STORAGE_SOURCE_OPTION: StorageSourceOption = { key: 'all', - label: 'All Sources', + label: 'All sources', tone: 'slate', };