Merge pull request #1037 from bluewave-labs/feat/be/hardware-monitor-notifications

feat/be/hardware monitor notifications
This commit is contained in:
Alexander Holliday
2024-10-26 09:32:41 +08:00
committed by GitHub
6 changed files with 137 additions and 21 deletions

View File

@@ -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: {

View File

@@ -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,

View File

@@ -65,6 +65,7 @@ class EmailService {
serverIsDownTemplate: this.loadTemplate("serverIsDown"),
serverIsUpTemplate: this.loadTemplate("serverIsUp"),
passwordResetTemplate: this.loadTemplate("passwordReset"),
thresholdViolatedTemplate: this.loadTemplate("thresholdViolated"),
};
/**

View File

@@ -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<void>}
*/ 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<void>}
*/
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);
}
}

View File

@@ -0,0 +1,40 @@
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
<mj-section padding="0px"></mj-section>
</mj-attributes>
</mj-head>
<mj-body>
<mj-section padding="20px 0">
<mj-column width="100%">
<mj-text align="left" font-size="10px">
Message from BlueWave Uptime Service
</mj-text>
</mj-column>
<mj-column width="45%" padding-top="20px">
<mj-text font-weight="500" padding="0px" font-size="18px">
{{message}}
</mj-text>
<mj-text font-weight="500" padding="0px" font-size="18px">
{{#if cpu}}
{{cpu}}
{{/if}}
</mj-text>
<mj-text font-weight="500" padding="0px" font-size="18px">
{{#if disk}}
{{disk}}
{{/if}}
</mj-text>
<mj-text font-weight="500" padding="0px" font-size="18px">
{{#if memory}}
{{memory}}
{{/if}}
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

View File

@@ -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()),
});