From 232be7a40534f58e83c714a4f4008e08a736abc0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 17 Feb 2026 17:28:43 +0000 Subject: [PATCH] cleanup --- .../service/infrastructure/emailService.ts | 3 - .../INotificationProvider.ts | 12 +- .../notificationProviders/discord.ts | 61 +------ .../notificationProviders/email.ts | 67 +------ .../notificationProviders/matrix.ts | 84 +-------- .../notificationProviders/pagerduty.ts | 68 +------ .../notificationProviders/slack.ts | 65 +------ .../notificationProviders/utils.ts | 167 +----------------- .../notificationProviders/webhook.ts | 72 +------- .../infrastructure/notificationsService.ts | 89 +++------- server/src/templates/hardwareIncident.mjml | 69 -------- server/src/templates/serverIsDown.mjml | 68 ------- server/src/templates/serverIsUp.mjml | 68 ------- 13 files changed, 38 insertions(+), 855 deletions(-) delete mode 100755 server/src/templates/hardwareIncident.mjml delete mode 100755 server/src/templates/serverIsDown.mjml delete mode 100755 server/src/templates/serverIsUp.mjml diff --git a/server/src/service/infrastructure/emailService.ts b/server/src/service/infrastructure/emailService.ts index 48653340a..26e3e3e6d 100755 --- a/server/src/service/infrastructure/emailService.ts +++ b/server/src/service/infrastructure/emailService.ts @@ -60,10 +60,7 @@ class EmailService { welcomeEmailTemplate: this.loadTemplate("welcomeEmail"), employeeActivationTemplate: this.loadTemplate("employeeActivation"), noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"), - serverIsDownTemplate: this.loadTemplate("serverIsDown"), - serverIsUpTemplate: this.loadTemplate("serverIsUp"), passwordResetTemplate: this.loadTemplate("passwordReset"), - hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"), testEmailTemplate: this.loadTemplate("testEmailTemplate"), unifiedNotificationTemplate: this.loadTemplate("unifiedNotification"), }; diff --git a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts b/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts index 73043fdb1..87056ef7a 100644 --- a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts +++ b/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts @@ -1,15 +1,7 @@ -import type { Monitor, Notification, Alert, MonitorStatusResponse } from "@/types/index.js"; -import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; +import type { Notification } from "@/types/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; export interface INotificationProvider { - sendAlert: ( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ) => Promise; - sendMessage?: (notification: Notification, message: NotificationMessage) => Promise; + sendMessage: (notification: Notification, message: NotificationMessage) => Promise; sendTestAlert(notification: Notification): Promise; } diff --git a/server/src/service/infrastructure/notificationProviders/discord.ts b/server/src/service/infrastructure/notificationProviders/discord.ts index dcf8927be..2f0b07fe8 100644 --- a/server/src/service/infrastructure/notificationProviders/discord.ts +++ b/server/src/service/infrastructure/notificationProviders/discord.ts @@ -1,8 +1,7 @@ const SERVICE_NAME = "DiscordProvider"; -import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js"; +import type { Notification } from "@/types/index.js"; import { INotificationProvider } from "@/service/index.js"; -import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import { buildHardwareAlerts, buildDiscordBody, getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; +import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; import got from "got"; @@ -12,58 +11,7 @@ export class DiscordProvider implements INotificationProvider { constructor(logger: any) { this.logger = logger; } - private getHardwareContent = ( - clientHost: string, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision - ) => { - // For status changes (recovery), use standard format - if (decision.notificationReason === "status_change") { - return buildDiscordBody(monitor, monitorStatusResponse); - } - // For threshold breaches, use hardware alert format - const { discordPayload } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse); - return discordPayload; - }; - sendAlert = async ( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ) => { - let body; - if (monitor.type === "hardware") { - body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision); - } else { - body = buildDiscordBody(monitor, monitorStatusResponse); - } - - if (!notification.address) { - return false; - } - - try { - await got.post(notification.address, { - json: body, - headers: { - "Content-Type": "application/json", - }, - }); - return true; - } catch (error) { - const err = error as Error; - this.logger.warn({ - message: "Discord alert failed", - service: SERVICE_NAME, - method: "sendAlert", - stack: err?.stack, - }); - return false; - } - }; sendTestAlert = async (notification: Notification) => { if (!notification.address) { return false; @@ -110,11 +58,6 @@ export class DiscordProvider implements INotificationProvider { "Content-Type": "application/json", }, }); - this.logger.info({ - message: "[NEW] Discord notification sent via sendMessage", - service: SERVICE_NAME, - method: "sendMessage", - }); return true; } catch (error) { const err = error as Error; diff --git a/server/src/service/infrastructure/notificationProviders/email.ts b/server/src/service/infrastructure/notificationProviders/email.ts index 3c8ba5527..effd94946 100644 --- a/server/src/service/infrastructure/notificationProviders/email.ts +++ b/server/src/service/infrastructure/notificationProviders/email.ts @@ -2,7 +2,7 @@ const SERVICE_NAME = "EmailProvider"; import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index.js"; import { INotificationProvider } from "@/service/index.js"; import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import { buildHardwareAlerts, buildHardwareEmail, buildEmail, buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js"; +import { buildTestEmail } from "@/service/infrastructure/notificationProviders/utils.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; export class EmailProvider implements INotificationProvider { @@ -14,68 +14,6 @@ export class EmailProvider implements INotificationProvider { this.logger = logger; } - private buildHardwareEmail = async ( - clientHost: string, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision - ) => { - // For status changes (recovery), use standard email format - if (decision.notificationReason === "status_change") { - return await buildEmail(this.emailService, monitor); - } - // For threshold breaches, use hardware alert format - const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse); - const html = buildHardwareEmail(this.emailService, monitor, alertsToSend); - return html; - }; - - async sendAlert( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ): Promise { - // For grouped notifications (identified by ":" in name), customize subject to indicate multiple services. - // Example: "2 services: Service A, Service B" becomes "Alert: 2 services are down" - const isGroupedNotification = monitor.name.includes(":"); - - // Build subject based on notification reason and monitor status - let subject: string; - if (isGroupedNotification) { - subject = `Alert: ${monitor.name} are down`; - } else if (decision.notificationReason === "threshold_breach") { - subject = `Monitor ${monitor.name} threshold breached`; - } else if (monitor.status === "up") { - subject = `Monitor ${monitor.name} is back up`; - } else { - subject = `Monitor ${monitor.name} is down`; - } - - let html; - if (monitor.type === "hardware") { - html = await this.buildHardwareEmail(clientHost, monitor, monitorStatusResponse, decision); - } else { - html = await buildEmail(this.emailService, monitor); - } - - if (!notification.address) { - return false; - } - - const messageId = await this.emailService.sendEmail(notification.address, subject, html); - if (!messageId) { - this.logger.warn({ - message: "Email alert failed", - service: SERVICE_NAME, - method: "sendAlert", - }); - return false; - } - return true; - } - async sendTestAlert(notification: Notification): Promise { const subject = "Test notification"; const html = await buildTestEmail(this.emailService); @@ -103,7 +41,7 @@ export class EmailProvider implements INotificationProvider { const messageId = await this.emailService.sendEmail(notification.address, subject, html); if (!messageId) { this.logger.warn({ - message: "Email notification failed via sendMessage", + message: "Email notification failed", service: SERVICE_NAME, method: "sendMessage", }); @@ -148,7 +86,6 @@ export class EmailProvider implements INotificationProvider { details: { context }, }); - return await this.emailService.buildEmail("unifiedNotificationTemplate", context); const html = await this.emailService.buildEmail("unifiedNotificationTemplate", context); this.logger.info({ diff --git a/server/src/service/infrastructure/notificationProviders/matrix.ts b/server/src/service/infrastructure/notificationProviders/matrix.ts index 63b24d7b7..8d58e6ead 100644 --- a/server/src/service/infrastructure/notificationProviders/matrix.ts +++ b/server/src/service/infrastructure/notificationProviders/matrix.ts @@ -1,15 +1,9 @@ const SERVICE_NAME = "MatrixProvider"; import got from "got"; import type { INotificationProvider } from "@/service/index.js"; -import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; -import type { Notification, Monitor, MonitorStatusResponse } from "@/types/index.js"; +import type { Notification } from "@/types/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { - buildHardwareAlerts, - buildHardwareNotificationMessage, - buildWebhookBody, - getTestMessage, -} from "@/service/infrastructure/notificationProviders/utils.js"; +import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; export class MatrixProvider implements INotificationProvider { private logger: any; @@ -17,73 +11,6 @@ export class MatrixProvider implements INotificationProvider { constructor(logger: any) { this.logger = logger; } - private getHardwareContent = ( - clientHost: string, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision - ) => { - // For status changes (recovery), use standard format - if (decision.notificationReason === "status_change") { - return buildWebhookBody(monitor, monitorStatusResponse); - } - // For threshold breaches, use hardware alert format - const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse); - const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor); - return body; - }; - - private getContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => { - const body = buildWebhookBody(monitor, monitorStatusResponse); - return body; - }; - - sendAlert = async ( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ) => { - const { homeserverUrl, accessToken, roomId } = notification; - - let content; - if (monitor.type === "hardware") { - content = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision); - } else { - content = this.getContent(monitor, monitorStatusResponse); - } - - const title = `Checkmate status for ${monitor.name}`; - - const formattedMessage = `## ${title}\n${content}`; - - const url = `${homeserverUrl}/_matrix/client/v3/rooms/${roomId}/send/m.room.message?access_token=${accessToken}`; - const body = { - msgtype: "m.text", - body: formattedMessage, - format: "org.matrix.custom.html", - formatted_body: formattedMessage, - }; - try { - await got.post(url, { - json: body, - headers: { - "Content-Type": "application/json", - }, - }); - return true; - } catch (error) { - const err = error as Error; - this.logger.warn({ - message: "Matrix alert failed", - service: SERVICE_NAME, - method: "sendAlert", - stack: err?.stack, - }); - return false; - } - }; sendTestAlert = async (notification: Notification) => { const { homeserverUrl, accessToken, roomId } = notification; @@ -142,16 +69,11 @@ export class MatrixProvider implements INotificationProvider { "Content-Type": "application/json", }, }); - this.logger.info({ - message: "[NEW] Matrix notification sent via sendMessage", - service: SERVICE_NAME, - method: "sendMessage", - }); return true; } catch (error) { const err = error as Error; this.logger.warn({ - message: "[NEW] Matrix alert failed via sendMessage", + message: "Matrix notification failed", service: SERVICE_NAME, method: "sendMessage", stack: err?.stack, diff --git a/server/src/service/infrastructure/notificationProviders/pagerduty.ts b/server/src/service/infrastructure/notificationProviders/pagerduty.ts index 2a51eb341..f96ea6cc5 100644 --- a/server/src/service/infrastructure/notificationProviders/pagerduty.ts +++ b/server/src/service/infrastructure/notificationProviders/pagerduty.ts @@ -4,12 +4,7 @@ import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; import { INotificationProvider } from "@/service/index.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { - buildHardwareAlerts, - buildHardwareNotificationMessage, - buildWebhookBody, - getTestMessage, -} from "@/service/infrastructure/notificationProviders/utils.js"; +import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; export class PagerDutyProvider implements INotificationProvider { private logger: any; @@ -17,67 +12,6 @@ export class PagerDutyProvider implements INotificationProvider { constructor(logger: any) { this.logger = logger; } - private getHardwareContent = ( - clientHost: string, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision - ) => { - // For status changes (recovery), use standard format - if (decision.notificationReason === "status_change") { - return buildWebhookBody(monitor, monitorStatusResponse); - } - // For threshold breaches, use hardware alert format - const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse); - const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor); - return body; - }; - - private getContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => { - const body = buildWebhookBody(monitor, monitorStatusResponse); - return body; - }; - - async sendAlert( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ): Promise { - let body; - if (monitor.type === "hardware") { - body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision); - } else { - body = this.getContent(monitor, monitorStatusResponse); - } - - try { - await got.post("https://events.pagerduty.com/v2/enqueue", { - json: { - routing_key: notification.address, - event_action: "trigger", - payload: { - summary: body, - severity: "critical", - source: monitor.url, - timestamp: new Date().toISOString(), - }, - }, - }); - - return true; - } catch (error) { - const err = error as Error; - this.logger.warn({ - message: "PagerDuty alert failed", - service: SERVICE_NAME, - method: "sendAlert", - stack: err?.stack, - }); - return false; - } - } async sendTestAlert(notification: Notification): Promise { try { diff --git a/server/src/service/infrastructure/notificationProviders/slack.ts b/server/src/service/infrastructure/notificationProviders/slack.ts index ede6b8d40..a4cf0ed26 100644 --- a/server/src/service/infrastructure/notificationProviders/slack.ts +++ b/server/src/service/infrastructure/notificationProviders/slack.ts @@ -3,12 +3,7 @@ import type { Monitor, Notification, MonitorStatusResponse } from "@/types/index import { INotificationProvider } from "@/service/index.js"; import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { - buildHardwareAlerts, - buildHardwareNotificationMessage, - buildWebhookBody, - getTestMessage, -} from "@/service/infrastructure/notificationProviders/utils.js"; +import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got, { HTTPError } from "got"; export class SlackProvider implements INotificationProvider { @@ -17,64 +12,6 @@ export class SlackProvider implements INotificationProvider { constructor(logger: any) { this.logger = logger; } - private getHardwareContent = ( - clientHost: string, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision - ) => { - // For status changes (recovery), use standard format - if (decision.notificationReason === "status_change") { - return buildWebhookBody(monitor, monitorStatusResponse); - } - // For threshold breaches, use hardware alert format - const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse); - const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor); - return body; - }; - - private getContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => { - const body = buildWebhookBody(monitor, monitorStatusResponse); - return body; - }; - - async sendAlert( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ): Promise { - let body; - if (monitor.type === "hardware") { - body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision); - } else { - body = this.getContent(monitor, monitorStatusResponse); - } - - if (!notification.address) { - return false; - } - - try { - await got.post(notification.address, { - json: { text: body }, - headers: { - "Content-Type": "application/json", - }, - }); - return true; - } catch (error) { - const err = error as Error; - this.logger.warn({ - message: "Slack alert failed", - service: SERVICE_NAME, - method: "sendAlert", - stack: err?.stack, - }); - return false; - } - } async sendTestAlert(notification: Notification): Promise { if (!notification.address) { diff --git a/server/src/service/infrastructure/notificationProviders/utils.ts b/server/src/service/infrastructure/notificationProviders/utils.ts index 72797c1d0..874152413 100644 --- a/server/src/service/infrastructure/notificationProviders/utils.ts +++ b/server/src/service/infrastructure/notificationProviders/utils.ts @@ -1,170 +1,5 @@ import { Monitor, HardwareStatusPayload, MonitorStatusResponse } from "@/types/index.js"; - -const getTime = (): { localTimeZone: string; localTime: string; utcTime: string } => { - const now = new Date(); - const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const localTime = new Intl.DateTimeFormat(undefined, { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: false, - timeZone: localTimeZone, - timeZoneName: "short", - }).format(now); - const utcTime = now.toUTCString(); - return { localTimeZone, localTime, utcTime }; -}; - -export const buildHardwareAlerts = ( - clientHost: string, - monitor: Monitor, - networkResponse: MonitorStatusResponse -): { alertsToSend: string[]; discordPayload: any } => { - // Thresholds are stored as percentages (0-100), convert to decimal (0-1) for comparison - const cpuThreshold = monitor.cpuAlertThreshold !== undefined ? monitor.cpuAlertThreshold / 100 : -1; - const memoryThreshold = monitor.memoryAlertThreshold !== undefined ? monitor.memoryAlertThreshold / 100 : -1; - const diskThreshold = monitor.diskAlertThreshold !== undefined ? monitor.diskAlertThreshold / 100 : -1; - const tempThreshold = monitor.tempAlertThreshold !== undefined ? monitor.tempAlertThreshold : -1; - - const payload = networkResponse?.payload as HardwareStatusPayload; - const metrics = payload.data || {}; - const { cpu = {}, memory = {}, disk = [] } = metrics; - const cpuUsage = cpu.usage_percent ?? -1; - const memoryUsage = memory.usage_percent ?? -1; - // Get max temperature from CPU temperature sensors array - const temps = cpu.temperature ?? []; - const maxTemp = temps.length > 0 ? Math.max(...temps) : -1; - - const alerts: Record = { - cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false, - memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false, - disk: disk?.some((d) => diskThreshold !== -1 && typeof d?.usage_percent === "number" && d?.usage_percent > diskThreshold) ?? false, - temp: tempThreshold !== -1 && maxTemp > tempThreshold ? true : false, - }; - - const alertsToSend = []; - const discordEmbeds = []; - const monitorInfoFields = [ - { name: "Monitor", value: monitor.name, inline: true }, - { name: "URL", value: monitor.url, inline: false }, - ]; - const goToIncidentField = { name: `Go to incident`, value: `${clientHost}/infrastructure/${monitor.id}` }; - - const formatDiscordAlert = { - cpu: () => ({ - title: "CPU alert", - description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, - color: 15548997, - fields: [...monitorInfoFields, goToIncidentField], - footer: { text: "Checkmate" }, - }), - - memory: () => ({ - title: "Memory alert", - description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, - color: 15548997, - fields: [...monitorInfoFields, goToIncidentField], - footer: { text: "Checkmate" }, - }), - - disk: () => ({ - title: "Disk alert", - description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, - color: 15548997, - footer: { text: "Checkmate" }, - fields: [ - ...monitorInfoFields, - ...(Array.isArray(disk) ? disk : []).map((d: any, idx: number) => ({ - name: `Disk ${idx}`, - value: `${(d?.usage_percent * 100).toFixed(0)}%`, - inline: true, - })), - goToIncidentField, - ], - }), - - temp: () => ({ - title: "Temperature alert", - description: `Your current temperature (${maxTemp.toFixed(0)}°C) is above your threshold (${tempThreshold.toFixed(0)}°C)`, - color: 15548997, - fields: [...monitorInfoFields, goToIncidentField], - footer: { text: "Checkmate" }, - }), - }; - - const alertTypes = ["cpu", "memory", "disk", "temp"] as const; - - for (const type of alertTypes) { - if (alerts[type] === true) { - const formatAlert = { - cpu: () => `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, - memory: () => - `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, - disk: () => - `Your current disk usage: ${disk.map((d, idx) => `(Disk${idx}: ${(d?.usage_percent ?? 0 * 100).toFixed(0)}%)`).join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, - temp: () => `Your current temperature (${maxTemp.toFixed(0)}°C) is above your threshold (${tempThreshold.toFixed(0)}°C)`, - }; - alertsToSend.push(formatAlert[type]()); - discordEmbeds.push(formatDiscordAlert[type]()); - } - } - - const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null; - return { alertsToSend, discordPayload }; -}; - -export const buildHardwareNotificationMessage = (clientHost: string, alerts: string[], monitor: Monitor): string => { - const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`]; - const alertFooter = [`Go to incident: ${clientHost}/infrastructure/${monitor.id}`]; - const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts, ...alertFooter] : []; - return alertText.join("\n"); -}; - -export const buildWebhookBody = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => { - const { status, code } = monitorStatusResponse; - const { localTimeZone, localTime, utcTime } = getTime(); - return `Monitor: ${monitor.name}\nLocal Time (${localTimeZone}): ${localTime}\nUTC Time: ${utcTime}\nStatus: ${status ? "UP" : "DOWN"}\nStatus Code: ${code}\n\u200B\n`; -}; - -export const buildHardwareEmail = async (emailService: any, monitor: Monitor, alerts: string[]) => { - const template = "hardwareIncidentTemplate"; - const context = { monitor: monitor.name, url: monitor.url, alerts }; - const html = await emailService.buildEmail(template, context); - return html; -}; - -export const buildEmail = async (emailService: any, monitor: Monitor): Promise => { - const template = monitor.status === "up" ? "serverIsUpTemplate" : "serverIsDownTemplate"; - const context = { monitor: monitor.name, url: monitor.url }; - const html = await emailService.buildEmail(template, context); - return html; -}; - -export const buildDiscordBody = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => { - const { localTime, utcTime } = getTime(); - let body = { - embeds: [ - { - title: `Monitor name: ${monitor.name}`, - color: monitorStatusResponse.status ? 5763719 : 15548997, - - fields: [ - { name: "Monitor", value: monitor.name, inline: false }, - { name: "Status", value: monitorStatusResponse.status ? "Up" : "Down", inline: false }, - { name: "Status Code", value: String(monitorStatusResponse.code), inline: false }, - { name: "Time", value: `${localTime} (Local) / ${utcTime} (UTC)`, inline: false }, - { name: "URL", value: monitor.url, inline: false }, - ], - footer: { text: "Checkmate" }, - }, - ], - }; - return body; -}; - +// Test notification helpers - used by all providers for test alerts export const buildTestEmail = async (emailService: any) => { const context = { testName: "Monitoring System" }; const html = await emailService.buildEmail("testEmailTemplate", context); diff --git a/server/src/service/infrastructure/notificationProviders/webhook.ts b/server/src/service/infrastructure/notificationProviders/webhook.ts index c4b637bef..532714827 100644 --- a/server/src/service/infrastructure/notificationProviders/webhook.ts +++ b/server/src/service/infrastructure/notificationProviders/webhook.ts @@ -3,12 +3,7 @@ import type { Monitor, Alert, Notification, MonitorStatusResponse } from "@/type import { INotificationProvider } from "@/service/index.js"; import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; import type { NotificationMessage } from "@/types/notificationMessage.js"; -import { - buildHardwareAlerts, - buildHardwareNotificationMessage, - buildWebhookBody, - getTestMessage, -} from "@/service/infrastructure/notificationProviders/utils.js"; +import { getTestMessage } from "@/service/infrastructure/notificationProviders/utils.js"; import got from "got"; export class WebhookProvider implements INotificationProvider { @@ -17,68 +12,7 @@ export class WebhookProvider implements INotificationProvider { constructor(logger: any) { this.logger = logger; } - private getHardwareContent = ( - clientHost: string, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision - ) => { - // For status changes (recovery), use standard format - if (decision.notificationReason === "status_change") { - return buildWebhookBody(monitor, monitorStatusResponse); - } - // For threshold breaches, use hardware alert format - const { alertsToSend } = buildHardwareAlerts(clientHost, monitor, monitorStatusResponse); - const body = buildHardwareNotificationMessage(clientHost, alertsToSend, monitor); - return body; - }; - private getContent = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse): string => { - const body = buildWebhookBody(monitor, monitorStatusResponse); - return body; - }; - - sendAlert = async ( - notification: Notification, - monitor: Monitor, - monitorStatusResponse: MonitorStatusResponse, - decision: MonitorActionDecision, - clientHost: string - ) => { - let body; - if (monitor.type === "hardware") { - body = this.getHardwareContent(clientHost, monitor, monitorStatusResponse, decision); - } else { - body = this.getContent(monitor, monitorStatusResponse); - } - - if (!notification.address) { - return false; - } - - try { - await got.post(notification.address, { - json: { text: body }, - headers: { - "Content-Type": "application/json", - }, - }); - return true; - } catch (error) { - const err = error as Error; - this.logger.warn({ - message: "Webhook alert failed", - service: SERVICE_NAME, - method: "sendAlert", - stack: err?.stack, - }); - return false; - } - }; - - /** - * New unified message format - builds webhook payload from NotificationMessage - */ sendMessage = async (notification: Notification, message: NotificationMessage): Promise => { if (!notification.address) { return false; @@ -95,7 +29,7 @@ export class WebhookProvider implements INotificationProvider { }, }); this.logger.info({ - message: "[NEW] Webhook notification sent via sendMessage", + message: "Webhook notification sent", service: SERVICE_NAME, method: "sendMessage", }); @@ -103,7 +37,7 @@ export class WebhookProvider implements INotificationProvider { } catch (error) { const err = error as Error; this.logger.warn({ - message: "[NEW] Webhook alert failed via sendMessage", + message: "Webhook alert failed", service: SERVICE_NAME, method: "sendMessage", stack: err?.stack, diff --git a/server/src/service/infrastructure/notificationsService.ts b/server/src/service/infrastructure/notificationsService.ts index 046560d61..4331c5ff0 100644 --- a/server/src/service/infrastructure/notificationsService.ts +++ b/server/src/service/infrastructure/notificationsService.ts @@ -1,4 +1,5 @@ import type { HardwareStatusPayload, Monitor, MonitorStatusResponse, Notification, MonitorStatus } from "@/types/index.js"; +import type { NotificationMessage } from "@/types/notificationMessage.js"; import { IMonitorsRepository, INotificationsRepository } from "@/repositories/index.js"; import { INotificationProvider } from "./notificationProviders/INotificationProvider.js"; import type { MonitorActionDecision } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js"; @@ -66,81 +67,37 @@ export class NotificationsService implements INotificationsService { monitor: Monitor, monitorStatusResponse: MonitorStatusResponse, decision: MonitorActionDecision, - notificationMessage?: any + notificationMessage: NotificationMessage | undefined ): Promise => { - const settings = this.settingsService.getSettings(); - const clientHost = settings.clientHost || "Host not defined"; - - // Route to new sendMessage method if provider supports it - if (notification.type === "webhook" && this.webhookProvider.sendMessage && notificationMessage) { - this.logger.info({ - message: "[NEW] Using sendMessage for webhook", + if (!notificationMessage) { + this.logger.warn({ + message: "Notification message not provided", service: SERVICE_NAME, method: "send", }); - return await this.webhookProvider.sendMessage(notification, notificationMessage); + return false; } - if (notification.type === "slack" && this.slackProvider.sendMessage && notificationMessage) { - this.logger.info({ - message: "[NEW] Using sendMessage for slack", - service: SERVICE_NAME, - method: "send", - }); - return await this.slackProvider.sendMessage(notification, notificationMessage); - } - - if (notification.type === "matrix" && this.matrixProvider.sendMessage && notificationMessage) { - this.logger.info({ - message: "[NEW] Using sendMessage for matrix", - service: SERVICE_NAME, - method: "send", - }); - return await this.matrixProvider.sendMessage(notification, notificationMessage); - } - - if (notification.type === "pager_duty" && this.pagerDutyProvider.sendMessage && notificationMessage) { - this.logger.info({ - message: "[NEW] Using sendMessage for pagerduty", - service: SERVICE_NAME, - method: "send", - }); - return await this.pagerDutyProvider.sendMessage(notification, notificationMessage); - } - - if (notification.type === "discord" && this.discordProvider.sendMessage && notificationMessage) { - this.logger.info({ - message: "[NEW] Using sendMessage for discord", - service: SERVICE_NAME, - method: "send", - }); - return await this.discordProvider.sendMessage(notification, notificationMessage); - } - - if (notification.type === "email" && this.emailProvider.sendMessage && notificationMessage) { - this.logger.info({ - message: "[NEW] Using sendMessage for email", - service: SERVICE_NAME, - method: "send", - }); - return await this.emailProvider.sendMessage(notification, notificationMessage); - } - - // Fallback to existing sendAlert for all providers + // Route to provider based on notification type switch (notification.type) { - case "email": - return await this.emailProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost); - case "slack": - return await this.slackProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost); - case "discord": - return await this.discordProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost); - case "pager_duty": - return await this.pagerDutyProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost); - case "matrix": - return await this.matrixProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost); case "webhook": - return await this.webhookProvider.sendAlert(notification, monitor, monitorStatusResponse, decision, clientHost); + return await this.webhookProvider.sendMessage!(notification, notificationMessage); + case "slack": + return await this.slackProvider.sendMessage!(notification, notificationMessage); + case "matrix": + return await this.matrixProvider.sendMessage!(notification, notificationMessage); + case "pager_duty": + return await this.pagerDutyProvider.sendMessage!(notification, notificationMessage); + case "discord": + return await this.discordProvider.sendMessage!(notification, notificationMessage); + case "email": + return await this.emailProvider.sendMessage!(notification, notificationMessage); default: + this.logger.warn({ + message: `Unknown notification type: ${notification.type}`, + service: SERVICE_NAME, + method: "send", + }); return false; } }; diff --git a/server/src/templates/hardwareIncident.mjml b/server/src/templates/hardwareIncident.mjml deleted file mode 100755 index 3f50a3d58..000000000 --- a/server/src/templates/hardwareIncident.mjml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - Message from BlueWave Infrastructure Monitoring - - - - - Infrastructure Alerts - - - - - - - -

Hello {{ name }}!

-

{{ monitor }} at {{ url }} has the following infrastructure alerts:

- {{#each alerts}} -

• {{ this }}

- {{/each}} -
-
- - - View Infrastructure Details - -

This email was sent by BlueWave Infrastructure Monitoring.

-
-
-
-
-
diff --git a/server/src/templates/serverIsDown.mjml b/server/src/templates/serverIsDown.mjml deleted file mode 100755 index bf6424a6b..000000000 --- a/server/src/templates/serverIsDown.mjml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - Message from Checkmate Service - - - - - {{ monitor }} is down - - - - - - - -

Hello {{ name }}!

-

We detected an incident on one of your monitors. Your service is currently down. We'll send a message to you once it is up again.

-

Monitor name: {{ monitor }}

-

URL: {{ url }}

-
-
- - - View incident details - -

This email was sent by Checkmate.

-
-
-
-
-
diff --git a/server/src/templates/serverIsUp.mjml b/server/src/templates/serverIsUp.mjml deleted file mode 100755 index 074c5e3dd..000000000 --- a/server/src/templates/serverIsUp.mjml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - Message from Checkmate Service - - - - - {{ monitor }} is up - - - - - - - -

Hello {{ name }}!

-

Your latest incident is resolved and your monitored service is up again.

-

Monitor name: {{ monitor }}

-

URL: {{ url }}

-
-
- - - View incident details - -

This email was sent by Checkmate.

-
-
-
-
-