From d7c152af0dd12c3a0cc5268066ecffe0feb2f7dc Mon Sep 17 00:00:00 2001 From: Aditya Nandlal <73009776+bestmaa@users.noreply.github.com> Date: Thu, 20 Aug 2026 07:03:39 +0000 Subject: [PATCH 1/2] fix: preserve selected log container on refetch --- .../__test__/logs/container-selection.test.ts | 41 +++++++++++++++++++ .../application/logs/container-selection.ts | 21 ++++++++++ .../dashboard/application/logs/show.tsx | 18 ++++---- 3 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 apps/dokploy/__test__/logs/container-selection.test.ts create mode 100644 apps/dokploy/components/dashboard/application/logs/container-selection.ts diff --git a/apps/dokploy/__test__/logs/container-selection.test.ts b/apps/dokploy/__test__/logs/container-selection.test.ts new file mode 100644 index 000000000..e87817556 --- /dev/null +++ b/apps/dokploy/__test__/logs/container-selection.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { resolveContainerSelection } from "@/components/dashboard/application/logs/container-selection"; + +const containers = [ + { containerId: "first-container" }, + { containerId: "selected-container" }, +]; + +describe("resolveContainerSelection", () => { + it("selects the first container when no container is selected", () => { + expect(resolveContainerSelection(undefined, containers)).toBe( + "first-container", + ); + }); + + it("preserves a manual selection when refreshed data contains it", () => { + const refreshedContainers = containers.map((container) => ({ + ...container, + })); + + expect( + resolveContainerSelection("selected-container", refreshedContainers), + ).toBe("selected-container"); + }); + + it("falls back to the first container when the selection disappears", () => { + expect(resolveContainerSelection("removed-container", containers)).toBe( + "first-container", + ); + }); + + it("keeps the current selection while container data is loading", () => { + expect(resolveContainerSelection("selected-container", undefined)).toBe( + "selected-container", + ); + }); + + it("clears the selection when no containers are available", () => { + expect(resolveContainerSelection("selected-container", [])).toBeUndefined(); + }); +}); diff --git a/apps/dokploy/components/dashboard/application/logs/container-selection.ts b/apps/dokploy/components/dashboard/application/logs/container-selection.ts new file mode 100644 index 000000000..445ffb0a8 --- /dev/null +++ b/apps/dokploy/components/dashboard/application/logs/container-selection.ts @@ -0,0 +1,21 @@ +interface ContainerOption { + containerId: string; +} + +export const resolveContainerSelection = ( + currentContainerId: string | undefined, + containers: readonly ContainerOption[] | undefined, +) => { + if (!containers) { + return currentContainerId; + } + + if ( + currentContainerId && + containers.some(({ containerId }) => containerId === currentContainerId) + ) { + return currentContainerId; + } + + return containers[0]?.containerId; +}; diff --git a/apps/dokploy/components/dashboard/application/logs/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index 52ab40ec4..d5db191d1 100644 --- a/apps/dokploy/components/dashboard/application/logs/show.tsx +++ b/apps/dokploy/components/dashboard/application/logs/show.tsx @@ -21,6 +21,7 @@ import { } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; +import { resolveContainerSelection } from "./container-selection"; export const DockerLogs = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( @@ -79,17 +80,13 @@ export const ShowDockerLogs = ({ appName, serverId, serviceId }: Props) => { }, ); + const availableContainers = option === "native" ? containers : services; + useEffect(() => { - if (option === "native") { - if (containers && containers?.length > 0) { - setContainerId(containers[0]?.containerId); - } - } else { - if (services && services?.length > 0) { - setContainerId(services[0]?.containerId); - } - } - }, [option, services, containers]); + setContainerId((currentContainerId) => + resolveContainerSelection(currentContainerId, availableContainers), + ); + }, [availableContainers]); const isLoading = option === "native" ? containersLoading : servicesLoading; const containersLength = @@ -114,6 +111,7 @@ export const ShowDockerLogs = ({ appName, serverId, serviceId }: Props) => { { + setContainerId(undefined); setOption(checked ? "native" : "swarm"); }} /> From 62f701425a5e32f54efa9c6ef1d0004386104789 Mon Sep 17 00:00:00 2001 From: Aditya Nandlal <73009776+bestmaa@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:32:49 +0000 Subject: [PATCH 2/2] refactor: share log container selection --- .../__test__/logs/container-selection.test.ts | 2 +- .../application/logs/container-selection.ts | 21 ------------------ .../dashboard/application/logs/show.tsx | 2 +- .../dashboard/compose/logs/show-stack.tsx | 20 +++++------------ .../components/dashboard/docker/logs/utils.ts | 22 +++++++++++++++++++ 5 files changed, 30 insertions(+), 37 deletions(-) delete mode 100644 apps/dokploy/components/dashboard/application/logs/container-selection.ts diff --git a/apps/dokploy/__test__/logs/container-selection.test.ts b/apps/dokploy/__test__/logs/container-selection.test.ts index e87817556..d8af2ede4 100644 --- a/apps/dokploy/__test__/logs/container-selection.test.ts +++ b/apps/dokploy/__test__/logs/container-selection.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { resolveContainerSelection } from "@/components/dashboard/application/logs/container-selection"; +import { resolveContainerSelection } from "@/components/dashboard/docker/logs/utils"; const containers = [ { containerId: "first-container" }, diff --git a/apps/dokploy/components/dashboard/application/logs/container-selection.ts b/apps/dokploy/components/dashboard/application/logs/container-selection.ts deleted file mode 100644 index 445ffb0a8..000000000 --- a/apps/dokploy/components/dashboard/application/logs/container-selection.ts +++ /dev/null @@ -1,21 +0,0 @@ -interface ContainerOption { - containerId: string; -} - -export const resolveContainerSelection = ( - currentContainerId: string | undefined, - containers: readonly ContainerOption[] | undefined, -) => { - if (!containers) { - return currentContainerId; - } - - if ( - currentContainerId && - containers.some(({ containerId }) => containerId === currentContainerId) - ) { - return currentContainerId; - } - - return containers[0]?.containerId; -}; diff --git a/apps/dokploy/components/dashboard/application/logs/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index d5db191d1..372640906 100644 --- a/apps/dokploy/components/dashboard/application/logs/show.tsx +++ b/apps/dokploy/components/dashboard/application/logs/show.tsx @@ -1,6 +1,7 @@ import { Loader2 } from "lucide-react"; import dynamic from "next/dynamic"; import { useEffect, useState } from "react"; +import { resolveContainerSelection } from "@/components/dashboard/docker/logs/utils"; import { Badge } from "@/components/ui/badge"; import { Card, @@ -21,7 +22,6 @@ import { } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { resolveContainerSelection } from "./container-selection"; export const DockerLogs = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( diff --git a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx index 558dc1c0f..acf296f7e 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx @@ -2,6 +2,7 @@ import { Loader2 } from "lucide-react"; import dynamic from "next/dynamic"; import { useEffect, useState } from "react"; import { badgeStateColor } from "@/components/dashboard/application/logs/show"; +import { resolveContainerSelection } from "@/components/dashboard/docker/logs/utils"; import { Badge } from "@/components/ui/badge"; import { Card, @@ -70,22 +71,13 @@ export const ShowDockerLogsStack = ({ ); const containers = data?.filter((container) => container.containerId); + const availableContainers = option === "native" ? containers : services; useEffect(() => { - const currentContainers = option === "native" ? containers : services; - - if (currentContainers) { - const nextContainerId = currentContainers.some( - (container) => container.containerId === containerId, - ) - ? containerId - : currentContainers[0]?.containerId; - - if (nextContainerId !== containerId) { - setContainerId(nextContainerId); - } - } - }, [option, services, containers, containerId]); + setContainerId((currentContainerId) => + resolveContainerSelection(currentContainerId, availableContainers), + ); + }, [availableContainers]); const isLoading = option === "native" ? containersLoading : servicesLoading; const containersLength = diff --git a/apps/dokploy/components/dashboard/docker/logs/utils.ts b/apps/dokploy/components/dashboard/docker/logs/utils.ts index f817d980e..81d86408d 100644 --- a/apps/dokploy/components/dashboard/docker/logs/utils.ts +++ b/apps/dokploy/components/dashboard/docker/logs/utils.ts @@ -7,6 +7,28 @@ export interface LogLine { message: string; } +interface ContainerOption { + containerId: string; +} + +export const resolveContainerSelection = ( + currentContainerId: string | undefined, + containers: readonly ContainerOption[] | undefined, +) => { + if (!containers) { + return currentContainerId; + } + + if ( + currentContainerId && + containers.some(({ containerId }) => containerId === currentContainerId) + ) { + return currentContainerId; + } + + return containers[0]?.containerId; +}; + interface LogStyle { type: LogType; variant: LogVariant;