mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
Promote Kubernetes nodes to platform tab
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -140,7 +140,7 @@ export const KubernetesNodesTable: Component<{
|
||||
>
|
||||
<TableCard class={PLATFORM_TABLE_CARD_CLASS}>
|
||||
<TableCardHeader title={props.title ?? 'Nodes'} />
|
||||
<Table class="min-w-full table-fixed text-xs md:min-w-[1280px]">
|
||||
<Table class="min-w-full table-fixed text-xs md:min-w-[1100px] 2xl:min-w-[1280px]">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
{/*
|
||||
@@ -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.
|
||||
*/}
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('name')} md:w-[15%]`}>
|
||||
Node
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[10%]`}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[10%]`}
|
||||
>
|
||||
Cluster
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[10%]`}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[10%]`}
|
||||
>
|
||||
Roles
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[8%]`}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[8%]`}
|
||||
>
|
||||
Kubelet
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[15%]`}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('text')} hidden md:table-cell md:w-[15%]`}
|
||||
>
|
||||
Runtime
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('metric-bar')} md:w-[11%]`}>
|
||||
@@ -173,10 +182,14 @@ export const KubernetesNodesTable: Component<{
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('metric-bar')} md:w-[11%]`}>
|
||||
Memory
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('numeric-value')} hidden md:table-cell md:w-[6%]`}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} hidden md:table-cell md:w-[6%]`}
|
||||
>
|
||||
Uptime
|
||||
</TableHead>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[14%]`}>
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[14%]`}
|
||||
>
|
||||
Capacity
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
|
||||
@@ -98,6 +98,14 @@ export function KubernetesPageSurface() {
|
||||
<Show when={activeTab() === 'overview'}>
|
||||
<KubernetesOverview model={model} />
|
||||
</Show>
|
||||
<Show when={activeTab() === 'nodes'}>
|
||||
<KubernetesNodesTable
|
||||
resources={model().nodes}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No nodes reported"
|
||||
emptyDescription="Kubernetes nodes appear here as soon as the agent enumerates Node resources."
|
||||
/>
|
||||
</Show>
|
||||
<Show when={activeTab() === 'workloads'}>
|
||||
<KubernetesWorkloads model={model} />
|
||||
</Show>
|
||||
|
||||
+75
-1
@@ -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) => (
|
||||
<div
|
||||
data-testid="kubernetes-workloads-surface"
|
||||
@@ -22,6 +23,14 @@ vi.mock('@/hooks/useUnifiedResources', () => ({
|
||||
useUnifiedResources: (...args: unknown[]) => mockUseUnifiedResources(...args),
|
||||
}));
|
||||
|
||||
vi.mock('@solidjs/router', async () => {
|
||||
const actual = await vi.importActual<typeof import('@solidjs/router')>('@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(() => (
|
||||
<Router>
|
||||
<Route path="/" component={KubernetesPageSurface} />
|
||||
</Router>
|
||||
));
|
||||
|
||||
expect(screen.getByTestId('nodes-table')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('kubernetes-workloads-surface')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user