From 6f3741dbfbc78fbd9b68c72ff5368994fce9cc5f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 22 Mar 2026 23:31:02 +0000 Subject: [PATCH] Split infrastructure drawer render owners --- .../v6/internal/subsystems/registry.json | 6 + .../internal/subsystems/unified-resources.md | 24 +- .../Infrastructure/ResourceDetailDrawer.tsx | 1513 +---------------- .../ResourceDetailDrawerDebugTab.tsx | 93 + .../ResourceDetailDrawerOverviewTab.tsx | 1158 +++++++++++++ .../ResourceDetailDrawerSupportDisclosure.tsx | 55 + .../ResourceDetailDrawer.history.test.tsx | 12 +- .../useResourceDetailDrawerState.ts | 2 + .../SharedPrimitives.guardrails.test.ts | 6 +- .../frontendResourceTypeBoundaries.test.ts | 12 +- 10 files changed, 1421 insertions(+), 1460 deletions(-) create mode 100644 frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx create mode 100644 frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx create mode 100644 frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 6ccb7b8c4..cf5d4c035 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -3105,6 +3105,9 @@ "frontend-modern/src/components/Discovery/useDiscoveryTabState.ts", "frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts", "frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx", "frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts", "frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx", "frontend-modern/src/components/Infrastructure/UnifiedResourceTable.tsx", @@ -3179,6 +3182,9 @@ "frontend-modern/src/components/Discovery/useDiscoveryTabState.ts", "frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts", "frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx", + "frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx", "frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts", "frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx", "frontend-modern/src/components/Infrastructure/UnifiedResourceTable.tsx", diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 484e4a3b7..17e59d0b5 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -44,13 +44,16 @@ cross-source deduplication. 22. `internal/unifiedresources/privacy.go` 23. `internal/unifiedresources/actions.go` 24. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx` -25. `frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx` -26. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts` -27. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts` -28. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx` -29. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts` -30. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx` -31. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts` +25. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx` +26. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx` +27. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx` +28. `frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx` +29. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts` +30. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts` +31. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx` +32. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts` +33. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx` +34. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts` ## Shared Boundaries @@ -77,7 +80,7 @@ assembly branch. 4. Add metrics-target normalization or synthetic metrics support through `internal/unifiedresources/metrics_targets.go` and `internal/unifiedresources/metrics.go` 5. Add platform registry, resolution, or host-dedup behavior through `internal/unifiedresources/registry.go`, `internal/unifiedresources/resolve.go`, `internal/unifiedresources/resolved_host_set.go`, `internal/unifiedresources/snapshot_source_filter.go`, `internal/unifiedresources/store.go`, `internal/unifiedresources/kubernetes_capabilities.go`, and `internal/unifiedresources/pbs_rollups.go` 6. Add canonical governed name-resolution or policy-aware resource lookup behavior through `internal/unifiedresources/resolve.go` and `internal/unifiedresources/resolve_context.go` -7. Add or change resource drawer timeline/facet presentation through `frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx`, `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts`, `frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx`, `frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`, and the governed `internal/api/resources.go` facet/timeline contract together +7. Add or change resource drawer timeline/facet presentation through `frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx`, `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx`, `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx`, `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts`, `frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx`, `frontend-modern/src/components/Infrastructure/resourceDetailMappers.ts`, and the governed `internal/api/resources.go` facet/timeline contract together 8. Add or change discovery-support runtime under the resource drawer through `frontend-modern/src/components/Discovery/DiscoveryTab.tsx` for shell/presentation ownership and `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts` for fetch, websocket-progress, and notes-mutation ownership ## Forbidden Paths @@ -131,6 +134,11 @@ hints. Those same relationship changes now summarize the actual edge(s) in `from` and `to`, so the canonical timeline keeps the relationship transition readable without needing the drawer to reconstruct an edge summary from raw endpoints. +The infrastructure resource drawer now follows the explicit shell/state/render +split used elsewhere in v6: `ResourceDetailDrawer.tsx` owns composition, +`useResourceDetailDrawerState.ts` owns runtime state and fetch orchestration, +and the overview/debug render-heavy surfaces live in dedicated drawer-local +owners instead of staying inline in the shell. The backend AI and Patrol context renderers now derive their canonical change kind, source type, source adapter, actor, reason, and related-resource fragments from `internal/unifiedresources/change_presentation.go`, so the diff --git a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx index 9189a2b0c..0f1e9a4d6 100644 --- a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx +++ b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx @@ -1,51 +1,14 @@ -import { Show, Suspense, For } from 'solid-js'; -import type { Component, JSX } from 'solid-js'; -import type { - Resource, - ResourceChangeKind, - ResourceChangeSourceAdapter, - ResourceChangeSourceType, -} from '@/types/resource'; -import { formatUptime, formatRelativeTime } from '@/utils/format'; +import { Show, For } from 'solid-js'; +import type { Component } from 'solid-js'; +import type { Resource } from '@/types/resource'; import { StatusDot } from '@/components/shared/StatusDot'; -import { Card } from '@/components/shared/Card'; -import { TagBadges } from '@/components/shared/TagBadges'; -import { SystemInfoCard } from '@/components/shared/cards/SystemInfoCard'; -import { HardwareCard } from '@/components/shared/cards/HardwareCard'; -import { RootDiskCard } from '@/components/shared/cards/RootDiskCard'; -import { NetworkInterfacesCard } from '@/components/shared/cards/NetworkInterfacesCard'; -import { DisksCard } from '@/components/shared/cards/DisksCard'; -import { TemperaturesCard } from '@/components/shared/cards/TemperaturesCard'; -import { RaidCard } from '@/components/shared/cards/RaidCard'; -import { getDiscoveryLoadingState } from '@/utils/discoveryPresentation'; import { ReportMergeModal } from './ReportMergeModal'; -import { DiscoveryTab } from '@/components/Discovery/DiscoveryTab'; import { PMGInstanceDrawer } from '@/components/PMG/PMGInstanceDrawer'; import { K8sDeploymentsDrawer } from '@/components/Kubernetes/K8sDeploymentsDrawer'; import { K8sNamespacesDrawer } from '@/components/Kubernetes/K8sNamespacesDrawer'; -import { MonitoringAPI } from '@/api/monitoring'; import { SwarmServicesDrawer } from '@/components/Docker/SwarmServicesDrawer'; -import { WebInterfaceUrlField } from '@/components/shared/WebInterfaceUrlField'; -import { getServiceHealthPresentation } from '@/utils/serviceHealthPresentation'; -import { - getResourceRoutingScopeLabel, - getResourceSensitivityLabel, -} from '@/utils/resourcePolicyPresentation'; -import { ResourceCorrelationSummary } from './ResourceCorrelationSummary'; -import { ResourceChangeSummary } from './ResourceChangeSummary'; -import { ResourceFacetSummary } from './ResourceFacetSummary'; -import { - RESOURCE_CHANGE_KIND_ORDER, - RESOURCE_CHANGE_SOURCE_ADAPTER_ORDER, - RESOURCE_CHANGE_SOURCE_TYPE_ORDER, - getResourceChangeKindPresentation, - getResourceChangeSourceAdapterPresentation, - getResourceChangeSourceTypePresentation, -} from '@/utils/resourceChangePresentation'; -import { formatConfidenceLabel } from '@/utils/confidencePresentation'; -import { formatIdentifierLabel } from '@/utils/textPresentation'; -import { buildInfrastructureResourceHref } from '@/routing/resourceLinks'; -import { formatInteger, formatSourceType } from './resourceDetailMappers'; +import { ResourceDetailDrawerDebugTab } from './ResourceDetailDrawerDebugTab'; +import { ResourceDetailDrawerOverviewTab } from './ResourceDetailDrawerOverviewTab'; import { useResourceDetailDrawerState } from './useResourceDetailDrawerState'; interface ResourceDetailDrawerProps { @@ -54,60 +17,6 @@ interface ResourceDetailDrawerProps { resolveResourceLabel?: (resourceId: string) => string | null | undefined; } -const hasMetadataEntries = (value?: Record | null): boolean => - Boolean(value && Object.keys(value).length > 0); - -interface SupportDisclosureProps { - title: string; - summary?: string | null; - expanded: boolean; - onToggle: () => void; - showLabel: string; - hideLabel: string; - children: JSX.Element; - class?: string; - buttonClass?: string; - contentClass?: string; - dataTestId?: string; -} - -const SupportDisclosure: Component = (props) => { - const summary = () => props.summary?.trim() ?? ''; - - return ( -
-
-
-
- {props.title} -
- -
{summary()}
-
-
- - -
- - -
{props.children}
-
-
- ); -}; - const TabAvailabilityNotice: Component<{ message: string }> = (props) => (
{props.message} @@ -128,149 +37,8 @@ export const getSpecializedTabAvailabilityMessage = (tab: SpecializedDrawerTab): } }; -const timelineKindOptions: Array<{ label: string; value: ResourceChangeKind | '' }> = [ - { label: 'All kinds', value: '' }, - ...RESOURCE_CHANGE_KIND_ORDER.map((kind) => ({ - label: getResourceChangeKindPresentation(kind).label, - value: kind, - })), -]; - -const timelineSourceTypeOptions: Array<{ label: string; value: ResourceChangeSourceType | '' }> = [ - { label: 'All sources', value: '' }, - ...RESOURCE_CHANGE_SOURCE_TYPE_ORDER.map((sourceType) => ({ - label: getResourceChangeSourceTypePresentation(sourceType).label, - value: sourceType, - })), -]; - -const timelineSourceAdapterOptions: Array<{ - label: string; - value: ResourceChangeSourceAdapter | ''; -}> = [ - { label: 'All adapters', value: '' }, - ...RESOURCE_CHANGE_SOURCE_ADAPTER_ORDER.map((sourceAdapter) => ({ - label: getResourceChangeSourceAdapterPresentation(sourceAdapter).label, - value: sourceAdapter, - })), -]; - const DrawerContent: Component = (props) => { - const { - activeTab, - setActiveTab, - debugEnabled, - copied, - showReportModal, - setShowReportModal, - showInvestigationContext, - setShowInvestigationContext, - showCorrelationContext, - setShowCorrelationContext, - showDiscoveryContext, - setShowDiscoveryContext, - showHostDetails, - setShowHostDetails, - showServiceDetails, - setShowServiceDetails, - showDockerUpdateControls, - setShowDockerUpdateControls, - showPbsJobDetail, - setShowPbsJobDetail, - showPmgMailFlowDetail, - setShowPmgMailFlowDetail, - displayName, - kubernetesClusterName, - resolveResourceLabel, - statusIndicator, - lastSeen, - lastSeenAbsolute, - platformBadge, - sourceBadge, - typeBadge, - unifiedSourceBadges, - hasUnifiedSources, - policyRedactions, - governanceSummary, - hasGovernanceData, - agentMeta, - kubernetesCapabilityBadges, - proxmoxNode, - agentInfo, - temperatureRows, - dockerHostData, - dockerHostSourceId, - dockerUpdatesAvailable, - dockerContainerCount, - dockerUpdatesCheckedRelative, - dockerHostCommand, - dockerHostCommandActive, - dockerUpdateActionsDisabled, - dockerUpdateActionsLoading, - dockerActionError, - setDockerActionError, - dockerActionNote, - setDockerActionNote, - confirmUpdateAll, - setConfirmUpdateAll, - dockerActionBusy, - setDockerActionBusy, - dockerSwarmInfo, - dockerSwarmClusterKey, - k8sDeploymentsPrefillNamespace, - setK8sDeploymentsPrefillNamespace, - timelineKindFilter, - setTimelineKindFilter, - timelineSourceTypeFilter, - setTimelineSourceTypeFilter, - timelineSourceAdapterFilter, - setTimelineSourceAdapterFilter, - resourceIntelligence, - resourceDependencies, - resourceDependents, - resourceCorrelations, - hasCorrelationContext, - hasInvestigationContext, - investigationContextSummary, - pbsData, - pmgData, - pbsJobTotal, - pmgQueueBacklog, - pmgUpdatedRelative, - pbsVisibleJobBreakdown, - pmgVisibleQueueBreakdown, - pmgVisibleMailBreakdown, - historyFacetCounts, - historyRecentChanges, - hasTimelineFilters, - historyLoadingLabel, - resourceTimelineCount, - sortedResourceTimeline, - facetBundleError, - refetchHistoryFacets, - mergedSources, - sourceSummary, - identityAliasValues, - primaryIdentityRows, - identityCardHasRichData, - aliasPreviewValues, - hasAliasOverflow, - hasIdentitySupportContext, - hasMergedSources, - discoveryConfig, - discoveryContextSummary, - hasHostDetails, - hostDetailSummary, - hasServiceDetails, - serviceDetailsSummary, - headerIdentity, - relatedLinks, - hasRuntimeOperationalContext, - sourceSections, - identityMatchInfo, - tabs, - handleCopyJson, - } = useResourceDetailDrawerState({ + const drawer = useResourceDetailDrawerState({ resource: props.resource, resolveResourceLabel: props.resolveResourceLabel, }); @@ -281,20 +49,23 @@ const DrawerContent: Component = (props) => {
-
- {displayName()} +
+ {drawer.displayName()}
-
- {headerIdentity()} +
+ {drawer.headerIdentity()}
- + {(badge) => ( {badge().label} @@ -302,17 +73,17 @@ const DrawerContent: Component = (props) => { )} - + {(badge) => ( {badge().label} )} - + {(badge) => ( {badge().label} @@ -322,7 +93,7 @@ const DrawerContent: Component = (props) => { } > - + {(badge) => ( {badge.label} @@ -353,16 +124,18 @@ const DrawerContent: Component = (props) => {
- + {(tab) => ( @@ -370,1106 +143,20 @@ const DrawerContent: Component = (props) => {
- {/* Overview Tab */} -
-
-
-
- Summary -
-
- -
- Current state -
-
-
- State - - {props.resource.status || 'unknown'} - -
- -
- Uptime - - {formatUptime(props.resource.uptime ?? 0)} - -
-
- -
- Last Seen - - {lastSeen() || '—'} - -
-
- -
- Sources - - {sourceSummary()!.label} - -
-
-
- Mode - - {formatSourceType(props.resource.sourceType)} - -
- 0}> -
- Alerts - - {formatInteger(props.resource.alerts?.length)} - -
-
- -
- Platform ID - - {props.resource.platformId} - -
-
- -
-
- -
- -
- Platform ID - - {props.resource.platformId} - -
-
- 0}> -
- Platform signals -
- - {(badge) => ( - - {badge.label} - - )} - -
-
-
- 0}> -
- Quick links -
- - {(link) => ( - - {link.compactLabel} - - )} - -
-
-
-
-
- -
- 0 - } - > -
- IP Addresses -
- - {(ip) => ( - - {ip} - - )} - -
-
-
- 0}> -
- Tags - -
-
- 0}> - - Aliases -
- - {(value) => ( - - {value} - - )} - -
-
- } - > -
- - Aliases - {identityAliasValues().length} - -
- - {(value) => ( - - {value} - - )} - -
-
-
- -
- -
-
- -
- - - -
- Identity -
-
- - {(row) => ( -
- {row.label} - - {row.value} - -
- )} -
- -
- No identity metadata yet. -
-
-
-
-
-
- -
-
-
-
-
- Change history -
- 0}> -
- -
-
-
-
-
{historyLoadingLabel()}
- -
Change filters active
-
-
-
-
- - - -
- - -
- -
-
- - -
-
- {facetBundleError()} - -
-
-
- -
-
- Event log -
- 0} - fallback={ -
- No events yet. -
- } - > -
- - {(change) => { - const kindPresentation = getResourceChangeKindPresentation(change.kind); - const sourceTypePresentation = getResourceChangeSourceTypePresentation( - change.sourceType, - ); - const sourceAdapterPresentation = change.sourceAdapter - ? getResourceChangeSourceAdapterPresentation(change.sourceAdapter) - : null; - - return ( -
-
-
-
- {kindPresentation.label} -
-
- {formatRelativeTime(change.observedAt)} - - - Occurred {formatRelativeTime(change.occurredAt)} - -
-
- {sourceTypePresentation.label} -
-
-
- Confidence - - {formatConfidenceLabel(change.confidence)} - -
-
- Adapter - - {sourceAdapterPresentation?.label || '—'} - -
- -
- Actor - {change.actor} -
-
- -
- Transition - - {change.from || '—'} → {change.to || '—'} - -
-
-
- -
- {change.reason} -
-
- -
- - Metadata - -
-                                  {JSON.stringify(change.metadata ?? {}, null, 2)}
-                                
-
-
- 0} - > -
- Related: - - {(relatedResource) => { - const label = resolveResourceLabel(relatedResource); - const href = buildInfrastructureResourceHref(relatedResource); - return href ? ( - - {label} - - ) : ( - - {label} - - ); - }} - -
-
-
- ); - }} -
-
-
-
-
- - - setShowServiceDetails((value) => !value)} - showLabel="Show service details" - hideLabel="Hide service details" - class="h-full" - contentClass="mt-3 space-y-3" - dataTestId="resource-service-details-section" - > - -
-
-
- Docker runtime -
- - - {dockerHostData()?.runtime} - - -
- -
-
- Containers - - {formatInteger(dockerContainerCount())} - -
-
- Updates - 0 ? 'text-sky-700 dark:text-sky-300' : 'text-base-content'}`} - > - {formatInteger(dockerUpdatesAvailable())} - -
- -
- Checked - - {dockerUpdatesCheckedRelative()} - -
-
- - -
- -
-
- Action - - {formatIdentifierLabel(dockerHostCommand()?.type, { - fallback: 'command', - })} - -
-
- State - - {formatIdentifierLabel(dockerHostCommand()?.status, { - fallback: 'unknown', - })} - -
- -
- {dockerHostCommand()?.message} -
-
- -
- {dockerHostCommand()?.failureReason} -
-
-
-
- - -
- {dockerActionError()} -
-
- -
- {dockerActionNote()} -
-
- -
- - - -
-
-
- - -
-
-
- - - {(pbs) => { - const connection = getServiceHealthPresentation( - props.resource.status, - pbs().connectionHealth, - ); - return ( -
-
-
- PBS -
- - - {pbs().hostname} - - -
-
-
- State - {connection.label} -
- -
- Version - {pbs().version} -
-
- -
- Uptime - - {formatUptime(pbs().uptimeSeconds ?? props.resource.uptime ?? 0)} - -
-
- -
-
-
-
Datastores
-
- {formatInteger(pbs().datastoreCount)} -
-
-
-
Jobs
-
- {formatInteger(pbsJobTotal())} -
-
-
-
- - Types - {pbsVisibleJobBreakdown().length} - -
- - {(entry) => ( - - {entry.label}:{' '} - - {formatInteger(entry.value)} - - - )} - -
-
-
-
- -
-
- ); - }} -
- - - {(pmg) => { - const connection = getServiceHealthPresentation( - props.resource.status, - pmg().connectionHealth, - ); - return ( -
-
-
- PMG -
- - - {pmg().hostname} - - -
-
-
- State - {connection.label} -
- -
- Version - {pmg().version} -
-
- -
- Uptime - - {formatUptime(pmg().uptimeSeconds ?? props.resource.uptime ?? 0)} - -
-
- -
-
-
-
Queue
-
0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`} - > - {formatInteger(pmg().queueTotal)} -
-
-
-
Backlog
-
0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`} - > - {formatInteger(pmgQueueBacklog())} -
-
-
- -
- -
- Nodes - - {formatInteger(pmg().nodeCount)} - -
-
- -
- Updated - - {pmgUpdatedRelative()} - -
-
-
-
-
- - Queue detail - -
- - {(entry) => ( -
- {entry.label} - - {formatInteger(entry.value)} - -
- )} -
-
-
-
- - Mail detail - -
- - {(entry) => ( -
- {entry.label} - - {formatInteger(entry.value)} - -
- )} -
-
-
-
-
- -
-
- ); - }} -
-
-
- - - setShowHostDetails((value) => !value)} - showLabel="Show host details" - hideLabel="Hide host details" - class="h-full" - contentClass="mt-3 flex flex-wrap gap-3 [&>*]:flex-1 [&>*]:basis-[calc(50%-0.375rem)] [&>*]:min-w-[220px] [&>*]:max-w-full [&>*]:overflow-hidden" - dataTestId="resource-host-details-section" - > - - {(node) => ( - <> - - - - - )} - - - {(agent) => ( - <> - - - - - - - - )} - - - - - - setShowInvestigationContext((value) => !value)} - showLabel="Show context" - hideLabel="Hide context" - class="h-full" - contentClass="mt-3 space-y-3" - dataTestId="resource-investigation-context" - > - - {(intel) => ( -
-
- AI Intelligence -
-
- Health - - {intel().health.grade} · {Math.round(intel().health.score)}/100 - -
-
- Trend - - {intel().health.trend} - -
-
- Notes - {intel().note_count} -
- - -
-
- - Correlation context - - -
- - -
- -
-
-
-
-
- )} -
- - -
-
- Data Governance -
- -
- Sensitivity - - {getResourceSensitivityLabel(props.resource.policy?.sensitivity)} - -
-
- Routing - - {getResourceRoutingScopeLabel(props.resource.policy?.routing.scope)} - -
-
- 0 || governanceSummary()}> -
- Redactions - - {policyRedactions().length} - -
-
- 0}> -
- Redaction labels -
- - {(label) => ( - - {label} - - )} - -
-
-
- -
- AI-Safe Summary -
- {governanceSummary()} -
-
-
-
-
-
-
-
- - - {(config) => ( -
- - - setShowDiscoveryContext((value) => !value)} - showLabel="Show metadata" - hideLabel="Hide metadata" - class="h-full" - dataTestId="resource-discovery-context" - > - -
- - {getDiscoveryLoadingState().text} - -
- } - > - -
-
-
- )} -
-
+
+
{/* PMG Mail Tab */} -
+
{/* Mount on-demand to avoid background fetching when the tab isn't open. */} - + = (props) => { > @@ -1486,11 +173,11 @@ const DrawerContent: Component = (props) => { {/* Kubernetes Namespaces Tab */}
{/* Mount on-demand to avoid background fetching when the tab isn't open. */} - + = (props) => { } > { - setK8sDeploymentsPrefillNamespace((ns || '').trim()); - setActiveTab('deployments'); + drawer.setK8sDeploymentsPrefillNamespace((ns || '').trim()); + drawer.setActiveTab('deployments'); }} /> @@ -1510,11 +197,11 @@ const DrawerContent: Component = (props) => { {/* Kubernetes Deployments Tab */}
{/* Mount on-demand to avoid background fetching when the tab isn't open. */} - + = (props) => { } >
{/* Docker Swarm Tab */} -
+
{/* Mount on-demand to avoid background fetching when the tab isn't open. */} - + } > - +
{/* Debug Tab */} - -
-
-
- Debug mode is enabled via localStorage (pulse_debug_mode). -
- -
- -
-
-
- Unified Resource -
-
-                {JSON.stringify(props.resource, null, 2)}
-              
-
- -
-
- Identity Matching -
-
-                {JSON.stringify(
-                  {
-                    identity: props.resource.identity,
-                    matchInfo: identityMatchInfo(),
-                  },
-                  null,
-                  2,
-                )}
-              
-
- -
-
- Sources -
-
- - {(section) => { - const status = sourceStatus()[section.id]; - const lastSeenText = formatRelativeTime(status?.lastSeen); - return ( -
- - {section.label} - - {status?.status ?? 'unknown'} - {lastSeenText ? ` • ${lastSeenText}` : ''} - - - -
- {status?.error} -
-
-
-                          {JSON.stringify(section.payload ?? {}, null, 2)}
-                        
-
- ); - }} -
-
-
-
+ +
+
- +
); diff --git a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx new file mode 100644 index 000000000..c716fc7b6 --- /dev/null +++ b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx @@ -0,0 +1,93 @@ +import { For, Show } from 'solid-js'; +import type { Component } from 'solid-js'; +import type { Resource } from '@/types/resource'; +import { formatRelativeTime } from '@/utils/format'; +import type { UseResourceDetailDrawerStateResult } from './useResourceDetailDrawerState'; + +interface ResourceDetailDrawerDebugTabProps { + resource: Resource; + drawer: UseResourceDetailDrawerStateResult; +} + +export const ResourceDetailDrawerDebugTab: Component = ( + props, +) => { + const { drawer } = props; + + return ( +
+
+
+ Debug mode is enabled via localStorage (pulse_debug_mode). +
+ +
+ +
+
+
+ Unified Resource +
+
+            {JSON.stringify(props.resource, null, 2)}
+          
+
+ +
+
+ Identity Matching +
+
+            {JSON.stringify(
+              {
+                identity: props.resource.identity,
+                matchInfo: drawer.identityMatchInfo(),
+              },
+              null,
+              2,
+            )}
+          
+
+ +
+
+ Sources +
+
+ + {(section) => { + const status = drawer.sourceStatus()[section.id]; + const lastSeenText = formatRelativeTime(status?.lastSeen); + return ( +
+ + {section.label} + + {status?.status ?? 'unknown'} + {lastSeenText ? ` • ${lastSeenText}` : ''} + + + +
+ {status?.error} +
+
+
+                      {JSON.stringify(section.payload ?? {}, null, 2)}
+                    
+
+ ); + }} +
+
+
+
+
+ ); +}; diff --git a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx new file mode 100644 index 000000000..f764a35ce --- /dev/null +++ b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx @@ -0,0 +1,1158 @@ +import { For, Show, Suspense } from 'solid-js'; +import type { Component } from 'solid-js'; +import type { + Resource, + ResourceChangeKind, + ResourceChangeSourceAdapter, + ResourceChangeSourceType, +} from '@/types/resource'; +import { formatUptime, formatRelativeTime } from '@/utils/format'; +import { Card } from '@/components/shared/Card'; +import { TagBadges } from '@/components/shared/TagBadges'; +import { SystemInfoCard } from '@/components/shared/cards/SystemInfoCard'; +import { HardwareCard } from '@/components/shared/cards/HardwareCard'; +import { RootDiskCard } from '@/components/shared/cards/RootDiskCard'; +import { NetworkInterfacesCard } from '@/components/shared/cards/NetworkInterfacesCard'; +import { DisksCard } from '@/components/shared/cards/DisksCard'; +import { TemperaturesCard } from '@/components/shared/cards/TemperaturesCard'; +import { RaidCard } from '@/components/shared/cards/RaidCard'; +import { DiscoveryTab } from '@/components/Discovery/DiscoveryTab'; +import { MonitoringAPI } from '@/api/monitoring'; +import { WebInterfaceUrlField } from '@/components/shared/WebInterfaceUrlField'; +import { getServiceHealthPresentation } from '@/utils/serviceHealthPresentation'; +import { + getResourceRoutingScopeLabel, + getResourceSensitivityLabel, +} from '@/utils/resourcePolicyPresentation'; +import { ResourceCorrelationSummary } from './ResourceCorrelationSummary'; +import { ResourceChangeSummary } from './ResourceChangeSummary'; +import { ResourceFacetSummary } from './ResourceFacetSummary'; +import { + RESOURCE_CHANGE_KIND_ORDER, + RESOURCE_CHANGE_SOURCE_ADAPTER_ORDER, + RESOURCE_CHANGE_SOURCE_TYPE_ORDER, + getResourceChangeKindPresentation, + getResourceChangeSourceAdapterPresentation, + getResourceChangeSourceTypePresentation, +} from '@/utils/resourceChangePresentation'; +import { formatConfidenceLabel } from '@/utils/confidencePresentation'; +import { formatIdentifierLabel } from '@/utils/textPresentation'; +import { buildInfrastructureResourceHref } from '@/routing/resourceLinks'; +import { getDiscoveryLoadingState } from '@/utils/discoveryPresentation'; +import { formatInteger, formatSourceType } from './resourceDetailMappers'; +import { + ResourceDetailDrawerSupportDisclosure as SupportDisclosure, +} from './ResourceDetailDrawerSupportDisclosure'; +import type { UseResourceDetailDrawerStateResult } from './useResourceDetailDrawerState'; + +interface ResourceDetailDrawerOverviewTabProps { + resource: Resource; + drawer: UseResourceDetailDrawerStateResult; +} + +const hasMetadataEntries = (value?: Record | null): boolean => + Boolean(value && Object.keys(value).length > 0); + +const timelineKindOptions: Array<{ label: string; value: ResourceChangeKind | '' }> = [ + { label: 'All kinds', value: '' }, + ...RESOURCE_CHANGE_KIND_ORDER.map((kind) => ({ + label: getResourceChangeKindPresentation(kind).label, + value: kind, + })), +]; + +const timelineSourceTypeOptions: Array<{ label: string; value: ResourceChangeSourceType | '' }> = [ + { label: 'All sources', value: '' }, + ...RESOURCE_CHANGE_SOURCE_TYPE_ORDER.map((sourceType) => ({ + label: getResourceChangeSourceTypePresentation(sourceType).label, + value: sourceType, + })), +]; + +const timelineSourceAdapterOptions: Array<{ + label: string; + value: ResourceChangeSourceAdapter | ''; +}> = [ + { label: 'All adapters', value: '' }, + ...RESOURCE_CHANGE_SOURCE_ADAPTER_ORDER.map((sourceAdapter) => ({ + label: getResourceChangeSourceAdapterPresentation(sourceAdapter).label, + value: sourceAdapter, + })), +]; + +export const ResourceDetailDrawerOverviewTab: Component = ( + props, +) => { + const { resource, drawer } = props; + + return ( +
+
+
Summary
+
+ +
+ Current state +
+
+
+ State + + {resource.status || 'unknown'} + +
+ +
+ Uptime + + {formatUptime(resource.uptime ?? 0)} + +
+
+ +
+ Last Seen + + {drawer.lastSeen() || '—'} + +
+
+ +
+ Sources + + {drawer.sourceSummary()!.label} + +
+
+
+ Mode + + {formatSourceType(resource.sourceType)} + +
+ 0}> +
+ Alerts + + {formatInteger(resource.alerts?.length)} + +
+
+ +
+ Platform ID + + {resource.platformId} + +
+
+ +
+
+ +
+ +
+ Platform ID + + {resource.platformId} + +
+
+ 0}> +
+ Platform signals +
+ + {(badge) => ( + + {badge.label} + + )} + +
+
+
+ 0}> +
+ Quick links +
+ + {(link) => ( + + {link.compactLabel} + + )} + +
+
+
+
+
+ +
+ 0}> +
+ IP Addresses +
+ + {(ip) => ( + + {ip} + + )} + +
+
+
+ 0}> +
+ Tags + +
+
+ 0}> + + Aliases +
+ + {(value) => ( + + {value} + + )} + +
+
+ } + > +
+ + Aliases + {drawer.identityAliasValues().length} + +
+ + {(value) => ( + + {value} + + )} + +
+
+
+ +
+ +
+
+ +
+ + + +
+ Identity +
+
+ + {(row) => ( +
+ {row.label} + + {row.value} + +
+ )} +
+ +
+ No identity metadata yet. +
+
+
+
+
+
+ +
+
+
+
+
+ Change history +
+ 0}> +
+ +
+
+
+
+
{drawer.historyLoadingLabel()}
+ +
Change filters active
+
+
+
+
+ + + +
+ + +
+ +
+
+ + +
+
+ {drawer.facetBundleError()} + +
+
+
+ +
+
+ Event log +
+ 0} + fallback={ +
+ No events yet. +
+ } + > +
+ + {(change) => { + const kindPresentation = getResourceChangeKindPresentation(change.kind); + const sourceTypePresentation = getResourceChangeSourceTypePresentation( + change.sourceType, + ); + const sourceAdapterPresentation = change.sourceAdapter + ? getResourceChangeSourceAdapterPresentation(change.sourceAdapter) + : null; + + return ( +
+
+
+
+ {kindPresentation.label} +
+
+ {formatRelativeTime(change.observedAt)} + + + Occurred {formatRelativeTime(change.occurredAt)} + +
+
+ {sourceTypePresentation.label} +
+
+
+ Confidence + + {formatConfidenceLabel(change.confidence)} + +
+
+ Adapter + + {sourceAdapterPresentation?.label || '—'} + +
+ +
+ Actor + {change.actor} +
+
+ +
+ Transition + + {change.from || '—'} → {change.to || '—'} + +
+
+
+ +
+ {change.reason} +
+
+ +
+ + Metadata + +
+                              {JSON.stringify(change.metadata ?? {}, null, 2)}
+                            
+
+
+ 0}> +
+ Related: + + {(relatedResource) => { + const label = drawer.resolveResourceLabel(relatedResource); + const href = buildInfrastructureResourceHref(relatedResource); + return href ? ( + + {label} + + ) : ( + + {label} + + ); + }} + +
+
+
+ ); + }} +
+
+
+
+
+ + + drawer.setShowServiceDetails((value) => !value)} + showLabel="Show service details" + hideLabel="Hide service details" + class="h-full" + contentClass="mt-3 space-y-3" + dataTestId="resource-service-details-section" + > + +
+
+
+ Docker runtime +
+ + + {drawer.dockerHostData()?.runtime} + + +
+ +
+
+ Containers + + {formatInteger(drawer.dockerContainerCount())} + +
+
+ Updates + 0 ? 'text-sky-700 dark:text-sky-300' : 'text-base-content'}`} + > + {formatInteger(drawer.dockerUpdatesAvailable())} + +
+ +
+ Checked + + {drawer.dockerUpdatesCheckedRelative()} + +
+
+ + +
+ +
+
+ Action + + {formatIdentifierLabel(drawer.dockerHostCommand()?.type, { + fallback: 'command', + })} + +
+
+ State + + {formatIdentifierLabel(drawer.dockerHostCommand()?.status, { + fallback: 'unknown', + })} + +
+ +
+ {drawer.dockerHostCommand()?.message} +
+
+ +
+ {drawer.dockerHostCommand()?.failureReason} +
+
+
+
+ + +
+ {drawer.dockerActionError()} +
+
+ +
+ {drawer.dockerActionNote()} +
+
+ +
+ + + +
+
+
+ + +
+
+
+ + + {(pbs) => { + const connection = getServiceHealthPresentation(resource.status, pbs().connectionHealth); + return ( +
+
+
+ PBS +
+ + + {pbs().hostname} + + +
+
+
+ State + {connection.label} +
+ +
+ Version + {pbs().version} +
+
+ +
+ Uptime + + {formatUptime(pbs().uptimeSeconds ?? resource.uptime ?? 0)} + +
+
+ +
+
+
+
Datastores
+
+ {formatInteger(pbs().datastoreCount)} +
+
+
+
Jobs
+
+ {formatInteger(drawer.pbsJobTotal())} +
+
+
+
+ + Types + {drawer.pbsVisibleJobBreakdown().length} + +
+ + {(entry) => ( + + {entry.label}:{' '} + + {formatInteger(entry.value)} + + + )} + +
+
+
+
+ +
+
+ ); + }} +
+ + + {(pmg) => { + const connection = getServiceHealthPresentation(resource.status, pmg().connectionHealth); + return ( +
+
+
+ PMG +
+ + + {pmg().hostname} + + +
+
+
+ State + {connection.label} +
+ +
+ Version + {pmg().version} +
+
+ +
+ Uptime + + {formatUptime(pmg().uptimeSeconds ?? resource.uptime ?? 0)} + +
+
+ +
+
+
+
Queue
+
0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`} + > + {formatInteger(pmg().queueTotal)} +
+
+
+
Backlog
+
0 ? 'text-amber-600 dark:text-amber-400' : 'text-base-content'}`} + > + {formatInteger(drawer.pmgQueueBacklog())} +
+
+
+ +
+ +
+ Nodes + + {formatInteger(pmg().nodeCount)} + +
+
+ +
+ Updated + + {drawer.pmgUpdatedRelative()} + +
+
+
+
+
+ + Queue detail + +
+ + {(entry) => ( +
+ {entry.label} + + {formatInteger(entry.value)} + +
+ )} +
+
+
+
+ + Mail detail + +
+ + {(entry) => ( +
+ {entry.label} + + {formatInteger(entry.value)} + +
+ )} +
+
+
+
+
+ +
+
+ ); + }} +
+
+
+ + + drawer.setShowHostDetails((value) => !value)} + showLabel="Show host details" + hideLabel="Hide host details" + class="h-full" + contentClass="mt-3 flex flex-wrap gap-3 [&>*]:flex-1 [&>*]:basis-[calc(50%-0.375rem)] [&>*]:min-w-[220px] [&>*]:max-w-full [&>*]:overflow-hidden" + dataTestId="resource-host-details-section" + > + + {(node) => ( + <> + + + + + )} + + + {(agent) => ( + <> + + + + + + + + )} + + + + + + drawer.setShowInvestigationContext((value) => !value)} + showLabel="Show context" + hideLabel="Hide context" + class="h-full" + contentClass="mt-3 space-y-3" + dataTestId="resource-investigation-context" + > + + {(intel) => ( +
+
+ AI Intelligence +
+
+ Health + + {intel().health.grade} · {Math.round(intel().health.score)}/100 + +
+
+ Trend + + {intel().health.trend} + +
+
+ Notes + {intel().note_count} +
+ + +
+
+ + Correlation context + + +
+ + +
+ +
+
+
+
+
+ )} +
+ + +
+
+ Data Governance +
+ +
+ Sensitivity + + {getResourceSensitivityLabel(resource.policy?.sensitivity)} + +
+
+ Routing + + {getResourceRoutingScopeLabel(resource.policy?.routing.scope)} + +
+
+ 0 || drawer.governanceSummary()}> +
+ Redactions + + {drawer.policyRedactions().length} + +
+
+ 0}> +
+ Redaction labels +
+ + {(label) => ( + + {label} + + )} + +
+
+
+ +
+ AI-Safe Summary +
+ {drawer.governanceSummary()} +
+
+
+
+
+
+
+
+ + + {(config) => ( +
+ + + drawer.setShowDiscoveryContext((value) => !value)} + showLabel="Show metadata" + hideLabel="Hide metadata" + class="h-full" + dataTestId="resource-discovery-context" + > + +
+ {getDiscoveryLoadingState().text} +
+ } + > + +
+
+
+ )} +
+
+ ); +}; diff --git a/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx new file mode 100644 index 000000000..d87e2f8fd --- /dev/null +++ b/frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx @@ -0,0 +1,55 @@ +import { Show } from 'solid-js'; +import type { Component, JSX } from 'solid-js'; + +interface ResourceDetailDrawerSupportDisclosureProps { + title: string; + summary?: string | null; + expanded: boolean; + onToggle: () => void; + showLabel: string; + hideLabel: string; + children: JSX.Element; + class?: string; + buttonClass?: string; + contentClass?: string; + dataTestId?: string; +} + +export const ResourceDetailDrawerSupportDisclosure: Component< + ResourceDetailDrawerSupportDisclosureProps +> = (props) => { + const summary = () => props.summary?.trim() ?? ''; + + return ( +
+
+
+
+ {props.title} +
+ +
{summary()}
+
+
+ + +
+ + +
{props.children}
+
+
+ ); +}; diff --git a/frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx b/frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx index e27ff067e..88e4a0f95 100644 --- a/frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx +++ b/frontend-modern/src/components/Infrastructure/__tests__/ResourceDetailDrawer.history.test.tsx @@ -3,7 +3,8 @@ import { fireEvent, render, screen, within } from '@solidjs/testing-library'; import discoveryTabSource from '@/components/Discovery/DiscoveryTab.tsx?raw'; import discoveryTabStateSource from '@/components/Discovery/useDiscoveryTabState.ts?raw'; -import resourceDetailDrawerSource from '@/components/Infrastructure/ResourceDetailDrawer.tsx?raw'; +import resourceDetailDrawerShellSource from '@/components/Infrastructure/ResourceDetailDrawer.tsx?raw'; +import resourceDetailDrawerOverviewSource from '@/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx?raw'; import type { Resource } from '@/types/resource'; import { ResourceDetailDrawer } from '@/components/Infrastructure/ResourceDetailDrawer'; @@ -109,10 +110,15 @@ describe('ResourceDetailDrawer change history section', () => { expect(discoveryTabSource).not.toContain('getConnectedAgents('); expect(discoveryTabSource).not.toContain('triggerDiscovery('); expect(discoveryTabSource).not.toContain('updateDiscoveryNotes('); - expect(resourceDetailDrawerSource).toContain("from '@/components/shared/TagBadges'"); - expect(resourceDetailDrawerSource).not.toContain( + expect(resourceDetailDrawerOverviewSource).toContain("from '@/components/shared/TagBadges'"); + expect(resourceDetailDrawerOverviewSource).not.toContain( "from '@/components/Dashboard/TagBadges'", ); + expect(resourceDetailDrawerShellSource).toContain( + "from './ResourceDetailDrawerOverviewTab'", + ); + expect(resourceDetailDrawerShellSource).toContain("from './ResourceDetailDrawerDebugTab'"); + expect(resourceDetailDrawerShellSource).not.toContain('Change history'); }); it('keeps compact timeline summary chips in overview while showing one embedded change history section', async () => { diff --git a/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts b/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts index 522839e5d..c72d9174a 100644 --- a/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts +++ b/frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts @@ -181,6 +181,7 @@ export interface UseResourceDetailDrawerStateResult { >; hasRuntimeOperationalContext: Accessor; sourceSections: Accessor>; + sourceStatus: Accessor>; identityMatchInfo: Accessor; debugJson: Accessor; tabs: Accessor>; @@ -910,6 +911,7 @@ export const useResourceDetailDrawerState = ( relatedLinks, hasRuntimeOperationalContext, sourceSections, + sourceStatus, identityMatchInfo, debugJson, tabs, diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index d4f2108ca..18caecea7 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -5,7 +5,7 @@ import monitoredSystemLimitWarningBannerSource from '@/components/shared/Monitor import selectionCardGroupSource from '@/components/shared/SelectionCardGroup.tsx?raw'; import tagBadgesSource from '@/components/shared/TagBadges.tsx?raw'; import guestRowSource from '@/components/Dashboard/GuestRow.tsx?raw'; -import resourceDetailDrawerSource from '@/components/Infrastructure/ResourceDetailDrawer.tsx?raw'; +import resourceDetailDrawerOverviewSource from '@/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx?raw'; import aiSettingsDialogsSource from '@/components/Settings/AISettingsDialogs.tsx?raw'; import generalSettingsPanelSource from '@/components/Settings/GeneralSettingsPanel.tsx?raw'; import proxmoxSettingsPanelSource from '@/components/Settings/ProxmoxSettingsPanel.tsx?raw'; @@ -100,8 +100,8 @@ describe('shared primitive guardrails', () => { expect(tagBadgesSource).toContain("from '@/components/shared/Tooltip'"); expect(guestRowSource).toContain("from '@/components/shared/TagBadges'"); expect(guestRowSource).not.toContain("from './TagBadges'"); - expect(resourceDetailDrawerSource).toContain("from '@/components/shared/TagBadges'"); - expect(resourceDetailDrawerSource).not.toContain( + expect(resourceDetailDrawerOverviewSource).toContain("from '@/components/shared/TagBadges'"); + expect(resourceDetailDrawerOverviewSource).not.toContain( "from '@/components/Dashboard/TagBadges'", ); }); diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index 7793a1f16..e8d7bbf74 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -243,6 +243,8 @@ import licensePresentationSource from '@/utils/licensePresentation.ts?raw'; import securityScorePresentationSource from '@/utils/securityScorePresentation.ts?raw'; import securityAuthPresentationSource from '@/utils/securityAuthPresentation.ts?raw'; import resourceDetailDrawerShellSource from '@/components/Infrastructure/ResourceDetailDrawer.tsx?raw'; +import resourceDetailDrawerOverviewSource from '@/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx?raw'; +import resourceDetailDrawerDebugSource from '@/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx?raw'; import infrastructureSummarySource from '@/components/Infrastructure/InfrastructureSummary.tsx?raw'; import resourceDetailMappersSource from '@/components/Infrastructure/resourceDetailMappers.ts?raw'; import resourceDetailDrawerStateSource from '@/components/Infrastructure/useResourceDetailDrawerState.ts?raw'; @@ -518,6 +520,8 @@ const aiSettingsSource = [ const resourceDetailDrawerSource = [ resourceDetailDrawerShellSource, + resourceDetailDrawerOverviewSource, + resourceDetailDrawerDebugSource, resourceDetailDrawerStateSource, ].join('\n'); @@ -861,10 +865,14 @@ describe('frontend resource type boundaries', () => { expect(guestRowCellsSource).toContain('useTooltip'); expect(guestRowSource).not.toContain('buildGuestId'); expect(tagBadgesSource).toContain("from '@/components/shared/Tooltip'"); - expect(resourceDetailDrawerShellSource).toContain("from '@/components/shared/TagBadges'"); - expect(resourceDetailDrawerShellSource).not.toContain( + expect(resourceDetailDrawerOverviewSource).toContain("from '@/components/shared/TagBadges'"); + expect(resourceDetailDrawerOverviewSource).not.toContain( "from '@/components/Dashboard/TagBadges'", ); + expect(resourceDetailDrawerShellSource).toContain( + "from './ResourceDetailDrawerOverviewTab'", + ); + expect(resourceDetailDrawerShellSource).toContain("from './ResourceDetailDrawerDebugTab'"); expect(guestDrawerSource).toContain('useGuestDrawerState'); expect(guestDrawerSource).toContain('GuestDrawerOverview'); expect(guestDrawerStateSource).toContain('getCanonicalWorkloadId');