diff --git a/frontend-modern/src/components/Dashboard/workloadSelectors.ts b/frontend-modern/src/components/Dashboard/workloadSelectors.ts index 87fded87c2..975606ea52 100644 --- a/frontend-modern/src/components/Dashboard/workloadSelectors.ts +++ b/frontend-modern/src/components/Dashboard/workloadSelectors.ts @@ -8,6 +8,7 @@ import { normalizeSourcePlatformQueryValue } from '@/utils/sourcePlatforms'; import { DEGRADED_HEALTH_STATUSES, OFFLINE_HEALTH_STATUSES } from '@/utils/status'; import { getNodeDisplayName } from '@/utils/nodes'; import { getCanonicalWorkloadId, resolveWorkloadType } from '@/utils/workloads'; +import { getWorkloadTypePresentation } from '@/utils/workloadTypePresentation'; import { buildNodeByInstance, getKubernetesContextKey, workloadNodeScopeId } from './workloadTopology'; export interface FilterWorkloadsParams { @@ -263,10 +264,14 @@ export const getWorkloadGroupLabel = ( if (normalizedNodeName) { return { type: '', name: normalizedNodeName }; } - if (prefix === 'app-container') return { type: 'App Containers', name: context }; - if (prefix === 'pod') return { type: 'Pods', name: context }; - if (prefix === 'vm') return { type: 'VM', name: context }; - if (prefix === 'system-container') return { type: 'Container', name: context }; + if ( + prefix === 'app-container' || + prefix === 'pod' || + prefix === 'vm' || + prefix === 'system-container' + ) { + return { type: getWorkloadTypePresentation(prefix).pluralLabel, name: context }; + } const first = guests[0]; if (first) { const cluster = (first.clusterName || '').trim(); diff --git a/frontend-modern/src/components/shared/__tests__/workloadTypeBadges.test.ts b/frontend-modern/src/components/shared/__tests__/workloadTypeBadges.test.ts index 2ea84d2d11..f5547c48fb 100644 --- a/frontend-modern/src/components/shared/__tests__/workloadTypeBadges.test.ts +++ b/frontend-modern/src/components/shared/__tests__/workloadTypeBadges.test.ts @@ -30,9 +30,9 @@ describe('workloadTypeBadges', () => { }); describe('System container types', () => { - it('returns Container badge for system-container', () => { + it('returns LXC badge for system-container', () => { const result = getWorkloadTypeBadge('system-container'); - expect(result.label).toBe('Container'); + expect(result.label).toBe('LXC'); expect(result.title).toBe('System Container'); }); @@ -50,14 +50,14 @@ describe('workloadTypeBadges', () => { }); describe('App container types', () => { - it('returns Containers badge for canonical app-container', () => { + it('returns Container badge for canonical app-container', () => { const result = getWorkloadTypeBadge('app-container'); - expect(result.label).toBe('Containers'); + expect(result.label).toBe('Container'); }); it('retains docker alias compatibility', () => { const result = getWorkloadTypeBadge('docker'); - expect(result.label).toBe('Containers'); + expect(result.label).toBe('Container'); }); it('does not normalize removed docker-container alias', () => { @@ -105,10 +105,10 @@ describe('workloadTypeBadges', () => { }); describe('OCI types', () => { - it('returns OCI badge for oci-container', () => { + it('collapses oci-container into the LXC badge — OCI is a flavor of system-container, not a distinct kind', () => { const result = getWorkloadTypeBadge('oci-container'); - expect(result.label).toBe('OCI'); - expect(result.title).toBe('OCI Container'); + expect(result.label).toBe('LXC'); + expect(result.title).toBe('System Container'); }); }); diff --git a/frontend-modern/src/utils/__tests__/recoveryItemTypePresentation.test.ts b/frontend-modern/src/utils/__tests__/recoveryItemTypePresentation.test.ts index 58be2b58ab..e328557e9a 100644 --- a/frontend-modern/src/utils/__tests__/recoveryItemTypePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/recoveryItemTypePresentation.test.ts @@ -27,7 +27,7 @@ describe('recoveryItemTypePresentation', () => { }); expect(getRecoveryItemTypePresentation('system-container')).toMatchObject({ key: 'system-container', - label: 'Container', + label: 'LXC', }); expect(getRecoveryItemTypePresentation('app-container')).toMatchObject({ key: 'app-container', diff --git a/frontend-modern/src/utils/__tests__/recoveryTablePresentation.test.ts b/frontend-modern/src/utils/__tests__/recoveryTablePresentation.test.ts index 13a7d3c0b0..183bd29b28 100644 --- a/frontend-modern/src/utils/__tests__/recoveryTablePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/recoveryTablePresentation.test.ts @@ -84,13 +84,13 @@ describe('recoveryTablePresentation', () => { expect(getRecoveryPointItemTypeBadgeClass(point)).toContain('px-1 py-0.5'); }); - it('normalizes proxmox lxc subjects to the canonical container badge', () => { + it('normalizes proxmox lxc subjects to the canonical LXC badge', () => { const point = { display: { subjectType: 'proxmox-lxc' }, subjectRef: { type: 'proxmox-lxc' }, } as RecoveryPoint; - expect(getRecoveryPointItemTypeLabel(point)).toBe('Container'); + expect(getRecoveryPointItemTypeLabel(point)).toBe('LXC'); expect(getRecoveryPointItemTypeBadgeClass(point)).toContain('bg-green-100'); expect(getRecoveryPointItemTypeBadgeClass(point)).toContain('text-green-700'); expect(getRecoveryPointItemTypeBadgeClass(point)).toContain('px-1 py-0.5'); diff --git a/frontend-modern/src/utils/__tests__/workloadTypePresentation.test.ts b/frontend-modern/src/utils/__tests__/workloadTypePresentation.test.ts index 7abf998981..23720d2e98 100644 --- a/frontend-modern/src/utils/__tests__/workloadTypePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/workloadTypePresentation.test.ts @@ -34,7 +34,7 @@ describe('workloadTypePresentation', () => { }); it('retains canonical alias compatibility through the utility layer', () => { - expect(getWorkloadTypePresentation('docker').label).toBe('Containers'); + expect(getWorkloadTypePresentation('docker').label).toBe('Container'); expect(getWorkloadTypePresentation('host').label).toBe('Agent'); expect(getWorkloadTypePresentation('k8s').label).toBe('Pod'); }); @@ -51,7 +51,7 @@ describe('workloadTypePresentation', () => { describe('getWorkloadTypeLabel', () => { it('returns the presentation label for known and unknown workload types', () => { expect(getWorkloadTypeLabel('vm')).toBe('VM'); - expect(getWorkloadTypeLabel('docker')).toBe('Containers'); + expect(getWorkloadTypeLabel('docker')).toBe('Container'); expect(getWorkloadTypeLabel('custom-type')).toBe('Custom Type'); }); }); diff --git a/frontend-modern/src/utils/recoveryItemTypePresentation.ts b/frontend-modern/src/utils/recoveryItemTypePresentation.ts index ab708521d2..eb36944433 100644 --- a/frontend-modern/src/utils/recoveryItemTypePresentation.ts +++ b/frontend-modern/src/utils/recoveryItemTypePresentation.ts @@ -62,7 +62,7 @@ export const normalizeRecoveryItemTypeQueryValue = ( case 'app-container': return 'app-container'; case 'oci-container': - return 'oci-container'; + return 'system-container'; case 'k8s-pod': case 'pod': return 'pod'; @@ -122,15 +122,6 @@ export const getRecoveryItemTypePresentation = ( tableBadgeClasses: `${TABLE_BADGE_BASE_CLASSES} ${presentation.className}`, }; } - case 'oci-container': { - const presentation = getWorkloadTypePresentation('oci-container'); - return { - key, - label: presentation.label, - badgeClasses: `${BADGE_BASE_CLASSES} ${presentation.className}`, - tableBadgeClasses: `${TABLE_BADGE_BASE_CLASSES} ${presentation.className}`, - }; - } case 'pod': { const presentation = getWorkloadTypePresentation('pod'); return { diff --git a/frontend-modern/src/utils/workloadTypePresentation.ts b/frontend-modern/src/utils/workloadTypePresentation.ts index 7e21dfe4b5..958159dff6 100644 --- a/frontend-modern/src/utils/workloadTypePresentation.ts +++ b/frontend-modern/src/utils/workloadTypePresentation.ts @@ -4,6 +4,7 @@ import { titleCaseDelimitedLabel } from '@/utils/textPresentation'; export interface WorkloadTypePresentation { label: string; + pluralLabel: string; title: string; className: string; } @@ -13,44 +14,44 @@ type WorkloadTypePresentationKey = | 'system-container' | 'app-container' | 'agent' - | 'pod' - | 'oci-container'; + | 'pod'; const PRESENTATION_MAP: Record = { vm: { label: 'VM', + pluralLabel: 'VMs', title: 'Virtual Machine', className: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300', }, 'system-container': { - label: 'Container', + label: 'LXC', + pluralLabel: 'LXC', title: 'System Container', className: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300', }, 'app-container': { - label: 'Containers', + label: 'Container', + pluralLabel: 'Containers', title: 'Application Container', className: 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-300', }, pod: { label: 'Pod', + pluralLabel: 'Pods', title: 'Kubernetes Pod', className: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300', }, agent: { label: 'Agent', + pluralLabel: 'Agents', title: 'Agent', className: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300', }, - 'oci-container': { - label: 'OCI', - title: 'OCI Container', - className: 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300', - }, }; const DEFAULT_PRESENTATION: WorkloadTypePresentation = { label: 'Unknown', + pluralLabel: 'Unknown', title: 'Unknown workload type', className: 'bg-surface-alt text-base-content', }; @@ -60,13 +61,15 @@ export const normalizeWorkloadTypePresentationKey = ( ): WorkloadTypePresentationKey | null => { const canonical = canonicalizeFrontendResourceType(value); if (!canonical) return null; + // OCI containers are a flavor of system-container, not a distinct workload kind. + // Callers differentiate via IsOCI / osTemplate and override the title at the call site. + if (canonical === 'oci-container') return 'system-container'; if ( canonical === 'vm' || canonical === 'system-container' || canonical === 'app-container' || canonical === 'pod' || - canonical === 'agent' || - canonical === 'oci-container' + canonical === 'agent' ) { return canonical; } @@ -75,7 +78,7 @@ export const normalizeWorkloadTypePresentationKey = ( export const getWorkloadTypePresentation = ( rawType: string | WorkloadType | null | undefined, - overrides?: Partial>, + overrides?: Partial>, ): WorkloadTypePresentation => { const normalized = normalizeWorkloadTypePresentationKey(rawType); const fallbackLabel = @@ -87,6 +90,7 @@ export const getWorkloadTypePresentation = ( return { ...DEFAULT_PRESENTATION, label: fallbackLabel, + pluralLabel: overrides?.pluralLabel || fallbackLabel, title: overrides?.title || fallbackLabel, }; } @@ -95,6 +99,7 @@ export const getWorkloadTypePresentation = ( return { ...base, label: overrides?.label || base.label, + pluralLabel: overrides?.pluralLabel || base.pluralLabel, title: overrides?.title || base.title, }; };