diff --git a/Server/db/models/HardwareCheck.js b/Server/db/models/HardwareCheck.js index b710087bf..5e7f53329 100644 --- a/Server/db/models/HardwareCheck.js +++ b/Server/db/models/HardwareCheck.js @@ -16,7 +16,7 @@ const memorySchema = mongoose.Schema({ usage_percent: { type: Number, default: 0 }, }); -const discSchema = mongoose.Schema({ +const diskSchema = mongoose.Schema({ read_speed_bytes: { type: Number, default: 0 }, write_speed_bytes: { type: Number, default: 0 }, total_bytes: { type: Number, default: 0 }, @@ -37,6 +37,10 @@ const HardwareCheckSchema = mongoose.Schema( ref: "Monitor", immutable: true, }, + status: { + type: Boolean, + index: true, + }, cpu: { type: cpuSchema, default: () => ({}), @@ -46,7 +50,7 @@ const HardwareCheckSchema = mongoose.Schema( default: () => ({}), }, disk: { - type: [discSchema], + type: [diskSchema], default: () => [], }, host: { diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js index 6d718e9cd..c4dcb649c 100644 --- a/Server/db/models/Monitor.js +++ b/Server/db/models/Monitor.js @@ -1,5 +1,4 @@ import mongoose from "mongoose"; -import Notification from "./Notification.js"; const MonitorSchema = mongoose.Schema( { @@ -48,6 +47,14 @@ const MonitorSchema = mongoose.Schema( type: Number, default: undefined, }, + thresholds: { + type: { + usage_cpu: { type: Number }, + usage_memory: { type: Number }, + usage_disk: { type: Number }, + }, + _id: false, + }, notifications: [ { type: mongoose.Schema.Types.ObjectId, diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 668a0094a..5977048ef 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -65,6 +65,7 @@ class EmailService { serverIsDownTemplate: this.loadTemplate("serverIsDown"), serverIsUpTemplate: this.loadTemplate("serverIsUp"), passwordResetTemplate: this.loadTemplate("passwordReset"), + thresholdViolatedTemplate: this.loadTemplate("thresholdViolated"), }; /** diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 3f2a98965..157a29006 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -34,24 +34,24 @@ class NetworkService { } /** - * Handles the notification process for a monitor. + * Handles notifications for a given monitor configuration. * - * @param {Object} monitor - The monitor object containing monitor details. - * @param {boolean} isAlive - The status of the monitor (true if up, false if down). - * @returns {Promise} - */ async handleNotification(monitor, isAlive) { + * @param {Object} config - The configuration object for the notification. + * @param {Object} config.monitor - The monitor object containing the monitor ID. + * @param {string} config.template - The email template to be used. + * @param {Object} config.context - The context for the email template. + * @param {string} config.subject - The subject of the email. + */ + async handleNotification(config) { try { - let template = isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; - let status = isAlive === true ? "up" : "down"; - - const notifications = await this.db.getNotificationsByMonitorId(monitor._id); + const notifications = await this.db.getNotificationsByMonitorId(config.monitor._id); for (const notification of notifications) { if (notification.type === "email") { await this.emailService.buildAndSendEmail( - template, - { monitorName: monitor.name, monitorUrl: monitor.url }, + config.template, + config.context, notification.address, - `Monitor ${monitor.name} is ${status}` + config.subject ); } } @@ -71,9 +71,10 @@ class NetworkService { * * @param {Object} job - The job object containing job details. * @param {boolean} isAlive - The status of the monitor (true if up, false if down). + * @param {Object} [hardwareData] - The hardware data for the monitor. * @returns {Promise} */ - async handleStatusUpdate(job, isAlive) { + async handleStatusUpdate(job, isAlive, hardwareData) { let monitor; const { _id } = job.data; @@ -99,9 +100,18 @@ class NetworkService { await monitor.save(); if (oldStatus !== undefined && oldStatus !== isAlive) { - this.handleNotification(monitor, isAlive); + const config = { + monitor: monitor, + template: isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate", + context: { monitorName: monitor.name, monitorUrl: monitor.url }, + subject: `Monitor ${monitor.name} is ${isAlive === true ? "up" : "down"}`, + }; + this.handleNotification(config); } } + if (monitor.type === this.TYPE_HARDWARE) { + this.handleHardwareUpdate(monitor, hardwareData); + } } catch (error) { this.logger.error({ message: error.message, @@ -113,6 +123,55 @@ class NetworkService { } } + async handleHardwareUpdate(monitor, hardwareData) { + //Get Thresholds + const thresholds = monitor?.thresholds; + if (thresholds === undefined) { + return; + } + //Get Values + const cpuUsage = hardwareData?.cpu?.usage_percent; + const memoryUsage = hardwareData?.memory?.usage_percent; + const diskUsage = hardwareData?.disk[0]?.usage_percent; + + // Return early if no values + if (cpuUsage === undefined && memoryUsage === undefined && diskUsage === undefined) { + return; + } + + // Return early if all values are below thresholds + if ( + cpuUsage < thresholds.usage_cpu && + memoryUsage < thresholds.usage_memory && + diskUsage < thresholds.usage_disk + ) { + return; + } + + let context = { + message: "Threshold(s) exceeded:", + }; + if (cpuUsage > thresholds.usage_cpu) { + context.cpu = `CPU USAGE: ${cpuUsage * 100}% > ${thresholds.usage_cpu * 100}%`; + } + + if (memoryUsage > thresholds.usage_memory) { + context.memory = `MEMORY USAGE: ${memoryUsage * 100}% > ${thresholds.usage_memory * 100}%`; + } + + if (diskUsage > thresholds.usage_disk) { + context.disk = `DISK USAGE: ${diskUsage * 100}% > ${thresholds.usage_disk * 100}%`; + } + + const config = { + monitor: monitor, + template: "thresholdViolatedTemplate", + context: context, + subject: `Threshold Violated for ${monitor.name}`, + }; + this.handleNotification(config); + } + /** * Measures the response time of an asynchronous operation. * @param {Function} operation - An asynchronous operation to measure. @@ -311,13 +370,13 @@ class NetworkService { frequency: 266, temperature: null, free_percent: null, - usage_percent: null, + usage_percent: 1, }, memory: { total_bytes: 4, available_bytes: 4, used_bytes: 2, - usage_percent: 0.5, + usage_percent: 1, }, disk: [ { @@ -325,7 +384,7 @@ class NetworkService { write_speed_bytes: 3, total_bytes: 10, free_bytes: 2, - usage_percent: 0.8, + usage_percent: 1, }, ], host: { @@ -372,7 +431,7 @@ class NetworkService { }; this.logAndStoreCheck(nullData, this.db.createHardwareCheck); } finally { - this.handleStatusUpdate(job, isAlive); + this.handleStatusUpdate(job, isAlive, hardwareData); } } diff --git a/Server/templates/thresholdViolated.mjml b/Server/templates/thresholdViolated.mjml new file mode 100644 index 000000000..a3d4344e4 --- /dev/null +++ b/Server/templates/thresholdViolated.mjml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + {{message}} + + + {{#if cpu}} + {{cpu}} + {{/if}} + + + {{#if disk}} + {{disk}} + {{/if}} + + + {{#if memory}} + {{memory}} + {{/if}} + + + + + + \ No newline at end of file diff --git a/Server/validation/joi.js b/Server/validation/joi.js index d1ba107bc..0b85a4557 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -222,6 +222,11 @@ const createMonitorBodyValidation = joi.object({ url: joi.string().required(), isActive: joi.boolean(), interval: joi.number(), + thresholds: joi.object().keys({ + usage_cpu: joi.number(), + usage_memory: joi.number(), + usage_disk: joi.number(), + }), notifications: joi.array().items(joi.object()), });