mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-21 07:05:21 +02:00
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.
This commit is contained in:
@@ -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",
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ export const ShowDomains = ({ id, type }: Props) => {
|
||||
const [validationStates, setValidationStates] = useState<ValidationStates>(
|
||||
{},
|
||||
);
|
||||
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()
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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<DockerProvider>({
|
||||
defaultValues: {},
|
||||
resolver: zodResolver(DockerProviderSchema),
|
||||
|
||||
@@ -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<DockerProvider>({
|
||||
defaultValues: {},
|
||||
resolver: zodResolver(DockerProviderSchema),
|
||||
|
||||
@@ -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<DockerProvider>({
|
||||
defaultValues: {},
|
||||
resolver: zodResolver(DockerProviderSchema),
|
||||
|
||||
@@ -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<DockerProvider>({
|
||||
|
||||
@@ -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<DockerProvider>({
|
||||
defaultValues: {},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ const Dashboard = () => {
|
||||
false,
|
||||
);
|
||||
|
||||
const { data: monitoring, isLoading } = api.user.getMetricsToken.useQuery();
|
||||
const { data: webServer, isLoading } = api.webServer.get.useQuery();
|
||||
return (
|
||||
<div className="space-y-4 pb-10">
|
||||
{/* <AlertBlock>
|
||||
@@ -59,12 +59,12 @@ const Dashboard = () => {
|
||||
<ShowPaidMonitoring
|
||||
BASE_URL={
|
||||
process.env.NODE_ENV === "production"
|
||||
? `http://${monitoring?.serverIp}:${monitoring?.metricsConfig?.server?.port}/metrics`
|
||||
? `http://${webServer?.serverIp}:${webServer?.metricsConfig?.server?.port}/metrics`
|
||||
: BASE_URL
|
||||
}
|
||||
token={
|
||||
process.env.NODE_ENV === "production"
|
||||
? monitoring?.metricsConfig?.server?.token
|
||||
? webServer?.metricsConfig?.server?.token
|
||||
: DEFAULT_TOKEN
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user