Refine Docker container table signals

This commit is contained in:
rcourtman
2026-06-12 09:27:36 +01:00
parent f6a13341a0
commit 25a3755bfc
4 changed files with 73 additions and 24 deletions
@@ -251,8 +251,18 @@ export const DockerContainersTable: Component<DockerNativeTableProps> = (props)
}
return kinds.size > 1;
});
const showRestartColumn = createMemo(() =>
scopedRows().some(
(resource) =>
typeof resource.docker?.restartCount === 'number' && resource.docker.restartCount > 0,
),
);
const visibleColumns = createMemo(() =>
getDockerContainerVisibleColumnsForLayout(layoutMode(), showRuntimeColumn()),
getDockerContainerVisibleColumnsForLayout(
layoutMode(),
showRuntimeColumn(),
showRestartColumn(),
),
);
const visibleColumnIds = createMemo(() => visibleColumns().map((column) => column.id));
const drawerColSpan = createMemo(() => visibleColumns().length);
@@ -335,7 +345,6 @@ export const DockerContainersTable: Component<DockerNativeTableProps> = (props)
const indicator = mapDockerContainerStatus(resource);
const image = () => dockerTextValue(resource.docker?.image);
const state = () => containerState(resource);
const health = () => dockerTextValue(resource.docker?.health);
const runtime = () => runtimeSummary(resource);
const host = () => dockerHostName(resource);
const running = () => isContainerRunning(resource);
@@ -402,16 +411,6 @@ export const DockerContainersTable: Component<DockerNativeTableProps> = (props)
</span>
</TableCell>
);
case 'health':
return (
<TableCell
class={`${getPlatformTableCellClassForKind(column.kind)} text-base-content`}
>
<span class="block max-w-full truncate" title={health()}>
{health()}
</span>
</TableCell>
);
case 'cpu':
return (
<TableCell class={getPlatformTableCellClassForKind(column.kind)}>
@@ -170,12 +170,12 @@ describe('Docker native tables', () => {
expect(screen.getByText('Memory')).toBeInTheDocument();
expect(screen.getByText('Restarts')).toBeInTheDocument();
expect(screen.getByText('Updates')).toBeInTheDocument();
expect(screen.queryByText('Health')).not.toBeInTheDocument();
expect(screen.getByText('edge-web')).toBeInTheDocument();
expect(screen.getByText('edge-01')).toBeInTheDocument();
expect(screen.getByText('docker 27.5.1')).toBeInTheDocument();
expect(screen.getByText('podman 5.2.1')).toBeInTheDocument();
expect(screen.getByText('nginx:latest')).toBeInTheDocument();
expect(screen.getAllByText('healthy').length).toBe(2);
expect(screen.getByText('2')).toBeInTheDocument();
expect(screen.getByText('0.0.0.0:8080->80/tcp')).toBeInTheDocument();
expect(screen.getByText('frontend 172.18.0.2')).toBeInTheDocument();
@@ -236,6 +236,46 @@ describe('Docker native tables', () => {
expect(screen.queryByText('docker 27.5.1')).not.toBeInTheDocument();
});
it('hides the Restarts column when the current containers have no restart signal', () => {
setViewportWidth(1500);
renderInRouter(() => (
<DockerContainersTable
resources={[
makeResource({
id: 'container-1',
type: 'app-container',
name: 'edge-web',
status: 'running',
docker: {
hostname: 'edge-01',
image: 'nginx:latest',
containerState: 'running',
restartCount: 0,
},
}),
makeResource({
id: 'container-2',
type: 'app-container',
name: 'edge-cache',
status: 'running',
docker: {
hostname: 'edge-02',
image: 'redis:7.4',
containerState: 'running',
},
}),
]}
emptyIcon={<span />}
emptyTitle="No containers"
emptyDescription="No containers"
showToolbar={false}
/>
));
expect(screen.queryByText('Restarts')).not.toBeInTheDocument();
});
it('renders actionable Docker container updates with native agent and container IDs', async () => {
renderInRouter(() => (
<DockerContainersTable
@@ -8,7 +8,7 @@ import {
describe('dockerContainerTableModel', () => {
it('keeps the mobile container table on identity, state, live metrics, and update action', () => {
const columns = getDockerContainerVisibleColumnsForLayout('mobile', true);
const columns = getDockerContainerVisibleColumnsForLayout('mobile', true, true);
const ids = columns.map((column) => column.id);
expect(ids).toEqual(['container', 'state', 'cpu', 'memory', 'updates']);
@@ -21,14 +21,26 @@ describe('dockerContainerTableModel', () => {
});
});
it('adds host and restart signal before slower forensic fields on tablet', () => {
it('adds host before slower forensic fields on tablet', () => {
expect(
getDockerContainerVisibleColumnsForLayout('tablet', true).map((column) => column.id),
).toEqual(['container', 'host', 'state', 'cpu', 'memory', 'restarts', 'updates']);
getDockerContainerVisibleColumnsForLayout('tablet', true, false).map((column) => column.id),
).toEqual(['container', 'host', 'state', 'cpu', 'memory', 'updates']);
});
it('adds restarts only when the current row set has restart signal to scan', () => {
const withoutRestarts = getDockerContainerVisibleColumnsForLayout('compact', true, false).map(
(column) => column.id,
);
const withRestarts = getDockerContainerVisibleColumnsForLayout('compact', true, true).map(
(column) => column.id,
);
expect(withoutRestarts).not.toContain('restarts');
expect(withRestarts).toContain('restarts');
});
it('keeps compact desktop scan-focused and hides wide forensic columns', () => {
const columns = getDockerContainerVisibleColumnsForLayout('compact', true);
const columns = getDockerContainerVisibleColumnsForLayout('compact', true, true);
const ids = columns.map((column) => column.id);
expect(ids).toEqual([
@@ -49,10 +61,10 @@ describe('dockerContainerTableModel', () => {
});
it('shows runtime only for mixed Docker and Podman fleets', () => {
const compactIds = getDockerContainerVisibleColumnsForLayout('compact', false).map(
const compactIds = getDockerContainerVisibleColumnsForLayout('compact', false, true).map(
(column) => column.id,
);
const wideIds = getDockerContainerVisibleColumnsForLayout('wide', false).map(
const wideIds = getDockerContainerVisibleColumnsForLayout('wide', false, true).map(
(column) => column.id,
);
@@ -9,7 +9,6 @@ export type DockerContainerTableColumnId =
| 'runtime'
| 'image'
| 'state'
| 'health'
| 'cpu'
| 'memory'
| 'restarts'
@@ -45,7 +44,6 @@ const DOCKER_CONTAINER_COLUMN_MIN_LAYOUT: Record<
image: 'compact',
runtime: 'compact',
ports: 'compact',
health: 'wide',
networks: 'wide',
mounts: 'wide',
};
@@ -56,7 +54,6 @@ const DOCKER_CONTAINER_COLUMNS: DockerContainerTableColumn[] = [
{ id: 'runtime', label: 'Runtime', kind: 'text' },
{ id: 'image', label: 'Image', kind: 'text' },
{ id: 'state', label: 'State', kind: 'text' },
{ id: 'health', label: 'Health', kind: 'text' },
{ id: 'cpu', label: 'CPU', kind: 'metric-bar' },
{ id: 'memory', label: 'Memory', kind: 'metric-bar' },
{ id: 'restarts', label: 'Restarts', kind: 'numeric-value' },
@@ -72,7 +69,6 @@ const DOCKER_CONTAINER_DESKTOP_WIDTHS: Record<DockerContainerTableColumnId, numb
runtime: 7,
image: 16,
state: 6,
health: 6,
cpu: 9,
memory: 10,
restarts: 6,
@@ -121,10 +117,12 @@ const formatPercentage = (value: number): string => `${Number(value.toFixed(4))}
export const getDockerContainerVisibleColumnsForLayout = (
layoutMode: WorkloadTableLayoutMode,
includeRuntime: boolean,
includeRestarts: boolean,
): DockerContainerTableColumn[] => {
const layoutRank = DOCKER_CONTAINER_TABLE_LAYOUT_ORDER[layoutMode];
return DOCKER_CONTAINER_COLUMNS.filter((column) => {
if (column.id === 'runtime' && !includeRuntime) return false;
if (column.id === 'restarts' && !includeRestarts) return false;
return (
DOCKER_CONTAINER_TABLE_LAYOUT_ORDER[DOCKER_CONTAINER_COLUMN_MIN_LAYOUT[column.id]] <=
layoutRank