Canonicalize platform drawer table shells

This commit is contained in:
rcourtman 2026-04-29 20:41:31 +01:00
parent d147154cfd
commit f6bf4779be
10 changed files with 314 additions and 300 deletions

View file

@ -187,7 +187,10 @@ work extends shared components instead of creating new local variants.
settings source/configured-node tables, must use `Table` directly rather than
nesting another card or scroll wrapper. If a framed table needs bounded
vertical height, that constraint belongs on `Table.wrapperClass` so the
shared table shell still owns overflow behavior.
shared table shell still owns overflow behavior. Resource-detail drawer
tables that consume `Table`, including Docker Swarm services and Kubernetes
namespaces/deployments, inherit the same scroll-shell owner instead of
carrying drawer-local `overflow-x-auto` wrappers.
Product-table subgroup/header rows must likewise consume the shared
`frontend-modern/src/components/shared/groupedTableRowPresentation.ts`
helper and `.grouped-table-row` CSS token contract instead of local

View file

@ -4859,6 +4859,7 @@
"owned_files": [
"frontend-modern/src/components/Discovery/DiscoveryTab.tsx",
"frontend-modern/src/components/Discovery/useDiscoveryTabState.ts",
"frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx",
"frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts",
"frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx",
"frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts",
@ -4891,6 +4892,8 @@
"frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts",
"frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts",
"frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts",
"frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx",
"frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx",
"frontend-modern/src/components/PMG/ServiceHealthBadge.tsx",
"frontend-modern/src/features/infrastructure/infrastructurePageModel.ts",
"frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx",
@ -4979,6 +4982,7 @@
"match_files": [
"frontend-modern/src/components/Discovery/DiscoveryTab.tsx",
"frontend-modern/src/components/Discovery/useDiscoveryTabState.ts",
"frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx",
"frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts",
"frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx",
"frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts",
@ -5010,6 +5014,8 @@
"frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts",
"frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts",
"frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts",
"frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx",
"frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx",
"frontend-modern/src/features/infrastructure/infrastructurePageModel.ts",
"frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx",
"frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts",
@ -5045,6 +5051,7 @@
"frontend-modern/src/routing/__tests__/resourceLinks.test.ts",
"frontend-modern/src/stores/__tests__/websocket-unified.test.ts",
"frontend-modern/src/types/__tests__/resource.test.ts",
"frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts",
"internal/unifiedresources/code_standards_test.go"
]
},

View file

