Extract resource drawer discovery config owner

This commit is contained in:
rcourtman
2026-03-23 10:57:22 +00:00
parent 7e121ab2c1
commit ac0204283a
13 changed files with 321 additions and 264 deletions
@@ -253,6 +253,14 @@ That drawer state now also consumes the same shared
`frontend-modern/src/routing/resourceLinks.ts` workload-to-infrastructure
helper, so row and drawer navigation stay aligned without a second
dashboard-local link-mapping file.
The shared infrastructure mapper hot path now stays intentionally narrow:
`frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`
continues to own sensor-label presentation and hot-path host/agent projection,
while canonical drawer discovery-target derivation now lives in
`frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts`
under `unified-resources`. Future discovery-config or target-resolution
changes must extend through that unified-resource owner instead of
re-accumulating discovery heuristics back into the performance hot-path mapper.
The dashboard disk list now follows the same pattern: the shell stays in
`frontend-modern/src/components/Dashboard/DiskList.tsx`, while disk-row
presentation derivations and fallback tooltip/runtime wiring live in
@@ -3206,6 +3206,7 @@
"frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts",
"frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx",
"frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts",
"frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts",
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx",
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx",
"frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts",
@@ -3301,6 +3302,7 @@
"frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts",
"frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx",
"frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts",
"frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts",
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx",
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx",
"frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts",
@@ -3337,6 +3339,7 @@
"test_prefixes": [],
"exact_files": [
"frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.discovery.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/resourceDetailDrawerOperationalModel.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/resourceDetailMappers.test.ts",
@@ -55,20 +55,22 @@ cross-source deduplication.
33. `frontend-modern/src/components/Infrastructure/unifiedResourceTableModel.ts`
34. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDerivedState.ts`
35. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerServiceModel.ts`
36. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerHistoryState.ts`
37. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDockerActionsState.ts`
38. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts`
39. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
40. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts`
41. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx`
42. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts`
43. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
44. `frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
45. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
46. `frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx`
47. `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts`
48. `frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts`
49. `frontend-modern/src/utils/resourceIdentity.ts`
36. `frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts`
37. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts`
38. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerHistoryState.ts`
39. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDockerActionsState.ts`
40. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts`
41. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
42. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts`
43. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx`
44. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts`
45. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
46. `frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
47. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
48. `frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx`
49. `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts`
50. `frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts`
51. `frontend-modern/src/utils/resourceIdentity.ts`
## Shared Boundaries
@@ -164,6 +166,11 @@ split used elsewhere in v6: `ResourceDetailDrawer.tsx` owns composition,
`useResourceDetailDrawerHistoryState.ts` owns facet/intelligence/timeline
runtime orchestration, `useResourceDetailDrawerDerivedState.ts` owns the
canonical drawer derivation layer, and
`resourceDetailDiscoveryModel.ts` owns the pure canonical discovery-config
derivation that feeds the drawer discovery surface,
`resourceDetailDrawerOperationalModel.ts` owns the pure source-health,
platform-signal, related-link, and host-detail overview derivations that feed
the current-state and host-details surfaces,
`resourceDetailDrawerServiceModel.ts` owns the pure Docker/PBS/PMG service
summary and breakdown derivations that feed the overview service-details
surface,
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest';
import type { Resource } from '@/types/resource';
import { toDiscoveryConfig } from '@/components/Infrastructure/resourceDetailMappers';
import { toDiscoveryConfig } from '@/components/Infrastructure/resourceDetailDiscoveryModel';
import { buildWorkloadsHref } from '@/components/Infrastructure/workloadsLink';
const baseResource = (): Resource => ({
@@ -7,6 +7,7 @@ import resourceDetailDrawerShellSource from '@/components/Infrastructure/Resourc
import resourceDetailDrawerOverviewSource from '@/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx?raw';
import resourceDetailDrawerHistoryStateSource from '@/components/Infrastructure/useResourceDetailDrawerHistoryState.ts?raw';
import resourceDetailDrawerDerivedStateSource from '@/components/Infrastructure/useResourceDetailDrawerDerivedState.ts?raw';
import resourceDetailDrawerDiscoveryModelSource from '@/components/Infrastructure/resourceDetailDiscoveryModel.ts?raw';
import resourceDetailDrawerServiceModelSource from '@/components/Infrastructure/resourceDetailDrawerServiceModel.ts?raw';
import resourceDetailDrawerDockerActionsStateSource from '@/components/Infrastructure/useResourceDetailDrawerDockerActionsState.ts?raw';
import resourceDetailDrawerStateSource from '@/components/Infrastructure/useResourceDetailDrawerState.ts?raw';
@@ -134,11 +135,14 @@ describe('ResourceDetailDrawer change history section', () => {
expect(resourceDetailDrawerHistoryStateSource).toContain('createResource(');
expect(resourceDetailDrawerHistoryStateSource).toContain('ResourceAPI.getFacetBundle');
expect(resourceDetailDrawerHistoryStateSource).toContain('AIAPI.getResourceIntelligence');
expect(resourceDetailDrawerDerivedStateSource).toContain('buildWorkloadsHref');
expect(resourceDetailDrawerDerivedStateSource).toContain('toDiscoveryConfig');
expect(resourceDetailDrawerDerivedStateSource).toContain(
"from '@/components/Infrastructure/resourceDetailDiscoveryModel'",
);
expect(resourceDetailDrawerDerivedStateSource).toContain(
"from './resourceDetailDrawerServiceModel'",
);
expect(resourceDetailDrawerDiscoveryModelSource).toContain('export const toDiscoveryConfig');
expect(resourceDetailDrawerServiceModelSource).toContain('export const getServiceDetailsSummary');
expect(resourceDetailDrawerServiceModelSource).toContain(
'export const buildPbsVisibleJobBreakdown',
@@ -12,6 +12,8 @@ import unifiedResourceTableModelSource from '@/components/Infrastructure/unified
import infrastructureSummarySource from '@/components/Infrastructure/InfrastructureSummary.tsx?raw';
import infrastructureSummaryStateSource from '@/components/Infrastructure/useInfrastructureSummaryState.ts?raw';
import infrastructureSummaryModelSource from '@/components/Infrastructure/infrastructureSummaryModel.ts?raw';
import resourceDetailMappersSource from '@/components/Infrastructure/resourceDetailMappers.ts?raw';
import resourceDetailDiscoveryModelSource from '@/components/Infrastructure/resourceDetailDiscoveryModel.ts?raw';
import {
buildStatusOptions,
filterResources,
@@ -180,6 +182,9 @@ describe('UnifiedResourceTable performance contract', () => {
it('formats sensor labels through the shared resource detail mapper helper', () => {
expect(formatSensorName('fan1_cpu_temp')).toBe('Cpu Temp');
expect(formatSensorName('psu_temp')).toBe('Temp');
expect(resourceDetailMappersSource).toContain('titleCaseDelimitedLabel');
expect(resourceDetailMappersSource).not.toContain('export const toDiscoveryConfig');
expect(resourceDetailDiscoveryModelSource).toContain('export const toDiscoveryConfig');
});
it('keeps hot-path table state and windowing in the shared table state owner', () => {
@@ -3,7 +3,6 @@ import {
formatInteger,
formatSensorName,
formatSourceType,
toDiscoveryConfig,
toAgentFromResource,
toNodeFromProxmox,
} from '@/components/Infrastructure/resourceDetailMappers';
@@ -112,48 +111,4 @@ describe('resourceDetailMappers', () => {
expect(agent?.id).not.toBe('resource:host:hash-1');
});
});
describe('toDiscoveryConfig', () => {
it('prefers the typed canonical hostname for explicit discovery targets', () => {
const config = toDiscoveryConfig({
...createHybridHostResource(),
displayName: '',
canonicalIdentity: {
hostname: 'tower.canonical',
displayName: 'Tower',
primaryId: 'node:instance-pve1',
},
discoveryTarget: {
resourceType: 'agent',
agentId: 'agent-canonical',
resourceId: 'agent-canonical',
},
});
expect(config?.hostname).toBe('tower.canonical');
});
it('uses the shared Kubernetes cluster helper for pod fallback agent ids', () => {
const config = toDiscoveryConfig({
...createHybridHostResource(),
id: 'resource:pod:hash-2',
type: 'pod',
platformType: 'kubernetes',
kubernetes: {
context: 'cluster-context',
podUid: 'pod-uid-2',
namespace: 'default',
},
platformData: {
kubernetes: {
context: 'cluster-context',
namespace: 'default',
},
},
} as unknown as Resource);
expect(config?.agentId).toBe('cluster-context');
expect(config?.resourceId).toBe('pod-uid-2');
});
});
});
@@ -0,0 +1,235 @@
import type { Resource } from '@/types/resource';
import type { ResourceType as DiscoveryResourceType } from '@/types/discovery';
import {
canonicalDiscoveryResourceType,
isAgentDiscoveryResourceType,
} from '@/utils/discoveryTarget';
import {
getActionableAgentIdFromResource,
getActionableDockerRuntimeIdFromResource,
getActionableKubernetesClusterIdFromResource,
} from '@/utils/agentResources';
import {
getPreferredResourceClusterName,
getPreferredResourceDisplayName,
getPreferredResourceHostname,
} from '@/utils/resourceIdentity';
export type DiscoveryConfig = {
resourceType: DiscoveryResourceType;
agentId: string;
resourceId: string;
hostname: string;
metadataKind: 'guest' | 'agent';
metadataId: string;
targetLabel: string;
};
type ProxmoxPlatformData = {
nodeName?: string;
vmid?: number;
};
type DockerPlatformData = {
containerId?: string;
hostname?: string;
};
type KubernetesPlatformData = {
agentId?: string;
namespace?: string;
podName?: string;
podUid?: string;
};
type PlatformData = {
agent?: {
hostname?: string;
};
docker?: DockerPlatformData;
kubernetes?: KubernetesPlatformData;
proxmox?: ProxmoxPlatformData;
};
const asString = (value: unknown): string | undefined =>
typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
const asNumber = (value: unknown): number | undefined =>
typeof value === 'number' && Number.isFinite(value) ? value : undefined;
const getPreferredHostLabel = (resource: Resource): string =>
getPreferredResourceHostname(resource) ||
getPreferredResourceDisplayName(resource) ||
resource.id;
export const toDiscoveryConfig = (resource: Resource): DiscoveryConfig | null => {
const explicitDiscoveryTarget = resource.discoveryTarget;
const explicitDiscoveryAgentId = asString(
(explicitDiscoveryTarget as { agentId?: unknown } | undefined)?.agentId,
);
if (
explicitDiscoveryTarget &&
explicitDiscoveryTarget.resourceType &&
explicitDiscoveryAgentId &&
explicitDiscoveryTarget.resourceId
) {
const explicitResourceType = canonicalDiscoveryResourceType(
explicitDiscoveryTarget.resourceType,
);
const resourceType = (() => {
switch (explicitResourceType) {
case 'agent':
return 'agent';
case 'vm':
case 'system-container':
case 'app-container':
case 'pod':
return explicitResourceType;
default:
return null;
}
})();
if (resourceType) {
const hostname = explicitDiscoveryTarget.hostname || getPreferredHostLabel(resource);
const isHostDiscovery = isAgentDiscoveryResourceType(resourceType);
const targetLabel = isHostDiscovery
? 'agent'
: resourceType === 'app-container'
? 'container'
: resourceType === 'pod'
? 'workload'
: 'guest';
return {
resourceType,
agentId: explicitDiscoveryAgentId,
resourceId: explicitDiscoveryTarget.resourceId,
hostname,
metadataKind: isHostDiscovery ? 'agent' : 'guest',
metadataId: explicitDiscoveryTarget.resourceId,
targetLabel,
};
}
}
const platformData = resource.platformData as PlatformData | undefined;
const dockerPlatformData = platformData?.docker;
const kubernetesPlatformData = platformData?.kubernetes;
const proxmoxVmid =
asNumber(resource.proxmox?.vmid) ??
asNumber(platformData?.proxmox?.vmid) ??
asNumber((platformData as { vmid?: unknown } | undefined)?.vmid);
const vmidResourceId =
proxmoxVmid !== undefined && proxmoxVmid > 0 ? String(proxmoxVmid) : undefined;
const proxmoxNodeName =
asString(resource.proxmox?.nodeName) ||
platformData?.proxmox?.nodeName ||
asString((platformData as { nodeName?: unknown } | undefined)?.nodeName);
const actionableAgentId = getActionableAgentIdFromResource(resource);
const actionableDockerHostId = getActionableDockerRuntimeIdFromResource(resource);
const actionableKubernetesId = getActionableKubernetesClusterIdFromResource(resource);
const kubernetesAgentId =
asString(resource.kubernetes?.agentId) ||
asString(kubernetesPlatformData?.agentId) ||
actionableKubernetesId ||
getPreferredResourceClusterName(resource);
const kubernetesResourceId =
asString(resource.kubernetes?.podUid) ||
asString(kubernetesPlatformData?.podUid) ||
(() => {
const namespace =
asString(resource.kubernetes?.namespace) || asString(kubernetesPlatformData?.namespace);
const podName =
asString(resource.kubernetes?.podName) ||
asString(kubernetesPlatformData?.podName) ||
asString(resource.name);
return namespace && podName ? `${namespace}/${podName}` : undefined;
})();
const agentLookupId =
actionableDockerHostId ||
actionableKubernetesId ||
actionableAgentId ||
proxmoxNodeName ||
platformData?.agent?.hostname ||
asString(dockerPlatformData?.hostname) ||
getPreferredResourceHostname(resource) ||
getPreferredResourceDisplayName(resource) ||
resource.platformId ||
resource.id;
const workloadAgentId =
proxmoxNodeName ||
actionableDockerHostId ||
kubernetesAgentId ||
actionableAgentId ||
asString(resource.parentName) ||
resource.parentId ||
getPreferredResourceHostname(resource) ||
resource.platformId ||
resource.id;
const hostname = getPreferredHostLabel(resource);
switch (resource.type) {
case 'agent':
case 'docker-host':
case 'pbs':
case 'pmg':
case 'k8s-cluster':
case 'k8s-node':
case 'truenas':
return {
resourceType: 'agent',
agentId: agentLookupId,
resourceId: agentLookupId,
hostname,
metadataKind: 'agent',
metadataId: agentLookupId,
targetLabel: 'agent',
};
case 'vm':
return {
resourceType: 'vm',
agentId: workloadAgentId,
resourceId: vmidResourceId || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'guest',
};
case 'system-container':
case 'oci-container':
return {
resourceType: 'system-container',
agentId: workloadAgentId,
resourceId: vmidResourceId || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'guest',
};
case 'app-container':
return {
resourceType: 'app-container',
agentId: workloadAgentId,
resourceId: asString(dockerPlatformData?.containerId) || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'container',
};
case 'pod':
case 'k8s-deployment':
case 'k8s-service':
return {
resourceType: 'pod',
agentId: workloadAgentId,
resourceId: kubernetesResourceId || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'workload',
};
default:
return null;
}
};
@@ -9,21 +9,8 @@ import type {
} from '@/types/api';
import type { Resource, ResourceMetric } from '@/types/resource';
import { formatTemperature } from '@/utils/temperature';
import type { ResourceType as DiscoveryResourceType } from '@/types/discovery';
import {
canonicalDiscoveryResourceType,
isAgentDiscoveryResourceType,
} from '@/utils/discoveryTarget';
import {
getActionableAgentIdFromResource,
getActionableDockerRuntimeIdFromResource,
getActionableKubernetesClusterIdFromResource,
} from '@/utils/agentResources';
import {
getPreferredResourceDisplayName,
getPreferredResourceClusterName,
getPreferredResourceHostname,
} from '@/utils/resourceIdentity';
import { getActionableAgentIdFromResource } from '@/utils/agentResources';
import { getPreferredResourceDisplayName, getPreferredResourceHostname } from '@/utils/resourceIdentity';
import { titleCaseDelimitedLabel } from '@/utils/textPresentation';
export { getSourceTypeLabel as formatSourceType } from '@/utils/sourceTypePresentation';
@@ -174,200 +161,14 @@ export type DockerPlatformData = {
command?: DockerRuntimeCommand;
};
export type DiscoveryConfig = {
resourceType: DiscoveryResourceType;
agentId: string;
resourceId: string;
hostname: string;
metadataKind: 'guest' | 'agent';
metadataId: string;
targetLabel: string;
};
const asString = (value: unknown): string | undefined =>
typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
const asNumber = (value: unknown): number | undefined =>
typeof value === 'number' && Number.isFinite(value) ? value : undefined;
const getPreferredHostLabel = (resource: Resource): string =>
getPreferredResourceHostname(resource) ||
getPreferredResourceDisplayName(resource) ||
resource.id;
export const toDiscoveryConfig = (resource: Resource): DiscoveryConfig | null => {
const explicitDiscoveryTarget = resource.discoveryTarget;
const explicitDiscoveryAgentId = asString(
(explicitDiscoveryTarget as { agentId?: unknown } | undefined)?.agentId,
);
if (
explicitDiscoveryTarget &&
explicitDiscoveryTarget.resourceType &&
explicitDiscoveryAgentId &&
explicitDiscoveryTarget.resourceId
) {
const explicitResourceType = canonicalDiscoveryResourceType(
explicitDiscoveryTarget.resourceType,
);
const resourceType = (() => {
switch (explicitResourceType) {
case 'agent':
return 'agent';
case 'vm':
case 'system-container':
case 'app-container':
case 'pod':
return explicitResourceType;
default:
return null;
}
})();
if (resourceType) {
const hostname = explicitDiscoveryTarget.hostname || getPreferredHostLabel(resource);
const isHostDiscovery = isAgentDiscoveryResourceType(resourceType);
const targetLabel = isHostDiscovery
? 'agent'
: resourceType === 'app-container'
? 'container'
: resourceType === 'pod'
? 'workload'
: 'guest';
return {
resourceType,
agentId: explicitDiscoveryAgentId,
resourceId: explicitDiscoveryTarget.resourceId,
hostname,
metadataKind: isHostDiscovery ? 'agent' : 'guest',
metadataId: explicitDiscoveryTarget.resourceId,
targetLabel,
};
}
}
const platformData = resource.platformData as PlatformData | undefined;
const dockerPlatformData = platformData?.docker as DockerPlatformData | undefined;
const kubernetesPlatformData = platformData?.kubernetes;
const proxmoxVmid =
asNumber(resource.proxmox?.vmid) ??
asNumber(platformData?.proxmox?.vmid) ??
asNumber((platformData as { vmid?: unknown } | undefined)?.vmid);
const vmidResourceId =
proxmoxVmid !== undefined && proxmoxVmid > 0 ? String(proxmoxVmid) : undefined;
const proxmoxNodeName =
asString(resource.proxmox?.nodeName) ||
platformData?.proxmox?.nodeName ||
asString((platformData as { nodeName?: unknown } | undefined)?.nodeName);
const actionableAgentId = getActionableAgentIdFromResource(resource);
const actionableDockerHostId = getActionableDockerRuntimeIdFromResource(resource);
const actionableKubernetesId = getActionableKubernetesClusterIdFromResource(resource);
const kubernetesAgentId =
asString(resource.kubernetes?.agentId) ||
asString(kubernetesPlatformData?.agentId) ||
actionableKubernetesId ||
getPreferredResourceClusterName(resource);
const kubernetesResourceId =
asString(resource.kubernetes?.podUid) ||
asString(kubernetesPlatformData?.podUid) ||
(() => {
const namespace =
asString(resource.kubernetes?.namespace) || asString(kubernetesPlatformData?.namespace);
const podName =
asString(resource.kubernetes?.podName) ||
asString(kubernetesPlatformData?.podName) ||
asString(resource.name);
return namespace && podName ? `${namespace}/${podName}` : undefined;
})();
const agentLookupId =
actionableDockerHostId ||
actionableKubernetesId ||
actionableAgentId ||
proxmoxNodeName ||
platformData?.agent?.hostname ||
asString(dockerPlatformData?.hostname) ||
getPreferredResourceHostname(resource) ||
getPreferredResourceDisplayName(resource) ||
resource.platformId ||
resource.id;
const agentLikeId = agentLookupId;
const workloadAgentId =
proxmoxNodeName ||
actionableDockerHostId ||
kubernetesAgentId ||
actionableAgentId ||
asString(resource.parentName) ||
resource.parentId ||
getPreferredResourceHostname(resource) ||
resource.platformId ||
resource.id;
const hostname = getPreferredHostLabel(resource);
switch (resource.type) {
case 'agent':
case 'docker-host':
case 'pbs':
case 'pmg':
case 'k8s-cluster':
case 'k8s-node':
case 'truenas':
return {
resourceType: 'agent',
agentId: agentLikeId,
resourceId: agentLikeId,
hostname,
metadataKind: 'agent',
metadataId: agentLikeId,
targetLabel: 'agent',
};
case 'vm':
return {
resourceType: 'vm',
agentId: workloadAgentId,
resourceId: vmidResourceId || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'guest',
};
case 'system-container':
case 'oci-container':
return {
resourceType: 'system-container',
agentId: workloadAgentId,
resourceId: vmidResourceId || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'guest',
};
case 'app-container':
return {
resourceType: 'app-container',
agentId: workloadAgentId,
resourceId: asString(dockerPlatformData?.containerId) || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'container',
};
case 'pod':
case 'k8s-deployment':
case 'k8s-service':
return {
resourceType: 'pod',
agentId: workloadAgentId,
resourceId: kubernetesResourceId || resource.id,
hostname,
metadataKind: 'guest',
metadataId: resource.id,
targetLabel: 'workload',
};
default:
return null;
}
};
export const buildMemory = (metric?: ResourceMetric, fallback?: Partial<Memory>): Memory => {
const total = metric?.total ?? fallback?.total ?? 0;
const used = metric?.used ?? fallback?.used ?? 0;
@@ -31,13 +31,13 @@ import {
ALIAS_COLLAPSE_THRESHOLD,
buildTemperatureRows,
toAgentFromResource,
toDiscoveryConfig,
toNodeFromProxmox,
type AgentPlatformData,
type DockerPlatformData,
type KubernetesPlatformData,
type PlatformData,
} from '@/components/Infrastructure/resourceDetailMappers';
import { toDiscoveryConfig } from '@/components/Infrastructure/resourceDetailDiscoveryModel';
import { formatIdentifierLabel } from '@/utils/textPresentation';
import {
buildPbsVisibleJobBreakdown,
@@ -337,6 +337,7 @@ import resourceDetailDrawerDebugSource from '@/components/Infrastructure/Resourc
import infrastructureSummarySource from '@/components/Infrastructure/InfrastructureSummary.tsx?raw';
import infrastructureSummaryStateSource from '@/components/Infrastructure/useInfrastructureSummaryState.ts?raw';
import infrastructureSummaryModelSource from '@/components/Infrastructure/infrastructureSummaryModel.ts?raw';
import resourceDetailDrawerDiscoveryModelSource from '@/components/Infrastructure/resourceDetailDiscoveryModel.ts?raw';
import resourceDetailMappersSource from '@/components/Infrastructure/resourceDetailMappers.ts?raw';
import resourceDetailDrawerHistoryStateSource from '@/components/Infrastructure/useResourceDetailDrawerHistoryState.ts?raw';
import resourceDetailDrawerDerivedStateSource from '@/components/Infrastructure/useResourceDetailDrawerDerivedState.ts?raw';
@@ -988,10 +989,15 @@ describe('frontend resource type boundaries', () => {
expect(resourceDetailDrawerStateSource).not.toContain('createResource(');
expect(resourceDetailDrawerStateSource).not.toContain('MonitoringAPI.');
expect(resourceDetailDrawerHistoryStateSource).toContain('createResource(');
expect(resourceDetailDrawerDerivedStateSource).toContain('buildRelatedLinks');
expect(resourceDetailDrawerDerivedStateSource).toContain(
"from '@/components/Infrastructure/resourceDetailDiscoveryModel'",
);
expect(resourceDetailDrawerDerivedStateSource).toContain('buildWorkloadsHref');
expect(resourceDetailDrawerDerivedStateSource).toContain('buildServiceDetailLinks');
expect(resourceDetailDrawerDerivedStateSource).toContain(
"from './resourceDetailDrawerServiceModel'",
);
expect(resourceDetailDrawerDiscoveryModelSource).toContain('export const toDiscoveryConfig');
expect(resourceDetailDrawerServiceModelSource).toContain('getServiceDetailsSummary');
expect(resourceDetailDrawerOverviewSource).not.toContain('MonitoringAPI.');
expect(resourceDetailDrawerOverviewSource).toContain('drawer.queueDockerUpdateCheck');
@@ -3142,6 +3148,7 @@ describe('frontend resource type boundaries', () => {
expect(useUnifiedResourcesSource).not.toContain('const resolvePolicyRedactionHints =');
expect(useUnifiedResourcesSource).not.toContain('const resolvePolicy =');
expect(resourceDetailMappersSource).toContain('titleCaseDelimitedLabel');
expect(resourceDetailMappersSource).not.toContain('export const toDiscoveryConfig');
expect(resourceDetailMappersSource).not.toContain('export const normalizeHealthLabel');
expect(resourceDetailMappersSource).not.toContain('export const healthToneClass');
expect(unifiedResourceTableSource).toContain('useUnifiedResourceTableState');
@@ -499,6 +499,7 @@ class CanonicalCompletionGuardTest(unittest.TestCase):
"allow_same_subsystem_tests": False,
"test_prefixes": [],
"exact_files": [
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.discovery.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.workloads-link.test.tsx",
@@ -156,6 +156,36 @@ class SubsystemLookupTest(unittest.TestCase):
match["verification_requirement"]["exact_files"],
)
def test_lookup_paths_assigns_resource_detail_drawer_discovery_model_to_unified_resources(self) -> None:
result = lookup_paths(
["frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts"]
)
self.assertEqual(result["unowned_runtime_files"], [])
self.assertEqual(
{item["subsystem"] for item in result["impacted_subsystems"]},
{"unified-resources"},
)
file_entry = result["files"][0]
self.assertEqual(file_entry["classification"], "runtime")
self.assertEqual(
{match["subsystem"] for match in file_entry["matches"]},
{"unified-resources"},
)
match = file_entry["matches"][0]
self.assertEqual(
match["contract"],
"docs/release-control/v6/internal/subsystems/unified-resources.md",
)
self.assertEqual(match["lane_context"]["lane_id"], "L13")
self.assertEqual(
match["verification_requirement"]["id"],
"resource-consumers",
)
self.assertIn(
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.discovery.test.ts",
match["verification_requirement"]["exact_files"],
)
def test_lookup_paths_assigns_recent_alerts_panel_to_alerts(self) -> None:
result = lookup_paths(["frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx"])
self.assertEqual(result["unowned_runtime_files"], [])
@@ -3994,6 +4024,7 @@ class SubsystemLookupTest(unittest.TestCase):
self.assertEqual(
match["verification_requirement"]["exact_files"],
[
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.discovery.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.workloads-link.test.tsx",