Share app-container column config between Docker and TrueNAS

TrueNAS apps are Docker containers running under the embedded daemon, so
the apps table on the TrueNAS overview should default to the same column
set as the Docker / Podman containers table: writable layer hidden by
default, tags hidden by default, and Net I/O and Disk I/O hidden when no
container in the set reports telemetry. Previously the TrueNAS surface
rendered the workload table with raw defaults, which exposed an empty
"Disk" column and an always-visible "Disk I/O" column that no real
TrueNAS app populates.

Extract APP_CONTAINER_BASE_DEFAULT_HIDDEN_COLUMNS,
APP_CONTAINER_COLUMN_LABEL_OVERRIDES, and the telemetry-aware
buildAppContainerDefaultHiddenColumnIds into a shared
appContainerColumns module under features/platformPage. The Docker page
keeps its context: 'Host' relabel on top of the shared overrides; the
TrueNAS page now scopes its column-visibility prefs under
truenas-app-containers and pulls the same hidden-column defaults from
its own app list. buildDockerContainerDefaultHiddenColumnIds is
preserved as an alias so the existing docker page tests still pass.
This commit is contained in:
rcourtman
2026-05-20 21:27:40 +01:00
parent 6708c6a2c7
commit 557c5151f2
4 changed files with 43 additions and 24 deletions
@@ -11,6 +11,7 @@ import {
PlatformTableEmptyState,
PlatformTableLoadingState,
} from '@/features/platformPage/sharedPlatformPage';
import { APP_CONTAINER_COLUMN_LABEL_OVERRIDES } from '@/features/platformPage/appContainerColumns';
import { DockerHostsTable } from './DockerHostsTable';
import { DockerServicesTable } from './DockerServicesTable';
import {
@@ -27,8 +28,8 @@ const DOCKER_WORKLOAD_FORCED_VIEW_MODE = 'app-container';
const DOCKER_WORKLOAD_DEFAULT_SORT_KEY = 'name';
const DOCKER_WORKLOAD_COLUMN_SCOPE = 'docker-runtime-containers';
const DOCKER_WORKLOAD_COLUMN_LABEL_OVERRIDES = {
...APP_CONTAINER_COLUMN_LABEL_OVERRIDES,
context: 'Host',
disk: 'Writable layer',
} as const;
const DOCKER_WORKLOAD_STATUS_OPTIONS: readonly WorkloadsStatusOption[] = [
{ value: 'all', label: 'All' },
@@ -6,6 +6,7 @@ import {
type ResourceBadge,
} from '@/utils/resourceBadgePresentation';
import { DEGRADED_HEALTH_STATUSES, OFFLINE_HEALTH_STATUSES } from '@/utils/status';
import { buildAppContainerDefaultHiddenColumnIds } from '@/features/platformPage/appContainerColumns';
const DOCKER_HOST_TYPES = new Set<ResourceType>(['agent', 'docker-host']);
const DOCKER_CONTAINER_TYPES = new Set<ResourceType>(['app-container']);
@@ -54,11 +55,6 @@ export interface DockerPageFilters {
statusMode?: WorkloadsStatusMode;
}
const DOCKER_CONTAINER_BASE_DEFAULT_HIDDEN_COLUMNS = ['disk', 'tags'] as const;
const hasFiniteMetric = (value: unknown): value is number =>
typeof value === 'number' && Number.isFinite(value);
const RUNTIME_ONLY_SYSTEM_LABELS = new Set(['docker', 'docker / podman', 'podman']);
const addDockerGroupBadgeAlias = (
@@ -109,24 +105,7 @@ export function buildDockerWorkloadGroupLabelBadges(
return badges;
}
export function buildDockerContainerDefaultHiddenColumnIds(
containers: readonly Resource[],
): string[] {
const hasNetworkIOTelemetry = containers.some(
(container) =>
hasFiniteMetric(container.network?.rxBytes) || hasFiniteMetric(container.network?.txBytes),
);
const hasDiskIOTelemetry = containers.some(
(container) =>
hasFiniteMetric(container.diskIO?.readRate) || hasFiniteMetric(container.diskIO?.writeRate),
);
return [
...DOCKER_CONTAINER_BASE_DEFAULT_HIDDEN_COLUMNS,
...(hasNetworkIOTelemetry ? [] : ['netIo']),
...(hasDiskIOTelemetry ? [] : ['diskIo']),
];
}
export const buildDockerContainerDefaultHiddenColumnIds = buildAppContainerDefaultHiddenColumnIds;
export function buildDockerPageModel(resources: Resource[]): DockerPageModel {
const dockerResources = resources.filter(
@@ -0,0 +1,29 @@
import type { Resource } from '@/types/resource';
export const APP_CONTAINER_BASE_DEFAULT_HIDDEN_COLUMNS = ['disk', 'tags'] as const;
export const APP_CONTAINER_COLUMN_LABEL_OVERRIDES = {
disk: 'Writable layer',
} as const;
const hasFiniteMetric = (value: unknown): value is number =>
typeof value === 'number' && Number.isFinite(value);
export function buildAppContainerDefaultHiddenColumnIds(
containers: readonly Resource[],
): string[] {
const hasNetworkIOTelemetry = containers.some(
(container) =>
hasFiniteMetric(container.network?.rxBytes) || hasFiniteMetric(container.network?.txBytes),
);
const hasDiskIOTelemetry = containers.some(
(container) =>
hasFiniteMetric(container.diskIO?.readRate) || hasFiniteMetric(container.diskIO?.writeRate),
);
return [
...APP_CONTAINER_BASE_DEFAULT_HIDDEN_COLUMNS,
...(hasNetworkIOTelemetry ? [] : ['netIo']),
...(hasDiskIOTelemetry ? [] : ['diskIo']),
];
}
@@ -14,6 +14,10 @@ import {
PlatformTableEmptyState,
PlatformTableLoadingState,
} from '@/features/platformPage/sharedPlatformPage';
import {
APP_CONTAINER_COLUMN_LABEL_OVERRIDES,
buildAppContainerDefaultHiddenColumnIds,
} from '@/features/platformPage/appContainerColumns';
import { TrueNASSystemsTable } from './TrueNASSystemsTable';
import {
TRUENAS_TAB_SPECS,
@@ -116,6 +120,9 @@ interface TrueNASOverviewProps {
}
function TrueNASOverview(props: TrueNASOverviewProps) {
const truenasAppDefaultHiddenColumns = createMemo(() =>
buildAppContainerDefaultHiddenColumnIds(props.model().apps),
);
const workloadsState = useWorkloadsState({
vms: [],
containers: [],
@@ -128,6 +135,9 @@ function TrueNASOverview(props: TrueNASOverviewProps) {
showFilterToolbar: true,
suppressPlatformFilter: true,
allowEmbeddedScopeFilters: true,
columnVisibilityStorageScope: 'truenas-app-containers',
additionalDefaultHiddenColumnIds: truenasAppDefaultHiddenColumns(),
columnLabelOverrides: APP_CONTAINER_COLUMN_LABEL_OVERRIDES,
compactGroupHeaders: true,
});
const showSharedFilterToolbar = createMemo(