From 899111e48bfa9c245da41968353cef5414862f16 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 10:23:26 -0700 Subject: [PATCH 01/18] 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 02/18] 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 03/18] 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 04/18] 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 789e6e91c472a93c9feda5b0961c27b26b9df995 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 12:07:59 -0700 Subject: [PATCH 05/18] 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 a6c3c56d0bca064eab89255ce28bbf19428fed0c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:00:30 -0700 Subject: [PATCH 06/18] 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 cece932e451cc199e1a117bf3ecc4390afd04451 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 15:07:11 -0700 Subject: [PATCH 07/18] 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 08/18] 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 09/18] 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 10/18] 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 11/18] 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 12/18] 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 13/18] 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 e22e374eef80b1ec4c58088c6917c394d2da8cf8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 3 Sep 2024 20:28:12 -0700 Subject: [PATCH 14/18] 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 15/18] 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 9edf5ba9f9e6674d06bef8a3944800270a797c25 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 4 Sep 2024 11:02:18 -0700 Subject: [PATCH 16/18] 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 17/18] 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 18/18] 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 }); };