mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-20 02:42:11 +00:00
Collapse OCI as a workload kind and unify group-header labels
OCI is a flavor of system-container, not a distinct workload kind, but
WorkloadTypePresentationKey had 'oci-container' with its own purple
badge — creating two presentation paths for the same thing. Group
headers in workloadSelectors had inline label strings
('App Containers', 'Container', 'Pods', 'VM') that drifted from the
row badge's PRESENTATION_MAP whenever either side changed.
Drop 'oci-container' from the workload-kind union and presentation
map; normalize 'oci-container' input to 'system-container'. Add
pluralLabel to WorkloadTypePresentation and route getWorkloadGroupLabel
through it so the row badge and group header share one source. Collapse
the matching branch in recoveryItemTypePresentation. Align the relevant
assertion tests with the canonical VM/LXC/Container/Pod labels.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<WorkloadTypePresentationKey, WorkloadTypePresentation> = {
|
||||
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<Pick<WorkloadTypePresentation, 'label' | 'title'>>,
|
||||
overrides?: Partial<Pick<WorkloadTypePresentation, 'label' | 'pluralLabel' | 'title'>>,
|
||||
): 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,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user