feat: add cleanup cache on deployments

This commit is contained in:
Mauricio Siu
2025-01-19 00:57:42 -06:00
parent 089274492d
commit 25a8df567e
24 changed files with 13380 additions and 53 deletions

View File

@@ -133,7 +133,9 @@ export const AddTemplate = ({ projectId }: Props) => {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
variant="outline" variant="outline"
className={cn("w-full sm:w-[200px] justify-between !bg-input")} className={cn(
"w-full sm:w-[200px] justify-between !bg-input",
)}
> >
{isLoadingTags {isLoadingTags
? "Loading...." ? "Loading...."

View File

@@ -118,14 +118,16 @@ export const UserNav = () => {
</DropdownMenuItem> </DropdownMenuItem>
)} )}
{data?.rol === "admin" && (
<DropdownMenuItem <DropdownMenuItem
className="cursor-pointer" className="cursor-pointer"
onClick={() => { onClick={() => {
router.push("/dashboard/settings/server"); router.push("/dashboard/settings");
}} }}
> >
Settings Settings
</DropdownMenuItem> </DropdownMenuItem>
)}
</> </>
) : ( ) : (
<> <>

View File

@@ -0,0 +1 @@
ALTER TABLE "admin" ADD COLUMN "enableCleanupCache" boolean DEFAULT true NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE "admin" RENAME COLUMN "enableCleanupCache" TO "cleanupCacheOnDeployments";

View File

@@ -0,0 +1,3 @@
ALTER TABLE "admin" RENAME COLUMN "cleanupCacheOnDeployments" TO "cleanupCacheApplications";--> statement-breakpoint
ALTER TABLE "admin" ADD COLUMN "cleanupCacheOnPreviews" boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE "admin" ADD COLUMN "cleanupCacheOnCompose" boolean DEFAULT false NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -400,6 +400,27 @@
"when": 1736789918294, "when": 1736789918294,
"tag": "0056_majestic_skaar", "tag": "0056_majestic_skaar",
"breakpoints": true "breakpoints": true
},
{
"idx": 57,
"version": "6",
"when": 1737266192013,
"tag": "0057_oval_ken_ellis",
"breakpoints": true
},
{
"idx": 58,
"version": "6",
"when": 1737266560950,
"tag": "0058_cultured_warpath",
"breakpoints": true
},
{
"idx": 59,
"version": "6",
"when": 1737268325181,
"tag": "0059_public_speed",
"breakpoints": true
} }
] ]
} }

View File

@@ -0,0 +1,220 @@
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { DialogFooter } from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { validateRequest } from "@dokploy/server";
import { zodResolver } from "@hookform/resolvers/zod";
import { createServerSideHelpers } from "@trpc/react-query/server";
import { Settings } from "lucide-react";
import type { GetServerSidePropsContext } from "next";
import React, { useEffect, type ReactElement } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import superjson from "superjson";
import { z } from "zod";
const settings = z.object({
cleanCacheOnApplications: z.boolean(),
cleanCacheOnCompose: z.boolean(),
cleanCacheOnPreviews: z.boolean(),
});
type SettingsType = z.infer<typeof settings>;
const Page = () => {
const { data, refetch } = api.admin.one.useQuery();
const { mutateAsync, isLoading, isError, error } =
api.admin.update.useMutation();
const form = useForm<SettingsType>({
defaultValues: {
cleanCacheOnApplications: false,
cleanCacheOnCompose: false,
cleanCacheOnPreviews: false,
},
resolver: zodResolver(settings),
});
useEffect(() => {
form.reset({
cleanCacheOnApplications: data?.cleanupCacheApplications || false,
cleanCacheOnCompose: data?.cleanupCacheOnCompose || false,
cleanCacheOnPreviews: data?.cleanupCacheOnPreviews || false,
});
}, [form, form.reset, form.formState.isSubmitSuccessful, data]);
const onSubmit = async (values: SettingsType) => {
await mutateAsync({
cleanupCacheApplications: values.cleanCacheOnApplications,
cleanupCacheOnCompose: values.cleanCacheOnCompose,
cleanupCacheOnPreviews: values.cleanCacheOnPreviews,
})
.then(() => {
toast.success("Settings updated");
refetch();
})
.catch(() => {
toast.error("Something went wrong");
});
};
return (
<div className="w-full">
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
<div className="rounded-xl bg-background shadow-md ">
<CardHeader className="">
<CardTitle className="text-xl flex flex-row gap-2">
<Settings className="size-6 text-muted-foreground self-center" />
Settings
</CardTitle>
<CardDescription>Manage your Dokploy settings</CardDescription>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
</CardHeader>
<CardContent className="space-y-2 py-8 border-t">
<Form {...form}>
<form
id="hook-form-add-security"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-2"
>
<FormField
control={form.control}
name="cleanCacheOnApplications"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>Clean Cache on Applications</FormLabel>
<FormDescription>
Clean the cache after every deployment
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="cleanCacheOnPreviews"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>Clean Cache on Previews</FormLabel>
<FormDescription>
Clean the cache after every deployment
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="cleanCacheOnCompose"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
<div className="space-y-0.5">
<FormLabel>Clean Cache on Compose</FormLabel>
<FormDescription>
Clean the cache after every deployment
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<DialogFooter>
<Button
isLoading={isLoading}
form="hook-form-add-security"
type="submit"
>
Update
</Button>
</DialogFooter>
</form>
</Form>
</CardContent>
</div>
</Card>
</div>
);
};
export default Page;
Page.getLayout = (page: ReactElement) => {
return <DashboardLayout metaName="Server">{page}</DashboardLayout>;
};
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(),
},
};
}

View File

@@ -239,7 +239,10 @@ export const projectRouter = createTRPCRouter({
} }
}), }),
}); });
function buildServiceFilter(fieldName: AnyPgColumn, accessedServices: string[]) { function buildServiceFilter(
fieldName: AnyPgColumn,
accessedServices: string[],
) {
return accessedServices.length > 0 return accessedServices.length > 0
? sql`${fieldName} IN (${sql.join( ? sql`${fieldName} IN (${sql.join(
accessedServices.map((serviceId) => sql`${serviceId}`), accessedServices.map((serviceId) => sql`${serviceId}`),

View File

@@ -1269,7 +1269,8 @@ export const templates: TemplateData[] = [
}, },
tags: ["cloud", "networking", "security", "tunnel"], tags: ["cloud", "networking", "security", "tunnel"],
load: () => import("./cloudflared/index").then((m) => m.generate), load: () => import("./cloudflared/index").then((m) => m.generate),
},{ },
{
id: "couchdb", id: "couchdb",
name: "CouchDB", name: "CouchDB",
version: "latest", version: "latest",

View File

@@ -31,6 +31,15 @@ export const admins = pgTable("admin", {
stripeCustomerId: text("stripeCustomerId"), stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"), stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0), 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 }) => ({ export const adminsRelations = relations(admins, ({ one, many }) => ({

View File

@@ -40,7 +40,7 @@ import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils"; import { encodeBase64 } from "../utils/docker/utils";
import { getDokployUrl } from "./admin"; import { findAdminById, getDokployUrl } from "./admin";
import { import {
createDeployment, createDeployment,
createDeploymentPreview, createDeploymentPreview,
@@ -58,6 +58,7 @@ import {
updatePreviewDeployment, updatePreviewDeployment,
} from "./preview-deployment"; } from "./preview-deployment";
import { validUniqueServerAppName } from "./project"; import { validUniqueServerAppName } from "./project";
import { cleanupFullDocker } from "./settings";
export type Application = typeof applications.$inferSelect; export type Application = typeof applications.$inferSelect;
export const createApplication = async ( export const createApplication = async (
@@ -213,7 +214,7 @@ export const deployApplication = async ({
applicationType: "application", applicationType: "application",
buildLink, buildLink,
adminId: application.project.adminId, adminId: application.project.adminId,
domains: application.domains domains: application.domains,
}); });
} catch (error) { } catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error"); await updateDeploymentStatus(deployment.deploymentId, "error");
@@ -229,6 +230,12 @@ export const deployApplication = async ({
}); });
throw error; throw error;
} finally {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
} }
return true; return true;
@@ -270,6 +277,12 @@ export const rebuildApplication = async ({
await updateDeploymentStatus(deployment.deploymentId, "error"); await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error"); await updateApplicationStatus(applicationId, "error");
throw error; throw error;
} finally {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
} }
return true; return true;
@@ -333,7 +346,7 @@ export const deployRemoteApplication = async ({
applicationType: "application", applicationType: "application",
buildLink, buildLink,
adminId: application.project.adminId, adminId: application.project.adminId,
domains: application.domains domains: application.domains,
}); });
} catch (error) { } catch (error) {
// @ts-ignore // @ts-ignore
@@ -359,15 +372,13 @@ export const deployRemoteApplication = async ({
adminId: application.project.adminId, adminId: application.project.adminId,
}); });
console.log(
"Error on ",
application.buildType,
"/",
application.sourceType,
error,
);
throw error; throw error;
} finally {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
} }
return true; return true;
@@ -475,6 +486,12 @@ export const deployPreviewApplication = async ({
previewStatus: "error", previewStatus: "error",
}); });
throw error; throw error;
} finally {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheOnPreviews) {
await cleanupFullDocker(application?.serverId);
}
} }
return true; return true;
@@ -587,6 +604,12 @@ export const deployRemotePreviewApplication = async ({
previewStatus: "error", previewStatus: "error",
}); });
throw error; throw error;
} finally {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheOnPreviews) {
await cleanupFullDocker(application?.serverId);
}
} }
return true; return true;
@@ -634,6 +657,12 @@ export const rebuildRemoteApplication = async ({
await updateDeploymentStatus(deployment.deploymentId, "error"); await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error"); await updateApplicationStatus(applicationId, "error");
throw error; throw error;
} finally {
const admin = await findAdminById(application.project.adminId);
if (admin.cleanupCacheApplications) {
await cleanupFullDocker(application?.serverId);
}
} }
return true; return true;

View File

@@ -3,7 +3,6 @@ import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db"; import { db } from "@dokploy/server/db";
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema"; import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema"; import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { import {
buildCompose, buildCompose,
getBuildComposeCommand, getBuildComposeCommand,
@@ -45,9 +44,10 @@ import {
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { encodeBase64 } from "../utils/docker/utils"; import { encodeBase64 } from "../utils/docker/utils";
import { getDokployUrl } from "./admin"; import { findAdminById, getDokployUrl } from "./admin";
import { createDeploymentCompose, updateDeploymentStatus } from "./deployment"; import { createDeploymentCompose, updateDeploymentStatus } from "./deployment";
import { validUniqueServerAppName } from "./project"; import { validUniqueServerAppName } from "./project";
import { cleanupFullDocker } from "./settings";
export type Compose = typeof compose.$inferSelect; export type Compose = typeof compose.$inferSelect;
@@ -260,6 +260,11 @@ export const deployCompose = async ({
adminId: compose.project.adminId, adminId: compose.project.adminId,
}); });
throw error; 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", composeStatus: "error",
}); });
throw error; throw error;
} finally {
const admin = await findAdminById(compose.project.adminId);
if (admin.cleanupCacheOnCompose) {
await cleanupFullDocker(compose?.serverId);
}
} }
return true; return true;
@@ -394,6 +404,11 @@ export const deployRemoteCompose = async ({
adminId: compose.project.adminId, adminId: compose.project.adminId,
}); });
throw error; 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", composeStatus: "error",
}); });
throw error; throw error;
} finally {
const admin = await findAdminById(compose.project.adminId);
if (admin.cleanupCacheOnCompose) {
await cleanupFullDocker(compose?.serverId);
}
} }
return true; return true;

View File

@@ -5,6 +5,7 @@ import {
execAsync, execAsync,
execAsyncRemote, execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync"; } from "@dokploy/server/utils/process/execAsync";
import { findAdminById } from "./admin";
// import packageInfo from "../../../package.json"; // import packageInfo from "../../../package.json";
export interface IUpdateData { export interface IUpdateData {
@@ -213,3 +214,35 @@ echo "$json_output"
} }
return result; 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);
}
};

View File

@@ -73,7 +73,8 @@ export const canPerformCreationService = async (
userId: string, userId: string,
projectId: string, projectId: string,
) => { ) => {
const { accessedProjects, canCreateServices } = await findUserByAuthId(userId); const { accessedProjects, canCreateServices } =
await findUserByAuthId(userId);
const haveAccessToProject = accessedProjects.includes(projectId); const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) { if (canCreateServices && haveAccessToProject) {
@@ -101,7 +102,8 @@ export const canPeformDeleteService = async (
authId: string, authId: string,
serviceId: string, serviceId: string,
) => { ) => {
const { accessedServices, canDeleteServices } = await findUserByAuthId(authId); const { accessedServices, canDeleteServices } =
await findUserByAuthId(authId);
const haveAccessToService = accessedServices.includes(serviceId); const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) { if (canDeleteServices && haveAccessToService) {

View File

@@ -2,8 +2,8 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema"; import { notifications } from "@dokploy/server/db/schema";
import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed"; import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed";
import { renderAsync } from "@react-email/components"; import { renderAsync } from "@react-email/components";
import { and, eq } from "drizzle-orm";
import { format } from "date-fns"; import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import { import {
sendDiscordNotification, sendDiscordNotification,
sendEmailNotification, sendEmailNotification,
@@ -143,7 +143,7 @@ export const sendBuildErrorNotifications = async ({
await sendTelegramNotification( await sendTelegramNotification(
telegram, telegram,
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`, `<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
inlineButton inlineButton,
); );
} }

View File

@@ -1,10 +1,10 @@
import { db } from "@dokploy/server/db"; import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema"; import { notifications } from "@dokploy/server/db/schema";
import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success"; 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 { renderAsync } from "@react-email/components";
import { and, eq } from "drizzle-orm";
import { format } from "date-fns"; import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import { import {
sendDiscordNotification, sendDiscordNotification,
sendEmailNotification, sendEmailNotification,
@@ -28,7 +28,7 @@ export const sendBuildSuccessNotifications = async ({
applicationType, applicationType,
buildLink, buildLink,
adminId, adminId,
domains domains,
}: Props) => { }: Props) => {
const date = new Date(); const date = new Date();
const unixDate = ~~(Number(date) / 1000); const unixDate = ~~(Number(date) / 1000);
@@ -128,7 +128,8 @@ export const sendBuildSuccessNotifications = async ({
if (telegram) { if (telegram) {
const chunkArray = <T>(array: T[], chunkSize: number): T[][] => const chunkArray = <T>(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 = [ const inlineButton = [
@@ -142,14 +143,14 @@ export const sendBuildSuccessNotifications = async ({
chunk.map((data) => ({ chunk.map((data) => ({
text: data.host, text: data.host,
url: `${data.https ? "https" : "http"}://${data.host}`, url: `${data.https ? "https" : "http"}://${data.host}`,
})) })),
), ),
]; ];
await sendTelegramNotification( await sendTelegramNotification(
telegram, telegram,
`<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`, `<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
inlineButton inlineButton,
); );
} }

View File

@@ -3,8 +3,8 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema"; import { notifications } from "@dokploy/server/db/schema";
import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup"; import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
import { renderAsync } from "@react-email/components"; import { renderAsync } from "@react-email/components";
import { and, eq } from "drizzle-orm";
import { format } from "date-fns"; import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import { import {
sendDiscordNotification, sendDiscordNotification,
sendEmailNotification, sendEmailNotification,
@@ -147,7 +147,9 @@ export const sendDatabaseBackupNotifications = async ({
const statusEmoji = type === "success" ? "✅" : "❌"; const statusEmoji = type === "success" ? "✅" : "❌";
const typeStatus = type === "success" ? "Successful" : "Failed"; const typeStatus = type === "success" ? "Successful" : "Failed";
const errorMsg = isError ? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>` : ""; const errorMsg = isError
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
: "";
const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`; const messageText = `<b>${statusEmoji} Database Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${databaseType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;

View File

@@ -2,8 +2,8 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema"; import { notifications } from "@dokploy/server/db/schema";
import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup"; import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup";
import { renderAsync } from "@react-email/components"; import { renderAsync } from "@react-email/components";
import { and, eq } from "drizzle-orm";
import { format } from "date-fns"; import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
import { import {
sendDiscordNotification, sendDiscordNotification,
sendEmailNotification, sendEmailNotification,
@@ -96,7 +96,7 @@ export const sendDockerCleanupNotifications = async (
if (telegram) { if (telegram) {
await sendTelegramNotification( await sendTelegramNotification(
telegram, telegram,
`<b>✅ Docker Cleanup</b>\n\n<b>Message:</b> ${message}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}` `<b>✅ Docker Cleanup</b>\n\n<b>Message:</b> ${message}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
); );
} }

View File

@@ -2,6 +2,7 @@ import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema"; import { notifications } from "@dokploy/server/db/schema";
import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart"; import DokployRestartEmail from "@dokploy/server/emails/emails/dokploy-restart";
import { renderAsync } from "@react-email/components"; import { renderAsync } from "@react-email/components";
import { format } from "date-fns";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { import {
sendDiscordNotification, sendDiscordNotification,
@@ -10,7 +11,6 @@ import {
sendSlackNotification, sendSlackNotification,
sendTelegramNotification, sendTelegramNotification,
} from "./utils"; } from "./utils";
import { format } from "date-fns";
export const sendDokployRestartNotifications = async () => { export const sendDokployRestartNotifications = async () => {
const date = new Date(); const date = new Date();
@@ -80,7 +80,7 @@ export const sendDokployRestartNotifications = async () => {
if (telegram) { if (telegram) {
await sendTelegramNotification( await sendTelegramNotification(
telegram, telegram,
`<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}` `<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
); );
} }

View File

@@ -59,7 +59,7 @@ export const sendTelegramNotification = async (
inlineButton?: { inlineButton?: {
text: string; text: string;
url: string; url: string;
}[][] }[][],
) => { ) => {
try { try {
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`; const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;