diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index f7a6a546..90d17364 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -133,7 +133,9 @@ export const AddTemplate = ({ projectId }: Props) => { + + + + + + + + ); +}; + +export default Page; + +Page.getLayout = (page: ReactElement) => { + return {page}; +}; +export async function getServerSideProps( + ctx: GetServerSidePropsContext<{ serviceId: string }>, +) { + const { req, res } = ctx; + const { user, session } = await validateRequest(ctx.req, ctx.res); + if (!user) { + return { + redirect: { + permanent: true, + destination: "/", + }, + }; + } + if (user.rol === "user") { + return { + redirect: { + permanent: true, + destination: "/dashboard/settings/profile", + }, + }; + } + + const helpers = createServerSideHelpers({ + router: appRouter, + ctx: { + req: req as any, + res: res as any, + db: null as any, + session: session, + user: user, + }, + transformer: superjson, + }); + await helpers.auth.get.prefetch(); + + return { + props: { + trpcState: helpers.dehydrate(), + }, + }; +} diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index 19acb9a3..9c2608cc 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -239,7 +239,10 @@ export const projectRouter = createTRPCRouter({ } }), }); -function buildServiceFilter(fieldName: AnyPgColumn, accessedServices: string[]) { +function buildServiceFilter( + fieldName: AnyPgColumn, + accessedServices: string[], +) { return accessedServices.length > 0 ? sql`${fieldName} IN (${sql.join( accessedServices.map((serviceId) => sql`${serviceId}`), diff --git a/apps/dokploy/styles/globals.css b/apps/dokploy/styles/globals.css index 3910d69a..7b7977b9 100644 --- a/apps/dokploy/styles/globals.css +++ b/apps/dokploy/styles/globals.css @@ -101,7 +101,7 @@ * { @apply border-border; } - + body { @apply bg-background text-foreground; } @@ -110,16 +110,16 @@ ::-webkit-scrollbar { width: 0.3125rem; } - + ::-webkit-scrollbar-track { background: transparent; } - + ::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 0.3125rem; } - + * { scrollbar-width: thin; scrollbar-color: hsl(var(--border)) transparent; diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts index 6238a19c..9531eb7a 100644 --- a/apps/dokploy/templates/templates.ts +++ b/apps/dokploy/templates/templates.ts @@ -1269,7 +1269,8 @@ export const templates: TemplateData[] = [ }, tags: ["cloud", "networking", "security", "tunnel"], load: () => import("./cloudflared/index").then((m) => m.generate), - },{ + }, + { id: "couchdb", name: "CouchDB", version: "latest", diff --git a/packages/server/src/db/schema/admin.ts b/packages/server/src/db/schema/admin.ts index 222fb16c..e9c73bcc 100644 --- a/packages/server/src/db/schema/admin.ts +++ b/packages/server/src/db/schema/admin.ts @@ -31,6 +31,15 @@ export const admins = pgTable("admin", { stripeCustomerId: text("stripeCustomerId"), stripeSubscriptionId: text("stripeSubscriptionId"), serversQuantity: integer("serversQuantity").notNull().default(0), + cleanupCacheApplications: boolean("cleanupCacheApplications") + .notNull() + .default(true), + cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews") + .notNull() + .default(false), + cleanupCacheOnCompose: boolean("cleanupCacheOnCompose") + .notNull() + .default(false), }); export const adminsRelations = relations(admins, ({ one, many }) => ({ diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index 068e69b3..ccadebf7 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -40,7 +40,7 @@ import { createTraefikConfig } from "@dokploy/server/utils/traefik/application"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { encodeBase64 } from "../utils/docker/utils"; -import { getDokployUrl } from "./admin"; +import { findAdminById, getDokployUrl } from "./admin"; import { createDeployment, createDeploymentPreview, @@ -58,6 +58,7 @@ import { updatePreviewDeployment, } from "./preview-deployment"; import { validUniqueServerAppName } from "./project"; +import { cleanupFullDocker } from "./settings"; export type Application = typeof applications.$inferSelect; export const createApplication = async ( @@ -213,7 +214,7 @@ export const deployApplication = async ({ applicationType: "application", buildLink, adminId: application.project.adminId, - domains: application.domains + domains: application.domains, }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); @@ -229,6 +230,12 @@ export const deployApplication = async ({ }); throw error; + } finally { + const admin = await findAdminById(application.project.adminId); + + if (admin.cleanupCacheApplications) { + await cleanupFullDocker(application?.serverId); + } } return true; @@ -270,6 +277,12 @@ export const rebuildApplication = async ({ await updateDeploymentStatus(deployment.deploymentId, "error"); await updateApplicationStatus(applicationId, "error"); throw error; + } finally { + const admin = await findAdminById(application.project.adminId); + + if (admin.cleanupCacheApplications) { + await cleanupFullDocker(application?.serverId); + } } return true; @@ -333,7 +346,7 @@ export const deployRemoteApplication = async ({ applicationType: "application", buildLink, adminId: application.project.adminId, - domains: application.domains + domains: application.domains, }); } catch (error) { // @ts-ignore @@ -359,15 +372,13 @@ export const deployRemoteApplication = async ({ adminId: application.project.adminId, }); - console.log( - "Error on ", - application.buildType, - "/", - application.sourceType, - error, - ); - throw error; + } finally { + const admin = await findAdminById(application.project.adminId); + + if (admin.cleanupCacheApplications) { + await cleanupFullDocker(application?.serverId); + } } return true; @@ -475,6 +486,12 @@ export const deployPreviewApplication = async ({ previewStatus: "error", }); throw error; + } finally { + const admin = await findAdminById(application.project.adminId); + + if (admin.cleanupCacheOnPreviews) { + await cleanupFullDocker(application?.serverId); + } } return true; @@ -587,6 +604,12 @@ export const deployRemotePreviewApplication = async ({ previewStatus: "error", }); throw error; + } finally { + const admin = await findAdminById(application.project.adminId); + + if (admin.cleanupCacheOnPreviews) { + await cleanupFullDocker(application?.serverId); + } } return true; @@ -634,6 +657,12 @@ export const rebuildRemoteApplication = async ({ await updateDeploymentStatus(deployment.deploymentId, "error"); await updateApplicationStatus(applicationId, "error"); throw error; + } finally { + const admin = await findAdminById(application.project.adminId); + + if (admin.cleanupCacheApplications) { + await cleanupFullDocker(application?.serverId); + } } return true; diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 8561dd37..7f6a5954 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -3,7 +3,6 @@ import { paths } from "@dokploy/server/constants"; import { db } from "@dokploy/server/db"; import { type apiCreateCompose, compose } from "@dokploy/server/db/schema"; import { buildAppName, cleanAppName } from "@dokploy/server/db/schema"; -import { generatePassword } from "@dokploy/server/templates/utils"; import { buildCompose, getBuildComposeCommand, @@ -45,9 +44,10 @@ import { import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { encodeBase64 } from "../utils/docker/utils"; -import { getDokployUrl } from "./admin"; +import { findAdminById, getDokployUrl } from "./admin"; import { createDeploymentCompose, updateDeploymentStatus } from "./deployment"; import { validUniqueServerAppName } from "./project"; +import { cleanupFullDocker } from "./settings"; export type Compose = typeof compose.$inferSelect; @@ -260,6 +260,11 @@ export const deployCompose = async ({ adminId: compose.project.adminId, }); throw error; + } finally { + const admin = await findAdminById(compose.project.adminId); + if (admin.cleanupCacheOnCompose) { + await cleanupFullDocker(compose?.serverId); + } } }; @@ -296,6 +301,11 @@ export const rebuildCompose = async ({ composeStatus: "error", }); throw error; + } finally { + const admin = await findAdminById(compose.project.adminId); + if (admin.cleanupCacheOnCompose) { + await cleanupFullDocker(compose?.serverId); + } } return true; @@ -394,6 +404,11 @@ export const deployRemoteCompose = async ({ adminId: compose.project.adminId, }); throw error; + } finally { + const admin = await findAdminById(compose.project.adminId); + if (admin.cleanupCacheOnCompose) { + await cleanupFullDocker(compose?.serverId); + } } }; @@ -438,6 +453,11 @@ export const rebuildRemoteCompose = async ({ composeStatus: "error", }); throw error; + } finally { + const admin = await findAdminById(compose.project.adminId); + if (admin.cleanupCacheOnCompose) { + await cleanupFullDocker(compose?.serverId); + } } return true; diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts index 37f7b2ee..d22780c9 100644 --- a/packages/server/src/services/settings.ts +++ b/packages/server/src/services/settings.ts @@ -5,6 +5,7 @@ import { execAsync, execAsyncRemote, } from "@dokploy/server/utils/process/execAsync"; +import { findAdminById } from "./admin"; // import packageInfo from "../../../package.json"; export interface IUpdateData { @@ -213,3 +214,35 @@ echo "$json_output" } return result; }; + +export const cleanupFullDocker = async (serverId?: string | null) => { + const cleanupImages = "docker image prune --all --force"; + const cleanupVolumes = "docker volume prune --all --force"; + const cleanupContainers = "docker container prune --force"; + const cleanupSystem = "docker system prune --all --force --volumes"; + const cleanupBuilder = "docker builder prune --all --force"; + + try { + if (serverId) { + await execAsyncRemote( + serverId, + ` + ${cleanupImages} + ${cleanupVolumes} + ${cleanupContainers} + ${cleanupSystem} + ${cleanupBuilder} + `, + ); + } + await execAsync(` + ${cleanupImages} + ${cleanupVolumes} + ${cleanupContainers} + ${cleanupSystem} + ${cleanupBuilder} + `); + } catch (error) { + console.log(error); + } +}; diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index c8a9849c..d8d9862c 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -73,7 +73,8 @@ export const canPerformCreationService = async ( userId: string, projectId: string, ) => { - const { accessedProjects, canCreateServices } = await findUserByAuthId(userId); + const { accessedProjects, canCreateServices } = + await findUserByAuthId(userId); const haveAccessToProject = accessedProjects.includes(projectId); if (canCreateServices && haveAccessToProject) { @@ -101,7 +102,8 @@ export const canPeformDeleteService = async ( authId: string, serviceId: string, ) => { - const { accessedServices, canDeleteServices } = await findUserByAuthId(authId); + const { accessedServices, canDeleteServices } = + await findUserByAuthId(authId); const haveAccessToService = accessedServices.includes(serviceId); if (canDeleteServices && haveAccessToService) { diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts index 2ab2125c..95936652 100644 --- a/packages/server/src/utils/notifications/build-error.ts +++ b/packages/server/src/utils/notifications/build-error.ts @@ -2,8 +2,8 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed"; import { renderAsync } from "@react-email/components"; -import { and, eq } from "drizzle-orm"; import { format } from "date-fns"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -139,11 +139,11 @@ export const sendBuildErrorNotifications = async ({ }, ], ]; - + await sendTelegramNotification( telegram, `⚠️ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}\n\nError:\n
${errorMessage}
`, - inlineButton + inlineButton, ); } diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts index 19b17811..960f7a6a 100644 --- a/packages/server/src/utils/notifications/build-success.ts +++ b/packages/server/src/utils/notifications/build-success.ts @@ -1,10 +1,10 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success"; -import { Domain } from "@dokploy/server/services/domain"; +import type { Domain } from "@dokploy/server/services/domain"; import { renderAsync } from "@react-email/components"; -import { and, eq } from "drizzle-orm"; import { format } from "date-fns"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -28,7 +28,7 @@ export const sendBuildSuccessNotifications = async ({ applicationType, buildLink, adminId, - domains + domains, }: Props) => { const date = new Date(); const unixDate = ~~(Number(date) / 1000); @@ -128,9 +128,10 @@ export const sendBuildSuccessNotifications = async ({ if (telegram) { const chunkArray = (array: T[], chunkSize: number): T[][] => - Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) => array.slice(i * chunkSize, i * chunkSize + chunkSize) - ); - + Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) => + array.slice(i * chunkSize, i * chunkSize + chunkSize), + ); + const inlineButton = [ [ { @@ -142,14 +143,14 @@ export const sendBuildSuccessNotifications = async ({ chunk.map((data) => ({ text: data.host, url: `${data.https ? "https" : "http"}://${data.host}`, - })) + })), ), ]; - + await sendTelegramNotification( telegram, - `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, - inlineButton + `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, + inlineButton, ); } diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts index 1460964d..0b1d61f7 100644 --- a/packages/server/src/utils/notifications/database-backup.ts +++ b/packages/server/src/utils/notifications/database-backup.ts @@ -3,8 +3,8 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup"; import { renderAsync } from "@react-email/components"; -import { and, eq } from "drizzle-orm"; import { format } from "date-fns"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -144,13 +144,15 @@ export const sendDatabaseBackupNotifications = async ({ if (telegram) { const isError = type === "error" && errorMessage; - + const statusEmoji = type === "success" ? "✅" : "❌"; const typeStatus = type === "success" ? "Successful" : "Failed"; - const errorMsg = isError ? `\n\nError:\n
${errorMessage}
` : ""; - + const errorMsg = isError + ? `\n\nError:\n
${errorMessage}
` + : ""; + const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`; - + await sendTelegramNotification(telegram, messageText); } diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts index 05624e49..b60e3b0a 100644 --- a/packages/server/src/utils/notifications/docker-cleanup.ts +++ b/packages/server/src/utils/notifications/docker-cleanup.ts @@ -2,8 +2,8 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup"; import { renderAsync } from "@react-email/components"; -import { and, eq } from "drizzle-orm"; import { format } from "date-fns"; +import { and, eq } from "drizzle-orm"; import { sendDiscordNotification, sendEmailNotification, @@ -96,7 +96,7 @@ export const sendDockerCleanupNotifications = async ( if (telegram) { await sendTelegramNotification( telegram, - `✅ Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}` + `✅ Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, ); } diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts index 6debb7a7..5a156aff 100644 --- a/packages/server/src/utils/notifications/dokploy-restart.ts +++ b/packages/server/src/utils/notifications/dokploy-restart.ts @@ -2,6 +2,7 @@ import { db } from "@dokploy/server/db"; import { notifications } from "@dokploy/server/db/schema"; import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart"; import { renderAsync } from "@react-email/components"; +import { format } from "date-fns"; import { eq } from "drizzle-orm"; import { sendDiscordNotification, @@ -10,7 +11,6 @@ import { sendSlackNotification, sendTelegramNotification, } from "./utils"; -import { format } from "date-fns"; export const sendDokployRestartNotifications = async () => { const date = new Date(); @@ -80,7 +80,7 @@ export const sendDokployRestartNotifications = async () => { if (telegram) { await sendTelegramNotification( telegram, - `✅ Dokploy Server Restarted\n\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}` + `✅ Dokploy Server Restarted\n\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, ); } diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts index ede46034..4f8bb1a5 100644 --- a/packages/server/src/utils/notifications/utils.ts +++ b/packages/server/src/utils/notifications/utils.ts @@ -59,7 +59,7 @@ export const sendTelegramNotification = async ( inlineButton?: { text: string; url: string; - }[][] + }[][], ) => { try { const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;