From fa665e00938ed42622b7fa77242e267e3e959e86 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Thu, 4 Jun 2026 13:53:51 +0200 Subject: [PATCH] fix(docker): fix breadcrumb, xterm cleanup, and replace action column with menu - Fix breadcrumb checking dynamicMappings before nonInteractable so container name shows instead of raw translation key - Fix xterm _linkifier2 error by disposing canvas addon before terminal - Replace single-action row column with a Menu dropdown containing all actions (view logs, start, stop, restart, remove, add to homarr) - Refactor action bar to use shared containerActions lookup table --- .../manage/tools/docker/docker-table.tsx | 124 ++++++++++++------ .../docker/logs/[containerId]/terminal.tsx | 3 +- .../[locale]/manage/tools/logs/terminal.tsx | 3 +- .../navigation/dynamic-breadcrumb.tsx | 20 +-- 4 files changed, 102 insertions(+), 48 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx index c2a28913d..9fbf45d9c 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx @@ -2,9 +2,10 @@ import { useRouter } from "next/navigation"; import type { MantineColor } from "@mantine/core"; -import { ActionIcon, Avatar, Badge, Box, Button, Group, Text, Tooltip } from "@mantine/core"; +import { ActionIcon, Avatar, Badge, Box, Button, Group, Menu, Text } from "@mantine/core"; import { IconCategoryPlus, + IconDots, IconFileText, IconPlayerPlay, IconPlayerStop, @@ -151,7 +152,6 @@ const createColumns = (t: TranslationFunction): MRT_ColumnDef[] export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["getContainers"]) { const t = useI18n(); const tDocker = useScopedI18n("docker"); - const router = useRouter(); const { data } = clientApi.docker.getContainers.useQuery(undefined, { initialData: { containers, timestamp }, refetchOnMount: false, @@ -197,19 +197,7 @@ export function DockerTable({ containers, timestamp }: RouterOutputs["docker"][" initialState: { density: "xs", showGlobalFilter: true, columnVisibility: { cpuUsage: false, memoryUsage: false } }, renderRowActions: ({ row }: { row: MRT_Row }) => ( - - { - event.stopPropagation(); - router.push(createContainerLogsPath(row.original)); - }} - > - - - + ), renderTopToolbarCustomActions: () => ( - diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/logs/[containerId]/terminal.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/logs/[containerId]/terminal.tsx index 67e168d3e..a8d0e1fa8 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/docker/logs/[containerId]/terminal.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/logs/[containerId]/terminal.tsx @@ -71,8 +71,9 @@ export const DockerLogsTerminal = ({ containerId }: DockerLogsTerminalProps) => return () => { isTerminalReadyRef.current = false; pendingLogsRef.current = []; - terminalRef.current?.dispose(); canvasAddon.dispose(); + terminalRef.current?.dispose(); + terminalRef.current = null; }; }, [writeToTerminal]); diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/terminal.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/terminal.tsx index 275282b71..54ecac0a3 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/terminal.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/terminal.tsx @@ -57,8 +57,9 @@ export const TerminalComponent = () => { }); return () => { - terminalRef.current?.dispose(); canvasAddon.dispose(); + terminalRef.current?.dispose(); + terminalRef.current = null; }; }, []); return ; diff --git a/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx b/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx index f6310d481..d200aa966 100644 --- a/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx +++ b/apps/nextjs/src/components/navigation/dynamic-breadcrumb.tsx @@ -58,19 +58,23 @@ export const DynamicBreadcrumb = ({ } const href = `/${pathnameParts.slice(0, index + 1).join("/")}`; const translationKey = `${pathnameParts.slice(0, index + 1).join(".")}`; + const mappedValue = dynamicMappings?.get(pathnamePart); + + if (mappedValue) { + if (nonInteractable?.includes(pathnamePart)) { + return {mappedValue}; + } + return ( + + {mappedValue} + + ); + } if (nonInteractable?.includes(pathnamePart)) { return {t(`${translationKey}.label` as TranslationKeys)}; } - if (dynamicMappings?.has(pathnamePart)) { - return ( - - {dynamicMappings.get(pathnamePart)} - - ); - } - return ( {t(`${translationKey}.label` as TranslationKeys)}