From ec1563bba2a5a2e7d4ebb98d674a3c35ef009b17 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 09:49:32 +0800 Subject: [PATCH 01/23] Add parameter for date ranges to return all checks --- Server/db/mongo/modules/monitorModule.js | 3 ++- Server/validation/joi.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 4d355141d..fa72aad21 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -198,7 +198,7 @@ const getIncidents = (checks) => { /** * Get date range parameters - * @param {string} dateRange - 'day' | 'week' | 'month' + * @param {string} dateRange - 'day' | 'week' | 'month' | 'all' * @returns {Object} Start and end dates */ const getDateRange = (dateRange) => { @@ -206,6 +206,7 @@ const getDateRange = (dateRange) => { day: new Date(new Date().setDate(new Date().getDate() - 1)), week: new Date(new Date().setDate(new Date().getDate() - 7)), month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + all: new Date(0), }; return { start: startDates[dateRange], diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 82a732686..bc3967657 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -178,7 +178,7 @@ const getMonitorStatsByIdQueryValidation = joi.object({ status: joi.string(), limit: joi.number(), sortOrder: joi.string().valid("asc", "desc"), - dateRange: joi.string().valid("day", "week", "month"), + dateRange: joi.string().valid("day", "week", "month", "all"), numToDisplay: joi.number(), normalize: joi.boolean(), }); From 69281eb3f1f41d7fbae55c25efdca6338e22606a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 09:50:02 +0800 Subject: [PATCH 02/23] update network request with correct params --- Client/src/Pages/Infrastructure/Details/index.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 5f3ec7f77..5f08d71e1 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -169,7 +169,7 @@ const InfrastructureDetails = () => { ]; const [monitor, setMonitor] = useState(null); const { authToken } = useSelector((state) => state.auth); - const [dateRange, setDateRange] = useState("day"); + const [dateRange, setDateRange] = useState("all"); const { statusColor, determineState } = useUtils(); // These calculations are needed because ResponsiveContainer // doesn't take padding of parent/siblings into account @@ -189,11 +189,11 @@ const InfrastructureDetails = () => { const response = await networkService.getStatsByMonitorId({ authToken: authToken, monitorId: monitorId, - sortOrder: null, + sortOrder: "asc", limit: null, dateRange: dateRange, - numToDisplay: 50, - normalize: true, + numToDisplay: null, + normalize: false, }); setMonitor(response.data.data); } catch (error) { From 3721af755503f5a485e7a52c5acda22988d395f6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:52:01 +0800 Subject: [PATCH 03/23] Add hardware incident email template --- Server/templates/hardwareIncident.mjml | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Server/templates/hardwareIncident.mjml diff --git a/Server/templates/hardwareIncident.mjml b/Server/templates/hardwareIncident.mjml new file mode 100644 index 000000000..e27933fb9 --- /dev/null +++ b/Server/templates/hardwareIncident.mjml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + Message from BlueWave Infrastructure Monitoring + + + + + Infrastructure Alerts + + + + + + + +

Hello {{name}}!

+

{{monitor}} at {{url}} has the following infrastructure alerts:

+ {{#each alerts}} +

• {{this}}

+ {{/each}} +
+
+ + + View Infrastructure Details + +

This email was sent by BlueWave Infrastructure Monitoring.

+
+
+
+
+
\ No newline at end of file From 930403af8cd9ae2dc9631a08fa13d55f97fccfe2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:52:32 +0800 Subject: [PATCH 04/23] Update Notification model to include metric specific notification thresholds --- Server/db/models/Notification.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Server/db/models/Notification.js b/Server/db/models/Notification.js index 9edb0187a..868324cc2 100644 --- a/Server/db/models/Notification.js +++ b/Server/db/models/Notification.js @@ -16,6 +16,28 @@ const NotificationSchema = mongoose.Schema( phone: { type: String, }, + alertThreshold: { + type: Number, + default: 5, + }, + cpuAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, + memoryAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, + diskAlertThreshold: { + type: Number, + default: function () { + return this.alertThreshold; + }, + }, }, { timestamps: true, From e52c55bf2f06e5eb09666c76683c19fba8b6d1ae Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:52:52 +0800 Subject: [PATCH 05/23] remove old treshold violated template --- Server/templates/thresholdViolated.mjml | 40 ------------------------- 1 file changed, 40 deletions(-) delete mode 100644 Server/templates/thresholdViolated.mjml diff --git a/Server/templates/thresholdViolated.mjml b/Server/templates/thresholdViolated.mjml deleted file mode 100644 index a3d4344e4..000000000 --- a/Server/templates/thresholdViolated.mjml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - {{message}} - - - {{#if cpu}} - {{cpu}} - {{/if}} - - - {{#if disk}} - {{disk}} - {{/if}} - - - {{#if memory}} - {{memory}} - {{/if}} - - - - - - \ No newline at end of file From b96648b1559bd3b2c2fdb73a58f875403760c555 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:55:31 +0800 Subject: [PATCH 06/23] add hardware monitor notification checking to jobQueue --- Server/service/jobQueue.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index eedeb1a5c..dd3ac010a 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -166,6 +166,15 @@ class JobQueue { const { monitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); + // If this is an infrastructure monitor, we need to check for thresholds + if (monitor.type === "hardware") { + await this.notificationService.handleInfrastructureNotifications({ + ...networkResponse, + monitor, + prevStatus, + }); + } + //If status hasn't changed, we're done if (statusChanged === false) return; From 355f7e0511ec04150130fe2eb392f2c722eac1a6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:56:13 +0800 Subject: [PATCH 07/23] add monitor an dprevStatus to response from statusService if monitor status does not change --- Server/service/statusService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index 4f7c1a4ce..6e61ea232 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -29,7 +29,8 @@ class StatusService { const { monitorId, status } = networkResponse; const monitor = await this.db.getMonitorById(monitorId); // No change in monitor status, return early - if (monitor.status === status) return { statusChanged: false }; + if (monitor.status === status) + return { monitor, statusChanged: false, prevStatus: monitor.status }; // Monitor status changed, save prev status and update monitor this.logger.info({ From b9cfbd7a7485dc78817245487a4ccdc1ea479300 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 11:56:26 +0800 Subject: [PATCH 08/23] handle hardware notifications --- Server/service/emailService.js | 2 +- Server/service/notificationService.js | 86 ++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 5977048ef..846b7c6c8 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -65,7 +65,7 @@ class EmailService { serverIsDownTemplate: this.loadTemplate("serverIsDown"), serverIsUpTemplate: this.loadTemplate("serverIsUp"), passwordResetTemplate: this.loadTemplate("passwordReset"), - thresholdViolatedTemplate: this.loadTemplate("thresholdViolated"), + hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"), }; /** diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index f464c08a7..5323f5393 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -24,8 +24,15 @@ class NotificationService { * @param {boolean} networkResponse.prevStatus - The previous status of the monitor (true for up, false for down). * @param {string} address - The email address to send the notification to. */ - async sendEmail(networkResponse, address) { + async sendEmail(networkResponse, address, alerts = []) { const { monitor, status, prevStatus } = networkResponse; + if (monitor.type === "hardware") { + 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; + } const template = prevStatus === false ? "serverIsUpTemplate" : "serverIsDownTemplate"; const context = { monitor: monitor.name, url: monitor.url }; const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`; @@ -58,6 +65,83 @@ class NotificationService { }); } } + + async handleInfrastructureNotifications(networkResponse) { + const thresholds = networkResponse?.monitor?.thresholds; + if (thresholds === undefined) return; // 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 ?? {}; + const { + cpu: { usage_percent: cpuUsage = -1 } = {}, + memory: { usage_percent: memoryUsage = -1 } = {}, + disk = [], + } = metrics; + + const alerts = { + cpu: + cpuThreshold !== -1 && cpuUsage > cpuThreshold / 100 + ? "CPU usage is above threshold" + : null, + memory: + memoryThreshold !== -1 && memoryUsage > memoryThreshold / 100 + ? "Memory usage is above threshold" + : null, + disk: disk.some( + (d) => diskThreshold !== -1 && d.usage_percent > diskThreshold / 100 + ) + ? "Disk usage is above threshold" + : null, + }; + + 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 decmremented + if (alerts[type] !== null) { + 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: () => + `${alerts.cpu} ${(cpuUsage * 100).toFixed(0)}% (${cpuThreshold}%)`, + memory: () => + `${alerts.memory} ${(memoryUsage * 100).toFixed(0)}% (${memoryThreshold}%)`, + disk: () => + `${alerts.disk} ${disk + .map((d, idx) => `Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%`) + .join(", ")} (${diskThreshold}%)`, + }; + + alertsToSend.push(formatAlert[type]()); + } + } + } + + await notification.save(); + + if (alertsToSend.length === 0) continue; // No alerts to send, we're done + + if (notification.type === "email") { + this.sendEmail(networkResponse, notification.address, alertsToSend); + } + } + } } export default NotificationService; From 6337cd6462c3ea09e353b847578b30a09f347784 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 12:23:24 +0800 Subject: [PATCH 09/23] convert percentage to decimal before submitting --- Client/src/Pages/Infrastructure/CreateMonitor/index.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index 6a7081d9c..352f57bce 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -132,7 +132,7 @@ const CreateInfrastructureMonitor = () => { Object.keys(form) .filter((k) => k.startsWith(THRESHOLD_FIELD_PREFIX)) .map((k) => { - if (form[k]) thresholds[k] = form[k]; + if (form[k]) thresholds[k] = form[k] / 100; delete form[k]; delete form[k.substring(THRESHOLD_FIELD_PREFIX.length)]; }); @@ -158,6 +158,7 @@ const CreateInfrastructureMonitor = () => { : infrastructureMonitor.name, interval: infrastructureMonitor.interval * MS_PER_MINUTE, }; + delete form.notifications; if (hasValidationErrors(form, infrastructureMonitorValidation, setErrors)) { return; From a5e918e0a7aa5f78785db41892d6b1dcd268e1e9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 12:23:40 +0800 Subject: [PATCH 10/23] remove percentage to decimal conversion from BE --- Server/service/notificationService.js | 32 +++++++++++---------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index 5323f5393..dfe3afc08 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -86,19 +86,11 @@ class NotificationService { } = metrics; const alerts = { - cpu: - cpuThreshold !== -1 && cpuUsage > cpuThreshold / 100 - ? "CPU usage is above threshold" - : null, - memory: - memoryThreshold !== -1 && memoryUsage > memoryThreshold / 100 - ? "Memory usage is above threshold" - : null, - disk: disk.some( - (d) => diskThreshold !== -1 && d.usage_percent > diskThreshold / 100 - ) - ? "Disk usage is above threshold" - : null, + 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( @@ -110,7 +102,7 @@ class NotificationService { for (const type of alertTypes) { // Iterate over each alert type to see if any need to be decmremented - if (alerts[type] !== null) { + if (alerts[type] === true) { notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered if (notification[`${type}AlertThreshold`] <= 0) { @@ -119,13 +111,15 @@ class NotificationService { const formatAlert = { cpu: () => - `${alerts.cpu} ${(cpuUsage * 100).toFixed(0)}% (${cpuThreshold}%)`, + `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`, memory: () => - `${alerts.memory} ${(memoryUsage * 100).toFixed(0)}% (${memoryThreshold}%)`, + `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`, disk: () => - `${alerts.disk} ${disk - .map((d, idx) => `Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%`) - .join(", ")} (${diskThreshold}%)`, + `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]()); From 80763356e5f2a3b7a108181d7d8ec8f676369caa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 22 Nov 2024 12:24:26 +0800 Subject: [PATCH 11/23] fix typo in comment --- Server/service/notificationService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index dfe3afc08..88bdc0a86 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -101,7 +101,7 @@ class NotificationService { const alertTypes = ["cpu", "memory", "disk"]; for (const type of alertTypes) { - // Iterate over each alert type to see if any need to be decmremented + // 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 From 24571df7f1c49c4ca7451099c71cace1aa21b9c2 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 22 Nov 2024 11:22:46 +0000 Subject: [PATCH 12/23] fix(deps): update dependency bullmq to v5.29.0 --- Server/package-lock.json | 8 ++++---- Server/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index c7b17e821..afef7f6c2 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.28.2", + "bullmq": "5.29.0", "cors": "^2.8.5", "dockerode": "4.0.2", "dotenv": "^16.4.5", @@ -1217,9 +1217,9 @@ } }, "node_modules/bullmq": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.28.2.tgz", - "integrity": "sha512-2F94CJnOKP2kyf29v8bxpNNyF7IsG+z0RY80gP7dAvUprcEbb3CAvyCKMcbkHeF/kBKkcJCNQOlVY/+JY8dsUQ==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.29.0.tgz", + "integrity": "sha512-mRA0m+XJvvpB+MG1xSefcbb3VdEhqCenH87fGE01o2VsoRC0VwXROIX23RXha9RJaJ5/iEOsgUICuVvW+MHpqw==", "license": "MIT", "dependencies": { "cron-parser": "^4.6.0", diff --git a/Server/package.json b/Server/package.json index fea0ca91f..7f48822c8 100644 --- a/Server/package.json +++ b/Server/package.json @@ -14,7 +14,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.28.2", + "bullmq": "5.29.0", "cors": "^2.8.5", "dockerode": "4.0.2", "dotenv": "^16.4.5", From 70df0738adeea8b2681fd000ee8eb7b15538299b Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Fri, 22 Nov 2024 15:11:54 -0500 Subject: [PATCH 13/23] - Remove inactive upcoming features --- .../Infrastructure/CreateMonitor/index.jsx | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index 6a7081d9c..9553b2a68 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -194,25 +194,6 @@ const CreateInfrastructureMonitor = () => { { _id: 10, name: "10 minutes" }, ]; - const NOTIFY_MULTIPLE_EMAIL_LABEL = ( - - - Also notify via email to multiple addresses (coming soon) - - logger.warn("disabled")} - onBlur={handleBlur} - /> - - You can separate multiple emails with a comma - - - ); - return ( { onChange={(e) => handleChange(e)} onBlur={handleBlur} /> - logger.warn("disabled")} - onBlur={handleBlur} - isDisabled={true} - /> From fe960978515770c5ebbcfb4f33497e70a2da7329 Mon Sep 17 00:00:00 2001 From: Shemy Gan Date: Fri, 22 Nov 2024 15:27:34 -0500 Subject: [PATCH 14/23] - Fix words in sentence based on figma --- Client/src/Pages/Infrastructure/CreateMonitor/index.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index 9553b2a68..f1650309b 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -234,7 +234,8 @@ const CreateInfrastructureMonitor = () => { General settings - Here you can select the URL of the host, together with the type of monitor. + Here you can select the URL of the host, together with the friendly name and + authorization secret to connect to the server agent. @@ -295,8 +296,8 @@ const CreateInfrastructureMonitor = () => { Customize alerts - Send a notification to user(s) When the thresholds exceed a certain number - or percentage. + Send a notification to user(s) when thresholds exceed a certain number of + percentage. From 5e069f8869798641c92f9526a99b7a35c299bf55 Mon Sep 17 00:00:00 2001 From: Alexander Holliday Date: Sat, 23 Nov 2024 10:07:02 +0800 Subject: [PATCH 15/23] Update index.jsx grammar fix --- Client/src/Pages/Infrastructure/CreateMonitor/index.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx index f1650309b..2171c3c19 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx @@ -296,8 +296,7 @@ const CreateInfrastructureMonitor = () => { Customize alerts - Send a notification to user(s) when thresholds exceed a certain number of - percentage. + Send a notification to user(s) when thresholds exceed a specified percentage. From 00a79d6be01c93d2891fcc481fa1b830b0903023 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 23 Nov 2024 08:07:54 +0000 Subject: [PATCH 16/23] fix(deps): update dependency bullmq to v5.29.1 --- Server/package-lock.json | 8 ++++---- Server/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index afef7f6c2..fe26d5531 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.29.0", + "bullmq": "5.29.1", "cors": "^2.8.5", "dockerode": "4.0.2", "dotenv": "^16.4.5", @@ -1217,9 +1217,9 @@ } }, "node_modules/bullmq": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.29.0.tgz", - "integrity": "sha512-mRA0m+XJvvpB+MG1xSefcbb3VdEhqCenH87fGE01o2VsoRC0VwXROIX23RXha9RJaJ5/iEOsgUICuVvW+MHpqw==", + "version": "5.29.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.29.1.tgz", + "integrity": "sha512-TZWiwRlPnpaN+Qwh4D8IQf2cYLpkiDX1LbaaWEabc6y37ojIttWOSynxDewpVHyW233LssSIC4+aLMSvAjtpmg==", "license": "MIT", "dependencies": { "cron-parser": "^4.6.0", diff --git a/Server/package.json b/Server/package.json index 7f48822c8..5d55ed058 100644 --- a/Server/package.json +++ b/Server/package.json @@ -14,7 +14,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "^5.1.1", - "bullmq": "5.29.0", + "bullmq": "5.29.1", "cors": "^2.8.5", "dockerode": "4.0.2", "dotenv": "^16.4.5", From 435345fa3d5fdbdb433005a5f08c4568fd2d21f1 Mon Sep 17 00:00:00 2001 From: DemonicQuasar <66374844+DemonicQuasar@users.noreply.github.com> Date: Sat, 23 Nov 2024 06:18:56 -0600 Subject: [PATCH 17/23] Update Login.jsx to remove Sign up link Removing sign up link as per issue #1180. --- Client/src/Pages/Auth/Login.jsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index 0554ad579..ece5de314 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -548,23 +548,6 @@ const Login = () => { ) )} - - Don't have an account? — - { - navigate("/register"); - }} - sx={{ userSelect: "none" }} - > - Sign Up - - ); }; From 257cc73ab3b559e9804273c321543f5f9682d593 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 24 Nov 2024 17:39:05 +0800 Subject: [PATCH 18/23] Add safety check for destructuing payload in StatusService --- Server/service/statusService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index 6e61ea232..442329620 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -104,7 +104,7 @@ class StatusService { if (type === "hardware") { const { cpu, memory, disk, host } = payload?.data ?? {}; - const { errors } = payload; + const { errors } = payload?.errors ?? []; check.cpu = cpu ?? {}; check.memory = memory ?? {}; check.disk = disk ?? {}; From 02666397c9187ee75108824bca6612fd869eb6a0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 24 Nov 2024 17:43:37 +0800 Subject: [PATCH 19/23] Move all notification handling from JobQueue to NotificaitonService where it should be --- Server/service/jobQueue.js | 18 +---- Server/service/notificationService.js | 107 +++++++++++++++++++------- 2 files changed, 80 insertions(+), 45 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index dd3ac010a..182f0235a 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -165,26 +165,12 @@ class JobQueue { // Handle status change const { monitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); - - // If this is an infrastructure monitor, we need to check for thresholds - if (monitor.type === "hardware") { - await this.notificationService.handleInfrastructureNotifications({ - ...networkResponse, - monitor, - prevStatus, - }); - } - - //If status hasn't changed, we're done - if (statusChanged === false) return; - - // if prevStatus is undefined, monitor is resuming, we're done - if (prevStatus === undefined) return; - + // Handle notifications this.notificationService.handleNotifications({ ...networkResponse, monitor, prevStatus, + statusChanged, }); } catch (error) { this.logger.error({ diff --git a/Server/service/notificationService.js b/Server/service/notificationService.js index 88bdc0a86..25bf5bfa6 100644 --- a/Server/service/notificationService.js +++ b/Server/service/notificationService.js @@ -14,48 +14,62 @@ class NotificationService { } /** - * Sends an email notification based on the network response. + * Sends an email notification for hardware infrastructure alerts * - * @param {Object} networkResponse - The response from the network monitor. - * @param {Object} networkResponse.monitor - The monitor object containing details about the monitored service. - * @param {string} networkResponse.monitor.name - The name of the monitor. - * @param {string} networkResponse.monitor.url - The URL of the monitor. - * @param {boolean} networkResponse.status - The current status of the monitor (true for up, false for down). - * @param {boolean} networkResponse.prevStatus - The previous status of the monitor (true for up, false for down). - * @param {string} address - The email address to send the notification to. + * @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 sendEmail(networkResponse, address, alerts = []) { + 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; - if (monitor.type === "hardware") { - 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; - } 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; } - /** - * Handles notifications based on the network response. - * - * @param {Object} networkResponse - The response from the network monitor. - * @param {string} networkResponse.monitorId - The ID of the monitor. - */ - async handleNotifications(networkResponse) { + 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, @@ -65,10 +79,18 @@ class NotificationService { }); } } - - async handleInfrastructureNotifications(networkResponse) { + /** + * 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; // No thresholds set, we're done + if (thresholds === undefined) return false; // No thresholds set, we're done // Get thresholds from monitor const { @@ -78,7 +100,9 @@ class NotificationService { } = thresholds; // Get metrics from response - const metrics = networkResponse?.payload?.data ?? {}; + const metrics = networkResponse?.payload?.data ?? null; + if (metrics === null) return false; + const { cpu: { usage_percent: cpuUsage = -1 } = {}, memory: { usage_percent: memoryUsage = -1 } = {}, @@ -121,7 +145,6 @@ class NotificationService { ", " )} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`, }; - alertsToSend.push(formatAlert[type]()); } } @@ -132,9 +155,35 @@ class NotificationService { if (alertsToSend.length === 0) continue; // No alerts to send, we're done if (notification.type === "email") { - this.sendEmail(networkResponse, notification.address, alertsToSend); + 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, + }); + } } } From b97e149d623546f5493ee2daac38d8e7a6845cf5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 24 Nov 2024 17:43:50 +0800 Subject: [PATCH 20/23] update tests --- .../services/notificationService.test.js | 202 +++++++++++++++++- 1 file changed, 197 insertions(+), 5 deletions(-) diff --git a/Server/tests/services/notificationService.test.js b/Server/tests/services/notificationService.test.js index b33bd21ed..ccfdd22ae 100644 --- a/Server/tests/services/notificationService.test.js +++ b/Server/tests/services/notificationService.test.js @@ -78,11 +78,23 @@ describe("NotificationService", () => { describe("handleNotifications", async () => { it("should handle notifications based on the network response", async () => { notificationService.sendEmail = sinon.stub(); - notificationService.db.getNotificationsByMonitorId.resolves([ - { type: "email", address: "www.google.com" }, - ]); - await notificationService.handleNotifications({ monitorId: "123" }); - expect(notificationService.sendEmail.calledOnce).to.be.true; + const res = await notificationService.handleNotifications({ + monitor: { + type: "email", + address: "www.google.com", + }, + }); + expect(res).to.be.true; + }); + it("should handle hardware notifications", async () => { + notificationService.sendEmail = sinon.stub(); + const res = await notificationService.handleNotifications({ + monitor: { + type: "hardware", + address: "www.google.com", + }, + }); + expect(res).to.be.true; }); it("should handle an error when getting notifications", async () => { @@ -92,4 +104,184 @@ describe("NotificationService", () => { expect(notificationService.logger.warn.calledOnce).to.be.true; }); }); + + describe("sendHardwareEmail", async () => { + let networkResponse, address, alerts; + beforeEach(() => { + networkResponse = { + monitor: { + name: "Test Monitor", + url: "http://test.com", + }, + status: true, + prevStatus: false, + }; + address = "test@test.com"; + alerts = ["test"]; + }); + + afterEach(() => { + sinon.restore(); + }); + it("should send an email notification with Hardware Template", async () => { + emailService.buildAndSendEmail.resolves(true); + const res = await notificationService.sendHardwareEmail( + networkResponse, + address, + alerts + ); + expect(res).to.be.true; + }); + it("should return false if no alerts are provided", async () => { + alerts = []; + emailService.buildAndSendEmail.resolves(true); + const res = await notificationService.sendHardwareEmail( + networkResponse, + address, + alerts + ); + expect(res).to.be.false; + }); + }); + describe("handleStatusNotifications", async () => { + let networkResponse; + beforeEach(() => { + networkResponse = { + monitor: { + name: "Test Monitor", + url: "http://test.com", + }, + statusChanged: true, + status: true, + prevStatus: false, + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should handle status notifications", async () => { + db.getNotificationsByMonitorId.resolves([ + { type: "email", address: "test@test.com" }, + ]); + const res = await notificationService.handleStatusNotifications(networkResponse); + expect(res).to.be.true; + }); + it("should return false if status hasn't changed", async () => { + networkResponse.statusChanged = false; + const res = await notificationService.handleStatusNotifications(networkResponse); + expect(res).to.be.false; + }); + it("should return false if prevStatus is undefined", async () => { + networkResponse.prevStatus = undefined; + const res = await notificationService.handleStatusNotifications(networkResponse); + expect(res).to.be.false; + }); + it("should handle an error", async () => { + const testError = new Error("Test Error"); + db.getNotificationsByMonitorId.rejects(testError); + try { + await notificationService.handleStatusNotifications(networkResponse); + } catch (error) { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal("Test Error"); + } + }); + }); + + describe("handleHardwareNotifications", async () => { + let networkResponse; + beforeEach(() => { + networkResponse = { + monitor: { + name: "Test Monitor", + url: "http://test.com", + thresholds: { + usage_cpu: 1, + usage_memory: 1, + usage_disk: 1, + }, + }, + payload: { + data: { + cpu: { + usage_percent: 0.655, + }, + memory: { + usage_percent: 0.783, + }, + disk: [ + { + name: "/dev/sda1", + usage_percent: 0.452, + }, + { + name: "/dev/sdb1", + usage_percent: 0.627, + }, + ], + }, + }, + }; + }); + afterEach(() => { + sinon.restore(); + }); + + describe("it should return false if no thresholds are set", () => { + it("should return false if no thresholds are set", async () => { + networkResponse.monitor.thresholds = undefined; + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.false; + }); + + it("should return false if metrics are null", async () => { + networkResponse.payload.data = null; + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.false; + }); + + it("should return true if request is well formed and thresholds > 0", async () => { + db.getNotificationsByMonitorId.resolves([ + { + type: "email", + address: "test@test.com", + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + save: sinon.stub().resolves(), + }, + ]); + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.true; + }); + + it("should return true if thresholds are exceeded", async () => { + db.getNotificationsByMonitorId.resolves([ + { + type: "email", + address: "test@test.com", + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + save: sinon.stub().resolves(), + }, + ]); + networkResponse.monitor.thresholds = { + usage_cpu: 0.01, + usage_memory: 0.01, + usage_disk: 0.01, + }; + const res = + await notificationService.handleHardwareNotifications(networkResponse); + expect(res).to.be.true; + }); + }); + }); }); From 6363fa72e278ae18fad4b6f0349c927ba489cb25 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 25 Nov 2024 09:54:17 +0800 Subject: [PATCH 21/23] add back 50 checks to display for sanity --- Client/src/Pages/Infrastructure/Details/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 5f08d71e1..179712c1f 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -192,7 +192,7 @@ const InfrastructureDetails = () => { sortOrder: "asc", limit: null, dateRange: dateRange, - numToDisplay: null, + numToDisplay: 50, normalize: false, }); setMonitor(response.data.data); From f90208bc0fa4e44c5244866b2d34a69e5005e11f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 25 Nov 2024 09:55:01 +0800 Subject: [PATCH 22/23] Add dependencies to useEffect --- Client/src/Pages/Infrastructure/Details/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 179712c1f..d32e94d50 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -201,7 +201,7 @@ const InfrastructureDetails = () => { } }; fetchData(); - }, []); + }, [dateRange, monitorId, authToken]); const statBoxConfigs = [ { From 6dab90f5184abfe9839b7d12bfb1cfd42a8051ef Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 25 Nov 2024 10:06:40 +0800 Subject: [PATCH 23/23] fix logger missing debug level, replace console.error with logger.error --- Client/src/Pages/Infrastructure/Details/index.jsx | 3 ++- Client/src/Utils/Logger.js | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index d32e94d50..bdb43123c 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -9,6 +9,7 @@ import { useSelector } from "react-redux"; import { networkService } from "../../../main"; import PulseDot from "../../../Components/Animated/PulseDot"; import useUtils from "../../Monitors/utils"; +import { logger } from "../../../Utils/Logger"; import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils"; import { TzTick, @@ -197,7 +198,7 @@ const InfrastructureDetails = () => { }); setMonitor(response.data.data); } catch (error) { - console.error(error); + logger.error(error); } }; fetchData(); diff --git a/Client/src/Utils/Logger.js b/Client/src/Utils/Logger.js index 177391b99..b22cc0a22 100644 --- a/Client/src/Utils/Logger.js +++ b/Client/src/Utils/Logger.js @@ -44,6 +44,14 @@ class Logger { this.log = NO_OP; return; } + + if (logLevel === "debug") { + this.error = console.error.bind(console); + this.warn = console.warn.bind(console); + this.info = console.info.bind(console); + this.log = console.log.bind(console); + return; + } } cleanup() {