diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 73158123d..a88e17f98 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -177,7 +177,9 @@ represented until the collector has a libpod-native source. Kubernetes config tables must preserve the same trust boundary for metadata-only ConfigMap and Secret rows: the shared table wording may indicate metadata-only inventory, but must not imply payload fields were read when the agent used Kubernetes -metadata-only API responses. +metadata-only API responses. Kubernetes Node inventory must also be reachable +through a dedicated native tab, not only the overview stack, while retaining the +shared `PlatformSectionTabs` shell. 1. `frontend-modern/src/components/Settings/APIAccessPanel.tsx` shared with `security-privacy`: the API Access settings intro is both a security/privacy token-management trust surface and a canonical settings-shell presentation boundary. The panel may own shell placement and local action layout, but diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 462031e90..f3bf1bfcc 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -5066,7 +5066,12 @@ "frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx", "frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts", "frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts", + "frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx", "frontend-modern/src/features/kubernetes/KubernetesInventoryTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx", + "frontend-modern/src/features/kubernetes/kubernetesPageModel.ts", + "frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx", "frontend-modern/src/hooks/useUnifiedResources.ts", "frontend-modern/src/routing/resourceLinks.ts", "frontend-modern/src/types/resource.ts", @@ -5194,7 +5199,12 @@ "frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx", "frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts", "frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts", + "frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx", "frontend-modern/src/features/kubernetes/KubernetesInventoryTable.tsx", + "frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx", + "frontend-modern/src/features/kubernetes/kubernetesPageModel.ts", + "frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx", "frontend-modern/src/hooks/useUnifiedResources.ts", "frontend-modern/src/routing/resourceLinks.ts", "frontend-modern/src/types/resource.ts", @@ -5222,7 +5232,10 @@ "frontend-modern/src/features/docker/__tests__/DockerPageSurface.test.tsx", "frontend-modern/src/features/infrastructure/__tests__/infrastructurePageModel.test.ts", "frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts", + "frontend-modern/src/features/kubernetes/__tests__/KubernetesClustersTable.test.tsx", "frontend-modern/src/features/kubernetes/__tests__/KubernetesInventoryTable.test.tsx", + "frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts", + "frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx", "frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts", "frontend-modern/src/routing/__tests__/resourceLinks.test.ts", "frontend-modern/src/stores/__tests__/websocket-unified.test.ts", diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index d33997292..3e5974274 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -124,6 +124,11 @@ cross-source deduplication. 100. `frontend-modern/src/features/agents/AgentsMachinesTable.tsx` 101. `internal/platformsupport/manifest_generated.go` 102. `frontend-modern/src/features/kubernetes/KubernetesInventoryTable.tsx` +103. `frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx` +104. `frontend-modern/src/features/kubernetes/kubernetesPageModel.ts` +105. `frontend-modern/src/features/kubernetes/KubernetesClustersTable.tsx` +106. `frontend-modern/src/features/kubernetes/KubernetesDeploymentsTable.tsx` +107. `frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx` ## Shared Boundaries @@ -250,7 +255,7 @@ reintroduce false Swarm capability surfaces. libpod-native source can populate a native contract instead of deriving pods from Docker-compatible container labels. Kubernetes inventory likewise projects native API objects as first-class - unified resources: namespaces, Services, ReplicaSets, StatefulSets, + unified resources: Nodes, namespaces, Services, ReplicaSets, StatefulSets, DaemonSets, Jobs, CronJobs, Ingresses, EndpointSlices, NetworkPolicies, PersistentVolumes, PersistentVolumeClaims, StorageClasses, ConfigMaps, Secrets, ServiceAccounts, ResourceQuotas, LimitRanges, @@ -263,6 +268,9 @@ reintroduce false Swarm capability surfaces. carry key names, but Secret values are outside the unified resource contract. Because Secret names and key names can disclose intent, `k8s-secret` policy metadata must classify them as `restricted` with `local-only` routing. + Kubernetes Node inventory is an API-native resource surface, not merely + overview chrome: `/kubernetes/nodes` must expose the bespoke node table while + the overview may still include the same rows for cluster orientation. 2. Add typed accessors and views in `internal/unifiedresources/views.go` 3. Add source ingestion/adaptation in the adapter layer only Frontend resource platform contracts in diff --git a/frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx b/frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx index 0e1a97e8d..aa6a9d9ca 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesNodesTable.tsx @@ -140,7 +140,7 @@ export const KubernetesNodesTable: Component<{ > - +
{/* @@ -149,22 +149,31 @@ export const KubernetesNodesTable: Component<{ -style values, Capacity gets room for "6 cores / 51.0 GB" strings, CPU and Memory bars share an equal slice, and the short-text columns (Cluster, - Roles, Kubelet, Uptime) trim accordingly. Mobile - widths are unchanged. + Roles, Kubelet, Uptime) trim accordingly. Wide + desktop gets extra room without forcing normal desktop + viewports to hide Capacity behind horizontal scroll. */} Node - diff --git a/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx b/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx index a870e8f2a..2c9441812 100644 --- a/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx +++ b/frontend-modern/src/features/kubernetes/KubernetesPageSurface.tsx @@ -98,6 +98,14 @@ export function KubernetesPageSurface() { + + + diff --git a/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx b/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx index 2777da626..4c947334e 100644 --- a/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx +++ b/frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { render, screen } from '@solidjs/testing-library'; import { Route, Router } from '@solidjs/router'; @@ -7,6 +7,7 @@ import { KubernetesPageSurface } from '../KubernetesPageSurface'; const mockUseUnifiedResources = vi.fn(); const mockUseWorkloadsState = vi.fn(); +const mockPathname = vi.hoisted(() => vi.fn(() => '/')); const mockWorkloadsSurface = vi.fn((props: WorkloadsSurfaceProps) => (
({ useUnifiedResources: (...args: unknown[]) => mockUseUnifiedResources(...args), })); +vi.mock('@solidjs/router', async () => { + const actual = await vi.importActual('@solidjs/router'); + return { + ...actual, + useLocation: () => ({ pathname: mockPathname() }), + }; +}); + vi.mock('@/components/Workloads/useWorkloadsState', () => ({ useWorkloadsState: (...args: unknown[]) => mockUseWorkloadsState(...args), })); @@ -50,6 +59,14 @@ vi.mock('../KubernetesDeploymentsTable', () => ({ })); describe('KubernetesPageSurface contract', () => { + beforeEach(() => { + mockPathname.mockReturnValue('/'); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + it('pins the embedded workloads surface to the pod-native Kubernetes columns', () => { mockUseUnifiedResources.mockReturnValue({ resources: () => [ @@ -129,4 +146,61 @@ describe('KubernetesPageSurface contract', () => { }), ); }); + + it('promotes Kubernetes node inventory to a dedicated API-native tab', () => { + mockPathname.mockReturnValue('/kubernetes/nodes'); + mockUseUnifiedResources.mockReturnValue({ + resources: () => [ + { + id: 'node-1', + type: 'k8s-node', + name: 'worker-1', + displayName: 'worker-1', + platformId: 'cluster-1', + platformType: 'kubernetes', + sourceType: 'agent', + sources: ['kubernetes'], + status: 'online', + lastSeen: 1_700_000_000_000, + }, + ], + loading: () => false, + error: () => null, + refetch: vi.fn(), + }); + mockUseWorkloadsState.mockReturnValue({ + allGuests: () => [], + clearPinnedSummaryScope: vi.fn(), + containerRuntimeFilterConfig: () => undefined, + focusedSummaryWorkloadGroupId: () => null, + groupingMode: () => 'grouped', + handleBeforeAutoFocus: vi.fn(), + search: () => '', + selectedGuestId: () => null, + setGroupingMode: vi.fn(), + setMetricDisplayMode: vi.fn(), + setSearch: vi.fn(), + setSortDirection: vi.fn(), + setSortKey: vi.fn(), + setStatusMode: vi.fn(), + setViewMode: vi.fn(), + setWorkloadMetricHistoryRange: vi.fn(), + statusMode: () => 'all', + surfaceConnected: () => false, + surfaceInitialDataReceived: () => false, + viewMode: () => 'pod', + workloadMetricDisplayMode: () => 'bars', + workloadMetricHistoryRange: () => '1h', + workloadsFilterColumnVisibility: () => undefined, + }); + + render(() => ( + + + + )); + + expect(screen.getByTestId('nodes-table')).toBeInTheDocument(); + expect(screen.queryByTestId('kubernetes-workloads-surface')).not.toBeInTheDocument(); + }); }); diff --git a/frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts b/frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts index 43ce47e86..b5dc35062 100644 --- a/frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts +++ b/frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts @@ -18,6 +18,7 @@ describe('kubernetesPageModel', () => { it('declares API-native Kubernetes sections', () => { expect(KUBERNETES_TAB_SPECS.map((tab) => tab.id)).toEqual([ 'overview', + 'nodes', 'workloads', 'services', 'storage', diff --git a/frontend-modern/src/features/kubernetes/kubernetesPageModel.ts b/frontend-modern/src/features/kubernetes/kubernetesPageModel.ts index c007763f2..9327fee7e 100644 --- a/frontend-modern/src/features/kubernetes/kubernetesPageModel.ts +++ b/frontend-modern/src/features/kubernetes/kubernetesPageModel.ts @@ -3,6 +3,7 @@ import { resolveResourcePlatformType } from '@/utils/sourcePlatforms'; export type KubernetesPageTabId = | 'overview' + | 'nodes' | 'workloads' | 'services' | 'storage' @@ -20,6 +21,7 @@ export type KubernetesTabSpec = { export const KUBERNETES_TAB_SPECS: readonly KubernetesTabSpec[] = [ { id: 'overview', label: 'Overview', path: '/kubernetes/overview' }, + { id: 'nodes', label: 'Nodes', path: '/kubernetes/nodes' }, { id: 'workloads', label: 'Workloads', path: '/kubernetes/workloads' }, { id: 'services', label: 'Services', path: '/kubernetes/services' }, { id: 'storage', label: 'Storage', path: '/kubernetes/storage' }, diff --git a/scripts/release_control/canonical_completion_guard_test.py b/scripts/release_control/canonical_completion_guard_test.py index cb684aa9d..a083d99d7 100644 --- a/scripts/release_control/canonical_completion_guard_test.py +++ b/scripts/release_control/canonical_completion_guard_test.py @@ -772,7 +772,10 @@ class CanonicalCompletionGuardTest(unittest.TestCase): "frontend-modern/src/features/docker/__tests__/dockerPageModel.test.ts", "frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts", "frontend-modern/src/features/infrastructure/__tests__/infrastructurePageModel.test.ts", + "frontend-modern/src/features/kubernetes/__tests__/KubernetesClustersTable.test.tsx", "frontend-modern/src/features/kubernetes/__tests__/KubernetesInventoryTable.test.tsx", + "frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx", + "frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts", "frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts", "frontend-modern/src/routing/__tests__/resourceLinks.test.ts", "frontend-modern/src/stores/__tests__/websocket-unified.test.ts", diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 0a5408e4e..9b7c19bee 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -4810,7 +4810,10 @@ class SubsystemLookupTest(unittest.TestCase): "frontend-modern/src/features/docker/__tests__/dockerPageModel.test.ts", "frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts", "frontend-modern/src/features/infrastructure/__tests__/infrastructurePageModel.test.ts", + "frontend-modern/src/features/kubernetes/__tests__/KubernetesClustersTable.test.tsx", "frontend-modern/src/features/kubernetes/__tests__/KubernetesInventoryTable.test.tsx", + "frontend-modern/src/features/kubernetes/__tests__/KubernetesPageSurface.contract.test.tsx", + "frontend-modern/src/features/kubernetes/__tests__/kubernetesPageModel.test.ts", "frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts", "frontend-modern/src/routing/__tests__/resourceLinks.test.ts", "frontend-modern/src/stores/__tests__/websocket-unified.test.ts",