diff --git a/server/src/service/index.ts b/server/src/service/index.ts index a094f8b01..4dba8f6f4 100644 --- a/server/src/service/index.ts +++ b/server/src/service/index.ts @@ -1,2 +1,4 @@ export * from "@/service/business/monitorService.js"; export * from "@/service/infrastructure/networkService.js"; +export * from "@/service/infrastructure/notificationProviders/webhook.js"; +export * from "@/service/infrastructure/notificationProviders/INotificationProvider.js"; diff --git a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts b/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts index 12b28d907..2743c40bb 100644 --- a/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts +++ b/server/src/service/infrastructure/notificationProviders/INotificationProvider.ts @@ -1,7 +1,7 @@ -import type { Monitor, Notification, Alert } from "@/types/index.js"; +import type { Monitor, Notification, Alert, MonitorStatusResponse } from "@/types/index.js"; -export interface IMessageProvider { - buildAlert: (monitor: Monitor) => Alert; +export interface INotificationProvider { + buildAlert: (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => Alert; sendAlert: (alert: Alert, notification: Notification) => Promise; sendTestAlert(notification: Notification): Promise; } diff --git a/server/src/service/infrastructure/notificationProviders/utils.ts b/server/src/service/infrastructure/notificationProviders/utils.ts new file mode 100644 index 000000000..9a1b2e7f8 --- /dev/null +++ b/server/src/service/infrastructure/notificationProviders/utils.ts @@ -0,0 +1,91 @@ +import { Monitor, HardwareStatusPayload, MonitorStatusResponse } from "@/types/index.js"; + +const buildHardwareAlerts = async (clientHost: string, monitor: Monitor, networkResponse: MonitorStatusResponse) => { + const thresholds = monitor.thresholds || {}; + const { usage_cpu: cpuThreshold = -1, usage_memory: memoryThreshold = -1, usage_disk: diskThreshold = -1 } = thresholds; + + const payload = networkResponse?.payload as HardwareStatusPayload; + const metrics = payload.data || {}; + const { cpu: { usage_percent: cpuUsage = -1 } = {}, memory: { usage_percent: memoryUsage = -1 } = {}, disk = [] } = metrics; + + const alerts = { + 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, + }; + + 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, + ], + }), + }; + const alertTypes = ["cpu", "memory", "disk"]; + for (const type of alertTypes) { + // Iterate over each alert type to see if any need to be decremented + if (alerts[type] === true) { + monitor[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered + + if (monitor[`${type}AlertThreshold`] <= 0) { + // If threshold drops below 0, reset and send notification + monitor[`${type}AlertThreshold`] = monitor.alertThreshold; + + 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 * 100).toFixed(0)}%)`) + .join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, + }; + alertsToSend.push(formatAlert[type]()); + discordEmbeds.push(formatDiscordAlert[type]()); + } + } + } + const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null; + return [alertsToSend, discordPayload]; +}; + +const buildHardwareNotificationMessage = (alerts: any, monitor: Monitor) => { + const { clientHost } = this.settingsService.getSettings(); + 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.map((alert) => alert).join("\n"); +}; diff --git a/server/src/service/infrastructure/notificationProviders/webhook.ts b/server/src/service/infrastructure/notificationProviders/webhook.ts new file mode 100644 index 000000000..4266bbf3b --- /dev/null +++ b/server/src/service/infrastructure/notificationProviders/webhook.ts @@ -0,0 +1,15 @@ +import type { Monitor, Alert, Notification, MonitorStatusResponse } from "@/types/index.js"; +import { INotificationProvider } from "@/service/index.js"; +export class WebhookProvider implements INotificationProvider { + buildAlert = (monitor: Monitor, monitorStatusResponse: MonitorStatusResponse) => { + return null; + }; + + sendAlert = async (alert: Alert) => { + return false; + }; + + sendTestAlert(notification: Notification): Promise { + return false; + } +}