From b1abe8763310320b61860f2d382dbeaf1af5ebb0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 13:46:51 +0800 Subject: [PATCH 1/9] Update HardwareCheck model to include all standard fields --- Server/db/models/HardwareCheck.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Server/db/models/HardwareCheck.js b/Server/db/models/HardwareCheck.js index 5e7f53329..c307b9804 100644 --- a/Server/db/models/HardwareCheck.js +++ b/Server/db/models/HardwareCheck.js @@ -36,11 +36,31 @@ const HardwareCheckSchema = mongoose.Schema( type: mongoose.Schema.Types.ObjectId, ref: "Monitor", immutable: true, + index: true, }, + status: { type: Boolean, index: true, }, + + responseTime: { + type: Number, + }, + + statusCode: { + type: Number, + index: true, + }, + + message: { + type: String, + }, + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24 * 30, // 30 days + }, cpu: { type: cpuSchema, default: () => ({}), From 7fd63f8004575118ebee471e18f88527eb8c4ce2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 13:47:30 +0800 Subject: [PATCH 2/9] Add option secret to Monitor for monitors that require authentication --- Server/db/models/Monitor.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js index c4dcb649c..111c48d03 100644 --- a/Server/db/models/Monitor.js +++ b/Server/db/models/Monitor.js @@ -61,6 +61,9 @@ const MonitorSchema = mongoose.Schema( ref: "Notification", }, ], + secret: { + type: String, + }, }, { timestamps: true, From 0c321cf484455fec664b23da0682bbe03816e414 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 13:51:00 +0800 Subject: [PATCH 3/9] Add optional authorization config to HTTP request. Delegate handleHardware to handleHttp as they are identical --- Server/service/jobQueue.js | 1 - Server/service/networkService.js | 20 ++++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index fcc9cc2b1..6d46052f1 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -163,7 +163,6 @@ class JobQueue { // Get the current status const networkResponse = await this.networkService.getStatus(job); // Handle status change - const { monitor, statusChanged, prevStatus } = await this.statusService.updateStatus(networkResponse); diff --git a/Server/service/networkService.js b/Server/service/networkService.js index efdd2e865..b4efab0f4 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -1,5 +1,4 @@ import { errorMessages, successMessages } from "../utils/messages.js"; - /** * Constructs a new NetworkService instance. * @@ -94,6 +93,7 @@ class NetworkService { * @param {Object} job.data - The data object within the job. * @param {string} job.data.url - The URL to send the HTTP GET request to. * @param {string} job.data._id - The monitor ID for the HTTP request. + * @param {string} [job.data.secret] - Secret for authorization if provided. * @returns {Promise} An object containing the HTTP response details. * @property {string} monitorId - The monitor ID for the HTTP request. * @property {string} type - The type of request, which is "http". @@ -105,13 +105,18 @@ class NetworkService { */ async requestHttp(job) { const url = job.data.url; + const config = {}; + + job.data.secret !== undefined && + (config.headers = { Authorization: `Bearer ${job.data.secret}` }); + const { response, responseTime, error } = await this.timeRequest(() => - this.axios.get(url) + this.axios.get(url, config) ); const httpResponse = { monitorId: job.data._id, - type: "http", + type: job.data.type, responseTime, payload: response?.data, }; @@ -121,6 +126,7 @@ class NetworkService { httpResponse.code = code; httpResponse.status = false; httpResponse.message = this.http.STATUS_CODES[code] || "Network Error"; + return httpResponse; } httpResponse.status = true; @@ -155,7 +161,7 @@ class NetworkService { const pagespeedResponse = { monitorId: job.data._id, - type: "pagespeed", + type: job.data.type, responseTime, payload: response?.data, }; @@ -174,7 +180,9 @@ class NetworkService { return pagespeedResponse; } - async requestHandleHardware(job) {} + async requestHardware(job) { + return this.requestHttp(job); + } /** * Gets the status of a job based on its type and returns the appropriate response. @@ -194,7 +202,7 @@ class NetworkService { case this.TYPE_PAGESPEED: return await this.requestPagespeed(job); case this.TYPE_HARDWARE: - return await this.requestHandleHardware(job); + return await this.requestHardware(job); default: this.logger.error({ message: `Unsupported type: ${job.data.type}`, From 10a438a67729a3dd2cc1dfc4564e2ee19aaaf090 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 13:51:31 +0800 Subject: [PATCH 4/9] Implement handling of hardwareChecks --- Server/service/statusService.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index c8089db98..694b0ed72 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -99,6 +99,13 @@ class StatusService { check.performance = (categories.performance?.score || 0) * 100; check.audits = { cls, si, fcp, lcp, tbt }; } + + if (type === "hardware") { + check.cpu = payload.cpu; + check.memory = payload.memory; + check.disk = payload.disk; + check.host = payload.host; + } return check; }; @@ -121,6 +128,7 @@ class StatusService { http: this.db.createCheck, ping: this.db.createCheck, pagespeed: this.db.createPageSpeedCheck, + hardware: this.db.createHardwareCheck, }; const operation = operationMap[networkResponse.type]; const check = this.buildCheck(networkResponse); @@ -130,7 +138,7 @@ class StatusService { message: error.message, service: this.SERVICE_NAME, method: "insertCheck", - details: `Error inserting check for monitor: ${networkResponse.monitorId}`, + details: `Error inserting check for monitor: ${networkResponse?.monitorId}`, stack: error.stack, }); } From a8060a72ab937e1748e185bcb65381485eaa5be0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 13:51:50 +0800 Subject: [PATCH 5/9] Add validation for secret to Create and Edit Monitor validation --- Server/validation/joi.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 0b85a4557..cd69a959d 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -228,6 +228,7 @@ const createMonitorBodyValidation = joi.object({ usage_disk: joi.number(), }), notifications: joi.array().items(joi.object()), + secret: joi.string(), }); const editMonitorBodyValidation = joi.object({ @@ -235,6 +236,7 @@ const editMonitorBodyValidation = joi.object({ description: joi.string(), interval: joi.number(), notifications: joi.array().items(joi.object()), + secret: joi.string(), }); const pauseMonitorParamValidation = joi.object({ From e940a0d1519f3974e0f5b72a293e3f5551a34041 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 13:51:57 +0800 Subject: [PATCH 6/9] update tests --- Server/tests/services/networkService.test.js | 71 +++++++++++++++++--- Server/tests/services/statusService.test.js | 20 ++++++ 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js index dbc2986ee..f27f0ef79 100644 --- a/Server/tests/services/networkService.test.js +++ b/Server/tests/services/networkService.test.js @@ -2,7 +2,6 @@ import sinon from "sinon"; import NetworkService from "../../service/networkService.js"; import { expect } from "chai"; import http from "http"; -import exp from "constants"; describe("Network Service", () => { let axios, ping, logger, networkService; @@ -74,7 +73,7 @@ describe("Network Service", () => { }); describe("requestHttp", () => { it("should return a response object if http successful", async () => { - const job = { data: { url: "http://test.com", _id: "123" } }; + const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; const httpResult = await networkService.requestHttp(job); expect(httpResult.monitorId).to.equal("123"); expect(httpResult.type).to.equal("http"); @@ -87,7 +86,7 @@ describe("Network Service", () => { networkService.timeRequest = sinon .stub() .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123" } }; + const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; const httpResult = await networkService.requestHttp(job); expect(httpResult.monitorId).to.equal("123"); expect(httpResult.type).to.equal("http"); @@ -101,7 +100,7 @@ describe("Network Service", () => { networkService.timeRequest = sinon .stub() .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123" } }; + const job = { data: { url: "http://test.com", _id: "123", type: "http" } }; const httpResult = await networkService.requestHttp(job); expect(httpResult.monitorId).to.equal("123"); expect(httpResult.type).to.equal("http"); @@ -113,7 +112,7 @@ describe("Network Service", () => { describe("requestPagespeed", () => { it("should return a response object if pagespeed successful", async () => { - const job = { data: { url: "http://test.com", _id: "123" } }; + const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; const pagespeedResult = await networkService.requestPagespeed(job); expect(pagespeedResult.monitorId).to.equal("123"); expect(pagespeedResult.type).to.equal("pagespeed"); @@ -126,7 +125,7 @@ describe("Network Service", () => { networkService.timeRequest = sinon .stub() .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123" } }; + const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; const pagespeedResult = await networkService.requestPagespeed(job); expect(pagespeedResult.monitorId).to.equal("123"); expect(pagespeedResult.type).to.equal("pagespeed"); @@ -140,7 +139,7 @@ describe("Network Service", () => { networkService.timeRequest = sinon .stub() .resolves({ response: null, responseTime: 1, error }); - const job = { data: { url: "http://test.com", _id: "123" } }; + const job = { data: { url: "http://test.com", _id: "123", type: "pagespeed" } }; const pagespeedResult = await networkService.requestPagespeed(job); expect(pagespeedResult.monitorId).to.equal("123"); expect(pagespeedResult.type).to.equal("pagespeed"); @@ -150,6 +149,60 @@ describe("Network Service", () => { }); }); + describe("requestHardware", () => { + it("should return a response object if hardware successful", async () => { + const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; + const httpResult = await networkService.requestHardware(job); + expect(httpResult.monitorId).to.equal("123"); + expect(httpResult.type).to.equal("hardware"); + expect(httpResult.responseTime).to.be.a("number"); + expect(httpResult.status).to.be.true; + }); + it("should return a response object if hardware successful and job jas a secret", async () => { + const job = { + data: { + url: "http://test.com", + _id: "123", + type: "hardware", + secret: "my_secret", + }, + }; + const httpResult = await networkService.requestHardware(job); + expect(httpResult.monitorId).to.equal("123"); + expect(httpResult.type).to.equal("hardware"); + expect(httpResult.responseTime).to.be.a("number"); + expect(httpResult.status).to.be.true; + }); + it("should return a response object if hardware unsuccessful", async () => { + const error = new Error("Test error"); + error.response = { status: 404 }; + networkService.timeRequest = sinon + .stub() + .resolves({ response: null, responseTime: 1, error }); + const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; + const httpResult = await networkService.requestHardware(job); + expect(httpResult.monitorId).to.equal("123"); + expect(httpResult.type).to.equal("hardware"); + expect(httpResult.responseTime).to.be.a("number"); + expect(httpResult.status).to.be.false; + expect(httpResult.code).to.equal(404); + }); + it("should return a response object if hardware unsuccessful with unknown code", async () => { + const error = new Error("Test error"); + error.response = {}; + networkService.timeRequest = sinon + .stub() + .resolves({ response: null, responseTime: 1, error }); + const job = { data: { url: "http://test.com", _id: "123", type: "hardware" } }; + const httpResult = await networkService.requestHardware(job); + expect(httpResult.monitorId).to.equal("123"); + expect(httpResult.type).to.equal("hardware"); + expect(httpResult.responseTime).to.be.a("number"); + expect(httpResult.status).to.be.false; + expect(httpResult.code).to.equal(networkService.NETWORK_ERROR); + }); + }); + describe("getStatus", () => { beforeEach(() => { networkService.requestPing = sinon.stub(); @@ -179,10 +232,10 @@ describe("Network Service", () => { expect(networkService.requestHttp.notCalled).to.be.true; expect(networkService.requestPagespeed.calledOnce).to.be.true; }); - it("should call requestHandleHardware if type is hardware", () => { + it("should call requestHardware if type is hardware", () => { networkService.getStatus({ data: { type: "hardware" } }); + expect(networkService.requestHardware.calledOnce).to.be.true; expect(networkService.requestPing.notCalled).to.be.true; - expect(networkService.requestHttp.notCalled).to.be.true; expect(networkService.requestPagespeed.notCalled).to.be.true; }); it("should log an error if an unknown type is provided", () => { diff --git a/Server/tests/services/statusService.test.js b/Server/tests/services/statusService.test.js index df74ef715..19b0edf19 100644 --- a/Server/tests/services/statusService.test.js +++ b/Server/tests/services/statusService.test.js @@ -173,6 +173,26 @@ describe("StatusService", () => { tbt: 0, }); }); + it("should build a check for hardware type", () => { + const check = statusService.buildCheck({ + monitorId: "test", + type: "hardware", + status: true, + responseTime: 100, + code: 200, + message: "Test message", + payload: { cpu: "cpu", memory: "memory", disk: "disk", host: "host" }, + }); + expect(check.monitorId).to.equal("test"); + expect(check.status).to.be.true; + expect(check.statusCode).to.equal(200); + expect(check.responseTime).to.equal(100); + expect(check.message).to.equal("Test message"); + expect(check.cpu).to.equal("cpu"); + expect(check.memory).to.equal("memory"); + expect(check.disk).to.equal("disk"); + expect(check.host).to.equal("host"); + }); }); describe("insertCheck", () => { it("should log an error if one is thrown", async () => { From b417b72450bbc434600ce5eb4ed2ff46a90b9452 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 14:47:54 +0800 Subject: [PATCH 7/9] Refactor requestPagespeed to simply call requestHttp with an updated job object --- Server/service/networkService.js | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index b4efab0f4..fa665e1ea 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -126,7 +126,6 @@ class NetworkService { httpResponse.code = code; httpResponse.status = false; httpResponse.message = this.http.STATUS_CODES[code] || "Network Error"; - return httpResponse; } httpResponse.status = true; @@ -153,31 +152,10 @@ class NetworkService { */ async requestPagespeed(job) { const url = job.data.url; - const { response, responseTime, error } = await this.timeRequest(() => - this.axios.get( - `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance` - ) - ); - - const pagespeedResponse = { - monitorId: job.data._id, - type: job.data.type, - responseTime, - payload: response?.data, - }; - - if (error) { - const code = error.response?.status || this.NETWORK_ERROR; - pagespeedResponse.code = code; - pagespeedResponse.status = false; - pagespeedResponse.message = this.http.STATUS_CODES[code] || "Network Error"; - return pagespeedResponse; - } - - pagespeedResponse.status = true; - pagespeedResponse.code = response.status; - pagespeedResponse.message = this.http.STATUS_CODES[response.status]; - return pagespeedResponse; + const updatedJob = { ...job }; + const pagespeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`; + updatedJob.data.url = pagespeedUrl; + return this.requestHttp(updatedJob); } async requestHardware(job) { From 29c83f5206ae691a7e2a4f091576dd637b0536c4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Oct 2024 14:48:19 +0800 Subject: [PATCH 8/9] Safely access payload values --- Server/service/statusService.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/service/statusService.js b/Server/service/statusService.js index 694b0ed72..64705e122 100644 --- a/Server/service/statusService.js +++ b/Server/service/statusService.js @@ -101,10 +101,10 @@ class StatusService { } if (type === "hardware") { - check.cpu = payload.cpu; - check.memory = payload.memory; - check.disk = payload.disk; - check.host = payload.host; + check.cpu = payload?.cpu ?? {}; + check.memory = payload?.memory ?? {}; + check.disk = payload?.disk ?? {}; + check.host = payload?.host ?? {}; } return check; }; From b1d794771985f0e43a3763c8e8bacfead0e9723d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Oct 2024 09:07:47 +0800 Subject: [PATCH 9/9] fix typo jas -> has --- Server/tests/services/networkService.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js index f27f0ef79..4e48524e2 100644 --- a/Server/tests/services/networkService.test.js +++ b/Server/tests/services/networkService.test.js @@ -158,7 +158,7 @@ describe("Network Service", () => { expect(httpResult.responseTime).to.be.a("number"); expect(httpResult.status).to.be.true; }); - it("should return a response object if hardware successful and job jas a secret", async () => { + it("should return a response object if hardware successful and job has a secret", async () => { const job = { data: { url: "http://test.com",