From 628f3a3d12727da77c4ccd316719ba64ccd32a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sun, 11 Aug 2024 16:15:30 +0300 Subject: [PATCH 01/96] chore: create openapi.json with openapi 3.1.0 format > Objects >> info >> servers --- Server/openapi.json | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 Server/openapi.json diff --git a/Server/openapi.json b/Server/openapi.json new file mode 100644 index 000000000..635dfe0ad --- /dev/null +++ b/Server/openapi.json @@ -0,0 +1,35 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "BluWave Uptime", + "summary": "BlueWave Uptime OpenAPI Specifications", + "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", + "contact": { + "name": "API Support", + "url": "mailto:hello@bluewavelabs.ca", + "email": "hello@bluewavelabs.ca" + }, + "license": { + "name": "AGPLv3", + "url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE" + }, + "version": "1.0" + }, + "servers": [ + { + "url": "https://bluewavelabs.ca", + "description": "Remote Development Server" + }, + { + "url": "http://localhost:{PORT}", + "description": "Local Development Server", + "variables": { + "PORT": { + "description": "The Data Set API is accessible via https and http", + "enum": ["5000", "3000", "8080"], + "default": "5000" + } + } + } + ] +} From 0d2366cb3fa05c9577a6e058bf134229955bc122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sun, 11 Aug 2024 18:52:19 +0300 Subject: [PATCH 02/96] chore: update contact email in openapi.json --- Server/openapi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index 635dfe0ad..8e219b5ae 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -6,8 +6,8 @@ "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", "contact": { "name": "API Support", - "url": "mailto:hello@bluewavelabs.ca", - "email": "hello@bluewavelabs.ca" + "url": "mailto:support@bluewavelabs.ca", + "email": "support@bluewavelabs.ca" }, "license": { "name": "AGPLv3", From adbbdd405c01854e0c47b5a63a91eda93f5b4d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sun, 11 Aug 2024 19:03:02 +0300 Subject: [PATCH 03/96] chore(openapi): dynamic API path variables on servers --- Server/openapi.json | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index 8e219b5ae..8d3fff916 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -17,17 +17,29 @@ }, "servers": [ { - "url": "https://bluewavelabs.ca", - "description": "Remote Development Server" + "url": "https://bluewavelabs.ca/{API_PATH}", + "description": "Remote Development Server", + "variables": { + "API_PATH": { + "description": "API Base Path", + "enum": ["api/v1", "api/v1.1", "api/v2"], + "default": "api/v1" + } + } }, { - "url": "http://localhost:{PORT}", + "url": "http://localhost:{PORT}/{API_PATH}", "description": "Local Development Server", "variables": { "PORT": { - "description": "The Data Set API is accessible via https and http", + "description": "API Port", "enum": ["5000", "3000", "8080"], "default": "5000" + }, + "API_PATH": { + "description": "API Base Path", + "enum": ["api/v1", "api/v1.1", "api/v2"], + "default": "api/v1" } } } From 1541fd20fc4934769692bf7b3e8311938f61da29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Mon, 12 Aug 2024 21:39:10 +0300 Subject: [PATCH 04/96] chore(openapi): define the endpoint, method and description on auth routes > Common Responses - 200 - 422 - 500 --- Server/openapi.json | 320 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 319 insertions(+), 1 deletion(-) diff --git a/Server/openapi.json b/Server/openapi.json index 8d3fff916..e66c38294 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -43,5 +43,323 @@ } } } - ] + ], + "tags": [ + { + "name": "auth", + "description": "Authentication" + } + ], + "paths": { + "/auth/register": { + "post": { + "tags": ["auth"], + "description": "Register a new user", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/login": { + "post": { + "tags": ["auth"], + "description": "Login with credentials", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/logout": { + "post": { + "tags": ["auth"], + "description": "Logout", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/user/:userId": { + "put": { + "tags": ["auth"], + "description": "Change user informations", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + }, + "delete": { + "tags": ["auth"], + "description": "Delete user", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/users/admin": { + "get": { + "tags": ["auth"], + "description": "Checks to see if an admin account exists", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/users": { + "get": { + "tags": ["auth"], + "description": "Get all users", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/invite": { + "post": { + "tags": ["auth"], + "description": "Invite people to application", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/invite/verify": { + "post": { + "tags": ["auth"], + "description": "Verify the invite", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/recovery/request": { + "post": { + "tags": ["auth"], + "description": "Request a recovery token", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/recovery/validate": { + "post": { + "tags": ["auth"], + "description": "Validate recovery token", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + }, + "/auth/recovery/reset": { + "post": { + "tags": ["auth"], + "description": "Password reset", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": {} + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": {} + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": {} + } + } + } + } + } + } } From 16eb578a5563f2ab289e167fb8178cce2cfea9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Mon, 12 Aug 2024 21:40:15 +0300 Subject: [PATCH 05/96] fix(openapi): update typo in title from 'BluWave uptime' to 'BlueWave Uptime' --- Server/openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/openapi.json b/Server/openapi.json index e66c38294..36f18a73f 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.1.0", "info": { - "title": "BluWave Uptime", + "title": "BlueWave Uptime", "summary": "BlueWave Uptime OpenAPI Specifications", "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", "contact": { From e28530cdc7bd2de9cf6689358d00e312626034f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Tue, 13 Aug 2024 00:14:29 +0300 Subject: [PATCH 06/96] chore(openapi): add ErrorResponse and SuccessResponse schema components to auth routes --- Server/openapi.json | 245 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 209 insertions(+), 36 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index 36f18a73f..b0b7deba6 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -59,19 +59,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -85,19 +97,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -111,19 +135,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -137,19 +173,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -161,19 +209,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -187,19 +247,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -213,19 +285,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -239,19 +323,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -265,19 +361,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -291,19 +399,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -317,19 +437,31 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } @@ -343,23 +475,64 @@ "200": { "description": "OK", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } } }, "422": { "description": "Unprocessable Content", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } }, "500": { "description": "Internal Server Error", "content": { - "application/json": {} + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } } } } } } + }, + "components": { + "schemas": { + "ErrorResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + } + } + }, + "SuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object" + } + } + } + } } } From 137500bbd0d523e14243e8cd97d5ee120d05b98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sun, 18 Aug 2024 20:01:34 +0300 Subject: [PATCH 07/96] chore(openapi): add UserSuccessResponse ref to auth responses --- Server/openapi.json | 241 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 229 insertions(+), 12 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index b0b7deba6..cf4338be6 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -61,7 +61,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -99,7 +99,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -137,7 +137,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -175,7 +175,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -211,7 +211,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -249,7 +249,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -287,7 +287,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -325,7 +325,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -363,7 +363,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -401,7 +401,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -439,7 +439,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -477,7 +477,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SuccessResponse" + "$ref": "#/components/schemas/UserSuccessResponse" } } } @@ -532,6 +532,223 @@ "type": "object" } } + }, + "UserSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/UserDataType" + } + } + }, + "MonitorSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/MonitorDataType" + } + } + }, + "CheckDataSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/CheckDataType" + } + } + }, + "AlertDataSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/AlertDataType" + } + } + }, + "UserDataType": { + "type": "object", + "required": [ + "firstname", + "lastname", + "email", + "profilePicUrl", + "isActive", + "isVerified", + "updated_at", + "created_at" + ], + "properties": { + "firstname": { + "type": "string" + }, + "lastname": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "profilePicUrl": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isVerified": { + "type": "boolean" + }, + "updated_at": { + "type": "string" + }, + "created_at": { + "type": "string" + } + } + }, + "MonitorDataType": { + "type": "object", + "required": [ + "userId", + "name", + "description", + "url", + "isActive", + "interval", + "updatedAt", + "createdAt" + ], + "properties": { + "userId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "interval": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + }, + "CheckDataType": { + "type": "object", + "required": [ + "monitorId", + "status", + "responseTime", + "statusCode", + "message", + "updatedAt", + "createdAt" + ], + "properties": { + "monitorId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "responseTime": { + "type": "integer" + }, + "statusCode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + }, + "AlertDataType": { + "type": "object", + "required": [ + "checkId", + "monitorId", + "userId", + "status", + "message", + "notifiedStatus", + "acknowledgedStatus", + "updatedAt", + "createdAt" + ], + "properties": { + "checkId": { + "type": "string" + }, + "monitorId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "message": { + "type": "string" + }, + "notifiedStatus": { + "type": "boolean" + }, + "acknowledgedStatus": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } } } } From cc08d7f8d59602fc33c84ebeb7ba6cf868f99826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sun, 18 Aug 2024 20:20:28 +0300 Subject: [PATCH 08/96] fix(openapi): update property names to camelCase in UserDataType schema --- Server/openapi.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index cf4338be6..536992638 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -602,8 +602,8 @@ "profilePicUrl", "isActive", "isVerified", - "updated_at", - "created_at" + "updatedAt", + "createdAt" ], "properties": { "firstname": { @@ -625,10 +625,10 @@ "isVerified": { "type": "boolean" }, - "updated_at": { + "updatedAt": { "type": "string" }, - "created_at": { + "createdAt": { "type": "string" } } From 21ce46a29e4bdd2e0ac19e16b8691c2829e806bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Fri, 30 Aug 2024 21:09:16 +0300 Subject: [PATCH 09/96] feat(openapi): add request body to login and register routes --- Server/openapi.json | 59 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Server/openapi.json b/Server/openapi.json index 536992638..066bded08 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -55,6 +55,45 @@ "post": { "tags": ["auth"], "description": "Register a new user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["firstName", "lastName", "email", "password", "role", "teamId"], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + }, + "profileImage": { + "type": "file", + "format": "file" + }, + "role": { + "type": "array", + "enum": [["user"], ["admin"], ["superadmin"]], + "default": ["superadmin"] + }, + "teamId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, "responses": { "200": { "description": "OK", @@ -93,6 +132,26 @@ "post": { "tags": ["auth"], "description": "Login with credentials", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "password"], + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, "responses": { "200": { "description": "OK", From bdb293c18159b152d35cbcdfd6ac8a167210d0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Fri, 30 Aug 2024 21:10:13 +0300 Subject: [PATCH 10/96] fix(openapi): remove logout route from auth --- Server/openapi.json | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index 066bded08..72397d139 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -186,44 +186,6 @@ } } }, - "/auth/logout": { - "post": { - "tags": ["auth"], - "description": "Logout", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserSuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, "/auth/user/:userId": { "put": { "tags": ["auth"], From 649cf6835654fbdb3bc3f84b535e8679b81a064f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Fri, 30 Aug 2024 21:12:08 +0300 Subject: [PATCH 11/96] chore(openapi): remove remote development server --- Server/openapi.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Server/openapi.json b/Server/openapi.json index 72397d139..5db6250c5 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -16,17 +16,6 @@ "version": "1.0" }, "servers": [ - { - "url": "https://bluewavelabs.ca/{API_PATH}", - "description": "Remote Development Server", - "variables": { - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1", "api/v1.1", "api/v2"], - "default": "api/v1" - } - } - }, { "url": "http://localhost:{PORT}/{API_PATH}", "description": "Local Development Server", From f680ed8af1b23b3ab0a0d8bbfc1dffbed0b71d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sat, 31 Aug 2024 13:44:23 +0300 Subject: [PATCH 12/96] chore(openapi): add missing route groups to tags - invite - monitors - checks - alerts - pagespeed - maintenance-window - healthy - mail --- Server/openapi.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Server/openapi.json b/Server/openapi.json index 5db6250c5..d9a55dbe6 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -37,6 +37,38 @@ { "name": "auth", "description": "Authentication" + }, + { + "name": "invite", + "description": "Invite" + }, + { + "name": "monitors", + "description": "Monitors" + }, + { + "name": "checks", + "description": "Checks" + }, + { + "name": "alerts", + "description": "Alerts" + }, + { + "name": "pagespeed", + "description": "Page Speed" + }, + { + "name": "maintenance-window", + "description": "Maintenance window" + }, + { + "name": "healthy", + "description": "Health check" + }, + { + "name": "mail", + "description": "Mail service" } ], "paths": { From b6190728a02d32c8ae23631db9d1bfd8a26742a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Sat, 31 Aug 2024 13:51:36 +0300 Subject: [PATCH 13/96] feat(openapi): add /healthy route for health check --- Server/openapi.json | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Server/openapi.json b/Server/openapi.json index d9a55dbe6..23c16521d 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -546,6 +546,44 @@ } } } + }, + "/healthy": { + "get": { + "tags": ["healthy"], + "description": "Health check", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } } }, "components": { From d75b494d58b1f8aa67d45312a2edacd22d32c6c3 Mon Sep 17 00:00:00 2001 From: M M Date: Sun, 1 Sep 2024 12:25:23 -0700 Subject: [PATCH 14/96] Change color of percentage according to uptime of monitor. --- Client/src/Components/Alert/index.jsx | 4 ++-- .../src/Pages/Monitors/Home/monitorData.jsx | 23 +++++++++++++------ Client/src/Utils/Theme/darkTheme.js | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Client/src/Components/Alert/index.jsx b/Client/src/Components/Alert/index.jsx index eb5fbd13b..cafd58451 100644 --- a/Client/src/Components/Alert/index.jsx +++ b/Client/src/Components/Alert/index.jsx @@ -31,7 +31,7 @@ const icons = { const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => { const theme = useTheme(); - const { text, light, border } = theme.palette[variant]; + const { text, bg, border } = theme.palette[variant]; const icon = icons[variant]; return ( @@ -45,7 +45,7 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => { padding: hasIcon ? theme.spacing(8) : `${theme.spacing(4)} ${theme.spacing(8)}`, - backgroundColor: light, + backgroundColor: bg, border: `solid 1px ${border}`, borderRadius: theme.shape.borderRadius, }} diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index d3fd66958..9fd97408c 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -32,21 +32,30 @@ export const buildData = (monitors, isAdmin, navigate) => { data.rows = monitors.map((monitor, idx) => { let uptimePercentage = ""; + let percentageColor = theme.palette.success.main; + if (monitor.uptimePercentage !== undefined) { uptimePercentage = monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); - } + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); + + if (monitor.uptimePercentage < 0.25) { + percentageColor = theme.palette.error.text; + } else if (monitor.uptimePercentage >= 0.25 && monitor.uptimePercentage < 0.5) { + percentageColor = theme.palette.warning.main; + } else if (monitor.uptimePercentage >= 0.5 && monitor.uptimePercentage < 0.75) { + percentageColor = theme.palette.warning.light; + } else if (monitor.uptimePercentage >= 0.75 && monitor.uptimePercentage <= 1) { + percentageColor = theme.palette.success.main; + } + } const params = { url: monitor.url, title: monitor.name, percentage: uptimePercentage, - percentageColor: - monitor.status === true - ? theme.palette.success.main - : theme.palette.error.text, + percentageColor, status: monitor.status === true ? "up" : "down", }; diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 15df7c1b8..a39fb4c1c 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -53,7 +53,7 @@ const darkTheme = createTheme({ warning: { text: "#e88c30", main: "#FF9F00", - light: "#27272a", + light: "#ffd600", bg: "#1E1E1E", border: "#e88c30", }, From 3da323e44e24180095198b3285cbc9ad2851af6e Mon Sep 17 00:00:00 2001 From: M M Date: Sun, 1 Sep 2024 13:40:52 -0700 Subject: [PATCH 15/96] Adjust colors for the light theme. --- Client/src/Utils/Theme/lightTheme.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index bb83d09b2..51639fb0e 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -36,7 +36,7 @@ const lightTheme = createTheme({ }, success: { text: "#079455", - main: "#17b26a", + main: "#45bb7a", light: "#d4f4e1", bg: "#ecfdf3", }, @@ -49,8 +49,8 @@ const lightTheme = createTheme({ }, warning: { text: "#DC6803", - main: "#fdb022", - light: "#fffcf5", + main: "#ff7a00", + light: "#ffb400", bg: "#ffecbc", border: "#fec84b", }, From c515358d78c161c9f7eaa132643658fb947441f2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Sep 2024 10:16:22 -0700 Subject: [PATCH 16/96] fix host proptypes --- Client/src/Pages/Monitors/Home/host.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/Monitors/Home/host.jsx b/Client/src/Pages/Monitors/Home/host.jsx index d450a53f4..9e6a71a05 100644 --- a/Client/src/Pages/Monitors/Home/host.jsx +++ b/Client/src/Pages/Monitors/Home/host.jsx @@ -53,7 +53,8 @@ Host.propTypes = { params: PropTypes.shape({ title: PropTypes.string, percentageColor: PropTypes.string, - percentage: PropTypes.number, + percentage: PropTypes.string, + url: PropTypes.string, }).isRequired, }; From 071a7fc9cf0e8307c9fd6921b7ac4a8ea3e0dc1d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Sep 2024 10:45:07 -0700 Subject: [PATCH 17/96] Add indeterminate state to monitors --- Client/src/Components/Label/index.jsx | 8 ++- Client/src/Pages/Monitors/Home/host.jsx | 3 +- .../src/Pages/Monitors/Home/monitorData.jsx | 14 +++-- Server/controllers/monitorController.js | 1 + Server/models/Monitor.js | 4 +- Server/service/jobQueue.js | 1 - Server/service/networkService.js | 62 ++++++++++++------- 7 files changed, 60 insertions(+), 33 deletions(-) diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 0799a87d7..96468ffd6 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -126,6 +126,7 @@ ColoredLabel.propTypes = { */ const StatusLabel = ({ status, text, customStyles }) => { + console.log(status); const theme = useTheme(); const colors = { up: { @@ -138,6 +139,11 @@ const StatusLabel = ({ status, text, customStyles }) => { bgColor: theme.palette.error.bg, borderColor: theme.palette.error.light, }, + unknown: { + dotColor: theme.palette.unresolved.main, + bgColor: theme.palette.unresolved.bg, + borderColor: theme.palette.unresolved.light, + }, "cannot resolve": { dotColor: theme.palette.unresolved.main, bgColor: theme.palette.unresolved.bg, @@ -170,7 +176,7 @@ const StatusLabel = ({ status, text, customStyles }) => { }; StatusLabel.propTypes = { - status: PropTypes.oneOf(["up", "down", "cannot resolve"]), + status: PropTypes.oneOf(["up", "down", "unknown", "cannot resolve"]), text: PropTypes.string, customStyles: PropTypes.object, }; diff --git a/Client/src/Pages/Monitors/Home/host.jsx b/Client/src/Pages/Monitors/Home/host.jsx index d450a53f4..9e6a71a05 100644 --- a/Client/src/Pages/Monitors/Home/host.jsx +++ b/Client/src/Pages/Monitors/Home/host.jsx @@ -53,7 +53,8 @@ Host.propTypes = { params: PropTypes.shape({ title: PropTypes.string, percentageColor: PropTypes.string, - percentage: PropTypes.number, + percentage: PropTypes.string, + url: PropTypes.string, }).isRequired, }; diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index d3fd66958..28ff08756 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -35,8 +35,8 @@ export const buildData = (monitors, isAdmin, navigate) => { if (monitor.uptimePercentage !== undefined) { uptimePercentage = monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); } const params = { @@ -47,7 +47,12 @@ export const buildData = (monitors, isAdmin, navigate) => { monitor.status === true ? theme.palette.success.main : theme.palette.error.text, - status: monitor.status === true ? "up" : "down", + status: + monitor.status === undefined + ? "unknown" + : monitor.status === true + ? "up" + : "down", }; // Reverse checks so latest check is on the right @@ -86,6 +91,3 @@ export const buildData = (monitors, isAdmin, navigate) => { }); return data; }; - - - diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f33ddcf10..702b28be4 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -359,6 +359,7 @@ const pauseMonitor = async (req, res, next) => { await req.jobQueue.addJob(monitor._id, monitor); } monitor.isActive = !monitor.isActive; + monitor.status = undefined; monitor.save(); return res.status(200).json({ success: true, diff --git a/Server/models/Monitor.js b/Server/models/Monitor.js index fdc147d1b..d91275e23 100644 --- a/Server/models/Monitor.js +++ b/Server/models/Monitor.js @@ -24,7 +24,7 @@ const MonitorSchema = mongoose.Schema( }, status: { type: Boolean, - default: true, + default: undefined, }, type: { type: String, @@ -46,7 +46,7 @@ const MonitorSchema = mongoose.Schema( }, uptimePercentage: { type: Number, - default: undefined, + default: undefined, }, notifications: [ { diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index ed15e0f94..5248c88fa 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -238,7 +238,6 @@ class JobQueue { console.log("Adding job", payload.url); // Execute job immediately await this.queue.add(jobName, payload); - await this.queue.add(jobName, payload, { repeat: { every: payload.interval, diff --git a/Server/service/networkService.js b/Server/service/networkService.js index f5c333a60..e9d44a5b3 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -15,10 +15,31 @@ class NetworkService { this.NETWORK_ERROR = 5000; } - async handleNotification(job, isAlive) { + async handleNotification(monitor, isAlive) { + let template = + isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; + let status = isAlive === true ? "up" : "down"; + + const notifications = await this.db.getNotificationsByMonitorId( + monitor._id + ); + for (const notification of notifications) { + if (notification.type === "email") { + await this.emailService.buildAndSendEmail( + template, + { monitorName: monitor.name, monitorUrl: monitor.url }, + notification.address, + `Monitor ${monitor.name} is ${status}` + ); + } + } + } + + async handleStatusUpdate(job, isAlive) { try { const { _id } = job.data; const monitor = await this.db.getMonitorById(_id); + if (monitor === null || monitor === undefined) { logger.error(`Null Monitor: ${_id}`, { method: "handleNotification", @@ -28,25 +49,22 @@ class NetworkService { return; } - // If monitor status changes, update monitor status and send notification - if (monitor.status !== isAlive) { - monitor.status = !monitor.status; + // Two general cases: + // 1. Monitor status is undefined + // 2. Monitor status is defined + + // If the monitor status is undefined, set it's status to isAlive. if isAlive is false, send notification + if (monitor.status === undefined) { + monitor.status = isAlive; await monitor.save(); - - let template = - isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; - let status = isAlive === true ? "up" : "down"; - - const notifications = await this.db.getNotificationsByMonitorId(_id); - for (const notification of notifications) { - if (notification.type === "email") { - await this.emailService.buildAndSendEmail( - template, - { monitorName: monitor.name, monitorUrl: monitor.url }, - notification.address, - `Monitor ${monitor.name} is ${status}` - ); - } + if (isAlive === false) { + this.handleNotification(monitor, isAlive); + } + } else { + if (monitor.status !== isAlive) { + monitor.status = !monitor.status; + await monitor.save(); + this.handleNotification(monitor, isAlive); } } } catch (error) { @@ -113,7 +131,7 @@ class NetworkService { }; return await this.logAndStoreCheck(checkData, this.db.createCheck); } finally { - this.handleNotification(job, isAlive); + this.handleStatusUpdate(job, isAlive); } } @@ -161,7 +179,7 @@ class NetworkService { return await this.logAndStoreCheck(checkData, this.db.createCheck); } finally { - this.handleNotification(job, isAlive); + this.handleStatusUpdate(job, isAlive); } } @@ -241,7 +259,7 @@ class NetworkService { }; this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); } finally { - this.handleNotification(job, isAlive); + this.handleStatusUpdate(job, isAlive); } } From 4d61ecfe35bfd6187e73e7ee229220fa51442d03 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Sep 2024 10:52:11 -0700 Subject: [PATCH 18/96] Add error handling to notifications --- Server/service/networkService.js | 40 +++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index e9d44a5b3..23b115c97 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -16,22 +16,30 @@ class NetworkService { } async handleNotification(monitor, isAlive) { - let template = - isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; - let status = isAlive === true ? "up" : "down"; + try { + let template = + isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; + let status = isAlive === true ? "up" : "down"; - const notifications = await this.db.getNotificationsByMonitorId( - monitor._id - ); - for (const notification of notifications) { - if (notification.type === "email") { - await this.emailService.buildAndSendEmail( - template, - { monitorName: monitor.name, monitorUrl: monitor.url }, - notification.address, - `Monitor ${monitor.name} is ${status}` - ); + const notifications = await this.db.getNotificationsByMonitorId( + monitor._id + ); + for (const notification of notifications) { + if (notification.type === "email") { + await this.emailService.buildAndSendEmail( + template, + { monitorName: monitor.name, monitorUrl: monitor.url }, + notification.address, + `Monitor ${monitor.name} is ${status}` + ); + } } + } catch (error) { + logger.error(error.message, { + method: "handleNotification", + service: this.SERVICE_NAME, + monitorId: monitor._id, + }); } } @@ -42,7 +50,7 @@ class NetworkService { if (monitor === null || monitor === undefined) { logger.error(`Null Monitor: ${_id}`, { - method: "handleNotification", + method: "handleStatusUpdate", service: this.SERVICE_NAME, jobId: job.id, }); @@ -69,7 +77,7 @@ class NetworkService { } } catch (error) { logger.error(error.message, { - method: "handleNotification", + method: "handleStatusUpdate", service: this.SERVICE_NAME, jobId: job.id, }); From d2528a694e23151a9890d778e7e045a1a626b32a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 2 Sep 2024 11:00:59 -0700 Subject: [PATCH 19/96] simplified conditionals for updating status and sending notifications --- Server/service/networkService.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 23b115c97..863349dbf 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -56,22 +56,12 @@ class NetworkService { }); return; } - - // Two general cases: - // 1. Monitor status is undefined - // 2. Monitor status is defined - - // If the monitor status is undefined, set it's status to isAlive. if isAlive is false, send notification - if (monitor.status === undefined) { + if (monitor.status === undefined || monitor.status !== isAlive) { + const oldStatus = monitor.status; monitor.status = isAlive; await monitor.save(); - if (isAlive === false) { - this.handleNotification(monitor, isAlive); - } - } else { - if (monitor.status !== isAlive) { - monitor.status = !monitor.status; - await monitor.save(); + + if (oldStatus !== undefined && oldStatus !== isAlive) { this.handleNotification(monitor, isAlive); } } From 3a27ce0a8aafbf8932e85c89ccc17f5097c8099a Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 2 Sep 2024 14:00:26 -0700 Subject: [PATCH 20/96] Revert to old colors on the light and dark theme. --- Client/src/Utils/Theme/darkTheme.js | 2 +- Client/src/Utils/Theme/lightTheme.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index a39fb4c1c..15df7c1b8 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -53,7 +53,7 @@ const darkTheme = createTheme({ warning: { text: "#e88c30", main: "#FF9F00", - light: "#ffd600", + light: "#27272a", bg: "#1E1E1E", border: "#e88c30", }, diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index 51639fb0e..bb83d09b2 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -36,7 +36,7 @@ const lightTheme = createTheme({ }, success: { text: "#079455", - main: "#45bb7a", + main: "#17b26a", light: "#d4f4e1", bg: "#ecfdf3", }, @@ -49,8 +49,8 @@ const lightTheme = createTheme({ }, warning: { text: "#DC6803", - main: "#ff7a00", - light: "#ffb400", + main: "#fdb022", + light: "#fffcf5", bg: "#ffecbc", border: "#fec84b", }, From f046f250ec924521058f10aaa6e1e7ffc3367aae Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 17:10:42 -0400 Subject: [PATCH 21/96] Revamped header section --- Client/src/Layouts/HomeLayout/index.css | 2 - Client/src/Layouts/HomeLayout/index.jsx | 10 +- Client/src/Pages/Incidents/index.jsx | 2 +- Client/src/Pages/Monitors/Configure/index.jsx | 1 + Client/src/Pages/Monitors/Details/index.css | 7 - Client/src/Pages/Monitors/Details/index.jsx | 210 ++++++++++++++---- Client/src/Pages/Monitors/Home/index.jsx | 16 -- Client/src/Utils/Theme/darkTheme.js | 15 +- Client/src/Utils/Theme/lightTheme.js | 9 + Client/src/assets/icons/certificate.svg | 3 + Client/src/index.css | 4 + 11 files changed, 205 insertions(+), 74 deletions(-) create mode 100644 Client/src/assets/icons/certificate.svg diff --git a/Client/src/Layouts/HomeLayout/index.css b/Client/src/Layouts/HomeLayout/index.css index 1434ff9d9..855bf33fc 100644 --- a/Client/src/Layouts/HomeLayout/index.css +++ b/Client/src/Layouts/HomeLayout/index.css @@ -3,9 +3,7 @@ } .home-layout { - display: flex; position: relative; - gap: var(--env-var-spacing-2); min-height: 100vh; max-width: 1400px; margin: 0 auto; diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx index 92d763f0c..fd92c59fa 100644 --- a/Client/src/Layouts/HomeLayout/index.jsx +++ b/Client/src/Layouts/HomeLayout/index.jsx @@ -1,6 +1,6 @@ import Sidebar from "../../Components/Sidebar"; import { Outlet } from "react-router"; -import { Box } from "@mui/material"; +import { Box, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import "./index.css"; @@ -10,10 +10,14 @@ const HomeLayout = () => { return ( - + - + ); }; diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 5d756298e..3152cf506 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -83,7 +83,7 @@ const Incidents = () => { }; return ( - + {loading ? ( ) : ( diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 483a2fd79..2e719a31a 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -254,6 +254,7 @@ const Configure = () => { color="secondary" loading={isLoading} sx={{ + border: "none", backgroundColor: theme.palette.background.main, px: theme.spacing(5), mr: theme.spacing(6), diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 59ffc5d85..108f09b21 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -1,13 +1,6 @@ -.monitor-details h1.MuiTypography-root { - font-size: var(--env-var-font-size-large-plus); - font-weight: 600; -} .monitor-details h2.MuiTypography-root { font-size: var(--env-var-font-size-large); } -.monitor-details h2.MuiTypography-root { - font-weight: 600; -} .monitor-details button.MuiButtonBase-root { height: var(--env-var-height-2); line-height: 1; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 616f18657..922231036 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -3,8 +3,10 @@ import { useEffect, useState, useCallback } from "react"; import { Box, Button, + Popover, Skeleton, Stack, + Tooltip, Typography, useTheme, } from "@mui/material"; @@ -13,12 +15,14 @@ import { useNavigate, useParams } from "react-router-dom"; import { networkService } from "../../../main"; import { logger } from "../../../Utils/Logger"; import { + formatDate, formatDuration, formatDurationRounded, } from "../../../Utils/timeUtils"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import ButtonGroup from "@mui/material/ButtonGroup"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; +import CertificateIcon from "../../../assets/icons/certificate.svg?react"; import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; @@ -154,6 +158,14 @@ const DetailsPage = ({ isAdmin }) => { const [certificateExpiry, setCertificateExpiry] = useState("N/A"); const navigate = useNavigate(); + const [anchorEl, setAnchorEl] = useState(null); + const openCertificate = (event) => { + setAnchorEl(event.currentTarget); + }; + const closeCertificate = () => { + setAnchorEl(null); + }; + const fetchMonitor = useCallback(async () => { try { const res = await networkService.getStatsByMonitorId( @@ -186,7 +198,16 @@ const DetailsPage = ({ isAdmin }) => { authToken, monitorId ); - setCertificateExpiry(res?.data?.data?.certificateDate ?? "N/A"); + + let [month, day, year] = res?.data?.data?.certificateDate.split("/"); + const date = new Date(year, month - 1, day); + + setCertificateExpiry( + formatDate(date, { + hour: undefined, + minute: undefined, + }) ?? "N/A" + ); } catch (error) { console.error(error); } @@ -207,65 +228,171 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + - - {monitor.url?.replace(/^https?:\/\//, "") || "..."} + {monitor.name} - - - Your site is {monitor?.status ? "up" : "down"}. - {" "} - Checking every {formatDurationRounded(monitor?.interval)}. - + + + + + + {monitor.url?.replace(/^https?:\/\//, "") || "..."} + + + Checking every {formatDurationRounded(monitor?.interval)}. + + - {isAdmin && ( - - )} + + + + + Certificate Expiry + + + {certificateExpiry} + + + {isAdmin && ( + + )} + { value={`${formatDurationRounded(monitor?.lastChecked)} ago`} /> - { borderRadius={theme.shape.borderRadius} sx={{ height: "250px" }} > - + /> */} History - + {/* */} diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 318a9da20..e5c9a765f 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -43,22 +43,6 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState.isLoading && monitorState.monitors.length === 0; - const now = new Date(); - const hour = now.getHours(); - - let greeting = ""; - let emoji = ""; - if (hour < 12) { - greeting = "morning"; - emoji = "🌅"; - } else if (hour < 18) { - greeting = "afternoon"; - emoji = "🌞"; - } else { - greeting = "evening"; - emoji = "🌙"; - } - return ( {loading ? ( diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 15df7c1b8..657f071e4 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -10,10 +10,10 @@ const text = { const background = { main: "#151518", alt: "#09090b", - fill: "#2e2e2e", + fill: "#2D2D33", accent: "#18181a", }; -const border = { light: "#27272a", dark: "#2c2c2c" }; +const border = { light: "#27272a", dark: "#36363e" }; const fontFamilyDefault = '"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif'; @@ -25,7 +25,7 @@ const darkTheme = createTheme({ palette: { mode: "dark", primary: { main: "#1570ef" }, - secondary: { main: "#2e2e2e" }, + secondary: { main: "#2D2D33" }, text: text, background: background, border: border, @@ -92,6 +92,15 @@ const darkTheme = createTheme({ backgroundColor: theme.palette.secondary.main, }, }, + { + props: (props) => + props.variant === "contained" && props.color === "secondary", + style: { + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.dark, + }, + }, ], fontWeight: 400, borderRadius: 4, diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index bb83d09b2..ef38a2abc 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -89,6 +89,15 @@ const lightTheme = createTheme({ backgroundColor: theme.palette.secondary.main, }, }, + { + props: (props) => + props.variant === "contained" && props.color === "secondary", + style: { + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + }, + }, ], fontWeight: 400, borderRadius: 4, diff --git a/Client/src/assets/icons/certificate.svg b/Client/src/assets/icons/certificate.svg new file mode 100644 index 000000000..a985bef9f --- /dev/null +++ b/Client/src/assets/icons/certificate.svg @@ -0,0 +1,3 @@ + + + diff --git a/Client/src/index.css b/Client/src/index.css index 105776bd8..740a22b7a 100644 --- a/Client/src/index.css +++ b/Client/src/index.css @@ -4,6 +4,10 @@ box-sizing: border-box; } +html { + scroll-behavior: smooth; +} + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-weight: 400; From 081ad080a8bb3d9cc4ea91b63b147a8d8e0346a4 Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 2 Sep 2024 15:03:40 -0700 Subject: [PATCH 22/96] Add new objects to the color palettes. --- Client/src/Pages/Monitors/Home/monitorData.jsx | 10 +++++----- Client/src/Utils/Theme/darkTheme.js | 6 ++++++ Client/src/Utils/Theme/lightTheme.js | 6 ++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 9fd97408c..ee167edb4 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -32,7 +32,7 @@ export const buildData = (monitors, isAdmin, navigate) => { data.rows = monitors.map((monitor, idx) => { let uptimePercentage = ""; - let percentageColor = theme.palette.success.main; + let percentageColor = theme.palette.percentage.green; if (monitor.uptimePercentage !== undefined) { uptimePercentage = @@ -41,13 +41,13 @@ export const buildData = (monitors, isAdmin, navigate) => { : (monitor.uptimePercentage * 100).toFixed(2); if (monitor.uptimePercentage < 0.25) { - percentageColor = theme.palette.error.text; + percentageColor = theme.palette.percentage.red; } else if (monitor.uptimePercentage >= 0.25 && monitor.uptimePercentage < 0.5) { - percentageColor = theme.palette.warning.main; + percentageColor = theme.palette.percentage.orange; } else if (monitor.uptimePercentage >= 0.5 && monitor.uptimePercentage < 0.75) { - percentageColor = theme.palette.warning.light; + percentageColor = theme.palette.percentage.yellow; } else if (monitor.uptimePercentage >= 0.75 && monitor.uptimePercentage <= 1) { - percentageColor = theme.palette.success.main; + percentageColor = theme.palette.percentage.green; } } diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 15df7c1b8..3b77966cd 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -57,6 +57,12 @@ const darkTheme = createTheme({ bg: "#1E1E1E", border: "#e88c30", }, + percentage: { + red: "#d32f2f", + orange: "#e88c30", + yellow: "#ffd600", + green: "#079455", + }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, other: { diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index bb83d09b2..67c2d72e5 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -54,6 +54,12 @@ const lightTheme = createTheme({ bg: "#ffecbc", border: "#fec84b", }, + percentage: { + red: "#d32f2f", + orange: "#ec8013", + yellow: "#ffb800", + green: "#079455", + }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, other: { From 4ebaadbe24549cdb50e77688c260bcc4444aa5ba Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 2 Sep 2024 15:34:01 -0700 Subject: [PATCH 23/96] Use ternary based logic rather than conditional statements for clarity. --- .../src/Pages/Monitors/Home/monitorData.jsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index ee167edb4..2f176bd04 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -40,16 +40,15 @@ export const buildData = (monitors, isAdmin, navigate) => { ? "0" : (monitor.uptimePercentage * 100).toFixed(2); - if (monitor.uptimePercentage < 0.25) { - percentageColor = theme.palette.percentage.red; - } else if (monitor.uptimePercentage >= 0.25 && monitor.uptimePercentage < 0.5) { - percentageColor = theme.palette.percentage.orange; - } else if (monitor.uptimePercentage >= 0.5 && monitor.uptimePercentage < 0.75) { - percentageColor = theme.palette.percentage.yellow; - } else if (monitor.uptimePercentage >= 0.75 && monitor.uptimePercentage <= 1) { - percentageColor = theme.palette.percentage.green; - } - } + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.percentage.red + : monitor.uptimePercentage < 0.5 + ? theme.palette.percentage.orange + : monitor.uptimePercentage < 0.75 + ? theme.palette.percentage.yellow + : theme.palette.percentage.green; + } const params = { url: monitor.url, From e5b347e14b18f7a0baad93347da6e9d52b8fdc99 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 18:46:51 -0400 Subject: [PATCH 24/96] Revamped chart cards 1/2 --- Client/src/Pages/Monitors/Details/index.css | 7 - Client/src/Pages/Monitors/Details/index.jsx | 122 +++++++++++++++--- Client/src/Pages/Monitors/Details/styled.jsx | 61 +++++++++ .../assets/icons/average-response-icon.svg | 3 + .../src/assets/icons/response-time-icon.svg | 3 + Client/src/assets/icons/uptime-icon.svg | 3 + 6 files changed, 171 insertions(+), 28 deletions(-) create mode 100644 Client/src/Pages/Monitors/Details/styled.jsx create mode 100644 Client/src/assets/icons/average-response-icon.svg create mode 100644 Client/src/assets/icons/response-time-icon.svg create mode 100644 Client/src/assets/icons/uptime-icon.svg diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 108f09b21..528ec1b92 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -1,10 +1,3 @@ -.monitor-details h2.MuiTypography-root { - font-size: var(--env-var-font-size-large); -} -.monitor-details button.MuiButtonBase-root { - height: var(--env-var-height-2); - line-height: 1; -} .monitor-details p.MuiTypography-root, .monitor-details p.MuiTypography-root span.MuiTypography-root { font-size: var(--env-var-font-size-small-plus); diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 922231036..20979d863 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -23,10 +23,15 @@ import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAr import ButtonGroup from "@mui/material/ButtonGroup"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; import CertificateIcon from "../../../assets/icons/certificate.svg?react"; +import UptimeIcon from "../../../assets/icons/uptime-icon.svg?react"; +import ResponseTimeIcon from "../../../assets/icons/response-time-icon.svg?react"; +import AverageResponseIcon from "../../../assets/icons/average-response-icon.svg?react"; +import IncidentsIcon from "../../../assets/icons/incidents.svg?react"; import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import "./index.css"; +import { ChartBox, IconBox } from "./styled"; const StatBox = ({ title, value }) => { const theme = useTheme(); @@ -228,7 +233,7 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + { alignSelf: "flex-end", }} > - - + { )} + + + + + + + + + Showing statistics for past{" "} + {dateRange === "day" + ? "24 hours" + : dateRange === "week" + ? "7 days" + : "30 days"} + . + + + + + + + + + Uptime + + + + Total Checks + 87 + + + Uptime Percentage + + 98.3% + + + + + + + + + + Incidents + + + Total Incidents + 0 + + + + + + + + + Average response time + + + + + + + + + Response Times + + + + ({ + flex: "1 30%", + height: 300, + minWidth: 250, + padding: theme.spacing(8), + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + borderRadius: 4, + backgroundColor: theme.palette.background.main, + "& h2": { + color: theme.palette.text.secondary, + fontSize: 15, + }, + "& p": { + color: theme.palette.text.tertiary, + fontSize: 13, + }, + "& span": { + color: theme.palette.text.primary, + fontSize: 20, + "& span": { + opacity: 0.8, + marginLeft: 2, + fontSize: 15, + }, + }, + "& .MuiStack-root": { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing(6), + }, + "& > .MuiBox-root:not(:first-of-type)": { + marginTop: theme.spacing(8), + }, +})); + +export const IconBox = styled(Box)(({ theme }) => ({ + height: 34, + minWidth: 34, + width: 34, + position: "relative", + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.dark, + borderRadius: 4, + backgroundColor: theme.palette.background.accent, + "& svg": { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 20, + height: 20, + "& path": { + stroke: theme.palette.text.tertiary, + }, + }, +})); diff --git a/Client/src/assets/icons/average-response-icon.svg b/Client/src/assets/icons/average-response-icon.svg new file mode 100644 index 000000000..572e2002f --- /dev/null +++ b/Client/src/assets/icons/average-response-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Client/src/assets/icons/response-time-icon.svg b/Client/src/assets/icons/response-time-icon.svg new file mode 100644 index 000000000..db5b2af38 --- /dev/null +++ b/Client/src/assets/icons/response-time-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Client/src/assets/icons/uptime-icon.svg b/Client/src/assets/icons/uptime-icon.svg new file mode 100644 index 000000000..55706ae71 --- /dev/null +++ b/Client/src/assets/icons/uptime-icon.svg @@ -0,0 +1,3 @@ + + + From 6e8334026c6985df02b87f5e33535a56d2b8a02e Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 2 Sep 2024 16:19:56 -0700 Subject: [PATCH 25/96] Add docs. --- Client/src/Pages/Monitors/Home/monitorData.jsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 2f176bd04..4220fc2c5 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -27,6 +27,14 @@ const data = { rows: [], }; +/** + * Builds table data for a list of monitors. + * + * @param {Array} monitors - An array of monitor objects containing information about each monitor. + * @param {boolean} isAdmin - Flag indicating if the current user is an admin. + * @param {Function} navigate - A function to navigate to the monitor detail page. + * @returns {Object} The data structure containing columns and rows for the table. + */ export const buildData = (monitors, isAdmin, navigate) => { const theme = useTheme(); @@ -34,13 +42,14 @@ export const buildData = (monitors, isAdmin, navigate) => { let uptimePercentage = ""; let percentageColor = theme.palette.percentage.green; + // Determine uptime percentage and color based on the monitor's uptimePercentage value if (monitor.uptimePercentage !== undefined) { uptimePercentage = monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2); - percentageColor = + percentageColor = monitor.uptimePercentage < 0.25 ? theme.palette.percentage.red : monitor.uptimePercentage < 0.5 @@ -58,7 +67,7 @@ export const buildData = (monitors, isAdmin, navigate) => { status: monitor.status === true ? "up" : "down", }; - // Reverse checks so latest check is on the right + // Reverse checks so the latest check is on the right const reversedChecks = monitor.checks.slice().reverse(); return { @@ -94,6 +103,3 @@ export const buildData = (monitors, isAdmin, navigate) => { }); return data; }; - - - From 0aa2c795fb38d76af03074bed78e3de979b94ced Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 19:31:20 -0400 Subject: [PATCH 26/96] Revamped chart cards 2/2 --- Client/src/Pages/Monitors/Details/index.css | 5 +- Client/src/Pages/Monitors/Details/index.jsx | 242 ++++++------------- Client/src/Pages/Monitors/Details/styled.jsx | 28 +++ Client/src/assets/icons/history-icon.svg | 3 + 4 files changed, 103 insertions(+), 175 deletions(-) create mode 100644 Client/src/assets/icons/history-icon.svg diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 528ec1b92..8b1378917 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -1,4 +1 @@ -.monitor-details p.MuiTypography-root, -.monitor-details p.MuiTypography-root span.MuiTypography-root { - font-size: var(--env-var-font-size-small-plus); -} + diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 20979d863..4c8eced23 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -27,58 +27,12 @@ import UptimeIcon from "../../../assets/icons/uptime-icon.svg?react"; import ResponseTimeIcon from "../../../assets/icons/response-time-icon.svg?react"; import AverageResponseIcon from "../../../assets/icons/average-response-icon.svg?react"; import IncidentsIcon from "../../../assets/icons/incidents.svg?react"; +import HistoryIcon from "../../../assets/icons/history-icon.svg?react"; import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import "./index.css"; -import { ChartBox, IconBox } from "./styled"; - -const StatBox = ({ title, value }) => { - const theme = useTheme(); - return ( - - - {title} - - - {value} - - - ); -}; - -StatBox.propTypes = { - title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; +import { AlertBox, ChartBox, IconBox } from "./styled"; /** * Renders a skeleton layout. @@ -233,7 +187,7 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + { { slotProps={{ paper: { sx: { - py: 4, - px: 6, + mt: theme.spacing(4), + py: theme.spacing(2), + px: theme.spacing(4), width: 140, backgroundColor: theme.palette.background.accent, }, @@ -376,14 +332,46 @@ const DetailsPage = ({ isAdmin }) => { )} + + + up for + + {formatDurationRounded(monitor?.uptimeDuration)} + + + + last check + + {formatDurationRounded(monitor?.lastChecked)} + ago + + + + last response time + + {monitor?.latestResponseTime} + ms + + + - + + Showing statistics for past{" "} + {dateRange === "day" + ? "24 hours" + : dateRange === "week" + ? "7 days" + : "30 days"} + . + + - - Showing statistics for past{" "} - {dateRange === "day" - ? "24 hours" - : dateRange === "week" - ? "7 days" - : "30 days"} - . - @@ -466,117 +445,38 @@ const DetailsPage = ({ isAdmin }) => { Response Times + + {/* */} + + + + + + + + + History + + + - - - - - - - Avg. Response Time{" "} - (24-hr) - - } - value={parseFloat(monitor?.avgResponseTime24hours) - .toFixed(2) - .replace(/\.?0+$/, "")} - /> - - Uptime (24-hr) - - } - value={`${parseFloat(monitor?.uptime24Hours) - .toFixed(2) - .replace(/\.?0+$/, "")}%`} - /> - - Uptime (30-day) - - } - value={`${parseFloat(monitor?.uptime30Days) - .toFixed(2) - .replace(/\.?0+$/, "")}%`} - /> - - - - - Response Times - - - - - - - - - {/* */} - - - - - History - - {/* */} - )} diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index a5bec8f39..479c82d0e 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -13,6 +13,7 @@ export const ChartBox = styled(Box)(({ theme }) => ({ "& h2": { color: theme.palette.text.secondary, fontSize: 15, + fontWeight: 500, }, "& p": { color: theme.palette.text.tertiary, @@ -59,3 +60,30 @@ export const IconBox = styled(Box)(({ theme }) => ({ }, }, })); + +export const AlertBox = styled(Box)(({ theme }) => ({ + padding: `${theme.spacing(4)} ${theme.spacing(8)}`, + minWidth: 200, + width: 225, + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + borderRadius: 4, + backgroundColor: theme.palette.background.main, + "& h2": { + fontSize: 13, + fontWeight: 500, + color: theme.palette.text.secondary, + textTransform: "uppercase", + }, + "& p": { + fontSize: 18, + color: theme.palette.text.primary, + marginTop: theme.spacing(2), + "& span": { + opacity: 0.8, + marginLeft: theme.spacing(3), + fontSize: 15, + }, + }, +})); diff --git a/Client/src/assets/icons/history-icon.svg b/Client/src/assets/icons/history-icon.svg new file mode 100644 index 000000000..7343455ec --- /dev/null +++ b/Client/src/assets/icons/history-icon.svg @@ -0,0 +1,3 @@ + + + From 9ae8c93cb0f8abc3feb3580f55c4117e9eb84d2d Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 20:05:02 -0400 Subject: [PATCH 27/96] Added format function that returns a split duration --- Client/src/Pages/Monitors/Details/index.jsx | 36 ++++++++++++++------ Client/src/Pages/Monitors/Details/styled.jsx | 8 ++--- Client/src/Utils/timeUtils.js | 19 ++++++++++- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 4c8eced23..c6413e9fb 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -18,6 +18,7 @@ import { formatDate, formatDuration, formatDurationRounded, + formatDurationSplit, } from "../../../Utils/timeUtils"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import ButtonGroup from "@mui/material/ButtonGroup"; @@ -31,8 +32,8 @@ import HistoryIcon from "../../../assets/icons/history-icon.svg?react"; import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; +import { StatBox, ChartBox, IconBox } from "./styled"; import "./index.css"; -import { AlertBox, ChartBox, IconBox } from "./styled"; /** * Renders a skeleton layout. @@ -174,6 +175,16 @@ const DetailsPage = ({ isAdmin }) => { fetchCertificate(); }, [authToken, monitorId, monitor]); + const splitDuration = (duration) => { + const { time, format } = formatDurationSplit(duration); + return ( + <> + {time} + {format} + + ); + }; + let loading = Object.keys(monitor).length === 0; return ( @@ -187,7 +198,7 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + { anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={closeCertificate} + disableScrollLock + marginThreshold={null} + anchorReference={anchorEl} anchorOrigin={{ vertical: "bottom", horizontal: "center", @@ -332,27 +346,27 @@ const DetailsPage = ({ isAdmin }) => { )} - - + + up for - {formatDurationRounded(monitor?.uptimeDuration)} + {splitDuration(monitor?.uptimeDuration)} - - + + last check - {formatDurationRounded(monitor?.lastChecked)} + {splitDuration(monitor?.lastChecked)} ago - - + + last response time {monitor?.latestResponseTime} ms - + ({ }, })); -export const AlertBox = styled(Box)(({ theme }) => ({ +export const StatBox = styled(Box)(({ theme }) => ({ padding: `${theme.spacing(4)} ${theme.spacing(8)}`, minWidth: 200, - width: 225, + flex: 1, border: 1, borderStyle: "solid", borderColor: theme.palette.border.light, @@ -81,8 +81,8 @@ export const AlertBox = styled(Box)(({ theme }) => ({ color: theme.palette.text.primary, marginTop: theme.spacing(2), "& span": { - opacity: 0.8, - marginLeft: theme.spacing(3), + color: theme.palette.text.tertiary, + marginLeft: theme.spacing(2), fontSize: 15, }, }, diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 2d1a2f180..fa4a27d95 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -43,6 +43,23 @@ export const formatDurationRounded = (ms) => { return time; }; +export const formatDurationSplit = (ms) => { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + return days > 0 + ? { time: days, format: days === 1 ? "day" : "days" } + : hours > 0 + ? { time: hours, format: hours === 1 ? "hour" : "hours" } + : minutes > 0 + ? { time: minutes, format: minutes === 1 ? "minute" : "minutes" } + : seconds > 0 + ? { time: seconds, format: seconds === 1 ? "second" : "seconds" } + : { time: 0, format: "seconds" }; +}; + export const formatDate = (date, customOptions) => { const options = { year: "numeric", @@ -51,7 +68,7 @@ export const formatDate = (date, customOptions) => { hour: "numeric", minute: "numeric", hour12: true, - ...customOptions + ...customOptions, }; // Return the date using the specified options From f1fbbe2e3aa1079ff3d14fd3ec4277af8580880e Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 20:43:27 -0400 Subject: [PATCH 28/96] Updated color values --- Client/src/Pages/Monitors/Details/index.jsx | 22 ++++++++++++++++---- Client/src/Pages/Monitors/Details/styled.jsx | 2 +- Client/src/Utils/Theme/darkTheme.js | 12 +++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index c6413e9fb..8aef758a0 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -346,9 +346,23 @@ const DetailsPage = ({ isAdmin }) => { )} - - - up for + + + active for {splitDuration(monitor?.uptimeDuration)} @@ -448,7 +462,7 @@ const DetailsPage = ({ isAdmin }) => { - Average response time + Average Response Time diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 1862c1c4e..8e1ee9774 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -64,7 +64,7 @@ export const IconBox = styled(Box)(({ theme }) => ({ export const StatBox = styled(Box)(({ theme }) => ({ padding: `${theme.spacing(4)} ${theme.spacing(8)}`, minWidth: 200, - flex: 1, + width: 225, border: 1, borderStyle: "solid", borderColor: theme.palette.border.light, diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 657f071e4..eca445976 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -39,22 +39,22 @@ const darkTheme = createTheme({ success: { text: "#079455", main: "#45bb7a", - light: "#1e1e1e", - bg: "#27272a", + light: "#1c4428", + bg: "#12261e", }, error: { text: "#f04438", main: "#d32f2f", - light: "#1e1e1e", - bg: "#27272a", + light: "#542426", + bg: "#301a1f", dark: "#932020", border: "#f04438", }, warning: { text: "#e88c30", main: "#FF9F00", - light: "#27272a", - bg: "#1E1E1E", + light: "#272115", + bg: "#624711", border: "#e88c30", }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, From c4dafae21e5f626b42e694519bf8e0f922e5c80a Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 2 Sep 2024 19:07:43 -0700 Subject: [PATCH 29/96] Give props more meaningful names. --- Client/src/Pages/Monitors/Home/monitorData.jsx | 10 +++++----- Client/src/Utils/Theme/darkTheme.js | 8 ++++---- Client/src/Utils/Theme/lightTheme.js | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 4220fc2c5..643327e7e 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -40,7 +40,7 @@ export const buildData = (monitors, isAdmin, navigate) => { data.rows = monitors.map((monitor, idx) => { let uptimePercentage = ""; - let percentageColor = theme.palette.percentage.green; + let percentageColor = theme.palette.percentage.q4; // Determine uptime percentage and color based on the monitor's uptimePercentage value if (monitor.uptimePercentage !== undefined) { @@ -51,12 +51,12 @@ export const buildData = (monitors, isAdmin, navigate) => { percentageColor = monitor.uptimePercentage < 0.25 - ? theme.palette.percentage.red + ? theme.palette.percentage.q1 : monitor.uptimePercentage < 0.5 - ? theme.palette.percentage.orange + ? theme.palette.percentage.q2 : monitor.uptimePercentage < 0.75 - ? theme.palette.percentage.yellow - : theme.palette.percentage.green; + ? theme.palette.percentage.q3 + : theme.palette.percentage.q4; } const params = { diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 3b77966cd..f680533f8 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -58,10 +58,10 @@ const darkTheme = createTheme({ border: "#e88c30", }, percentage: { - red: "#d32f2f", - orange: "#e88c30", - yellow: "#ffd600", - green: "#079455", + q1: "#d32f2f", + q2: "#e88c30", + q3: "#ffd600", + q4: "#079455", }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index 67c2d72e5..cc8b2f226 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -55,10 +55,10 @@ const lightTheme = createTheme({ border: "#fec84b", }, percentage: { - red: "#d32f2f", - orange: "#ec8013", - yellow: "#ffb800", - green: "#079455", + q1: "#d32f2f", + q2: "#ec8013", + q3: "#ffb800", + q4: "#079455", }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, From ed99748718545b457ed8d49f63ee87bda34ec4a0 Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 2 Sep 2024 19:14:43 -0700 Subject: [PATCH 30/96] Name props according to purpose rather than value. --- Client/src/Pages/Monitors/Home/monitorData.jsx | 10 +++++----- Client/src/Utils/Theme/darkTheme.js | 8 ++++---- Client/src/Utils/Theme/lightTheme.js | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 643327e7e..1942e5a9c 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -40,7 +40,7 @@ export const buildData = (monitors, isAdmin, navigate) => { data.rows = monitors.map((monitor, idx) => { let uptimePercentage = ""; - let percentageColor = theme.palette.percentage.q4; + let percentageColor = theme.palette.percentage.uptimeExcellent; // Determine uptime percentage and color based on the monitor's uptimePercentage value if (monitor.uptimePercentage !== undefined) { @@ -51,12 +51,12 @@ export const buildData = (monitors, isAdmin, navigate) => { percentageColor = monitor.uptimePercentage < 0.25 - ? theme.palette.percentage.q1 + ? theme.palette.percentage.uptimePoor : monitor.uptimePercentage < 0.5 - ? theme.palette.percentage.q2 + ? theme.palette.percentage.uptimeFair : monitor.uptimePercentage < 0.75 - ? theme.palette.percentage.q3 - : theme.palette.percentage.q4; + ? theme.palette.percentage.uptimeGood + : theme.palette.percentage.uptimeExcellent; } const params = { diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index f680533f8..008de87db 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -58,10 +58,10 @@ const darkTheme = createTheme({ border: "#e88c30", }, percentage: { - q1: "#d32f2f", - q2: "#e88c30", - q3: "#ffd600", - q4: "#079455", + uptimePoor: "#d32f2f", + uptimeFair: "#e88c30", + uptimeGood: "#ffd600", + uptimeExcellent: "#079455", }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index cc8b2f226..6688a0ca4 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -55,10 +55,10 @@ const lightTheme = createTheme({ border: "#fec84b", }, percentage: { - q1: "#d32f2f", - q2: "#ec8013", - q3: "#ffb800", - q4: "#079455", + uptimePoor: "#d32f2f", + uptimeFair: "#ec8013", + uptimeGood: "#ffb800", + uptimeExcellent: "#079455", }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, From d77be4ce7413054d6cfead0b2db62a9d997e65f0 Mon Sep 17 00:00:00 2001 From: M M Date: Tue, 3 Sep 2024 07:41:42 -0700 Subject: [PATCH 31/96] Delete unused component. --- .../Charts/ResponseTimeChart/index.css | 0 .../Charts/ResponseTimeChart/index.jsx | 43 ------------------- 2 files changed, 43 deletions(-) delete mode 100644 Client/src/Components/Charts/ResponseTimeChart/index.css delete mode 100644 Client/src/Components/Charts/ResponseTimeChart/index.jsx diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.css b/Client/src/Components/Charts/ResponseTimeChart/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.jsx b/Client/src/Components/Charts/ResponseTimeChart/index.jsx deleted file mode 100644 index fd6c0f41e..000000000 --- a/Client/src/Components/Charts/ResponseTimeChart/index.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from "prop-types"; -import { Stack } from "@mui/material"; -import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts"; -import "./index.css"; - -const ResponseTimeChart = ({ checks = [] }) => { - return ( - - - - - {checks.map((check, index) => ( - - ))} - - - - - ); -}; - -ResponseTimeChart.propTypes = { - checks: PropTypes.array, -}; -export default ResponseTimeChart; From 1bde8fdb868924646b636e0647aa047dccb1838d Mon Sep 17 00:00:00 2001 From: Alexander Holliday Date: Tue, 3 Sep 2024 09:30:43 -0700 Subject: [PATCH 32/96] Revert "Add indeterminate state to monitors, resolves #765" --- Client/src/Components/Label/index.jsx | 8 +-- .../src/Pages/Monitors/Home/monitorData.jsx | 9 +++ Server/controllers/monitorController.js | 1 - Server/models/Monitor.js | 4 +- Server/service/jobQueue.js | 1 + Server/service/networkService.js | 64 +++++++------------ 6 files changed, 37 insertions(+), 50 deletions(-) diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 96468ffd6..0799a87d7 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -126,7 +126,6 @@ ColoredLabel.propTypes = { */ const StatusLabel = ({ status, text, customStyles }) => { - console.log(status); const theme = useTheme(); const colors = { up: { @@ -139,11 +138,6 @@ const StatusLabel = ({ status, text, customStyles }) => { bgColor: theme.palette.error.bg, borderColor: theme.palette.error.light, }, - unknown: { - dotColor: theme.palette.unresolved.main, - bgColor: theme.palette.unresolved.bg, - borderColor: theme.palette.unresolved.light, - }, "cannot resolve": { dotColor: theme.palette.unresolved.main, bgColor: theme.palette.unresolved.bg, @@ -176,7 +170,7 @@ const StatusLabel = ({ status, text, customStyles }) => { }; StatusLabel.propTypes = { - status: PropTypes.oneOf(["up", "down", "unknown", "cannot resolve"]), + status: PropTypes.oneOf(["up", "down", "cannot resolve"]), text: PropTypes.string, customStyles: PropTypes.object, }; diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index d84496923..1942e5a9c 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -48,6 +48,15 @@ export const buildData = (monitors, isAdmin, navigate) => { monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.percentage.uptimePoor + : monitor.uptimePercentage < 0.5 + ? theme.palette.percentage.uptimeFair + : monitor.uptimePercentage < 0.75 + ? theme.palette.percentage.uptimeGood + : theme.palette.percentage.uptimeExcellent; } const params = { diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 702b28be4..f33ddcf10 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -359,7 +359,6 @@ const pauseMonitor = async (req, res, next) => { await req.jobQueue.addJob(monitor._id, monitor); } monitor.isActive = !monitor.isActive; - monitor.status = undefined; monitor.save(); return res.status(200).json({ success: true, diff --git a/Server/models/Monitor.js b/Server/models/Monitor.js index d91275e23..fdc147d1b 100644 --- a/Server/models/Monitor.js +++ b/Server/models/Monitor.js @@ -24,7 +24,7 @@ const MonitorSchema = mongoose.Schema( }, status: { type: Boolean, - default: undefined, + default: true, }, type: { type: String, @@ -46,7 +46,7 @@ const MonitorSchema = mongoose.Schema( }, uptimePercentage: { type: Number, - default: undefined, + default: undefined, }, notifications: [ { diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 5248c88fa..ed15e0f94 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -238,6 +238,7 @@ class JobQueue { console.log("Adding job", payload.url); // Execute job immediately await this.queue.add(jobName, payload); + await this.queue.add(jobName, payload, { repeat: { every: payload.interval, diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 863349dbf..f5c333a60 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -15,59 +15,43 @@ class NetworkService { this.NETWORK_ERROR = 5000; } - async handleNotification(monitor, isAlive) { - try { - let template = - isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; - let status = isAlive === true ? "up" : "down"; - - const notifications = await this.db.getNotificationsByMonitorId( - monitor._id - ); - for (const notification of notifications) { - if (notification.type === "email") { - await this.emailService.buildAndSendEmail( - template, - { monitorName: monitor.name, monitorUrl: monitor.url }, - notification.address, - `Monitor ${monitor.name} is ${status}` - ); - } - } - } catch (error) { - logger.error(error.message, { - method: "handleNotification", - service: this.SERVICE_NAME, - monitorId: monitor._id, - }); - } - } - - async handleStatusUpdate(job, isAlive) { + async handleNotification(job, isAlive) { try { const { _id } = job.data; const monitor = await this.db.getMonitorById(_id); - if (monitor === null || monitor === undefined) { logger.error(`Null Monitor: ${_id}`, { - method: "handleStatusUpdate", + method: "handleNotification", service: this.SERVICE_NAME, jobId: job.id, }); return; } - if (monitor.status === undefined || monitor.status !== isAlive) { - const oldStatus = monitor.status; - monitor.status = isAlive; + + // If monitor status changes, update monitor status and send notification + if (monitor.status !== isAlive) { + monitor.status = !monitor.status; await monitor.save(); - if (oldStatus !== undefined && oldStatus !== isAlive) { - this.handleNotification(monitor, isAlive); + let template = + isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; + let status = isAlive === true ? "up" : "down"; + + const notifications = await this.db.getNotificationsByMonitorId(_id); + for (const notification of notifications) { + if (notification.type === "email") { + await this.emailService.buildAndSendEmail( + template, + { monitorName: monitor.name, monitorUrl: monitor.url }, + notification.address, + `Monitor ${monitor.name} is ${status}` + ); + } } } } catch (error) { logger.error(error.message, { - method: "handleStatusUpdate", + method: "handleNotification", service: this.SERVICE_NAME, jobId: job.id, }); @@ -129,7 +113,7 @@ class NetworkService { }; return await this.logAndStoreCheck(checkData, this.db.createCheck); } finally { - this.handleStatusUpdate(job, isAlive); + this.handleNotification(job, isAlive); } } @@ -177,7 +161,7 @@ class NetworkService { return await this.logAndStoreCheck(checkData, this.db.createCheck); } finally { - this.handleStatusUpdate(job, isAlive); + this.handleNotification(job, isAlive); } } @@ -257,7 +241,7 @@ class NetworkService { }; this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); } finally { - this.handleStatusUpdate(job, isAlive); + this.handleNotification(job, isAlive); } } From 899111e48bfa9c245da41968353cef5414862f16 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 10:23:26 -0700 Subject: [PATCH 33/96] Add loading button to createPageSpeed --- Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx index 00c02a362..4c8204153 100644 --- a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx @@ -1,4 +1,6 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton"; + import { useState } from "react"; import { useTheme } from "@emotion/react"; import { useDispatch, useSelector } from "react-redux"; @@ -12,11 +14,11 @@ import { createPageSpeed } from "../../../Features/PageSpeedMonitor/pageSpeedMon import Breadcrumbs from "../../../Components/Breadcrumbs"; import "./index.css"; import { logger } from "../../../Utils/Logger"; - const CreatePageSpeed = () => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); + const { isLoading } = useSelector((state) => state.pageSpeedMonitors); const MS_PER_MINUTE = 60000; const { user, authToken } = useSelector((state) => state.auth); @@ -226,7 +228,8 @@ const CreatePageSpeed = () => { - + From e00938e12a832669a3d25757065eca7fb2bb056d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 10:34:24 -0700 Subject: [PATCH 34/96] Add loading state to team panel --- .../TabPanels/Account/TeamPanel.jsx | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 8c4630b19..9809ac710 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -1,25 +1,13 @@ import { useTheme } from "@emotion/react"; import TabPanel from "@mui/lab/TabPanel"; -import { - Box, - Button, - ButtonGroup, - Divider, - IconButton, - Modal, - Stack, - TextField, - Typography, -} from "@mui/material"; +import { Button, ButtonGroup, Modal, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; -import EditSvg from "../../../assets/icons/edit.svg?react"; import Field from "../../Inputs/Field"; import { credentials } from "../../../Validation/validation"; import { networkService } from "../../../main"; import { createToast } from "../../../Utils/toastUtils"; import { useSelector } from "react-redux"; import BasicTable from "../../BasicTable"; -import Remove from "../../../assets/icons/trash-bin.svg?react"; import Select from "../../Inputs/Select"; import LoadingButton from "@mui/lab/LoadingButton"; @@ -54,6 +42,7 @@ const TeamPanel = () => { const [members, setMembers] = useState([]); const [filter, setFilter] = useState("all"); const [errors, setErrors] = useState({}); + const [isSendingInvite, setIsSendingInvite] = useState(false); useEffect(() => { const fetchTeam = async () => { @@ -173,6 +162,7 @@ const TeamPanel = () => { }; const handleInviteMember = async () => { + setIsSendingInvite(true); if (!toInvite.role.includes("user") || !toInvite.role.includes("admin")) setToInvite((prev) => ({ ...prev, role: ["user"] })); @@ -185,24 +175,28 @@ const TeamPanel = () => { if (error) { setErrors((prev) => ({ ...prev, email: error.details[0].message })); - } else - try { - await networkService.requestInvitationToken( - authToken, - toInvite.email, - toInvite.role - ); + return; + } - closeInviteModal(); - createToast({ - body: "Member invited. They will receive an email with details on how to create their account.", - }); - } catch (error) { - createToast({ - body: error.message || "Unknown error.", - }); - } + try { + await networkService.requestInvitationToken( + authToken, + toInvite.email, + toInvite.role + ); + closeInviteModal(); + createToast({ + body: "Member invited. They will receive an email with details on how to create their account.", + }); + } catch (error) { + createToast({ + body: error.message || "Unknown error.", + }); + } finally { + setIsSendingInvite(false); + } }; + const closeInviteModal = () => { setIsOpen(false); setToInvite({ email: "", role: ["0"] }); @@ -307,13 +301,14 @@ const TeamPanel = () => { - + { mt={theme.spacing(8)} justifyContent="flex-end" > - + Send invite From 560ad118e7612a43ad906bd6f5a2a17cfe4aeb5e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 10:37:26 -0700 Subject: [PATCH 35/96] Add loading button to settings page --- Client/src/Pages/Settings/index.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index fc615d848..d1f15ea8c 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -16,6 +16,9 @@ const Settings = ({ isAdmin }) => { const { isLoading } = useSelector((state) => state.uptimeMonitors); const dispatch = useDispatch(); + + // TODO Handle saving + const handleClearStats = async () => { try { const action = await dispatch( @@ -156,13 +159,14 @@ const Settings = ({ isAdmin }) => { - + From 0d8f3f3666464eec17965787bb2b203a924100d0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 10:38:45 -0700 Subject: [PATCH 36/96] Add loading button to config page --- Client/src/Pages/Monitors/Configure/index.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 483a2fd79..6e3caeff7 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -275,14 +275,15 @@ const Configure = () => { )} - + From cfc2700d19d1ee52d84f89b3ca14e4ddbbb360eb Mon Sep 17 00:00:00 2001 From: Mohammad Khalilzadeh <140876993+MuhammadKhalilzadeh@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:42:31 +0330 Subject: [PATCH 37/96] Autocomplete (#774) * autocomplete implementation * autocomplete implementation * autocomplete implementation * fetched upstream * fixes after fetched upstream * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete * autocomplete --- .../Components/Inputs/Autocomplete/index.jsx | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Client/src/Components/Inputs/Autocomplete/index.jsx diff --git a/Client/src/Components/Inputs/Autocomplete/index.jsx b/Client/src/Components/Inputs/Autocomplete/index.jsx new file mode 100644 index 000000000..5256efccc --- /dev/null +++ b/Client/src/Components/Inputs/Autocomplete/index.jsx @@ -0,0 +1,116 @@ +import { Autocomplete, TextField } from "@mui/material"; +import React from "react"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; + +/** + * @example + * + * const options = [ + * { _id: "66d6119ef959cbc681e034f0", name: "Googler" }, + * { _id: "66d6119ef959cbc681e034f0", name: "CNN" }, + * { _id: "66d61a1bf959cbc681e0353f", name: "X Corp." }, + * ]; + * + * + * + */ + +/** + * AutoCompleteField component. + * + * @component + * @param {Object} props - The component props. + * @param {string} props.id - The ID of the autocomplete field. + * @param {string} props.type - The type of the input field (text or number). + * @param {Array} props.options - The options for the autocomplete field. + * @param {string} props.placeholder - The placeholder text for the input field. + * @param {boolean} props.disabled - Indicates if the field is disabled. + * @returns {JSX.Element} The AutoCompleteField component. + */ + +const AutoCompleteField = ({ + id, + type, + options, + placeholder = "Type to search", + disabled, +}) => { + const [value, setValue] = React.useState(); + const [inputValue, setInputValue] = React.useState(""); + const theme = useTheme(); + + return ( + { + setValue(newValue); + }} + inputValue={inputValue} + onInputChange={(event, newInputValue) => { + setInputValue(newInputValue); + }} + options={options} + getOptionLabel={(option) => option.name} + renderInput={(params) => ( + + )} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( +
  • +
    {{option.name}}
    +
  • + ); + }} + slotProps={{ + popper: { + sx: { + "& ul": { p: 0 }, + "& li": { borderRadius: theme.shape.borderRadius }, + }, + }, + paper: { + sx: { + p: 2, + fontSize: 13, + }, + }, + }} + /> + ); +}; + +AutoCompleteField.displayName = "AutoCompleteField"; + +AutoCompleteField.propTypes = { + id: PropTypes.string, + type: PropTypes.oneOf(["text", "number"]), + options: PropTypes.array, + placeholder: PropTypes.string, + disabled: PropTypes.bool, + setWidth: PropTypes.string, +}; + +export default AutoCompleteField; From 789e6e91c472a93c9feda5b0961c27b26b9df995 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 12:07:59 -0700 Subject: [PATCH 38/96] add thunks for getting pagespeed monitor --- .../PageSpeedMonitor/pageSpeedMonitorSlice.js | 78 +++++++++- .../UptimeMonitors/uptimeMonitorsSlice.js | 2 +- .../src/Pages/PageSpeed/Configure/index.jsx | 133 +++++++++--------- .../Pages/PageSpeed/Configure/skeleton.jsx | 51 +++++++ 4 files changed, 191 insertions(+), 73 deletions(-) create mode 100644 Client/src/Pages/PageSpeed/Configure/skeleton.jsx diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index e32d673cc..317ab1320 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -28,6 +28,26 @@ export const createPageSpeed = createAsyncThunk( } ); +export const getPagespeedMonitorById = createAsyncThunk( + "monitors/getMonitorById", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.getMonitorByid(authToken, monitorId); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + export const getPageSpeedByTeamId = createAsyncThunk( "pageSpeedMonitors/getPageSpeedByTeamId", async (token, thunkApi) => { @@ -109,6 +129,25 @@ export const deletePageSpeed = createAsyncThunk( } } ); +export const pausePageSpeed = createAsyncThunk( + "pageSpeedMonitors/pausePageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.pauseMonitorById(authToken, monitorId); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); const pageSpeedMonitorSlice = createSlice({ name: "pageSpeedMonitor", @@ -124,7 +163,7 @@ const pageSpeedMonitorSlice = createSlice({ extraReducers: (builder) => { builder // ***************************************************** - // Monitors by userId + // Monitors by teamId // ***************************************************** .addCase(getPageSpeedByTeamId.pending, (state) => { @@ -143,6 +182,23 @@ const pageSpeedMonitorSlice = createSlice({ : "Getting page speed monitors failed"; }) + // ***************************************************** + .addCase(getPagespeedMonitorById.pending, (state) => { + state.isLoading = true; + }) + .addCase(getPagespeedMonitorById.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(getPagespeedMonitorById.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to get pagespeed monitor"; + }) + // ***************************************************** // Create Monitor // ***************************************************** @@ -163,7 +219,7 @@ const pageSpeedMonitorSlice = createSlice({ }) // ***************************************************** - // Create Monitor + // Update Monitor // ***************************************************** .addCase(updatePageSpeed.pending, (state) => { state.isLoading = true; @@ -198,6 +254,24 @@ const pageSpeedMonitorSlice = createSlice({ state.msg = action.payload ? action.payload.msg : "Failed to delete page speed monitor"; + }) + // ***************************************************** + // Pause Monitor + // ***************************************************** + .addCase(pausePageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(pausePageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(pausePageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to pause page speed monitor"; }); }, }); diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 2c381812c..895bd1493 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -236,7 +236,7 @@ const uptimeMonitorsSlice = createSlice({ state.success = false; state.msg = action.payload ? action.payload.msg - : "Failed to pause uptime monitor"; + : "Failed to get uptime monitor"; }) // ***************************************************** // update Monitor diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx index 92dd5bb36..005583f2c 100644 --- a/Client/src/Pages/PageSpeed/Configure/index.jsx +++ b/Client/src/Pages/PageSpeed/Configure/index.jsx @@ -1,12 +1,14 @@ import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; -import { Box, Button, Modal, Skeleton, Stack, Typography } from "@mui/material"; +import { Box, Button, Modal, Stack, Typography } from "@mui/material"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router"; import { deletePageSpeed, + getPagespeedMonitorById, getPageSpeedByTeamId, updatePageSpeed, + pausePageSpeed, } from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice"; import { monitorValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; @@ -17,63 +19,18 @@ import Checkbox from "../../../Components/Inputs/Checkbox"; import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; - +import LoadingButton from "@mui/lab/LoadingButton"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import SkeletonLayout from "./skeleton"; import "./index.css"; -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - ); -}; - const PageSpeedConfigure = () => { const theme = useTheme(); const navigate = useNavigate(); const dispatch = useDispatch(); const MS_PER_MINUTE = 60000; const { authToken } = useSelector((state) => state.auth); - const { monitors } = useSelector((state) => state.pageSpeedMonitors); + const { isLoading } = useSelector((state) => state.pageSpeedMonitors); const { monitorId } = useParams(); const [monitor, setMonitor] = useState({}); const [errors, setErrors] = useState({}); @@ -89,15 +46,25 @@ const PageSpeedConfigure = () => { ]; useEffect(() => { - const data = monitors.find((monitor) => monitor._id === monitorId); - if (!data) { - logger.error("Error fetching pagespeed monitor of id: " + monitorId); - navigate("/not-found", { replace: true }); - } - setMonitor({ - ...data, - }); - }, [monitorId, monitors, navigate]); + const fetchMonitor = async () => { + try { + const action = await dispatch( + getPagespeedMonitorById({ authToken, monitorId }) + ); + + if (getPagespeedMonitorById.fulfilled.match(action)) { + const monitor = action.payload.data; + setMonitor(monitor); + } else if (getPagespeedMonitorById.rejected.match(action)) { + throw new Error(action.error.message); + } + } catch (error) { + logger.error("Error fetching monitor of id: " + monitorId); + navigate("/not-found", { replace: true }); + } + }; + fetchMonitor(); + }, [dispatch, authToken, monitorId, navigate]); const handleChange = (event, id) => { let { value } = event.target; @@ -119,6 +86,21 @@ const PageSpeedConfigure = () => { }); }; + const handlePause = async () => { + try { + const action = await dispatch(pausePageSpeed({ authToken, monitorId })); + if (pausePageSpeed.fulfilled.match(action)) { + const monitor = action.payload.data; + setMonitor(monitor); + } else if (pausePageSpeed.rejected.match(action)) { + throw new Error(action.error.message); + } + } catch (error) { + logger.error("Error pausing monitor: " + monitorId); + createToast({ body: "Failed to pause monitor" }); + } + }; + const handleSave = async (event) => { event.preventDefault(); const action = await dispatch( @@ -143,11 +125,9 @@ const PageSpeedConfigure = () => { } }; - let loading = Object.keys(monitor).length === 0; - return ( - {loading ? ( + {Object.keys(monitor).length === 0 ? ( ) : ( <> @@ -195,7 +175,9 @@ const PageSpeedConfigure = () => { - - + { - + diff --git a/Client/src/Pages/PageSpeed/Configure/skeleton.jsx b/Client/src/Pages/PageSpeed/Configure/skeleton.jsx new file mode 100644 index 000000000..301a00e52 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Configure/skeleton.jsx @@ -0,0 +1,51 @@ +import { Box, Skeleton, Stack } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +/** + * Renders a skeleton layout. + * + * @returns {JSX.Element} + */ +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + <> + + + + + + + + + + + + + + + + + + + + ); +}; + +export default SkeletonLayout; From 88316de306d56e92e961b76f8053700075f5a2f4 Mon Sep 17 00:00:00 2001 From: Alexander Holliday Date: Tue, 3 Sep 2024 12:08:15 -0700 Subject: [PATCH 39/96] add indeterminate state back to monitorData after bad conflict resolution (#785) --- Client/src/Pages/Monitors/Home/monitorData.jsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 1942e5a9c..2351a59e9 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -40,7 +40,7 @@ export const buildData = (monitors, isAdmin, navigate) => { data.rows = monitors.map((monitor, idx) => { let uptimePercentage = ""; - let percentageColor = theme.palette.percentage.uptimeExcellent; + let percentageColor = theme.palette.percentage.uptimeExcellent; // Determine uptime percentage and color based on the monitor's uptimePercentage value if (monitor.uptimePercentage !== undefined) { @@ -63,8 +63,13 @@ export const buildData = (monitors, isAdmin, navigate) => { url: monitor.url, title: monitor.name, percentage: uptimePercentage, - percentageColor, - status: monitor.status === true ? "up" : "down", + percentageColor, + status: + monitor.status === undefined + ? "unknown" + : monitor.status === true + ? "up" + : "down", }; // Reverse checks so the latest check is on the right From bc0df325d1043fc4b2fa0caa414724045a5253e7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 14:39:16 -0700 Subject: [PATCH 40/96] Add route, controller, db operations, and validation for aggregate monitor stats --- Client/src/Utils/NetworkService.js | 2 +- Server/controllers/monitorController.js | 42 +++++++++++- Server/db/mongo/MongoDB.js | 2 + Server/db/mongo/modules/monitorModule.js | 83 ++++++++++++++++++++++++ Server/routes/monitorRoute.js | 1 + Server/validation/joi.js | 9 +++ 6 files changed, 137 insertions(+), 2 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 31a23b5b1..6dcde82bb 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -116,7 +116,7 @@ class NetworkService { * @param {string} [sortOrder] - The order in which to sort the retrieved statistics. * @param {number} [limit] - The maximum number of statistics to retrieve. * @param {string} [dateRange] - The date range for which to retrieve statistics. - * @param {number} [numToDisplay] - The number of statistics to display. + * @param {number} [numToDisplay] - The number of checks to display. * @param {boolean} [normalize] - Whether to normalize the retrieved statistics. * @returns {Promise} The response from the axios GET request. */ diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f33ddcf10..0d831f5b6 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -6,12 +6,13 @@ const { editMonitorBodyValidation, getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, + getMonitorAggregateStatsParamValidation, + getMonitorAggregateStatsQueryValidation, } = require("../validation/joi"); const sslChecker = require("ssl-checker"); const SERVICE_NAME = "monitorController"; const { errorMessages, successMessages } = require("../utils/messages"); -const { runInNewContext } = require("vm"); /** * Returns all monitors @@ -35,6 +36,44 @@ const getAllMonitors = async (req, res, next) => { } }; +/** + * Returns agregate stats for a monitor + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ + +const getMonitorAggregateStats = async (req, res, next) => { + try { + await getMonitorAggregateStatsParamValidation.validateAsync(req.params); + await getMonitorAggregateStatsQueryValidation.validateAsync(req.query); + } catch (error) { + error.status = 422; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { + const { monitorId } = req.params; + const dateRange = req.query.dateRange; + const aggregateStats = await req.db.getMonitorAggregateStats( + monitorId, + dateRange + ); + return res.json({ + success: true, + msg: successMessages.MONTIOR_STATS_BY_ID, + data: aggregateStats, + }); + } catch (error) { + next(error); + } +}; + /** * Returns monitor stats for monitor with matching ID * @async @@ -376,6 +415,7 @@ const pauseMonitor = async (req, res, next) => { module.exports = { getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorCertificate, getMonitorById, diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 912bfb438..6f959f758 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -64,6 +64,7 @@ const { const { getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, @@ -144,6 +145,7 @@ module.exports = { resetPassword, checkSuperadmin, getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 79e8917ea..0bd054000 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -143,6 +143,88 @@ const getStatusBarValues = (monitor, checks) => { } return statusBarValues.reverse(); }; + +/** + * Get aggregate monitor stats for charts + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const getMonitorAggregateStats = async (monitorId, dateRange) => { + const startDates = { + 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)), + }; + + try { + const startDate = startDates[dateRange]; + if (!startDate) { + throw new Error("Invalid date range specified"); + } + const endDate = new Date(); + + const checks = await Check.find({ + monitorId: monitorId, + createdAt: { + $gte: startDate, + $lte: endDate, + }, + }); + + let groupedChecks; + // Group checks by hour if range is day + if (dateRange === "day") { + groupedChecks = checks.reduce((acc, check) => { + const hour = new Date(check.createdAt).getHours(); + if (!acc[hour]) { + acc[hour] = { hour, checks: [] }; + } + acc[hour].checks.push(check); + return acc; + }, {}); + } + + // Group checks by day if range is week or month + else { + groupedChecks = checks.reduce((acc, check) => { + const day = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[day]) { + acc[day] = { day, checks: [] }; + } + acc[day].checks.push(check); + return acc; + }, {}); + } + + // Map grouped checks to stats + stats = Object.values(groupedChecks).map((group) => { + const totalChecks = group.checks.length; + const totalIncidents = group.checks.filter( + (check) => check.status === false + ).length; + const avgResponseTime = + group.checks.reduce((sum, check) => sum + check.responseTime, 0) / + totalChecks; + + return { + hour: group.hour, + day: group.day, + totalChecks, + totalIncidents, + avgResponseTime: `${avgResponseTime.toFixed(2)}ms`, + }; + }); + + return stats; + } catch (error) { + throw error; + } +}; + /** * Get stats by monitor ID * @async @@ -452,6 +534,7 @@ const editMonitor = async (candidateId, candidateMonitor) => { module.exports = { getAllMonitors, + getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index ee1c08488..7831f42f7 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -3,6 +3,7 @@ const monitorController = require("../controllers/monitorController"); const { isAllowed } = require("../middleware/isAllowed"); router.get("/", monitorController.getAllMonitors); +router.get("/aggregate/:monitorId", monitorController.getMonitorAggregateStats); router.get("/stats/:monitorId", monitorController.getMonitorStatsById); router.get("/certificate/:monitorId", monitorController.getMonitorCertificate); router.get("/:monitorId", monitorController.getMonitorById); diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ccc8da9f6..629deddf5 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -201,6 +201,13 @@ const pauseMonitorParamValidation = joi.object({ monitorId: joi.string().required(), }); +const getMonitorAggregateStatsParamValidation = joi.object({ + monitorId: joi.string().required(), +}); +const getMonitorAggregateStatsQueryValidation = joi.object({ + dateRange: joi.string().valid("day", "week", "month"), +}); + //**************************************** // Alerts //**************************************** @@ -357,6 +364,8 @@ module.exports = { getMonitorByIdQueryValidation, getMonitorsByTeamIdValidation, getMonitorsByTeamIdQueryValidation, + getMonitorAggregateStatsParamValidation, + getMonitorAggregateStatsQueryValidation, editMonitorBodyValidation, pauseMonitorParamValidation, editUserParamValidation, From 99b4da347f0bb51ed5781699ca3f6c64336fd38d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 14:47:37 -0700 Subject: [PATCH 41/96] remove space --- Server/db/mongo/modules/monitorModule.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 0bd054000..11b4b49a3 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -155,7 +155,6 @@ const getStatusBarValues = (monitor, checks) => { const getMonitorAggregateStats = async (monitorId, dateRange) => { const startDates = { 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)), }; From 64fe1a3af58579913f08126a1015d0a326a17248 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 14:52:00 -0700 Subject: [PATCH 42/96] Return raw value instead of formatted string --- Server/db/mongo/modules/monitorModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 11b4b49a3..e0718a0bb 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -214,7 +214,7 @@ const getMonitorAggregateStats = async (monitorId, dateRange) => { day: group.day, totalChecks, totalIncidents, - avgResponseTime: `${avgResponseTime.toFixed(2)}ms`, + avgResponseTime, }; }); From a6c3c56d0bca064eab89255ce28bbf19428fed0c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:00:30 -0700 Subject: [PATCH 43/96] add getMonitorStatsByIdValidation --- Server/validation/joi.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ccc8da9f6..7e16fa21f 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -1,4 +1,5 @@ const joi = require("joi"); +const { normalize } = require("path"); //**************************************** // Custom Validators @@ -177,6 +178,18 @@ const getMonitorsByTeamIdQueryValidation = joi.object({ ), }); +getMonitorStatsByIdParamValidation = joi.object({ + monitorId: joi.string().required(), +}); +getMonitorStatsByIdQueryValidation = joi.object({ + status: joi.string(), + limit: joi.number(), + sortOrder: joi.string().valid("asc", "desc"), + dateRange: joi.string().valid("day", "week", "month"), + numToDisplay: joi.number(), + normalize: joi.boolean(), +}); + const createMonitorBodyValidation = joi.object({ _id: joi.string(), userId: joi.string().required(), From 34ce3af412d483b04ddfedd367984fa5ec4142fe Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:04:29 -0700 Subject: [PATCH 44/96] Add 7 days uptime percentage stat --- Server/db/mongo/modules/monitorModule.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index e0718a0bb..b913519b6 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -281,6 +281,10 @@ const getMonitorStatsById = async (req) => { ...checksQuery, createdAt: { $gte: filterLookup.day }, }; + const checksQuery7Days = { + ...checksQuery, + createdAt: { $gte: filterLookup.week }, + }; const checksQuery30Days = { ...checksQuery, createdAt: { $gte: filterLookup.month }, @@ -290,15 +294,18 @@ const getMonitorStatsById = async (req) => { createdAt: { $gte: filterLookup.hour }, }; - const [checks24Hours, checks30Days, checks60Mins] = await Promise.all([ - model.find(checksQuery24Hours).sort({ createdAt: sortOrder }), - model.find(checksQuery30Days).sort({ createdAt: sortOrder }), - model.find(checksQuery60Mins).sort({ createdAt: sortOrder }), - ]); + const [checks24Hours, checks7Days, checks30Days, checks60Mins] = + await Promise.all([ + model.find(checksQuery24Hours).sort({ createdAt: sortOrder }), + model.find(checksQuery7Days).sort({ createdAt: sortOrder }), + model.find(checksQuery30Days).sort({ createdAt: sortOrder }), + model.find(checksQuery60Mins).sort({ createdAt: sortOrder }), + ]); // HTTP/PING Specific stats monitorStats.avgResponseTime24hours = getAverageResponseTime24Hours(checks24Hours); + monitorStats.uptime7Days = getUptimePercentage(checks7Days); monitorStats.uptime24Hours = getUptimePercentage(checks24Hours); monitorStats.uptime30Days = getUptimePercentage(checks30Days); monitorStats.statusBar = getStatusBarValues(monitor, checks60Mins); From cece932e451cc199e1a117bf3ecc4390afd04451 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:07:11 -0700 Subject: [PATCH 45/96] Implement validation --- Server/controllers/monitorController.js | 4 ++++ Server/validation/joi.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f33ddcf10..c0243adac 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -6,6 +6,8 @@ const { editMonitorBodyValidation, getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, + getMonitorStatsByIdParamValidation, + getMonitorStatsByIdQueryValidation, } = require("../validation/joi"); const sslChecker = require("ssl-checker"); @@ -46,6 +48,8 @@ const getAllMonitors = async (req, res, next) => { const getMonitorStatsById = async (req, res, next) => { try { //Validation + await getMonitorStatsByIdParamValidation.validateAsync(req.params); + await getMonitorStatsByIdQueryValidation.validateAsync(req.query); } catch (error) { error.status = 422; error.message = diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 7e16fa21f..921831b5e 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -370,6 +370,8 @@ module.exports = { getMonitorByIdQueryValidation, getMonitorsByTeamIdValidation, getMonitorsByTeamIdQueryValidation, + getMonitorStatsByIdParamValidation, + getMonitorStatsByIdQueryValidation, editMonitorBodyValidation, pauseMonitorParamValidation, editUserParamValidation, From 8bac81fb09397f0f77f8b73203cb293b17ad3aaf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:11:17 -0700 Subject: [PATCH 46/96] Add validation for certificate request --- Server/controllers/monitorController.js | 5 ++--- Server/validation/joi.js | 9 +++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index c0243adac..9576f3064 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -8,12 +8,12 @@ const { pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, + getCertificateParamValidation, } = require("../validation/joi"); const sslChecker = require("ssl-checker"); const SERVICE_NAME = "monitorController"; const { errorMessages, successMessages } = require("../utils/messages"); -const { runInNewContext } = require("vm"); /** * Returns all monitors @@ -47,7 +47,6 @@ const getAllMonitors = async (req, res, next) => { */ const getMonitorStatsById = async (req, res, next) => { try { - //Validation await getMonitorStatsByIdParamValidation.validateAsync(req.params); await getMonitorStatsByIdQueryValidation.validateAsync(req.query); } catch (error) { @@ -73,7 +72,7 @@ const getMonitorStatsById = async (req, res, next) => { const getMonitorCertificate = async (req, res, next) => { try { - //validation + await getCertificateParamValidation.validateAsync(req.params); } catch (error) { error.status = 422; error.message = diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 921831b5e..f6a3aba68 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -178,10 +178,10 @@ const getMonitorsByTeamIdQueryValidation = joi.object({ ), }); -getMonitorStatsByIdParamValidation = joi.object({ +const getMonitorStatsByIdParamValidation = joi.object({ monitorId: joi.string().required(), }); -getMonitorStatsByIdQueryValidation = joi.object({ +const getMonitorStatsByIdQueryValidation = joi.object({ status: joi.string(), limit: joi.number(), sortOrder: joi.string().valid("asc", "desc"), @@ -190,6 +190,10 @@ getMonitorStatsByIdQueryValidation = joi.object({ normalize: joi.boolean(), }); +const getCertificateParamValidation = joi.object({ + monitorId: joi.string().required(), +}); + const createMonitorBodyValidation = joi.object({ _id: joi.string(), userId: joi.string().required(), @@ -372,6 +376,7 @@ module.exports = { getMonitorsByTeamIdQueryValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, + getCertificateParamValidation, editMonitorBodyValidation, pauseMonitorParamValidation, editUserParamValidation, From 5af81c729112d50fce4e8df48022fdc187353fa0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:43:12 -0700 Subject: [PATCH 47/96] restore controller --- Server/controllers/monitorController.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f33ddcf10..702b28be4 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -359,6 +359,7 @@ const pauseMonitor = async (req, res, next) => { await req.jobQueue.addJob(monitor._id, monitor); } monitor.isActive = !monitor.isActive; + monitor.status = undefined; monitor.save(); return res.status(200).json({ success: true, From c490722eae352a2807c04381d4eda2bee0e33273 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:45:11 -0700 Subject: [PATCH 48/96] restore netowrk service --- Server/service/networkService.js | 82 +++++++++++++++++++------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/Server/service/networkService.js b/Server/service/networkService.js index f5c333a60..863349dbf 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -15,44 +15,60 @@ class NetworkService { this.NETWORK_ERROR = 5000; } - async handleNotification(job, isAlive) { + async handleNotification(monitor, isAlive) { try { - const { _id } = job.data; - const monitor = await this.db.getMonitorById(_id); - if (monitor === null || monitor === undefined) { - logger.error(`Null Monitor: ${_id}`, { - method: "handleNotification", - service: this.SERVICE_NAME, - jobId: job.id, - }); - return; - } + let template = + isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; + let status = isAlive === true ? "up" : "down"; - // If monitor status changes, update monitor status and send notification - if (monitor.status !== isAlive) { - monitor.status = !monitor.status; - await monitor.save(); - - let template = - isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; - let status = isAlive === true ? "up" : "down"; - - const notifications = await this.db.getNotificationsByMonitorId(_id); - for (const notification of notifications) { - if (notification.type === "email") { - await this.emailService.buildAndSendEmail( - template, - { monitorName: monitor.name, monitorUrl: monitor.url }, - notification.address, - `Monitor ${monitor.name} is ${status}` - ); - } + const notifications = await this.db.getNotificationsByMonitorId( + monitor._id + ); + for (const notification of notifications) { + if (notification.type === "email") { + await this.emailService.buildAndSendEmail( + template, + { monitorName: monitor.name, monitorUrl: monitor.url }, + notification.address, + `Monitor ${monitor.name} is ${status}` + ); } } } catch (error) { logger.error(error.message, { method: "handleNotification", service: this.SERVICE_NAME, + monitorId: monitor._id, + }); + } + } + + async handleStatusUpdate(job, isAlive) { + try { + const { _id } = job.data; + const monitor = await this.db.getMonitorById(_id); + + if (monitor === null || monitor === undefined) { + logger.error(`Null Monitor: ${_id}`, { + method: "handleStatusUpdate", + service: this.SERVICE_NAME, + jobId: job.id, + }); + return; + } + if (monitor.status === undefined || monitor.status !== isAlive) { + const oldStatus = monitor.status; + monitor.status = isAlive; + await monitor.save(); + + if (oldStatus !== undefined && oldStatus !== isAlive) { + this.handleNotification(monitor, isAlive); + } + } + } catch (error) { + logger.error(error.message, { + method: "handleStatusUpdate", + service: this.SERVICE_NAME, jobId: job.id, }); } @@ -113,7 +129,7 @@ class NetworkService { }; return await this.logAndStoreCheck(checkData, this.db.createCheck); } finally { - this.handleNotification(job, isAlive); + this.handleStatusUpdate(job, isAlive); } } @@ -161,7 +177,7 @@ class NetworkService { return await this.logAndStoreCheck(checkData, this.db.createCheck); } finally { - this.handleNotification(job, isAlive); + this.handleStatusUpdate(job, isAlive); } } @@ -241,7 +257,7 @@ class NetworkService { }; this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); } finally { - this.handleNotification(job, isAlive); + this.handleStatusUpdate(job, isAlive); } } From f52285b32ff13ac099ca2686ab393d321bb68b89 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:46:17 -0700 Subject: [PATCH 49/96] Restore job queue --- Server/service/jobQueue.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index ed15e0f94..5248c88fa 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -238,7 +238,6 @@ class JobQueue { console.log("Adding job", payload.url); // Execute job immediately await this.queue.add(jobName, payload); - await this.queue.add(jobName, payload, { repeat: { every: payload.interval, From 587cd1777ab3a77d6373331afe0ff01d69fccb66 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:47:20 -0700 Subject: [PATCH 50/96] restore label --- Client/src/Components/Label/index.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 0799a87d7..96468ffd6 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -126,6 +126,7 @@ ColoredLabel.propTypes = { */ const StatusLabel = ({ status, text, customStyles }) => { + console.log(status); const theme = useTheme(); const colors = { up: { @@ -138,6 +139,11 @@ const StatusLabel = ({ status, text, customStyles }) => { bgColor: theme.palette.error.bg, borderColor: theme.palette.error.light, }, + unknown: { + dotColor: theme.palette.unresolved.main, + bgColor: theme.palette.unresolved.bg, + borderColor: theme.palette.unresolved.light, + }, "cannot resolve": { dotColor: theme.palette.unresolved.main, bgColor: theme.palette.unresolved.bg, @@ -170,7 +176,7 @@ const StatusLabel = ({ status, text, customStyles }) => { }; StatusLabel.propTypes = { - status: PropTypes.oneOf(["up", "down", "cannot resolve"]), + status: PropTypes.oneOf(["up", "down", "unknown", "cannot resolve"]), text: PropTypes.string, customStyles: PropTypes.object, }; From 9110e0b6cce672bff433adebc38d76aee21fa003 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 16:01:24 -0700 Subject: [PATCH 51/96] restore monitor --- Server/models/Monitor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Server/models/Monitor.js b/Server/models/Monitor.js index fdc147d1b..d91275e23 100644 --- a/Server/models/Monitor.js +++ b/Server/models/Monitor.js @@ -24,7 +24,7 @@ const MonitorSchema = mongoose.Schema( }, status: { type: Boolean, - default: true, + default: undefined, }, type: { type: String, @@ -46,7 +46,7 @@ const MonitorSchema = mongoose.Schema( }, uptimePercentage: { type: Number, - default: undefined, + default: undefined, }, notifications: [ { From 0be386513fdc5cf285a169ed8ee88215df3960bc Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Tue, 3 Sep 2024 20:00:48 -0400 Subject: [PATCH 52/96] Implemented basic charts --- .../Pages/Monitors/Details/Charts/index.jsx | 92 +++++++++++++++++++ Client/src/Pages/Monitors/Details/index.jsx | 67 +++++++++++++- Client/src/Pages/Monitors/Details/styled.jsx | 4 + Client/src/Utils/NetworkService.js | 25 +++++ 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 Client/src/Pages/Monitors/Details/Charts/index.jsx diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx new file mode 100644 index 000000000..54d9716fa --- /dev/null +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -0,0 +1,92 @@ +import { useTheme } from "@emotion/react"; +import { + BarChart, + Bar, + XAxis, + CartesianGrid, + ResponsiveContainer, +} from "recharts"; +import { formatDate } from "../../../../Utils/timeUtils"; + +const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint }) => { + let options = { + month: "short", + year: undefined, + hour: undefined, + minute: undefined, + }; + + return ( + <> + + {formatDate(new Date(firstDataPoint.day), options)} + + + {formatDate(new Date(lastDataPoint.day), options)} + + + ); +}; + +export const UpBarChart = ({ data, dateRange }) => { + const theme = useTheme(); + + return ( + + + + } + /> + + + + ); +}; + +export const DownBarChart = ({ data }) => { + const theme = useTheme(); + + return ( + + + + } + /> + + + + ); +}; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 8aef758a0..6f717143e 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -33,6 +33,7 @@ import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import { StatBox, ChartBox, IconBox } from "./styled"; +import { DownBarChart, UpBarChart } from "./Charts"; import "./index.css"; /** @@ -186,6 +187,55 @@ const DetailsPage = ({ isAdmin }) => { }; let loading = Object.keys(monitor).length === 0; + + const [aggregateStats, setAggregateStats] = useState({}); + useEffect(() => { + const fetchAggregateStats = async () => { + try { + const res = await networkService.getAggregateStatsById( + authToken, + monitorId, + dateRange + ); + + setAggregateStats(res?.data?.data); + } catch (error) { + console.error(error); + } + }; + fetchAggregateStats(); + }, [monitorId, dateRange]); + + const calculateStats = () => { + let totalChecks = 0; + let totalIncidents = 0; + let totalResponseTime = 0; + let count = 0; + + Object.keys(aggregateStats).forEach((entry) => { + totalChecks += aggregateStats[entry].totalChecks; + totalIncidents += aggregateStats[entry].totalIncidents; + totalResponseTime += aggregateStats[entry].totalIncidents; + count++; + }); + + let uptime = + dateRange === "day" + ? monitor.uptime24Hours + : dateRange === "week" + ? monitor.uptime7Days + : dateRange === "month" && monitor.uptime30Days; + + return { + totalChecks: totalChecks, + totalIncidents: totalIncidents, + averageResponseTime: totalResponseTime / count, + uptime: uptime, + }; + }; + + const monitorStats = calculateStats(); + console.log(aggregateStats); return ( {loading ? ( @@ -431,18 +481,22 @@ const DetailsPage = ({ isAdmin }) => { Uptime - + Total Checks - 87 + + {monitorStats.totalChecks} + Uptime Percentage - 98.3% + {monitorStats.uptime} + % + @@ -451,10 +505,13 @@ const DetailsPage = ({ isAdmin }) => { Incidents - + Total Incidents - 0 + + {monitorStats.totalIncidents} + + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 8e1ee9774..1ed6ca8ee 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -36,6 +36,10 @@ export const ChartBox = styled(Box)(({ theme }) => ({ "& > .MuiBox-root:not(:first-of-type)": { marginTop: theme.spacing(8), }, + "& tspan, & text": { + fontSize: 11, + fill: theme.palette.text.tertiary, + }, })); export const IconBox = styled(Box)(({ theme }) => ({ diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 6dcde82bb..acb1134e4 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -146,6 +146,31 @@ class NetworkService { ); } + /** + * ************************************ + * Gets aggregate stats by monitor ID + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor whose certificate expiry is to be retrieved. + * @returns {Promise} The response from the axios GET request. + * + */ + async getAggregateStatsById(authToken, monitorId, dateRange) { + const params = new URLSearchParams(); + if (dateRange) params.append("dateRange", dateRange); + + return this.axiosInstance.get( + `/monitors/aggregate/${monitorId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + } + ); + } + /** * ************************************ * Updates a single monitor From e22e374eef80b1ec4c58088c6917c394d2da8cf8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 20:28:12 -0700 Subject: [PATCH 53/96] Added avgResponseTime to monitor stats, changed date grouping for aggregate stats --- Server/db/mongo/modules/monitorModule.js | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index b913519b6..0f0189694 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -77,11 +77,11 @@ const getLatestResponseTime = (checks) => { }; /** - * Helper function to get average 24h response time + * Helper function to get average response time * @param {Array} checks Array of check objects. * @returns {number} Timestamp of the most recent check. */ -const getAverageResponseTime24Hours = (checks) => { +const getAverageResponseTime = (checks) => { if (!checks || checks.length === 0) { return 0; } @@ -178,11 +178,12 @@ const getMonitorAggregateStats = async (monitorId, dateRange) => { // Group checks by hour if range is day if (dateRange === "day") { groupedChecks = checks.reduce((acc, check) => { - const hour = new Date(check.createdAt).getHours(); - if (!acc[hour]) { - acc[hour] = { hour, checks: [] }; + const time = new Date(check.createdAt); + time.setMinutes(0, 0, 0); + if (!acc[time]) { + acc[time] = { time, checks: [] }; } - acc[hour].checks.push(check); + acc[time].checks.push(check); return acc; }, {}); } @@ -190,11 +191,11 @@ const getMonitorAggregateStats = async (monitorId, dateRange) => { // Group checks by day if range is week or month else { groupedChecks = checks.reduce((acc, check) => { - const day = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part - if (!acc[day]) { - acc[day] = { day, checks: [] }; + const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[time]) { + acc[time] = { time, checks: [] }; } - acc[day].checks.push(check); + acc[time].checks.push(check); return acc; }, {}); } @@ -210,8 +211,7 @@ const getMonitorAggregateStats = async (monitorId, dateRange) => { totalChecks; return { - hour: group.hour, - day: group.day, + time: group.time, totalChecks, totalIncidents, avgResponseTime, @@ -303,11 +303,19 @@ const getMonitorStatsById = async (req) => { ]); // HTTP/PING Specific stats + + //24 hour data monitorStats.avgResponseTime24hours = - getAverageResponseTime24Hours(checks24Hours); - monitorStats.uptime7Days = getUptimePercentage(checks7Days); + getAverageResponseTime(checks24Hours); monitorStats.uptime24Hours = getUptimePercentage(checks24Hours); + + //7 day data + monitorStats.uptime7Days = getUptimePercentage(checks7Days); + monitorStats.avgResponseTime7days = getAverageResponseTime(checks7Days); + // 30 day data monitorStats.uptime30Days = getUptimePercentage(checks30Days); + monitorStats.avgResponseTime30days = getAverageResponseTime(checks30Days); + monitorStats.statusBar = getStatusBarValues(monitor, checks60Mins); } From 02c2689e1e243035b810b5dfe0da98ed2e84aa85 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 21:05:36 -0700 Subject: [PATCH 54/96] Add pending state to monitors and config screens --- Client/src/Components/Label/index.jsx | 10 +- Client/src/Pages/Monitors/Configure/index.jsx | 29 ++--- Client/src/Pages/Monitors/Details/index.jsx | 107 +++--------------- .../src/Pages/Monitors/Details/skeleton.jsx | 73 ++++++++++++ .../src/Pages/Monitors/Home/monitorData.jsx | 2 +- 5 files changed, 112 insertions(+), 109 deletions(-) create mode 100644 Client/src/Pages/Monitors/Details/skeleton.jsx diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 96468ffd6..330c6de0a 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -139,10 +139,10 @@ const StatusLabel = ({ status, text, customStyles }) => { bgColor: theme.palette.error.bg, borderColor: theme.palette.error.light, }, - unknown: { - dotColor: theme.palette.unresolved.main, - bgColor: theme.palette.unresolved.bg, - borderColor: theme.palette.unresolved.light, + pending: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, }, "cannot resolve": { dotColor: theme.palette.unresolved.main, @@ -176,7 +176,7 @@ const StatusLabel = ({ status, text, customStyles }) => { }; StatusLabel.propTypes = { - status: PropTypes.oneOf(["up", "down", "unknown", "cannot resolve"]), + status: PropTypes.oneOf(["up", "down", "pending", "cannot resolve"]), text: PropTypes.string, customStyles: PropTypes.object, }; diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 6e3caeff7..7dd6a1e8f 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -53,7 +53,6 @@ const Configure = () => { const [monitor, setMonitor] = useState({}); const [errors, setErrors] = useState({}); const { monitorId } = useParams(); - console.log(monitor); const idMap = { "monitor-url": "url", "monitor-name": "name", @@ -190,6 +189,18 @@ const Configure = () => { const parsedUrl = parseUrl(monitor?.url); const protocol = parsedUrl?.protocol?.replace(":", "") || ""; + const statusColor = { + true: theme.palette.success.main, + false: theme.palette.error.main, + undefined: theme.palette.warning.main, + }; + + const statusMsg = { + true: "Your site is up.", + false: "Your site is down.", + undefined: "Pending...", + }; + return ( {Object.keys(monitor).length === 0 ? ( @@ -211,13 +222,7 @@ const Configure = () => { flex={1} > - + {parsedUrl?.host ? ( { - Your site is {monitor?.status ? "up" : "down"}. + {statusMsg[monitor?.status ?? undefined]} { @@ -71,76 +65,6 @@ StatBox.propTypes = { value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - /** * Details page component displaying monitor details and related information. * @component @@ -195,6 +119,19 @@ const DetailsPage = ({ isAdmin }) => { }, [authToken, monitorId, monitor]); let loading = Object.keys(monitor).length === 0; + + const statusColor = { + true: theme.palette.success.main, + false: theme.palette.error.main, + undefined: theme.palette.warning.main, + }; + + const statusMsg = { + true: "Your site is up.", + false: "Your site is down.", + undefined: "Pending...", + }; + return ( {loading ? ( @@ -209,13 +146,7 @@ const DetailsPage = ({ isAdmin }) => { /> - + { - Your site is {monitor?.status ? "up" : "down"}. + {statusMsg[monitor?.status ?? undefined]} {" "} Checking every {formatDurationRounded(monitor?.interval)}. diff --git a/Client/src/Pages/Monitors/Details/skeleton.jsx b/Client/src/Pages/Monitors/Details/skeleton.jsx new file mode 100644 index 000000000..b267f413e --- /dev/null +++ b/Client/src/Pages/Monitors/Details/skeleton.jsx @@ -0,0 +1,73 @@ +import { Box, Skeleton, Stack, useTheme } from "@mui/material"; + +/** + * Renders a skeleton layout. + * + * @returns {JSX.Element} + */ +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 2351a59e9..afe943f4e 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -66,7 +66,7 @@ export const buildData = (monitors, isAdmin, navigate) => { percentageColor, status: monitor.status === undefined - ? "unknown" + ? "pending" : monitor.status === true ? "up" : "down", From a192b3d14c396439497dde7859a92e1227719760 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 12:57:08 -0400 Subject: [PATCH 55/96] Added hover state to bars --- Client/src/Pages/Monitors/Configure/index.jsx | 1 - .../Pages/Monitors/Details/Charts/index.jsx | 117 +++++++++++++++--- Client/src/Pages/Monitors/Details/index.jsx | 75 ++++++++--- Client/src/Pages/Monitors/Details/styled.jsx | 12 +- Client/src/Pages/Monitors/Home/StatusBox.jsx | 3 +- Client/src/Pages/Monitors/Home/index.jsx | 9 +- Client/src/Utils/Theme/darkTheme.js | 2 +- Client/src/Utils/greeting.jsx | 6 +- Client/src/Utils/timeUtils.js | 4 +- Client/src/assets/icons/top-right-arrow.svg | 2 +- 10 files changed, 181 insertions(+), 50 deletions(-) diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 2e719a31a..637c71ddc 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -53,7 +53,6 @@ const Configure = () => { const [monitor, setMonitor] = useState({}); const [errors, setErrors] = useState({}); const { monitorId } = useParams(); - console.log(monitor); const idMap = { "monitor-url": "url", "monitor-name": "name", diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index 54d9716fa..44c7ba163 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -5,35 +5,61 @@ import { XAxis, CartesianGrid, ResponsiveContainer, + Cell, } from "recharts"; import { formatDate } from "../../../../Utils/timeUtils"; +import { useState } from "react"; -const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint }) => { +const CustomLabels = ({ + x, + width, + height, + firstDataPoint, + lastDataPoint, + type, +}) => { let options = { month: "short", year: undefined, hour: undefined, minute: undefined, }; + if (type === "day") delete options.hour; return ( <> - - {formatDate(new Date(firstDataPoint.day), options)} + + {formatDate(new Date(firstDataPoint.time), options)} - - {formatDate(new Date(lastDataPoint.day), options)} + + {formatDate(new Date(lastDataPoint.time), options)} ); }; -export const UpBarChart = ({ data, dateRange }) => { +export const UpBarChart = ({ data, type, onBarHover }) => { const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + return ( - - + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: null }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > { height="100%" firstDataPoint={data[0]} lastDataPoint={data[data.length - 1]} - dateRange={dateRange} + type={type} /> } /> + background={{ fill: "transparent" }} + > + {data.map((entry, index) => ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalChecks: null }); + }} + /> + ))} + ); }; -export const DownBarChart = ({ data }) => { +export const DownBarChart = ({ data, type, onBarHover }) => { const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + return ( - - + + { + setChartHovered(true); + onBarHover({ time: null, totalIncidents: null }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > { height="100%" firstDataPoint={data[0]} lastDataPoint={data[data.length - 1]} + type={type} /> } /> + background={{ fill: "transparent" }} + > + {data.map((entry, index) => ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalIncidents: null }); + }} + /> + ))} + ); diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 6f717143e..f3239fb42 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -210,13 +210,11 @@ const DetailsPage = ({ isAdmin }) => { let totalChecks = 0; let totalIncidents = 0; let totalResponseTime = 0; - let count = 0; Object.keys(aggregateStats).forEach((entry) => { totalChecks += aggregateStats[entry].totalChecks; totalIncidents += aggregateStats[entry].totalIncidents; - totalResponseTime += aggregateStats[entry].totalIncidents; - count++; + totalResponseTime += aggregateStats[entry].avgResponseTime; }); let uptime = @@ -229,13 +227,17 @@ const DetailsPage = ({ isAdmin }) => { return { totalChecks: totalChecks, totalIncidents: totalIncidents, - averageResponseTime: totalResponseTime / count, - uptime: uptime, + averageResponseTime: + Math.floor((totalResponseTime / aggregateStats.length) * 100) / 100, + uptime: Math.floor(uptime), }; }; const monitorStats = calculateStats(); - console.log(aggregateStats); + + const [hoveredUptimeData, setHoveredUptimeData] = useState(null); + const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); + return ( {loading ? ( @@ -344,7 +346,6 @@ const DetailsPage = ({ isAdmin }) => { onClose={closeCertificate} disableScrollLock marginThreshold={null} - anchorReference={anchorEl} anchorOrigin={{ vertical: "bottom", horizontal: "center", @@ -481,12 +482,31 @@ const DetailsPage = ({ isAdmin }) => { Uptime - - + + Total Checks - {monitorStats.totalChecks} + {hoveredUptimeData !== null + ? hoveredUptimeData.totalChecks || 0 + : monitorStats.totalChecks} + {hoveredUptimeData !== null && + hoveredUptimeData.time !== null && ( + + {formatDate(new Date(hoveredUptimeData.time), { + month: "short", + year: undefined, + minute: undefined, + hour: dateRange === "day" ? "numeric" : undefined, + })} + + )} Uptime Percentage @@ -496,7 +516,11 @@ const DetailsPage = ({ isAdmin }) => { - + @@ -505,13 +529,36 @@ const DetailsPage = ({ isAdmin }) => { Incidents - + Total Incidents - {monitorStats.totalIncidents} + {hoveredIncidentsData !== null + ? hoveredIncidentsData.totalIncidents || 0 + : monitorStats.totalIncidents} + {hoveredIncidentsData !== null && + hoveredIncidentsData.time !== null && ( + + {formatDate(new Date(hoveredIncidentsData.time), { + month: "short", + year: undefined, + minute: undefined, + hour: dateRange === "day" ? "numeric" : undefined, + })} + + )} - + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 1ed6ca8ee..3c178cb00 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -1,7 +1,8 @@ -import { Box, styled } from "@mui/material"; +import { Box, Stack, styled } from "@mui/material"; -export const ChartBox = styled(Box)(({ theme }) => ({ +export const ChartBox = styled(Stack)(({ theme }) => ({ flex: "1 30%", + gap: theme.spacing(8), height: 300, minWidth: 250, padding: theme.spacing(8), @@ -30,16 +31,15 @@ export const ChartBox = styled(Box)(({ theme }) => ({ }, "& .MuiStack-root": { flexDirection: "row", - alignItems: "center", gap: theme.spacing(6), }, - "& > .MuiBox-root:not(:first-of-type)": { - marginTop: theme.spacing(8), - }, "& tspan, & text": { fontSize: 11, fill: theme.palette.text.tertiary, }, + "& path": { + transition: "fill 300ms ease", + }, })); export const IconBox = styled(Box)(({ theme }) => ({ diff --git a/Client/src/Pages/Monitors/Home/StatusBox.jsx b/Client/src/Pages/Monitors/Home/StatusBox.jsx index 08b2e88d9..ece8cde16 100644 --- a/Client/src/Pages/Monitors/Home/StatusBox.jsx +++ b/Client/src/Pages/Monitors/Home/StatusBox.jsx @@ -48,8 +48,7 @@ const StatusBox = ({ title, value }) => { borderColor={theme.palette.border.light} borderRadius={theme.shape.borderRadius} backgroundColor={theme.palette.background.main} - px={theme.spacing(12)} - py={theme.spacing(8)} + p={theme.spacing(8)} overflow="hidden" sx={{ "&:hover": { diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index e5c9a765f..64c01b7ac 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -44,7 +44,7 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState.isLoading && monitorState.monitors.length === 0; return ( - + {loading ? ( ) : ( @@ -79,7 +79,7 @@ const Monitors = ({ isAdmin }) => { {monitorState.monitors?.length !== 0 && ( <> @@ -89,8 +89,7 @@ const Monitors = ({ isAdmin }) => { { { lineHeight={1} fontWeight={500} color={theme.palette.text.primary} + mb={theme.spacing(1)} > { {append} — Here’s an overview of your {type} monitors. diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index fa4a27d95..f9f916d04 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -72,5 +72,7 @@ export const formatDate = (date, customOptions) => { }; // Return the date using the specified options - return date.toLocaleString("en-US", options); + return date + .toLocaleString("en-US", options) + .replace(/\b(AM|PM)\b/g, (match) => match.toLowerCase()); }; diff --git a/Client/src/assets/icons/top-right-arrow.svg b/Client/src/assets/icons/top-right-arrow.svg index 471b9454c..d569cdd7e 100644 --- a/Client/src/assets/icons/top-right-arrow.svg +++ b/Client/src/assets/icons/top-right-arrow.svg @@ -1,3 +1,3 @@ - + From 9edf5ba9f9e6674d06bef8a3944800270a797c25 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:02:18 -0700 Subject: [PATCH 56/96] Refactor --- Client/src/Components/Label/index.jsx | 1 - Server/db/mongo/MongoDB.js | 2 +- Server/db/mongo/modules/monitorModule.js | 246 +++++++---------------- 3 files changed, 79 insertions(+), 170 deletions(-) diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index 330c6de0a..886f66fb9 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -126,7 +126,6 @@ ColoredLabel.propTypes = { */ const StatusLabel = ({ status, text, customStyles }) => { - console.log(status); const theme = useTheme(); const colors = { up: { diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 6f959f758..29233b73a 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -65,6 +65,7 @@ const { const { getAllMonitors, getMonitorAggregateStats, + testMethod, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, @@ -145,7 +146,6 @@ module.exports = { resetPassword, checkSuperadmin, getAllMonitors, - getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 0f0189694..bf056782b 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -145,39 +145,103 @@ const getStatusBarValues = (monitor, checks) => { }; /** - * Get aggregate monitor stats for charts + * Get stats by monitor ID * @async * @param {Express.Request} req * @param {Express.Response} res * @returns {Promise} * @throws {Error} */ -const getMonitorAggregateStats = async (monitorId, dateRange) => { +const getMonitorStatsById = async (req) => { const startDates = { 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)), }; - + const endDate = new Date(); try { - const startDate = startDates[dateRange]; - if (!startDate) { - throw new Error("Invalid date range specified"); + // Get monitor + const { monitorId } = req.params; + let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; + const monitor = await Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); } - const endDate = new Date(); - const checks = await Check.find({ - monitorId: monitorId, + // This effectively removes limit, returning all checks + if (limit === undefined) limit = 0; + + // Default sort order is newest -> oldest + sortOrder = sortOrder === "asc" ? 1 : -1; + + let model = + monitor.type === "http" || monitor.type === "ping" + ? Check + : PageSpeedCheck; + + const monitorStats = { + ...monitor.toObject(), + }; + + // Build checks query + const checksQuery = { monitorId: monitor._id }; + + // Get all checks + const checksAll = await model.find(checksQuery).sort({ + createdAt: sortOrder, + }); + + const checksQueryForDateRange = { + ...checksQuery, createdAt: { - $gte: startDate, + $gte: startDates[dateRange], $lte: endDate, }, - }); + }; + + const checksForDateRange = await model + .find(checksQueryForDateRange) + .sort({ createdAt: sortOrder }); + + if (monitor.type === "http" || monitor.type === "ping") { + // HTTP/PING Specific stats + monitorStats.avgResponseTime = getAverageResponseTime(checksForDateRange); + monitorStats.uptime = getUptimePercentage(checksForDateRange); + } + + monitorStats.incidents = getIncidents(checksForDateRange); + + // If more than numToDisplay checks, pick every nth check + + let nthChecks = checksForDateRange; + + if ( + numToDisplay !== undefined && + checksForDateRange && + checksForDateRange.length > numToDisplay + ) { + const n = Math.ceil(checksForDateRange.length / numToDisplay); + nthChecks = checksForDateRange.filter((_, index) => index % n === 0); + } + + // Normalize checks if requested + if (normalize !== undefined) { + const normailzedChecks = NormalizeData(checksForDateRange, 1, 100); + monitorStats.checks = normailzedChecks; + } else { + monitorStats.checks = nthChecks; + } + + monitorStats.uptimeDuration = calculateUptimeDuration(checksAll); + monitorStats.lastChecked = getLastChecked(checksAll); + monitorStats.latestResponseTime = getLatestResponseTime(checksAll); + + // Aggregate data let groupedChecks; // Group checks by hour if range is day if (dateRange === "day") { - groupedChecks = checks.reduce((acc, check) => { + groupedChecks = checksForDateRange.reduce((acc, check) => { const time = new Date(check.createdAt); time.setMinutes(0, 0, 0); if (!acc[time]) { @@ -188,20 +252,8 @@ const getMonitorAggregateStats = async (monitorId, dateRange) => { }, {}); } - // Group checks by day if range is week or month - else { - groupedChecks = checks.reduce((acc, check) => { - const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - } - // Map grouped checks to stats - stats = Object.values(groupedChecks).map((group) => { + const aggregateData = Object.values(groupedChecks).map((group) => { const totalChecks = group.checks.length; const totalIncidents = group.checks.filter( (check) => check.status === false @@ -217,148 +269,7 @@ const getMonitorAggregateStats = async (monitorId, dateRange) => { avgResponseTime, }; }); - - return stats; - } catch (error) { - throw error; - } -}; - -/** - * Get stats by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorStatsById = async (req) => { - const filterLookup = { - hour: new Date(new Date().getTime() - 60 * 60 * 1000), - 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)), - }; - - try { - const { monitorId } = req.params; - let { status, limit, sortOrder, dateRange, numToDisplay, normalize } = - req.query; - - // This effectively removes limit, returning all checks - if (limit === undefined) limit = 0; - - // Default sort order is newest -> oldest - sortOrder = sortOrder === "asc" ? 1 : -1; - - // Get monitor - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); - } - - // Determine if this is a pagespeed monitor or an http/ping monitor - let model = - monitor.type === "http" || monitor.type === "ping" - ? Check - : PageSpeedCheck; - - // Build monitor stats object - const monitorStats = { - ...monitor.toObject(), - }; - - // Start building query - const checksQuery = { monitorId: monitor._id }; - - // Get all checks - const checksAll = await model.find(checksQuery).sort({ - createdAt: sortOrder, - }); - - if (monitor.type === "http" || monitor.type === "ping") { - const checksQuery24Hours = { - ...checksQuery, - createdAt: { $gte: filterLookup.day }, - }; - const checksQuery7Days = { - ...checksQuery, - createdAt: { $gte: filterLookup.week }, - }; - const checksQuery30Days = { - ...checksQuery, - createdAt: { $gte: filterLookup.month }, - }; - const checksQuery60Mins = { - ...checksQuery, - createdAt: { $gte: filterLookup.hour }, - }; - - const [checks24Hours, checks7Days, checks30Days, checks60Mins] = - await Promise.all([ - model.find(checksQuery24Hours).sort({ createdAt: sortOrder }), - model.find(checksQuery7Days).sort({ createdAt: sortOrder }), - model.find(checksQuery30Days).sort({ createdAt: sortOrder }), - model.find(checksQuery60Mins).sort({ createdAt: sortOrder }), - ]); - - // HTTP/PING Specific stats - - //24 hour data - monitorStats.avgResponseTime24hours = - getAverageResponseTime(checks24Hours); - monitorStats.uptime24Hours = getUptimePercentage(checks24Hours); - - //7 day data - monitorStats.uptime7Days = getUptimePercentage(checks7Days); - monitorStats.avgResponseTime7days = getAverageResponseTime(checks7Days); - // 30 day data - monitorStats.uptime30Days = getUptimePercentage(checks30Days); - monitorStats.avgResponseTime30days = getAverageResponseTime(checks30Days); - - monitorStats.statusBar = getStatusBarValues(monitor, checks60Mins); - } - - //Get checks for dateRange - if (status !== undefined) { - checksQuery.status = status; - } - - // Filter checks by "day", "week", or "month" - if (dateRange !== undefined) { - checksQuery.createdAt = { $gte: filterLookup[dateRange] }; - } - - let dateRangeChecks = await model - .find(checksQuery) - .sort({ - createdAt: sortOrder, - }) - .limit(limit); - - const incidents = getIncidents(dateRangeChecks); - - // If more than numToDisplay checks, pick every nth check - if ( - numToDisplay !== undefined && - dateRangeChecks && - dateRangeChecks.length > numToDisplay - ) { - const n = Math.ceil(dateRangeChecks.length / numToDisplay); - dateRangeChecks = dateRangeChecks.filter((_, index) => index % n === 0); - } - - // Normalize checks if requested - if (normalize !== undefined) { - dateRangeChecks = NormalizeData(dateRangeChecks, 1, 100); - } - - // Add common stats and stats that depend on the dateRange - monitorStats.uptimeDuration = calculateUptimeDuration(checksAll); - monitorStats.lastChecked = getLastChecked(checksAll); - monitorStats.latestResponseTime = getLatestResponseTime(checksAll); - monitorStats.incidents = incidents; - monitorStats.checks = dateRangeChecks; + monitorStats.aggregateData = aggregateData; return monitorStats; } catch (error) { throw error; @@ -548,7 +459,6 @@ const editMonitor = async (candidateId, candidateMonitor) => { module.exports = { getAllMonitors, - getMonitorAggregateStats, getMonitorStatsById, getMonitorById, getMonitorsByTeamId, From 5ccb85e75519b9faee4cc1ae078476f339477b21 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:09:08 -0700 Subject: [PATCH 57/96] remove unused methods --- Server/db/mongo/modules/monitorModule.js | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index bf056782b..9f801a9df 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -35,7 +35,7 @@ const calculateUptimeDuration = (checks) => { const latestCheck = new Date(checks[0].createdAt); let latestDownCheck = 0; - for (let i = 0; i < checks.length; i++) { + for (let i = checks.length; i <= 0; i--) { if (checks[i].status === false) { latestDownCheck = new Date(checks[i].createdAt); break; @@ -121,28 +121,6 @@ const getIncidents = (checks) => { return check.status === false ? (acc += 1) : acc; }, 0); }; -/** - * Helper function to get all incidents - * @param {Array} checks Array of check objects. - * @returns {Array} Array of booleans representing up/down. - */ -const getStatusBarValues = (monitor, checks) => { - const checksIn60Mins = Math.floor((60 * 60 * 1000) / monitor.interval); - const noBlankChecks = checksIn60Mins - checks.length; - - const statusBarValues = checks.map((check) => { - return { - status: check.status, - responseTime: check.responseTime, - value: 75, - }; - }); - - for (let i = 0; i < noBlankChecks; i++) { - statusBarValues.push({ status: undefined, responseTime: 0, value: 75 }); - } - return statusBarValues.reverse(); -}; /** * Get stats by monitor ID From df16bf3db55285053f242c9d31b31bb8f32b800a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:25:08 -0700 Subject: [PATCH 58/96] Moved http/ping specific data --- Server/db/mongo/modules/monitorModule.js | 70 ++++++++++++------------ Server/middleware/handleErrors.js | 5 +- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 9f801a9df..ab867ff47 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -145,7 +145,6 @@ const getMonitorStatsById = async (req) => { if (monitor === null || monitor === undefined) { throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId)); } - // This effectively removes limit, returning all checks if (limit === undefined) limit = 0; @@ -185,6 +184,40 @@ const getMonitorStatsById = async (req) => { // HTTP/PING Specific stats monitorStats.avgResponseTime = getAverageResponseTime(checksForDateRange); monitorStats.uptime = getUptimePercentage(checksForDateRange); + + // Aggregate data + let groupedChecks; + // Group checks by hour if range is day + if (dateRange === "day") { + groupedChecks = checksForDateRange.reduce((acc, check) => { + const time = new Date(check.createdAt); + time.setMinutes(0, 0, 0); + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); + } + + // Map grouped checks to stats + const aggregateData = Object.values(groupedChecks).map((group) => { + const totalChecks = group.checks.length; + const totalIncidents = group.checks.filter( + (check) => check.status === false + ).length; + const avgResponseTime = + group.checks.reduce((sum, check) => sum + check.responseTime, 0) / + totalChecks; + + return { + time: group.time, + totalChecks, + totalIncidents, + avgResponseTime, + }; + }); + monitorStats.aggregateData = aggregateData; } monitorStats.incidents = getIncidents(checksForDateRange); @@ -214,42 +247,9 @@ const getMonitorStatsById = async (req) => { monitorStats.lastChecked = getLastChecked(checksAll); monitorStats.latestResponseTime = getLatestResponseTime(checksAll); - // Aggregate data - - let groupedChecks; - // Group checks by hour if range is day - if (dateRange === "day") { - groupedChecks = checksForDateRange.reduce((acc, check) => { - const time = new Date(check.createdAt); - time.setMinutes(0, 0, 0); - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - } - - // Map grouped checks to stats - const aggregateData = Object.values(groupedChecks).map((group) => { - const totalChecks = group.checks.length; - const totalIncidents = group.checks.filter( - (check) => check.status === false - ).length; - const avgResponseTime = - group.checks.reduce((sum, check) => sum + check.responseTime, 0) / - totalChecks; - - return { - time: group.time, - totalChecks, - totalIncidents, - avgResponseTime, - }; - }); - monitorStats.aggregateData = aggregateData; return monitorStats; } catch (error) { + error.methodName = "getMonitorStatsById"; throw error; } }; diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js index 1e1e955c5..db51c1af6 100644 --- a/Server/middleware/handleErrors.js +++ b/Server/middleware/handleErrors.js @@ -5,7 +5,10 @@ const handleErrors = (error, req, res, next) => { const status = error.status || 500; const message = error.message || errorMessages.FRIENDLY_ERROR; const service = error.service || errorMessages.UNKNOWN_SERVICE; - logger.error(error.message, { service: service }); + logger.error(error.message, { + service: service, + methodName: error.methodName, + }); res.status(status).json({ success: false, msg: message }); }; From ffc132e19a670f900e922b5edacecf886d084bb5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:45:54 -0700 Subject: [PATCH 59/96] Add missing else block --- Client/src/Pages/PageSpeed/Details/index.jsx | 3 ++- Server/db/mongo/modules/monitorModule.js | 17 +++++++++++++---- Server/service/networkService.js | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 29c0b72e0..f4dd1f64d 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -213,10 +213,11 @@ const PageSpeedDetails = () => { monitorId, "desc", 50, - null, + "day", null, null ); + console.log(res.data.data); setMonitor(res?.data?.data ?? {}); setAudits(res?.data?.data?.checks?.[0]?.audits ?? []); } catch (error) { diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index ab867ff47..858976285 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -147,7 +147,6 @@ const getMonitorStatsById = async (req) => { } // This effectively removes limit, returning all checks if (limit === undefined) limit = 0; - // Default sort order is newest -> oldest sortOrder = sortOrder === "asc" ? 1 : -1; @@ -182,8 +181,9 @@ const getMonitorStatsById = async (req) => { if (monitor.type === "http" || monitor.type === "ping") { // HTTP/PING Specific stats - monitorStats.avgResponseTime = getAverageResponseTime(checksForDateRange); - monitorStats.uptime = getUptimePercentage(checksForDateRange); + monitorStats.periodAvgResponseTime = + getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = getUptimePercentage(checksForDateRange); // Aggregate data let groupedChecks; @@ -198,6 +198,15 @@ const getMonitorStatsById = async (req) => { acc[time].checks.push(check); return acc; }, {}); + } else { + groupedChecks = checksForDateRange.reduce((acc, check) => { + const day = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[day]) { + acc[day] = { day, checks: [] }; + } + acc[day].checks.push(check); + return acc; + }, {}); } // Map grouped checks to stats @@ -220,7 +229,7 @@ const getMonitorStatsById = async (req) => { monitorStats.aggregateData = aggregateData; } - monitorStats.incidents = getIncidents(checksForDateRange); + monitorStats.periodIncidents = getIncidents(checksForDateRange); // If more than numToDisplay checks, pick every nth check diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 863349dbf..cfa2a2c35 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -197,7 +197,6 @@ class NetworkService { */ async handlePagespeed(job) { let isAlive; - try { const url = job.data.url; const response = await axios.get( @@ -297,6 +296,7 @@ class NetworkService { async logAndStoreCheck(data, writeToDB) { try { const insertedCheck = await writeToDB(data); + console.log(insertedCheck); return insertedCheck.status; } catch (error) { console.log(error); From be582edeeb649f7760d91189d06d1a47f90017a8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:54:20 -0700 Subject: [PATCH 60/96] add time --- Client/src/Pages/Monitors/Details/index.jsx | 1 + Server/db/mongo/modules/monitorModule.js | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 833e11ef9..4bea7d836 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -89,6 +89,7 @@ const DetailsPage = ({ isAdmin }) => { 50, true ); + console.log(res.data.data); setMonitor(res?.data?.data ?? {}); } catch (error) { logger.error(error); diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 858976285..44ec99a6d 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -200,11 +200,11 @@ const getMonitorStatsById = async (req) => { }, {}); } else { groupedChecks = checksForDateRange.reduce((acc, check) => { - const day = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part - if (!acc[day]) { - acc[day] = { day, checks: [] }; + const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[time]) { + acc[time] = { time, checks: [] }; } - acc[day].checks.push(check); + acc[time].checks.push(check); return acc; }, {}); } From a5697d419cbdc85f42c8281c483a11cf968c5fb1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:54:59 -0700 Subject: [PATCH 61/96] Remove console log --- Client/src/Pages/Monitors/Details/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 4bea7d836..833e11ef9 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -89,7 +89,6 @@ const DetailsPage = ({ isAdmin }) => { 50, true ); - console.log(res.data.data); setMonitor(res?.data?.data ?? {}); } catch (error) { logger.error(error); From 3270d61624edcb04e7b948a39465690ca13278a7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 12:00:01 -0700 Subject: [PATCH 62/96] Add period total checks --- Server/db/mongo/modules/monitorModule.js | 1 + Server/service/networkService.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 44ec99a6d..393aa93cc 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -230,6 +230,7 @@ const getMonitorStatsById = async (req) => { } monitorStats.periodIncidents = getIncidents(checksForDateRange); + monitor.periodTotalChecks = checksForDateRange.length; // If more than numToDisplay checks, pick every nth check diff --git a/Server/service/networkService.js b/Server/service/networkService.js index cfa2a2c35..e25a63ce5 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -296,7 +296,6 @@ class NetworkService { async logAndStoreCheck(data, writeToDB) { try { const insertedCheck = await writeToDB(data); - console.log(insertedCheck); return insertedCheck.status; } catch (error) { console.log(error); From 6424a0436f9bbd68f5777df330f0330fcbd3b3d9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 12:15:53 -0700 Subject: [PATCH 63/96] Fixed incorrect assignment --- Server/db/mongo/modules/monitorModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 393aa93cc..29e98fc2c 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -230,7 +230,7 @@ const getMonitorStatsById = async (req) => { } monitorStats.periodIncidents = getIncidents(checksForDateRange); - monitor.periodTotalChecks = checksForDateRange.length; + monitorStats.periodTotalChecks = checksForDateRange.length; // If more than numToDisplay checks, pick every nth check From 70a40589933de0987255ea696457a49743ec4b28 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 13:21:51 -0700 Subject: [PATCH 64/96] add uptimePercentage to aggregate data --- Server/db/mongo/modules/monitorModule.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 29e98fc2c..558fbae4d 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -212,6 +212,7 @@ const getMonitorStatsById = async (req) => { // Map grouped checks to stats const aggregateData = Object.values(groupedChecks).map((group) => { const totalChecks = group.checks.length; + const uptimePercentage = getUptimePercentage(group.checks); const totalIncidents = group.checks.filter( (check) => check.status === false ).length; @@ -221,6 +222,7 @@ const getMonitorStatsById = async (req) => { return { time: group.time, + uptimePercentage, totalChecks, totalIncidents, avgResponseTime, From 38fddf5b9b47aa687506406dffbb41096b73a26b Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 16:41:23 -0400 Subject: [PATCH 65/96] Added color range to uptime chart --- Client/src/Components/Animated/PulseDot.jsx | 4 +- .../Pages/Monitors/Details/Charts/index.jsx | 61 +++++++++------ Client/src/Pages/Monitors/Details/index.jsx | 76 +++++-------------- Client/src/Pages/Monitors/Details/styled.jsx | 3 + Client/src/Pages/PageSpeed/Details/index.jsx | 2 +- 5 files changed, 62 insertions(+), 84 deletions(-) diff --git a/Client/src/Components/Animated/PulseDot.jsx b/Client/src/Components/Animated/PulseDot.jsx index 38e956b50..9ac536fe6 100644 --- a/Client/src/Components/Animated/PulseDot.jsx +++ b/Client/src/Components/Animated/PulseDot.jsx @@ -41,8 +41,8 @@ const PulseDot = ({ color }) => { "&::after": { content: `""`, position: "absolute", - width: "6px", - height: "6px", + width: "7px", + height: "7px", borderRadius: "50%", backgroundColor: "white", top: "50%", diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index 44c7ba163..2927a6cc0 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -44,6 +44,14 @@ export const UpBarChart = ({ data, type, onBarHover }) => { const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + const getColorRange = (uptime) => { + return uptime > 80 + ? { main: theme.palette.success.main, light: theme.palette.success.light } + : uptime > 50 + ? { main: theme.palette.warning.main, light: theme.palette.warning.light } + : { main: theme.palette.error.text, light: theme.palette.error.light }; + }; + return ( { data={data} onMouseEnter={() => { setChartHovered(true); - onBarHover({ time: null, totalChecks: null }); + onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 }); }} onMouseLeave={() => { setChartHovered(false); @@ -78,29 +86,32 @@ export const UpBarChart = ({ data, type, onBarHover }) => { /> - {data.map((entry, index) => ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ time: null, totalChecks: null }); - }} - /> - ))} + {data.map((entry, index) => { + let { main, light } = getColorRange(entry.uptimePercentage); + return ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ + time: null, + totalChecks: 0, + uptimePercentage: 0, + }); + }} + /> + ); + })} @@ -121,7 +132,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { data={data} onMouseEnter={() => { setChartHovered(true); - onBarHover({ time: null, totalIncidents: null }); + onBarHover({ time: null, totalIncidents: 0 }); }} onMouseLeave={() => { setChartHovered(false); @@ -147,7 +158,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { /> {data.map((entry, index) => ( @@ -166,7 +177,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { }} onMouseLeave={() => { setHoveredBarIndex(null); - onBarHover({ time: null, totalIncidents: null }); + onBarHover({ time: null, totalIncidents: 0 }); }} /> ))} diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 4d9917586..46d4772d7 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -118,57 +118,9 @@ const DetailsPage = ({ isAdmin }) => { let loading = Object.keys(monitor).length === 0; - const [aggregateStats, setAggregateStats] = useState({}); - useEffect(() => { - const fetchAggregateStats = async () => { - try { - const res = await networkService.getAggregateStatsById( - authToken, - monitorId, - dateRange - ); - - setAggregateStats(res?.data?.data); - } catch (error) { - console.error(error); - } - }; - fetchAggregateStats(); - }, [monitorId, dateRange]); - - const calculateStats = () => { - let totalChecks = 0; - let totalIncidents = 0; - let totalResponseTime = 0; - - Object.keys(aggregateStats).forEach((entry) => { - totalChecks += aggregateStats[entry].totalChecks; - totalIncidents += aggregateStats[entry].totalIncidents; - totalResponseTime += aggregateStats[entry].avgResponseTime; - }); - - let uptime = - dateRange === "day" - ? monitor.uptime24Hours - : dateRange === "week" - ? monitor.uptime7Days - : dateRange === "month" && monitor.uptime30Days; - - return { - totalChecks: totalChecks, - totalIncidents: totalIncidents, - averageResponseTime: - Math.floor((totalResponseTime / aggregateStats.length) * 100) / 100, - uptime: Math.floor(uptime), - }; - }; - - const monitorStats = calculateStats(); - const [hoveredUptimeData, setHoveredUptimeData] = useState(null); const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); - const statusColor = { true: theme.palette.success.main, false: theme.palette.error.main, @@ -181,6 +133,8 @@ const DetailsPage = ({ isAdmin }) => { undefined: "Pending...", }; + console.log(monitor); + return ( {loading ? ( @@ -339,7 +293,13 @@ const DetailsPage = ({ isAdmin }) => { { Total Checks {hoveredUptimeData !== null - ? hoveredUptimeData.totalChecks || 0 - : monitorStats.totalChecks} + ? hoveredUptimeData.totalChecks + : monitor?.periodTotalChecks} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( @@ -450,13 +410,17 @@ const DetailsPage = ({ isAdmin }) => { Uptime Percentage - {monitorStats.uptime} + {hoveredUptimeData !== null + ? Math.floor( + hoveredUptimeData.uptimePercentage * 10 + ) / 10 + : Math.floor(monitor?.periodUptime * 10) / 10} % @@ -472,8 +436,8 @@ const DetailsPage = ({ isAdmin }) => { Total Incidents {hoveredIncidentsData !== null - ? hoveredIncidentsData.totalIncidents || 0 - : monitorStats.totalIncidents} + ? hoveredIncidentsData.totalIncidents + : monitor?.periodIncidents} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( @@ -494,7 +458,7 @@ const DetailsPage = ({ isAdmin }) => { )} diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 3c178cb00..6c3f8eb04 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -33,6 +33,9 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ flexDirection: "row", gap: theme.spacing(6), }, + "& .MuiStack-root:first-of-type": { + alignItems: "center", + }, "& tspan, & text": { fontSize: 11, fill: theme.palette.text.tertiary, diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 29c0b72e0..4004e1715 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -213,7 +213,7 @@ const PageSpeedDetails = () => { monitorId, "desc", 50, - null, + "day", null, null ); From edeea4497e8d0075847ebf1fd756bfc011146683 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 15:48:18 -0700 Subject: [PATCH 66/96] Add JSON data for default monitors, remove unused code --- Client/src/Pages/Monitors/Home/index.jsx | 16 - Client/src/assets/data/hostnames.json | 1290 ++++++++++++++++++++++ 2 files changed, 1290 insertions(+), 16 deletions(-) create mode 100644 Client/src/assets/data/hostnames.json diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 318a9da20..e5c9a765f 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -43,22 +43,6 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState.isLoading && monitorState.monitors.length === 0; - const now = new Date(); - const hour = now.getHours(); - - let greeting = ""; - let emoji = ""; - if (hour < 12) { - greeting = "morning"; - emoji = "🌅"; - } else if (hour < 18) { - greeting = "afternoon"; - emoji = "🌞"; - } else { - greeting = "evening"; - emoji = "🌙"; - } - return ( {loading ? ( diff --git a/Client/src/assets/data/hostnames.json b/Client/src/assets/data/hostnames.json new file mode 100644 index 000000000..b66818c3a --- /dev/null +++ b/Client/src/assets/data/hostnames.json @@ -0,0 +1,1290 @@ +[ + { + "Company Name": "0to255", + "Company Link ": "https://0to255.com" + }, + { + "Company Name": "10015.io", + "Company Link ": "https://10015.io" + }, + { + "Company Name": "3DIcons", + "Company Link ": "https://3dicons.co" + }, + { + "Company Name": "About.me", + "Company Link ": "https://about.me" + }, + { + "Company Name": "Alias", + "Company Link ": "https://alias.co" + }, + { + "Company Name": "All About Berlin", + "Company Link ": "https://allaboutberlin.com" + }, + { + "Company Name": "All Acronyms", + "Company Link ": "https://allacronyms.com" + }, + { + "Company Name": "All You Can Read ", + "Company Link ": "https://allyoucanread.com" + }, + { + "Company Name": "AllTrails", + "Company Link ": "https://alltrails.com" + }, + { + "Company Name": "Anotepad", + "Company Link ": "https://anotepad.com" + }, + { + "Company Name": "AnswerSocrates", + "Company Link ": "https://answersocrates.com" + }, + { + "Company Name": "AnswerThePublic ", + "Company Link ": "https://answerthepublic.com" + }, + { + "Company Name": "Apollo ", + "Company Link ": "https://apollo.io" + }, + { + "Company Name": "ArrayList", + "Company Link ": "https://arraylist.org" + }, + { + "Company Name": "Ask Difference", + "Company Link ": "https://askdifference.com" + }, + { + "Company Name": "Audd.io", + "Company Link ": "https://audd.io" + }, + { + "Company Name": "Audiocheck", + "Company Link ": "https://audiocheck.net" + }, + { + "Company Name": "Audionautix", + "Company Link ": "https://audionautix.com" + }, + { + "Company Name": "Authentic Jobs", + "Company Link ": "https://authenticjobs.com" + }, + { + "Company Name": "Behind the Name", + "Company Link ": "https://behindthename.com" + }, + { + "Company Name": "Bilim Terimleri", + "Company Link ": "https://terimler.org" + }, + { + "Company Name": "BitBof", + "Company Link ": "https://bitbof.com" + }, + { + "Company Name": "Blank Page", + "Company Link ": "https://blank.page" + }, + { + "Company Name": "Bonanza", + "Company Link ": "https://bonanza.com" + }, + { + "Company Name": "BookCrossing", + "Company Link ": "https://bookcrossing.com" + }, + { + "Company Name": "Browse AI", + "Company Link ": "https://browse.ai" + }, + { + "Company Name": "Bubbl.us", + "Company Link ": "https://bubbl.us" + }, + { + "Company Name": "Business Model Toolbox", + "Company Link ": "https://bmtoolbox.net" + }, + { + "Company Name": "ByClickDownloader", + "Company Link ": "https://byclickdownloader.com" + }, + { + "Company Name": "Calligraphr", + "Company Link ": "https://calligraphr.com" + }, + { + "Company Name": "CertificateClaim", + "Company Link ": "https://certificateclaim.com" + }, + { + "Company Name": "Chosic", + "Company Link ": "https://chosic.com" + }, + { + "Company Name": "ClipDrop", + "Company Link ": "https://clipdrop.co" + }, + { + "Company Name": "CloudConvert", + "Company Link ": "https://cloudconvert.com" + }, + { + "Company Name": "CodingFont", + "Company Link ": "https://codingfont.com" + }, + { + "Company Name": "Color Hunt", + "Company Link ": "https://colorhunt.co" + }, + { + "Company Name": "ColorHexa", + "Company Link ": "https://colorhexa.com" + }, + { + "Company Name": "Conversion-Tool", + "Company Link ": "https://conversion-tool.com" + }, + { + "Company Name": "Cool Startup Jobs", + "Company Link ": "https://coolstartupjobs.com" + }, + { + "Company Name": "Coroflot", + "Company Link ": "https://coroflot.com" + }, + { + "Company Name": "Corrupt-a-File", + "Company Link ": "https://corrupt-a-file.net" + }, + { + "Company Name": "Couchsurfing", + "Company Link ": "https://couchsurfing.com" + }, + { + "Company Name": "Countries Been", + "Company Link ": "https://countriesbeen.com" + }, + { + "Company Name": "Country Code", + "Company Link ": "https://countrycode.org" + }, + { + "Company Name": "Creately", + "Company Link ": "https://creately.com" + }, + { + "Company Name": "Creately ", + "Company Link ": "https://creately.com" + }, + { + "Company Name": "Crossfade.io", + "Company Link ": "https://crossfade.io" + }, + { + "Company Name": "Crunchbase", + "Company Link ": "https://crunchbase.com" + }, + { + "Company Name": "CVmkr", + "Company Link ": "https://cvwizard.com" + }, + { + "Company Name": "Daily Remote", + "Company Link ": "https://dailyremote.com" + }, + { + "Company Name": "David Li", + "Company Link ": "https://david.li" + }, + { + "Company Name": "DemandHunt", + "Company Link ": "https://demandhunt.com" + }, + { + "Company Name": "Designify", + "Company Link ": "https://designify.com" + }, + { + "Company Name": "Diff Checker", + "Company Link ": "https://diffchecker.com" + }, + { + "Company Name": "DifferenceBetween.info", + "Company Link ": "https://differencebetween.info" + }, + { + "Company Name": "Digital Glossary", + "Company Link ": "https://digital-glossary.com" + }, + { + "Company Name": "Dimensions", + "Company Link ": "https://dimensions.com" + }, + { + "Company Name": "Discoverify Music", + "Company Link ": "https://discoverifymusic.com" + }, + { + "Company Name": "discu.eu", + "Company Link ": "https://discu.eu" + }, + { + "Company Name": "Do It Yourself", + "Company Link ": "https://doityourself.com" + }, + { + "Company Name": "Donkey Car", + "Company Link ": "https://donkeycar.com" + }, + { + "Company Name": "draw.io", + "Company Link ": "https://drawio.com" + }, + { + "Company Name": "Drumeo", + "Company Link ": "https://drumeo.com" + }, + { + "Company Name": "Dummies", + "Company Link ": "https://dummies.com" + }, + { + "Company Name": "Easel.ly", + "Company Link ": "https://easel.ly" + }, + { + "Company Name": "Educalingo", + "Company Link ": "https://educalingo.com" + }, + { + "Company Name": "Emoji Combos", + "Company Link ": "https://emojicombos.com" + }, + { + "Company Name": "EquityBee", + "Company Link ": "https://equitybee.com" + }, + { + "Company Name": "EquityZen", + "Company Link ": "https://equityzen.com" + }, + { + "Company Name": "Escape Room Tips", + "Company Link ": "https://escaperoomtips.com" + }, + { + "Company Name": "Every Noise", + "Company Link ": "https://everynoise.com" + }, + { + "Company Name": "Every Time Zone", + "Company Link ": "https://everytimezone.com" + }, + { + "Company Name": "Excalideck", + "Company Link ": "https://excalideck.com" + }, + { + "Company Name": "Excalidraw", + "Company Link ": "https://excalidraw.com" + }, + { + "Company Name": "Extract pics", + "Company Link ": "https://extract.pics" + }, + { + "Company Name": "EZGIF", + "Company Link ": "https://ezgif.com" + }, + { + "Company Name": "FactSlides", + "Company Link ": "https://factslides.com" + }, + { + "Company Name": "FIGR ", + "Company Link ": "https://figr.app" + }, + { + "Company Name": "Fine Dictionary", + "Company Link ": "https://finedictionary.com" + }, + { + "Company Name": "Fiverr", + "Company Link ": "https://fiverr.com" + }, + { + "Company Name": "Fix It Club", + "Company Link ": "https://fixitclub.com" + }, + { + "Company Name": "FlexJobs", + "Company Link ": "https://flexjobs.com" + }, + { + "Company Name": "Flightradar24", + "Company Link ": "https://flightradar24.com" + }, + { + "Company Name": "FlowCV ", + "Company Link ": "https://flowcv.com" + }, + { + "Company Name": "Foco Clipping", + "Company Link ": "https://fococlipping.com" + }, + { + "Company Name": "Font Squirrel", + "Company Link ": "https://fontsquirrel.com" + }, + { + "Company Name": "FontAwesome", + "Company Link ": "https://fontawesome.com" + }, + { + "Company Name": "Fontello ", + "Company Link ": "https://fontello.com" + }, + { + "Company Name": "Form to Chatbot", + "Company Link ": "https://formtochatbot.com" + }, + { + "Company Name": "Founder Resources", + "Company Link ": "https://founderresources.io" + }, + { + "Company Name": "Franz", + "Company Link ": "https://meetfranz.com" + }, + { + "Company Name": "Fraze It", + "Company Link ": "https://fraze.it" + }, + { + "Company Name": "Freecycle", + "Company Link ": "https://freecycle.org" + }, + { + "Company Name": "FreeType", + "Company Link ": "https://freetype.org" + }, + { + "Company Name": "FutureM", + "Company Link ": "https://futureme.org" + }, + { + "Company Name": "Generated.Photos", + "Company Link ": "https://generated.photos" + }, + { + "Company Name": "Get Human", + "Company Link ": "https://gethuman.com" + }, + { + "Company Name": "Go Bento", + "Company Link ": "https://gobento.com" + }, + { + "Company Name": "Good CV", + "Company Link ": "https://goodcv.com" + }, + { + "Company Name": "Grammar Monster", + "Company Link ": "https://grammar-monster.com" + }, + { + "Company Name": "Grammar Book", + "Company Link ": "https://grammarbook.com" + }, + { + "Company Name": "Gummy Search", + "Company Link ": "https://gummysearch.com" + }, + { + "Company Name": "Gumroad", + "Company Link ": "https://gumroad.com" + }, + { + "Company Name": "HealthIcons", + "Company Link ": "https://healthicons.org" + }, + { + "Company Name": "HexColor", + "Company Link ": "https://hexcolor.co" + }, + { + "Company Name": "Hidden Life Radio", + "Company Link ": "https://hiddenliferadio.com" + }, + { + "Company Name": "Hired", + "Company Link ": "https://lhh.com" + }, + { + "Company Name": "Honey", + "Company Link ": "https://joinhoney.com" + }, + { + "Company Name": "HowStuffWorks", + "Company Link ": "https://howstuffworks.com" + }, + { + "Company Name": "HugeIcons Pro", + "Company Link ": "https://hugeicons.com" + }, + { + "Company Name": "Humble Bundle", + "Company Link ": "https://humblebundle.com" + }, + { + "Company Name": "I Have No TV", + "Company Link ": "https://ihavenotv.com" + }, + { + "Company Name": "I Miss the Office", + "Company Link ": "https://imisstheoffice.eu" + }, + { + "Company Name": "IcoMoon", + "Company Link ": "https://icomoon.io" + }, + { + "Company Name": "Iconfinder", + "Company Link ": "https://iconfinder.com" + }, + { + "Company Name": "Icon Packs", + "Company Link ": "https://iconpacks.net" + }, + { + "Company Name": "Iconshock", + "Company Link ": "https://iconshock.com" + }, + { + "Company Name": "Iconz Design", + "Company Link ": "https://iconz.design" + }, + { + "Company Name": "iFixit", + "Company Link ": "https://ifixit.com" + }, + { + "Company Name": "IFTTT", + "Company Link ": "https://ifttt.com" + }, + { + "Company Name": "Illlustrations", + "Company Link ": "https://illlustrations.co" + }, + { + "Company Name": "Illustration Kit", + "Company Link ": "https://illustrationkit.com" + }, + { + "Company Name": "IMSDB", + "Company Link ": "https://imsdb.com" + }, + { + "Company Name": "Incompetech", + "Company Link ": "https://incompetech.com" + }, + { + "Company Name": "Incredibox", + "Company Link ": "https://incredibox.com" + }, + { + "Company Name": "InnerBod", + "Company Link ": "https://innerbody.com" + }, + { + "Company Name": "Instructables", + "Company Link ": "https://instructables.com" + }, + { + "Company Name": "Integromat", + "Company Link ": "https://make.com" + }, + { + "Company Name": "Investopedia", + "Company Link ": "https://investopedia.com" + }, + { + "Company Name": "Japanese Wiki Corpus", + "Company Link ": "https://japanesewiki.com" + }, + { + "Company Name": "Jitter.Video", + "Company Link ": "https://jitter.video" + }, + { + "Company Name": "Jobspresso", + "Company Link ": "https://jobspresso.co" + }, + { + "Company Name": "JPEG-Optimizer", + "Company Link ": "https://jpeg-optimizer.com" + }, + { + "Company Name": "JS Remotely", + "Company Link ": "https://jsremotely.com" + }, + { + "Company Name": "JScreenFix", + "Company Link ": "https://jscreenfix.com" + }, + { + "Company Name": "JSON Resume", + "Company Link ": "https://jsonresume.io" + }, + { + "Company Name": "Just Join", + "Company Link ": "https://justjoin.it" + }, + { + "Company Name": "Just the Recipe", + "Company Link ": "https://justtherecipe.com" + }, + { + "Company Name": "JustRemote", + "Company Link ": "https://justremote.co" + }, + { + "Company Name": "JustWatch", + "Company Link ": "https://justwatch.com" + }, + { + "Company Name": "Kanopy", + "Company Link ": "https://kanopy.com" + }, + { + "Company Name": "Kassellabs", + "Company Link ": "https://kassellabs.io" + }, + { + "Company Name": "Key Differences", + "Company Link ": "https://keydifferences.com" + }, + { + "Company Name": "Keybase", + "Company Link ": "https://keybase.io" + }, + { + "Company Name": "KeyValues", + "Company Link ": "https://keyvalues.com" + }, + { + "Company Name": "KHInsider", + "Company Link ": "https://khinsider.com" + }, + { + "Company Name": "Killed by Google", + "Company Link ": "https://killedbygoogle.com" + }, + { + "Company Name": "Kimovil", + "Company Link ": "https://kimovil.com" + }, + { + "Company Name": "Lalal.ai", + "Company Link ": "https://www.lalal.ai" + }, + { + "Company Name": "Learn Anything", + "Company Link ": "https://learn-anything.xyz" + }, + { + "Company Name": "LendingTree", + "Company Link ": "https://lendingtree.com" + }, + { + "Company Name": "Lightyear.fm", + "Company Link ": "https://lightyear.fm" + }, + { + "Company Name": "LittleSis", + "Company Link ": "https://littlesis.org" + }, + { + "Company Name": "Looria", + "Company Link ": "https://looria.com" + }, + { + "Company Name": "Lucidchart", + "Company Link ": "https://lucidchart.com" + }, + { + "Company Name": "Lunar", + "Company Link ": "https://lunar.fyi" + }, + { + "Company Name": "Manuals Lib", + "Company Link ": "https://manualslib.com" + }, + { + "Company Name": "Map Crunch", + "Company Link ": "https://mapcrunch.com" + }, + { + "Company Name": "Masterworks", + "Company Link ": "https://masterworks.com" + }, + { + "Company Name": "MediaFire", + "Company Link ": "https://mediafire.com" + }, + { + "Company Name": "Mind Mup", + "Company Link ": "https://mindmup.com" + }, + { + "Company Name": "Mixlr", + "Company Link ": "https://mixlr.com" + }, + { + "Company Name": "Moises AI", + "Company Link ": "https://moises.ai" + }, + { + "Company Name": "Money", + "Company Link ": "https://money.com" + }, + { + "Company Name": "Mountain Project", + "Company Link ": "https://mountainproject.com" + }, + { + "Company Name": "Movie Map", + "Company Link ": "https://movie-map.com" + }, + { + "Company Name": "Movie Sounds", + "Company Link ": "https://movie-sounds.org" + }, + { + "Company Name": "MP3Cut", + "Company Link ": "https://mp3cut.net" + }, + { + "Company Name": "Murmel", + "Company Link ": "https://murmel.social" + }, + { + "Company Name": "Muscle Wiki", + "Company Link ": "https://musclewiki.com" + }, + { + "Company Name": "Music-Map", + "Company Link ": "https://music-map.com" + }, + { + "Company Name": "MusicTheory.net", + "Company Link ": "https://musictheory.net" + }, + { + "Company Name": "MyFonts", + "Company Link ": "https://myfonts.com" + }, + { + "Company Name": "MyFridgeFood", + "Company Link ": "https://myfridgefood.com" + }, + { + "Company Name": "Nameberry", + "Company Link ": "https://nameberry.com" + }, + { + "Company Name": "Namechk", + "Company Link ": "https://namechk.com" + }, + { + "Company Name": "Ncase", + "Company Link ": "https://ncase.me" + }, + { + "Company Name": "News in Levels", + "Company Link ": "https://newsinlevels.com" + }, + { + "Company Name": "Noisli", + "Company Link ": "https://noisli.com" + }, + { + "Company Name": "Notes.io", + "Company Link ": "https://notes.io" + }, + { + "Company Name": "Novoresume", + "Company Link ": "https://novoresume.com" + }, + { + "Company Name": "Ocoya", + "Company Link ": "https://ocoya.com" + }, + { + "Company Name": "Old Computers Museum", + "Company Link ": "https://oldcomputers.net" + }, + { + "Company Name": "Online Tone Generator", + "Company Link ": "https://onlinetonegenerator.com" + }, + { + "Company Name": "Online-Convert", + "Company Link ": "https://online-convert.com" + }, + { + "Company Name": "OnlineConversion", + "Company Link ": "https://onlineconversion.com" + }, + { + "Company Name": "Online OCR", + "Company Link ": "https://onlineocr.net" + }, + { + "Company Name": "OpenWeatherMap", + "Company Link ": "https://openweathermap.org" + }, + { + "Company Name": "OrgPad", + "Company Link ": "https://orgpad.com" + }, + { + "Company Name": "Passport Index", + "Company Link ": "https://passportindex.org" + }, + { + "Company Name": "PDF Candy", + "Company Link ": "https://pdfcandy.com" + }, + { + "Company Name": "PDF2DOC", + "Company Link ": "https://pdf2doc.com" + }, + { + "Company Name": "PDFescape", + "Company Link ": "https://pdfescape.com" + }, + { + "Company Name": "PfpMaker", + "Company Link ": "https://pfpmaker.com" + }, + { + "Company Name": "PIDGI Wiki ", + "Company Link ": "https://pidgi.net" + }, + { + "Company Name": "PimEyes", + "Company Link ": "https://pimeyes.com" + }, + { + "Company Name": "Pipl ", + "Company Link ": "https://pipl.com" + }, + { + "Company Name": "PixelBazaar", + "Company Link ": "https://pixelbazaar.com" + }, + { + "Company Name": "PixelPaper", + "Company Link ": "https://pixelpaper.io" + }, + { + "Company Name": "Ponly", + "Company Link ": "https://ponly.com" + }, + { + "Company Name": "PowerToFly", + "Company Link ": "https://powertofly.com" + }, + { + "Company Name": "Pretzel Rocks", + "Company Link ": "https://pretzel.rocks" + }, + { + "Company Name": "PrintIt", + "Company Link ": "https://printit.work" + }, + { + "Company Name": "Prismatext", + "Company Link ": "https://prismatext.com" + }, + { + "Company Name": "Puffin Maps", + "Company Link ": "https://puffinmaps.com" + }, + { + "Company Name": "Puzzle Loop ", + "Company Link ": "https://puzzle-loop.com" + }, + { + "Company Name": "QuoteMaster", + "Company Link ": "https://quotemaster.org" + }, + { + "Company Name": "Radio Garden", + "Company Link ": "https://radio.garden" + }, + { + "Company Name": "Radiooooo", + "Company Link ": "https://radiooooo.com" + }, + { + "Company Name": "Radiosondy", + "Company Link ": "https://radiosondy.info" + }, + { + "Company Name": "Rainy Mood", + "Company Link ": "https://rainymood.com" + }, + { + "Company Name": "Random Street View", + "Company Link ": "https://randomstreetview.com" + }, + { + "Company Name": "Rap4Ever", + "Company Link ": "https://rap4all.com" + }, + { + "Company Name": "RareFilm", + "Company Link ": "https://rarefilm.net" + }, + { + "Company Name": "Rattibha", + "Company Link ": "https://rattibha.com" + }, + { + "Company Name": "Reddit List ", + "Company Link ": "https://redditlist.com" + }, + { + "Company Name": "RedditSearch.io", + "Company Link ": "https://redditsearch.io" + }, + { + "Company Name": "Reelgood", + "Company Link ": "https://reelgood.com" + }, + { + "Company Name": "Reface", + "Company Link ": "https://reface.ai" + }, + { + "Company Name": "Rejected.us", + "Company Link ": "https://rejected.us" + }, + { + "Company Name": "Relanote", + "Company Link ": "https://relanote.com" + }, + { + "Company Name": "Remote Leaf", + "Company Link ": "https://remoteleaf.com" + }, + { + "Company Name": "Remote OK", + "Company Link ": "https://remoteok.com" + }, + { + "Company Name": "Remote Starter Kit ", + "Company Link ": "https://remotestarterkit.com" + }, + { + "Company Name": "Remote.co", + "Company Link ": "https://remote.co" + }, + { + "Company Name": "Remote Base ", + "Company Link ": "https://remotebase.com" + }, + { + "Company Name": "Remote Bear", + "Company Link ": "https://remotebear.io" + }, + { + "Company Name": "Remove.bg", + "Company Link ": "https://remove.bg" + }, + { + "Company Name": "Respresso", + "Company Link ": "https://respresso.io" + }, + { + "Company Name": "Reveddit", + "Company Link ": "https://reveddit.com" + }, + { + "Company Name": "Rhymer", + "Company Link ": "https://rhymer.com" + }, + { + "Company Name": "RhymeZone", + "Company Link ": "https://rhymezone.com" + }, + { + "Company Name": "Ribbet", + "Company Link ": "https://ribbet.com" + }, + { + "Company Name": "Roadmap.sh", + "Company Link ": "https://roadmap.sh" + }, + { + "Company Name": "Roadtrippers", + "Company Link ": "https://roadtrippers.com" + }, + { + "Company Name": "RxResu.me", + "Company Link ": "https://rxresu.me" + }, + { + "Company Name": "SchemeColor", + "Company Link ": "https://schemecolor.com" + }, + { + "Company Name": "Screenshot.Guru", + "Company Link ": "https://screenshot.guru" + }, + { + "Company Name": "SeatGuru", + "Company Link ": "https://seatguru.com" + }, + { + "Company Name": "Sessions", + "Company Link ": "https://sessions.us" + }, + { + "Company Name": "Shottr", + "Company Link ": "https://shottr.cc" + }, + { + "Company Name": "Signature Maker", + "Company Link ": "https://signature-maker.net" + }, + { + "Company Name": "Skip The Drive", + "Company Link ": "https://skipthedrive.com" + }, + { + "Company Name": "Slowby", + "Company Link ": "https://slowby.travel" + }, + { + "Company Name": "Small World", + "Company Link ": "https://smallworld.kiwi" + }, + { + "Company Name": "SmallPDF", + "Company Link ": "https://smallpdf.com" + }, + { + "Company Name": "Social Image Maker", + "Company Link ": "https://socialimagemaker.io" + }, + { + "Company Name": "Social Sizes", + "Company Link ": "https://socialsizes.io" + }, + { + "Company Name": "SoundLove", + "Company Link ": "https://soundlove.se" + }, + { + "Company Name": "Spline", + "Company Link ": "https://spline.design" + }, + { + "Company Name": "Starkey Comics", + "Company Link ": "https://starkeycomics.com" + }, + { + "Company Name": "Startups List", + "Company Link ": "https://startups-list.com" + }, + { + "Company Name": "Statista", + "Company Link ": "https://statista.com" + }, + { + "Company Name": "Stolen Camera Finder", + "Company Link ": "https://stolencamerafinder.com" + }, + { + "Company Name": "Strobe.Cool", + "Company Link ": "https://strobe.cool" + }, + { + "Company Name": "Sumo", + "Company Link ": "https://sumo.app" + }, + { + "Company Name": "SuperMeme AI", + "Company Link ": "https://supermeme.ai" + }, + { + "Company Name": "Synthesia", + "Company Link ": "https://synthesia.io" + }, + { + "Company Name": "TablerIcons", + "Company Link ": "https://tablericons.com" + }, + { + "Company Name": "Tango", + "Company Link ": "https://tango.us" + }, + { + "Company Name": "TasteDive", + "Company Link ": "https://tastedive.com" + }, + { + "Company Name": "TechSpecs", + "Company Link ": "https://techspecs.io" + }, + { + "Company Name": "Teoria", + "Company Link ": "https://teoria.com" + }, + { + "Company Name": "Text Faces", + "Company Link ": "https://textfac.es" + }, + { + "Company Name": "The Balance Money", + "Company Link ": "https://thebalancemoney.com" + }, + { + "Company Name": "The Punctuation Guide", + "Company Link ": "https://thepunctuationguide.com" + }, + { + "Company Name": "This to That", + "Company Link ": "https://thistothat.com" + }, + { + "Company Name": "This vs That", + "Company Link ": "https://thisvsthat.io" + }, + { + "Company Name": "ThreadReaderApp ", + "Company Link ": "https://threadreaderapp.com" + }, + { + "Company Name": "Thumbly", + "Company Link ": "https://tokee.ai" + }, + { + "Company Name": "Tiii.me", + "Company Link ": "https://tiii.me" + }, + { + "Company Name": "TikTok Video Downloader", + "Company Link ": "https://ttvdl.com" + }, + { + "Company Name": "Time and Date", + "Company Link ": "https://timeanddate.com" + }, + { + "Company Name": "Time.is", + "Company Link ": "https://time.is" + }, + { + "Company Name": "Title Case", + "Company Link ": "https://titlecase.com" + }, + { + "Company Name": "Toaster Central", + "Company Link ": "https://toastercentral.com" + }, + { + "Company Name": "Tongue-Twister ", + "Company Link ": "https://tongue-twister.net" + }, + { + "Company Name": "TradingView", + "Company Link ": "https://tradingview.com" + }, + { + "Company Name": "Transparent Textures", + "Company Link ": "https://transparenttextures.com" + }, + { + "Company Name": "Tubi TV", + "Company Link ": "https://tubitv.com" + }, + { + "Company Name": "Tunefind", + "Company Link ": "https://tunefind.com" + }, + { + "Company Name": "TuneMyMusic", + "Company Link ": "https://tunemymusic.com" + }, + { + "Company Name": "Tweepsmap", + "Company Link ": "https://fedica.com" + }, + { + "Company Name": "Two Peas and Their Pod", + "Company Link ": "https://twopeasandtheirpod.com" + }, + { + "Company Name": "Typatone", + "Company Link ": "https://typatone.com" + }, + { + "Company Name": "Under Glass", + "Company Link ": "https://underglass.io" + }, + { + "Company Name": "UniCorner", + "Company Link ": "https://unicorner.news" + }, + { + "Company Name": "Unita", + "Company Link ": "https://unita.co" + }, + { + "Company Name": "UnitConverters", + "Company Link ": "https://unitconverters.net" + }, + { + "Company Name": "Unreadit", + "Company Link ": "https://unreadit.com" + }, + { + "Company Name": "Unscreen", + "Company Link ": "https://unscreen.com" + }, + { + "Company Name": "UnTools ", + "Company Link ": "https://untools.co" + }, + { + "Company Name": "Upwork", + "Company Link ": "https://upwork.com" + }, + { + "Company Name": "UTF8 Icons", + "Company Link ": "https://utf8icons.com" + }, + { + "Company Name": "Vector Magic", + "Company Link ": "https://vectormagic.com" + }, + { + "Company Name": "Virtual Vacation", + "Company Link ": "https://virtualvacation.us" + }, + { + "Company Name": "Virtual Vocations", + "Company Link ": "https://virtualvocations.com" + }, + { + "Company Name": "Visiwig", + "Company Link ": "https://visiwig.com" + }, + { + "Company Name": "Visual CV", + "Company Link ": "https://visualcv.com" + }, + { + "Company Name": "Vocus.io", + "Company Link ": "https://vocus.io" + }, + { + "Company Name": "Voscreen", + "Company Link ": "https://voscreen.com" + }, + { + "Company Name": "Wanderprep", + "Company Link ": "https://wanderprep.com" + }, + { + "Company Name": "Warmshowers", + "Company Link ": "https:/warmshowers.org" + }, + { + "Company Name": "Watch Documentaries", + "Company Link ": "https://watchdocumentaries.com" + }, + { + "Company Name": "We Work Remotely", + "Company Link ": "https://weworkremotely.com" + }, + { + "Company Name": "Web2PDFConvert", + "Company Link ": "https://web2pdfconvert.com" + }, + { + "Company Name": "Welcome to My Garden", + "Company Link ": "https://welcometomygarden.org" + }, + { + "Company Name": "When2meet ", + "Company Link ": "https://when2meet.com" + }, + { + "Company Name": "Where's George", + "Company Link ": "https://wheresgeorge.com" + }, + { + "Company Name": "Where's Willy", + "Company Link ": "https://whereswilly.com" + }, + { + "Company Name": "WikiHow", + "Company Link ": "https://wikihow.com" + }, + { + "Company Name": "Windy", + "Company Link ": "https://www.windy.com" + }, + { + "Company Name": "WonderHowTo", + "Company Link ": "https://wonderhowto.com" + }, + { + "Company Name": "Working Nomads", + "Company Link ": "https://workingnomads.com" + }, + { + "Company Name": "Wormhole", + "Company Link ": "https://wormhole.app" + }, + { + "Company Name": "Y Combinator Jobs", + "Company Link ": "https://ycombinator.com" + }, + { + "Company Name": "Yes Promo", + "Company Link ": "https://yespromo.me" + }, + { + "Company Name": "YouGlish", + "Company Link ": "https://youglish.com" + }, + { + "Company Name": "Zamzar", + "Company Link ": "https://zamzar.com" + }, + { + "Company Name": "Zippyshare", + "Company Link ": "https://zippyshare.com" + }, + { + "Company Name": "Zoom Earth", + "Company Link ": "https://zoom.earth" + }, + { + "Company Name": "Zoom.it", + "Company Link ": "https://zoom.it" + } +] \ No newline at end of file From ada89f9972fca055b42034165e1a63bebfd2dbe9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 15:51:45 -0700 Subject: [PATCH 67/96] Extract incidents skeleton --- Client/src/Pages/Incidents/index.jsx | 35 ++----------------------- Client/src/Pages/Incidents/skeleton.jsx | 30 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 33 deletions(-) create mode 100644 Client/src/Pages/Incidents/skeleton.jsx diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 5d756298e..b61b04e16 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -1,43 +1,12 @@ import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import { - ButtonGroup, - Stack, - Skeleton, - Typography, - Button, -} from "@mui/material"; +import { ButtonGroup, Stack, Typography, Button } from "@mui/material"; import { networkService } from "../../main"; import { useTheme } from "@emotion/react"; import Select from "../../Components/Inputs/Select"; import IncidentTable from "./IncidentTable"; import "./index.css"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - ); -}; +import SkeletonLayout from "./skeleton"; const Incidents = () => { const theme = useTheme(); diff --git a/Client/src/Pages/Incidents/skeleton.jsx b/Client/src/Pages/Incidents/skeleton.jsx new file mode 100644 index 000000000..d1571ad3a --- /dev/null +++ b/Client/src/Pages/Incidents/skeleton.jsx @@ -0,0 +1,30 @@ +import { Stack, Skeleton } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +/** + * Renders a skeleton layout. + * + * @returns {JSX.Element} + */ +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + <> + + + + + + + + + ); +}; + +export default SkeletonLayout; From 717923fff4bbf3cfbd119d25b1decf3bda8ed237 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 16:15:14 -0700 Subject: [PATCH 68/96] Add monitor table component --- .../Pages/Incidents/IncidentTable/index.jsx | 2 - .../Monitors/Home/MonitorTable/index.jsx | 117 ++++++++++++++++++ Client/src/Pages/Monitors/Home/index.jsx | 5 +- 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 Client/src/Pages/Monitors/Home/MonitorTable/index.jsx diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index aa80b35ce..d10c79c68 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -15,14 +15,12 @@ import { import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; - import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; import { networkService } from "../../../main"; import { StatusLabel } from "../../../Components/Label"; import { logger } from "../../../Utils/Logger"; import { useTheme } from "@emotion/react"; - const IncidentTable = ({ monitors, selectedMonitor, filter }) => { const theme = useTheme(); const { authToken, user } = useSelector((state) => state.auth); diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx new file mode 100644 index 000000000..5119618c3 --- /dev/null +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -0,0 +1,117 @@ +import PropTypes from "prop-types"; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + Pagination, + PaginationItem, + Paper, + Box, +} from "@mui/material"; +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; +import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; +import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; + +import { useState, useEffect } from "react"; +import { logger } from "../../../../Utils/Logger"; +import Host from "../host"; +import { StatusLabel } from "../../../../Components/Label"; +const MonitorTable = ({ teamId }) => { + const [paginationController, setPaginationController] = useState({ + page: 0, + rowsPerPage: 14, + }); + const [monitors, setMonitors] = useState([]); + const [monitorCount, setMonitorCount] = useState(0); + + const handlePageChange = (_, newPage) => { + setPaginationController({ + ...paginationController, + page: newPage - 1, // 0-indexed + }); + }; + + useEffect(() => { + const fetchPage = async () => { + if (!monitors || Object.keys(monitors).length === 0) { + return; + } + try { + console.log("DO NETWORK"); + + const res = await networkService.getMonitorsByTeamId( + token, + user.teamId, + 25, + ["http", "ping"], + null, + "desc", + true + ); + + setMonitors([]); + setMonitorCount(0); + } catch (error) { + logger.error(error); + } + }; + fetchPage(); + }, [monitors]); + + let paginationComponent = <>; + if (monitorCount > paginationController.rowsPerPage) { + paginationComponent = ( + ( + + )} + /> + ); + } + + return ( + <> + + + + + Host + + {" "} + + Status + + + + + + Response Time + Type + Actions + + + +
    +
    + {paginationComponent} + + ); +}; + +MonitorTable.propTypes = { + teamId: PropTypes.string, +}; +export default MonitorTable; diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index e5c9a765f..af4e9faef 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -4,7 +4,6 @@ import { useSelector, useDispatch } from "react-redux"; import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@emotion/react"; -import BasicTable from "../../../Components/BasicTable"; import { Box, Button, Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import SkeletonLayout from "./skeleton"; @@ -13,6 +12,7 @@ import StatusBox from "./StatusBox"; import { buildData } from "./monitorData"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import Greeting from "../../../Utils/greeting"; +import MonitorTable from "./MonitorTable"; const Monitors = ({ isAdmin }) => { const theme = useTheme(); @@ -119,7 +119,8 @@ const Monitors = ({ isAdmin }) => {
    {/* TODO - add search bar */}
    - + + {/* */}
    )} From 2a1afc8610d8ecf7391ff98c413b30b8e7537983 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 19:17:12 -0400 Subject: [PATCH 69/96] Implemented basic gauge chart --- .../Charts/MonitorDetails60MinChart/index.jsx | 53 ----------- .../Pages/Monitors/Details/Charts/index.jsx | 90 ++++++++++++++++++- Client/src/Pages/Monitors/Details/index.jsx | 9 +- Client/src/Pages/Monitors/Details/styled.jsx | 1 - Server/db/mongo/modules/monitorModule.js | 19 +++- 5 files changed, 109 insertions(+), 63 deletions(-) delete mode 100644 Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx diff --git a/Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx b/Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx deleted file mode 100644 index 57cb344e7..000000000 --- a/Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import { BarChart, Bar, Cell, ReferenceLine, Label } from "recharts"; - -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; - -const MonitorDetails60MinChart = ({ data }) => { - const theme = useTheme(); - - const labelStyle = { - fontSize: "10px", - fill: theme.palette.text.tertiary, - }; - - const color = { - true: theme.palette.success.main, - false: theme.palette.error.text, - undefined: theme.palette.unresolved.main, - }; - return ( - - - {data.map((check, index) => ( - - ))} - - - - - - - - - ); -}; - -MonitorDetails60MinChart.propTypes = { - data: PropTypes.array.isRequired, -}; - -export default MonitorDetails60MinChart; diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index 2927a6cc0..c390a3699 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -6,6 +6,8 @@ import { CartesianGrid, ResponsiveContainer, Cell, + RadialBarChart, + RadialBar, } from "recharts"; import { formatDate } from "../../../../Utils/timeUtils"; import { useState } from "react"; @@ -28,10 +30,10 @@ const CustomLabels = ({ return ( <> - + {formatDate(new Date(firstDataPoint.time), options)} - + {formatDate(new Date(lastDataPoint.time), options)} @@ -186,3 +188,87 @@ export const DownBarChart = ({ data, type, onBarHover }) => { ); }; + +export const ResponseGaugeChart = ({ data }) => { + const theme = useTheme(); + + let max = 1000; // max ms + data = [{ response: max, fill: "transparent", background: false }, ...data]; + + let responseTime = Math.floor(data[1].response); + let responseProps = + responseTime <= 200 + ? { + category: "Excellent", + main: theme.palette.success.main, + bg: theme.palette.success.bg, + } + : responseTime <= 500 + ? { + category: "Fair", + main: theme.palette.success.main, + bg: theme.palette.success.bg, + } + : responseTime <= 600 + ? { + category: "Acceptable", + main: theme.palette.warning.main, + bg: theme.palette.warning.bg, + } + : { + category: "Poor", + main: theme.palette.error.text, + bg: theme.palette.error.bg, + }; + + return ( + + + + low + + + high + + + {responseProps.category} + + + {responseTime}{" "} + ms + + + + + + + + ); +}; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 46d4772d7..dff958dd6 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -32,7 +32,7 @@ import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import { StatBox, ChartBox, IconBox } from "./styled"; -import { DownBarChart, UpBarChart } from "./Charts"; +import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts"; import SkeletonLayout from "./skeleton"; import "./index.css"; @@ -133,8 +133,6 @@ const DetailsPage = ({ isAdmin }) => { undefined: "Pending...", }; - console.log(monitor); - return ( {loading ? ( @@ -463,7 +461,7 @@ const DetailsPage = ({ isAdmin }) => { onBarHover={setHoveredIncidentsData} /> - + @@ -472,6 +470,9 @@ const DetailsPage = ({ isAdmin }) => { Average Response Time + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 6c3f8eb04..b8de5edfc 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -37,7 +37,6 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ alignItems: "center", }, "& tspan, & text": { - fontSize: 11, fill: theme.palette.text.tertiary, }, "& path": { diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index ab867ff47..9451b6187 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -182,8 +182,9 @@ const getMonitorStatsById = async (req) => { if (monitor.type === "http" || monitor.type === "ping") { // HTTP/PING Specific stats - monitorStats.avgResponseTime = getAverageResponseTime(checksForDateRange); - monitorStats.uptime = getUptimePercentage(checksForDateRange); + monitorStats.periodAvgResponseTime = + getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = getUptimePercentage(checksForDateRange); // Aggregate data let groupedChecks; @@ -198,11 +199,21 @@ const getMonitorStatsById = async (req) => { acc[time].checks.push(check); return acc; }, {}); + } else { + groupedChecks = checksForDateRange.reduce((acc, check) => { + const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); } // Map grouped checks to stats const aggregateData = Object.values(groupedChecks).map((group) => { const totalChecks = group.checks.length; + const uptimePercentage = getUptimePercentage(group.checks); const totalIncidents = group.checks.filter( (check) => check.status === false ).length; @@ -212,6 +223,7 @@ const getMonitorStatsById = async (req) => { return { time: group.time, + uptimePercentage, totalChecks, totalIncidents, avgResponseTime, @@ -220,7 +232,8 @@ const getMonitorStatsById = async (req) => { monitorStats.aggregateData = aggregateData; } - monitorStats.incidents = getIncidents(checksForDateRange); + monitorStats.periodIncidents = getIncidents(checksForDateRange); + monitorStats.periodTotalChecks = checksForDateRange.length; // If more than numToDisplay checks, pick every nth check From d32a2a7efa467902c33ab37dae84fec6c4a9858e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 17:37:30 -0700 Subject: [PATCH 70/96] Add pagination to --- .../Monitors/Home/MonitorTable/index.jsx | 22 +++++++++++-------- Client/src/Utils/NetworkService.js | 6 ++++- Server/db/mongo/modules/monitorModule.js | 13 +++++++++-- Server/validation/joi.js | 2 ++ 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index 5119618c3..421b7d90c 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -19,6 +19,10 @@ import { useState, useEffect } from "react"; import { logger } from "../../../../Utils/Logger"; import Host from "../host"; import { StatusLabel } from "../../../../Components/Label"; +import { jwtDecode } from "jwt-decode"; +import { useSelector } from "react-redux"; +import { networkService } from "../../../../main"; + const MonitorTable = ({ teamId }) => { const [paginationController, setPaginationController] = useState({ page: 0, @@ -26,6 +30,7 @@ const MonitorTable = ({ teamId }) => { }); const [monitors, setMonitors] = useState([]); const [monitorCount, setMonitorCount] = useState(0); + const authState = useSelector((state) => state.auth); const handlePageChange = (_, newPage) => { setPaginationController({ @@ -36,22 +41,21 @@ const MonitorTable = ({ teamId }) => { useEffect(() => { const fetchPage = async () => { - if (!monitors || Object.keys(monitors).length === 0) { - return; - } try { - console.log("DO NETWORK"); - + const { authToken } = authState; + const user = jwtDecode(authToken); const res = await networkService.getMonitorsByTeamId( - token, + authToken, user.teamId, 25, ["http", "ping"], null, "desc", - true + true, + paginationController.page, + paginationController.rowsPerPage ); - + console.log(res.data.data); setMonitors([]); setMonitorCount(0); } catch (error) { @@ -59,7 +63,7 @@ const MonitorTable = ({ teamId }) => { } }; fetchPage(); - }, [monitors]); + }, []); let paginationComponent = <>; if (monitorCount > paginationController.rowsPerPage) { diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 6dcde82bb..e983aa5f6 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -80,7 +80,9 @@ class NetworkService { types, status, sortOrder, - normalize + normalize, + page, + rowsPerPage ) { const params = new URLSearchParams(); @@ -93,6 +95,8 @@ class NetworkService { if (status) params.append("status", status); if (sortOrder) params.append("sortOrder", sortOrder); if (normalize) params.append("normalize", normalize); + if (page) params.append("page", page); + if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); return this.axiosInstance.get( `/monitors/team/${teamId}?${params.toString()}`, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index b913519b6..2aba60c73 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -393,9 +393,16 @@ const getMonitorById = async (monitorId) => { */ const getMonitorsByTeamId = async (req, res) => { try { - let { limit, type, status, sortOrder, normalize } = req.query || {}; + let { limit, type, status, sortOrder, normalize, page, rowsPerPage } = + req.query || {}; const monitorQuery = { teamId: req.params.teamId }; + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + if (type !== undefined) { const types = Array.isArray(type) ? type : [type]; monitorQuery.type = { $in: types }; @@ -411,7 +418,9 @@ const getMonitorsByTeamId = async (req, res) => { // This effectively removes limit, returning all checks if (limit === undefined) limit = 0; - const monitors = await Monitor.find(monitorQuery); + const monitors = await Monitor.find(monitorQuery) + .skip(skip) + .limit(rowsPerPage); // Map each monitor to include its associated checks const monitorsWithChecks = await Promise.all( monitors.map(async (monitor) => { diff --git a/Server/validation/joi.js b/Server/validation/joi.js index d0ef24751..090a49a7a 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -176,6 +176,8 @@ const getMonitorsByTeamIdQueryValidation = joi.object({ joi.string().valid("http", "ping", "pagespeed"), joi.array().items(joi.string().valid("http", "ping", "pagespeed")) ), + page: joi.number(), + rowsPerPage: joi.number(), }); const getMonitorStatsByIdParamValidation = joi.object({ From 6aef23e8a456e5ed7228f88c76d791b9b232a663 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 17:41:28 -0700 Subject: [PATCH 71/96] Use correct array for normalization --- Server/db/mongo/modules/monitorModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 558fbae4d..bfa4bd73a 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -249,7 +249,7 @@ const getMonitorStatsById = async (req) => { // Normalize checks if requested if (normalize !== undefined) { - const normailzedChecks = NormalizeData(checksForDateRange, 1, 100); + const normailzedChecks = NormalizeData(nthChecks, 1, 100); monitorStats.checks = normailzedChecks; } else { monitorStats.checks = nthChecks; From 7e01006cef0438b40b224794b7d4d603a119f7a5 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 21:09:18 -0400 Subject: [PATCH 72/96] Added gradient to area chart --- .../Charts/MonitorDetailsAreaChart/index.jsx | 20 +++++++++++++++-- .../Pages/Monitors/Details/Charts/index.jsx | 22 ++++++++++++------- Client/src/Pages/Monitors/Details/index.jsx | 8 +++---- Server/db/mongo/modules/monitorModule.js | 2 +- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 4e5d1d7b0..5b6b0dd74 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -64,6 +64,8 @@ const MonitorDetailsAreaChart = ({ checks }) => { }); }; + const theme = useTheme(); + return ( { bottom: 0, }} > + + + + + + { diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index c390a3699..b0382f958 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -54,12 +54,15 @@ export const UpBarChart = ({ data, type, onBarHover }) => { : { main: theme.palette.error.text, light: theme.palette.error.light }; }; + // TODO - REMOVE THIS LATER + let reversedData = [...data].reverse(); + return ( { setChartHovered(true); onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 }); @@ -80,8 +83,8 @@ export const UpBarChart = ({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={data[0]} - lastDataPoint={data[data.length - 1]} + firstDataPoint={reversedData[0]} + lastDataPoint={reversedData[reversedData.length - 1]} type={type} /> } @@ -91,7 +94,7 @@ export const UpBarChart = ({ data, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {data.map((entry, index) => { + {reversedData.map((entry, index) => { let { main, light } = getColorRange(entry.uptimePercentage); return ( { const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + // TODO - REMOVE THIS LATER + let reversedData = [...data].reverse(); + return ( { setChartHovered(true); onBarHover({ time: null, totalIncidents: 0 }); @@ -152,8 +158,8 @@ export const DownBarChart = ({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={data[0]} - lastDataPoint={data[data.length - 1]} + firstDataPoint={reversedData[0]} + lastDataPoint={reversedData[reversedData.length - 1]} type={type} /> } @@ -163,7 +169,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {data.map((entry, index) => ( + {reversedData.map((entry, index) => ( { Response Times - - {/* */} - + { // Normalize checks if requested if (normalize !== undefined) { - const normailzedChecks = NormalizeData(checksForDateRange, 1, 100); + const normailzedChecks = NormalizeData(nthChecks, 1, 100); monitorStats.checks = normailzedChecks; } else { monitorStats.checks = nthChecks; From 1212a273aaa4f8cf0f7a05c2ea2e32c9ea5cce91 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 18:22:40 -0700 Subject: [PATCH 73/96] Add table cells --- .../Monitors/Home/MonitorTable/index.jsx | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index 421b7d90c..669624025 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -22,8 +22,13 @@ import { StatusLabel } from "../../../../Components/Label"; import { jwtDecode } from "jwt-decode"; import { useSelector } from "react-redux"; import { networkService } from "../../../../main"; +import { useTheme } from "@emotion/react"; +import BarChart from "../../../../Components/Charts/BarChart"; +import ActionsMenu from "../actionsMenu"; + +const MonitorTable = ({ isAdmin }) => { + const theme = useTheme(); -const MonitorTable = ({ teamId }) => { const [paginationController, setPaginationController] = useState({ page: 0, rowsPerPage: 14, @@ -56,7 +61,7 @@ const MonitorTable = ({ teamId }) => { paginationController.rowsPerPage ); console.log(res.data.data); - setMonitors([]); + setMonitors(res?.data?.data ?? []); setMonitorCount(0); } catch (error) { logger.error(error); @@ -107,7 +112,68 @@ const MonitorTable = ({ teamId }) => { Actions - + + {monitors.map((monitor) => { + let uptimePercentage = ""; + let percentageColor = theme.palette.percentage.uptimeExcellent; + + // Determine uptime percentage and color based on the monitor's uptimePercentage value + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.percentage.uptimePoor + : monitor.uptimePercentage < 0.5 + ? theme.palette.percentage.uptimeFair + : monitor.uptimePercentage < 0.75 + ? theme.palette.percentage.uptimeGood + : theme.palette.percentage.uptimeExcellent; + } + + const params = { + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + status: + monitor.status === undefined + ? "pending" + : monitor.status === true + ? "up" + : "down", + }; + + return ( + + + + + + + + + + + + + {monitor.type} + + + + + + + ); + })} + {paginationComponent} @@ -115,7 +181,4 @@ const MonitorTable = ({ teamId }) => { ); }; -MonitorTable.propTypes = { - teamId: PropTypes.string, -}; export default MonitorTable; From f689b304c86231968439c54aeb35d47a7669c4cf Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 21:41:16 -0400 Subject: [PATCH 74/96] Fixed area chart tooltip --- .../Charts/MonitorDetailsAreaChart/index.jsx | 61 +++++++++++++------ Client/src/Pages/Monitors/Details/index.jsx | 8 ++- Client/src/Pages/Monitors/Details/styled.jsx | 4 +- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 5b6b0dd74..68fdb1cd4 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { AreaChart, Area, XAxis, Tooltip, ResponsiveContainer } from "recharts"; -import { Box, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import "./index.css"; @@ -16,14 +16,15 @@ const CustomToolTip = ({ active, payload, label }) => { border: 1, borderColor: theme.palette.border.dark, borderRadius: theme.shape.borderRadius, - py: theme.spacing(6), - px: theme.spacing(8), + py: theme.spacing(2), + px: theme.spacing(4), }} > {new Date(label).toLocaleDateString("en-US", { @@ -38,15 +39,39 @@ const CustomToolTip = ({ active, payload, label }) => { hour12: true, // AM/PM format })} - - Response Time (ms): {payload[0].payload.originalResponseTime} - {" "} + + + + + Response Time + {" "} + + {payload[0].payload.originalResponseTime} + + {" "} + ms + + + + {/* Display original value */} ); @@ -67,10 +92,10 @@ const MonitorDetailsAreaChart = ({ checks }) => { const theme = useTheme(); return ( - + { } /> { data={[{ response: monitor?.periodAvgResponseTime }]} /> - + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index b8de5edfc..99112802d 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -16,11 +16,11 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ fontSize: 15, fontWeight: 500, }, - "& p": { + "& .MuiBox-root:not(.area-tooltip) p": { color: theme.palette.text.tertiary, fontSize: 13, }, - "& span": { + "& .MuiBox-root > span": { color: theme.palette.text.primary, fontSize: 20, "& span": { From bc7f110b019c3995e30e3a01dfbecc3f4ff513bc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 18:42:50 -0700 Subject: [PATCH 75/96] Add update hook --- .../Monitors/Home/MonitorTable/index.jsx | 27 +++++++++++++++---- .../src/Pages/Monitors/Home/actionsMenu.jsx | 4 ++- Client/src/Pages/Monitors/Home/index.jsx | 8 ++---- Server/db/mongo/modules/monitorModule.js | 3 ++- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index 669624025..dae082379 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -36,6 +36,11 @@ const MonitorTable = ({ isAdmin }) => { const [monitors, setMonitors] = useState([]); const [monitorCount, setMonitorCount] = useState(0); const authState = useSelector((state) => state.auth); + const [updateTrigger, setUpdateTrigger] = useState(false); + + const handleActionMenuDelete = () => { + setUpdateTrigger((prev) => !prev); + }; const handlePageChange = (_, newPage) => { setPaginationController({ @@ -60,15 +65,19 @@ const MonitorTable = ({ isAdmin }) => { paginationController.page, paginationController.rowsPerPage ); - console.log(res.data.data); - setMonitors(res?.data?.data ?? []); - setMonitorCount(0); + setMonitors(res?.data?.data?.monitors ?? []); + setMonitorCount(res?.data?.data?.monitorCount ?? 0); } catch (error) { logger.error(error); } }; fetchPage(); - }, []); + }, [ + updateTrigger, + authState, + paginationController.page, + paginationController.rowsPerPage, + ]); let paginationComponent = <>; if (monitorCount > paginationController.rowsPerPage) { @@ -168,7 +177,11 @@ const MonitorTable = ({ isAdmin }) => { - + ); @@ -181,4 +194,8 @@ const MonitorTable = ({ isAdmin }) => { ); }; +MonitorTable.propTypes = { + isAdmin: PropTypes.bool, +}; + export default MonitorTable; diff --git a/Client/src/Pages/Monitors/Home/actionsMenu.jsx b/Client/src/Pages/Monitors/Home/actionsMenu.jsx index 7d342f5d7..efb29402d 100644 --- a/Client/src/Pages/Monitors/Home/actionsMenu.jsx +++ b/Client/src/Pages/Monitors/Home/actionsMenu.jsx @@ -19,7 +19,7 @@ import { import Settings from "../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -const ActionsMenu = ({ monitor, isAdmin }) => { +const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => { const [anchorEl, setAnchorEl] = useState(null); const [actions, setActions] = useState({}); const [isOpen, setIsOpen] = useState(false); @@ -37,6 +37,7 @@ const ActionsMenu = ({ monitor, isAdmin }) => { if (action.meta.requestStatus === "fulfilled") { setIsOpen(false); // close modal dispatch(getUptimeMonitorsByTeamId(authState.authToken)); + updateCallback(); createToast({ body: "Monitor deleted successfully." }); } else { createToast({ body: "Failed to delete monitor." }); @@ -222,6 +223,7 @@ ActionsMenu.propTypes = { type: PropTypes.string, }).isRequired, isAdmin: PropTypes.bool, + updateCallback: PropTypes.func, }; export default ActionsMenu; diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index af4e9faef..43ed7f94c 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -9,7 +9,6 @@ import PropTypes from "prop-types"; import SkeletonLayout from "./skeleton"; import Fallback from "./fallback"; import StatusBox from "./StatusBox"; -import { buildData } from "./monitorData"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import Greeting from "../../../Utils/greeting"; import MonitorTable from "./MonitorTable"; @@ -25,7 +24,7 @@ const Monitors = ({ isAdmin }) => { dispatch(getUptimeMonitorsByTeamId(authState.authToken)); }, [authState.authToken, dispatch]); - const monitorStats = monitorState.monitors.reduce( + const monitorStats = monitorState.monitors.monitors.reduce( (acc, monitor) => { if (monitor.isActive === false) { acc["paused"] += 1; @@ -39,8 +38,6 @@ const Monitors = ({ isAdmin }) => { { paused: 0, up: 0, down: 0 } ); - const data = buildData(monitorState.monitors, isAdmin, navigate); - let loading = monitorState.isLoading && monitorState.monitors.length === 0; return ( @@ -119,8 +116,7 @@ const Monitors = ({ isAdmin }) => { {/* TODO - add search bar */} - - {/* */} + )} diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 2aba60c73..4d312dd2c 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -396,6 +396,7 @@ const getMonitorsByTeamId = async (req, res) => { let { limit, type, status, sortOrder, normalize, page, rowsPerPage } = req.query || {}; const monitorQuery = { teamId: req.params.teamId }; + const monitorsCount = await Monitor.countDocuments(monitorQuery); // Pagination let skip = 0; @@ -449,7 +450,7 @@ const getMonitorsByTeamId = async (req, res) => { return { ...monitor.toObject(), checks }; }) ); - return monitorsWithChecks; + return { monitors: monitorsWithChecks, monitorCount: monitorsCount }; } catch (error) { throw error; } From 52b5ac82a545f842ef916326cb65b58b46e9208b Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Wed, 4 Sep 2024 21:50:26 -0400 Subject: [PATCH 76/96] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9cbd37355..e050b221e 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,8 @@

    An open source server monitoring application

    -https://github.com/user-attachments/assets/c8234cce-edf8-4d7e-8f6b-f18b06669fcc - - +![Dashboard-dark](https://github.com/user-attachments/assets/db875138-164f-453c-a75e-889f88747578) +(yes, we have a light theme as well, but this looks better on readme.md) BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time. From 25c08ad9a40060d4624d65d7552aa7daa2271b85 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 21:59:16 -0400 Subject: [PATCH 77/96] Fixed key props --- Client/src/Pages/Monitors/Details/Charts/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index b0382f958..1d0bb3bf2 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -98,7 +98,7 @@ export const UpBarChart = ({ data, type, onBarHover }) => { let { main, light } = getColorRange(entry.uptimePercentage); return ( { > {reversedData.map((entry, index) => ( Date: Thu, 5 Sep 2024 00:38:52 -0400 Subject: [PATCH 78/96] Fixed monitor state error and added row click back --- .../Pages/Monitors/Home/MonitorTable/index.jsx | 15 ++++++++++++++- Client/src/Pages/Monitors/Home/index.jsx | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index dae082379..d4cc08224 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -16,6 +16,7 @@ import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import { useState, useEffect } from "react"; +import { useNavigate } from "react-router"; import { logger } from "../../../../Utils/Logger"; import Host from "../host"; import { StatusLabel } from "../../../../Components/Label"; @@ -28,6 +29,7 @@ import ActionsMenu from "../actionsMenu"; const MonitorTable = ({ isAdmin }) => { const theme = useTheme(); + const navigate = useNavigate(); const [paginationController, setPaginationController] = useState({ page: 0, @@ -157,7 +159,18 @@ const MonitorTable = ({ isAdmin }) => { }; return ( - + { + navigate(`/monitors/${monitor._id}`); + }} + > diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 9de50df83..8bff60d19 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -24,7 +24,7 @@ const Monitors = ({ isAdmin }) => { dispatch(getUptimeMonitorsByTeamId(authState.authToken)); }, [authState.authToken, dispatch]); - const monitorStats = monitorState.monitors.monitors.reduce( + const monitorStats = monitorState?.monitors?.monitors?.reduce( (acc, monitor) => { if (monitor.isActive === false) { acc["paused"] += 1; @@ -111,7 +111,7 @@ const Monitors = ({ isAdmin }) => { borderColor={theme.palette.border.light} backgroundColor={theme.palette.background.accent} > - {monitorState.monitors.length} + {monitorState.monitors.monitors.length} {/* TODO - add search bar */} From 5609e54127ee09eaed900458c9b1b09c1eeceaa7 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 5 Sep 2024 01:41:39 -0400 Subject: [PATCH 79/96] Fixed pagination styling --- .../Monitors/Home/MonitorTable/index.jsx | 224 ++++++++++++++---- 1 file changed, 181 insertions(+), 43 deletions(-) diff --git a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx index d4cc08224..4a0325246 100644 --- a/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx +++ b/Client/src/Pages/Monitors/Home/MonitorTable/index.jsx @@ -6,35 +6,113 @@ import { TableRow, TableCell, TableBody, - Pagination, - PaginationItem, Paper, Box, + TablePagination, + Stack, + Typography, + Button, } from "@mui/material"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; +import { setRowsPerPage } from "../../../../Features/UI/uiSlice"; import { useState, useEffect } from "react"; import { useNavigate } from "react-router"; import { logger } from "../../../../Utils/Logger"; import Host from "../host"; import { StatusLabel } from "../../../../Components/Label"; import { jwtDecode } from "jwt-decode"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { networkService } from "../../../../main"; import { useTheme } from "@emotion/react"; import BarChart from "../../../../Components/Charts/BarChart"; +import LeftArrowDouble from "../../../../assets/icons/left-arrow-double.svg?react"; +import RightArrowDouble from "../../../../assets/icons/right-arrow-double.svg?react"; +import LeftArrow from "../../../../assets/icons/left-arrow.svg?react"; +import RightArrow from "../../../../assets/icons/right-arrow.svg?react"; +import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react"; import ActionsMenu from "../actionsMenu"; +/** + * Component for pagination actions (first, previous, next, last). + * + * @component + * @param {Object} props + * @param {number} props.count - Total number of items. + * @param {number} props.page - Current page number. + * @param {number} props.rowsPerPage - Number of rows per page. + * @param {function} props.onPageChange - Callback function to handle page change. + * + * @returns {JSX.Element} Pagination actions component. + */ +const TablePaginationActions = (props) => { + const { count, page, rowsPerPage, onPageChange } = props; + + const handleFirstPageButtonClick = (event) => { + onPageChange(event, 0); + }; + const handleBackButtonClick = (event) => { + onPageChange(event, page - 1); + }; + const handleNextButtonClick = (event) => { + onPageChange(event, page + 1); + }; + const handleLastPageButtonClick = (event) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; + + return ( + + + + + + + ); +}; + +TablePaginationActions.propTypes = { + count: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, +}; + const MonitorTable = ({ isAdmin }) => { const theme = useTheme(); const navigate = useNavigate(); + const dispatch = useDispatch(); - const [paginationController, setPaginationController] = useState({ - page: 0, - rowsPerPage: 14, - }); + const { rowsPerPage } = useSelector((state) => state.ui.monitors); + const [page, setPage] = useState(0); const [monitors, setMonitors] = useState([]); const [monitorCount, setMonitorCount] = useState(0); const authState = useSelector((state) => state.auth); @@ -44,11 +122,18 @@ const MonitorTable = ({ isAdmin }) => { setUpdateTrigger((prev) => !prev); }; - const handlePageChange = (_, newPage) => { - setPaginationController({ - ...paginationController, - page: newPage - 1, // 0-indexed - }); + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + dispatch( + setRowsPerPage({ + value: parseInt(event.target.value, 10), + table: "monitors", + }) + ); + setPage(0); }; useEffect(() => { @@ -64,8 +149,8 @@ const MonitorTable = ({ isAdmin }) => { null, "desc", true, - paginationController.page, - paginationController.rowsPerPage + page, + rowsPerPage ); setMonitors(res?.data?.data?.monitors ?? []); setMonitorCount(res?.data?.data?.monitorCount ?? 0); @@ -74,33 +159,17 @@ const MonitorTable = ({ isAdmin }) => { } }; fetchPage(); - }, [ - updateTrigger, - authState, - paginationController.page, - paginationController.rowsPerPage, - ]); + }, [updateTrigger, authState, page, rowsPerPage]); - let paginationComponent = <>; - if (monitorCount > paginationController.rowsPerPage) { - paginationComponent = ( - ( - - )} - /> - ); - } + /** + * Helper function to calculate the range of displayed rows. + * @returns {string} + */ + const getRange = () => { + let start = page * rowsPerPage + 1; + let end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount); + return `${start} - ${end}`; + }; return ( <> @@ -202,7 +271,76 @@ const MonitorTable = ({ isAdmin }) => { - {paginationComponent} + + + Showing {getRange()} of {monitorCount} monitor(s) + + + `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` + } + slotProps={{ + select: { + MenuProps: { + keepMounted: true, + PaperProps: { + className: "pagination-dropdown", + sx: { + mt: 0, + mb: theme.spacing(2), + }, + }, + transformOrigin: { vertical: "bottom", horizontal: "left" }, + anchorOrigin: { vertical: "top", horizontal: "left" }, + sx: { mt: theme.spacing(-2) }, + }, + inputProps: { id: "pagination-dropdown" }, + IconComponent: SelectorVertical, + sx: { + ml: theme.spacing(4), + mr: theme.spacing(12), + minWidth: theme.spacing(20), + textAlign: "left", + "&.Mui-focused > div": { + backgroundColor: theme.palette.background.main, + }, + }, + }, + }} + sx={{ + mt: theme.spacing(6), + color: theme.palette.text.secondary, + "& svg path": { + stroke: theme.palette.text.tertiary, + strokeWidth: 1.3, + }, + "& .MuiSelect-select": { + border: 1, + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + }, + }} + /> + ); }; From adf75b701af0c697c18b2db6150b75bd413a077d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 22:51:00 -0700 Subject: [PATCH 80/96] Pagespeed fixes --- .../UptimeMonitors/uptimeMonitorsSlice.js | 2 +- Client/src/Pages/PageSpeed/card.jsx | 78 ++++++++++ Client/src/Pages/PageSpeed/index.jsx | 145 +----------------- Client/src/Pages/PageSpeed/skeleton.jsx | 65 ++++++++ 4 files changed, 151 insertions(+), 139 deletions(-) create mode 100644 Client/src/Pages/PageSpeed/card.jsx create mode 100644 Client/src/Pages/PageSpeed/skeleton.jsx diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 895bd1493..79789e16b 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -183,7 +183,7 @@ const uptimeMonitorsSlice = createSlice({ extraReducers: (builder) => { builder // ***************************************************** - // Monitors by userId + // Monitors by teamId // ***************************************************** .addCase(getUptimeMonitorsByTeamId.pending, (state) => { diff --git a/Client/src/Pages/PageSpeed/card.jsx b/Client/src/Pages/PageSpeed/card.jsx new file mode 100644 index 000000000..22406d714 --- /dev/null +++ b/Client/src/Pages/PageSpeed/card.jsx @@ -0,0 +1,78 @@ +import { getLastChecked } from "../../Utils/monitorUtils"; +import { formatDate, formatDurationRounded } from "../../Utils/timeUtils"; +import PageSpeedIcon from "../../assets/icons/page-speed.svg?react"; +import { StatusLabel } from "../../Components/Label"; +import { Box, Grid, Stack, Typography } from "@mui/material"; +import { useNavigate } from "react-router"; +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; + +const Card = ({ data }) => { + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + .MuiStack-root": { + backgroundColor: theme.palette.background.accent, + }, + }} + > + navigate(`/pagespeed/${data._id}`)} + border={1} + borderColor={theme.palette.border.light} + borderRadius={theme.shape.borderRadius} + backgroundColor={theme.palette.background.main} + sx={{ + cursor: "pointer", + "& svg path": { stroke: theme.palette.other.icon, strokeWidth: 0.8 }, + }} + > + + + + + {data.name} + + + + + {data.url.replace(/^https?:\/\//, "")} + + + + Last checked:{" "} + + {formatDate(getLastChecked(data.checks, false))}{" "} + + ({formatDurationRounded(getLastChecked(data.checks))} ago) + + + + + + ); +}; + +Card.propTypes = { + data: PropTypes.object.isRequired, +}; + +export default Card; diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index eaaa3bc3e..8d1273b04 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -1,154 +1,23 @@ -import { Box, Button, Grid, Skeleton, Stack, Typography } from "@mui/material"; +import { Box, Button, Grid, Stack } from "@mui/material"; import { useEffect } from "react"; import { useTheme } from "@emotion/react"; -import { formatDate, formatDurationRounded } from "../../Utils/timeUtils"; -import { StatusLabel } from "../../Components/Label"; import { useDispatch, useSelector } from "react-redux"; import { getPageSpeedByTeamId } from "../../Features/PageSpeedMonitor/pageSpeedMonitorSlice"; -import PageSpeedIcon from "../../assets/icons/page-speed.svg?react"; import Fallback from "../../Components/Fallback"; import "./index.css"; import { useNavigate } from "react-router"; -import { getLastChecked } from "../../Utils/monitorUtils"; import PropTypes from "prop-types"; import Breadcrumbs from "../../Components/Breadcrumbs"; import Greeting from "../../Utils/greeting"; - -const Card = ({ data }) => { - const theme = useTheme(); - const navigate = useNavigate(); - - return ( - .MuiStack-root": { - backgroundColor: theme.palette.background.accent, - }, - }} - > - navigate(`/pagespeed/${data._id}`)} - border={1} - borderColor={theme.palette.border.light} - borderRadius={theme.shape.borderRadius} - backgroundColor={theme.palette.background.main} - sx={{ - cursor: "pointer", - "& svg path": { stroke: theme.palette.other.icon, strokeWidth: 0.8 }, - }} - > - - - - - {data.name} - - - - - {data.url.replace(/^https?:\/\//, "")} - - - - Last checked:{" "} - - {formatDate(getLastChecked(data.checks, false))}{" "} - - ({formatDurationRounded(getLastChecked(data.checks))} ago) - - - - - - ); -}; - -Card.propTypes = { - data: PropTypes.object.isRequired, -}; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - - - - - - - - - - - - - - - - ); -}; +import SkeletonLayout from "./skeleton"; +import Card from "./card"; const PageSpeed = ({ isAdmin }) => { const theme = useTheme(); const dispatch = useDispatch(); const navigate = useNavigate(); - const { authToken, user } = useSelector((state) => state.auth); + const { authToken } = useSelector((state) => state.auth); const { monitors, isLoading } = useSelector( (state) => state.pageSpeedMonitors ); @@ -158,7 +27,7 @@ const PageSpeed = ({ isAdmin }) => { // will show skeletons only on initial load // since monitor state is being added to redux persist, there's no reason to display skeletons on every render - let isActuallyLoading = isLoading && monitors.length === 0; + let isActuallyLoading = isLoading && monitors?.monitors.length === 0; return ( { > {isActuallyLoading ? ( - ) : monitors?.length !== 0 ? ( + ) : monitors?.monitors?.length !== 0 ? ( { - {monitors?.map((monitor) => ( + {monitors?.monitors?.map((monitor) => ( ))} diff --git a/Client/src/Pages/PageSpeed/skeleton.jsx b/Client/src/Pages/PageSpeed/skeleton.jsx new file mode 100644 index 000000000..2338ab636 --- /dev/null +++ b/Client/src/Pages/PageSpeed/skeleton.jsx @@ -0,0 +1,65 @@ +import { Box, Skeleton, Stack } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +/** + * Renders a skeleton layout. + * + * @returns {JSX.Element} + */ +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + + + + + + + + + + + + + + + + ); +}; + +export default SkeletonLayout; From 8ea0ef77e91857a106f5c5eee3b44832f7c6f32d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 23:09:14 -0700 Subject: [PATCH 81/96] Count monitors by type --- Server/db/mongo/modules/monitorModule.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 276c0cf6f..0b1a38140 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -305,7 +305,10 @@ const getMonitorsByTeamId = async (req, res) => { let { limit, type, status, sortOrder, normalize, page, rowsPerPage } = req.query || {}; const monitorQuery = { teamId: req.params.teamId }; - const monitorsCount = await Monitor.countDocuments(monitorQuery); + const monitorsCount = await Monitor.countDocuments({ + teamId: req.params.teamId, + type, + }); // Pagination let skip = 0; From 193e7d7371a66e4e753ca5c26e96e946f4ad10da Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 23:14:15 -0700 Subject: [PATCH 82/96] Fixed incident data access --- Client/src/Pages/Incidents/index.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index d5d8d001e..0bfc46325 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -29,11 +29,14 @@ const Incidents = () => { null, null, null, + null, + null, null ); + console.log(res); // Reduce to a lookup object for 0(1) lookup - if (res.data && res.data.data.length > 0) { - const monitorLookup = res.data.data.reduce((acc, monitor) => { + if (res?.data?.data?.monitors?.length > 0) { + const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => { acc[monitor._id] = monitor; return acc; }, {}); From ea49865a237a3db7be85d7457d652ee3cc4f400a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 23:15:17 -0700 Subject: [PATCH 83/96] remove console log --- Client/src/Pages/Incidents/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 0bfc46325..1fd91c10a 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -33,7 +33,6 @@ const Incidents = () => { null, null ); - console.log(res); // Reduce to a lookup object for 0(1) lookup if (res?.data?.data?.monitors?.length > 0) { const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => { From 91909ed3502a6c99d36bda01a2ec0c5f4be15bbd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 5 Sep 2024 10:44:04 -0700 Subject: [PATCH 84/96] Add check for monitor active state before adding jobs to queue on startup --- Server/service/jobQueue.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 5248c88fa..1c44c4062 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -39,7 +39,9 @@ class JobQueue { queue.networkService = networkService; const monitors = await db.getAllMonitors(); for (const monitor of monitors) { - await queue.addJob(monitor.id, monitor); + if (monitor.active) { + await queue.addJob(monitor.id, monitor); + } } const workerStats = await queue.getWorkerStats(); await queue.scaleWorkers(workerStats); From 5afe21767b020b0371c2690cdbb16f73c7b13711 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 5 Sep 2024 13:45:25 -0400 Subject: [PATCH 85/96] Fixed dark mode skeleton color --- .../Charts/MonitorDetailsAreaChart/index.jsx | 16 +++++++++++++++- .../src/Pages/Monitors/Details/Charts/index.jsx | 1 - Client/src/Utils/Theme/darkTheme.js | 7 +++++++ Client/src/Utils/Theme/lightTheme.js | 7 +++++++ Client/src/index.css | 4 ---- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 68fdb1cd4..4773b683c 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -1,5 +1,12 @@ import PropTypes from "prop-types"; -import { AreaChart, Area, XAxis, Tooltip, ResponsiveContainer } from "recharts"; +import { + AreaChart, + Area, + XAxis, + Tooltip, + CartesianGrid, + ResponsiveContainer, +} from "recharts"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import "./index.css"; @@ -104,6 +111,13 @@ const MonitorDetailsAreaChart = ({ checks }) => { bottom: 0, }} > + Date: Thu, 5 Sep 2024 11:17:31 -0700 Subject: [PATCH 86/96] add DB and controller methods --- Server/controllers/monitorController.js | 40 +++++++++++++++++++++++- Server/db/mongo/MongoDB.js | 4 +-- Server/db/mongo/modules/monitorModule.js | 37 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 091d41e2a..f5c42607c 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -16,6 +16,9 @@ const { const sslChecker = require("ssl-checker"); const SERVICE_NAME = "monitorController"; const { errorMessages, successMessages } = require("../utils/messages"); +const { + getMonitorsAndSummaryByTeamId, +} = require("../db/mongo/modules/monitorModule"); /** * Returns all monitors @@ -186,7 +189,41 @@ const getMonitorById = async (req, res, next) => { }; /** - * Returns all monitors belong to User with UserID + * Returns all monitors and a sumamry for a team with TeamID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ + +const getMonitorsAndSumamryByTeamId = async (req, res, next) => { + try { + //validation + } catch (error) { + error.status = 422; + error.service = SERVICE_NAME; + error.method === undefined && + error.method === "getMonitorsAndSummaryByTeamId"; + error.message = + error.details?.[0]?.message || error.message || "Validation Error"; + next(error); + return; + } + + try { + const { teamId, type } = req.params; + await req.db.getMonitorsAndSummaryByTeamId(req, res); + } catch (error) { + error.service = SERVICE_NAME; + error.method === undefined && + error.method === "getMonitorsAndSummaryByTeamId"; + next(error); + } +}; + +/** + * Returns all monitors belong to team with TeamID * @async * @param {Express.Request} req * @param {Express.Response} res @@ -424,6 +461,7 @@ module.exports = { getMonitorStatsById, getMonitorCertificate, getMonitorById, + getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, deleteMonitor, diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 29233b73a..d6a4a243f 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -64,10 +64,9 @@ const { const { getAllMonitors, - getMonitorAggregateStats, - testMethod, getMonitorStatsById, getMonitorById, + getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, deleteMonitor, @@ -148,6 +147,7 @@ module.exports = { getAllMonitors, getMonitorStatsById, getMonitorById, + getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, deleteMonitor, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 0b1a38140..d8b11d5a1 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -292,6 +292,42 @@ const getMonitorById = async (monitorId) => { } }; +/** + * Get monitors and Summary by TeamID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise>} + * @throws {Error} + */ + +const getMonitorsAndSummaryByTeamId = async (teamId, type) => { + try { + const monitors = await Monitor.find({ teamId, type }); + const monitorCounts = monitors.reduce( + (acc, monitor) => { + if (monitor.status === true) { + acc.up += 1; + } + + if (monitor.status === false) { + acc.up += 1; + } + + if (monitor.isActive === false) { + acc.paused += 1; + } + }, + { up: 0, down: 0, paused: 0 } + ); + monitorCounts.total = monitors.length; + return { monitors, monitorCounts }; + } catch (error) { + error.methodName = "getMonitorsAndSummaryByTeamId"; + throw error; + } +}; + /** * Get monitors by TeamID * @async @@ -464,6 +500,7 @@ module.exports = { getAllMonitors, getMonitorStatsById, getMonitorById, + getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, deleteMonitor, From 9eaab0da1a0669327407ce3a5a6e4aaa0eabe2b4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 5 Sep 2024 12:16:29 -0700 Subject: [PATCH 87/96] Add network requests and validation --- .../UptimeMonitors/uptimeMonitorsSlice.js | 8 ++---- Client/src/Utils/NetworkService.js | 21 +++++++++++++++- Server/controllers/monitorController.js | 25 +++++++++++++------ Server/db/mongo/modules/monitorModule.js | 6 +++-- Server/routes/monitorRoute.js | 4 +++ Server/validation/joi.js | 15 +++++++++++ 6 files changed, 63 insertions(+), 16 deletions(-) diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 79789e16b..59a40e1c4 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -53,14 +53,10 @@ export const getUptimeMonitorsByTeamId = createAsyncThunk( async (token, thunkApi) => { const user = jwtDecode(token); try { - const res = await networkService.getMonitorsByTeamId( + const res = await networkService.getMonitorsAndSummaryByTeamId( token, user.teamId, - 25, - ["http", "ping"], - null, - "desc", - true + ["http", "ping"] ); return res.data; } catch (error) { diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 4c8dd57e8..e4b7be526 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -58,9 +58,28 @@ class NetworkService { }); } + async getMonitorsAndSummaryByTeamId(authToken, teamId, types) { + const params = new URLSearchParams(); + + if (types) { + types.forEach((type) => { + params.append("type", type); + }); + } + return this.axiosInstance.get( + `/monitors/team/summary/${teamId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + } + ); + } + /** * ************************************ - * Get all uptime monitors for a user + * Get all uptime monitors for a Team * ************************************ * * @async diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f5c42607c..9be234353 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -4,6 +4,8 @@ const { getMonitorsByTeamIdValidation, createMonitorBodyValidation, editMonitorBodyValidation, + getMonitorsAndSumamryByTeamIdParamValidation, + getMonitorsAndSummaryByTeamIdQueryValidation, getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, @@ -16,10 +18,6 @@ const { const sslChecker = require("ssl-checker"); const SERVICE_NAME = "monitorController"; const { errorMessages, successMessages } = require("../utils/messages"); -const { - getMonitorsAndSummaryByTeamId, -} = require("../db/mongo/modules/monitorModule"); - /** * Returns all monitors * @async @@ -197,8 +195,12 @@ const getMonitorById = async (req, res, next) => { * @throws {Error} */ -const getMonitorsAndSumamryByTeamId = async (req, res, next) => { +const getMonitorsAndSummaryByTeamId = async (req, res, next) => { try { + await getMonitorsAndSumamryByTeamIdParamValidation.validateAsync( + req.params + ); + await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query); //validation } catch (error) { error.status = 422; @@ -212,8 +214,17 @@ const getMonitorsAndSumamryByTeamId = async (req, res, next) => { } try { - const { teamId, type } = req.params; - await req.db.getMonitorsAndSummaryByTeamId(req, res); + const { teamId } = req.params; + const { type } = req.query; + const monitorsSummary = await req.db.getMonitorsAndSummaryByTeamId( + teamId, + type + ); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), + data: monitorsSummary, + }); } catch (error) { error.service = SERVICE_NAME; error.method === undefined && diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index d8b11d5a1..dd96e6450 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -311,19 +311,21 @@ const getMonitorsAndSummaryByTeamId = async (teamId, type) => { } if (monitor.status === false) { - acc.up += 1; + acc.down += 1; } if (monitor.isActive === false) { acc.paused += 1; } + return acc; }, { up: 0, down: 0, paused: 0 } ); + console.log(monitorCounts); monitorCounts.total = monitors.length; return { monitors, monitorCounts }; } catch (error) { - error.methodName = "getMonitorsAndSummaryByTeamId"; + error.method = "getMonitorsAndSummaryByTeamId"; throw error; } }; diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index 7831f42f7..b29b7130a 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -7,6 +7,10 @@ router.get("/aggregate/:monitorId", monitorController.getMonitorAggregateStats); router.get("/stats/:monitorId", monitorController.getMonitorStatsById); router.get("/certificate/:monitorId", monitorController.getMonitorCertificate); router.get("/:monitorId", monitorController.getMonitorById); +router.get( + "/team/summary/:teamId", + monitorController.getMonitorsAndSummaryByTeamId +); router.get("/team/:teamId", monitorController.getMonitorsByTeamId); router.post( diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 090a49a7a..22aec5eb8 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -161,6 +161,19 @@ const getMonitorByIdQueryValidation = joi.object({ normalize: joi.boolean(), }); +const getMonitorsAndSumamryByTeamIdParamValidation = joi.object({ + teamId: joi.string().required(), +}); + +const getMonitorsAndSummaryByTeamIdQueryValidation = joi.object({ + type: joi + .alternatives() + .try( + joi.string().valid("http", "ping", "pagespeed"), + joi.array().items(joi.string().valid("http", "ping", "pagespeed")) + ), +}); + const getMonitorsByTeamIdValidation = joi.object({ teamId: joi.string().required(), }); @@ -381,6 +394,8 @@ module.exports = { createMonitorBodyValidation, getMonitorByIdParamValidation, getMonitorByIdQueryValidation, + getMonitorsAndSumamryByTeamIdParamValidation, + getMonitorsAndSummaryByTeamIdQueryValidation, getMonitorsByTeamIdValidation, getMonitorsByTeamIdQueryValidation, getMonitorStatsByIdParamValidation, From 0487095d05787b7b56d85c536bf056a043a6f136 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 5 Sep 2024 12:47:27 -0700 Subject: [PATCH 88/96] Updated uptime redux state --- .../UptimeMonitors/uptimeMonitorsSlice.js | 6 +-- Client/src/Pages/Monitors/Home/index.jsx | 48 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 59a40e1c4..ac3ce1d6f 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -3,7 +3,7 @@ import { jwtDecode } from "jwt-decode"; import { networkService } from "../../main"; const initialState = { isLoading: false, - monitors: [], + monitorsSummary: [], success: null, msg: null, }; @@ -171,7 +171,7 @@ const uptimeMonitorsSlice = createSlice({ reducers: { clearUptimeMonitorState: (state) => { state.isLoading = false; - state.monitors = []; + state.monitorsSummary = []; state.success = null; state.msg = null; }, @@ -188,7 +188,7 @@ const uptimeMonitorsSlice = createSlice({ .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => { state.isLoading = false; state.success = action.payload.msg; - state.monitors = action.payload.data; + state.monitorsSummary = action.payload.data; }) .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => { state.isLoading = false; diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 8bff60d19..19d8eb5a6 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -23,23 +23,9 @@ const Monitors = ({ isAdmin }) => { useEffect(() => { dispatch(getUptimeMonitorsByTeamId(authState.authToken)); }, [authState.authToken, dispatch]); - - const monitorStats = monitorState?.monitors?.monitors?.reduce( - (acc, monitor) => { - if (monitor.isActive === false) { - acc["paused"] += 1; - } else if (monitor.status === true) { - acc["up"] += 1; - } else { - acc["down"] += 1; - } - return acc; - }, - { paused: 0, up: 0, down: 0 } - ); - - let loading = monitorState.isLoading && monitorState.monitors.length === 0; - + let loading = + monitorState?.isLoading && + monitorState?.monitorsSummary?.monitors?.length === 0; return ( {loading ? ( @@ -55,7 +41,7 @@ const Monitors = ({ isAdmin }) => { mt={theme.spacing(5)} > - {monitorState.monitors?.length !== 0 && ( + {monitorState?.montiorsSummary?.monitors?.length !== 0 && ( - + ); }; From 209d7f0b5720186248e91e8d7f3e524dc486041d Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Thu, 5 Sep 2024 17:19:59 -0400 Subject: [PATCH 91/96] Fixed janky bar animations --- .../Charts/MonitorDetailsAreaChart/index.jsx | 7 +++-- .../Pages/Monitors/Details/Charts/index.jsx | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 4773b683c..cb01734fd 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -10,6 +10,7 @@ import { import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import "./index.css"; +import React, { useMemo } from "react"; const CustomToolTip = ({ active, payload, label }) => { const theme = useTheme(); @@ -96,14 +97,16 @@ const MonitorDetailsAreaChart = ({ checks }) => { }); }; + const memoizedChecks = useMemo(() => checks, []); + const theme = useTheme(); return ( - + { +export const UpBarChart = memo(({ data, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); @@ -54,10 +54,10 @@ export const UpBarChart = ({ data, type, onBarHover }) => { }; // TODO - REMOVE THIS LATER - let reversedData = [...data].reverse(); + const reversedData = useMemo(() => [...data].reverse(), [data]); return ( - + { ); -}; +}); -export const DownBarChart = ({ data, type, onBarHover }) => { +export const DownBarChart = memo(({ data, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); // TODO - REMOVE THIS LATER - let reversedData = [...data].reverse(); + const reversedData = useMemo(() => [...data].reverse(), [data]); return ( - + { ); -}; +}); export const ResponseGaugeChart = ({ data }) => { const theme = useTheme(); let max = 1000; // max ms - data = [{ response: max, fill: "transparent", background: false }, ...data]; - let responseTime = Math.floor(data[1].response); + const memoizedData = useMemo( + () => [{ response: max, fill: "transparent", background: false }, ...data], + [data[0].response] + ); + + let responseTime = Math.floor(memoizedData[1].response); let responseProps = responseTime <= 200 ? { @@ -227,12 +231,12 @@ export const ResponseGaugeChart = ({ data }) => { }; return ( - + Date: Thu, 5 Sep 2024 14:27:13 -0700 Subject: [PATCH 92/96] fix typo --- Client/src/Pages/Monitors/Home/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 19d8eb5a6..607484321 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -41,7 +41,7 @@ const Monitors = ({ isAdmin }) => { mt={theme.spacing(5)} > - {monitorState?.montiorsSummary?.monitors?.length !== 0 && ( + {monitorState?.monitorsSummary?.monitors?.length !== 0 && (