From 3052979bdde1c37ac69bee7939307016ad76f781 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sat, 12 Jul 2025 23:16:06 -0600 Subject: [PATCH] refactor(web-server): update components to utilize web server data - Replaced user IP references with web server data across various components, including domain management and database credential displays. - Adjusted API calls to fetch web server information instead of user data, enhancing data consistency and clarity. - Refactored related functions to streamline the handling of server configurations and improve overall code maintainability. --- .../server/update-server-config.test.ts | 31 +- .../application/domains/show-domains.tsx | 8 +- .../show-external-mariadb-credentials.tsx | 4 +- .../show-external-mongo-credentials.tsx | 4 +- .../show-external-mysql-credentials.tsx | 4 +- .../show-external-postgres-credentials.tsx | 4 +- .../show-external-redis-credentials.tsx | 4 +- .../servers/actions/toggle-docker-cleanup.tsx | 6 +- .../settings/web-server/update-server-ip.tsx | 2 +- apps/dokploy/pages/dashboard/monitoring.tsx | 6 +- apps/dokploy/server/api/routers/admin.ts | 7 +- apps/dokploy/server/api/routers/compose.ts | 14 +- .../server/api/routers/notification.ts | 10 +- apps/dokploy/server/api/routers/web-server.ts | 694 +----------------- 14 files changed, 50 insertions(+), 748 deletions(-) diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts index 6858f0f00..04e18a8dc 100644 --- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts +++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts @@ -5,7 +5,8 @@ vi.mock("node:fs", () => ({ default: fs, })); -import type { FileConfig, User } from "@dokploy/server"; +import type { FileConfig } from "@dokploy/server"; +import type { WebServer } from "@dokploy/server/db/schema"; import { createDefaultServerTraefikConfig, loadOrCreateConfig, @@ -13,11 +14,8 @@ import { } from "@dokploy/server"; import { beforeEach, expect, test, vi } from "vitest"; -const baseAdmin: User = { +const baseAdmin: WebServer = { https: false, - enablePaidFeatures: false, - allowImpersonation: false, - role: "user", metricsConfig: { containers: { refreshRate: 20, @@ -40,10 +38,6 @@ const baseAdmin: User = { urlCallback: "", }, }, - cleanupCacheApplications: false, - cleanupCacheOnCompose: false, - cleanupCacheOnPreviews: false, - createdAt: new Date(), serverIp: null, certificateType: "none", host: null, @@ -51,22 +45,7 @@ const baseAdmin: User = { sshPrivateKey: null, enableDockerCleanup: false, logCleanupCron: null, - serversQuantity: 0, - stripeCustomerId: "", - stripeSubscriptionId: "", - banExpires: new Date(), - banned: true, - banReason: "", - email: "", - expirationDate: "", - id: "", - isRegistered: false, - name: "", - createdAt2: new Date().toISOString(), - emailVerified: false, - image: "", - updatedAt: new Date(), - twoFactorEnabled: false, + webServerId: "1", }; beforeEach(() => { @@ -85,8 +64,6 @@ test("Should apply redirect-to-https", () => { updateServerTraefik( { ...baseAdmin, - https: true, - certificateType: "letsencrypt", }, "example.com", ); diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index 7bb58dfbe..4326d2e8d 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -71,7 +71,7 @@ export const ShowDomains = ({ id, type }: Props) => { const [validationStates, setValidationStates] = useState( {}, ); - const { data: ip } = api.settings.getIp.useQuery(); + const { data: webServer } = api.webServer.get.useQuery(); const { data, @@ -110,7 +110,9 @@ export const ShowDomains = ({ id, type }: Props) => { const result = await validateDomain({ domain: host, serverIp: - application?.server?.ipAddress?.toString() || ip?.toString() || "", + application?.server?.ipAddress?.toString() || + webServer?.serverIp?.toString() || + "", }); setValidationStates((prev) => ({ @@ -210,7 +212,7 @@ export const ShowDomains = ({ id, type }: Props) => { }} serverIp={ application?.server?.ipAddress?.toString() || - ip?.toString() + webServer?.serverIp?.toString() } /> )} diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx index c00af42be..e2e6d4a7a 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx @@ -46,11 +46,11 @@ interface Props { mariadbId: string; } export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); + const { data: webServer } = api.webServer.get.useQuery(); const { data, refetch } = api.mariadb.one.useQuery({ mariadbId }); const { mutateAsync, isLoading } = api.mariadb.saveExternalPort.useMutation(); const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; + const getIp = data?.server?.ipAddress || webServer?.serverIp; const form = useForm({ defaultValues: {}, resolver: zodResolver(DockerProviderSchema), diff --git a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx index 75772bfdf..8585d9ebf 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx @@ -46,11 +46,11 @@ interface Props { mongoId: string; } export const ShowExternalMongoCredentials = ({ mongoId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); + const { data: webServer } = api.webServer.get.useQuery(); const { data, refetch } = api.mongo.one.useQuery({ mongoId }); const { mutateAsync, isLoading } = api.mongo.saveExternalPort.useMutation(); const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; + const getIp = data?.server?.ipAddress || webServer?.serverIp; const form = useForm({ defaultValues: {}, resolver: zodResolver(DockerProviderSchema), diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index 73f99b7d0..19c3c6a36 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -46,11 +46,11 @@ interface Props { mysqlId: string; } export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); + const { data: webServer } = api.webServer.get.useQuery(); const { data, refetch } = api.mysql.one.useQuery({ mysqlId }); const { mutateAsync, isLoading } = api.mysql.saveExternalPort.useMutation(); const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; + const getIp = data?.server?.ipAddress || webServer?.serverIp; const form = useForm({ defaultValues: {}, resolver: zodResolver(DockerProviderSchema), diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index 444fa0cee..dc00e8432 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -46,11 +46,11 @@ interface Props { postgresId: string; } export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); + const { data: webServer } = api.webServer.get.useQuery(); const { data, refetch } = api.postgres.one.useQuery({ postgresId }); const { mutateAsync, isLoading } = api.postgres.saveExternalPort.useMutation(); - const getIp = data?.server?.ipAddress || ip; + const getIp = data?.server?.ipAddress || webServer?.serverIp; const [connectionUrl, setConnectionUrl] = useState(""); const form = useForm({ diff --git a/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx b/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx index 46cb09530..102e0f3e6 100644 --- a/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx +++ b/apps/dokploy/components/dashboard/redis/general/show-external-redis-credentials.tsx @@ -46,11 +46,11 @@ interface Props { redisId: string; } export const ShowExternalRedisCredentials = ({ redisId }: Props) => { - const { data: ip } = api.settings.getIp.useQuery(); + const { data: webServer } = api.webServer.get.useQuery(); const { data, refetch } = api.redis.one.useQuery({ redisId }); const { mutateAsync, isLoading } = api.redis.saveExternalPort.useMutation(); const [connectionUrl, setConnectionUrl] = useState(""); - const getIp = data?.server?.ipAddress || ip; + const getIp = data?.server?.ipAddress || webServer?.serverIp; const form = useForm({ defaultValues: {}, diff --git a/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx b/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx index 604ab6ce0..1eb6c5d68 100644 --- a/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/actions/toggle-docker-cleanup.tsx @@ -7,7 +7,7 @@ interface Props { serverId?: string; } export const ToggleDockerCleanup = ({ serverId }: Props) => { - const { data, refetch } = api.user.get.useQuery(undefined, { + const { data, refetch } = api.webServer.get.useQuery(undefined, { enabled: !serverId, }); @@ -20,9 +20,9 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => { }, ); - const enabled = data?.user.enableDockerCleanup || server?.enableDockerCleanup; + const enabled = data?.enableDockerCleanup || server?.enableDockerCleanup; - const { mutateAsync } = api.settings.updateDockerCleanup.useMutation(); + const { mutateAsync } = api.webServer.updateDockerCleanup.useMutation(); const handleToggle = async (checked: boolean) => { try { diff --git a/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx b/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx index 2a55fbc4f..f464618b3 100644 --- a/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx +++ b/apps/dokploy/components/dashboard/settings/web-server/update-server-ip.tsx @@ -80,7 +80,7 @@ export const UpdateServerIp = ({ children }: Props) => { }) .then(async () => { toast.success("Server IP Updated"); - await utils.user.get.invalidate(); + await utils.webServer.get.invalidate(); setIsOpen(false); }) .catch(() => { diff --git a/apps/dokploy/pages/dashboard/monitoring.tsx b/apps/dokploy/pages/dashboard/monitoring.tsx index 4272c4536..6af9ef233 100644 --- a/apps/dokploy/pages/dashboard/monitoring.tsx +++ b/apps/dokploy/pages/dashboard/monitoring.tsx @@ -20,7 +20,7 @@ const Dashboard = () => { false, ); - const { data: monitoring, isLoading } = api.user.getMetricsToken.useQuery(); + const { data: webServer, isLoading } = api.webServer.get.useQuery(); return (
{/* @@ -59,12 +59,12 @@ const Dashboard = () => { diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index 47bd9cd9c..9378261d3 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -3,7 +3,7 @@ import { IS_CLOUD, findUserById, setupWebMonitoring, - updateUser, + updateWebServer, } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; import { adminProcedure, createTRPCRouter } from "../trpc"; @@ -27,7 +27,8 @@ export const adminRouter = createTRPCRouter({ }); } - await updateUser(user.id, { + await updateWebServer({ + // @ts-expect-error - TODO: fix this metricsConfig: { server: { type: "Dokploy", @@ -52,7 +53,7 @@ export const adminRouter = createTRPCRouter({ }, }); - const currentServer = await setupWebMonitoring(user.id); + const currentServer = await setupWebMonitoring(); return currentServer; } catch (error) { throw error; diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index cdb2c0fbe..bd838afeb 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -31,7 +31,7 @@ import { findGitProviderById, findProjectById, findServerById, - findUserById, + findWebServer, getComposeContainer, loadServices, randomizeComposeFile, @@ -487,8 +487,8 @@ export const composeRouter = createTRPCRouter({ const template = await fetchTemplateFiles(input.id, input.baseUrl); - const admin = await findUserById(ctx.user.ownerId); - let serverIp = admin.serverIp || "127.0.0.1"; + const webServer = await findWebServer(); + let serverIp = webServer.serverIp || "127.0.0.1"; const project = await findProjectById(input.projectId); @@ -709,8 +709,8 @@ export const composeRouter = createTRPCRouter({ const decodedData = Buffer.from(input.base64, "base64").toString( "utf-8", ); - const admin = await findUserById(ctx.user.ownerId); - let serverIp = admin.serverIp || "127.0.0.1"; + const webServer = await findWebServer(); + let serverIp = webServer.serverIp || "127.0.0.1"; if (compose.serverId) { const server = await findServerById(compose.serverId); @@ -785,8 +785,8 @@ export const composeRouter = createTRPCRouter({ await removeDomainById(domain.domainId); } - const admin = await findUserById(ctx.user.ownerId); - let serverIp = admin.serverIp || "127.0.0.1"; + const webServer = await findWebServer(); + let serverIp = webServer.serverIp || "127.0.0.1"; if (compose.serverId) { const server = await findServerById(compose.serverId); diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts index 7b2c69405..4da81103b 100644 --- a/apps/dokploy/server/api/routers/notification.ts +++ b/apps/dokploy/server/api/routers/notification.ts @@ -24,7 +24,7 @@ import { apiUpdateTelegram, notifications, server, - users, + webServer, } from "@/server/db/schema"; import { IS_CLOUD, @@ -345,19 +345,19 @@ export const notificationRouter = createTRPCRouter({ if (input.ServerType === "Dokploy") { const result = await db .select() - .from(users) + .from(webServer) .where( - sql`${users.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`, + sql`${webServer.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`, ); - if (!result?.[0]?.id) { + if (!result?.[0]?.webServerId) { throw new TRPCError({ code: "BAD_REQUEST", message: "Token not found", }); } - organizationId = result?.[0]?.id; + organizationId = result?.[0]?.webServerId; ServerName = "Dokploy"; } else { const result = await db diff --git a/apps/dokploy/server/api/routers/web-server.ts b/apps/dokploy/server/api/routers/web-server.ts index 3c5e4ec9e..9c334cad1 100644 --- a/apps/dokploy/server/api/routers/web-server.ts +++ b/apps/dokploy/server/api/routers/web-server.ts @@ -1,204 +1,47 @@ -import { db } from "@/server/db"; import { apiAssignDomain, - apiEnableDashboard, - apiModifyTraefikConfig, - apiReadStatsLogs, - apiReadTraefikConfig, apiSaveSSHKey, - apiServerSchema, - apiTraefikConfig, apiUpdateDockerCleanup, updateWebServerSchema, } from "@/server/db/schema"; import { removeJob, schedule } from "@/server/utils/backup"; import { - DEFAULT_UPDATE_DATA, IS_CLOUD, - canAccessToTraefikFiles, - cleanStoppedContainers, cleanUpDockerBuilder, cleanUpSystemPrune, cleanUpUnusedImages, - cleanUpUnusedVolumes, - execAsync, - execAsyncRemote, findServerById, findWebServer, - getDokployImage, - getDokployImageTag, - getLogCleanupStatus, - getUpdateData, - initializeTraefik, - parseRawConfig, - paths, - prepareEnvironmentVariables, - processLogs, - pullLatestRelease, - readConfig, - readConfigInPath, - readDirectory, - readMainConfig, - readMonitoringConfig, - recreateDirectory, sendDockerCleanupNotifications, - spawnAsync, - startLogCleanup, - stopLogCleanup, updateLetsEncryptEmail, updateServerById, updateServerTraefik, updateWebServer, - writeConfig, - writeMainConfig, - writeTraefikConfigInPath, } from "@dokploy/server"; -import { checkGPUStatus, setupGPUSupport } from "@dokploy/server"; import { TRPCError } from "@trpc/server"; -import { sql } from "drizzle-orm"; -import { dump, load } from "js-yaml"; import { scheduleJob, scheduledJobs } from "node-schedule"; -import { z } from "zod"; -import packageInfo from "../../../package.json"; -import { - adminProcedure, - createTRPCRouter, - protectedProcedure, - publicProcedure, -} from "../trpc"; +import { adminProcedure, createTRPCRouter } from "../trpc"; export const webServerRouter = createTRPCRouter({ get: adminProcedure.query(async () => { + if (IS_CLOUD) { + return null; + } return await findWebServer(); }), update: adminProcedure .input(updateWebServerSchema) .mutation(async ({ input }) => { + if (IS_CLOUD) { + return null; + } return await updateWebServer(input); }), - reloadServer: adminProcedure.mutation(async () => { - if (IS_CLOUD) { - return true; - } - const { stdout } = await execAsync( - "docker service inspect dokploy --format '{{.ID}}'", - ); - await execAsync(`docker service update --force ${stdout.trim()}`); - return true; - }), - cleanRedis: adminProcedure.mutation(async () => { - if (IS_CLOUD) { - return true; - } - - const { stdout: containerId } = await execAsync( - `docker ps --filter "name=dokploy-redis" --filter "status=running" -q | head -n 1`, - ); - - if (!containerId) { - throw new Error("Redis container not found"); - } - - const redisContainerId = containerId.trim(); - - await execAsync(`docker exec -i ${redisContainerId} redis-cli flushall`); - return true; - }), - reloadRedis: adminProcedure.mutation(async () => { - if (IS_CLOUD) { - return true; - } - - await execAsync("docker service scale dokploy-redis=0"); - await execAsync("docker service scale dokploy-redis=1"); - return true; - }), - reloadTraefik: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - try { - if (input?.serverId) { - await execAsync("docker restart dokploy-traefik"); - } else if (!IS_CLOUD) { - await execAsync("docker restart dokploy-traefik"); - } - } catch (err) { - console.error(err); - } - - return true; - }), - toggleDashboard: adminProcedure - .input(apiEnableDashboard) - .mutation(async ({ input }) => { - const ports = (await getTraefikPorts(input.serverId)).filter( - (port) => - port.targetPort !== 80 && - port.targetPort !== 443 && - port.targetPort !== 8080, - ); - await initializeTraefik({ - additionalPorts: ports, - enableDashboard: input.enableDashboard, - serverId: input.serverId, - force: true, - }); - return true; - }), - cleanUnusedImages: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - await cleanUpUnusedImages(input?.serverId); - return true; - }), - cleanUnusedVolumes: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - await cleanUpUnusedVolumes(input?.serverId); - return true; - }), - cleanStoppedContainers: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - await cleanStoppedContainers(input?.serverId); - return true; - }), - cleanDockerBuilder: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - await cleanUpDockerBuilder(input?.serverId); - }), - cleanDockerPrune: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - await cleanUpSystemPrune(input?.serverId); - await cleanUpDockerBuilder(input?.serverId); - - return true; - }), - cleanAll: adminProcedure - .input(apiServerSchema) - .mutation(async ({ input }) => { - await cleanUpUnusedImages(input?.serverId); - await cleanStoppedContainers(input?.serverId); - await cleanUpDockerBuilder(input?.serverId); - await cleanUpSystemPrune(input?.serverId); - - return true; - }), - cleanMonitoring: adminProcedure.mutation(async () => { - if (IS_CLOUD) { - return true; - } - const { MONITORING_PATH } = paths(); - await recreateDirectory(MONITORING_PATH); - return true; - }), saveSSHPrivateKey: adminProcedure .input(apiSaveSSHKey) .mutation(async ({ input }) => { if (IS_CLOUD) { - return true; + return null; } await updateWebServer({ sshPrivateKey: input.sshPrivateKey, @@ -206,13 +49,6 @@ export const webServerRouter = createTRPCRouter({ return true; }), - getIp: protectedProcedure.query(async () => { - if (IS_CLOUD) { - return true; - } - const webServer = await findWebServer(); - return webServer?.serverIp; - }), assignDomainServer: adminProcedure .input(apiAssignDomain) .mutation(async ({ input }) => { @@ -330,518 +166,4 @@ export const webServerRouter = createTRPCRouter({ return true; }), - - readTraefikConfig: adminProcedure.query(() => { - if (IS_CLOUD) { - return true; - } - const traefikConfig = readMainConfig(); - return traefikConfig; - }), - - updateTraefikConfig: adminProcedure - .input(apiTraefikConfig) - .mutation(async ({ input }) => { - if (IS_CLOUD) { - return true; - } - writeMainConfig(input.traefikConfig); - return true; - }), - - readWebServerTraefikConfig: adminProcedure.query(() => { - if (IS_CLOUD) { - return true; - } - const traefikConfig = readConfig("dokploy"); - return traefikConfig; - }), - updateWebServerTraefikConfig: adminProcedure - .input(apiTraefikConfig) - .mutation(async ({ input }) => { - if (IS_CLOUD) { - return true; - } - writeConfig("dokploy", input.traefikConfig); - return true; - }), - - readMiddlewareTraefikConfig: adminProcedure.query(() => { - if (IS_CLOUD) { - return true; - } - const traefikConfig = readConfig("middlewares"); - return traefikConfig; - }), - - updateMiddlewareTraefikConfig: adminProcedure - .input(apiTraefikConfig) - .mutation(async ({ input }) => { - if (IS_CLOUD) { - return true; - } - writeConfig("middlewares", input.traefikConfig); - return true; - }), - getUpdateData: protectedProcedure.mutation(async () => { - if (IS_CLOUD) { - return DEFAULT_UPDATE_DATA; - } - - return await getUpdateData(); - }), - updateServer: adminProcedure.mutation(async () => { - if (IS_CLOUD) { - return true; - } - - await pullLatestRelease(); - - // This causes restart of dokploy, thus it will not finish executing properly, so don't await it - // Status after restart is checked via frontend /api/health endpoint - void spawnAsync("docker", [ - "service", - "update", - "--force", - "--image", - getDokployImage(), - "dokploy", - ]); - - return true; - }), - - getDokployVersion: protectedProcedure.query(() => { - return packageInfo.version; - }), - getReleaseTag: protectedProcedure.query(() => { - return getDokployImageTag(); - }), - readDirectories: protectedProcedure - .input(apiServerSchema) - .query(async ({ ctx, input }) => { - try { - if (ctx.user.role === "member") { - const canAccess = await canAccessToTraefikFiles( - ctx.user.id, - ctx.session.activeOrganizationId, - ); - - if (!canAccess) { - throw new TRPCError({ code: "UNAUTHORIZED" }); - } - } - const { MAIN_TRAEFIK_PATH } = paths(!!input?.serverId); - const result = await readDirectory(MAIN_TRAEFIK_PATH, input?.serverId); - return result || []; - } catch (error) { - throw error; - } - }), - - updateTraefikFile: protectedProcedure - .input(apiModifyTraefikConfig) - .mutation(async ({ input, ctx }) => { - if (ctx.user.role === "member") { - const canAccess = await canAccessToTraefikFiles( - ctx.user.id, - ctx.session.activeOrganizationId, - ); - - if (!canAccess) { - throw new TRPCError({ code: "UNAUTHORIZED" }); - } - } - await writeTraefikConfigInPath( - input.path, - input.traefikConfig, - input?.serverId, - ); - return true; - }), - - readTraefikFile: protectedProcedure - .input(apiReadTraefikConfig) - .query(async ({ input, ctx }) => { - if (ctx.user.role === "member") { - const canAccess = await canAccessToTraefikFiles( - ctx.user.id, - ctx.session.activeOrganizationId, - ); - - if (!canAccess) { - throw new TRPCError({ code: "UNAUTHORIZED" }); - } - } - - if (input.serverId) { - const server = await findServerById(input.serverId); - - if (server.organizationId !== ctx.session?.activeOrganizationId) { - throw new TRPCError({ code: "UNAUTHORIZED" }); - } - } - - return readConfigInPath(input.path, input.serverId); - }), - - readTraefikEnv: adminProcedure - .input(apiServerSchema) - .query(async ({ input }) => { - const command = - "docker container inspect dokploy-traefik --format '{{json .Config.Env}}'"; - - let result = ""; - if (input?.serverId) { - const execResult = await execAsyncRemote(input.serverId, command); - result = execResult.stdout; - } else { - const execResult = await execAsync(command); - result = execResult.stdout; - } - const envVars = JSON.parse(result.trim()); - return envVars.join("\n"); - }), - - writeTraefikEnv: adminProcedure - .input(z.object({ env: z.string(), serverId: z.string().optional() })) - .mutation(async ({ input }) => { - const envs = prepareEnvironmentVariables(input.env); - await initializeTraefik({ - env: envs, - serverId: input.serverId, - force: true, - }); - - return true; - }), - haveTraefikDashboardPortEnabled: adminProcedure - .input(apiServerSchema) - .query(async ({ input }) => { - const command = `docker container inspect --format='{{json .NetworkSettings.Ports}}' dokploy-traefik`; - - let stdout = ""; - if (input?.serverId) { - const result = await execAsyncRemote(input.serverId, command); - stdout = result.stdout; - } else if (!IS_CLOUD) { - const result = await execAsync(command); - stdout = result.stdout; - } - - const ports = JSON.parse(stdout.trim()); - return Object.entries(ports).some(([containerPort, bindings]) => { - const [port] = containerPort.split("/"); - return port === "8080" && bindings && (bindings as any[]).length > 0; - }); - }), - - readStatsLogs: adminProcedure - .meta({ - openapi: { - path: "/read-stats-logs", - method: "POST", - override: true, - enabled: false, - }, - }) - .input(apiReadStatsLogs) - .query(async ({ input }) => { - if (IS_CLOUD) { - return { - data: [], - totalCount: 0, - }; - } - const rawConfig = await readMonitoringConfig( - !!input.dateRange?.start && !!input.dateRange?.end, - ); - - const parsedConfig = parseRawConfig( - rawConfig as string, - input.page, - input.sort, - input.search, - input.status, - input.dateRange, - ); - - return parsedConfig; - }), - readStats: adminProcedure - .meta({ - openapi: { - path: "/read-stats", - method: "POST", - override: true, - enabled: false, - }, - }) - .input( - z - .object({ - dateRange: z - .object({ - start: z.string().optional(), - end: z.string().optional(), - }) - .optional(), - }) - .optional(), - ) - .query(async ({ input }) => { - if (IS_CLOUD) { - return []; - } - const rawConfig = await readMonitoringConfig( - !!input?.dateRange?.start || !!input?.dateRange?.end, - ); - const processedLogs = processLogs(rawConfig as string, input?.dateRange); - return processedLogs || []; - }), - haveActivateRequests: adminProcedure.query(async () => { - if (IS_CLOUD) { - return true; - } - const config = readMainConfig(); - - if (!config) return false; - const parsedConfig = load(config) as { - accessLog?: { - filePath: string; - }; - }; - - return !!parsedConfig?.accessLog?.filePath; - }), - toggleRequests: adminProcedure - .input( - z.object({ - enable: z.boolean(), - }), - ) - .mutation(async ({ input }) => { - if (IS_CLOUD) { - return true; - } - const mainConfig = readMainConfig(); - if (!mainConfig) return false; - - const currentConfig = load(mainConfig) as { - accessLog?: { - filePath: string; - }; - }; - - if (input.enable) { - const config = { - accessLog: { - filePath: "/etc/dokploy/traefik/dynamic/access.log", - format: "json", - bufferingSize: 100, - filters: { - retryAttempts: true, - minDuration: "10ms", - }, - }, - }; - currentConfig.accessLog = config.accessLog; - } else { - currentConfig.accessLog = undefined; - } - - writeMainConfig(dump(currentConfig)); - - return true; - }), - isCloud: publicProcedure.query(async () => { - return IS_CLOUD; - }), - health: publicProcedure.query(async () => { - if (IS_CLOUD) { - try { - await db.execute(sql`SELECT 1`); - return { status: "ok" }; - } catch (error) { - console.error("Database connection error:", error); - throw error; - } - } - return { status: "not_cloud" }; - }), - setupGPU: adminProcedure - .input( - z.object({ - serverId: z.string().optional(), - }), - ) - .mutation(async ({ input }) => { - if (IS_CLOUD && !input.serverId) { - throw new Error("Select a server to enable the GPU Setup"); - } - - try { - await setupGPUSupport(input.serverId); - return { success: true }; - } catch (error) { - console.error("GPU Setup Error:", error); - throw error; - } - }), - checkGPUStatus: adminProcedure - .input( - z.object({ - serverId: z.string().optional(), - }), - ) - .query(async ({ input }) => { - if (IS_CLOUD && !input.serverId) { - return { - driverInstalled: false, - driverVersion: undefined, - gpuModel: undefined, - runtimeInstalled: false, - runtimeConfigured: false, - cudaSupport: undefined, - cudaVersion: undefined, - memoryInfo: undefined, - availableGPUs: 0, - swarmEnabled: false, - gpuResources: 0, - }; - } - - try { - return await checkGPUStatus(input.serverId || ""); - } catch (error) { - const message = - error instanceof Error ? error.message : "Failed to check GPU status"; - throw new TRPCError({ - code: "BAD_REQUEST", - message, - }); - } - }), - updateTraefikPorts: adminProcedure - .input( - z.object({ - serverId: z.string().optional(), - additionalPorts: z.array( - z.object({ - targetPort: z.number(), - publishedPort: z.number(), - }), - ), - }), - ) - .mutation(async ({ input }) => { - try { - if (IS_CLOUD && !input.serverId) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "Please set a serverId to update Traefik ports", - }); - } - await initializeTraefik({ - serverId: input.serverId, - additionalPorts: input.additionalPorts, - force: true, - }); - return true; - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: - error instanceof Error - ? error.message - : "Error updating Traefik ports", - cause: error, - }); - } - }), - getTraefikPorts: adminProcedure - .input(apiServerSchema) - .query(async ({ input }) => { - return await getTraefikPorts(input?.serverId); - }), - updateLogCleanup: adminProcedure - .input( - z.object({ - cronExpression: z.string().nullable(), - }), - ) - .mutation(async ({ input }) => { - if (IS_CLOUD) { - return true; - } - if (input.cronExpression) { - return startLogCleanup(input.cronExpression); - } - return stopLogCleanup(); - }), - - getLogCleanupStatus: adminProcedure.query(async () => { - return getLogCleanupStatus(); - }), - - getDokployCloudIps: adminProcedure.query(async () => { - if (!IS_CLOUD) { - return []; - } - const ips = process.env.DOKPLOY_CLOUD_IPS?.split(","); - return ips; - }), }); - -export const getTraefikPorts = async (serverId?: string) => { - const command = `docker container inspect --format='{{json .NetworkSettings.Ports}}' dokploy-traefik`; - try { - let stdout = ""; - if (serverId) { - const result = await execAsyncRemote(serverId, command); - stdout = result.stdout; - } else if (!IS_CLOUD) { - const result = await execAsync(command); - stdout = result.stdout; - } - - const portsMap = JSON.parse(stdout.trim()); - const additionalPorts: Array<{ - targetPort: number; - publishedPort: number; - }> = []; - - // Convert the Docker container port format to our expected format - for (const [containerPort, bindings] of Object.entries(portsMap)) { - if (!bindings) continue; - - const [port = ""] = containerPort.split("/"); - if (!port) continue; - - const targetPortNum = Number.parseInt(port, 10); - if (Number.isNaN(targetPortNum)) continue; - - // Skip default ports - if ([80, 443].includes(targetPortNum)) continue; - - for (const binding of bindings as Array<{ HostPort: string }>) { - if (!binding.HostPort) continue; - const publishedPort = Number.parseInt(binding.HostPort, 10); - if (Number.isNaN(publishedPort)) continue; - - additionalPorts.push({ - targetPort: targetPortNum, - publishedPort, - }); - } - } - - return additionalPorts; - } catch (error) { - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: "Failed to get Traefik ports", - cause: error, - }); - } -};