mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Extract resource drawer discovery config owner
This commit is contained in:
@@ -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
-1
@@ -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 => ({
|
||||
|
||||
+5
-1
@@ -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',
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user