From 25a8df567e4ed4d992e8540560e6150f2e6e047c Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 19 Jan 2025 00:57:42 -0600 Subject: [PATCH 1/5] feat: add cleanup cache on deployments --- .../dashboard/project/add-template.tsx | 4 +- apps/dokploy/components/layouts/user-nav.tsx | 18 +- apps/dokploy/drizzle/0057_oval_ken_ellis.sql | 1 + .../dokploy/drizzle/0058_cultured_warpath.sql | 1 + apps/dokploy/drizzle/0059_public_speed.sql | 3 + apps/dokploy/drizzle/meta/0057_snapshot.json | 4321 ++++++++++++++++ apps/dokploy/drizzle/meta/0058_snapshot.json | 4321 ++++++++++++++++ apps/dokploy/drizzle/meta/0059_snapshot.json | 4335 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 21 + .../pages/dashboard/settings/index.tsx | 220 + apps/dokploy/server/api/routers/project.ts | 5 +- apps/dokploy/styles/globals.css | 8 +- apps/dokploy/templates/templates.ts | 3 +- packages/server/src/db/schema/admin.ts | 9 + packages/server/src/services/application.ts | 51 +- packages/server/src/services/compose.ts | 24 +- packages/server/src/services/settings.ts | 33 + packages/server/src/services/user.ts | 6 +- .../src/utils/notifications/build-error.ts | 6 +- .../src/utils/notifications/build-success.ts | 21 +- .../utils/notifications/database-backup.ts | 12 +- .../src/utils/notifications/docker-cleanup.ts | 4 +- .../utils/notifications/dokploy-restart.ts | 4 +- .../server/src/utils/notifications/utils.ts | 2 +- 24 files changed, 13380 insertions(+), 53 deletions(-) create mode 100644 apps/dokploy/drizzle/0057_oval_ken_ellis.sql create mode 100644 apps/dokploy/drizzle/0058_cultured_warpath.sql create mode 100644 apps/dokploy/drizzle/0059_public_speed.sql create mode 100644 apps/dokploy/drizzle/meta/0057_snapshot.json create mode 100644 apps/dokploy/drizzle/meta/0058_snapshot.json create mode 100644 apps/dokploy/drizzle/meta/0059_snapshot.json create mode 100644 apps/dokploy/pages/dashboard/settings/index.tsx 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`; From 52c83fd6fcb1927acbb94a74f9de4bda73338a7e Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 19 Jan 2025 01:07:41 -0600 Subject: [PATCH 2/5] refactor: update text --- apps/dokploy/pages/dashboard/settings/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/pages/dashboard/settings/index.tsx b/apps/dokploy/pages/dashboard/settings/index.tsx index f5b73ea7..bf76607b 100644 --- a/apps/dokploy/pages/dashboard/settings/index.tsx +++ b/apps/dokploy/pages/dashboard/settings/index.tsx @@ -102,7 +102,7 @@ const Page = () => {
Clean Cache on Applications - Clean the cache after every deployment + Clean the cache after every application deployment
@@ -122,7 +122,7 @@ const Page = () => {
Clean Cache on Previews - Clean the cache after every deployment + Clean the cache after every preview deployment
@@ -142,7 +142,7 @@ const Page = () => {
Clean Cache on Compose - Clean the cache after every deployment + Clean the cache after every compose deployment
From 43b7db00f93db5715a469996589c03a1d3444713 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 19 Jan 2025 01:21:03 -0600 Subject: [PATCH 3/5] refactor: add missing values to test --- .../server/update-server-config.test.ts | 109 +++++++++--------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts index c966748a..9c5beb40 100644 --- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts +++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts @@ -1,95 +1,98 @@ import { fs, vol } from "memfs"; vi.mock("node:fs", () => ({ - ...fs, - default: fs, + ...fs, + default: fs, })); import type { Admin, FileConfig } from "@dokploy/server"; import { - createDefaultServerTraefikConfig, - loadOrCreateConfig, - updateServerTraefik, + createDefaultServerTraefikConfig, + loadOrCreateConfig, + updateServerTraefik, } from "@dokploy/server"; import { beforeEach, expect, test, vi } from "vitest"; const baseAdmin: Admin = { - createdAt: "", - authId: "", - adminId: "string", - serverIp: null, - certificateType: "none", - host: null, - letsEncryptEmail: null, - sshPrivateKey: null, - enableDockerCleanup: false, - enableLogRotation: false, - serversQuantity: 0, - stripeCustomerId: "", - stripeSubscriptionId: "", + cleanupCacheApplications: false, + cleanupCacheOnCompose: false, + cleanupCacheOnPreviews: false, + createdAt: "", + authId: "", + adminId: "string", + serverIp: null, + certificateType: "none", + host: null, + letsEncryptEmail: null, + sshPrivateKey: null, + enableDockerCleanup: false, + enableLogRotation: false, + serversQuantity: 0, + stripeCustomerId: "", + stripeSubscriptionId: "", }; beforeEach(() => { - vol.reset(); - createDefaultServerTraefikConfig(); + vol.reset(); + createDefaultServerTraefikConfig(); }); test("Should read the configuration file", () => { - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe( - "dokploy-service-app", - ); + expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe( + "dokploy-service-app" + ); }); test("Should apply redirect-to-https", () => { - updateServerTraefik( - { - ...baseAdmin, - certificateType: "letsencrypt", - }, - "example.com", - ); + updateServerTraefik( + { + ...baseAdmin, + certificateType: "letsencrypt", + }, + "example.com" + ); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app"]?.middlewares).toContain( - "redirect-to-https", - ); + expect(config.http?.routers?.["dokploy-router-app"]?.middlewares).toContain( + "redirect-to-https" + ); }); test("Should change only host when no certificate", () => { - updateServerTraefik(baseAdmin, "example.com"); + updateServerTraefik(baseAdmin, "example.com"); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); + expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); }); test("Should not touch config without host", () => { - const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); + const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); - updateServerTraefik(baseAdmin, null); + updateServerTraefik(baseAdmin, null); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(originalConfig).toEqual(config); + expect(originalConfig).toEqual(config); }); test("Should remove websecure if https rollback to http", () => { - const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); + const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); - updateServerTraefik( - { ...baseAdmin, certificateType: "letsencrypt" }, - "example.com", - ); + updateServerTraefik( + { ...baseAdmin, certificateType: "letsencrypt" }, + "example.com" + ); - updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com"); + updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com"); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); - expect( - config.http?.routers?.["dokploy-router-app"]?.middlewares, - ).not.toContain("redirect-to-https"); + expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); + expect( + config.http?.routers?.["dokploy-router-app"]?.middlewares + ).not.toContain("redirect-to-https"); }); From 5310a559b0487a946e3bd603751e688691a971cf Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 19 Jan 2025 01:29:29 -0600 Subject: [PATCH 4/5] refactor: improve sidebar --- .../server/update-server-config.test.ts | 112 +++++++++--------- .../database/backups/show-backups.tsx | 2 +- .../dashboard/monitoring/docker/show.tsx | 2 +- .../components/dashboard/projects/show.tsx | 6 +- .../certificates/show-certificates.tsx | 2 +- .../notifications/show-notifications.tsx | 2 +- .../dashboard/swarm/details/details-card.tsx | 2 +- .../dashboard/swarm/monitoring-card.tsx | 2 +- .../components/layouts/dashboard-layout.tsx | 6 +- .../components/layouts/onboarding-layout.tsx | 23 +--- .../components/layouts/project-layout.tsx | 6 +- apps/dokploy/components/layouts/side.tsx | 4 +- .../components/shared/breadcrumb-sidebar.tsx | 4 +- apps/dokploy/components/ui/sidebar.tsx | 2 +- .../pages/dashboard/project/[projectId].tsx | 6 +- apps/dokploy/pages/dashboard/swarm.tsx | 6 +- 16 files changed, 78 insertions(+), 109 deletions(-) diff --git a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts index 9c5beb40..fac90cc7 100644 --- a/apps/dokploy/__test__/traefik/server/update-server-config.test.ts +++ b/apps/dokploy/__test__/traefik/server/update-server-config.test.ts @@ -1,98 +1,98 @@ import { fs, vol } from "memfs"; vi.mock("node:fs", () => ({ - ...fs, - default: fs, + ...fs, + default: fs, })); import type { Admin, FileConfig } from "@dokploy/server"; import { - createDefaultServerTraefikConfig, - loadOrCreateConfig, - updateServerTraefik, + createDefaultServerTraefikConfig, + loadOrCreateConfig, + updateServerTraefik, } from "@dokploy/server"; import { beforeEach, expect, test, vi } from "vitest"; const baseAdmin: Admin = { - cleanupCacheApplications: false, - cleanupCacheOnCompose: false, - cleanupCacheOnPreviews: false, - createdAt: "", - authId: "", - adminId: "string", - serverIp: null, - certificateType: "none", - host: null, - letsEncryptEmail: null, - sshPrivateKey: null, - enableDockerCleanup: false, - enableLogRotation: false, - serversQuantity: 0, - stripeCustomerId: "", - stripeSubscriptionId: "", + cleanupCacheApplications: false, + cleanupCacheOnCompose: false, + cleanupCacheOnPreviews: false, + createdAt: "", + authId: "", + adminId: "string", + serverIp: null, + certificateType: "none", + host: null, + letsEncryptEmail: null, + sshPrivateKey: null, + enableDockerCleanup: false, + enableLogRotation: false, + serversQuantity: 0, + stripeCustomerId: "", + stripeSubscriptionId: "", }; beforeEach(() => { - vol.reset(); - createDefaultServerTraefikConfig(); + vol.reset(); + createDefaultServerTraefikConfig(); }); test("Should read the configuration file", () => { - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe( - "dokploy-service-app" - ); + expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe( + "dokploy-service-app", + ); }); test("Should apply redirect-to-https", () => { - updateServerTraefik( - { - ...baseAdmin, - certificateType: "letsencrypt", - }, - "example.com" - ); + updateServerTraefik( + { + ...baseAdmin, + certificateType: "letsencrypt", + }, + "example.com", + ); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app"]?.middlewares).toContain( - "redirect-to-https" - ); + expect(config.http?.routers?.["dokploy-router-app"]?.middlewares).toContain( + "redirect-to-https", + ); }); test("Should change only host when no certificate", () => { - updateServerTraefik(baseAdmin, "example.com"); + updateServerTraefik(baseAdmin, "example.com"); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); + expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); }); test("Should not touch config without host", () => { - const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); + const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); - updateServerTraefik(baseAdmin, null); + updateServerTraefik(baseAdmin, null); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(originalConfig).toEqual(config); + expect(originalConfig).toEqual(config); }); test("Should remove websecure if https rollback to http", () => { - const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); + const originalConfig: FileConfig = loadOrCreateConfig("dokploy"); - updateServerTraefik( - { ...baseAdmin, certificateType: "letsencrypt" }, - "example.com" - ); + updateServerTraefik( + { ...baseAdmin, certificateType: "letsencrypt" }, + "example.com", + ); - updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com"); + updateServerTraefik({ ...baseAdmin, certificateType: "none" }, "example.com"); - const config: FileConfig = loadOrCreateConfig("dokploy"); + const config: FileConfig = loadOrCreateConfig("dokploy"); - expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); - expect( - config.http?.routers?.["dokploy-router-app"]?.middlewares - ).not.toContain("redirect-to-https"); + expect(config.http?.routers?.["dokploy-router-app-secure"]).toBeUndefined(); + expect( + config.http?.routers?.["dokploy-router-app"]?.middlewares, + ).not.toContain("redirect-to-https"); }); diff --git a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx index 9e493529..21fe28d4 100644 --- a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx +++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx @@ -75,7 +75,7 @@ export const ShowBackups = ({ id, type }: Props) => { {data?.length === 0 ? (
- + To create a backup it is required to set at least 1 provider. Please, go to{" "} -
+
CPU Usage diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 16f842ed..c4b2f672 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -77,8 +77,8 @@ export const ShowProjects = () => {
-
- +
+ Projects @@ -87,7 +87,7 @@ export const ShowProjects = () => { Create and manage your projects -
+
diff --git a/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx b/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx index f28a814d..6aaa2563 100644 --- a/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx +++ b/apps/dokploy/components/dashboard/settings/certificates/show-certificates.tsx @@ -50,7 +50,7 @@ export const ShowCertificates = () => { {data?.length === 0 ? (
- + You don't have any certificates created diff --git a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx index 77fb8858..d65069d4 100644 --- a/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx +++ b/apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx @@ -47,7 +47,7 @@ export const ShowNotifications = () => { {data?.length === 0 ? (
- + To send notifications it is required to set at least 1 provider. diff --git a/apps/dokploy/components/dashboard/swarm/details/details-card.tsx b/apps/dokploy/components/dashboard/swarm/details/details-card.tsx index 8901ba58..52c90c0f 100644 --- a/apps/dokploy/components/dashboard/swarm/details/details-card.tsx +++ b/apps/dokploy/components/dashboard/swarm/details/details-card.tsx @@ -60,7 +60,7 @@ export function NodeCard({ node, serverId }: Props) {
{node.Hostname}
{node.ManagerStatus || "Worker"}
-
+
TLS Status: {node.TLSStatus} Availability: {node.Availability}
diff --git a/apps/dokploy/components/dashboard/swarm/monitoring-card.tsx b/apps/dokploy/components/dashboard/swarm/monitoring-card.tsx index 98dc0d96..0c38b509 100644 --- a/apps/dokploy/components/dashboard/swarm/monitoring-card.tsx +++ b/apps/dokploy/components/dashboard/swarm/monitoring-card.tsx @@ -72,7 +72,7 @@ export default function SwarmMonitorCard({ serverId }: Props) { return (
-
+
diff --git a/apps/dokploy/components/layouts/dashboard-layout.tsx b/apps/dokploy/components/layouts/dashboard-layout.tsx index 13e9061d..00697e7c 100644 --- a/apps/dokploy/components/layouts/dashboard-layout.tsx +++ b/apps/dokploy/components/layouts/dashboard-layout.tsx @@ -5,9 +5,5 @@ interface Props { } export const DashboardLayout = ({ children }: Props) => { - return ( - -
{children}
-
- ); + return {children}; }; diff --git a/apps/dokploy/components/layouts/onboarding-layout.tsx b/apps/dokploy/components/layouts/onboarding-layout.tsx index 093c1492..9d4068cf 100644 --- a/apps/dokploy/components/layouts/onboarding-layout.tsx +++ b/apps/dokploy/components/layouts/onboarding-layout.tsx @@ -11,7 +11,7 @@ interface Props { export const OnboardingLayout = ({ children }: Props) => { return (
-
+
{
-

+

“The Open Source alternative to Netlify, Vercel, Heroku.”

- {/*
Sofia Davis
*/}
{children} - - {/*

- By clicking continue, you agree to our{" "} - - Terms of Service - {" "} - and{" "} - - Privacy Policy - - . -

*/}
diff --git a/apps/dokploy/pages/dashboard/swarm.tsx b/apps/dokploy/pages/dashboard/swarm.tsx index f40a0a83..3a8a60b2 100644 --- a/apps/dokploy/pages/dashboard/swarm.tsx +++ b/apps/dokploy/pages/dashboard/swarm.tsx @@ -8,11 +8,7 @@ import type { ReactElement } from "react"; import superjson from "superjson"; const Dashboard = () => { - return ( - <> - - - ); + return ; }; export default Dashboard; From adb204ec1f99bb5be46115d9ade2e27c1ce1c1cf Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 19 Jan 2025 01:44:42 -0600 Subject: [PATCH 5/5] refactor: add sidebar persistence --- apps/dokploy/components/layouts/side.tsx | 23 ++++++++++++++++++++++- apps/dokploy/components/ui/sidebar.tsx | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/components/layouts/side.tsx b/apps/dokploy/components/layouts/side.tsx index 7a083167..fba01cfc 100644 --- a/apps/dokploy/components/layouts/side.tsx +++ b/apps/dokploy/components/layouts/side.tsx @@ -1,5 +1,5 @@ "use client"; - +import { useState, useEffect } from "react"; import { Activity, AudioWaveform, @@ -46,6 +46,7 @@ import { import { Separator } from "@/components/ui/separator"; import { Sidebar, + SIDEBAR_COOKIE_NAME, SidebarContent, SidebarFooter, SidebarGroup, @@ -369,6 +370,19 @@ function SidebarLogo() { } export default function Page({ children }: Props) { + const [defaultOpen, setDefaultOpen] = useState( + undefined, + ); + + useEffect(() => { + const cookieValue = document.cookie + .split("; ") + .find((row) => row.startsWith(`${SIDEBAR_COOKIE_NAME}=`)) + ?.split("=")[1]; + + setDefaultOpen(cookieValue === undefined ? true : cookieValue === "true"); + }, []); + const router = useRouter(); const pathname = usePathname(); const currentPath = router.pathname; @@ -445,6 +459,13 @@ export default function Page({ children }: Props) { return ( { + setDefaultOpen(open); + + document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}`; + }} style={ { "--sidebar-width": "19.5rem", diff --git a/apps/dokploy/components/ui/sidebar.tsx b/apps/dokploy/components/ui/sidebar.tsx index 24c80914..40f84873 100644 --- a/apps/dokploy/components/ui/sidebar.tsx +++ b/apps/dokploy/components/ui/sidebar.tsx @@ -17,7 +17,7 @@ import { import { useIsMobile } from "@/hooks/use-mobile"; import { cn } from "@/lib/utils"; -const SIDEBAR_COOKIE_NAME = "sidebar:state"; +export const SIDEBAR_COOKIE_NAME = "sidebar:state"; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; const SIDEBAR_WIDTH = "16rem"; const SIDEBAR_WIDTH_MOBILE = "18rem";