mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 08:33:03 +00:00
Normalize Docker swarm drawer table
This commit is contained in:
@@ -15,6 +15,12 @@ import {
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import { getSimpleStatusIndicator } from '@/utils/status';
|
||||
import { asTrimmedString } from '@/utils/stringUtils';
|
||||
import {
|
||||
PLATFORM_TABLE_BODY_CLASS,
|
||||
PLATFORM_TABLE_HEADER_ROW_CLASS,
|
||||
getPlatformTableCellClassForKind,
|
||||
getPlatformTableHeadClassForKind,
|
||||
} from '@/features/platformPage/sharedPlatformPage';
|
||||
import {
|
||||
formatSwarmClusterId,
|
||||
formatSwarmClusterSummary,
|
||||
@@ -176,7 +182,8 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
|
||||
const swarm = createMemo(() => props.swarm);
|
||||
const clusterName = createMemo(
|
||||
() => asTrimmedString(swarm()?.clusterName) || asTrimmedString(swarm()?.clusterId) || clusterKey(),
|
||||
() =>
|
||||
asTrimmedString(swarm()?.clusterName) || asTrimmedString(swarm()?.clusterId) || clusterKey(),
|
||||
);
|
||||
const clusterId = createMemo(() => asTrimmedString(swarm()?.clusterId) ?? '');
|
||||
|
||||
@@ -258,36 +265,40 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
}
|
||||
>
|
||||
<Card padding="none" tone="card" class="overflow-hidden">
|
||||
<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">
|
||||
<Table class="min-w-full table-fixed text-xs md:min-w-[960px]">
|
||||
<TableHeader>
|
||||
<TableRow class={PLATFORM_TABLE_HEADER_ROW_CLASS}>
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('name')} md:w-[18%]`}>
|
||||
{drawerPresentation.serviceColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[13%]`}>
|
||||
{drawerPresentation.stackColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[22%]`}>
|
||||
{drawerPresentation.imageColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[10%]`}>
|
||||
{drawerPresentation.modeColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[8%]`}
|
||||
>
|
||||
{drawerPresentation.desiredColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead
|
||||
class={`${getPlatformTableHeadClassForKind('numeric-value')} md:w-[8%]`}
|
||||
>
|
||||
{drawerPresentation.runningColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[12%]`}>
|
||||
{drawerPresentation.updateColumnLabel}
|
||||
</TableHead>
|
||||
<TableHead class="px-3 py-2 font-medium">
|
||||
<TableHead class={`${getPlatformTableHeadClassForKind('text')} md:w-[9%]`}>
|
||||
{drawerPresentation.portsColumnLabel}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody class="divide-y divide-border-subtle">
|
||||
<TableBody class={PLATFORM_TABLE_BODY_CLASS}>
|
||||
<For each={filteredServices()}>
|
||||
{(svc) => {
|
||||
const name = () => asTrimmedString(svc.name) || svc.id;
|
||||
@@ -301,8 +312,8 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
const status = () => getSimpleStatusIndicator(svc.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"
|
||||
@@ -315,18 +326,48 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo
|
||||
</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
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
<span class="block truncate" title={stack()}>
|
||||
{stack()}
|
||||
</span>
|
||||
</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
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
<span class="block truncate" title={image()}>
|
||||
{image()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell class="px-3 py-2 text-base-content truncate" title={ports()}>
|
||||
{ports()}
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
{mode()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
<span class="tabular-nums">{desired()}</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('numeric-value')} text-base-content`}
|
||||
>
|
||||
<span class="tabular-nums">{running()}</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
<span class="block truncate" title={update()}>
|
||||
{update()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
class={`${getPlatformTableCellClassForKind('text')} text-base-content`}
|
||||
>
|
||||
<span class="block truncate font-mono text-[11px]" title={ports()}>
|
||||
{ports()}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,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>,
|
||||
@@ -299,6 +299,21 @@ describe('SwarmServicesDrawer', () => {
|
||||
expect(screen.getByText('2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses the shared platform table spacing and divider contract', async () => {
|
||||
mockApiResponse([makeService({ id: 'svc-1', name: 'web-frontend' })]);
|
||||
const { container } = render(() => <SwarmServicesDrawer 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 service id as name when name is empty', async () => {
|
||||
const svc = makeService({ id: 'svc-abc', name: '' });
|
||||
mockApiResponse([svc]);
|
||||
|
||||
Reference in New Issue
Block a user