mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-19 21:05:21 +02:00
feat(notifications): add Microsoft Teams integration for notifications
- Introduced support for Microsoft Teams notifications, including the ability to create, update, and test connections for Teams notifications. - Updated the notification schema to include Teams as a notification type. - Added Teams icon and UI components for handling Teams notifications in the dashboard. - Implemented backend logic for creating and updating Teams notifications, along with necessary database schema changes. - Enhanced existing notification functionalities to support Teams notifications across various events (e.g., build success, failure, database backups).
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -52,6 +53,7 @@ export const sendBuildErrorNotifications = async ({
|
||||
custom: true,
|
||||
lark: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -67,6 +69,7 @@ export const sendBuildErrorNotifications = async ({
|
||||
custom,
|
||||
lark,
|
||||
pushover,
|
||||
teams,
|
||||
} = notification;
|
||||
try {
|
||||
if (email || resend) {
|
||||
@@ -382,6 +385,26 @@ export const sendBuildErrorNotifications = async ({
|
||||
`Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
const limitCharacter = 800;
|
||||
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
|
||||
await sendTeamsNotification(teams, {
|
||||
title: "⚠️ Build Failed",
|
||||
facts: [
|
||||
{ name: "Project", value: projectName },
|
||||
{ name: "Application", value: applicationName },
|
||||
{ name: "Type", value: applicationType },
|
||||
{ name: "Date", value: format(date, "PP pp") },
|
||||
{ name: "Error Message", value: truncatedErrorMessage },
|
||||
],
|
||||
potentialAction: {
|
||||
type: "Action.OpenUrl",
|
||||
title: "View Build Details",
|
||||
url: buildLink,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -55,6 +56,7 @@ export const sendBuildSuccessNotifications = async ({
|
||||
custom: true,
|
||||
lark: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -70,6 +72,7 @@ export const sendBuildSuccessNotifications = async ({
|
||||
custom,
|
||||
lark,
|
||||
pushover,
|
||||
teams,
|
||||
} = notification;
|
||||
try {
|
||||
if (email || resend) {
|
||||
@@ -396,6 +399,24 @@ export const sendBuildSuccessNotifications = async ({
|
||||
`Project: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
await sendTeamsNotification(teams, {
|
||||
title: "✅ Build Success",
|
||||
facts: [
|
||||
{ name: "Project", value: projectName },
|
||||
{ name: "Application", value: applicationName },
|
||||
{ name: "Environment", value: environmentName },
|
||||
{ name: "Type", value: applicationType },
|
||||
{ name: "Date", value: format(date, "PP pp") },
|
||||
],
|
||||
potentialAction: {
|
||||
type: "Action.OpenUrl",
|
||||
title: "View Build Details",
|
||||
url: buildLink,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -52,6 +53,7 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
custom: true,
|
||||
lark: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -67,6 +69,7 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
custom,
|
||||
lark,
|
||||
pushover,
|
||||
teams,
|
||||
} = notification;
|
||||
try {
|
||||
if (email || resend) {
|
||||
@@ -410,6 +413,27 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
`Project: ${projectName}\nApplication: ${applicationName}\nDatabase: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
const facts = [
|
||||
{ name: "Project", value: projectName },
|
||||
{ name: "Application", value: applicationName },
|
||||
{ name: "Database Type", value: databaseType },
|
||||
{ name: "Database Name", value: databaseName },
|
||||
{ name: "Date", value: format(date, "PP pp") },
|
||||
{ name: "Status", value: type === "success" ? "Successful" : "Failed" },
|
||||
];
|
||||
if (type === "error" && errorMessage) {
|
||||
facts.push({ name: "Error", value: errorMessage.substring(0, 500) });
|
||||
}
|
||||
await sendTeamsNotification(teams, {
|
||||
title:
|
||||
type === "success"
|
||||
? "✅ Database Backup Successful"
|
||||
: "❌ Database Backup Failed",
|
||||
facts,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -39,6 +40,7 @@ export const sendDockerCleanupNotifications = async (
|
||||
custom: true,
|
||||
lark: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -54,6 +56,7 @@ export const sendDockerCleanupNotifications = async (
|
||||
custom,
|
||||
lark,
|
||||
pushover,
|
||||
teams,
|
||||
} = notification;
|
||||
try {
|
||||
if (email || resend) {
|
||||
@@ -262,6 +265,16 @@ export const sendDockerCleanupNotifications = async (
|
||||
`Date: ${date.toLocaleString()}\nMessage: ${message}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
await sendTeamsNotification(teams, {
|
||||
title: "✅ Docker Cleanup",
|
||||
facts: [
|
||||
{ name: "Date", value: format(date, "PP pp") },
|
||||
{ name: "Message", value: message },
|
||||
],
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -33,6 +34,7 @@ export const sendDokployRestartNotifications = async () => {
|
||||
custom: true,
|
||||
lark: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,6 +50,7 @@ export const sendDokployRestartNotifications = async () => {
|
||||
custom,
|
||||
lark,
|
||||
pushover,
|
||||
teams,
|
||||
} = notification;
|
||||
|
||||
try {
|
||||
@@ -251,6 +254,16 @@ export const sendDokployRestartNotifications = async () => {
|
||||
`Date: ${date.toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
await sendTeamsNotification(teams, {
|
||||
title: "✅ Dokploy Server Restarted",
|
||||
facts: [
|
||||
{ name: "Status", value: "Successful" },
|
||||
{ name: "Restart Time", value: format(date, "PP pp") },
|
||||
],
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
sendLarkNotification,
|
||||
sendPushoverNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -40,6 +41,7 @@ export const sendServerThresholdNotifications = async (
|
||||
custom: true,
|
||||
lark: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -47,7 +49,8 @@ export const sendServerThresholdNotifications = async (
|
||||
const typeColor = 0xff0000; // Rojo para indicar alerta
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { discord, telegram, slack, custom, lark, pushover } = notification;
|
||||
const { discord, telegram, slack, custom, lark, pushover, teams } =
|
||||
notification;
|
||||
|
||||
if (discord) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
@@ -276,5 +279,19 @@ export const sendServerThresholdNotifications = async (
|
||||
`Server: ${payload.ServerName}\nType: ${payload.Type}\nCurrent: ${payload.Value.toFixed(2)}%\nThreshold: ${payload.Threshold.toFixed(2)}%\nMessage: ${payload.Message}\nTime: ${date.toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
await sendTeamsNotification(teams, {
|
||||
title: `⚠️ Server ${payload.Type} Alert`,
|
||||
facts: [
|
||||
{ name: "Server Name", value: payload.ServerName },
|
||||
{ name: "Type", value: payload.Type },
|
||||
{ name: "Current Value", value: `${payload.Value.toFixed(2)}%` },
|
||||
{ name: "Threshold", value: `${payload.Threshold.toFixed(2)}%` },
|
||||
{ name: "Time", value: date.toLocaleString() },
|
||||
{ name: "Message", value: payload.Message },
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
pushover,
|
||||
resend,
|
||||
slack,
|
||||
teams,
|
||||
telegram,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import nodemailer from "nodemailer";
|
||||
@@ -253,6 +254,84 @@ export const sendLarkNotification = async (
|
||||
}
|
||||
};
|
||||
|
||||
export interface TeamsAdaptiveCardMessage {
|
||||
title: string;
|
||||
themeColor?: string;
|
||||
facts?: { name: string; value: string }[];
|
||||
potentialAction?: { type: "Action.OpenUrl"; title: string; url: string };
|
||||
}
|
||||
|
||||
export const sendTeamsNotification = async (
|
||||
connection: typeof teams.$inferInsert,
|
||||
message: TeamsAdaptiveCardMessage,
|
||||
) => {
|
||||
try {
|
||||
const bodyElements: Record<string, unknown>[] = [
|
||||
{
|
||||
type: "TextBlock",
|
||||
text: message.title,
|
||||
size: "Medium",
|
||||
weight: "Bolder",
|
||||
wrap: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (message.facts && message.facts.length > 0) {
|
||||
bodyElements.push({
|
||||
type: "FactSet",
|
||||
facts: message.facts.map((f) => ({
|
||||
title: f.name,
|
||||
value: f.value,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
const cardContent: Record<string, unknown> = {
|
||||
type: "AdaptiveCard",
|
||||
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
version: "1.4",
|
||||
body: bodyElements,
|
||||
};
|
||||
|
||||
if (message.potentialAction) {
|
||||
cardContent.actions = [
|
||||
{
|
||||
type: "Action.OpenUrl",
|
||||
title: message.potentialAction.title,
|
||||
url: message.potentialAction.url,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const payload = {
|
||||
type: "message",
|
||||
attachments: [
|
||||
{
|
||||
contentType: "application/vnd.microsoft.card.adaptive",
|
||||
content: cardContent,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await fetch(connection.webhookUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to send Teams notification: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw new Error(
|
||||
`Failed to send Teams notification ${err instanceof Error ? err.message : "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const sendPushoverNotification = async (
|
||||
connection: typeof pushover.$inferInsert,
|
||||
title: string,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
sendPushoverNotification,
|
||||
sendResendNotification,
|
||||
sendSlackNotification,
|
||||
sendTeamsNotification,
|
||||
sendTelegramNotification,
|
||||
} from "./utils";
|
||||
|
||||
@@ -57,11 +58,12 @@ export const sendVolumeBackupNotifications = async ({
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
pushover: true,
|
||||
teams: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, resend, discord, telegram, slack, gotify, ntfy, pushover } =
|
||||
const { email, resend, discord, telegram, slack, gotify, ntfy, pushover, teams } =
|
||||
notification;
|
||||
|
||||
if (email || resend) {
|
||||
@@ -288,5 +290,29 @@ export const sendVolumeBackupNotifications = async ({
|
||||
`Project: ${projectName}\nApplication: ${applicationName}\nVolume: ${volumeName}\nService Type: ${serviceType}${backupSize ? `\nBackup Size: ${backupSize}` : ""}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (teams) {
|
||||
const facts = [
|
||||
{ name: "Project", value: projectName },
|
||||
{ name: "Application", value: applicationName },
|
||||
{ name: "Volume Name", value: volumeName },
|
||||
{ name: "Service Type", value: serviceType },
|
||||
{ name: "Date", value: format(date, "PP pp") },
|
||||
{ name: "Status", value: type === "success" ? "Successful" : "Failed" },
|
||||
];
|
||||
if (backupSize) {
|
||||
facts.push({ name: "Backup Size", value: backupSize });
|
||||
}
|
||||
if (type === "error" && errorMessage) {
|
||||
facts.push({ name: "Error", value: errorMessage.substring(0, 500) });
|
||||
}
|
||||
await sendTeamsNotification(teams, {
|
||||
title:
|
||||
type === "success"
|
||||
? "✅ Volume Backup Successful"
|
||||
: "❌ Volume Backup Failed",
|
||||
facts,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user