Canonicalize shared all-filter labels

This commit is contained in:
rcourtman 2026-04-29 11:23:12 +01:00
parent bfd6b42f15
commit 2e473b2ba1
23 changed files with 139 additions and 31 deletions

View file

@ -228,6 +228,9 @@ Alert history filter defaults such as the all-time period option must likewise
come from the alert overview/history presentation helper and the shared
filter-option label primitive rather than hard-coded title-case strings in the
history filter card.
Alert configuration select options share that same rule: all-channel
escalation labels must come from `alertConfigPresentation.ts` plus the shared
filter-option primitive, not a schedule-page-local `All Channels` string.
Thresholds empty states that hand operators to Infrastructure settings must use
`frontend-modern/src/utils/infrastructureSettingsPresentation.ts` for the
canonical `Settings → Infrastructure` label instead of hard-coding generic

View file

@ -1677,6 +1677,14 @@ fetch orchestration. The shell must not re-accumulate localStorage or API
runtime logic inline. Audit-log filter option labels must come from
`frontend-modern/src/utils/auditLogPresentation.ts` and the shared filter-option
label primitive instead of hard-coded title-case strings in the settings shell.
That shared filter-option primitive is also the canonical owner for default
`All <scope>` option wording wherever a product surface exposes filter selects
or segmented filter choices. Workloads/dashboard filters, storage source
filters, recovery history and platform/type filters, Kubernetes namespace
drawers, resource-change timeline filters, and alert configuration options must
call `frontend-modern/src/components/shared/filterOptionPresentation.ts` through
their nearest presentation/model owner instead of hard-coding page-local `All
...` labels.
The audit webhook settings surface now follows that same owner split.
`frontend-modern/src/components/Settings/AuditWebhookPanel.tsx` stays the

View file

@ -208,9 +208,12 @@ regression protection.
header row exposes names such as Uptime, Image, Context, and Node instead
of collapsing to only the visible text columns.
Pod-mode workload filters must use the workload-owned
`K8s Cluster` / `All K8s clusters` labels for Kubernetes context selection,
`K8s cluster` / `All K8s clusters` labels for Kubernetes context selection,
rather than a generic `Cluster` label that can be confused with Proxmox
cluster terminology on adjacent infrastructure surfaces.
cluster terminology on adjacent infrastructure surfaces, and all workload
default filter-option labels must flow through the shared all-option
presentation helper via the workload filter config model instead of the
hot-path shell hard-coding local `All ...` strings.
26. Keep long-range workload chart capping time-proportional across `frontend-modern/src/components/Workloads/WorkloadsSummary.tsx`, `frontend-modern/src/api/charts.ts`, and `internal/api/router.go`: when the workload hot path caps mixed-cadence history for top cards, it must bucket by time window rather than raw point index so 7-day and 30-day workload cards stay visually even without relaxing the protected payload budget.
27. Keep summary hover/focus and sticky-card behavior on shared hot paths: infrastructure, workloads, and storage summary shells must reuse one page/group/entity scope model plus `frontend-modern/src/components/shared/StickySummarySection.tsx` inside the app scroll shell instead of per-page scroll listeners or per-card hover derivations, so row scrubbing highlights all cards, workload group headers, infrastructure cluster headers, and storage pool-group headers scope the summary coherently, pinned group focus remains route-backed and reversible, and the hot path does not multiply render or scroll work. That hot path stays row-first rather than adding fallback chrome: the on-screen row or group header is the scoped state, and any explicit reset belongs to one compact shared table-header action plus the shared `Escape` path, not to page-level scope strips, search-row widgets, or filter-bar badges. Background whitespace clearing may exist as a convenience, but the hot path must not depend on brittle dead-space hit testing as the only reversible control. The same hot path must therefore keep one page-level reset owner for filters plus pinned summary selections, and it must keep chart-backed summary-card geometry explicit and stable so hover rerenders, synchronized readouts, or idle header metadata cannot feed layout loops that grow or shrink the top cards over time. Recoverys summary rail is not part of this interactive hot path; it may share summary-card framing, but it must remain non-interactive until a separately governed model says otherwise.
The input path for that hot summary contract must stay shared too:
@ -805,6 +808,10 @@ The dashboard-owned filter-config assembly now lives in
filter runtime changes must extend through those owners instead of
reintroducing dashboard-local state, reset drift, or inline config assembly
into the shell.
Workload type option labels are part of that filter-config model ownership:
`DashboardFilter.tsx` must render the exported workload type option catalog
instead of embedding its own `System containers` / `App containers` wording in
the shell.
The dashboard threshold slider now follows that same pattern: the shell stays
in `frontend-modern/src/components/Dashboard/ThresholdSlider.tsx`, while
metric-type text and fill presentation live in

