From b5da9291b49e185197abf140b8774e043e9f19d8 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 18 Aug 2024 23:18:54 -0600 Subject: [PATCH] feat: add enviroment variables editor to traefik --- .../settings/web-server/edit-traefik-env.tsx | 146 ++++++++++++++++++ apps/dokploy/server/api/routers/settings.ts | 39 ++++- apps/dokploy/server/setup/traefik-setup.ts | 11 +- 3 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx diff --git a/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx new file mode 100644 index 00000000..c0504ab4 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/web-server/edit-traefik-env.tsx @@ -0,0 +1,146 @@ +import { AlertBlock } from "@/components/shared/alert-block"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +const schema = z.object({ + env: z.string(), +}); + +type Schema = z.infer; + +interface Props { + children?: React.ReactNode; +} + +export const EditTraefikEnv = ({ children }: Props) => { + const [canEdit, setCanEdit] = useState(true); + + const { data } = api.settings.readTraefikEnv.useQuery(); + + const { mutateAsync, isLoading, error, isError } = + api.settings.writeTraefikEnv.useMutation(); + + const form = useForm({ + defaultValues: { + env: data || "", + }, + disabled: canEdit, + resolver: zodResolver(schema), + }); + + useEffect(() => { + if (data) { + form.reset({ + env: data || "", + }); + } + }, [form, form.reset, data]); + + const onSubmit = async (data: Schema) => { + await mutateAsync(data.env) + .then(async () => { + toast.success("Traefik Env Updated"); + }) + .catch(() => { + toast.error("Error to update the traefik env"); + }); + }; + + return ( + + {children} + + + Update Traefik Env + Update the traefik env + + {isError && {error?.message}} + +
+ +
+ ( + + Env + + + + +
+											
+										
+
+ +
+
+ )} + /> +
+
+ + + + + +
+
+ ); +}; diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts index a26de6e3..06323452 100644 --- a/apps/dokploy/server/api/routers/settings.ts +++ b/apps/dokploy/server/api/routers/settings.ts @@ -15,6 +15,7 @@ import { cleanUpSystemPrune, cleanUpUnusedImages, cleanUpUnusedVolumes, + prepareEnvironmentVariables, startService, stopService, } from "@/server/utils/docker/utils"; @@ -47,6 +48,7 @@ import { } from "../services/settings"; import { canAccessToTraefikFiles } from "../services/user"; import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc"; +import { z } from "zod"; export const settingsRouter = createTRPCRouter({ reloadServer: adminProcedure.mutation(async () => { @@ -69,7 +71,9 @@ export const settingsRouter = createTRPCRouter({ toggleDashboard: adminProcedure .input(apiEnableDashboard) .mutation(async ({ input }) => { - await initializeTraefik(input.enableDashboard); + await initializeTraefik({ + enableDashboard: input.enableDashboard, + }); return true; }), @@ -309,4 +313,37 @@ export const settingsRouter = createTRPCRouter({ return openApiDocument; }, ), + readTraefikEnv: adminProcedure.query(async () => { + const { stdout } = await execAsync( + "docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik", + ); + + return stdout.trim(); + }), + + writeTraefikEnv: adminProcedure + .input(z.string()) + .mutation(async ({ input }) => { + const envs = prepareEnvironmentVariables(input); + await initializeTraefik({ + env: envs, + }); + + return true; + }), + haveTraefikDashboardPortEnabled: adminProcedure.query(async () => { + const { stdout } = await execAsync( + "docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik", + ); + + const parsed: any[] = JSON.parse(stdout.trim()); + + for (const port of parsed) { + if (port.PublishedPort === 8080) { + return true; + } + } + + return false; + }), }); diff --git a/apps/dokploy/server/setup/traefik-setup.ts b/apps/dokploy/server/setup/traefik-setup.ts index 2b44cf40..afebf07e 100644 --- a/apps/dokploy/server/setup/traefik-setup.ts +++ b/apps/dokploy/server/setup/traefik-setup.ts @@ -11,7 +11,15 @@ const TRAEFIK_SSL_PORT = Number.parseInt(process.env.TRAEFIK_SSL_PORT ?? "", 10) || 443; const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT ?? "", 10) || 80; -export const initializeTraefik = async (enableDashboard = false) => { +interface TraefikOptions { + enableDashboard?: boolean; + env?: string[]; +} + +export const initializeTraefik = async ({ + enableDashboard = false, + env = [], +}: TraefikOptions = {}) => { const imageName = "traefik:v2.5"; const containerName = "dokploy-traefik"; const settings: CreateServiceOptions = { @@ -19,6 +27,7 @@ export const initializeTraefik = async (enableDashboard = false) => { TaskTemplate: { ContainerSpec: { Image: imageName, + Env: env, Mounts: [ { Type: "bind",