From c03c154fc4edc6d98fe667189eee8a53854bb2e5 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 21 Sep 2024 15:16:15 -0600 Subject: [PATCH] feat(multi-server): add docker containers view to servers --- .../docker/config/show-container-config.tsx | 4 +- .../dashboard/docker/logs/docker-logs-id.tsx | 2 +- .../docker/logs/show-docker-modal-logs.tsx | 13 +- .../dashboard/docker/show/colums.tsx | 10 +- .../dashboard/docker/show/show-containers.tsx | 10 +- .../servers/show-docker-containers-modal.tsx | 50 +++++++ .../settings/servers/show-server.tsx | 78 ++++++++++ .../settings/servers/show-servers.tsx | 7 + .../settings/servers/traefik-actions.tsx | 135 ++++++++++++++++++ apps/dokploy/server/api/routers/docker.ts | 15 +- apps/dokploy/server/api/services/docker.ts | 40 ++++-- 11 files changed, 344 insertions(+), 20 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx create mode 100644 apps/dokploy/components/dashboard/settings/servers/show-server.tsx create mode 100644 apps/dokploy/components/dashboard/settings/servers/traefik-actions.tsx diff --git a/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx b/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx index 9aee9f76..84b6ede8 100644 --- a/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx +++ b/apps/dokploy/components/dashboard/docker/config/show-container-config.tsx @@ -11,12 +11,14 @@ import { api } from "@/utils/api"; interface Props { containerId: string; + serverId?: string | null; } -export const ShowContainerConfig = ({ containerId }: Props) => { +export const ShowContainerConfig = ({ containerId, serverId }: Props) => { const { data } = api.docker.getConfig.useQuery( { containerId, + serverId, }, { enabled: !!containerId, diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index ddc9abc9..d9bce727 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -8,7 +8,7 @@ import "@xterm/xterm/css/xterm.css"; interface Props { id: string; containerId: string; - serverId?: string; + serverId?: string | null; } export const DockerLogsId: React.FC = ({ diff --git a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx index 07678b6f..c3d38d98 100644 --- a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx @@ -22,9 +22,14 @@ export const DockerLogsId = dynamic( interface Props { containerId: string; children?: React.ReactNode; + serverId?: string | null; } -export const ShowDockerModalLogs = ({ containerId, children }: Props) => { +export const ShowDockerModalLogs = ({ + containerId, + children, + serverId, +}: Props) => { return ( @@ -41,7 +46,11 @@ export const ShowDockerModalLogs = ({ containerId, children }: Props) => { View the logs for {containerId}
- +
diff --git a/apps/dokploy/components/dashboard/docker/show/colums.tsx b/apps/dokploy/components/dashboard/docker/show/colums.tsx index 243ea4b3..3706d56e 100644 --- a/apps/dokploy/components/dashboard/docker/show/colums.tsx +++ b/apps/dokploy/components/dashboard/docker/show/colums.tsx @@ -114,10 +114,16 @@ export const columns: ColumnDef[] = [ Actions - + View Logs - + Terminal diff --git a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx index e8b56dae..a39dad97 100644 --- a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx +++ b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx @@ -34,8 +34,14 @@ export type Container = NonNullable< RouterOutputs["docker"]["getContainers"] >[0]; -export const ShowContainers = () => { - const { data, isLoading } = api.docker.getContainers.useQuery(); +interface Props { + serverId?: string; +} + +export const ShowContainers = ({ serverId }: Props) => { + const { data, isLoading } = api.docker.getContainers.useQuery({ + serverId, + }); const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( [], diff --git a/apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx b/apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx new file mode 100644 index 00000000..f2724697 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/show-docker-containers-modal.tsx @@ -0,0 +1,50 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { ContainerIcon, FileTextIcon } from "lucide-react"; +import { useState } from "react"; +import { ShowTraefikSystem } from "../../file-system/show-traefik-system"; +import { ShowContainers } from "../../docker/show/show-containers"; + +interface Props { + serverId: string; +} + +export const ShowDockerContainersModal = ({ serverId }: Props) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + + + e.preventDefault()} + > + Show Docker Containers + + + + +
+ + Docker Containers + +

+ See all the containers of your remote server +

+
+
+ +
+ + {/* */} +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/show-server.tsx b/apps/dokploy/components/dashboard/settings/servers/show-server.tsx new file mode 100644 index 00000000..81bdd2a1 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/show-server.tsx @@ -0,0 +1,78 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { TraefikActions } from "./traefik-actions"; +interface Props { + serverId: string; +} + +export const ShowServer = ({ serverId }: Props) => { + const [isOpen, setIsOpen] = useState(false); + return ( + + + e.preventDefault()} + > + View Actions + + + + +
+ + Server Actions + +

+ View all the actions you can do with this server remotely +

+
+
+ +
+ + + Traefik + + Deploy your new project in one-click. + + + + + + + + + + + {/* */} +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index 4afdaf76..976860a4 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -31,6 +31,8 @@ import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { ShowModalLogs } from "../web-server/show-modal-logs"; import { ToggleTraefikDashboard } from "./toggle-traefik-dashboard"; import { EditTraefikEnv } from "../web-server/edit-traefik-env"; +import { ShowServer } from "./show-server"; +import { ShowDockerContainersModal } from "./show-docker-containers-modal"; export const ShowServers = () => { const { data, refetch } = api.server.all.useQuery(); const { mutateAsync } = api.server.remove.useMutation(); @@ -171,6 +173,7 @@ export const ShowServers = () => { + { Delete Server + Traefik { + { + api.settings.reloadServer.useMutation(); + const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } = + api.settings.reloadTraefik.useMutation(); + const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } = + api.settings.cleanAll.useMutation(); + const { mutateAsync: toggleDashboard, isLoading: toggleDashboardIsLoading } = + api.settings.toggleDashboard.useMutation(); + + const { + mutateAsync: cleanStoppedContainers, + isLoading: cleanStoppedContainersIsLoading, + } = api.settings.cleanStoppedContainers.useMutation(); + + const { data: dokployVersion } = api.settings.getDokployVersion.useQuery(); + + const { mutateAsync: updateDockerCleanup } = + api.settings.updateDockerCleanup.useMutation(); + + const { data: haveTraefikDashboardPortEnabled, refetch: refetchDashboard } = + api.settings.haveTraefikDashboardPortEnabled.useQuery(); + + return ( + + + + + + Actions + + + { + await reloadTraefik({ + serverId: serverId, + }) + .then(async () => { + toast.success("Traefik Reloaded"); + }) + .catch(() => { + toast.error("Error to reload the traefik"); + }); + }} + > + Reload + + + Watch logs + + {!serverId && ( + + e.preventDefault()} + className="w-full cursor-pointer space-x-3" + > + View Traefik config + + + )} + + + e.preventDefault()} + className="w-full cursor-pointer space-x-3" + > + Modify Env + + + + { + await toggleDashboard({ + enableDashboard: !haveTraefikDashboardPortEnabled, + serverId: serverId, + }) + .then(async () => { + toast.success( + `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`, + ); + refetchDashboard(); + }) + .catch(() => { + toast.error( + `${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`, + ); + }); + }} + className="w-full cursor-pointer space-x-3" + > + + {haveTraefikDashboardPortEnabled ? "Disable" : "Enable"} Dashboard + + + + + e.preventDefault()} + > + Enter the terminal + + + + + + ); +}; diff --git a/apps/dokploy/server/api/routers/docker.ts b/apps/dokploy/server/api/routers/docker.ts index 27671310..4281cb3b 100644 --- a/apps/dokploy/server/api/routers/docker.ts +++ b/apps/dokploy/server/api/routers/docker.ts @@ -9,9 +9,15 @@ import { import { createTRPCRouter, protectedProcedure } from "../trpc"; export const dockerRouter = createTRPCRouter({ - getContainers: protectedProcedure.query(async () => { - return await getContainers(); - }), + getContainers: protectedProcedure + .input( + z.object({ + serverId: z.string().optional(), + }), + ) + .query(async ({ input }) => { + return await getContainers(input.serverId); + }), restartContainer: protectedProcedure .input( @@ -27,10 +33,11 @@ export const dockerRouter = createTRPCRouter({ .input( z.object({ containerId: z.string().min(1), + serverId: z.string().optional(), }), ) .query(async ({ input }) => { - return await getConfig(input.containerId); + return await getConfig(input.containerId, input.serverId); }), getContainersByAppNameMatch: protectedProcedure diff --git a/apps/dokploy/server/api/services/docker.ts b/apps/dokploy/server/api/services/docker.ts index 09451efb..0accb84a 100644 --- a/apps/dokploy/server/api/services/docker.ts +++ b/apps/dokploy/server/api/services/docker.ts @@ -1,11 +1,22 @@ import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync"; -export const getContainers = async () => { +export const getContainers = async (serverId?: string | null) => { try { - const { stdout, stderr } = await execAsync( - "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | Image: {{.Image}} | Ports: {{.Ports}} | State: {{.State}} | Status: {{.Status}}'", - ); + const command = + "docker ps -a --format 'CONTAINER ID : {{.ID}} | Name: {{.Names}} | Image: {{.Image}} | Ports: {{.Ports}} | State: {{.State}} | Status: {{.Status}}'"; + let stdout = ""; + let stderr = ""; + if (serverId) { + const result = await execAsyncRemote(serverId, command); + + stdout = result.stdout; + stderr = result.stderr; + } else { + const result = await execAsync(command); + stdout = result.stdout; + stderr = result.stderr; + } if (stderr) { console.error(`Error: ${stderr}`); return; @@ -41,6 +52,7 @@ export const getContainers = async () => { ports, state, status, + serverId, }; }) .filter((container) => !container.name.includes("dokploy")); @@ -49,11 +61,23 @@ export const getContainers = async () => { } catch (error) {} }; -export const getConfig = async (containerId: string) => { +export const getConfig = async ( + containerId: string, + serverId?: string | null, +) => { try { - const { stdout, stderr } = await execAsync( - `docker inspect ${containerId} --format='{{json .}}'`, - ); + const command = `docker inspect ${containerId} --format='{{json .}}'`; + let stdout = ""; + let stderr = ""; + if (serverId) { + const result = await execAsyncRemote(serverId, command); + stdout = result.stdout; + stderr = result.stderr; + } else { + const result = await execAsync(command); + stdout = result.stdout; + stderr = result.stderr; + } if (stderr) { console.error(`Error: ${stderr}`);