View file

@ -869,6 +869,10 @@ 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.
Recovery all-history, all-item-type, and all-platform defaults follow the same
shared filter-option contract through
`frontend-modern/src/utils/recoveryTablePresentation.ts`, so recovery history
and protected-item tables do not invent separate default-filter wording.
Physical-disk role and group filter defaults plus disk-type display labels
must likewise come from `frontend-modern/src/features/storageBackups/diskPresentation.ts`;
storage pages must not reintroduce local `All Roles`, `All Groups`, or

View file

@ -1192,6 +1192,11 @@ timeline reads as the primary inspection surface instead of opening on a form.
When that reveal is open, the controls still stack vertically instead of using
a paired filter grid, so the filter state reads as one subordinate control
column instead of a competing secondary layout.
Timeline filter default options such as all kinds, all sources, and all
adapters must use the shared frontend all-option presentation helper through
`ResourceDetailDrawerOverviewTab.tsx`, not drawer-local hard-coded strings, so
future wording changes stay aligned with workload, storage, recovery, and alert
filters.
When filters are active, the filtered facet bundle drives both the summary
chips and the event log, so the header and the list stay aligned instead of
showing stale unfiltered counts above filtered results.

View file

@ -6,6 +6,7 @@ import { PageControls } from '@/components/shared/PageControls';
import { SearchInput } from '@/components/shared/SearchInput';
import { STORAGE_KEYS } from '@/utils/localStorage';
import type { DashboardFilterProps } from './dashboardFilterModel';
import { DASHBOARD_WORKLOAD_TYPE_OPTIONS } from './dashboardWorkloadFilterConfigModel';
import { useDashboardFilterState } from './useDashboardFilterState';
export const DashboardFilter: Component<DashboardFilterProps> = (props) => {
@ -153,11 +154,9 @@ export const DashboardFilter: Component<DashboardFilterProps> = (props) => {
}
selectClass="min-w-[7rem]"
>
<option value="all">All</option>
<option value="vm">VMs</option>
<option value="system-container">System Containers</option>
<option value="app-container">App Containers</option>
<option value="pod">Pods</option>
<For each={DASHBOARD_WORKLOAD_TYPE_OPTIONS}>
{(option) => <option value={option.value}>{option.label}</option>}
</For>
</LabeledFilterSelect>
<LabeledFilterSelect

View file

@ -370,7 +370,7 @@ describe('Dashboard pod workloads integration', () => {
await waitFor(() => {
expect(lastHostFilter).toBeDefined();
expect(lastHostFilter?.label).toBe('K8s Cluster');
expect(lastHostFilter?.label).toBe('K8s cluster');
});
const hostFilter = requireLastHostFilter();
hostFilter.onChange('cluster-a');

View file

@ -676,10 +676,13 @@ describe('Dashboard performance contract', () => {
'export const buildDashboardHostFilterConfig',
);
expect(dashboardWorkloadFilterConfigModelSource).toContain(
"DASHBOARD_KUBERNETES_CONTEXT_FILTER_LABEL = 'K8s Cluster'",
"DASHBOARD_KUBERNETES_CONTEXT_FILTER_LABEL = 'K8s cluster'",
);
expect(dashboardWorkloadFilterConfigModelSource).toContain(
"DASHBOARD_KUBERNETES_CONTEXT_ALL_OPTION_LABEL = 'All K8s clusters'",
"getAllFilterOptionLabel('K8s clusters')",
);
expect(dashboardWorkloadFilterConfigModelSource).toContain(
'DASHBOARD_WORKLOAD_TYPE_OPTIONS',
);
expect(dashboardWorkloadFilterConfigModelSource).toContain(
'export const buildDashboardNamespaceFilterConfig',

View file

@ -86,7 +86,9 @@ describe('DashboardFilter', () => {
const options = typeSelect.querySelectorAll('option');
const values = Array.from(options).map((o) => o.value);
const labels = Array.from(options).map((o) => o.textContent);
expect(values).toEqual(['all', 'vm', 'system-container', 'app-container', 'pod']);
expect(labels).toEqual(['All', 'VMs', 'System containers', 'App containers', 'Pods']);
});
it('renders the Status filter select with all options', () => {

View file

@ -5,8 +5,13 @@ import {
buildDashboardHostFilterConfig,
buildDashboardNamespaceFilterConfig,
buildDashboardPlatformFilterConfig,
DASHBOARD_CONTAINER_RUNTIME_ALL_OPTION_LABEL,
DASHBOARD_KUBERNETES_CONTEXT_ALL_OPTION_LABEL,
DASHBOARD_KUBERNETES_CONTEXT_FILTER_LABEL,
DASHBOARD_NAMESPACE_ALL_OPTION_LABEL,
DASHBOARD_NODE_ALL_OPTION_LABEL,
DASHBOARD_PLATFORM_ALL_OPTION_LABEL,
DASHBOARD_WORKLOAD_TYPE_OPTIONS,
} from '../dashboardWorkloadFilterConfigModel';
describe('dashboardWorkloadFilterConfigModel', () => {
@ -25,6 +30,11 @@ describe('dashboardWorkloadFilterConfigModel', () => {
id: 'workloads-container-runtime-filter',
label: 'Runtime',
value: 'docker',
options: [
{ value: '', label: DASHBOARD_CONTAINER_RUNTIME_ALL_OPTION_LABEL },
{ value: 'containerd', label: 'containerd' },
{ value: 'docker', label: 'docker' },
],
});
expect(
@ -79,6 +89,10 @@ describe('dashboardWorkloadFilterConfigModel', () => {
id: 'workloads-node-filter',
label: 'Node',
value: 'cluster-a-node-a',
options: [
{ value: '', label: DASHBOARD_NODE_ALL_OPTION_LABEL },
{ value: 'cluster-a-node-a', label: 'node-a' },
],
});
});
@ -97,6 +111,11 @@ describe('dashboardWorkloadFilterConfigModel', () => {
id: 'workloads-k8s-namespace-filter',
label: 'Namespace',
value: 'default',
options: [
{ value: '', label: DASHBOARD_NAMESPACE_ALL_OPTION_LABEL },
{ value: 'default', label: 'default' },
{ value: 'kube-system', label: 'kube-system' },
],
});
expect(
@ -128,10 +147,20 @@ describe('dashboardWorkloadFilterConfigModel', () => {
label: 'Platform',
value: 'truenas',
options: [
{ value: '', label: 'All platforms' },
{ value: '', label: DASHBOARD_PLATFORM_ALL_OPTION_LABEL },
{ value: 'docker', label: 'Docker' },
{ value: 'truenas', label: 'TrueNAS' },
],
});
});
it('keeps workload type filter labels in the presentation model', () => {
expect(DASHBOARD_WORKLOAD_TYPE_OPTIONS).toEqual([
{ value: 'all', label: 'All' },
{ value: 'vm', label: 'VMs' },
{ value: 'system-container', label: 'System containers' },
{ value: 'app-container', label: 'App containers' },
{ value: 'pod', label: 'Pods' },
]);
});
});

View file

@ -1,9 +1,23 @@
import type { ViewMode } from '@/types/workloads';
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
import type { DashboardToolbarFilterConfig } from './dashboardFilterModel';
import type { DashboardWorkloadNodeOption } from './dashboardWorkloadRouteModel';
export const DASHBOARD_KUBERNETES_CONTEXT_FILTER_LABEL = 'K8s Cluster';
export const DASHBOARD_KUBERNETES_CONTEXT_ALL_OPTION_LABEL = 'All K8s clusters';
export const DASHBOARD_KUBERNETES_CONTEXT_FILTER_LABEL = 'K8s cluster';
export const DASHBOARD_KUBERNETES_CONTEXT_ALL_OPTION_LABEL =
getAllFilterOptionLabel('K8s clusters');
export const DASHBOARD_CONTAINER_RUNTIME_ALL_OPTION_LABEL =
getAllFilterOptionLabel('runtimes');
export const DASHBOARD_PLATFORM_ALL_OPTION_LABEL = getAllFilterOptionLabel('platforms');
export const DASHBOARD_NODE_ALL_OPTION_LABEL = getAllFilterOptionLabel('nodes');
export const DASHBOARD_NAMESPACE_ALL_OPTION_LABEL = getAllFilterOptionLabel('namespaces');
export const DASHBOARD_WORKLOAD_TYPE_OPTIONS: Array<{ value: ViewMode; label: string }> = [
{ value: 'all', label: 'All' },
{ value: 'vm', label: 'VMs' },
{ value: 'system-container', label: 'System containers' },
{ value: 'app-container', label: 'App containers' },
{ value: 'pod', label: 'Pods' },
];
interface DashboardContainerRuntimeFilterConfigOptions {
isWorkloadsRoute: boolean;
@ -29,7 +43,7 @@ export const buildDashboardContainerRuntimeFilterConfig = ({
label: 'Runtime',
value: containerRuntime,
options: [
{ value: '', label: 'All runtimes' },
{ value: '', label: DASHBOARD_CONTAINER_RUNTIME_ALL_OPTION_LABEL },
...runtimeOptions.map((value) => ({ value, label: value })),
],
onChange,
@ -57,7 +71,7 @@ export const buildDashboardPlatformFilterConfig = ({
id: 'workloads-platform-filter',
label: 'Platform',
value: selectedPlatform ?? '',
options: [{ value: '', label: 'All platforms' }, ...platformOptions],
options: [{ value: '', label: DASHBOARD_PLATFORM_ALL_OPTION_LABEL }, ...platformOptions],
onChange,
};
};
@ -102,7 +116,7 @@ export const buildDashboardHostFilterConfig = ({
id: 'workloads-node-filter',
label: 'Node',
value: selectedNode ?? '',
options: [{ value: '', label: 'All nodes' }, ...workloadNodeOptions],
options: [{ value: '', label: DASHBOARD_NODE_ALL_OPTION_LABEL }, ...workloadNodeOptions],
onChange: onNodeChange,
};
};
@ -131,7 +145,7 @@ export const buildDashboardNamespaceFilterConfig = ({
label: 'Namespace',
value: selectedNamespace ?? '',
options: [
{ value: '', label: 'All namespaces' },
{ value: '', label: DASHBOARD_NAMESPACE_ALL_OPTION_LABEL },
...namespaceOptions.map((value) => ({ value, label: value })),
],
onChange,

View file

@ -18,6 +18,7 @@ import { TemperaturesCard } from '@/components/shared/cards/TemperaturesCard';
import { RaidCard } from '@/components/shared/cards/RaidCard';
import { DiscoveryTab } from '@/components/Discovery/DiscoveryTab';
import { WebInterfaceUrlField } from '@/components/shared/WebInterfaceUrlField';
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
import { getServiceHealthPresentation } from '@/utils/serviceHealthPresentation';
import {
getResourceRoutingScopeLabel,
@ -69,7 +70,7 @@ const vmwareRowToneClass = (tone?: 'default' | 'accent' | 'warning'): string =>
};
const timelineKindOptions: Array<{ label: string; value: ResourceChangeKind | '' }> = [
{ label: 'All kinds', value: '' },
{ label: getAllFilterOptionLabel('kinds'), value: '' },
...RESOURCE_CHANGE_KIND_ORDER.map((kind) => ({
label: getResourceChangeKindPresentation(kind).label,
value: kind,
@ -77,7 +78,7 @@ const timelineKindOptions: Array<{ label: string; value: ResourceChangeKind | ''
];
const timelineSourceTypeOptions: Array<{ label: string; value: ResourceChangeSourceType | '' }> = [
{ label: 'All sources', value: '' },
{ label: getAllFilterOptionLabel('sources'), value: '' },
...RESOURCE_CHANGE_SOURCE_TYPE_ORDER.map((sourceType) => ({
label: getResourceChangeSourceTypePresentation(sourceType).label,
value: sourceType,
@ -88,7 +89,7 @@ const timelineSourceAdapterOptions: Array<{
label: string;
value: ResourceChangeSourceAdapter | '';
}> = [
{ label: 'All adapters', value: '' },
{ label: getAllFilterOptionLabel('adapters'), value: '' },
...RESOURCE_CHANGE_SOURCE_ADAPTER_ORDER.map((sourceAdapter) => ({
label: getResourceChangeSourceAdapterPresentation(sourceAdapter).label,
value: sourceAdapter,

View file

@ -122,6 +122,10 @@ describe('ResourceDetailDrawer change history section', () => {
expect(discoveryTabSource).not.toContain('triggerDiscovery(');
expect(discoveryTabSource).not.toContain('updateDiscoveryNotes(');
expect(resourceDetailDrawerOverviewSource).toContain("from '@/components/shared/TagBadges'");
expect(resourceDetailDrawerOverviewSource).toContain('getAllFilterOptionLabel');
expect(resourceDetailDrawerOverviewSource).not.toContain("'All kinds'");
expect(resourceDetailDrawerOverviewSource).not.toContain("'All sources'");
expect(resourceDetailDrawerOverviewSource).not.toContain("'All adapters'");
expect(resourceDetailDrawerOverviewSource).not.toContain(
"from '@/components/Dashboard/TagBadges'",
);

View file

@ -37,6 +37,7 @@ import {
import { normalizeRecoveryModeQueryValue } from '@/utils/recoveryRecordPresentation';
import {
getRecoveryArtifactColumnLabel,
getRecoveryAllHistoryLabel,
getRecoveryAllItemTypesLabel,
getRecoveryAllPlatformsLabel,
getRecoveryHistorySearchPlaceholder,
@ -250,7 +251,7 @@ export const RecoveryHistorySection: Component<RecoveryHistorySectionProps> = (p
}}
class={RECOVERY_ADVANCED_FILTER_FIELD_CLASS}
>
<option value="all">All history</option>
<option value="all">{getRecoveryAllHistoryLabel()}</option>
<option value="workload">Workloads only</option>
</select>
</label>

View file

@ -1,4 +1,5 @@
import type { Accessor } from 'solid-js';
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
import type { StorageHealthFilter, StorageRecord } from '@/features/storageBackups/models';
import {
DEFAULT_PHYSICAL_DISK_FACET_FILTER,
@ -196,7 +197,9 @@ 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'
? getAllFilterOptionLabel('disk hosts')
: getAllFilterOptionLabel('nodes');
export const readStorageRouteValue = (value: string | undefined, defaultValue: string): string => {
const normalized = (value || '').trim();

View file

@ -115,7 +115,7 @@ describe('FilterHeader', () => {
}
: {
id: 'workloads-k8s-context-filter',
label: 'K8s Cluster',
label: 'K8s cluster',
options: [{ value: '', label: 'All K8s clusters' }],
};
@ -142,7 +142,7 @@ describe('FilterHeader', () => {
screen.getByRole('button', { name: 'Show pods' }).click();
await waitFor(() =>
expect(screen.getByLabelText('K8s Cluster')).toBe(screen.getByTestId('dynamic-filter')),
expect(screen.getByLabelText('K8s cluster')).toBe(screen.getByTestId('dynamic-filter')),
);
});

View file

@ -140,7 +140,7 @@ describe('alertConfigPresentation', () => {
);
expect(ALERT_CONFIG_ESCALATION_NOTIFY_EMAIL).toBe('Email');
expect(ALERT_CONFIG_ESCALATION_NOTIFY_WEBHOOKS).toBe('Webhooks');
expect(ALERT_CONFIG_ESCALATION_NOTIFY_ALL).toBe('All Channels');
expect(ALERT_CONFIG_ESCALATION_NOTIFY_ALL).toBe('All channels');
expect(ALERT_CONFIG_SUMMARY_TITLE).toBe('Configuration summary');
expect(ALERT_CONFIG_SUMMARY_DESCRIPTION).toBe(
'Preview of the active schedule settings.',
@ -193,7 +193,7 @@ describe('alertConfigPresentation', () => {
);
expect(getAlertConfigEscalationNotifyLabel('email')).toBe('Email');
expect(getAlertConfigEscalationNotifyLabel('webhook')).toBe('Webhooks');
expect(getAlertConfigEscalationNotifyLabel('all')).toBe('All Channels');
expect(getAlertConfigEscalationNotifyLabel('all')).toBe('All channels');
expect(getAlertConfigSummaryRecoveryEnabled()).toBe(
ALERT_CONFIG_SUMMARY_RECOVERY,
);

View file

@ -697,6 +697,7 @@ describe('frontend resource type boundaries', () => {
expect(sourcePlatformsSource).toContain('titleCaseDelimitedLabel');
expect(sourcePlatformsSource).not.toContain('const titleize =');
expect(storageSourcesSource).toContain('titleCaseDelimitedLabel');
expect(storageSourcesSource).toContain('getAllFilterOptionLabel');
expect(storageSourcesSource).not.toContain('const titleCaseLabel =');
expect(workloadsSource).toContain('export const normalizeWorkloadViewModeParam');
expect(orgScopeSource).toContain("export const DEFAULT_ORG_SCOPE = 'default'");
@ -804,6 +805,10 @@ describe('frontend resource type boundaries', () => {
expect(dashboardWorkloadFilterConfigModelSource).toContain(
'export const buildDashboardHostFilterConfig',
);
expect(dashboardWorkloadFilterConfigModelSource).toContain('getAllFilterOptionLabel');
expect(dashboardWorkloadFilterConfigModelSource).toContain(
'DASHBOARD_WORKLOAD_TYPE_OPTIONS',
);
expect(dashboardWorkloadFilterConfigModelSource).toContain(
'export const buildDashboardNamespaceFilterConfig',
);
@ -989,6 +994,7 @@ describe('frontend resource type boundaries', () => {
expect(guestRowSource).not.toContain('buildGuestId');
expect(tagBadgesSource).toContain("from '@/components/shared/Tooltip'");
expect(resourceDetailDrawerOverviewSource).toContain("from '@/components/shared/TagBadges'");
expect(resourceDetailDrawerOverviewSource).toContain('getAllFilterOptionLabel');
expect(resourceDetailDrawerOverviewSource).not.toContain(
"from '@/components/Dashboard/TagBadges'",
);
@ -1111,6 +1117,8 @@ describe('frontend resource type boundaries', () => {
expect(recoveryHistorySectionSource).toContain('useRecoveryHistorySectionState');
expect(recoveryHistorySectionSource).toContain('RecoveryHistoryTable');
expect(recoveryHistorySectionSource).toContain('TableCard');
expect(recoveryHistorySectionSource).toContain('getRecoveryAllHistoryLabel');
expect(recoveryTablePresentationSource).toContain('getAllFilterOptionLabel');
expect(recoveryHistorySectionSource).not.toContain(
'overflow-hidden border-border-subtle bg-surface',
);
@ -2428,6 +2436,7 @@ describe('frontend resource type boundaries', () => {
expect(storagePageStateSource).toContain('export const countActiveStorageFilters');
expect(storagePageStateSource).toContain('export const hasActiveStorageFilters');
expect(storagePageStateSource).toContain('export const getStorageNodeFilterLabel');
expect(storagePageStateSource).toContain('getAllFilterOptionLabel');
expect(storagePageStateSource).toContain('export const readStorageRouteValue');
expect(storagePageStateSource).toContain('export const writeStorageRouteValue');
expect(storagePageStateSource).toContain('export const coerceSelectedStorageNodeId');
@ -2470,6 +2479,7 @@ describe('frontend resource type boundaries', () => {
expect(k8sDeploymentsDrawerSource).toContain('getK8sDeploymentsDrawerPresentation');
expect(k8sDeploymentsDrawerSource).toContain('getK8sDeploymentsEmptyState');
expect(k8sDeploymentsDrawerSource).toContain('getK8sDeploymentsLoadingState');
expect(k8sDeploymentPresentationSource).toContain('getAllFilterOptionLabel');
expect(k8sDeploymentsDrawerSource).not.toContain('const statusTone =');
expect(k8sDeploymentsDrawerSource).not.toContain('Desired state controllers (not Pods)');
expect(k8sDeploymentsDrawerSource).not.toContain('Search deployments...');

View file

@ -5,6 +5,7 @@ import {
getRecoveryArtifactColumnHeaderClass,
getRecoveryArtifactRowClass,
getRecoveryArtifactTableMinWidth,
getRecoveryAllHistoryLabel,
getRecoveryAllItemTypesLabel,
getRecoveryAllPlatformsLabel,
getRecoveryAnyItemLabel,
@ -21,6 +22,7 @@ import {
isRecoveryRollupStale,
RECOVERY_ADVANCED_FILTER_FIELD_CLASS,
RECOVERY_ADVANCED_FILTER_LABEL_CLASS,
RECOVERY_ALL_HISTORY_LABEL,
RECOVERY_ALL_ITEM_TYPES_LABEL,
RECOVERY_ALL_PLATFORMS_LABEL,
RECOVERY_ANY_ITEM_LABEL,
@ -43,6 +45,7 @@ describe('recoveryTablePresentation', () => {
expect(RECOVERY_PROTECTED_SEARCH_PLACEHOLDER).toBe('Search protected items...');
expect(RECOVERY_HISTORY_SEARCH_PLACEHOLDER).toBe('Search recovery history...');
expect(RECOVERY_SEARCH_HISTORY_EMPTY_MESSAGE).toBe('Recent searches appear here.');
expect(RECOVERY_ALL_HISTORY_LABEL).toBe('All history');
expect(RECOVERY_ALL_ITEM_TYPES_LABEL).toBe('All item types');
expect(RECOVERY_ALL_PLATFORMS_LABEL).toBe('All platforms');
expect(RECOVERY_ANY_ITEM_LABEL).toBe('Any item');
@ -50,6 +53,7 @@ describe('recoveryTablePresentation', () => {
expect(getRecoveryProtectedSearchPlaceholder()).toBe('Search protected items...');
expect(getRecoveryHistorySearchPlaceholder()).toBe('Search recovery history...');
expect(getRecoverySearchHistoryEmptyMessage()).toBe('Recent searches appear here.');
expect(getRecoveryAllHistoryLabel()).toBe('All history');
expect(getRecoveryAllItemTypesLabel()).toBe('All item types');
expect(getRecoveryAllPlatformsLabel()).toBe('All platforms');
expect(getRecoveryAnyItemLabel()).toBe('Any item');

View file

@ -1,3 +1,5 @@
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
export const ALERT_CONFIG_UNSAVED_CHANGES = 'You have unsaved changes';
export const ALERT_CONFIG_SAVE_CHANGES = 'Save Changes';
export const ALERT_CONFIG_RESET_DEFAULTS = 'Reset to defaults';
@ -69,7 +71,7 @@ export const ALERT_CONFIG_ESCALATION_NOTIFY_LABEL = 'Notify';
export const ALERT_CONFIG_ESCALATION_MINUTES_SUFFIX = 'min';
export const ALERT_CONFIG_ESCALATION_NOTIFY_EMAIL = 'Email';
export const ALERT_CONFIG_ESCALATION_NOTIFY_WEBHOOKS = 'Webhooks';
export const ALERT_CONFIG_ESCALATION_NOTIFY_ALL = 'All Channels';
export const ALERT_CONFIG_ESCALATION_NOTIFY_ALL = getAllFilterOptionLabel('channels');
export const ALERT_CONFIG_ESCALATION_REMOVE_TITLE = 'Remove escalation level';
export const ALERT_CONFIG_ESCALATION_ADD_LABEL = 'Add Escalation Level';
export const ALERT_CONFIG_SUMMARY_TITLE = 'Configuration summary';

View file

@ -1,8 +1,10 @@
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
export const K8S_DEPLOYMENTS_DRAWER_TITLE = 'Deployments';
export const K8S_DEPLOYMENTS_DRAWER_DESCRIPTION = 'Desired state controllers (not Pods)';
export const K8S_DEPLOYMENTS_SEARCH_PLACEHOLDER = 'Search deployments...';
export const K8S_DEPLOYMENTS_NAMESPACE_FILTER_LABEL = 'Namespace';
export const K8S_DEPLOYMENTS_ALL_NAMESPACES_LABEL = 'All namespaces';
export const K8S_DEPLOYMENTS_ALL_NAMESPACES_LABEL = getAllFilterOptionLabel('namespaces');
export const K8S_DEPLOYMENTS_OPEN_PODS_LABEL = 'Open Pods';
export const K8S_DEPLOYMENTS_VIEW_PODS_LABEL = 'View Pods';
export const K8S_DEPLOYMENTS_COLUMN_DEPLOYMENT_LABEL = 'Deployment';

View file

@ -2,6 +2,7 @@ import {
getGroupedTableRowCellClass,
getGroupedTableRowClass,
} from '@/components/shared/groupedTableRowPresentation';
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
import type { ProtectionRollup, RecoveryOutcome, RecoveryPoint } from '@/types/recovery';
import {
getRecoveryItemTypeBadgeClass,
@ -27,8 +28,9 @@ export const RECOVERY_GROUP_NO_TIMESTAMP_LABEL = 'No Timestamp';
export const RECOVERY_PROTECTED_SEARCH_PLACEHOLDER = 'Search protected items...';
export const RECOVERY_HISTORY_SEARCH_PLACEHOLDER = 'Search recovery history...';
export const RECOVERY_SEARCH_HISTORY_EMPTY_MESSAGE = 'Recent searches appear here.';
export const RECOVERY_ALL_ITEM_TYPES_LABEL = 'All item types';
export const RECOVERY_ALL_PLATFORMS_LABEL = 'All platforms';
export const RECOVERY_ALL_HISTORY_LABEL = getAllFilterOptionLabel('history');
export const RECOVERY_ALL_ITEM_TYPES_LABEL = getAllFilterOptionLabel('item types');
export const RECOVERY_ALL_PLATFORMS_LABEL = getAllFilterOptionLabel('platforms');
export const RECOVERY_ANY_ITEM_LABEL = 'Any item';
export const RECOVERY_ARTIFACT_METADATA_TEXT_CLASS = 'text-[11px] font-medium text-base-content/80';
export const RECOVERY_ARTIFACT_COLUMN_LABELS: Record<string, string> = {
@ -99,6 +101,10 @@ export function getRecoveryAllItemTypesLabel(): string {
return RECOVERY_ALL_ITEM_TYPES_LABEL;
}
export function getRecoveryAllHistoryLabel(): string {
return RECOVERY_ALL_HISTORY_LABEL;
}
export function getRecoveryAllPlatformsLabel(): string {
return RECOVERY_ALL_PLATFORMS_LABEL;
}

View file

@ -1,4 +1,5 @@
import type { Storage } from '@/types/api';
import { getAllFilterOptionLabel } from '@/components/shared/filterOptionPresentation';
import { getSourcePlatformLabel, normalizeSourcePlatformKey } from '@/utils/sourcePlatforms';
import { titleCaseDelimitedLabel } from '@/utils/textPresentation';
@ -19,7 +20,7 @@ export interface StorageSourceOption {
const ALL_STORAGE_SOURCE_OPTION: StorageSourceOption = {
key: 'all',
label: 'All sources',
label: getAllFilterOptionLabel('sources'),
tone: 'slate',
};