mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-13 23:45:17 +00:00
feat: add volume backup notification support (#2875)
This commit is contained in:
@@ -49,6 +49,7 @@ const notificationBaseSchema = z.object({
|
|||||||
appDeploy: z.boolean().default(false),
|
appDeploy: z.boolean().default(false),
|
||||||
appBuildError: z.boolean().default(false),
|
appBuildError: z.boolean().default(false),
|
||||||
databaseBackup: z.boolean().default(false),
|
databaseBackup: z.boolean().default(false),
|
||||||
|
volumeBackup: z.boolean().default(false),
|
||||||
dokployRestart: z.boolean().default(false),
|
dokployRestart: z.boolean().default(false),
|
||||||
dockerCleanup: z.boolean().default(false),
|
dockerCleanup: z.boolean().default(false),
|
||||||
serverThreshold: z.boolean().default(false),
|
serverThreshold: z.boolean().default(false),
|
||||||
@@ -221,6 +222,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: notification.appDeploy,
|
appDeploy: notification.appDeploy,
|
||||||
dokployRestart: notification.dokployRestart,
|
dokployRestart: notification.dokployRestart,
|
||||||
databaseBackup: notification.databaseBackup,
|
databaseBackup: notification.databaseBackup,
|
||||||
|
volumeBackup: notification.volumeBackup,
|
||||||
dockerCleanup: notification.dockerCleanup,
|
dockerCleanup: notification.dockerCleanup,
|
||||||
webhookUrl: notification.slack?.webhookUrl,
|
webhookUrl: notification.slack?.webhookUrl,
|
||||||
channel: notification.slack?.channel || "",
|
channel: notification.slack?.channel || "",
|
||||||
@@ -234,6 +236,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: notification.appDeploy,
|
appDeploy: notification.appDeploy,
|
||||||
dokployRestart: notification.dokployRestart,
|
dokployRestart: notification.dokployRestart,
|
||||||
databaseBackup: notification.databaseBackup,
|
databaseBackup: notification.databaseBackup,
|
||||||
|
volumeBackup: notification.volumeBackup,
|
||||||
botToken: notification.telegram?.botToken,
|
botToken: notification.telegram?.botToken,
|
||||||
messageThreadId: notification.telegram?.messageThreadId || "",
|
messageThreadId: notification.telegram?.messageThreadId || "",
|
||||||
chatId: notification.telegram?.chatId,
|
chatId: notification.telegram?.chatId,
|
||||||
@@ -248,6 +251,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: notification.appDeploy,
|
appDeploy: notification.appDeploy,
|
||||||
dokployRestart: notification.dokployRestart,
|
dokployRestart: notification.dokployRestart,
|
||||||
databaseBackup: notification.databaseBackup,
|
databaseBackup: notification.databaseBackup,
|
||||||
|
volumeBackup: notification.volumeBackup,
|
||||||
type: notification.notificationType,
|
type: notification.notificationType,
|
||||||
webhookUrl: notification.discord?.webhookUrl,
|
webhookUrl: notification.discord?.webhookUrl,
|
||||||
decoration: notification.discord?.decoration || undefined,
|
decoration: notification.discord?.decoration || undefined,
|
||||||
@@ -261,6 +265,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: notification.appDeploy,
|
appDeploy: notification.appDeploy,
|
||||||
dokployRestart: notification.dokployRestart,
|
dokployRestart: notification.dokployRestart,
|
||||||
databaseBackup: notification.databaseBackup,
|
databaseBackup: notification.databaseBackup,
|
||||||
|
volumeBackup: notification.volumeBackup,
|
||||||
type: notification.notificationType,
|
type: notification.notificationType,
|
||||||
smtpServer: notification.email?.smtpServer,
|
smtpServer: notification.email?.smtpServer,
|
||||||
smtpPort: notification.email?.smtpPort,
|
smtpPort: notification.email?.smtpPort,
|
||||||
@@ -278,6 +283,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: notification.appDeploy,
|
appDeploy: notification.appDeploy,
|
||||||
dokployRestart: notification.dokployRestart,
|
dokployRestart: notification.dokployRestart,
|
||||||
databaseBackup: notification.databaseBackup,
|
databaseBackup: notification.databaseBackup,
|
||||||
|
volumeBackup: notification.volumeBackup,
|
||||||
type: notification.notificationType,
|
type: notification.notificationType,
|
||||||
appToken: notification.gotify?.appToken,
|
appToken: notification.gotify?.appToken,
|
||||||
decoration: notification.gotify?.decoration || undefined,
|
decoration: notification.gotify?.decoration || undefined,
|
||||||
@@ -292,6 +298,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: notification.appDeploy,
|
appDeploy: notification.appDeploy,
|
||||||
dokployRestart: notification.dokployRestart,
|
dokployRestart: notification.dokployRestart,
|
||||||
databaseBackup: notification.databaseBackup,
|
databaseBackup: notification.databaseBackup,
|
||||||
|
volumeBackup: notification.volumeBackup,
|
||||||
type: notification.notificationType,
|
type: notification.notificationType,
|
||||||
accessToken: notification.ntfy?.accessToken,
|
accessToken: notification.ntfy?.accessToken,
|
||||||
topic: notification.ntfy?.topic,
|
topic: notification.ntfy?.topic,
|
||||||
@@ -321,6 +328,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy,
|
appDeploy,
|
||||||
dokployRestart,
|
dokployRestart,
|
||||||
databaseBackup,
|
databaseBackup,
|
||||||
|
volumeBackup,
|
||||||
dockerCleanup,
|
dockerCleanup,
|
||||||
serverThreshold,
|
serverThreshold,
|
||||||
} = data;
|
} = data;
|
||||||
@@ -331,6 +339,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
|
volumeBackup: volumeBackup,
|
||||||
webhookUrl: data.webhookUrl,
|
webhookUrl: data.webhookUrl,
|
||||||
channel: data.channel,
|
channel: data.channel,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@@ -345,6 +354,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
|
volumeBackup: volumeBackup,
|
||||||
botToken: data.botToken,
|
botToken: data.botToken,
|
||||||
messageThreadId: data.messageThreadId || "",
|
messageThreadId: data.messageThreadId || "",
|
||||||
chatId: data.chatId,
|
chatId: data.chatId,
|
||||||
@@ -360,6 +370,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
|
volumeBackup: volumeBackup,
|
||||||
webhookUrl: data.webhookUrl,
|
webhookUrl: data.webhookUrl,
|
||||||
decoration: data.decoration,
|
decoration: data.decoration,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@@ -374,6 +385,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
|
volumeBackup: volumeBackup,
|
||||||
smtpServer: data.smtpServer,
|
smtpServer: data.smtpServer,
|
||||||
smtpPort: data.smtpPort,
|
smtpPort: data.smtpPort,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
@@ -392,6 +404,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
|
volumeBackup: volumeBackup,
|
||||||
serverUrl: data.serverUrl,
|
serverUrl: data.serverUrl,
|
||||||
appToken: data.appToken,
|
appToken: data.appToken,
|
||||||
priority: data.priority,
|
priority: data.priority,
|
||||||
@@ -407,6 +420,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
|
volumeBackup: volumeBackup,
|
||||||
serverUrl: data.serverUrl,
|
serverUrl: data.serverUrl,
|
||||||
accessToken: data.accessToken,
|
accessToken: data.accessToken,
|
||||||
topic: data.topic,
|
topic: data.topic,
|
||||||
@@ -1072,6 +1086,27 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="volumeBackup"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Volume Backup</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Trigger the action when a volume backup is created.
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="dockerCleanup"
|
name="dockerCleanup"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "notification" ADD COLUMN "volumeBackup" boolean DEFAULT false NOT NULL;
|
||||||
@@ -23,6 +23,7 @@ export const notifications = pgTable("notification", {
|
|||||||
appDeploy: boolean("appDeploy").notNull().default(false),
|
appDeploy: boolean("appDeploy").notNull().default(false),
|
||||||
appBuildError: boolean("appBuildError").notNull().default(false),
|
appBuildError: boolean("appBuildError").notNull().default(false),
|
||||||
databaseBackup: boolean("databaseBackup").notNull().default(false),
|
databaseBackup: boolean("databaseBackup").notNull().default(false),
|
||||||
|
volumeBackup: boolean("volumeBackup").notNull().default(false),
|
||||||
dokployRestart: boolean("dokployRestart").notNull().default(false),
|
dokployRestart: boolean("dokployRestart").notNull().default(false),
|
||||||
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
|
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
|
||||||
serverThreshold: boolean("serverThreshold").notNull().default(false),
|
serverThreshold: boolean("serverThreshold").notNull().default(false),
|
||||||
@@ -153,6 +154,7 @@ export const apiCreateSlack = notificationsSchema
|
|||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
@@ -180,6 +182,7 @@ export const apiCreateTelegram = notificationsSchema
|
|||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
@@ -209,6 +212,7 @@ export const apiCreateDiscord = notificationsSchema
|
|||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
@@ -239,6 +243,7 @@ export const apiCreateEmail = notificationsSchema
|
|||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
@@ -274,6 +279,7 @@ export const apiCreateGotify = notificationsSchema
|
|||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
@@ -307,6 +313,7 @@ export const apiCreateNtfy = notificationsSchema
|
|||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export const createSlackNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "slack",
|
notificationType: "slack",
|
||||||
@@ -85,6 +86,7 @@ export const updateSlackNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
@@ -145,6 +147,7 @@ export const createTelegramNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "telegram",
|
notificationType: "telegram",
|
||||||
@@ -176,6 +179,7 @@ export const updateTelegramNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
@@ -236,6 +240,7 @@ export const createDiscordNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "discord",
|
notificationType: "discord",
|
||||||
@@ -267,6 +272,7 @@ export const updateDiscordNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
@@ -330,6 +336,7 @@ export const createEmailNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "email",
|
notificationType: "email",
|
||||||
@@ -361,6 +368,7 @@ export const updateEmailNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
@@ -426,6 +434,7 @@ export const createGotifyNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "gotify",
|
notificationType: "gotify",
|
||||||
@@ -456,6 +465,7 @@ export const updateGotifyNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
@@ -516,6 +526,7 @@ export const createNtfyNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "ntfy",
|
notificationType: "ntfy",
|
||||||
@@ -546,6 +557,7 @@ export const updateNtfyNotification = async (
|
|||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
dockerCleanup: input.dockerCleanup,
|
dockerCleanup: input.dockerCleanup,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
|
|||||||
@@ -12,13 +12,69 @@ export const findVolumeBackupById = async (volumeBackupId: string) => {
|
|||||||
const volumeBackup = await db.query.volumeBackups.findFirst({
|
const volumeBackup = await db.query.volumeBackups.findFirst({
|
||||||
where: eq(volumeBackups.volumeBackupId, volumeBackupId),
|
where: eq(volumeBackups.volumeBackupId, volumeBackupId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
postgres: true,
|
with: {
|
||||||
mysql: true,
|
environment: {
|
||||||
mariadb: true,
|
with: {
|
||||||
mongo: true,
|
project: true,
|
||||||
redis: true,
|
},
|
||||||
compose: true,
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mongo: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
destination: true,
|
destination: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
import { db } from "@dokploy/server/db";
|
||||||
|
import { notifications } from "@dokploy/server/db/schema";
|
||||||
|
import { renderAsync } from "@react-email/components";
|
||||||
|
import { format } from "date-fns";
|
||||||
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
sendDiscordNotification,
|
||||||
|
sendEmailNotification,
|
||||||
|
sendGotifyNotification,
|
||||||
|
sendNtfyNotification,
|
||||||
|
sendSlackNotification,
|
||||||
|
sendTelegramNotification,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
export const sendVolumeBackupNotifications = async ({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
volumeName,
|
||||||
|
serviceType,
|
||||||
|
type,
|
||||||
|
errorMessage,
|
||||||
|
organizationId,
|
||||||
|
backupSize,
|
||||||
|
}: {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
volumeName: string;
|
||||||
|
serviceType: "application" | "postgres" | "mysql" | "mongodb" | "mariadb" | "redis" | "compose";
|
||||||
|
type: "error" | "success";
|
||||||
|
organizationId: string;
|
||||||
|
errorMessage?: string;
|
||||||
|
backupSize?: string;
|
||||||
|
}) => {
|
||||||
|
const date = new Date();
|
||||||
|
const unixDate = ~~(Number(date) / 1000);
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: and(
|
||||||
|
eq(notifications.volumeBackup, true),
|
||||||
|
eq(notifications.organizationId, organizationId),
|
||||||
|
),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
gotify: true,
|
||||||
|
ntfy: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
const subject = `Volume Backup ${type === "success" ? "Successful" : "Failed"} - ${applicationName}`;
|
||||||
|
const htmlContent = `
|
||||||
|
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||||
|
<h2 style="color: ${type === "success" ? "#00AA00" : "#AA0000"};">
|
||||||
|
${type === "success" ? "✅" : "❌"} Volume Backup ${type === "success" ? "Successful" : "Failed"}
|
||||||
|
</h2>
|
||||||
|
<div style="background-color: #f5f5f5; padding: 20px; border-radius: 5px; margin: 20px 0;">
|
||||||
|
<p><strong>Project:</strong> ${projectName}</p>
|
||||||
|
<p><strong>Application:</strong> ${applicationName}</p>
|
||||||
|
<p><strong>Volume Name:</strong> ${volumeName}</p>
|
||||||
|
<p><strong>Service Type:</strong> ${serviceType}</p>
|
||||||
|
${backupSize ? `<p><strong>Backup Size:</strong> ${backupSize}</p>` : ""}
|
||||||
|
<p><strong>Date:</strong> ${date.toLocaleString()}</p>
|
||||||
|
${type === "error" && errorMessage ? `<p><strong>Error:</strong> <code>${errorMessage}</code></p>` : ""}
|
||||||
|
</div>
|
||||||
|
<p style="color: #666; font-size: 12px;">
|
||||||
|
This notification was sent by Dokploy Volume Backup System.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
await sendEmailNotification(email, subject, htmlContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
const decorate = (decoration: string, text: string) =>
|
||||||
|
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
||||||
|
|
||||||
|
await sendDiscordNotification(discord, {
|
||||||
|
title:
|
||||||
|
type === "success"
|
||||||
|
? decorate(">", "`✅` Volume Backup Successful")
|
||||||
|
: decorate(">", "`❌` Volume Backup Failed"),
|
||||||
|
color: type === "success" ? 0x57f287 : 0xed4245,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: decorate("`🛠️`", "Project"),
|
||||||
|
value: projectName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`⚙️`", "Application"),
|
||||||
|
value: applicationName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`💾`", "Volume Name"),
|
||||||
|
value: volumeName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`🔧`", "Service Type"),
|
||||||
|
value: serviceType,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
...(backupSize ? [{
|
||||||
|
name: decorate("`📊`", "Backup Size"),
|
||||||
|
value: backupSize,
|
||||||
|
inline: true,
|
||||||
|
}] : []),
|
||||||
|
{
|
||||||
|
name: decorate("`📅`", "Date"),
|
||||||
|
value: `<t:${unixDate}:D>`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`⌚`", "Time"),
|
||||||
|
value: `<t:${unixDate}:t>`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: decorate("`❓`", "Type"),
|
||||||
|
value: type
|
||||||
|
.replace("error", "Failed")
|
||||||
|
.replace("success", "Successful"),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
...(type === "error" && errorMessage
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: decorate("`⚠️`", "Error Message"),
|
||||||
|
value: `\`\`\`${errorMessage}\`\`\``,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Volume Backup Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gotify) {
|
||||||
|
const decorate = (decoration: string, text: string) =>
|
||||||
|
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||||
|
|
||||||
|
await sendGotifyNotification(
|
||||||
|
gotify,
|
||||||
|
decorate(
|
||||||
|
type === "success" ? "✅" : "❌",
|
||||||
|
`Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||||
|
),
|
||||||
|
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||||
|
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||||
|
`${decorate("💾", `Volume Name: ${volumeName}`)}` +
|
||||||
|
`${decorate("🔧", `Service Type: ${serviceType}`)}` +
|
||||||
|
`${backupSize ? decorate("📊", `Backup Size: ${backupSize}`) : ""}` +
|
||||||
|
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||||
|
`${type === "error" && errorMessage ? decorate("❌", `Error:\n${errorMessage}`) : ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ntfy) {
|
||||||
|
await sendNtfyNotification(
|
||||||
|
ntfy,
|
||||||
|
`Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||||
|
`${type === "success" ? "white_check_mark" : "x"}`,
|
||||||
|
"",
|
||||||
|
`🛠️Project: ${projectName}\n` +
|
||||||
|
`⚙️Application: ${applicationName}\n` +
|
||||||
|
`💾Volume Name: ${volumeName}\n` +
|
||||||
|
`🔧Service Type: ${serviceType}\n` +
|
||||||
|
`${backupSize ? `📊Backup Size: ${backupSize}\n` : ""}` +
|
||||||
|
`🕒Date: ${date.toLocaleString()}\n` +
|
||||||
|
`${type === "error" && errorMessage ? `❌Error:\n${errorMessage}` : ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
const isError = type === "error" && errorMessage;
|
||||||
|
|
||||||
|
const statusEmoji = type === "success" ? "✅" : "❌";
|
||||||
|
const typeStatus = type === "success" ? "Successful" : "Failed";
|
||||||
|
const errorMsg = isError
|
||||||
|
? `\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`
|
||||||
|
: "";
|
||||||
|
const sizeInfo = backupSize ? `\n<b>Backup Size:</b> ${backupSize}` : "";
|
||||||
|
|
||||||
|
const messageText = `<b>${statusEmoji} Volume Backup ${typeStatus}</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Volume Name:</b> ${volumeName}\n<b>Service Type:</b> ${serviceType}${sizeInfo}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}${isError ? errorMsg : ""}`;
|
||||||
|
|
||||||
|
await sendTelegramNotification(telegram, messageText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { channel } = slack;
|
||||||
|
await sendSlackNotification(slack, {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: type === "success" ? "#00FF00" : "#FF0000",
|
||||||
|
pretext:
|
||||||
|
type === "success"
|
||||||
|
? ":white_check_mark: *Volume Backup Successful*"
|
||||||
|
: ":x: *Volume Backup Failed*",
|
||||||
|
fields: [
|
||||||
|
...(type === "error" && errorMessage
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: "Error Message",
|
||||||
|
value: errorMessage,
|
||||||
|
short: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
title: "Project",
|
||||||
|
value: projectName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Volume Name",
|
||||||
|
value: volumeName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Service Type",
|
||||||
|
value: serviceType,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
...(backupSize ? [{
|
||||||
|
title: "Backup Size",
|
||||||
|
value: backupSize,
|
||||||
|
short: true,
|
||||||
|
}] : []),
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: type,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
value: type === "success" ? "Successful" : "Failed",
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "@dokploy/server/utils/process/execAsync";
|
} from "@dokploy/server/utils/process/execAsync";
|
||||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||||
import { getS3Credentials, normalizeS3Path } from "../backups/utils";
|
import { getS3Credentials, normalizeS3Path } from "../backups/utils";
|
||||||
|
import { sendVolumeBackupNotifications } from "../notifications/volume-backup";
|
||||||
import { backupVolume } from "./backup";
|
import { backupVolume } from "./backup";
|
||||||
|
|
||||||
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
|
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
|
||||||
@@ -77,6 +78,41 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
|
|
||||||
|
// Send success notification
|
||||||
|
try {
|
||||||
|
const projectName = volumeBackup.application?.environment?.project?.name ||
|
||||||
|
volumeBackup.compose?.environment?.project?.name ||
|
||||||
|
volumeBackup.postgres?.environment?.project?.name ||
|
||||||
|
volumeBackup.mysql?.environment?.project?.name ||
|
||||||
|
volumeBackup.mariadb?.environment?.project?.name ||
|
||||||
|
volumeBackup.mongo?.environment?.project?.name ||
|
||||||
|
volumeBackup.redis?.environment?.project?.name ||
|
||||||
|
"Unknown Project";
|
||||||
|
|
||||||
|
const organizationId = volumeBackup.application?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.compose?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.postgres?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.mysql?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.mariadb?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.mongo?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.redis?.environment?.project?.organizationId ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
// Map service type to match notification function expectations
|
||||||
|
const mappedServiceType = volumeBackup.serviceType === "mongo" ? "mongodb" : volumeBackup.serviceType;
|
||||||
|
|
||||||
|
await sendVolumeBackupNotifications({
|
||||||
|
projectName,
|
||||||
|
applicationName: volumeBackup.name,
|
||||||
|
volumeName: volumeBackup.volumeName,
|
||||||
|
serviceType: mappedServiceType as "application" | "postgres" | "mysql" | "mongodb" | "mariadb" | "redis" | "compose",
|
||||||
|
type: "success",
|
||||||
|
organizationId,
|
||||||
|
});
|
||||||
|
} catch (notificationError) {
|
||||||
|
console.error("Failed to send volume backup success notification:", notificationError);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { VOLUME_BACKUPS_PATH } = paths(!!serverId);
|
const { VOLUME_BACKUPS_PATH } = paths(!!serverId);
|
||||||
const volumeBackupPath = path.join(
|
const volumeBackupPath = path.join(
|
||||||
@@ -92,6 +128,42 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
|
|||||||
}
|
}
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
|
|
||||||
|
// Send error notification
|
||||||
|
try {
|
||||||
|
const projectName = volumeBackup.application?.environment?.project?.name ||
|
||||||
|
volumeBackup.compose?.environment?.project?.name ||
|
||||||
|
volumeBackup.postgres?.environment?.project?.name ||
|
||||||
|
volumeBackup.mysql?.environment?.project?.name ||
|
||||||
|
volumeBackup.mariadb?.environment?.project?.name ||
|
||||||
|
volumeBackup.mongo?.environment?.project?.name ||
|
||||||
|
volumeBackup.redis?.environment?.project?.name ||
|
||||||
|
"Unknown Project";
|
||||||
|
|
||||||
|
const organizationId = volumeBackup.application?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.compose?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.postgres?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.mysql?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.mariadb?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.mongo?.environment?.project?.organizationId ||
|
||||||
|
volumeBackup.redis?.environment?.project?.organizationId ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
// Map service type to match notification function expectations
|
||||||
|
const mappedServiceType = volumeBackup.serviceType === "mongo" ? "mongodb" : volumeBackup.serviceType;
|
||||||
|
|
||||||
|
await sendVolumeBackupNotifications({
|
||||||
|
projectName,
|
||||||
|
applicationName: volumeBackup.name,
|
||||||
|
volumeName: volumeBackup.volumeName,
|
||||||
|
serviceType: mappedServiceType as "application" | "postgres" | "mysql" | "mongodb" | "mariadb" | "redis" | "compose",
|
||||||
|
type: "error",
|
||||||
|
organizationId,
|
||||||
|
errorMessage: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
} catch (notificationError) {
|
||||||
|
console.error("Failed to send volume backup error notification:", notificationError);
|
||||||
|
}
|
||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user