diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts index 85eb9763..68f5e4e0 100644 --- a/apps/dokploy/server/api/root.ts +++ b/apps/dokploy/server/api/root.ts @@ -21,6 +21,7 @@ import { mysqlRouter } from "./routers/mysql"; import { notificationRouter } from "./routers/notification"; import { portRouter } from "./routers/port"; import { postgresRouter } from "./routers/postgres"; +import { previewDeploymentRouter } from "./routers/preview-deployment"; import { projectRouter } from "./routers/project"; import { redirectsRouter } from "./routers/redirects"; import { redisRouter } from "./routers/redis"; @@ -30,8 +31,8 @@ import { serverRouter } from "./routers/server"; import { settingsRouter } from "./routers/settings"; import { sshRouter } from "./routers/ssh-key"; import { stripeRouter } from "./routers/stripe"; +import { swarmRouter } from "./routers/swarm"; import { userRouter } from "./routers/user"; -import { previewDeploymentRouter } from "./routers/preview-deployment"; /** * This is the primary router for your server. @@ -73,6 +74,7 @@ export const appRouter = createTRPCRouter({ github: githubRouter, server: serverRouter, stripe: stripeRouter, + swarm: swarmRouter, }); // export type definition of API diff --git a/apps/dokploy/server/api/routers/swarm.ts b/apps/dokploy/server/api/routers/swarm.ts new file mode 100644 index 00000000..fe15d0ef --- /dev/null +++ b/apps/dokploy/server/api/routers/swarm.ts @@ -0,0 +1,31 @@ +import { + getApplicationInfo, + getNodeApplications, + getNodeInfo, + getSwarmNodes, +} from "@dokploy/server"; +import { z } from "zod"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; + +export const swarmRouter = createTRPCRouter({ + getNodes: protectedProcedure.query(async () => { + return await getSwarmNodes(); + }), + getNodeInfo: protectedProcedure + .input(z.object({ nodeId: z.string() })) + .query(async ({ input }) => { + return await getNodeInfo(input.nodeId); + }), + getNodeApps: protectedProcedure.query(async () => { + return getNodeApplications(); + }), + getAppInfos: protectedProcedure + .input( + z.object({ + appName: z.string(), + }), + ) + .query(async ({ input }) => { + return await getApplicationInfo(input.appName); + }), +}); diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts index 6ac61354..8681cb22 100644 --- a/packages/server/src/services/docker.ts +++ b/packages/server/src/services/docker.ts @@ -224,3 +224,106 @@ export const containerRestart = async (containerId: string) => { return config; } catch (error) {} }; + +export const getSwarmNodes = async () => { + try { + const { stdout, stderr } = await execAsync( + "docker node ls --format '{{json .}}'", + ); + + if (stderr) { + console.error(`Error: ${stderr}`); + return; + } + + const nodes = JSON.parse(stdout); + + const nodesArray = stdout + .trim() + .split("\n") + .map((line) => JSON.parse(line)); + return nodesArray; + } catch (error) {} +}; + +export const getNodeInfo = async (nodeId: string) => { + try { + const { stdout, stderr } = await execAsync( + `docker node inspect ${nodeId} --format '{{json .}}'`, + ); + + if (stderr) { + console.error(`Error: ${stderr}`); + return; + } + + const nodeInfo = JSON.parse(stdout); + + return nodeInfo; + } catch (error) {} +}; + +export const getNodeApplications = async () => { + try { + // TODO: Implement this + // const { stdout, stderr } = await execAsync( + // `docker service ls --format '{{json .}}'` + // ); + + const stdout = `{"ID":"pxvnj68dxom9","Image":"dokploy/dokploy:latest","Mode":"replicated","Name":"dokploy","Ports":"","Replicas":"1/1"} +{"ID":"1sweo6dr2vrn","Image":"postgres:16","Mode":"replicated","Name":"dokploy-postgres","Ports":"","Replicas":"1/1"} +{"ID":"tnl2fck3rbop","Image":"redis:7","Mode":"replicated","Name":"dokploy-redis","Ports":"","Replicas":"1/1"} +{"ID":"o9ady4y1p96x","Image":"traefik:v3.1.2","Mode":"replicated","Name":"dokploy-traefik","Ports":"","Replicas":"1/1"} +{"ID":"rsxe3l71h9y4","Image":"esports-manager-api-eg8t7w:latest","Mode":"replicated","Name":"esports-manager-api-eg8t7w","Ports":"","Replicas":"1/1"} +{"ID":"fw52vzcw5dc0","Image":"team-synix-admin-dvgspy:latest","Mode":"replicated","Name":"team-synix-admin-dvgspy","Ports":"","Replicas":"1/1"} +{"ID":"551bwmtd6b4t","Image":"team-synix-leaderboard-9vx8ca:latest","Mode":"replicated","Name":"team-synix-leaderboard-9vx8ca","Ports":"","Replicas":"1/1"} +{"ID":"h1eyg3g1tyn3","Image":"postgres:15","Mode":"replicated","Name":"team-synix-webpage-db-fkivnf","Ports":"","Replicas":"1/1"}`; + + // if (stderr) { + // console.error(`Error: ${stderr}`); + // return; + // } + + const appArray = stdout + .trim() + .split("\n") + .map((line) => JSON.parse(line)); + + console.log(appArray); + return appArray; + } catch (error) {} +}; + +export const getApplicationInfo = async (appName: string) => { + try { + // TODO: Implement this + // const { stdout, stderr } = await execAsync( + // `docker service ps ${appName} --format '{{json .}}'` + // ); + + const stdout = `{"CurrentState":"Running 2 weeks ago","DesiredState":"Running","Error":"","ID":"nx8jxlmb8niw","Image":"postgres:16","Name":"dokploy-postgres.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Running 2 weeks ago","DesiredState":"Running","Error":"","ID":"s288g9lwtvi4","Image":"redis:7","Name":"dokploy-redis.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Running 2 weeks ago","DesiredState":"Running","Error":"","ID":"2vcmejz51b23","Image":"traefik:v3.1.2","Name":"dokploy-traefik.1","Node":"v2202411192718297480","Ports":"*:80-\u003e80/tcp,*:80-\u003e80/tcp,*:443-\u003e443/tcp,*:443-\u003e443/tcp"} +{"CurrentState":"Running 26 hours ago","DesiredState":"Running","Error":"","ID":"79iatnbsm2um","Image":"dokploy/dokploy:latest","Name":"dokploy.1","Node":"v2202411192718297480","Ports":"*:3000-\u003e3000/tcp,*:3000-\u003e3000/tcp"} +{"CurrentState":"Shutdown 26 hours ago","DesiredState":"Shutdown","Error":"","ID":"zcwxs501zs7w","Image":"dokploy/dokploy:latest","Name":"dokploy.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Shutdown 7 days ago","DesiredState":"Shutdown","Error":"","ID":"t59qhkoenno4","Image":"dokploy/dokploy:latest","Name":"dokploy.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Shutdown 7 days ago","DesiredState":"Shutdown","Error":"","ID":"o5xtcuj6um7e","Image":"dokploy/dokploy:latest","Name":"dokploy.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Running 2 weeks ago","DesiredState":"Running","Error":"","ID":"q1lr9rmf452g","Image":"esports-manager-api-eg8t7w:latest","Name":"esports-manager-api-eg8t7w.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Shutdown 2 weeks ago","DesiredState":"Shutdown","Error":"","ID":"y9ixpg6b8qdo","Image":"esports-manager-api-eg8t7w:latest","Name":"esports-manager-api-eg8t7w.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Running 24 hours ago","DesiredState":"Running","Error":"","ID":"xgcb919qjg1a","Image":"team-synix-admin-dvgspy:latest","Name":"team-synix-admin-dvgspy.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Running 26 hours ago","DesiredState":"Running","Error":"","ID":"7yi95wh8zhh6","Image":"team-synix-leaderboard-9vx8ca:latest","Name":"team-synix-leaderboard-9vx8ca.1","Node":"v2202411192718297480","Ports":""} +{"CurrentState":"Running 2 weeks ago","DesiredState":"Running","Error":"","ID":"89yzsnghpbq6","Image":"postgres:15","Name":"team-synix-webpage-db-fkivnf.1","Node":"v2202411192718297480","Ports":""}`; + + // if (stderr) { + // console.error(`Error: ${stderr}`); + // return; + // } + + const appArray = stdout + .trim() + .split("\n") + .map((line) => JSON.parse(line)); + + return appArray; + } catch (error) {} +};