From 1ce880bd6dba372ec972deca7363b54255c33bfb Mon Sep 17 00:00:00 2001 From: ChristoferMendes Date: Fri, 26 Sep 2025 17:06:31 -0300 Subject: [PATCH] feat(notifications): integrate custom notification handling in server threshold alerts --- .../utils/notifications/server-threshold.ts | 19 +- .../server/src/utils/notifications/utils.ts | 286 ++++++++++-------- 2 files changed, 181 insertions(+), 124 deletions(-) diff --git a/packages/server/src/utils/notifications/server-threshold.ts b/packages/server/src/utils/notifications/server-threshold.ts index 2e63ba25a..292f57c5e 100644 --- a/packages/server/src/utils/notifications/server-threshold.ts +++ b/packages/server/src/utils/notifications/server-threshold.ts @@ -2,6 +2,7 @@ import { and, eq } from "drizzle-orm"; import { db } from "../../db"; import { notifications } from "../../db/schema"; import { + sendCustomNotification, sendDiscordNotification, sendSlackNotification, sendTelegramNotification, @@ -34,6 +35,7 @@ export const sendServerThresholdNotifications = async ( discord: true, telegram: true, slack: true, + custom: true, }, }); @@ -41,7 +43,7 @@ export const sendServerThresholdNotifications = async ( const typeColor = 0xff0000; // Rojo para indicar alerta for (const notification of notificationList) { - const { discord, telegram, slack } = notification; + const { discord, telegram, slack, custom } = notification; if (discord) { const decorate = (decoration: string, text: string) => @@ -151,5 +153,20 @@ export const sendServerThresholdNotifications = async ( ], }); } + + if (custom) { + await sendCustomNotification(custom, { + title: `Server ${payload.Type} Alert`, + message: payload.Message, + serverName: payload.ServerName, + type: payload.Type, + currentValue: payload.Value, + threshold: payload.Threshold, + timestamp: date.toISOString(), + date: date.toLocaleString(), + status: "alert", + alertType: "server-threshold", + }); + } } }; diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts index ec1c020ad..3ce5c98bc 100644 --- a/packages/server/src/utils/notifications/utils.ts +++ b/packages/server/src/utils/notifications/utils.ts @@ -1,153 +1,193 @@ import type { - discord, - email, - gotify, - ntfy, - slack, - telegram, + custom, + discord, + email, + gotify, + ntfy, + slack, + telegram, } from "@dokploy/server/db/schema"; import nodemailer from "nodemailer"; export const sendEmailNotification = async ( - connection: typeof email.$inferInsert, - subject: string, - htmlContent: string, + connection: typeof email.$inferInsert, + subject: string, + htmlContent: string ) => { - try { - const { - smtpServer, - smtpPort, - username, - password, - fromAddress, - toAddresses, - } = connection; - const transporter = nodemailer.createTransport({ - host: smtpServer, - port: smtpPort, - auth: { user: username, pass: password }, - }); + try { + const { + smtpServer, + smtpPort, + username, + password, + fromAddress, + toAddresses, + } = connection; + const transporter = nodemailer.createTransport({ + host: smtpServer, + port: smtpPort, + auth: { user: username, pass: password }, + }); - await transporter.sendMail({ - from: fromAddress, - to: toAddresses.join(", "), - subject, - html: htmlContent, - }); - } catch (err) { - console.log(err); - } + await transporter.sendMail({ + from: fromAddress, + to: toAddresses.join(", "), + subject, + html: htmlContent, + }); + } catch (err) { + console.log(err); + } }; export const sendDiscordNotification = async ( - connection: typeof discord.$inferInsert, - embed: any, + connection: typeof discord.$inferInsert, + embed: any ) => { - // try { - await fetch(connection.webhookUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ embeds: [embed] }), - }); - // } catch (err) { - // console.log(err); - // } + // try { + await fetch(connection.webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ embeds: [embed] }), + }); + // } catch (err) { + // console.log(err); + // } }; export const sendTelegramNotification = async ( - connection: typeof telegram.$inferInsert, - messageText: string, - inlineButton?: { - text: string; - url: string; - }[][], + connection: typeof telegram.$inferInsert, + messageText: string, + inlineButton?: { + text: string; + url: string; + }[][] ) => { - try { - const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`; - await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - chat_id: connection.chatId, - message_thread_id: connection.messageThreadId, - text: messageText, - parse_mode: "HTML", - disable_web_page_preview: true, - reply_markup: { - inline_keyboard: inlineButton, - }, - }), - }); - } catch (err) { - console.log(err); - } + try { + const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`; + await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + chat_id: connection.chatId, + message_thread_id: connection.messageThreadId, + text: messageText, + parse_mode: "HTML", + disable_web_page_preview: true, + reply_markup: { + inline_keyboard: inlineButton, + }, + }), + }); + } catch (err) { + console.log(err); + } }; export const sendSlackNotification = async ( - connection: typeof slack.$inferInsert, - message: any, + connection: typeof slack.$inferInsert, + message: any ) => { - try { - await fetch(connection.webhookUrl, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(message), - }); - } catch (err) { - console.log(err); - } + try { + await fetch(connection.webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(message), + }); + } catch (err) { + console.log(err); + } }; export const sendGotifyNotification = async ( - connection: typeof gotify.$inferInsert, - title: string, - message: string, + connection: typeof gotify.$inferInsert, + title: string, + message: string ) => { - const response = await fetch(`${connection.serverUrl}/message`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Gotify-Key": connection.appToken, - }, - body: JSON.stringify({ - title: title, - message: message, - priority: connection.priority, - extras: { - "client::display": { - contentType: "text/plain", - }, - }, - }), - }); + const response = await fetch(`${connection.serverUrl}/message`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Gotify-Key": connection.appToken, + }, + body: JSON.stringify({ + title: title, + message: message, + priority: connection.priority, + extras: { + "client::display": { + contentType: "text/plain", + }, + }, + }), + }); - if (!response.ok) { - throw new Error( - `Failed to send Gotify notification: ${response.statusText}`, - ); - } + if (!response.ok) { + throw new Error( + `Failed to send Gotify notification: ${response.statusText}` + ); + } }; export const sendNtfyNotification = async ( - connection: typeof ntfy.$inferInsert, - title: string, - tags: string, - actions: string, - message: string, + connection: typeof ntfy.$inferInsert, + title: string, + tags: string, + actions: string, + message: string ) => { - const response = await fetch(`${connection.serverUrl}/${connection.topic}`, { - method: "POST", - headers: { - Authorization: `Bearer ${connection.accessToken}`, - "X-Priority": connection.priority?.toString() || "3", - "X-Title": title, - "X-Tags": tags, - "X-Actions": actions, - }, - body: message, - }); + const response = await fetch(`${connection.serverUrl}/${connection.topic}`, { + method: "POST", + headers: { + Authorization: `Bearer ${connection.accessToken}`, + "X-Priority": connection.priority?.toString() || "3", + "X-Title": title, + "X-Tags": tags, + "X-Actions": actions, + }, + body: message, + }); - if (!response.ok) { - throw new Error(`Failed to send ntfy notification: ${response.statusText}`); - } + if (!response.ok) { + throw new Error(`Failed to send ntfy notification: ${response.statusText}`); + } +}; + +export const sendCustomNotification = async ( + connection: typeof custom.$inferInsert, + payload: Record +) => { + try { + // Parse headers if provided + let headers: Record = { + "Content-Type": "application/json", + }; + if (connection.headers) { + try { + headers = { ...headers, ...JSON.parse(connection.headers) }; + } catch (error) { + console.error("Error parsing headers:", error); + } + } + + // Default body with payload + const body = JSON.stringify(payload); + + const response = await fetch(connection.endpoint, { + method: "POST", + headers, + body, + }); + + if (!response.ok) { + throw new Error( + `Failed to send custom notification: ${response.statusText}` + ); + } + + return response; + } catch (error) { + console.error("Error sending custom notification:", error); + throw error; + } };