feat(volume-backup): add volume backup email notification template and integrate with notification system

This commit is contained in:
Mauricio Siu
2025-12-07 02:15:10 -06:00
parent bd751658be
commit f5de5130f3
6 changed files with 7074 additions and 119 deletions

View File

@@ -1,5 +1,6 @@
import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import { VolumeBackupEmail } from "@dokploy/server/emails/emails/volume-backup";
import { renderAsync } from "@react-email/components";
import { format } from "date-fns";
import { and, eq } from "drizzle-orm";
@@ -60,25 +61,18 @@ export const sendVolumeBackupNotifications = async ({
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>
`;
const htmlContent = await renderAsync(
VolumeBackupEmail({
projectName,
applicationName,
volumeName,
serviceType,
type,
errorMessage,
backupSize,
date: date.toISOString(),
}),
);
await sendEmailNotification(email, subject, htmlContent);
}

View File

@@ -14,6 +14,51 @@ import { getS3Credentials, normalizeS3Path } from "../backups/utils";
import { sendVolumeBackupNotifications } from "../notifications/volume-backup";
import { backupVolume } from "./backup";
// Helper functions to extract project info from volume backup
const getProjectName = (
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,
): string => {
const services = [
volumeBackup.application,
volumeBackup.compose,
volumeBackup.postgres,
volumeBackup.mysql,
volumeBackup.mariadb,
volumeBackup.mongo,
volumeBackup.redis,
];
for (const service of services) {
if (service?.environment?.project?.name) {
return service.environment.project.name;
}
}
return "Unknown Project";
};
const getOrganizationId = (
volumeBackup: Awaited<ReturnType<typeof findVolumeBackupById>>,
): string => {
const services = [
volumeBackup.application,
volumeBackup.compose,
volumeBackup.postgres,
volumeBackup.mysql,
volumeBackup.mariadb,
volumeBackup.mongo,
volumeBackup.redis,
];
for (const service of services) {
if (service?.environment?.project?.organizationId) {
return service.environment.project.organizationId;
}
}
return "";
};
export const scheduleVolumeBackup = async (volumeBackupId: string) => {
const volumeBackup = await findVolumeBackupById(volumeBackupId);
scheduleJob(volumeBackupId, volumeBackup.cronExpression, async () => {
@@ -62,7 +107,8 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
title: "Volume Backup",
description: "Volume Backup",
});
const projectName = getProjectName(volumeBackup);
const organizationId = getOrganizationId(volumeBackup);
try {
const command = await backupVolume(volumeBackup);
@@ -79,55 +125,20 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
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";
// Map service type to match notification function expectations
const mappedServiceType =
volumeBackup.serviceType === "mongo"
? "mongodb"
: volumeBackup.serviceType;
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,
);
}
await sendVolumeBackupNotifications({
projectName,
applicationName: volumeBackup.name,
volumeName: volumeBackup.volumeName,
serviceType: mappedServiceType,
type: "success",
organizationId,
});
} catch (error) {
const { VOLUME_BACKUPS_PATH } = paths(!!serverId);
const volumeBackupPath = path.join(
@@ -144,56 +155,19 @@ export const runVolumeBackup = async (volumeBackupId: string) => {
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 mappedServiceType =
volumeBackup.serviceType === "mongo"
? "mongodb"
: volumeBackup.serviceType;
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);
await sendVolumeBackupNotifications({
projectName,
applicationName: volumeBackup.name,
volumeName: volumeBackup.volumeName,
serviceType: mappedServiceType,
type: "error",
organizationId,
errorMessage: error instanceof Error ? error.message : String(error),
});
}
};