mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 08:33:03 +00:00
Normalize Kubernetes drawer tables
This commit is contained in:
@@ -16,6 +16,12 @@ import {
|
||||
} from '@/components/shared/Table';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { buildKubernetesPath } from '@/routing/resourceLinks';
|
||||
import {
|
||||
PLATFORM_TABLE_BODY_CLASS,
|
||||
PLATFORM_TABLE_HEADER_ROW_CLASS,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import {
|
||||
getK8sDeploymentsDrawerPresentation,
|
||||
getK8sDeploymentsEmptyState,
|
||||
@@ -224,33 +230,41 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
}
|
||||
>
|
||||
<Card padding="none" tone="card" class="overflow-hidden">
|
||||
<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">
|
||||
<Table class="min-w-full table-fixed text-xs md:min-w-[820px]">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('name')} md:w-[26%]`}>
|
||||
{drawerPresentation.deploymentColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[16%]`}>
|
||||
{drawerPresentation.namespaceColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[10%]`}
|
||||
>
|
||||
{drawerPresentation.desiredColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[10%]`}
|
||||
>
|
||||
{drawerPresentation.updatedColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[10%]`}
|
||||
>
|
||||
{drawerPresentation.readyColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[10%]`}
|
||||
>
|
||||
{drawerPresentation.availableColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[18%]`}>
|
||||
{drawerPresentation.actionsColumnLabel}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="divide-y divide-border-subtle">
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={filteredDeployments()}>
|
||||
{(dep) => {
|
||||
const name = () => asTrimmedString(dep.name) || dep.id;
|
||||
@@ -262,8 +276,8 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
const status = () => getSimpleStatusIndicator(dep.status);
|
||||
|
||||
return (
|
||||
<TableRow class="hover:bg-surface-hover">
|
||||
<TableCell class="px-3 py-2">
|
||||
<TableRow class="text-[11px] sm:text-xs">
|
||||
<TableCell class={getPlatformTableCellClassForKind('name')}>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
@@ -276,12 +290,34 @@ export const K8sDeploymentsDrawer: Component<{
|
||||
</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">
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
<span class="block truncate" title={ns()}>
|
||||
{ns()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{desired()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{updated()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{ready()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content tabular-nums`}
|
||||
>
|
||||
{available()}
|
||||
</TableCell>
|
||||
<TableCell class={getPlatformTableCellClassForKind('text')}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openPods(dep.kubernetes?.namespace)}
|
||||
|
||||
@@ -15,6 +15,12 @@ import {
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { buildKubernetesPath } from '@/routing/resourceLinks';
|
||||
import {
|
||||
PLATFORM_TABLE_BODY_CLASS,
|
||||
PLATFORM_TABLE_HEADER_ROW_CLASS,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import {
|
||||
getK8sNamespacesDrawerPresentation,
|
||||
getK8sNamespacesEmptyState,
|
||||
@@ -144,30 +150,34 @@ export const K8sNamespacesDrawer: Component<{
|
||||
}
|
||||
>
|
||||
<Card padding="none" tone="card" class="overflow-hidden">
|
||||
<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">
|
||||
<Table class="min-w-full table-fixed text-xs md:min-w-[760px]">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('name')} md:w-[22%]`}>
|
||||
{drawerPresentation.namespaceColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[18%]`}
|
||||
>
|
||||
{drawerPresentation.podsColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[22%]`}
|
||||
>
|
||||
{drawerPresentation.deploymentsColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[38%]`}>
|
||||
{drawerPresentation.actionsColumnLabel}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="divide-y divide-border-subtle">
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={filteredRows()}>
|
||||
{(row) => {
|
||||
const podIndicator = () => getNamespaceCountsIndicator(row.pods);
|
||||
return (
|
||||
<TableRow class="hover:bg-surface-hover">
|
||||
<TableCell class="px-3 py-2">
|
||||
<TableRow class="text-[11px] sm:text-xs">
|
||||
<TableCell class={getPlatformTableCellClassForKind('name')}>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<StatusDot
|
||||
size="sm"
|
||||
@@ -183,8 +193,12 @@ export const K8sNamespacesDrawer: Component<{
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content">
|
||||
<span class="font-semibold">{formatInteger(row.pods.total)}</span>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
<span class="font-semibold tabular-nums">
|
||||
{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
|
||||
@@ -192,8 +206,10 @@ export const K8sNamespacesDrawer: Component<{
|
||||
: ''}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content">
|
||||
<span class="font-semibold">
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
<span class="font-semibold tabular-nums">
|
||||
{formatInteger(row.deployments.total)}
|
||||
</span>
|
||||
<span class="ml-2 text-[11px] text-muted">
|
||||
@@ -205,7 +221,7 @@ export const K8sNamespacesDrawer: Component<{
|
||||
: ''}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2">
|
||||
<TableCell class={getPlatformTableCellClassForKind('text')}>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -36,7 +36,7 @@ vi.mock('@/components/shared/Table', () => ({
|
||||
{props.children}
|
||||
</table>
|
||||
),
|
||||
TableHeader: (props: any) => <thead>{props.children}</thead>,
|
||||
TableHeader: (props: any) => <thead class={props.class}>{props.children}</thead>,
|
||||
TableBody: (props: any) => <tbody class={props.class}>{props.children}</tbody>,
|
||||
TableRow: (props: any) => <tr class={props.class}>{props.children}</tr>,
|
||||
TableHead: (props: any) => <th class={props.class}>{props.children}</th>,
|
||||
@@ -190,6 +190,21 @@ describe('K8sDeploymentsDrawer', () => {
|
||||
expect(screen.getByText('2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses the shared platform table spacing and divider contract', async () => {
|
||||
mockApiResponse([makeDeployment({ id: 'dep-1', name: 'web-frontend' })]);
|
||||
const { container } = render(() => <K8sDeploymentsDrawer cluster="c1" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('web-frontend')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('table')).toHaveClass('table-fixed');
|
||||
expect(container.querySelector('thead tr')).toHaveClass('bg-surface-alt');
|
||||
expect(container.querySelector('tbody')).toHaveClass('divide-y', 'divide-border');
|
||||
expect(container.querySelector('tbody')).not.toHaveClass('divide-border-subtle');
|
||||
expect(container.querySelector('th')).toHaveClass('px-1.5', 'py-0.5');
|
||||
});
|
||||
|
||||
it('uses deployment id as name when name is empty', async () => {
|
||||
const dep = makeDeployment({ id: 'dep-abc', name: '' });
|
||||
mockApiResponse([dep]);
|
||||
|
||||
@@ -61,6 +61,21 @@ describe('K8sNamespacesDrawer', () => {
|
||||
expect(navigateMock).toHaveBeenCalledWith('/kubernetes/pods');
|
||||
});
|
||||
|
||||
it('uses the shared platform table spacing and divider contract', async () => {
|
||||
apiFetchJSONMock.mockResolvedValueOnce(makeResponse([{ namespace: 'default' }]));
|
||||
const { container } = render(() => <K8sNamespacesDrawer cluster="cluster-a" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('default')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(container.querySelector('table')).toHaveClass('table-fixed');
|
||||
expect(container.querySelector('thead tr')).toHaveClass('bg-surface-alt');
|
||||
expect(container.querySelector('tbody')).toHaveClass('divide-y', 'divide-border');
|
||||
expect(container.querySelector('tbody')).not.toHaveClass('divide-border-subtle');
|
||||
expect(container.querySelector('th')).toHaveClass('px-1.5', 'py-0.5');
|
||||
});
|
||||
|
||||
it('does not navigate when cluster is empty', async () => {
|
||||
render(() => <K8sNamespacesDrawer cluster="" />);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user