Files
Checkmate/server/src/service/infrastructure/notificationUtils.js
T
Alex Holliday e090d3cfd3 -> timeseries
2026-01-12 19:32:23 +00:00

206 lines
7.6 KiB
JavaScript

const SERVICE_NAME = "NotificationUtils";
class NotificationUtils {
static SERVICE_NAME = SERVICE_NAME;
constructor({ stringService, emailService, settingsService }) {
this.stringService = stringService;
this.emailService = emailService;
this.settingsService = settingsService;
}
get serviceName() {
return NotificationUtils.SERVICE_NAME;
}
buildTestEmail = async () => {
const context = { testName: "Monitoring System" };
const html = await this.emailService.buildEmail("testEmailTemplate", context);
return html;
};
buildStatusEmail = async (networkResponse) => {
const { monitor, status, prevStatus } = networkResponse;
const template = prevStatus === false ? "serverIsUpTemplate" : "serverIsDownTemplate";
const context = { monitor: monitor.name, url: monitor.url };
const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`;
const html = await this.emailService.buildEmail(template, context);
return { subject, html };
};
buildWebhookMessage = (networkResponse) => {
const { monitor, status, code, timestamp } = networkResponse;
// Format timestamp using the local system timezone
const formatTime = (timestamp) => {
const date = new Date(timestamp);
// Get timezone abbreviation and format the date
const timeZoneAbbr = date.toLocaleTimeString("en-US", { timeZoneName: "short" }).split(" ").pop();
// Format the date with readable format
return (
date
.toLocaleString("en-US", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
})
.replace(/(\d+)\/(\d+)\/(\d+),\s/, "$3-$1-$2 ") +
" " +
timeZoneAbbr
);
};
// Get formatted time
const formattedTime = timestamp ? formatTime(timestamp) : formatTime(new Date().getTime());
// Create different messages based on status with extra spacing
let messageText;
if (status === true) {
messageText = this.stringService.monitorUpAlert
.replace("{monitorName}", monitor.name)
.replace("{time}", formattedTime)
.replace("{code}", code || "Unknown");
} else {
messageText = this.stringService.monitorDownAlert
.replace("{monitorName}", monitor.name)
.replace("{time}", formattedTime)
.replace("{code}", code || "Unknown");
}
const dn = this.stringService.discordNotification;
let discordMessageText = {
embeds: [
{
title: status
? dn.uptimeAlert.replace("{monitorName}", monitor?.name ?? dn.unknown)
: dn.downtimeAlert.replace("{monitorName}", monitor?.name ?? dn.unknown),
color: status ? 5763719 : 15548997,
fields: [
{ name: dn.monitor, value: monitor?.name ?? dn.unknown, inline: true },
{ name: dn.status, value: status ? dn.up : dn.down, inline: true },
{ name: dn.statusCode, value: String(code ?? dn.unknown), inline: true },
{ name: dn.time, value: formattedTime, inline: true },
{ name: dn.url, value: monitor?.url ?? dn.unknown, inline: false },
],
footer: { text: dn.checkmate },
},
],
};
return [messageText, discordMessageText];
};
buildHardwareAlerts = async (networkResponse) => {
const monitor = networkResponse?.monitor;
const thresholds = networkResponse?.monitor?.thresholds;
const { usage_cpu: cpuThreshold = -1, usage_memory: memoryThreshold = -1, usage_disk: diskThreshold = -1 } = thresholds;
const metrics = networkResponse?.payload?.data;
const { cpu: { usage_percent: cpuUsage = -1 } = {}, memory: { usage_percent: memoryUsage = -1 } = {}, disk = [] } = metrics;
const { clientHost } = this.settingsService.getSettings();
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, idx) => ({
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]());
}
}
}
await monitor.save();
const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null;
return [alertsToSend, discordPayload];
};
buildHardwareEmail = async (networkResponse, alerts) => {
const { monitor } = networkResponse;
const template = "hardwareIncidentTemplate";
const context = { monitor: monitor.name, url: monitor.url, alerts };
const subject = `Monitor ${monitor.name} infrastructure alerts`;
const html = await this.emailService.buildEmail(template, context);
return { subject, html };
};
buildHardwareNotificationMessage = (alerts, 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");
};
buildHardwareWebhookBody = (alerts, monitor) => {
const content = alerts.map((alert) => alert).join("\n");
const body = { text: content, name: monitor.name, url: monitor.url };
return body;
};
}
export default NotificationUtils;