@ -52,60 +52,63 @@ cross-source deduplication.
28. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerOverviewTab.tsx`
29. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerDebugTab.tsx`
30. `frontend-modern/src/components/Infrastructure/ResourceDetailDrawerSupportDisclosure.tsx`
31. `frontend-modern/src/components/Infrastructure/ResourceActionHistory.tsx`
31. `frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx`
32. `frontend-modern/src/components/Infrastructure/ResourceChangeSummary.tsx`
33. `frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx`
34. `frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx`
35. `frontend-modern/src/components/Infrastructure/resourceBadges.ts`
36. `frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx`
37. `frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx`
38. `frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx`
39. `frontend-modern/src/components/Infrastructure/UnifiedResourceServiceInfrastructureCard.tsx`
40. `frontend-modern/src/components/Infrastructure/unifiedResourceTableModel.ts`
41. `frontend-modern/src/components/Infrastructure/unifiedResourceTableStateModel.ts`
42. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDerivedState.ts`
43. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerServiceModel.ts`
44. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerVmwareModel.ts`
45. `frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts`
46. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts`
47. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerHistoryState.ts`
48. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDockerActionsState.ts`
49. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts`
50. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
51. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts`
52. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx`
53. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts`
54. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
55. `frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
56. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
57. `frontend-modern/src/features/infrastructure/infrastructurePageModel.ts`
58. `frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx`
59. `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts`
60. `frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts`
61. `frontend-modern/src/utils/agentResources.ts`
62. `frontend-modern/src/utils/canonicalResourceTypes.ts`
63. `frontend-modern/src/utils/resourceBadgePresentation.ts`
64. `frontend-modern/src/utils/resourceChangePresentation.ts`
65. `frontend-modern/src/utils/actionAuditPresentation.ts`
65. `frontend-modern/src/utils/resourceCorrelationPresentation.ts`
66. `frontend-modern/src/utils/resourcePlatformData.ts`
67. `frontend-modern/src/utils/resourcePolicyPresentation.ts`
68. `frontend-modern/src/utils/resourceStateAdapters.ts`
69. `frontend-modern/src/utils/resourceTypeCompat.ts`
70. `frontend-modern/src/utils/resourceTypePresentation.ts`
71. `frontend-modern/src/utils/serviceHealthPresentation.ts`
72. `frontend-modern/src/utils/sourceTypePresentation.ts`
73. `frontend-modern/src/utils/workloadTypePresentation.ts`
74. `frontend-modern/src/components/PMG/ServiceHealthBadge.tsx`
75. `frontend-modern/src/utils/resourceIdentity.ts`
76. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerIdentityModel.ts`
77. `frontend-modern/src/hooks/useUnifiedResources.ts`
79. `frontend-modern/src/types/resource.ts`
80. `frontend-modern/src/utils/sourcePlatforms.ts`
81. `frontend-modern/src/utils/platformSupportManifest.generated.ts`
82. `internal/unifiedresources/kubernetes_metric_ids.go`
83. `internal/unifiedresources/policy_posture.go`
31. `frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx`
32. `frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx`
33. `frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx`
34. `frontend-modern/src/components/Infrastructure/ResourceActionHistory.tsx`
35. `frontend-modern/src/components/Infrastructure/ResourceFacetSummary.tsx`
36. `frontend-modern/src/components/Infrastructure/ResourceChangeSummary.tsx`
37. `frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx`
38. `frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx`
39. `frontend-modern/src/components/Infrastructure/resourceBadges.ts`
40. `frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx`
41. `frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx`
42. `frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx`
43. `frontend-modern/src/components/Infrastructure/UnifiedResourceServiceInfrastructureCard.tsx`
44. `frontend-modern/src/components/Infrastructure/unifiedResourceTableModel.ts`
45. `frontend-modern/src/components/Infrastructure/unifiedResourceTableStateModel.ts`
46. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDerivedState.ts`
47. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerServiceModel.ts`
48. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerVmwareModel.ts`
49. `frontend-modern/src/components/Infrastructure/resourceDetailDiscoveryModel.ts`
50. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerOperationalModel.ts`
51. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerHistoryState.ts`
52. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerDockerActionsState.ts`
53. `frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts`
54. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts`
55. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts`
56. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx`
57. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts`
58. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
59. `frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
60. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
61. `frontend-modern/src/features/infrastructure/infrastructurePageModel.ts`
62. `frontend-modern/src/components/Infrastructure/InfrastructureSummary.tsx`
63. `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts`
64. `frontend-modern/src/components/Infrastructure/infrastructureSummaryModel.ts`
65. `frontend-modern/src/utils/agentResources.ts`
66. `frontend-modern/src/utils/canonicalResourceTypes.ts`
67. `frontend-modern/src/utils/resourceBadgePresentation.ts`
68. `frontend-modern/src/utils/resourceChangePresentation.ts`
69. `frontend-modern/src/utils/actionAuditPresentation.ts`
70. `frontend-modern/src/utils/resourceCorrelationPresentation.ts`
71. `frontend-modern/src/utils/resourcePlatformData.ts`
72. `frontend-modern/src/utils/resourcePolicyPresentation.ts`
73. `frontend-modern/src/utils/resourceStateAdapters.ts`
74. `frontend-modern/src/utils/resourceTypeCompat.ts`
75. `frontend-modern/src/utils/resourceTypePresentation.ts`
76. `frontend-modern/src/utils/serviceHealthPresentation.ts`
77. `frontend-modern/src/utils/sourceTypePresentation.ts`
78. `frontend-modern/src/utils/workloadTypePresentation.ts`
79. `frontend-modern/src/components/PMG/ServiceHealthBadge.tsx`
80. `frontend-modern/src/utils/resourceIdentity.ts`
81. `frontend-modern/src/components/Infrastructure/resourceDetailDrawerIdentityModel.ts`
82. `frontend-modern/src/hooks/useUnifiedResources.ts`
83. `frontend-modern/src/types/resource.ts`
84. `frontend-modern/src/utils/sourcePlatforms.ts`
85. `frontend-modern/src/utils/platformSupportManifest.generated.ts`
86. `internal/unifiedresources/kubernetes_metric_ids.go`
87. `internal/unifiedresources/policy_posture.go`
## Shared Boundaries
@ -162,6 +165,10 @@ cross-source deduplication.
infrastructure tables must use the shared `Table` primitive's scroll frame
directly instead of reintroducing page-local `overflow-x-auto` wrappers
inside the canonical card shell.
Docker Swarm service and Kubernetes namespace/deployment resource-drawer
tables follow that same shell boundary: their platform-specific rows and
actions are unified-resource-owned, while horizontal overflow is owned by the
shared `Table` wrapper rather than drawer-local scroll divs.
Resource detail mappers now reuse the shared
`frontend-modern/src/utils/textPresentation.ts` title-case helper for sensor
@ -221,7 +228,7 @@ AI-only summary payloads, or page-local heuristics.
generic disk-count aggregate, so resource drawers and incidents do not hide
the actual protection boundary behind a broader count phrase.
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/action-history 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/ResourceActionHistory.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 plus `internal/api/activity_audit_handlers.go` action-audit contracts together
7. Add or change resource drawer timeline/facet/action-history 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/Docker/SwarmServicesDrawer.tsx`, `frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx`, `frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx`, `frontend-modern/src/components/Infrastructure/ResourceActionHistory.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 plus `internal/api/activity_audit_handlers.go` action-audit contracts 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
9. Keep dashboard and infrastructure freshness on the canonical unified-resource
ownership path. `frontend-modern/src/stores/websocket.ts`,

View file

@ -253,90 +253,82 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
}
>
<Card padding="none" tone="card" class="overflow-hidden">
<div class="overflow-x-auto">
<Table class="w-full min-w-[900px] border-collapse text-xs">
<TableHeader class="bg-surface-alt text-muted border-b border-border">
<TableRow class="text-left text-[10px] uppercase tracking-wide">
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.serviceColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.stackColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.imageColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.modeColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.desiredColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.runningColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.updateColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.portsColumnLabel}
</TableHead>
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border-subtle">
<For each={filteredServices()}>
{(svc) => {
const name = () => asTrimmedString(svc.name) || svc.id;
const stack = () => asTrimmedString(svc.docker?.stack) || '—';
const image = () => asTrimmedString(svc.docker?.image) || '—';
const mode = () => asTrimmedString(svc.docker?.mode) || '—';
const desired = () => svc.docker?.desiredTasks ?? 0;
const running = () => svc.docker?.runningTasks ?? 0;
const update = () => formatUpdate(svc.docker?.serviceUpdate);
const ports = () => formatPorts(svc.docker?.endpointPorts);
const status = () => getSimpleStatusIndicator(svc.status);
<Table class="w-full min-w-[900px] border-collapse text-xs">
<TableHeader class="bg-surface-alt text-muted border-b border-border">
<TableRow class="text-left text-[10px] uppercase tracking-wide">
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.serviceColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.stackColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.imageColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.modeColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.desiredColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.runningColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.updateColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.portsColumnLabel}
</TableHead>
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border-subtle">
<For each={filteredServices()}>
{(svc) => {
const name = () => asTrimmedString(svc.name) || svc.id;
const stack = () => asTrimmedString(svc.docker?.stack) || '—';
const image = () => asTrimmedString(svc.docker?.image) || '—';
const mode = () => asTrimmedString(svc.docker?.mode) || '—';
const desired = () => svc.docker?.desiredTasks ?? 0;
const running = () => svc.docker?.runningTasks ?? 0;
const update = () => formatUpdate(svc.docker?.serviceUpdate);
const ports = () => formatPorts(svc.docker?.endpointPorts);
const status = () => getSimpleStatusIndicator(svc.status);
return (
<TableRow class="hover:bg-surface-hover">
<TableCell class="px-3 py-2">
<div class="flex items-center gap-2 min-w-0">
<StatusDot
size="sm"
variant={status().variant}
title={svc.status || 'unknown'}
ariaHidden
/>
<span
class="font-semibold text-base-content truncate"
title={name()}
>
{name()}
</span>
</div>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">{stack()}</TableCell>
<TableCell class="px-3 py-2 text-base-content truncate" title={image()}>
{image()}
</TableCell>
<TableCell class="px-3 py-2 text-base-content">{mode()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{desired()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{running()}</TableCell>
<TableCell
class="px-3 py-2 text-base-content truncate"
title={update()}
>
{update()}
</TableCell>
<TableCell class="px-3 py-2 text-base-content truncate" title={ports()}>
{ports()}
</TableCell>
</TableRow>
);
}}
</For>
</TableBody>
</Table>
</div>
return (
<TableRow class="hover:bg-surface-hover">
<TableCell class="px-3 py-2">
<div class="flex items-center gap-2 min-w-0">
<StatusDot
size="sm"
variant={status().variant}
title={svc.status || 'unknown'}
ariaHidden
/>
<span class="font-semibold text-base-content truncate" title={name()}>
{name()}
</span>
</div>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">{stack()}</TableCell>
<TableCell class="px-3 py-2 text-base-content truncate" title={image()}>
{image()}
</TableCell>
<TableCell class="px-3 py-2 text-base-content">{mode()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{desired()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{running()}</TableCell>
<TableCell class="px-3 py-2 text-base-content truncate" title={update()}>
{update()}
</TableCell>
<TableCell class="px-3 py-2 text-base-content truncate" title={ports()}>
{ports()}
</TableCell>
</TableRow>
);
}}
</For>
</TableBody>
</Table>
</Card>
</Show>
}

View file

@ -226,83 +226,78 @@ export const K8sDeploymentsDrawer: Component<{
}
>
<Card padding="none" tone="card" class="overflow-hidden">
<div class="overflow-x-auto">
<Table class="w-full min-w-[760px] border-collapse text-xs">
<TableHeader class="bg-surface-alt text-muted border-b border-border">
<TableRow class="text-left text-[10px] uppercase tracking-wide">
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.deploymentColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.namespaceColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.desiredColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.updatedColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.readyColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.availableColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.actionsColumnLabel}
</TableHead>
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border-subtle">
<For each={filteredDeployments()}>
{(dep) => {
const name = () => asTrimmedString(dep.name) || dep.id;
const ns = () => asTrimmedString(dep.kubernetes?.namespace) || '—';
const desired = () => dep.kubernetes?.desiredReplicas ?? 0;
const updated = () => dep.kubernetes?.updatedReplicas ?? 0;
const ready = () => dep.kubernetes?.readyReplicas ?? 0;
const available = () => dep.kubernetes?.availableReplicas ?? 0;
const status = () => getSimpleStatusIndicator(dep.status);
<Table class="w-full min-w-[760px] border-collapse text-xs">
<TableHeader class="bg-surface-alt text-muted border-b border-border">
<TableRow class="text-left text-[10px] uppercase tracking-wide">
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.deploymentColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.namespaceColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.desiredColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.updatedColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.readyColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.availableColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.actionsColumnLabel}
</TableHead>
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border-subtle">
<For each={filteredDeployments()}>
{(dep) => {
const name = () => asTrimmedString(dep.name) || dep.id;
const ns = () => asTrimmedString(dep.kubernetes?.namespace) || '—';
const desired = () => dep.kubernetes?.desiredReplicas ?? 0;
const updated = () => dep.kubernetes?.updatedReplicas ?? 0;
const ready = () => dep.kubernetes?.readyReplicas ?? 0;
const available = () => dep.kubernetes?.availableReplicas ?? 0;
const status = () => getSimpleStatusIndicator(dep.status);
return (
<TableRow class="hover:bg-surface-hover">
<TableCell class="px-3 py-2">
<div class="flex items-center gap-2 min-w-0">
<StatusDot
size="sm"
variant={status().variant}
title={dep.status || 'unknown'}
ariaHidden
/>
<span
class="font-semibold text-base-content truncate"
title={name()}
>
{name()}
</span>
</div>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">{ns()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{desired()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{updated()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{ready()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{available()}</TableCell>
<TableCell class="px-3 py-2">
<button
type="button"
onClick={() => openPods(dep.kubernetes?.namespace)}
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
>
{drawerPresentation.viewPodsLabel}
</button>
</TableCell>
</TableRow>
);
}}
</For>
</TableBody>
</Table>
</div>
return (
<TableRow class="hover:bg-surface-hover">
<TableCell class="px-3 py-2">
<div class="flex items-center gap-2 min-w-0">
<StatusDot
size="sm"
variant={status().variant}
title={dep.status || 'unknown'}
ariaHidden
/>
<span class="font-semibold text-base-content truncate" title={name()}>
{name()}
</span>
</div>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">{ns()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{desired()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{updated()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{ready()}</TableCell>
<TableCell class="px-3 py-2 text-base-content">{available()}</TableCell>
<TableCell class="px-3 py-2">
<button
type="button"
onClick={() => openPods(dep.kubernetes?.namespace)}
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
>
{drawerPresentation.viewPodsLabel}
</button>
</TableCell>
</TableRow>
);
}}
</For>
</TableBody>
</Table>
</Card>
</Show>
}

View file

@ -144,97 +144,93 @@ export const K8sNamespacesDrawer: Component<{
}
>
<Card padding="none" tone="card" class="overflow-hidden">
<div class="overflow-x-auto">
<Table class="w-full min-w-[720px] border-collapse text-xs">
<TableHeader class="bg-surface-alt text-muted border-b border-border">
<TableRow class="text-left text-[10px] uppercase tracking-wide">
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.namespaceColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.podsColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.deploymentsColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.actionsColumnLabel}
</TableHead>
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border-subtle">
<For each={filteredRows()}>
{(row) => {
const podIndicator = () => getNamespaceCountsIndicator(row.pods);
return (
<TableRow class="hover:bg-surface-hover">
<TableCell class="px-3 py-2">
<div class="flex items-center gap-2 min-w-0">
<StatusDot
size="sm"
variant={podIndicator().variant}
title={podIndicator().label}
ariaHidden
/>
<span
class="font-semibold text-base-content truncate"
title={row.namespace}
>
{row.namespace}
</span>
</div>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">
<span class="font-semibold">{formatInteger(row.pods.total)}</span>
<span class="ml-2 text-[11px] text-muted">
{row.pods.offline > 0
? `${formatInteger(row.pods.offline)} off`
: ''}
{row.pods.warning > 0
? `${row.pods.offline > 0 ? ' · ' : ''}${formatInteger(row.pods.warning)} warn`
: ''}
<Table class="w-full min-w-[720px] border-collapse text-xs">
<TableHeader class="bg-surface-alt text-muted border-b border-border">
<TableRow class="text-left text-[10px] uppercase tracking-wide">
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.namespaceColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.podsColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.deploymentsColumnLabel}
</TableHead>
<TableHead class="px-3 py-2 font-medium">
{drawerPresentation.actionsColumnLabel}
</TableHead>
</TableRow>
</TableHeader>
<TableBody class="divide-y divide-border-subtle">
<For each={filteredRows()}>
{(row) => {
const podIndicator = () => getNamespaceCountsIndicator(row.pods);
return (
<TableRow class="hover:bg-surface-hover">
<TableCell class="px-3 py-2">
<div class="flex items-center gap-2 min-w-0">
<StatusDot
size="sm"
variant={podIndicator().variant}
title={podIndicator().label}
ariaHidden
/>
<span
class="font-semibold text-base-content truncate"
title={row.namespace}
>
{row.namespace}
</span>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">
<span class="font-semibold">
{formatInteger(row.deployments.total)}
</span>
<span class="ml-2 text-[11px] text-muted">
{row.deployments.warning > 0
? `${formatInteger(row.deployments.warning)} warn`
: ''}
{row.deployments.offline > 0
? `${row.deployments.warning > 0 ? ' · ' : ''}${formatInteger(row.deployments.offline)} off`
: ''}
</span>
</TableCell>
<TableCell class="px-3 py-2">
<div class="flex flex-wrap items-center gap-2">
</div>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">
<span class="font-semibold">{formatInteger(row.pods.total)}</span>
<span class="ml-2 text-[11px] text-muted">
{row.pods.offline > 0 ? `${formatInteger(row.pods.offline)} off` : ''}
{row.pods.warning > 0
? `${row.pods.offline > 0 ? ' · ' : ''}${formatInteger(row.pods.warning)} warn`
: ''}
</span>
</TableCell>
<TableCell class="px-3 py-2 text-base-content">
<span class="font-semibold">
{formatInteger(row.deployments.total)}
</span>
<span class="ml-2 text-[11px] text-muted">
{row.deployments.warning > 0
? `${formatInteger(row.deployments.warning)} warn`
: ''}
{row.deployments.offline > 0
? `${row.deployments.warning > 0 ? ' · ' : ''}${formatInteger(row.deployments.offline)} off`
: ''}
</span>
</TableCell>
<TableCell class="px-3 py-2">
<div class="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => openPods(row.namespace)}
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
>
{drawerPresentation.openPodsLabel}
</button>
<Show when={props.onOpenDeployments}>
<button
type="button"
onClick={() => openPods(row.namespace)}
onClick={() => props.onOpenDeployments?.(row.namespace)}
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
>
{drawerPresentation.openPodsLabel}
{drawerPresentation.viewDeploymentsLabel}
</button>
<Show when={props.onOpenDeployments}>
<button
type="button"
onClick={() => props.onOpenDeployments?.(row.namespace)}
class="rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-semibold text-base-content shadow-sm hover:bg-surface-hover"
>
{drawerPresentation.viewDeploymentsLabel}
</button>
</Show>
</div>
</TableCell>
</TableRow>
);
}}
</For>
</TableBody>
</Table>
</div>
</Show>
</div>
</TableCell>
</TableRow>
);
}}
</For>
</TableBody>
</Table>
</Card>
</Show>
</Show>

View file

@ -131,6 +131,9 @@ import unifiedResourceHostTableCardSource from '@/components/Infrastructure/Unif
import unifiedResourceServiceInfrastructureCardSource from '@/components/Infrastructure/UnifiedResourceServiceInfrastructureCard.tsx?raw';
import unifiedResourcePBSTableSectionSource from '@/components/Infrastructure/UnifiedResourcePBSTableSection.tsx?raw';
import unifiedResourcePMGTableSectionSource from '@/components/Infrastructure/UnifiedResourcePMGTableSection.tsx?raw';
import swarmServicesDrawerSource from '@/components/Docker/SwarmServicesDrawer.tsx?raw';
import k8sDeploymentsDrawerSource from '@/components/Kubernetes/K8sDeploymentsDrawer.tsx?raw';
import k8sNamespacesDrawerSource from '@/components/Kubernetes/K8sNamespacesDrawer.tsx?raw';
import nodeGroupHeaderSource from '@/components/shared/NodeGroupHeader.tsx?raw';
import storageGroupRowSource from '@/components/Storage/StorageGroupRow.tsx?raw';
import storageGroupPresentationSource from '@/features/storageBackups/groupPresentation.ts?raw';
@ -528,6 +531,9 @@ describe('shared primitive guardrails', () => {
diskListSource,
infrastructureSourceManagerSource,
configuredNodeTablesSource,
swarmServicesDrawerSource,
k8sDeploymentsDrawerSource,
k8sNamespacesDrawerSource,
]) {
expect(source).toContain('<Table');
expect(source).not.toContain('<div class="overflow-x-auto">');

View file

@ -2422,6 +2422,8 @@ describe('frontend resource type boundaries', () => {
expect(swarmServicesDrawerSource).toContain('getSwarmDrawerPresentation');
expect(swarmServicesDrawerSource).toContain('getSwarmServicesEmptyState');
expect(swarmServicesDrawerSource).toContain('getSwarmServicesLoadingState');
expect(swarmServicesDrawerSource).toMatch(/<Table(?:\s|>)/);
expect(swarmServicesDrawerSource).not.toContain('<div class="overflow-x-auto">');
expect(swarmServicesDrawerSource).not.toContain('const statusTone =');
expect(swarmServicesDrawerSource).not.toContain('No Swarm cluster detected');
expect(swarmServicesDrawerSource).not.toContain('Search services...');
@ -2436,6 +2438,8 @@ describe('frontend resource type boundaries', () => {
expect(k8sDeploymentsDrawerSource).toContain('getK8sDeploymentsDrawerPresentation');
expect(k8sDeploymentsDrawerSource).toContain('getK8sDeploymentsEmptyState');
expect(k8sDeploymentsDrawerSource).toContain('getK8sDeploymentsLoadingState');
expect(k8sDeploymentsDrawerSource).toMatch(/<Table(?:\s|>)/);
expect(k8sDeploymentsDrawerSource).not.toContain('<div class="overflow-x-auto">');
expect(k8sDeploymentPresentationSource).toContain('getAllFilterOptionLabel');
expect(k8sDeploymentsDrawerSource).not.toContain('const statusTone =');
expect(k8sDeploymentsDrawerSource).not.toContain('Desired state controllers (not Pods)');
@ -2463,6 +2467,8 @@ describe('frontend resource type boundaries', () => {
expect(k8sNamespacesDrawerSource).toContain('getK8sNamespacesLoadingState');
expect(k8sNamespacesDrawerSource).toContain('getK8sNamespacesFailureState');
expect(k8sNamespacesDrawerSource).toContain('<StatusDot');
expect(k8sNamespacesDrawerSource).toMatch(/<Table(?:\s|>)/);
expect(k8sNamespacesDrawerSource).not.toContain('<div class="overflow-x-auto">');
expect(k8sNamespacesDrawerSource).not.toContain('const statusTone =');
expect(k8sNamespacesDrawerSource).not.toContain('Scope Pods and Deployments by namespace');
expect(k8sNamespacesDrawerSource).not.toContain('Search namespaces...');

View file

@ -774,6 +774,7 @@ class CanonicalCompletionGuardTest(unittest.TestCase):
"frontend-modern/src/routing/__tests__/resourceLinks.test.ts",
"frontend-modern/src/stores/__tests__/websocket-unified.test.ts",
"frontend-modern/src/types/__tests__/resource.test.ts",
"frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts",
"internal/unifiedresources/code_standards_test.go",
],
}

View file

@ -4771,6 +4771,7 @@ class SubsystemLookupTest(unittest.TestCase):
"frontend-modern/src/routing/__tests__/resourceLinks.test.ts",
"frontend-modern/src/stores/__tests__/websocket-unified.test.ts",
"frontend-modern/src/types/__tests__/resource.test.ts",
"frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts",
"internal/unifiedresources/code_standards_test.go",
],
)