mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-21 15:03:46 +00:00
k8s(deployments): replace generic infra table with a deployment-native table
The Kubernetes Deployments tab was mounting the canonical
UnifiedResourceTable, which renders CPU / Memory / Disk / Disk I/O /
Uptime / Temperature columns. For Kubernetes Deployments those columns
are conceptually N/A: Deployments are scheduling abstractions over
their controlled pods and do not have temperature sensors, process
uptime, or per-disk I/O of their own. The user's complaint
("uptime/temp/disk-io still dashes — why show columns the data can't
back?") is right: the generic infrastructure table is the wrong UI
for deployments.
Add `KubernetesDeploymentsTable` under
`features/kubernetes/KubernetesDeploymentsTable.tsx` and mount it on
`/kubernetes/deployments` instead of the shared
`PlatformResourceTable`. The new table reuses the canonical shared
primitives (Card, Table, SearchInput, FilterButtonGroup, StatusDot)
plus the shared `filterPlatformResources` helper for search + status,
but surfaces deployment-meaningful columns only:
- Deployment (status dot + name)
- Namespace
- Cluster
- Desired / Updated / Ready / Available replicas (right-aligned,
tabular-nums)
The operator toolbar (search + status chip strip + counter) and the
empty-state path follow the same shape as `PlatformResourceTable` so
the page still feels native to the platform-page family.
`ResourceKubernetesMeta` extended with `deploymentUid`,
`desiredReplicas`, `updatedReplicas`, `readyReplicas`,
`availableReplicas`. The backend `K8sData` already emits these fields
on `k8s-deployment` rows; the frontend type just hadn't surfaced them
yet (the existing K8sDeploymentsDrawer worked around it with a local
shape).
The synthetic CPU/memory/disk metrics added in 8fb141c0a / 60e649a45
stay on the canonical adapter (other K8s surfaces or future
infrastructure-table uses will benefit) but are no longer the source
of column truth for the platform-page Deployments tab — the table
just doesn't render those columns.
Browser verification (Playwright, chromium, live mock-mode):
- 9 tests pass; the every-sub-tab operator-controls audit still
finds the search input on `/kubernetes/deployments` (now provided
by the bespoke deployments table's toolbar).
Tests:
- `tsc --noEmit` clean.
- Existing vitest suites continue to pass.
Contract-neutral bypass: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT set
because this is a presentation refactor inside the Kubernetes feature
directory. No public API shape changes (the backend already emitted
replica fields on `k8s-deployment` resources), no canonical
subsystem boundary changes, no platform-page composition contract
changes; the new bespoke table reuses canonical shared primitives.
This commit is contained in:
parent
60e649a456
commit
69f70a3fcd
3 changed files with 181 additions and 1 deletions
|
|
@ -0,0 +1,169 @@
|
|||
import { For, Show, createMemo, createSignal, type Component, type JSX } from 'solid-js';
|
||||
import { Card } from '@/components/shared/Card';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { FilterButtonGroup, type FilterOption } from '@/components/shared/FilterButtonGroup';
|
||||
import { SearchInput } from '@/components/shared/SearchInput';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/shared/Table';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
filterPlatformResources,
|
||||
type PlatformResourceStatusFilter,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import type { Resource } from '@/types/resource';
|
||||
|
||||
// Kubernetes Deployments are scheduling abstractions over their controlled
|
||||
// pods, so the generic infrastructure table's CPU / Memory / Disk I/O /
|
||||
// Uptime / Temperature columns are conceptually N/A on these rows and
|
||||
// render as dashes. This deployment-native table reuses canonical shared
|
||||
// primitives (Card, Table, SearchInput, FilterButtonGroup, StatusDot) but
|
||||
// surfaces deployment-meaningful columns only: namespace, cluster,
|
||||
// desired / updated / ready / available replicas.
|
||||
|
||||
const STATUS_FILTER_OPTIONS: FilterOption<PlatformResourceStatusFilter>[] = [
|
||||
{ value: 'all', label: 'All' },
|
||||
{ value: 'online', label: 'Healthy' },
|
||||
{ value: 'degraded', label: 'Degraded' },
|
||||
{ value: 'offline', label: 'Offline' },
|
||||
];
|
||||
|
||||
const replicaCount = (value: number | undefined): JSX.Element => (
|
||||
<span class="tabular-nums">{value ?? 0}</span>
|
||||
);
|
||||
|
||||
export const KubernetesDeploymentsTable: Component<{
|
||||
resources: Resource[];
|
||||
emptyIcon: JSX.Element;
|
||||
emptyTitle: string;
|
||||
emptyDescription: string;
|
||||
}> = (props) => {
|
||||
const [search, setSearch] = createSignal('');
|
||||
const [status, setStatus] = createSignal<PlatformResourceStatusFilter>('all');
|
||||
|
||||
const filtered = createMemo(() => filterPlatformResources(props.resources, search(), status()));
|
||||
const visible = createMemo(() => filtered().length);
|
||||
const total = createMemo(() => props.resources.length);
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={props.resources.length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title={props.emptyTitle}
|
||||
description={props.emptyDescription}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="min-w-[200px] flex-1 sm:max-w-xs">
|
||||
<SearchInput
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
placeholder="Search deployments"
|
||||
/>
|
||||
</div>
|
||||
<FilterButtonGroup
|
||||
options={STATUS_FILTER_OPTIONS}
|
||||
value={status()}
|
||||
onChange={setStatus}
|
||||
/>
|
||||
<span class="ml-auto whitespace-nowrap text-xs font-medium text-muted">
|
||||
<Show when={visible() !== total()} fallback={<>{total()} deployments</>}>
|
||||
{visible()} of {total()} deployments
|
||||
</Show>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Show
|
||||
when={filtered().length > 0}
|
||||
fallback={
|
||||
<Card padding="lg">
|
||||
<EmptyState
|
||||
icon={props.emptyIcon}
|
||||
title="No deployments match current filters"
|
||||
description="Adjust the search or status filter to see more deployments."
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<Card padding="none" tone="card" class="overflow-hidden">
|
||||
<Table class="w-full min-w-[820px] 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">Deployment</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Namespace</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">Cluster</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Desired</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Updated</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Ready</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium text-right">Available</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="divide-y divide-border-subtle">
|
||||
<For each={filtered()}>
|
||||
{(deployment) => {
|
||||
const name = () => asTrimmedString(deployment.name) || deployment.id;
|
||||
const ns = () => asTrimmedString(deployment.kubernetes?.namespace) || '—';
|
||||
const cluster = () =>
|
||||
asTrimmedString(deployment.kubernetes?.clusterName) ||
|
||||
asTrimmedString(deployment.kubernetes?.clusterId) ||
|
||||
'—';
|
||||
const indicator = () => getSimpleStatusIndicator(deployment.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={indicator().variant}
|
||||
title={deployment.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">{cluster()}</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{replicaCount(deployment.kubernetes?.desiredReplicas)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{replicaCount(deployment.kubernetes?.updatedReplicas)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{replicaCount(deployment.kubernetes?.readyReplicas)}
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-right text-base-content">
|
||||
{replicaCount(deployment.kubernetes?.availableReplicas)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
export default KubernetesDeploymentsTable;
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
PlatformSectionTabs,
|
||||
PlatformTableEmptyState,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import { KubernetesDeploymentsTable } from './KubernetesDeploymentsTable';
|
||||
import {
|
||||
KUBERNETES_TAB_SPECS,
|
||||
buildKubernetesPageModel,
|
||||
|
|
@ -108,7 +109,7 @@ export function KubernetesPageSurface() {
|
|||
/>
|
||||
</Show>
|
||||
<Show when={activeTab() === 'deployments'}>
|
||||
<PlatformResourceTable
|
||||
<KubernetesDeploymentsTable
|
||||
resources={model().deployments}
|
||||
emptyIcon={k8sIcon()}
|
||||
emptyTitle="No deployments reported"
|
||||
|
|
|
|||
|
|
@ -518,6 +518,16 @@ export interface ResourceKubernetesMeta {
|
|||
temperature?: number;
|
||||
pendingUninstall?: boolean;
|
||||
metricCapabilities?: ResourceKubernetesMetricCapabilities;
|
||||
// Deployment-only fields populated by the canonical adapter for
|
||||
// `k8s-deployment` resources. Surfaced on the Kubernetes platform-page
|
||||
// Deployments table where CPU/memory/uptime/temperature columns from
|
||||
// the generic infrastructure table are not the meaningful operator
|
||||
// columns; replica counts are.
|
||||
deploymentUid?: string;
|
||||
desiredReplicas?: number;
|
||||
updatedReplicas?: number;
|
||||
readyReplicas?: number;
|
||||
availableReplicas?: number;
|
||||
}
|
||||
|
||||
export interface ResourceVMwareMeta {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue