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
@@ -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...."
+10 -8
View File
@@ -118,14 +118,16 @@ export const UserNav = () => {
</DropdownMenuItem> </DropdownMenuItem>
)} )}
<DropdownMenuItem {data?.rol === "admin" && (
className="cursor-pointer" <DropdownMenuItem
onClick={() => { className="cursor-pointer"
router.push("/dashboard/settings/server"); onClick={() => {
}} router.push("/dashboard/settings");
> }}
Settings >
</DropdownMenuItem> Settings
</DropdownMenuItem>
)}
</> </>
) : ( ) : (
<> <>
@@ -0,0 +1 @@
ALTER TABLE "admin" ADD COLUMN "enableCleanupCache" boolean DEFAULT true NOT NULL;
@@ -0,0 +1 @@
ALTER TABLE "admin" RENAME COLUMN "enableCleanupCache" TO "cleanupCacheOnDeployments";
@@ -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
+21
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
} }
] ]
} }
@@ -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(),
},
};
}
+4 -1
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}`),
+4 -4
View File
@@ -101,7 +101,7 @@
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
@@ -110,16 +110,16 @@
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.3125rem; width: 0.3125rem;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: transparent; background: transparent;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: hsl(var(--border)); background: hsl(var(--border));
border-radius: 0.3125rem; border-radius: 0.3125rem;
} }
* { * {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: hsl(var(--border)) transparent; scrollbar-color: hsl(var(--border)) transparent;
+2 -1
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",
+9
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 }) => ({
+40 -11
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;
+22 -2
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;
+33
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);
}
};
+4 -2
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) {
@@ -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,
@@ -139,11 +139,11 @@ 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,
); );
} }
@@ -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,9 +128,10 @@ 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,
); );
} }
@@ -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,
@@ -144,13 +144,15 @@ export const sendDatabaseBackupNotifications = async ({
if (telegram) { if (telegram) {
const isError = type === "error" && errorMessage; const isError = type === "error" && errorMessage;
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 : ""}`;
await sendTelegramNotification(telegram, messageText); await sendTelegramNotification(telegram, messageText);
} }
@@ -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")}`,
); );
} }
@@ -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")}`,
); );
} }
@@ -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`;