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..d8af2ede4 --- /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/docker/logs/utils"; + +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/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index 52ab40ec4..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, @@ -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"); }} /> 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;