class NotificationService { /** * Creates an instance of NotificationService. * * @param {Object} emailService - The email service used for sending notifications. * @param {Object} db - The database instance for storing notification data. * @param {Object} logger - The logger instance for logging activities. */ constructor(emailService, db, logger) { this.SERVICE_NAME = "NotificationService"; this.emailService = emailService; this.db = db; this.logger = logger; } /** * Sends an email notification for hardware infrastructure alerts * * @async * @function sendHardwareEmail * @param {Object} networkResponse - Response object containing monitor information * @param {string} address - Email address to send the notification to * @param {Array} [alerts=[]] - List of hardware alerts to include in the email * @returns {Promise} - Indicates whether email was sent successfully * @throws {Error} */ async sendHardwareEmail(networkResponse, address, alerts = []) { if (alerts.length === 0) return false; const { monitor, status, prevStatus } = networkResponse; const template = "hardwareIncidentTemplate"; const context = { monitor: monitor.name, url: monitor.url, alerts }; const subject = `Monitor ${monitor.name} infrastructure alerts`; this.emailService.buildAndSendEmail(template, context, address, subject); return true; } /** * Sends an email notification about monitor status change * * @async * @function sendEmail * @param {Object} networkResponse - Response object containing monitor status information * @param {string} address - Email address to send the notification to * @returns {Promise} - Indicates email was sent successfully */ async sendEmail(networkResponse, address) { 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"}`; this.emailService.buildAndSendEmail(template, context, address, subject); return true; } async handleStatusNotifications(networkResponse) { try { //If status hasn't changed, we're done if (networkResponse.statusChanged === false) return false; // if prevStatus is undefined, monitor is resuming, we're done if (networkResponse.prevStatus === undefined) return false; const notifications = await this.db.getNotificationsByMonitorId( networkResponse.monitorId ); for (const notification of notifications) { if (notification.type === "email") { this.sendEmail(networkResponse, notification.address); } // Handle other types of notifications here } return true; } catch (error) { this.logger.warn({ message: error.message, service: this.SERVICE_NAME, method: "handleNotifications", stack: error.stack, }); } } /** * Handles status change notifications for a monitor * * @async * @function handleStatusNotifications * @param {Object} networkResponse - Response object containing monitor status information * @returns {Promise} - Indicates whether notifications were processed * @throws {Error} */ async handleHardwareNotifications(networkResponse) { const thresholds = networkResponse?.monitor?.thresholds; if (thresholds === undefined) return false; // No thresholds set, we're done // Get thresholds from monitor const { usage_cpu: cpuThreshold = -1, usage_memory: memoryThreshold = -1, usage_disk: diskThreshold = -1, } = thresholds; // Get metrics from response const metrics = networkResponse?.payload?.data ?? null; if (metrics === null) return false; 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 && d.usage_percent > diskThreshold) ? true : false, }; const notifications = await this.db.getNotificationsByMonitorId( networkResponse.monitorId ); for (const notification of notifications) { const alertsToSend = []; 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) { notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered if (notification[`${type}AlertThreshold`] <= 0) { // If threshold drops below 0, reset and send notification notification[`${type}AlertThreshold`] = notification.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]()); } } } await notification.save(); if (alertsToSend.length === 0) continue; // No alerts to send, we're done if (notification.type === "email") { this.sendHardwareEmail(networkResponse, notification.address, alertsToSend); } } return true; } /** * Handles notifications for different monitor types * * @async * @function handleNotifications * @param {Object} networkResponse - Response object containing monitor information * @returns {Promise} - Indicates whether notifications were processed successfully */ async handleNotifications(networkResponse) { try { if (networkResponse.monitor.type === "hardware") { this.handleHardwareNotifications(networkResponse); } this.handleStatusNotifications(networkResponse); return true; } catch (error) { this.logger.warn({ message: error.message, service: this.SERVICE_NAME, method: "handleNotifications", stack: error.stack, }); } } } export default NotificationService;