mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor(notification): split functions for each notification actio
This commit is contained in:
parent
e0bde5cec9
commit
9954d5b209
@ -17,10 +17,9 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { findAdmin } from "./admin";
|
import { findAdmin } from "./admin";
|
||||||
import { createDeployment, updateDeploymentStatus } from "./deployment";
|
import { createDeployment, updateDeploymentStatus } from "./deployment";
|
||||||
import {
|
|
||||||
sendBuildErrorNotifications,
|
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
||||||
sendBuildSuccessNotifications,
|
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
||||||
} from "./notification";
|
|
||||||
import { validUniqueServerAppName } from "./project";
|
import { validUniqueServerAppName } from "./project";
|
||||||
export type Application = typeof applications.$inferSelect;
|
export type Application = typeof applications.$inferSelect;
|
||||||
|
|
||||||
@ -168,7 +167,6 @@ export const deployApplication = async ({
|
|||||||
buildLink: deployment.logPath,
|
buildLink: deployment.logPath,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error on build", error);
|
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
await updateApplicationStatus(applicationId, "error");
|
await updateApplicationStatus(applicationId, "error");
|
||||||
await sendBuildErrorNotifications({
|
await sendBuildErrorNotifications({
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
import { BuildFailedEmail } from "@/emails/emails/build-failed";
|
|
||||||
import BuildSuccessEmail from "@/emails/emails/build-success";
|
|
||||||
import DatabaseBackupEmail from "@/emails/emails/database-backup";
|
|
||||||
import DockerCleanupEmail from "@/emails/emails/docker-cleanup";
|
|
||||||
import DokployRestartEmail from "@/emails/emails/dokploy-restart";
|
|
||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
type apiCreateDiscord,
|
type apiCreateDiscord,
|
||||||
type apiCreateEmail,
|
type apiCreateEmail,
|
||||||
type apiCreateSlack,
|
type apiCreateSlack,
|
||||||
type apiCreateTelegram,
|
type apiCreateTelegram,
|
||||||
type apiTestDiscordConnection,
|
|
||||||
type apiTestEmailConnection,
|
|
||||||
type apiTestSlackConnection,
|
|
||||||
type apiTestTelegramConnection,
|
|
||||||
type apiUpdateDiscord,
|
type apiUpdateDiscord,
|
||||||
type apiUpdateEmail,
|
type apiUpdateEmail,
|
||||||
type apiUpdateSlack,
|
type apiUpdateSlack,
|
||||||
@ -23,11 +14,8 @@ import {
|
|||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { render } from "@react-email/components";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import nodemailer from "nodemailer";
|
|
||||||
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
|
||||||
|
|
||||||
export type Notification = typeof notifications.$inferSelect;
|
export type Notification = typeof notifications.$inferSelect;
|
||||||
|
|
||||||
@ -419,697 +407,3 @@ export const updateDestinationById = async (
|
|||||||
|
|
||||||
return result[0];
|
return result[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BuildFailedEmailProps {
|
|
||||||
projectName: string;
|
|
||||||
applicationName: string;
|
|
||||||
applicationType: string;
|
|
||||||
errorMessage: string;
|
|
||||||
buildLink: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendBuildErrorNotifications = async ({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
applicationType,
|
|
||||||
errorMessage,
|
|
||||||
buildLink,
|
|
||||||
}: BuildFailedEmailProps) => {
|
|
||||||
const date = new Date();
|
|
||||||
const notificationList = await db.query.notifications.findMany({
|
|
||||||
where: eq(notifications.appBuildError, true),
|
|
||||||
with: {
|
|
||||||
email: true,
|
|
||||||
discord: true,
|
|
||||||
telegram: true,
|
|
||||||
slack: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
|
||||||
const { email, discord, telegram, slack } = notification;
|
|
||||||
if (email) {
|
|
||||||
await sendEmailNotification(
|
|
||||||
email,
|
|
||||||
"Build failed for dokploy",
|
|
||||||
render(
|
|
||||||
BuildFailedEmail({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
applicationType,
|
|
||||||
errorMessage,
|
|
||||||
buildLink,
|
|
||||||
date: date.toLocaleString(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discord) {
|
|
||||||
const { webhookUrl } = discord;
|
|
||||||
await sendDiscordNotification(discord, {
|
|
||||||
title: "⚠️ Build Failed",
|
|
||||||
color: 0xff0000,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Project",
|
|
||||||
value: projectName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Application",
|
|
||||||
value: applicationName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Type",
|
|
||||||
value: applicationType,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error",
|
|
||||||
value: errorMessage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Build Link",
|
|
||||||
value: buildLink,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: date.toISOString(),
|
|
||||||
footer: {
|
|
||||||
text: "Dokploy Build Notification",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (telegram) {
|
|
||||||
await sendTelegramNotification(
|
|
||||||
telegram,
|
|
||||||
`
|
|
||||||
<b>⚠️ Build Failed</b>
|
|
||||||
|
|
||||||
<b>Project:</b> ${projectName}
|
|
||||||
<b>Application:</b> ${applicationName}
|
|
||||||
<b>Type:</b> ${applicationType}
|
|
||||||
<b>Time:</b> ${date.toLocaleString()}
|
|
||||||
|
|
||||||
<b>Error:</b>
|
|
||||||
<pre>${errorMessage}</pre>
|
|
||||||
|
|
||||||
<b>Build Details:</b> ${buildLink}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slack) {
|
|
||||||
const { channel } = slack;
|
|
||||||
await sendSlackNotification(slack, {
|
|
||||||
channel: channel,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
color: "#FF0000",
|
|
||||||
pretext: ":warning: *Build Failed*",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: "Project",
|
|
||||||
value: projectName,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Application",
|
|
||||||
value: applicationName,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Type",
|
|
||||||
value: applicationType,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Error",
|
|
||||||
value: `\`\`\`${errorMessage}\`\`\``,
|
|
||||||
short: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
text: "View Build Details",
|
|
||||||
url: "https://doks.dev/build-details",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface BuildSuccessEmailProps {
|
|
||||||
projectName: string;
|
|
||||||
applicationName: string;
|
|
||||||
applicationType: string;
|
|
||||||
buildLink: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sendBuildSuccessNotifications = async ({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
applicationType,
|
|
||||||
buildLink,
|
|
||||||
}: BuildSuccessEmailProps) => {
|
|
||||||
const date = new Date();
|
|
||||||
const notificationList = await db.query.notifications.findMany({
|
|
||||||
where: eq(notifications.appDeploy, true),
|
|
||||||
with: {
|
|
||||||
email: true,
|
|
||||||
discord: true,
|
|
||||||
telegram: true,
|
|
||||||
slack: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
|
||||||
const { email, discord, telegram, slack } = notification;
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
await sendEmailNotification(
|
|
||||||
email,
|
|
||||||
"Build success for dokploy",
|
|
||||||
render(
|
|
||||||
BuildSuccessEmail({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
applicationType,
|
|
||||||
buildLink,
|
|
||||||
date: date.toLocaleString(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discord) {
|
|
||||||
await sendDiscordNotification(discord, {
|
|
||||||
title: "✅ Build Success",
|
|
||||||
color: 0x00ff00,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Project",
|
|
||||||
value: projectName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Application",
|
|
||||||
value: applicationName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Type",
|
|
||||||
value: applicationType,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Build Link",
|
|
||||||
value: buildLink,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: date.toISOString(),
|
|
||||||
footer: {
|
|
||||||
text: "Dokploy Build Notification",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (telegram) {
|
|
||||||
await sendTelegramNotification(
|
|
||||||
telegram,
|
|
||||||
`
|
|
||||||
<b>✅ Build Success</b>
|
|
||||||
|
|
||||||
<b>Project:</b> ${projectName}
|
|
||||||
<b>Application:</b> ${applicationName}
|
|
||||||
<b>Type:</b> ${applicationType}
|
|
||||||
<b>Time:</b> ${date.toLocaleString()}
|
|
||||||
|
|
||||||
<b>Build Details:</b> ${buildLink}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slack) {
|
|
||||||
const { webhookUrl, channel } = slack;
|
|
||||||
await sendSlackNotification(slack, {
|
|
||||||
channel: channel,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
color: "#00FF00",
|
|
||||||
pretext: ":white_check_mark: *Build Success*",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: "Project",
|
|
||||||
value: projectName,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Application",
|
|
||||||
value: applicationName,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Type",
|
|
||||||
value: applicationType,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Build Link",
|
|
||||||
value: buildLink,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
text: "View Build Details",
|
|
||||||
url: "https://doks.dev/build-details",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendDatabaseBackupNotifications = async ({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
databaseType,
|
|
||||||
type,
|
|
||||||
errorMessage,
|
|
||||||
}: {
|
|
||||||
projectName: string;
|
|
||||||
applicationName: string;
|
|
||||||
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
|
|
||||||
type: "error" | "success";
|
|
||||||
errorMessage?: string;
|
|
||||||
}) => {
|
|
||||||
const date = new Date();
|
|
||||||
const notificationList = await db.query.notifications.findMany({
|
|
||||||
where: eq(notifications.databaseBackup, true),
|
|
||||||
with: {
|
|
||||||
email: true,
|
|
||||||
discord: true,
|
|
||||||
telegram: true,
|
|
||||||
slack: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
|
||||||
const { email, discord, telegram, slack } = notification;
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
await sendEmailNotification(
|
|
||||||
email,
|
|
||||||
"Database backup for dokploy",
|
|
||||||
render(
|
|
||||||
DatabaseBackupEmail({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
databaseType,
|
|
||||||
type,
|
|
||||||
errorMessage,
|
|
||||||
date: date.toLocaleString(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discord) {
|
|
||||||
await sendDiscordNotification(discord, {
|
|
||||||
title:
|
|
||||||
type === "success"
|
|
||||||
? "✅ Database Backup Successful"
|
|
||||||
: "❌ Database Backup Failed",
|
|
||||||
color: type === "success" ? 0x00ff00 : 0xff0000,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Project",
|
|
||||||
value: projectName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Application",
|
|
||||||
value: applicationName,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Type",
|
|
||||||
value: databaseType,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Type",
|
|
||||||
value: type,
|
|
||||||
},
|
|
||||||
...(type === "error" && errorMessage
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: "Error Message",
|
|
||||||
value: errorMessage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
timestamp: date.toISOString(),
|
|
||||||
footer: {
|
|
||||||
text: "Dokploy Database Backup Notification",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (telegram) {
|
|
||||||
const statusEmoji = type === "success" ? "✅" : "❌";
|
|
||||||
const messageText = `
|
|
||||||
<b>${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"}</b>
|
|
||||||
|
|
||||||
<b>Project:</b> ${projectName}
|
|
||||||
<b>Application:</b> ${applicationName}
|
|
||||||
<b>Type:</b> ${databaseType}
|
|
||||||
<b>Time:</b> ${date.toLocaleString()}
|
|
||||||
|
|
||||||
<b>Status:</b> ${type === "success" ? "Successful" : "Failed"}
|
|
||||||
${type === "error" && errorMessage ? `<b>Error:</b> ${errorMessage}` : ""}
|
|
||||||
`;
|
|
||||||
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: *Database Backup Successful*"
|
|
||||||
: ":x: *Database 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: "Type",
|
|
||||||
value: databaseType,
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Type",
|
|
||||||
value: type,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Status",
|
|
||||||
value: type === "success" ? "Successful" : "Failed",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
text: "View Build Details",
|
|
||||||
url: "https://doks.dev/build-details",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendDockerCleanupNotifications = async (
|
|
||||||
message = "Docker cleanup for dokploy",
|
|
||||||
) => {
|
|
||||||
const date = new Date();
|
|
||||||
const notificationList = await db.query.notifications.findMany({
|
|
||||||
where: eq(notifications.dockerCleanup, true),
|
|
||||||
with: {
|
|
||||||
email: true,
|
|
||||||
discord: true,
|
|
||||||
telegram: true,
|
|
||||||
slack: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
|
||||||
const { email, discord, telegram, slack } = notification;
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
await sendEmailNotification(
|
|
||||||
email,
|
|
||||||
"Docker cleanup for dokploy",
|
|
||||||
render(DockerCleanupEmail({ message, date: date.toLocaleString() })),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discord) {
|
|
||||||
await sendDiscordNotification(discord, {
|
|
||||||
title: "✅ Docker Cleanup",
|
|
||||||
color: 0x00ff00,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Message",
|
|
||||||
value: message,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: date.toISOString(),
|
|
||||||
footer: {
|
|
||||||
text: "Dokploy Docker Cleanup Notification",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (telegram) {
|
|
||||||
await sendTelegramNotification(
|
|
||||||
telegram,
|
|
||||||
`
|
|
||||||
<b>✅ Docker Cleanup</b>
|
|
||||||
<b>Message:</b> ${message}
|
|
||||||
<b>Time:</b> ${date.toLocaleString()}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slack) {
|
|
||||||
const { channel } = slack;
|
|
||||||
await sendSlackNotification(slack, {
|
|
||||||
channel: channel,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
color: "#00FF00",
|
|
||||||
pretext: ":white_check_mark: *Docker Cleanup*",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: "Message",
|
|
||||||
value: message,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
text: "View Build Details",
|
|
||||||
url: "https://doks.dev/build-details",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendEmailNotification = async (
|
|
||||||
connection: typeof email.$inferInsert,
|
|
||||||
subject: string,
|
|
||||||
htmlContent: string,
|
|
||||||
) => {
|
|
||||||
const { smtpServer, smtpPort, username, password, fromAddress, toAddresses } =
|
|
||||||
connection;
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
host: smtpServer,
|
|
||||||
port: smtpPort,
|
|
||||||
secure: smtpPort === 465,
|
|
||||||
auth: { user: username, pass: password },
|
|
||||||
});
|
|
||||||
|
|
||||||
await transporter.sendMail({
|
|
||||||
from: fromAddress,
|
|
||||||
to: toAddresses.join(", "),
|
|
||||||
subject,
|
|
||||||
html: htmlContent,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendDiscordNotification = async (
|
|
||||||
connection: typeof discord.$inferInsert,
|
|
||||||
embed: any,
|
|
||||||
) => {
|
|
||||||
const response = await fetch(connection.webhookUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ embeds: [embed] }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to send Discord notification");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendTelegramNotification = async (
|
|
||||||
connection: typeof telegram.$inferInsert,
|
|
||||||
messageText: string,
|
|
||||||
) => {
|
|
||||||
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
chat_id: connection.chatId,
|
|
||||||
text: messageText,
|
|
||||||
parse_mode: "HTML",
|
|
||||||
disable_web_page_preview: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to send Telegram notification");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendSlackNotification = async (
|
|
||||||
connection: typeof slack.$inferInsert,
|
|
||||||
message: any,
|
|
||||||
) => {
|
|
||||||
const response = await fetch(connection.webhookUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(message),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to send Slack notification");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendDokployRestartNotifications = async () => {
|
|
||||||
const date = new Date();
|
|
||||||
const notificationList = await db.query.notifications.findMany({
|
|
||||||
where: eq(notifications.dokployRestart, true),
|
|
||||||
with: {
|
|
||||||
email: true,
|
|
||||||
discord: true,
|
|
||||||
telegram: true,
|
|
||||||
slack: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
|
||||||
const { email, discord, telegram, slack } = notification;
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
await sendEmailNotification(
|
|
||||||
email,
|
|
||||||
"Dokploy Server Restarted",
|
|
||||||
render(DokployRestartEmail({ date: date.toLocaleString() })),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discord) {
|
|
||||||
await sendDiscordNotification(discord, {
|
|
||||||
title: "✅ Dokploy Server Restarted",
|
|
||||||
color: 0x00ff00,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timestamp: date.toISOString(),
|
|
||||||
footer: {
|
|
||||||
text: "Dokploy Restart Notification",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (telegram) {
|
|
||||||
await sendTelegramNotification(
|
|
||||||
telegram,
|
|
||||||
`
|
|
||||||
<b>✅ Dokploy Serverd Restarted</b>
|
|
||||||
<b>Time:</b> ${date.toLocaleString()}
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slack) {
|
|
||||||
const { channel } = slack;
|
|
||||||
await sendSlackNotification(slack, {
|
|
||||||
channel: channel,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
color: "#FF0000",
|
|
||||||
pretext: ":white_check_mark: *Dokploy Server Restarted*",
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
title: "Time",
|
|
||||||
value: date.toLocaleString(),
|
|
||||||
short: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
text: "View Build Details",
|
|
||||||
url: "https://doks.dev/build-details",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -2,7 +2,6 @@ import http from "node:http";
|
|||||||
import { migration } from "@/server/db/migration";
|
import { migration } from "@/server/db/migration";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
import next from "next";
|
import next from "next";
|
||||||
import { sendDokployRestartNotifications } from "./api/services/notification";
|
|
||||||
import { deploymentWorker } from "./queues/deployments-queue";
|
import { deploymentWorker } from "./queues/deployments-queue";
|
||||||
import { setupDirectories } from "./setup/config-paths";
|
import { setupDirectories } from "./setup/config-paths";
|
||||||
import { initializePostgres } from "./setup/postgres-setup";
|
import { initializePostgres } from "./setup/postgres-setup";
|
||||||
@ -15,6 +14,7 @@ import {
|
|||||||
initializeTraefik,
|
initializeTraefik,
|
||||||
} from "./setup/traefik-setup";
|
} from "./setup/traefik-setup";
|
||||||
import { initCronJobs } from "./utils/backups";
|
import { initCronJobs } from "./utils/backups";
|
||||||
|
import { sendDokployRestartNotifications } from "./utils/notifications/dokploy-restart";
|
||||||
import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs";
|
import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs";
|
||||||
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
|
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
|
||||||
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";
|
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";
|
||||||
|
@ -2,9 +2,9 @@ import { unlink } from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import type { Mariadb } from "@/server/api/services/mariadb";
|
import type { Mariadb } from "@/server/api/services/mariadb";
|
||||||
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
|
||||||
import { findProjectById } from "@/server/api/services/project";
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ import { unlink } from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import type { Mongo } from "@/server/api/services/mongo";
|
import type { Mongo } from "@/server/api/services/mongo";
|
||||||
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
|
||||||
import { findProjectById } from "@/server/api/services/project";
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ import { unlink } from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import type { MySql } from "@/server/api/services/mysql";
|
import type { MySql } from "@/server/api/services/mysql";
|
||||||
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
|
||||||
import { findProjectById } from "@/server/api/services/project";
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { unlink } from "node:fs/promises";
|
import { unlink } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
|
||||||
import type { Postgres } from "@/server/api/services/postgres";
|
import type { Postgres } from "@/server/api/services/postgres";
|
||||||
import { findProjectById } from "@/server/api/services/project";
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
|
|
||||||
|
160
server/utils/notifications/build-error.ts
Normal file
160
server/utils/notifications/build-error.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import BuildFailedEmail from "@/emails/emails/build-failed";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { notifications } from "@/server/db/schema";
|
||||||
|
import { render } from "@react-email/components";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
sendDiscordNotification,
|
||||||
|
sendEmailNotification,
|
||||||
|
sendSlackNotification,
|
||||||
|
sendTelegramNotification,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
applicationType: string;
|
||||||
|
errorMessage: string;
|
||||||
|
buildLink: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendBuildErrorNotifications = async ({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
applicationType,
|
||||||
|
errorMessage,
|
||||||
|
buildLink,
|
||||||
|
}: Props) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.appBuildError, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
if (email) {
|
||||||
|
await sendEmailNotification(
|
||||||
|
email,
|
||||||
|
"Build failed for dokploy",
|
||||||
|
render(
|
||||||
|
BuildFailedEmail({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
applicationType,
|
||||||
|
errorMessage,
|
||||||
|
buildLink,
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
await sendDiscordNotification(discord, {
|
||||||
|
title: "⚠️ Build Failed",
|
||||||
|
color: 0xff0000,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Project",
|
||||||
|
value: projectName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: applicationType,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error",
|
||||||
|
value: errorMessage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Build Link",
|
||||||
|
value: buildLink,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Build Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
await sendTelegramNotification(
|
||||||
|
telegram,
|
||||||
|
`
|
||||||
|
<b>⚠️ Build Failed</b>
|
||||||
|
|
||||||
|
<b>Project:</b> ${projectName}
|
||||||
|
<b>Application:</b> ${applicationName}
|
||||||
|
<b>Type:</b> ${applicationType}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
|
||||||
|
<b>Error:</b>
|
||||||
|
<pre>${errorMessage}</pre>
|
||||||
|
|
||||||
|
<b>Build Details:</b> ${buildLink}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { channel } = slack;
|
||||||
|
await sendSlackNotification(slack, {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#FF0000",
|
||||||
|
pretext: ":warning: *Build Failed*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Project",
|
||||||
|
value: projectName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: applicationType,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Error",
|
||||||
|
value: `\`\`\`${errorMessage}\`\`\``,
|
||||||
|
short: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
150
server/utils/notifications/build-success.ts
Normal file
150
server/utils/notifications/build-success.ts
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import BuildSuccessEmail from "@/emails/emails/build-success";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { notifications } from "@/server/db/schema";
|
||||||
|
import { render } from "@react-email/components";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
sendDiscordNotification,
|
||||||
|
sendEmailNotification,
|
||||||
|
sendSlackNotification,
|
||||||
|
sendTelegramNotification,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
applicationType: string;
|
||||||
|
buildLink: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendBuildSuccessNotifications = async ({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
applicationType,
|
||||||
|
buildLink,
|
||||||
|
}: Props) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.appDeploy, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
await sendEmailNotification(
|
||||||
|
email,
|
||||||
|
"Build success for dokploy",
|
||||||
|
render(
|
||||||
|
BuildSuccessEmail({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
applicationType,
|
||||||
|
buildLink,
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
await sendDiscordNotification(discord, {
|
||||||
|
title: "✅ Build Success",
|
||||||
|
color: 0x00ff00,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Project",
|
||||||
|
value: projectName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: applicationType,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Build Link",
|
||||||
|
value: buildLink,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Build Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
await sendTelegramNotification(
|
||||||
|
telegram,
|
||||||
|
`
|
||||||
|
<b>✅ Build Success</b>
|
||||||
|
|
||||||
|
<b>Project:</b> ${projectName}
|
||||||
|
<b>Application:</b> ${applicationName}
|
||||||
|
<b>Type:</b> ${applicationType}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
|
||||||
|
<b>Build Details:</b> ${buildLink}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { webhookUrl, channel } = slack;
|
||||||
|
await sendSlackNotification(slack, {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#00FF00",
|
||||||
|
pretext: ":white_check_mark: *Build Success*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Project",
|
||||||
|
value: projectName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: applicationType,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Build Link",
|
||||||
|
value: buildLink,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
183
server/utils/notifications/database-backup.ts
Normal file
183
server/utils/notifications/database-backup.ts
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import DatabaseBackupEmail from "@/emails/emails/database-backup";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { notifications } from "@/server/db/schema";
|
||||||
|
import { render } from "@react-email/components";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
sendDiscordNotification,
|
||||||
|
sendEmailNotification,
|
||||||
|
sendSlackNotification,
|
||||||
|
sendTelegramNotification,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
export const sendDatabaseBackupNotifications = async ({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
databaseType,
|
||||||
|
type,
|
||||||
|
errorMessage,
|
||||||
|
}: {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
|
||||||
|
type: "error" | "success";
|
||||||
|
errorMessage?: string;
|
||||||
|
}) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.databaseBackup, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
await sendEmailNotification(
|
||||||
|
email,
|
||||||
|
"Database backup for dokploy",
|
||||||
|
render(
|
||||||
|
DatabaseBackupEmail({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
databaseType,
|
||||||
|
type,
|
||||||
|
errorMessage,
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
await sendDiscordNotification(discord, {
|
||||||
|
title:
|
||||||
|
type === "success"
|
||||||
|
? "✅ Database Backup Successful"
|
||||||
|
: "❌ Database Backup Failed",
|
||||||
|
color: type === "success" ? 0x00ff00 : 0xff0000,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Project",
|
||||||
|
value: projectName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: databaseType,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: type,
|
||||||
|
},
|
||||||
|
...(type === "error" && errorMessage
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: "Error Message",
|
||||||
|
value: errorMessage,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Database Backup Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
const statusEmoji = type === "success" ? "✅" : "❌";
|
||||||
|
const messageText = `
|
||||||
|
<b>${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"}</b>
|
||||||
|
|
||||||
|
<b>Project:</b> ${projectName}
|
||||||
|
<b>Application:</b> ${applicationName}
|
||||||
|
<b>Type:</b> ${databaseType}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
|
||||||
|
<b>Status:</b> ${type === "success" ? "Successful" : "Failed"}
|
||||||
|
${type === "error" && errorMessage ? `<b>Error:</b> ${errorMessage}` : ""}
|
||||||
|
`;
|
||||||
|
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: *Database Backup Successful*"
|
||||||
|
: ":x: *Database 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: "Type",
|
||||||
|
value: databaseType,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
value: type === "success" ? "Successful" : "Failed",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
97
server/utils/notifications/docker-cleanup.ts
Normal file
97
server/utils/notifications/docker-cleanup.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import DockerCleanupEmail from "@/emails/emails/docker-cleanup";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { notifications } from "@/server/db/schema";
|
||||||
|
import { render } from "@react-email/components";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
sendDiscordNotification,
|
||||||
|
sendEmailNotification,
|
||||||
|
sendSlackNotification,
|
||||||
|
sendTelegramNotification,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
export const sendDockerCleanupNotifications = async (
|
||||||
|
message = "Docker cleanup for dokploy",
|
||||||
|
) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.dockerCleanup, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
await sendEmailNotification(
|
||||||
|
email,
|
||||||
|
"Docker cleanup for dokploy",
|
||||||
|
render(DockerCleanupEmail({ message, date: date.toLocaleString() })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
await sendDiscordNotification(discord, {
|
||||||
|
title: "✅ Docker Cleanup",
|
||||||
|
color: 0x00ff00,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Message",
|
||||||
|
value: message,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Docker Cleanup Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
await sendTelegramNotification(
|
||||||
|
telegram,
|
||||||
|
`
|
||||||
|
<b>✅ Docker Cleanup</b>
|
||||||
|
<b>Message:</b> ${message}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { channel } = slack;
|
||||||
|
await sendSlackNotification(slack, {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#00FF00",
|
||||||
|
pretext: ":white_check_mark: *Docker Cleanup*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Message",
|
||||||
|
value: message,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
91
server/utils/notifications/dokploy-restart.ts
Normal file
91
server/utils/notifications/dokploy-restart.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import DokployRestartEmail from "@/emails/emails/dokploy-restart";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { notifications } from "@/server/db/schema";
|
||||||
|
import { render } from "@react-email/components";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
sendDiscordNotification,
|
||||||
|
sendEmailNotification,
|
||||||
|
sendSlackNotification,
|
||||||
|
sendTelegramNotification,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
|
export const sendDokployRestartNotifications = async () => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.dokployRestart, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
await sendEmailNotification(
|
||||||
|
email,
|
||||||
|
"Dokploy Server Restarted",
|
||||||
|
render(DokployRestartEmail({ date: date.toLocaleString() })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
await sendDiscordNotification(discord, {
|
||||||
|
title: "✅ Dokploy Server Restarted",
|
||||||
|
color: 0x00ff00,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Restart Notification",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
await sendTelegramNotification(
|
||||||
|
telegram,
|
||||||
|
`
|
||||||
|
<b>✅ Dokploy Serverd Restarted</b>
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { channel } = slack;
|
||||||
|
await sendSlackNotification(slack, {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#FF0000",
|
||||||
|
pretext: ":white_check_mark: *Dokploy Server Restarted*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
69
server/utils/notifications/utils.ts
Normal file
69
server/utils/notifications/utils.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import type { discord, email, slack, telegram } from "@/server/db/schema";
|
||||||
|
import nodemailer from "nodemailer";
|
||||||
|
|
||||||
|
export const sendEmailNotification = async (
|
||||||
|
connection: typeof email.$inferInsert,
|
||||||
|
subject: string,
|
||||||
|
htmlContent: string,
|
||||||
|
) => {
|
||||||
|
const { smtpServer, smtpPort, username, password, fromAddress, toAddresses } =
|
||||||
|
connection;
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: smtpServer,
|
||||||
|
port: smtpPort,
|
||||||
|
secure: smtpPort === 465,
|
||||||
|
auth: { user: username, pass: password },
|
||||||
|
});
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddresses.join(", "),
|
||||||
|
subject,
|
||||||
|
html: htmlContent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendDiscordNotification = async (
|
||||||
|
connection: typeof discord.$inferInsert,
|
||||||
|
embed: any,
|
||||||
|
) => {
|
||||||
|
const response = await fetch(connection.webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ embeds: [embed] }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to send Discord notification");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendTelegramNotification = async (
|
||||||
|
connection: typeof telegram.$inferInsert,
|
||||||
|
messageText: string,
|
||||||
|
) => {
|
||||||
|
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: connection.chatId,
|
||||||
|
text: messageText,
|
||||||
|
parse_mode: "HTML",
|
||||||
|
disable_web_page_preview: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to send Telegram notification");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendSlackNotification = async (
|
||||||
|
connection: typeof slack.$inferInsert,
|
||||||
|
message: any,
|
||||||
|
) => {
|
||||||
|
const response = await fetch(connection.webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to send Slack notification");
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user