mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-10 08:25:22 +02:00
fix: resolved merge conflicts with fork/canary
This commit is contained in:
@@ -345,6 +345,7 @@ export const applicationRouter = createTRPCRouter({
|
||||
buildPath: input.buildPath,
|
||||
applicationStatus: "idle",
|
||||
githubId: input.githubId,
|
||||
watchPaths: input.watchPaths,
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -371,6 +372,7 @@ export const applicationRouter = createTRPCRouter({
|
||||
gitlabId: input.gitlabId,
|
||||
gitlabProjectId: input.gitlabProjectId,
|
||||
gitlabPathNamespace: input.gitlabPathNamespace,
|
||||
watchPaths: input.watchPaths,
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -395,6 +397,7 @@ export const applicationRouter = createTRPCRouter({
|
||||
sourceType: "bitbucket",
|
||||
applicationStatus: "idle",
|
||||
bitbucketId: input.bitbucketId,
|
||||
watchPaths: input.watchPaths,
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -467,6 +470,7 @@ export const applicationRouter = createTRPCRouter({
|
||||
customGitSSHKeyId: input.customGitSSHKeyId,
|
||||
sourceType: "git",
|
||||
applicationStatus: "idle",
|
||||
watchPaths: input.watchPaths,
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -695,4 +699,49 @@ export const applicationRouter = createTRPCRouter({
|
||||
|
||||
return stats;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
applicationId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const application = await findApplicationById(input.applicationId);
|
||||
if (
|
||||
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this application",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the application's projectId
|
||||
const updatedApplication = await db
|
||||
.update(applications)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(applications.applicationId, input.applicationId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedApplication) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move application",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedApplication;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
||||
import { type DockerNode, IS_CLOUD, docker, execAsync } from "@dokploy/server";
|
||||
import {
|
||||
type DockerNode,
|
||||
IS_CLOUD,
|
||||
execAsync,
|
||||
getRemoteDocker,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const clusterRouter = createTRPCRouter({
|
||||
getNodes: protectedProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
return [];
|
||||
}
|
||||
const workers: DockerNode[] = await docker.listNodes();
|
||||
getNodes: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
if (IS_CLOUD) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return workers;
|
||||
}),
|
||||
const docker = await getRemoteDocker(input.serverId);
|
||||
const workers: DockerNode[] = await docker.listNodes();
|
||||
|
||||
return workers;
|
||||
}),
|
||||
removeWorker: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
nodeId: z.string(),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
@@ -40,37 +53,51 @@ export const clusterRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}),
|
||||
addWorker: protectedProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
return {
|
||||
command: "",
|
||||
version: "",
|
||||
};
|
||||
}
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
addWorker: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
if (IS_CLOUD) {
|
||||
return {
|
||||
command: "",
|
||||
version: "",
|
||||
};
|
||||
}
|
||||
const docker = await getRemoteDocker(input.serverId);
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
|
||||
return {
|
||||
command: `docker swarm join --token ${
|
||||
result.JoinTokens.Worker
|
||||
} ${await getPublicIpWithFallback()}:2377`,
|
||||
version: docker_version.Version,
|
||||
};
|
||||
}),
|
||||
addManager: protectedProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
return {
|
||||
command: "",
|
||||
version: "",
|
||||
command: `docker swarm join --token ${
|
||||
result.JoinTokens.Worker
|
||||
} ${await getPublicIpWithFallback()}:2377`,
|
||||
version: docker_version.Version,
|
||||
};
|
||||
}
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
return {
|
||||
command: `docker swarm join --token ${
|
||||
result.JoinTokens.Manager
|
||||
} ${await getPublicIpWithFallback()}:2377`,
|
||||
version: docker_version.Version,
|
||||
};
|
||||
}),
|
||||
}),
|
||||
addManager: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
if (IS_CLOUD) {
|
||||
return {
|
||||
command: "",
|
||||
version: "",
|
||||
};
|
||||
}
|
||||
const docker = await getRemoteDocker(input.serverId);
|
||||
const result = await docker.swarmInspect();
|
||||
const docker_version = await docker.version();
|
||||
return {
|
||||
command: `docker swarm join --token ${
|
||||
result.JoinTokens.Manager
|
||||
} ${await getPublicIpWithFallback()}:2377`,
|
||||
version: docker_version.Version,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,29 +2,28 @@ import { slugify } from "@/lib/slug";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
apiCreateCompose,
|
||||
apiCreateComposeByTemplate,
|
||||
apiDeleteCompose,
|
||||
apiFetchServices,
|
||||
apiFindCompose,
|
||||
apiRandomizeCompose,
|
||||
apiUpdateCompose,
|
||||
compose,
|
||||
compose as composeTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
||||
import { templates } from "@/templates/templates";
|
||||
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import {
|
||||
generatePassword,
|
||||
loadTemplateModule,
|
||||
readTemplateComposeFile,
|
||||
} from "@/templates/utils";
|
||||
type CompleteTemplate,
|
||||
fetchTemplateFiles,
|
||||
fetchTemplatesList,
|
||||
} from "@dokploy/server/templates/github";
|
||||
import { processTemplate } from "@dokploy/server/templates/processors";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { dump } from "js-yaml";
|
||||
import { dump, load } from "js-yaml";
|
||||
import _ from "lodash";
|
||||
import { nanoid } from "nanoid";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||
import { z } from "zod";
|
||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||
import { deploy } from "@/server/utils/deploy";
|
||||
import {
|
||||
@@ -39,6 +38,7 @@ import {
|
||||
createComposeByTemplate,
|
||||
createDomain,
|
||||
createMount,
|
||||
deleteMount,
|
||||
findComposeById,
|
||||
findDomainsByComposeId,
|
||||
findProjectById,
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
removeCompose,
|
||||
removeComposeDirectory,
|
||||
removeDeploymentsByComposeId,
|
||||
removeDomainById,
|
||||
startCompose,
|
||||
stopCompose,
|
||||
updateCompose,
|
||||
@@ -157,8 +158,8 @@ export const composeRouter = createTRPCRouter({
|
||||
4;
|
||||
|
||||
const result = await db
|
||||
.delete(compose)
|
||||
.where(eq(compose.composeId, input.composeId))
|
||||
.delete(composeTable)
|
||||
.where(eq(composeTable.composeId, input.composeId))
|
||||
.returning();
|
||||
|
||||
const cleanupOperations = [
|
||||
@@ -397,7 +398,14 @@ export const composeRouter = createTRPCRouter({
|
||||
return true;
|
||||
}),
|
||||
deployTemplate: protectedProcedure
|
||||
.input(apiCreateComposeByTemplate)
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
serverId: z.string().optional(),
|
||||
id: z.string(),
|
||||
baseUrl: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
if (ctx.user.rol === "member") {
|
||||
await checkServiceAccess(
|
||||
@@ -415,9 +423,7 @@ export const composeRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
const composeFile = await readTemplateComposeFile(input.id);
|
||||
|
||||
const generate = await loadTemplateModule(input.id as TemplatesKeys);
|
||||
const template = await fetchTemplateFiles(input.id, input.baseUrl);
|
||||
|
||||
const admin = await findUserById(ctx.user.ownerId);
|
||||
let serverIp = admin.serverIp || "127.0.0.1";
|
||||
@@ -430,16 +436,17 @@ export const composeRouter = createTRPCRouter({
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
serverIp = "127.0.0.1";
|
||||
}
|
||||
|
||||
const projectName = slugify(`${project.name} ${input.id}`);
|
||||
const { envs, mounts, domains } = generate({
|
||||
serverIp: serverIp || "",
|
||||
const generate = processTemplate(template.config, {
|
||||
serverIp: serverIp,
|
||||
projectName: projectName,
|
||||
});
|
||||
|
||||
const compose = await createComposeByTemplate({
|
||||
...input,
|
||||
composeFile: composeFile,
|
||||
env: envs?.join("\n"),
|
||||
composeFile: template.dockerCompose,
|
||||
env: generate.envs?.join("\n"),
|
||||
serverId: input.serverId,
|
||||
name: input.id,
|
||||
sourceType: "raw",
|
||||
@@ -451,12 +458,12 @@ export const composeRouter = createTRPCRouter({
|
||||
await addNewService(
|
||||
ctx.user.id,
|
||||
compose.composeId,
|
||||
project.organizationId,
|
||||
ctx.session.activeOrganizationId,
|
||||
);
|
||||
}
|
||||
|
||||
if (mounts && mounts?.length > 0) {
|
||||
for (const mount of mounts) {
|
||||
if (generate.mounts && generate.mounts?.length > 0) {
|
||||
for (const mount of generate.mounts) {
|
||||
await createMount({
|
||||
filePath: mount.filePath,
|
||||
mountPath: "",
|
||||
@@ -468,13 +475,14 @@ export const composeRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
|
||||
if (domains && domains?.length > 0) {
|
||||
for (const domain of domains) {
|
||||
if (generate.domains && generate.domains?.length > 0) {
|
||||
for (const domain of generate.domains) {
|
||||
await createDomain({
|
||||
...domain,
|
||||
domainType: "compose",
|
||||
certificateType: "none",
|
||||
composeId: compose.composeId,
|
||||
host: domain.host || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -482,23 +490,238 @@ export const composeRouter = createTRPCRouter({
|
||||
return null;
|
||||
}),
|
||||
|
||||
templates: protectedProcedure.query(async () => {
|
||||
const templatesData = templates.map((t) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
id: t.id,
|
||||
links: t.links,
|
||||
tags: t.tags,
|
||||
logo: t.logo,
|
||||
version: t.version,
|
||||
}));
|
||||
templates: publicProcedure
|
||||
.input(z.object({ baseUrl: z.string().optional() }))
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const githubTemplates = await fetchTemplatesList(input.baseUrl);
|
||||
|
||||
return templatesData;
|
||||
}),
|
||||
if (githubTemplates.length > 0) {
|
||||
return githubTemplates;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"Failed to fetch templates from GitHub, falling back to local templates:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
|
||||
getTags: protectedProcedure.query(async () => {
|
||||
const allTags = templates.flatMap((template) => template.tags);
|
||||
const uniqueTags = _.uniq(allTags);
|
||||
return uniqueTags;
|
||||
}),
|
||||
getTags: protectedProcedure
|
||||
.input(z.object({ baseUrl: z.string().optional() }))
|
||||
.query(async ({ input }) => {
|
||||
const githubTemplates = await fetchTemplatesList(input.baseUrl);
|
||||
|
||||
const allTags = githubTemplates.flatMap((template) => template.tags);
|
||||
const uniqueTags = _.uniq(allTags);
|
||||
return uniqueTags;
|
||||
}),
|
||||
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
composeId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this compose",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedCompose = await db
|
||||
.update(composeTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(composeTable.composeId, input.composeId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedCompose) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move compose",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedCompose;
|
||||
}),
|
||||
|
||||
processTemplate: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
base64: z.string(),
|
||||
composeId: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
|
||||
if (
|
||||
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to update this compose",
|
||||
});
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
if (compose.serverId) {
|
||||
const server = await findServerById(compose.serverId);
|
||||
serverIp = server.ipAddress;
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
serverIp = "127.0.0.1";
|
||||
}
|
||||
const templateData = JSON.parse(decodedData);
|
||||
const config = load(templateData.config) as CompleteTemplate;
|
||||
|
||||
if (!templateData.compose || !config) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
"Invalid template format. Must contain compose and config fields",
|
||||
});
|
||||
}
|
||||
|
||||
const processedTemplate = processTemplate(config, {
|
||||
serverIp: serverIp,
|
||||
projectName: compose.appName,
|
||||
});
|
||||
|
||||
return {
|
||||
compose: templateData.compose,
|
||||
template: processedTemplate,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `Error processing template: ${error instanceof Error ? error.message : error}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
import: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
base64: z.string(),
|
||||
composeId: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
const decodedData = Buffer.from(input.base64, "base64").toString(
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
if (
|
||||
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to update this compose",
|
||||
});
|
||||
}
|
||||
|
||||
for (const mount of compose.mounts) {
|
||||
await deleteMount(mount.mountId);
|
||||
}
|
||||
|
||||
for (const domain of compose.domains) {
|
||||
await removeDomainById(domain.domainId);
|
||||
}
|
||||
|
||||
const admin = await findUserById(ctx.user.ownerId);
|
||||
let serverIp = admin.serverIp || "127.0.0.1";
|
||||
|
||||
if (compose.serverId) {
|
||||
const server = await findServerById(compose.serverId);
|
||||
serverIp = server.ipAddress;
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
serverIp = "127.0.0.1";
|
||||
}
|
||||
|
||||
const templateData = JSON.parse(decodedData);
|
||||
const config = load(templateData.config) as CompleteTemplate;
|
||||
|
||||
if (!templateData.compose || !config) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
"Invalid template format. Must contain compose and config fields",
|
||||
});
|
||||
}
|
||||
|
||||
const processedTemplate = processTemplate(config, {
|
||||
serverIp: serverIp,
|
||||
projectName: compose.appName,
|
||||
});
|
||||
|
||||
// Update compose file
|
||||
await updateCompose(input.composeId, {
|
||||
composeFile: templateData.compose,
|
||||
sourceType: "raw",
|
||||
env: processedTemplate.envs?.join("\n"),
|
||||
isolatedDeployment: true,
|
||||
});
|
||||
|
||||
// Create mounts
|
||||
if (processedTemplate.mounts && processedTemplate.mounts.length > 0) {
|
||||
for (const mount of processedTemplate.mounts) {
|
||||
await createMount({
|
||||
filePath: mount.filePath,
|
||||
mountPath: "",
|
||||
content: mount.content,
|
||||
serviceId: compose.composeId,
|
||||
serviceType: "compose",
|
||||
type: "file",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create domains
|
||||
if (processedTemplate.domains && processedTemplate.domains.length > 0) {
|
||||
for (const domain of processedTemplate.domains) {
|
||||
await createDomain({
|
||||
...domain,
|
||||
domainType: "compose",
|
||||
certificateType: "none",
|
||||
composeId: compose.composeId,
|
||||
host: domain.host || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Template imported successfully",
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `Error importing template: ${error instanceof Error ? error.message : error}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -65,10 +65,15 @@ export const dockerRouter = createTRPCRouter({
|
||||
z.object({
|
||||
appName: z.string().min(1),
|
||||
serverId: z.string().optional(),
|
||||
type: z.enum(["standalone", "swarm"]),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
return await getContainersByAppLabel(input.appName, input.serverId);
|
||||
return await getContainersByAppLabel(
|
||||
input.appName,
|
||||
input.type,
|
||||
input.serverId,
|
||||
);
|
||||
}),
|
||||
|
||||
getStackContainersByAppName: protectedProcedure
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
apiSaveEnvironmentVariablesMariaDB,
|
||||
apiSaveExternalPortMariaDB,
|
||||
apiUpdateMariaDB,
|
||||
apiRebuildMariadb,
|
||||
mariadb as mariadbTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
@@ -30,7 +32,10 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { rebuildDatabase } from "@dokploy/server";
|
||||
export const mariadbRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateMariaDB)
|
||||
@@ -320,6 +325,63 @@ export const mariadbRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
mariadbId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mariadb = await findMariadbById(input.mariadbId);
|
||||
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mariadb",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mariadb's projectId
|
||||
const updatedMariadb = await db
|
||||
.update(mariadbTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(mariadbTable.mariadbId, input.mariadbId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedMariadb) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move mariadb",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedMariadb;
|
||||
}),
|
||||
rebuild: protectedProcedure
|
||||
.input(apiRebuildMariadb)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mariadb = await findMariadbById(input.mariadbId);
|
||||
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to rebuild this MariaDB database",
|
||||
});
|
||||
}
|
||||
|
||||
await rebuildDatabase(mariadb.mariadbId, "mariadb");
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
apiCreateMongo,
|
||||
apiDeployMongo,
|
||||
apiFindOneMongo,
|
||||
apiRebuildMongo,
|
||||
apiResetMongo,
|
||||
apiSaveEnvironmentVariablesMongo,
|
||||
apiSaveExternalPortMongo,
|
||||
apiUpdateMongo,
|
||||
mongo as mongoTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
@@ -30,7 +32,10 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { rebuildDatabase } from "@dokploy/server";
|
||||
export const mongoRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateMongo)
|
||||
@@ -334,6 +339,64 @@ export const mongoRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
mongoId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mongo = await findMongoById(input.mongoId);
|
||||
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mongo",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mongo's projectId
|
||||
const updatedMongo = await db
|
||||
.update(mongoTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(mongoTable.mongoId, input.mongoId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedMongo) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move mongo",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedMongo;
|
||||
}),
|
||||
rebuild: protectedProcedure
|
||||
.input(apiRebuildMongo)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mongo = await findMongoById(input.mongoId);
|
||||
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to rebuild this MongoDB database",
|
||||
});
|
||||
}
|
||||
|
||||
await rebuildDatabase(mongo.mongoId, "mongo");
|
||||
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
apiCreateMySql,
|
||||
apiDeployMySql,
|
||||
apiFindOneMySql,
|
||||
apiRebuildMysql,
|
||||
apiResetMysql,
|
||||
apiSaveEnvironmentVariablesMySql,
|
||||
apiSaveExternalPortMySql,
|
||||
apiUpdateMySql,
|
||||
mysql as mysqlTable,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -23,6 +25,7 @@ import {
|
||||
findBackupsByDbId,
|
||||
findMySqlById,
|
||||
findProjectById,
|
||||
rebuildDatabase,
|
||||
removeMySqlById,
|
||||
removeService,
|
||||
startService,
|
||||
@@ -32,6 +35,9 @@ import {
|
||||
updateMySqlById,
|
||||
} from "@dokploy/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { z } from "zod";
|
||||
|
||||
export const mysqlRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -330,6 +336,64 @@ export const mysqlRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
mysqlId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mysql = await findMySqlById(input.mysqlId);
|
||||
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mysql",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mysql's projectId
|
||||
const updatedMysql = await db
|
||||
.update(mysqlTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(mysqlTable.mysqlId, input.mysqlId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedMysql) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move mysql",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedMysql;
|
||||
}),
|
||||
rebuild: protectedProcedure
|
||||
.input(apiRebuildMysql)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const mysql = await findMySqlById(input.mysqlId);
|
||||
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to rebuild this MySQL database",
|
||||
});
|
||||
}
|
||||
|
||||
await rebuildDatabase(mysql.mysqlId, "mysql");
|
||||
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -4,10 +4,12 @@ import {
|
||||
apiCreatePostgres,
|
||||
apiDeployPostgres,
|
||||
apiFindOnePostgres,
|
||||
apiRebuildPostgres,
|
||||
apiResetPostgres,
|
||||
apiSaveEnvironmentVariablesPostgres,
|
||||
apiSaveExternalPortPostgres,
|
||||
apiUpdatePostgres,
|
||||
postgres as postgresTable,
|
||||
} from "@/server/db/schema";
|
||||
import { cancelJobs } from "@/server/utils/backup";
|
||||
import {
|
||||
@@ -20,6 +22,7 @@ import {
|
||||
findBackupsByDbId,
|
||||
findPostgresById,
|
||||
findProjectById,
|
||||
rebuildDatabase,
|
||||
removePostgresById,
|
||||
removeService,
|
||||
startService,
|
||||
@@ -30,7 +33,9 @@ import {
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
export const postgresRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreatePostgres)
|
||||
@@ -350,6 +355,68 @@ export const postgresRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
postgresId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const postgres = await findPostgresById(input.postgresId);
|
||||
if (
|
||||
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this postgres",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the postgres's projectId
|
||||
const updatedPostgres = await db
|
||||
.update(postgresTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(postgresTable.postgresId, input.postgresId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedPostgres) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move postgres",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedPostgres;
|
||||
}),
|
||||
rebuild: protectedProcedure
|
||||
.input(apiRebuildPostgres)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const postgres = await findPostgresById(input.postgresId);
|
||||
if (
|
||||
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to rebuild this Postgres database",
|
||||
});
|
||||
}
|
||||
|
||||
await rebuildDatabase(postgres.postgresId, "postgres");
|
||||
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
apiSaveEnvironmentVariablesRedis,
|
||||
apiSaveExternalPortRedis,
|
||||
apiUpdateRedis,
|
||||
redis as redisTable,
|
||||
apiRebuildRedis,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -30,7 +32,10 @@ import {
|
||||
updateRedisById,
|
||||
} from "@dokploy/server";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/server/db";
|
||||
import { z } from "zod";
|
||||
import { rebuildDatabase } from "@dokploy/server";
|
||||
export const redisRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateRedis)
|
||||
@@ -314,6 +319,63 @@ export const redisRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
redisId: z.string(),
|
||||
targetProjectId: z.string(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const redis = await findRedisById(input.redisId);
|
||||
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this redis",
|
||||
});
|
||||
}
|
||||
|
||||
const targetProject = await findProjectById(input.targetProjectId);
|
||||
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this project",
|
||||
});
|
||||
}
|
||||
|
||||
// Update the redis's projectId
|
||||
const updatedRedis = await db
|
||||
.update(redisTable)
|
||||
.set({
|
||||
projectId: input.targetProjectId,
|
||||
})
|
||||
.where(eq(redisTable.redisId, input.redisId))
|
||||
.returning()
|
||||
.then((res) => res[0]);
|
||||
|
||||
if (!updatedRedis) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to move redis",
|
||||
});
|
||||
}
|
||||
|
||||
return updatedRedis;
|
||||
}),
|
||||
rebuild: protectedProcedure
|
||||
.input(apiRebuildRedis)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const redis = await findRedisById(input.redisId);
|
||||
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to rebuild this Redis database",
|
||||
});
|
||||
}
|
||||
|
||||
await rebuildDatabase(redis.redisId, "redis");
|
||||
return true;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
getDokployImageTag,
|
||||
getUpdateData,
|
||||
initializeTraefik,
|
||||
logRotationManager,
|
||||
parseRawConfig,
|
||||
paths,
|
||||
prepareEnvironmentVariables,
|
||||
@@ -42,10 +41,6 @@ import {
|
||||
recreateDirectory,
|
||||
sendDockerCleanupNotifications,
|
||||
spawnAsync,
|
||||
startService,
|
||||
startServiceRemote,
|
||||
stopService,
|
||||
stopServiceRemote,
|
||||
updateLetsEncryptEmail,
|
||||
updateServerById,
|
||||
updateServerTraefik,
|
||||
@@ -53,6 +48,9 @@ import {
|
||||
writeConfig,
|
||||
writeMainConfig,
|
||||
writeTraefikConfigInPath,
|
||||
startLogCleanup,
|
||||
stopLogCleanup,
|
||||
getLogCleanupStatus,
|
||||
} from "@dokploy/server";
|
||||
import { checkGPUStatus, setupGPUSupport } from "@dokploy/server";
|
||||
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
|
||||
@@ -86,11 +84,9 @@ export const settingsRouter = createTRPCRouter({
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
if (input?.serverId) {
|
||||
await stopServiceRemote(input.serverId, "dokploy-traefik");
|
||||
await startServiceRemote(input.serverId, "dokploy-traefik");
|
||||
await execAsync("docker restart dokploy-traefik");
|
||||
} else if (!IS_CLOUD) {
|
||||
await stopService("dokploy-traefik");
|
||||
await startService("dokploy-traefik");
|
||||
await execAsync("docker restart dokploy-traefik");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -101,13 +97,20 @@ export const settingsRouter = createTRPCRouter({
|
||||
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 }) => {
|
||||
@@ -512,16 +515,18 @@ export const settingsRouter = createTRPCRouter({
|
||||
.input(apiServerSchema)
|
||||
.query(async ({ input }) => {
|
||||
const command =
|
||||
"docker service inspect --format='{{range .Spec.TaskTemplate.ContainerSpec.Env}}{{println .}}{{end}}' dokploy-traefik";
|
||||
"docker container inspect dokploy-traefik --format '{{json .Config.Env}}'";
|
||||
|
||||
let result = "";
|
||||
if (input?.serverId) {
|
||||
const result = await execAsyncRemote(input.serverId, command);
|
||||
return result.stdout.trim();
|
||||
}
|
||||
if (!IS_CLOUD) {
|
||||
const result = await execAsync(command);
|
||||
return result.stdout.trim();
|
||||
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
|
||||
@@ -531,6 +536,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
await initializeTraefik({
|
||||
env: envs,
|
||||
serverId: input.serverId,
|
||||
force: true,
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -538,27 +544,22 @@ export const settingsRouter = createTRPCRouter({
|
||||
haveTraefikDashboardPortEnabled: adminProcedure
|
||||
.input(apiServerSchema)
|
||||
.query(async ({ input }) => {
|
||||
const command = `docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik`;
|
||||
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(
|
||||
"docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik",
|
||||
);
|
||||
const result = await execAsync(command);
|
||||
stdout = result.stdout;
|
||||
}
|
||||
|
||||
const parsed: any[] = JSON.parse(stdout.trim());
|
||||
for (const port of parsed) {
|
||||
if (port.PublishedPort === 8080) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
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
|
||||
@@ -578,48 +579,51 @@ export const settingsRouter = createTRPCRouter({
|
||||
totalCount: 0,
|
||||
};
|
||||
}
|
||||
const rawConfig = readMonitoringConfig();
|
||||
const rawConfig = 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.query(() => {
|
||||
if (IS_CLOUD) {
|
||||
return [];
|
||||
}
|
||||
const rawConfig = readMonitoringConfig();
|
||||
const processedLogs = processLogs(rawConfig as string);
|
||||
return processedLogs || [];
|
||||
}),
|
||||
getLogRotateStatus: adminProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
return true;
|
||||
}
|
||||
return await logRotationManager.getStatus();
|
||||
}),
|
||||
toggleLogRotate: adminProcedure
|
||||
readStats: adminProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
path: "/read-stats",
|
||||
method: "POST",
|
||||
override: true,
|
||||
enabled: false,
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
enable: z.boolean(),
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
dateRange: z
|
||||
.object({
|
||||
start: z.string().optional(),
|
||||
end: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
.query(({ input }) => {
|
||||
if (IS_CLOUD) {
|
||||
return true;
|
||||
return [];
|
||||
}
|
||||
if (input.enable) {
|
||||
await logRotationManager.activate();
|
||||
} else {
|
||||
await logRotationManager.deactivate();
|
||||
}
|
||||
|
||||
return true;
|
||||
const rawConfig = readMonitoringConfig(
|
||||
!!input?.dateRange?.start || !!input?.dateRange?.end,
|
||||
);
|
||||
const processedLogs = processLogs(rawConfig as string, input?.dateRange);
|
||||
return processedLogs || [];
|
||||
}),
|
||||
haveActivateRequests: adminProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
@@ -752,7 +756,6 @@ export const settingsRouter = createTRPCRouter({
|
||||
z.object({
|
||||
targetPort: z.number(),
|
||||
publishedPort: z.number(),
|
||||
publishMode: z.enum(["ingress", "host"]).default("host"),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
@@ -768,6 +771,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
await initializeTraefik({
|
||||
serverId: input.serverId,
|
||||
additionalPorts: input.additionalPorts,
|
||||
force: true,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -784,47 +788,75 @@ export const settingsRouter = createTRPCRouter({
|
||||
getTraefikPorts: adminProcedure
|
||||
.input(apiServerSchema)
|
||||
.query(async ({ input }) => {
|
||||
const command = `docker service inspect --format='{{json .Endpoint.Ports}}' dokploy-traefik`;
|
||||
return await getTraefikPorts(input?.serverId);
|
||||
}),
|
||||
updateLogCleanup: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
cronExpression: z.string().nullable(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
if (input.cronExpression) {
|
||||
return startLogCleanup(input.cronExpression);
|
||||
}
|
||||
return stopLogCleanup();
|
||||
}),
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
getLogCleanupStatus: adminProcedure.query(async () => {
|
||||
return getLogCleanupStatus();
|
||||
}),
|
||||
});
|
||||
|
||||
const ports: {
|
||||
Protocol: string;
|
||||
TargetPort: number;
|
||||
PublishedPort: number;
|
||||
PublishMode: string;
|
||||
}[] = JSON.parse(stdout.trim());
|
||||
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;
|
||||
}
|
||||
|
||||
// Filter out the default ports (80, 443, and optionally 8080)
|
||||
const additionalPorts = ports
|
||||
.filter((port) => ![80, 443, 8080].includes(port.PublishedPort))
|
||||
.map((port) => ({
|
||||
targetPort: port.TargetPort,
|
||||
publishedPort: port.PublishedPort,
|
||||
publishMode: port.PublishMode.toLowerCase() as "host" | "ingress",
|
||||
}));
|
||||
const portsMap = JSON.parse(stdout.trim());
|
||||
const additionalPorts: Array<{
|
||||
targetPort: number;
|
||||
publishedPort: number;
|
||||
}> = [];
|
||||
|
||||
return additionalPorts;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to get Traefik ports",
|
||||
cause: error,
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
// {
|
||||
// "Parallelism": 1,
|
||||
// "Delay": 10000000000,
|
||||
// "FailureAction": "rollback",
|
||||
// "Order": "start-first"
|
||||
// }
|
||||
}
|
||||
|
||||
return additionalPorts;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to get Traefik ports",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user