From d9c583f972b68e11ee86af8ebaf640f60591557c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 14:56:38 -0800 Subject: [PATCH 01/27] Add a route for uptime details --- Server/routes/monitorRoute.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index c3257372b..39ef105c2 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -13,6 +13,13 @@ class MonitorRoutes { this.router.get("/", this.monitorController.getAllMonitors); this.router.get("/uptime", this.monitorController.getAllMonitorsWithUptimeStats); this.router.get("/stats/:monitorId", this.monitorController.getMonitorStatsById); + + // New details routes + this.router.get( + "/uptime/details/:monitorId", + this.monitorController.getUptimeDetailsById + ); + this.router.get("/certificate/:monitorId", (req, res, next) => { this.monitorController.getMonitorCertificate( req, From 0551eb010dd5c9c0f40ab39dc97a0795da697946 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 14:56:50 -0800 Subject: [PATCH 02/27] Add controller method for uptime details --- Server/controllers/monitorController.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 613027f53..ec317bbb5 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -74,6 +74,19 @@ class MonitorController { } }; + getUptimeDetailsById = async (req, res, next) => { + try { + const monitor = await this.db.getUptimeDetailsById(req); + return res.status(200).json({ + success: true, + msg: successMessages.MONITOR_GET_BY_ID, + data: monitor, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorDetailsById")); + } + }; + /** * Returns monitor stats for monitor with matching ID * @async From 5391145a9a7858fecc8db70a38abfdfb5909b32f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 14:57:19 -0800 Subject: [PATCH 03/27] Add aggregate queries for uptime monitor stats --- Server/db/mongo/modules/monitorModule.js | 179 +++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 61eab4e0e..7dcc450bd 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -304,6 +304,184 @@ const calculateGroupStats = (group) => { }; }; +/** + * Get uptime details by monitor ID + * @async + * @param {Express.Request} req + * @param {Express.Response} res + * @returns {Promise} + * @throws {Error} + */ +const getUptimeDetailsById = async (req) => { + try { + const { monitorId } = req.params; + const monitor = await Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); + } + + const { dateRange, normalize } = req.query; + const dates = getDateRange(dateRange); + const currentTime = new Date(); + + // Build pipeline + const aggregateResults = await Check.aggregate([ + { + $match: { monitorId: monitor._id }, + }, + { + $sort: { createdAt: 1 }, + }, + { + $group: { + _id: null, + latestCheck: { $last: "$$ROOT" }, + lastFalseCheck: { + $last: { + $cond: [{ $eq: ["$status", false] }, "$$ROOT", null], + }, + }, + firstCheck: { $first: "$$ROOT" }, + averageResponseTime: { $avg: "$responseTime" }, + upChecks: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + totalChecks: { $sum: 1 }, + }, + }, + { + $project: { + timeSinceLastCheck: { + $subtract: [currentTime, "$latestCheck.createdAt"], + }, + latestResponseTime: "$latestCheck.responseTime", + averageResponseTime: "$averageResponseTime", + latestStatus: "$latestCheck.status", + uptimeDuration: { + $cond: [ + { $eq: ["$latestCheck.status", false] }, + 0, + { + $subtract: [ + currentTime, + { + $ifNull: ["$lastFalseCheck.createdAt", "$firstCheck.createdAt"], + }, + ], + }, + ], + }, + uptimeDecimal: { + $divide: ["$upChecks", "$totalChecks"], + }, + }, + }, + ]); + + const formatLookup = { + day: "%Y-%m-%d-%H", + week: "%Y-%m-%d-%H", + month: "%Y-%m-%d", + }; + + const dateString = formatLookup[dateRange]; + + const dateRangeResults = await Check.aggregate([ + { + $match: { + monitorId: monitor._id, + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { + $group: { + _id: null, + totalChecks: { $sum: 1 }, + upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { + $addFields: { + uptimePercentage: { + $multiply: [{ $divide: ["$upChecks", "$totalChecks"] }, 100], + }, + }, + }, + { + $lookup: { + from: "checks", + let: { monitorId: monitor._id, start: dates.start, end: dates.end }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { $eq: ["$monitorId", "$$monitorId"] }, + { $gte: ["$createdAt", "$$start"] }, + { $lte: ["$createdAt", "$$end"] }, + ], + }, + }, + }, + { + $sort: { createdAt: 1 }, + }, + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + upChecks: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + totalChecks: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { + $project: { + avgResponseTime: 1, + groupUptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, + }, + }, + ], + as: "groupedResults", + }, + }, + { + $unwind: "$groupedResults", + }, + { + $replaceRoot: { + newRoot: { + $mergeObjects: [ + "$groupedResults", + { overallUptimePercentage: "$uptimePercentage" }, + ], + }, + }, + }, + { + $sort: { _id: 1 }, + }, + ]); + + return { aggregateResults, dateRangeResults }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUptimeDetailsById"; + throw error; + } +}; + /** * Get stats by monitor ID * @async @@ -652,6 +830,7 @@ export { getAllMonitorsWithUptimeStats, getMonitorStatsById, getMonitorById, + getUptimeDetailsById, getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, From e5181727fe68078593a1c37a59c1b72425fbb316 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 14:58:20 -0800 Subject: [PATCH 04/27] Add netwokr request for uptime details --- Client/src/Utils/NetworkService.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 71cbe9670..7854fec7a 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -242,6 +242,18 @@ class NetworkService { ); } + async getUptimeDetailsById(config) { + const params = new URLSearchParams(); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.normalize) params.append("normalize", config.normalize); + + return this.axiosInstance.get(`/monitors/uptime/details/${config.monitorId}`, { + headers: { + Authorization: `Bearer ${config.authToken}`, + }, + }); + } + /** * ************************************ * Updates a single monitor From 107540b2a8c8fec98785ac633cbc09a4662aa9b7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 16:07:28 -0800 Subject: [PATCH 05/27] FE refactor --- .../src/Pages/Uptime/Details/Charts/index.jsx | 25 +++++++------ Client/src/Pages/Uptime/Details/index.jsx | 36 +++++++++---------- Client/src/Utils/NetworkService.js | 13 ++++--- Client/src/Utils/timeUtils.js | 10 +++++- Server/db/mongo/modules/monitorModule.js | 13 +++++-- 5 files changed, 60 insertions(+), 37 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Charts/index.jsx b/Client/src/Pages/Uptime/Details/Charts/index.jsx index 7c5adc9e2..b9e17c138 100644 --- a/Client/src/Pages/Uptime/Details/Charts/index.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/index.jsx @@ -11,11 +11,11 @@ import { } from "recharts"; import { memo, useMemo, useState } from "react"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../../Utils/timeUtils"; +import { formatDateWithTz, toTimeStamp } from "../../../../Utils/timeUtils"; const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { const uiTimezone = useSelector((state) => state.ui.timezone); - const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; + const dateFormat = type === "day" ? "MMM D, h A" : "MMM D"; return ( <> @@ -26,7 +26,11 @@ const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) textAnchor="start" fontSize={11} > - {formatDateWithTz(new Date(firstDataPoint.time), dateFormat, uiTimezone)} + {formatDateWithTz( + toTimeStamp(firstDataPoint._id, "YYYY-MM-DD-HH"), + dateFormat, + uiTimezone + )} - {formatDateWithTz(new Date(lastDataPoint.time), dateFormat, uiTimezone)} + {lastDataPoint._id} + {/* {formatDateWithTz(new Date(lastDataPoint.time), dateFormat, uiTimezone)} */} ); @@ -57,9 +62,9 @@ const UpBarChart = memo(({ data, type, onBarHover }) => { const [hoveredBarIndex, setHoveredBarIndex] = useState(null); const getColorRange = (uptime) => { - return uptime > 80 + return uptime > 0.8 ? { main: theme.palette.success.main, light: theme.palette.success.light } - : uptime > 50 + : uptime > 0.5 ? { main: theme.palette.warning.main, light: theme.palette.warning.light } : { main: theme.palette.error.contrastText, light: theme.palette.error.light }; }; @@ -79,7 +84,7 @@ const UpBarChart = memo(({ data, type, onBarHover }) => { data={reversedData} onMouseEnter={() => { setChartHovered(true); - onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 }); + onBarHover({ time: null, totalChecks: 0, groupUptimePercentage: 0 }); }} onMouseLeave={() => { setChartHovered(false); @@ -104,12 +109,12 @@ const UpBarChart = memo(({ data, type, onBarHover }) => { } /> {reversedData.map((entry, index) => { - let { main, light } = getColorRange(entry.uptimePercentage); + let { main, light } = getColorRange(entry.groupUptimePercentage); return ( { onBarHover({ time: null, totalChecks: 0, - uptimePercentage: 0, + groupUptimePercentage: 0, }); }} /> diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 631667d08..46e769571 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -5,7 +5,6 @@ import { useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; import { networkService } from "../../../main"; import { logger } from "../../../Utils/Logger"; -import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import ButtonGroup from "@mui/material/ButtonGroup"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; @@ -22,7 +21,7 @@ import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts"; import SkeletonLayout from "./skeleton"; import "./index.css"; import useUtils from "../utils"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; import StatBox from "../../../Components/StatBox"; @@ -47,13 +46,10 @@ const DetailsPage = () => { const fetchMonitor = useCallback(async () => { try { - const res = await networkService.getStatsByMonitorId({ + const res = await networkService.getUptimeDetailsById({ authToken: authToken, monitorId: monitorId, - sortOrder: null, - limit: null, dateRange: dateRange, - numToDisplay: 50, normalize: true, }); console.log(res?.data?.data); @@ -185,7 +181,7 @@ const DetailsPage = () => { }, }} > - Checking every {formatDurationRounded(monitor?.interval)}. + {/* Checking every {formatDurationRounded(monitor?.interval)}. */} @@ -224,17 +220,17 @@ const DetailsPage = () => { - {monitor?.latestResponseTime} + {monitor?.aggregateData?.latestResponseTime} {"ms"} } @@ -311,7 +307,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : monitor?.periodTotalChecks} + : monitor.dateRangeData[0].overallTotalChecks} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { Uptime Percentage {hoveredUptimeData !== null - ? Math.floor(hoveredUptimeData.uptimePercentage * 10) / 10 - : Math.floor(monitor?.periodUptime * 10) / 10} + ? Math.floor(hoveredUptimeData.groupUptimePercentage * 100) + : Math.floor( + monitor?.dateRangeData[0].overallUptimePercentage * 100 + )} % @@ -376,11 +374,11 @@ const DetailsPage = () => { )} - + /> */} @@ -389,9 +387,9 @@ const DetailsPage = () => { Average Response Time - + /> */} { Response Times - + {/* */} { : { time: 0, format: "seconds" }; }; +export const toTimeStamp = (date, format) => { + const dayJsDate = dayjs(date, format); + return dayJsDate.valueOf(); +}; + export const formatDate = (date, customOptions) => { const options = { year: "numeric", @@ -90,6 +98,6 @@ export const formatDate = (date, customOptions) => { }; export const formatDateWithTz = (timestamp, format, timezone) => { - const formattedDate = dayjs(timestamp, timezone).tz(timezone).format(format); + const formattedDate = dayjs(timestamp).tz(timezone).format(format); return formattedDate; }; diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 7dcc450bd..f58d8fa6f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -406,8 +406,9 @@ const getUptimeDetailsById = async (req) => { { $addFields: { uptimePercentage: { - $multiply: [{ $divide: ["$upChecks", "$totalChecks"] }, 100], + $divide: ["$upChecks", "$totalChecks"], }, + overallTotalChecks: "$totalChecks", }, }, { @@ -448,6 +449,7 @@ const getUptimeDetailsById = async (req) => { }, { $project: { + totalChecks: 1, avgResponseTime: 1, groupUptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, }, @@ -465,6 +467,7 @@ const getUptimeDetailsById = async (req) => { $mergeObjects: [ "$groupedResults", { overallUptimePercentage: "$uptimePercentage" }, + { overallTotalChecks: "$overallTotalChecks" }, ], }, }, @@ -474,7 +477,13 @@ const getUptimeDetailsById = async (req) => { }, ]); - return { aggregateResults, dateRangeResults }; + const monitorStats = { + ...monitor.toObject(), + }; + monitorStats.aggregateData = aggregateResults[0]; + monitorStats.dateRangeData = dateRangeResults; + + return monitorStats; } catch (error) { error.service = SERVICE_NAME; error.method = "getUptimeDetailsById"; From d574faa7d88c9c01b94978452c4c517db4fe9e17 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 19:49:09 -0800 Subject: [PATCH 06/27] Add groupUpChecks and groupDownChecks --- Server/db/mongo/modules/monitorModule.js | 98 +++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index f58d8fa6f..5cbb0734a 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -388,11 +388,12 @@ const getUptimeDetailsById = async (req) => { const dateString = formatLookup[dateRange]; - const dateRangeResults = await Check.aggregate([ + const groupUpChecks = await Check.aggregate([ { $match: { monitorId: monitor._id, createdAt: { $gte: dates.start, $lte: dates.end }, + status: true, }, }, { @@ -418,6 +419,98 @@ const getUptimeDetailsById = async (req) => { pipeline: [ { $match: { + status: true, + $expr: { + $and: [ + { $eq: ["$monitorId", "$$monitorId"] }, + { $gte: ["$createdAt", "$$start"] }, + { $lte: ["$createdAt", "$$end"] }, + ], + }, + }, + }, + { + $sort: { createdAt: 1 }, + }, + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + upChecks: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], + }, + }, + totalChecks: { $sum: 1 }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { + $project: { + totalChecks: 1, + avgResponseTime: 1, + groupUptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, + }, + }, + ], + as: "groupedResults", + }, + }, + { + $unwind: "$groupedResults", + }, + { + $replaceRoot: { + newRoot: { + $mergeObjects: [ + "$groupedResults", + { overallUptimePercentage: "$uptimePercentage" }, + { overallTotalChecks: "$overallTotalChecks" }, + ], + }, + }, + }, + { + $sort: { _id: 1 }, + }, + ]); + + const groupDownChecks = await Check.aggregate([ + { + $match: { + monitorId: monitor._id, + createdAt: { $gte: dates.start, $lte: dates.end }, + status: false, + }, + }, + { + $group: { + _id: null, + totalChecks: { $sum: 1 }, + upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, + avgResponseTime: { $avg: "$responseTime" }, + }, + }, + { + $addFields: { + uptimePercentage: { + $divide: ["$upChecks", "$totalChecks"], + }, + overallTotalChecks: "$totalChecks", + }, + }, + { + $lookup: { + from: "checks", + let: { monitorId: monitor._id, start: dates.start, end: dates.end }, + pipeline: [ + { + $match: { + status: false, $expr: { $and: [ { $eq: ["$monitorId", "$$monitorId"] }, @@ -481,7 +574,8 @@ const getUptimeDetailsById = async (req) => { ...monitor.toObject(), }; monitorStats.aggregateData = aggregateResults[0]; - monitorStats.dateRangeData = dateRangeResults; + monitorStats.groupUpChecks = groupUpChecks; + monitorStats.groupDownChecks = groupDownChecks; return monitorStats; } catch (error) { From 853a28d31609935194bd28332adae44832e2b08b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 19:49:28 -0800 Subject: [PATCH 07/27] migrate to new query --- .../src/Pages/Uptime/Details/Charts/index.jsx | 40 +++++++++---------- Client/src/Pages/Uptime/Details/index.jsx | 37 +++++++++++------ 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Charts/index.jsx b/Client/src/Pages/Uptime/Details/Charts/index.jsx index b9e17c138..053f8f879 100644 --- a/Client/src/Pages/Uptime/Details/Charts/index.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/index.jsx @@ -15,7 +15,8 @@ import { formatDateWithTz, toTimeStamp } from "../../../../Utils/timeUtils"; const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { const uiTimezone = useSelector((state) => state.ui.timezone); - const dateFormat = type === "day" ? "MMM D, h A" : "MMM D"; + const formatString = type === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; + const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; return ( <> @@ -27,7 +28,7 @@ const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) fontSize={11} > {formatDateWithTz( - toTimeStamp(firstDataPoint._id, "YYYY-MM-DD-HH"), + toTimeStamp(firstDataPoint._id, formatString), dateFormat, uiTimezone )} @@ -39,8 +40,11 @@ const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) textAnchor="end" fontSize={11} > - {lastDataPoint._id} - {/* {formatDateWithTz(new Date(lastDataPoint.time), dateFormat, uiTimezone)} */} + {formatDateWithTz( + toTimeStamp(lastDataPoint._id, formatString), + dateFormat, + uiTimezone + )} ); @@ -70,7 +74,6 @@ const UpBarChart = memo(({ data, type, onBarHover }) => { }; // TODO - REMOVE THIS LATER - const reversedData = useMemo(() => [...data].reverse(), [data]); return ( { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0, groupUptimePercentage: 0 }); @@ -102,8 +105,8 @@ const UpBarChart = memo(({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={reversedData[0]} - lastDataPoint={reversedData[reversedData.length - 1]} + firstDataPoint={data[0]} + lastDataPoint={data[data.length - 1]} type={type} /> } @@ -113,7 +116,7 @@ const UpBarChart = memo(({ data, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {reversedData.map((entry, index) => { + {data.map((entry, index) => { let { main, light } = getColorRange(entry.groupUptimePercentage); return ( { const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - // TODO - REMOVE THIS LATER - const reversedData = useMemo(() => [...data].reverse(), [data]); - return ( { { setChartHovered(true); - onBarHover({ time: null, totalIncidents: 0 }); + onBarHover({ time: null, totalChecks: 0 }); }} onMouseLeave={() => { setChartHovered(false); @@ -190,18 +190,18 @@ const DownBarChart = memo(({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={reversedData[0]} - lastDataPoint={reversedData[reversedData.length - 1]} + firstDataPoint={data[0]} + lastDataPoint={data[data.length - 1]} type={type} /> } /> - {reversedData.map((entry, index) => ( + {data.map((entry, index) => ( { ? theme.palette.error.contrastText : chartHovered ? theme.palette.error.light - : theme.palette.error.contrastText + : theme.palette.error.main } onMouseEnter={() => { setHoveredBarIndex(index); @@ -217,7 +217,7 @@ const DownBarChart = memo(({ data, type, onBarHover }) => { }} onMouseLeave={() => { setHoveredBarIndex(null); - onBarHover({ time: null, totalIncidents: 0 }); + onBarHover({ time: null, totalChecks: 0 }); }} /> ))} diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 46e769571..3f816ddb9 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -25,6 +25,8 @@ import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils" import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; import StatBox from "../../../Components/StatBox"; +import { toTimeStamp } from "../../../Utils/timeUtils"; + /** * Details page component displaying monitor details and related information. * @component @@ -103,6 +105,10 @@ const DetailsPage = () => { const [hoveredUptimeData, setHoveredUptimeData] = useState(null); const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); + useEffect(() => { + console.log(hoveredUptimeData); + }, [hoveredUptimeData]); + const BREADCRUMBS = [ { name: "uptime", path: "/uptime" }, { name: "details", path: `/uptime/${monitorId}` }, @@ -307,7 +313,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : monitor.dateRangeData[0].overallTotalChecks} + : (monitor.groupUpChecks[0]?.overallTotalChecks ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { color={theme.palette.text.tertiary} > {formatDateWithTz( - hoveredUptimeData.time, + toTimeStamp( + hoveredUptimeData._id, + dateRange === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH" + ), dateFormat, uiTimezone )} @@ -331,14 +340,15 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? Math.floor(hoveredUptimeData.groupUptimePercentage * 100) : Math.floor( - monitor?.dateRangeData[0].overallUptimePercentage * 100 + monitor?.groupUpChecks[0]?.overallUptimePercentage ?? + 0 * 100 )} % @@ -354,8 +364,8 @@ const DetailsPage = () => { Total Incidents {hoveredIncidentsData !== null - ? hoveredIncidentsData.totalIncidents - : monitor?.periodIncidents} + ? hoveredIncidentsData.totalChecks + : (monitor.groupDownChecks[0]?.overallTotalChecks ?? 0)} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( @@ -367,18 +377,21 @@ const DetailsPage = () => { color={theme.palette.text.tertiary} > {formatDateWithTz( - hoveredIncidentsData.time, + toTimeStamp( + hoveredIncidentsData._id, + dateRange === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH" + ), dateFormat, uiTimezone )} )} - {/* */} + /> @@ -422,10 +435,10 @@ const DetailsPage = () => { History - + /> */} From b5ceb5c4c17d190c4bc0d292c3726729de9d9d85 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 19:49:41 -0800 Subject: [PATCH 08/27] Add timestamp method --- Client/src/Utils/timeUtils.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 38576715b..04b51d780 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -76,8 +76,9 @@ export const formatDurationSplit = (ms) => { }; export const toTimeStamp = (date, format) => { - const dayJsDate = dayjs(date, format); - return dayJsDate.valueOf(); + const dayJsDate = dayjs.utc(date, format); + const timestamp = dayJsDate.valueOf(); + return timestamp; }; export const formatDate = (date, customOptions) => { From 3e995c82aae50a051e6f6633a9c9c56043d80e78 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 30 Dec 2024 20:11:05 -0800 Subject: [PATCH 09/27] update response time gauge --- .../src/Pages/Uptime/Details/Charts/index.jsx | 23 +++++++++---------- Client/src/Pages/Uptime/Details/index.jsx | 6 ++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Charts/index.jsx b/Client/src/Pages/Uptime/Details/Charts/index.jsx index 053f8f879..8f135196a 100644 --- a/Client/src/Pages/Uptime/Details/Charts/index.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/index.jsx @@ -190,8 +190,8 @@ const DownBarChart = memo(({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={data[0]} - lastDataPoint={data[data.length - 1]} + firstDataPoint={data?.[0] ?? {}} + lastDataPoint={data?.[data.length - 1] ?? {}} type={type} /> } @@ -235,17 +235,16 @@ DownBarChart.propTypes = { }; export { DownBarChart }; -const ResponseGaugeChart = ({ data }) => { +const ResponseGaugeChart = ({ avgResponseTime }) => { const theme = useTheme(); let max = 1000; // max ms - const memoizedData = useMemo( - () => [{ response: max, fill: "transparent", background: false }, ...data], - [data[0].response] - ); - - let responseTime = Math.floor(memoizedData[1].response); + const data = [ + { response: max, fill: "transparent", background: false }, + { response: avgResponseTime, background: true }, + ]; + let responseTime = Math.floor(avgResponseTime); let responseProps = responseTime <= 200 ? { @@ -267,8 +266,8 @@ const ResponseGaugeChart = ({ data }) => { } : { category: "Poor", - main: theme.palette.error.light, - bg: theme.palette.error.dark, + main: theme.palette.error.main, + bg: theme.palette.error.light, }; return ( @@ -281,7 +280,7 @@ const ResponseGaugeChart = ({ data }) => { width="100%" height="100%" cy="89%" - data={memoizedData} + data={data} startAngle={180} endAngle={0} innerRadius={100} diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 3f816ddb9..ef66ca866 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -400,9 +400,9 @@ const DetailsPage = () => { Average Response Time - {/* */} + Date: Mon, 30 Dec 2024 22:23:46 -0800 Subject: [PATCH 10/27] modify query --- Client/src/Pages/Uptime/Details/index.jsx | 8 +- Server/db/mongo/modules/monitorModule.js | 220 ++++++++++------------ 2 files changed, 101 insertions(+), 127 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index ef66ca866..93c1b99d7 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -313,7 +313,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.groupUpChecks[0]?.overallTotalChecks ?? 0)} + : (monitor.groupUpChecks[0]?.upChecksCount ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { {hoveredUptimeData !== null ? Math.floor(hoveredUptimeData.groupUptimePercentage * 100) : Math.floor( - monitor?.groupUpChecks[0]?.overallUptimePercentage ?? - 0 * 100 + (monitor?.groupUpChecks[0]?.overallUptimePercentage ?? 0) * + 100 )} % @@ -365,7 +365,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.groupDownChecks[0]?.overallTotalChecks ?? 0)} + : (monitor.groupDownChecks[0]?.downChecksCount ?? 0)} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 5cbb0734a..5d4794904 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -388,49 +388,29 @@ const getUptimeDetailsById = async (req) => { const dateString = formatLookup[dateRange]; - const groupUpChecks = await Check.aggregate([ + const groupedChecks = await Check.aggregate([ { $match: { monitorId: monitor._id, createdAt: { $gte: dates.start, $lte: dates.end }, - status: true, }, }, { - $group: { - _id: null, - totalChecks: { $sum: 1 }, - upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, - avgResponseTime: { $avg: "$responseTime" }, - }, - }, - { - $addFields: { - uptimePercentage: { - $divide: ["$upChecks", "$totalChecks"], - }, - overallTotalChecks: "$totalChecks", - }, - }, - { - $lookup: { - from: "checks", - let: { monitorId: monitor._id, start: dates.start, end: dates.end }, - pipeline: [ + $facet: { + upChecksCount: [ { - $match: { - status: true, - $expr: { - $and: [ - { $eq: ["$monitorId", "$$monitorId"] }, - { $gte: ["$createdAt", "$$start"] }, - { $lte: ["$createdAt", "$$end"] }, - ], - }, - }, + $match: { status: true }, }, { - $sort: { createdAt: 1 }, + $group: { + _id: null, + totalChecks: { $sum: 1 }, + }, + }, + ], + upChecks: [ + { + $match: { status: true }, }, { $group: { @@ -440,12 +420,8 @@ const getUptimeDetailsById = async (req) => { date: "$createdAt", }, }, - upChecks: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, totalChecks: { $sum: 1 }, + upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, avgResponseTime: { $avg: "$responseTime" }, }, }, @@ -456,72 +432,24 @@ const getUptimeDetailsById = async (req) => { groupUptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, }, }, - ], - as: "groupedResults", - }, - }, - { - $unwind: "$groupedResults", - }, - { - $replaceRoot: { - newRoot: { - $mergeObjects: [ - "$groupedResults", - { overallUptimePercentage: "$uptimePercentage" }, - { overallTotalChecks: "$overallTotalChecks" }, - ], - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ]); - - const groupDownChecks = await Check.aggregate([ - { - $match: { - monitorId: monitor._id, - createdAt: { $gte: dates.start, $lte: dates.end }, - status: false, - }, - }, - { - $group: { - _id: null, - totalChecks: { $sum: 1 }, - upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, - avgResponseTime: { $avg: "$responseTime" }, - }, - }, - { - $addFields: { - uptimePercentage: { - $divide: ["$upChecks", "$totalChecks"], - }, - overallTotalChecks: "$totalChecks", - }, - }, - { - $lookup: { - from: "checks", - let: { monitorId: monitor._id, start: dates.start, end: dates.end }, - pipeline: [ { - $match: { - status: false, - $expr: { - $and: [ - { $eq: ["$monitorId", "$$monitorId"] }, - { $gte: ["$createdAt", "$$start"] }, - { $lte: ["$createdAt", "$$end"] }, - ], - }, - }, + $sort: { _id: 1 }, + }, + ], + downChecksCount: [ + { + $match: { status: false }, }, { - $sort: { createdAt: 1 }, + $group: { + _id: null, + totalChecks: { $sum: 1 }, + }, + }, + ], + downChecks: [ + { + $match: { status: false }, }, { $group: { @@ -531,12 +459,8 @@ const getUptimeDetailsById = async (req) => { date: "$createdAt", }, }, - upChecks: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, totalChecks: { $sum: 1 }, + downChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } }, avgResponseTime: { $avg: "$responseTime" }, }, }, @@ -544,38 +468,88 @@ const getUptimeDetailsById = async (req) => { $project: { totalChecks: 1, avgResponseTime: 1, - groupUptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, + groupUptimePercentage: { $divide: ["$downChecks", "$totalChecks"] }, + }, + }, + { + $sort: { _id: 1 }, + }, + ], + overall: [ + { + $group: { + _id: null, + totalChecks: { $sum: 1 }, + groupAvgResponseTime: { $avg: "$responseTime" }, + upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, + }, + }, + { + $project: { + uptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, + totalChecks: 1, + groupAvgResponseTime: 1, }, }, ], - as: "groupedResults", }, }, { - $unwind: "$groupedResults", - }, - { - $replaceRoot: { - newRoot: { - $mergeObjects: [ - "$groupedResults", - { overallUptimePercentage: "$uptimePercentage" }, - { overallTotalChecks: "$overallTotalChecks" }, - ], + $project: { + upChecks: { + $map: { + input: "$upChecks", + as: "check", + in: { + $mergeObjects: [ + "$$check", + { + overallUptimePercentage: { + $arrayElemAt: ["$overall.uptimePercentage", 0], + }, + overallTotalChecks: { $arrayElemAt: ["$overall.totalChecks", 0] }, + upChecksCount: { + $arrayElemAt: ["$upChecksCount.totalChecks", 0], + }, + groupAvgResponseTime: { + $arrayElemAt: ["$overall.groupAvgResponseTime", 0], + }, + }, + ], + }, + }, + }, + downChecks: { + $map: { + input: "$downChecks", + as: "check", + in: { + $mergeObjects: [ + "$$check", + { + overallUptimePercentage: { + $arrayElemAt: ["$overall.uptimePercentage", 0], + }, + overallTotalChecks: { $arrayElemAt: ["$overall.totalChecks", 0] }, + downChecksCount: { + $arrayElemAt: ["$downChecksCount.totalChecks", 0], + }, + }, + ], + }, + }, }, }, }, - { - $sort: { _id: 1 }, - }, ]); const monitorStats = { ...monitor.toObject(), }; + console.log(groupedChecks); monitorStats.aggregateData = aggregateResults[0]; - monitorStats.groupUpChecks = groupUpChecks; - monitorStats.groupDownChecks = groupDownChecks; + monitorStats.groupUpChecks = groupedChecks[0].upChecks; + monitorStats.groupDownChecks = groupedChecks[0].downChecks; return monitorStats; } catch (error) { From 7f601b1e117c5fa9bffe1e17c6f30e5086b2af0a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 11:43:04 -0800 Subject: [PATCH 11/27] rebuild query --- Server/db/mongo/modules/monitorModule.js | 559 +++++++++++++++++------ 1 file changed, 413 insertions(+), 146 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 5d4794904..aa429b6e4 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -325,60 +325,60 @@ const getUptimeDetailsById = async (req) => { const currentTime = new Date(); // Build pipeline - const aggregateResults = await Check.aggregate([ - { - $match: { monitorId: monitor._id }, - }, - { - $sort: { createdAt: 1 }, - }, - { - $group: { - _id: null, - latestCheck: { $last: "$$ROOT" }, - lastFalseCheck: { - $last: { - $cond: [{ $eq: ["$status", false] }, "$$ROOT", null], - }, - }, - firstCheck: { $first: "$$ROOT" }, - averageResponseTime: { $avg: "$responseTime" }, - upChecks: { - $sum: { - $cond: [{ $eq: ["$status", true] }, 1, 0], - }, - }, - totalChecks: { $sum: 1 }, - }, - }, - { - $project: { - timeSinceLastCheck: { - $subtract: [currentTime, "$latestCheck.createdAt"], - }, - latestResponseTime: "$latestCheck.responseTime", - averageResponseTime: "$averageResponseTime", - latestStatus: "$latestCheck.status", - uptimeDuration: { - $cond: [ - { $eq: ["$latestCheck.status", false] }, - 0, - { - $subtract: [ - currentTime, - { - $ifNull: ["$lastFalseCheck.createdAt", "$firstCheck.createdAt"], - }, - ], - }, - ], - }, - uptimeDecimal: { - $divide: ["$upChecks", "$totalChecks"], - }, - }, - }, - ]); + // const aggregateResults = await Check.aggregate([ + // { + // $match: { monitorId: monitor._id }, + // }, + // { + // $sort: { createdAt: 1 }, + // }, + // { + // $group: { + // _id: null, + // latestCheck: { $last: "$$ROOT" }, + // lastFalseCheck: { + // $last: { + // $cond: [{ $eq: ["$status", false] }, "$$ROOT", null], + // }, + // }, + // firstCheck: { $first: "$$ROOT" }, + // averageResponseTime: { $avg: "$responseTime" }, + // upChecks: { + // $sum: { + // $cond: [{ $eq: ["$status", true] }, 1, 0], + // }, + // }, + // totalChecks: { $sum: 1 }, + // }, + // }, + // { + // $project: { + // timeSinceLastCheck: { + // $subtract: [currentTime, "$latestCheck.createdAt"], + // }, + // latestResponseTime: "$latestCheck.responseTime", + // averageResponseTime: "$averageResponseTime", + // latestStatus: "$latestCheck.status", + // uptimeDuration: { + // $cond: [ + // { $eq: ["$latestCheck.status", false] }, + // 0, + // { + // $subtract: [ + // currentTime, + // { + // $ifNull: ["$lastFalseCheck.createdAt", "$firstCheck.createdAt"], + // }, + // ], + // }, + // ], + // }, + // uptimeDecimal: { + // $divide: ["$upChecks", "$totalChecks"], + // }, + // }, + // }, + // ]); const formatLookup = { day: "%Y-%m-%d-%H", @@ -388,29 +388,288 @@ const getUptimeDetailsById = async (req) => { const dateString = formatLookup[dateRange]; - const groupedChecks = await Check.aggregate([ + // const groupedChecks = await Check.aggregate([ + // { + // $match: { + // monitorId: monitor._id, + // }, + // }, + // { + // $sort: { + // createdAt: 1, + // }, + // }, + // { + // $facet: { + // aggregateData: [ + // { + // $group: { + // _id: null, + // avgResponseTime: { + // $avg: "$responseTime", + // }, + // firstCheck: { + // $first: "$$ROOT", + // }, + // lastCheck: { + // $last: "$$ROOT", + // }, + // }, + // }, + // ], + // uptimeDuration: [ + // { + // $match: { + // status: false, + // }, + // }, + // { + // $sort: { + // createdAt: 1, + // }, + // }, + // { + // $group: { + // _id: null, + // lastFalseCheck: { + // $last: "$$ROOT", + // }, + // }, + // }, + // ], + // upChecksAggregate: [ + // { + // $match: { + // status: true, + // createdAt: { $gte: dates.start, $lte: dates.end }, + // }, + // }, + // { + // $group: { + // _id: null, + // avgResponseTime: { + // $avg: "$responseTime", + // }, + // totalChecks: { + // $sum: 1, + // }, + // }, + // }, + // ], + // upChecks: [ + // { + // $match: { + // status: true, + // createdAt: { $gte: dates.start, $lte: dates.end }, + // }, + // }, + // { + // $group: { + // _id: { + // $dateToString: { + // format: dateString, + // date: "$createdAt", + // }, + // }, + // totalChecks: { + // $sum: 1, + // }, + // avgResponseTime: { + // $avg: "$responseTime", + // }, + // }, + // }, + // ], + // downChecksAggregate: [ + // { + // $match: { + // status: false, + // createdAt: { $gte: dates.start, $lte: dates.end }, + // }, + // }, + // { + // $group: { + // _id: null, + // avgResponseTime: { + // $avg: "$responseTime", + // }, + // totalChecks: { + // $sum: 1, + // }, + // }, + // }, + // ], + // downChecks: [ + // { + // $match: { + // status: false, + // createdAt: { $gte: dates.start, $lte: dates.end }, + // }, + // }, + // { + // $group: { + // _id: { + // $dateToString: { + // format: dateString, + // date: "$createdAt", + // }, + // }, + // totalChecks: { + // $sum: 1, + // }, + // avgResponseTime: { + // $avg: "$responseTime", + // }, + // }, + // }, + // ], + // }, + // }, + // { + // $project: { + // avgResponseTime: { + // $arrayElemAt: ["$aggregateData.avgResponseTime", 0], + // }, + // timeSinceLastCheck: { + // $let: { + // vars: { + // lastCheck: { + // $arrayElemAt: ["$aggregateData.lastCheck", 0], + // }, + // }, + // in: { + // $cond: [ + // { + // $ifNull: ["$$lastCheck", false], + // }, + // { + // $subtract: [new Date(), "$$lastCheck.createdAt"], + // }, + // 0, + // ], + // }, + // }, + // }, + // timeSinceLastFalseCheck: { + // $let: { + // vars: { + // lastFalseCheck: { + // $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0], + // }, + // firstCheck: { + // $arrayElemAt: ["$aggregateData.firstCheck", 0], + // }, + // }, + // in: { + // $cond: [ + // { + // $ifNull: ["$$lastFalseCheck", false], + // }, + // { + // $subtract: [new Date(), "$$lastFalseCheck.createdAt"], + // }, + // { + // $cond: [ + // { + // $ifNull: ["$$firstCheck", false], + // }, + // { + // $subtract: [new Date(), "$$firstCheck.createdAt"], + // }, + // 0, + // ], + // }, + // ], + // }, + // }, + // }, + // upChecksAggregate: { + // $arrayElemAt: ["$upChecksAggregate", 0], + // }, + // upChecks: "$upChecks", + // downChecksAggregate: { + // $arrayElemAt: ["$downChecksAggregate", 0], + // }, + // downChecks: "$downChecks", + // }, + // }, + // ]); + + const monitorData = await Check.aggregate([ { $match: { monitorId: monitor._id, - createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { + $sort: { + createdAt: 1, }, }, { $facet: { - upChecksCount: [ + aggregateData: [ { - $match: { status: true }, + $group: { + _id: null, + avgResponseTime: { + $avg: "$responseTime", + }, + firstCheck: { + $first: "$$ROOT", + }, + lastCheck: { + $last: "$$ROOT", + }, + totalChecks: { + $sum: 1, + }, + }, + }, + ], + uptimeDuration: [ + { + $match: { + status: false, + }, + }, + { + $sort: { + createdAt: 1, + }, }, { $group: { _id: null, - totalChecks: { $sum: 1 }, + lastFalseCheck: { + $last: "$$ROOT", + }, + }, + }, + ], + upChecksAggregate: [ + { + $match: { + status: true, + }, + }, + { + $group: { + _id: null, + avgResponseTime: { + $avg: "$responseTime", + }, + totalChecks: { + $sum: 1, + }, }, }, ], upChecks: [ { - $match: { status: true }, + $match: { + status: true, + }, }, { $group: { @@ -420,36 +679,38 @@ const getUptimeDetailsById = async (req) => { date: "$createdAt", }, }, - totalChecks: { $sum: 1 }, - upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, - avgResponseTime: { $avg: "$responseTime" }, + totalChecks: { + $sum: 1, + }, + avgResponseTime: { + $avg: "$responseTime", + }, }, }, - { - $project: { - totalChecks: 1, - avgResponseTime: 1, - groupUptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, - }, - }, - { - $sort: { _id: 1 }, - }, ], - downChecksCount: [ + downChecksAggregate: [ { - $match: { status: false }, + $match: { + status: false, + }, }, { $group: { _id: null, - totalChecks: { $sum: 1 }, + avgResponseTime: { + $avg: "$responseTime", + }, + totalChecks: { + $sum: 1, + }, }, }, ], downChecks: [ { - $match: { status: false }, + $match: { + status: false, + }, }, { $group: { @@ -459,36 +720,12 @@ const getUptimeDetailsById = async (req) => { date: "$createdAt", }, }, - totalChecks: { $sum: 1 }, - downChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } }, - avgResponseTime: { $avg: "$responseTime" }, - }, - }, - { - $project: { - totalChecks: 1, - avgResponseTime: 1, - groupUptimePercentage: { $divide: ["$downChecks", "$totalChecks"] }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - overall: [ - { - $group: { - _id: null, - totalChecks: { $sum: 1 }, - groupAvgResponseTime: { $avg: "$responseTime" }, - upChecks: { $sum: { $cond: [{ $eq: ["$status", true] }, 1, 0] } }, - }, - }, - { - $project: { - uptimePercentage: { $divide: ["$upChecks", "$totalChecks"] }, - totalChecks: 1, - groupAvgResponseTime: 1, + totalChecks: { + $sum: 1, + }, + avgResponseTime: { + $avg: "$responseTime", + }, }, }, ], @@ -496,60 +733,90 @@ const getUptimeDetailsById = async (req) => { }, { $project: { - upChecks: { - $map: { - input: "$upChecks", - as: "check", + avgResponseTime: { + $arrayElemAt: ["$aggregateData.avgResponseTime", 0], + }, + totalChecks: { + $arrayElemAt: ["$aggregateData.totalChecks", 0], + }, + timeSinceLastCheck: { + $let: { + vars: { + lastCheck: { + $arrayElemAt: ["$aggregateData.lastCheck", 0], + }, + }, in: { - $mergeObjects: [ - "$$check", + $cond: [ { - overallUptimePercentage: { - $arrayElemAt: ["$overall.uptimePercentage", 0], - }, - overallTotalChecks: { $arrayElemAt: ["$overall.totalChecks", 0] }, - upChecksCount: { - $arrayElemAt: ["$upChecksCount.totalChecks", 0], - }, - groupAvgResponseTime: { - $arrayElemAt: ["$overall.groupAvgResponseTime", 0], - }, + $ifNull: ["$$lastCheck", false], + }, + { + $subtract: [new Date(), "$$lastCheck.createdAt"], + }, + 0, + ], + }, + }, + }, + timeSinceLastFalseCheck: { + $let: { + vars: { + lastFalseCheck: { + $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0], + }, + firstCheck: { + $arrayElemAt: ["$aggregateData.firstCheck", 0], + }, + }, + in: { + $cond: [ + { + $ifNull: ["$$lastFalseCheck", false], + }, + { + $subtract: [new Date(), "$$lastFalseCheck.createdAt"], + }, + { + $cond: [ + { + $ifNull: ["$$firstCheck", false], + }, + { + $subtract: [new Date(), "$$firstCheck.createdAt"], + }, + 0, + ], }, ], }, }, }, - downChecks: { - $map: { - input: "$downChecks", - as: "check", - in: { - $mergeObjects: [ - "$$check", - { - overallUptimePercentage: { - $arrayElemAt: ["$overall.uptimePercentage", 0], - }, - overallTotalChecks: { $arrayElemAt: ["$overall.totalChecks", 0] }, - downChecksCount: { - $arrayElemAt: ["$downChecksCount.totalChecks", 0], - }, - }, - ], - }, - }, + upChecksAggregate: { + $arrayElemAt: ["$upChecksAggregate", 0], }, + upChecks: "$upChecks", + downChecksAggregate: { + $arrayElemAt: ["$downChecksAggregate", 0], + }, + downChecks: "$downChecks", }, }, ]); const monitorStats = { ...monitor.toObject(), + stats: { + avgResponseTime: monitorData[0].avgResponseTime, + totalChecks: monitorData[0].totalChecks, + timeSinceLastCheck: monitorData[0].timeSinceLastCheck, + timeSinceLastFalseCheck: monitorData[0].timeSinceLastFalseCheck, + upChecksAggregate: monitorData[0].upChecksAggregate, + upChecks: monitorData[0].upChecks, + downChecksAggregate: monitorData[0].downChecksAggregate, + downChecks: monitorData[0].downChecks, + }, }; - console.log(groupedChecks); - monitorStats.aggregateData = aggregateResults[0]; - monitorStats.groupUpChecks = groupedChecks[0].upChecks; - monitorStats.groupDownChecks = groupedChecks[0].downChecks; return monitorStats; } catch (error) { From fabfbec3297974a3b33c5116d78a8a06d4332b3c Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 31 Dec 2024 21:31:36 +0000 Subject: [PATCH 12/27] fix(deps): update dependency bullmq to v5.34.6 --- Server/package-lock.json | 8 ++++---- Server/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index e9dc276c4..3b7c616d2 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "5.1.1", - "bullmq": "5.34.5", + "bullmq": "5.34.6", "cors": "^2.8.5", "dockerode": "4.0.2", "dotenv": "^16.4.5", @@ -1541,9 +1541,9 @@ } }, "node_modules/bullmq": { - "version": "5.34.5", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.5.tgz", - "integrity": "sha512-MHho9EOhLCTY3ZF+dd0wHv0VlY2FtpBcopMRsvj0kPra4TAwBFh2pik/s4WbX56cIfCE+VzfHIHy4xvqp3g1+Q==", + "version": "5.34.6", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.6.tgz", + "integrity": "sha512-pRCYyO9RlkQWxdmKlrNnUthyFwurYXRYLVXD1YIx+nCCdhAOiHatD8FDHbsT/w2I31c0NWoMcfZiIGuipiF7Lg==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index c8460aa03..894aca859 100644 --- a/Server/package.json +++ b/Server/package.json @@ -18,7 +18,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "5.1.1", - "bullmq": "5.34.5", + "bullmq": "5.34.6", "cors": "^2.8.5", "dockerode": "4.0.2", "dotenv": "^16.4.5", From bbae5052c86753826a5f86f3b3fea25de80d1caa Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 13:42:25 -0800 Subject: [PATCH 13/27] Add group checks to DB query --- Server/db/mongo/modules/monitorModule.js | 67 +++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index aa429b6e4..27bb409b3 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -4,7 +4,7 @@ import PageSpeedCheck from "../../models/PageSpeedCheck.js"; import HardwareCheck from "../../models/HardwareCheck.js"; import { errorMessages } from "../../../utils/messages.js"; import Notification from "../../models/Notification.js"; -import { NormalizeData } from "../../../utils/dataUtils.js"; +import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js"; import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; @@ -647,6 +647,49 @@ const getUptimeDetailsById = async (req) => { }, }, ], + groupChecks: [ + { + $match: { + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + avgResponseTime: { + $avg: "$responseTime", + }, + totalChecks: { + $sum: 1, + }, + }, + }, + { + $sort: { + _id: 1, + }, + }, + ], + groupAggregate: [ + { + $match: { + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { + $group: { + _id: null, + avgResponseTime: { + $avg: "$responseTime", + }, + }, + }, + ], upChecksAggregate: [ { $match: { @@ -687,6 +730,9 @@ const getUptimeDetailsById = async (req) => { }, }, }, + { + $sort: { _id: 1 }, + }, ], downChecksAggregate: [ { @@ -728,6 +774,9 @@ const getUptimeDetailsById = async (req) => { }, }, }, + { + $sort: { _id: 1 }, + }, ], }, }, @@ -739,6 +788,9 @@ const getUptimeDetailsById = async (req) => { totalChecks: { $arrayElemAt: ["$aggregateData.totalChecks", 0], }, + latestResponseTime: { + $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 0], + }, timeSinceLastCheck: { $let: { vars: { @@ -792,6 +844,10 @@ const getUptimeDetailsById = async (req) => { }, }, }, + groupChecks: "$groupChecks", + groupAggregate: { + $arrayElemAt: ["$groupAggregate", 0], + }, upChecksAggregate: { $arrayElemAt: ["$upChecksAggregate", 0], }, @@ -804,6 +860,12 @@ const getUptimeDetailsById = async (req) => { }, ]); + const normalizedGroupChecks = NormalizeDataUptimeDetails( + monitorData[0].groupChecks, + 10, + 100 + ); + const monitorStats = { ...monitor.toObject(), stats: { @@ -811,6 +873,9 @@ const getUptimeDetailsById = async (req) => { totalChecks: monitorData[0].totalChecks, timeSinceLastCheck: monitorData[0].timeSinceLastCheck, timeSinceLastFalseCheck: monitorData[0].timeSinceLastFalseCheck, + latestResponseTime: monitorData[0].latestResponseTime, + groupChecks: normalizedGroupChecks, + groupAggregate: monitorData[0].groupAggregate, upChecksAggregate: monitorData[0].upChecksAggregate, upChecks: monitorData[0].upChecks, downChecksAggregate: monitorData[0].downChecksAggregate, From 17bd1f5286bd04d06380400941e11bac4a191aa1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 13:42:49 -0800 Subject: [PATCH 14/27] Add temporary normalization functoins --- Server/utils/dataUtils.js | 52 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index b0f8a2fd1..25f26d439 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -8,12 +8,23 @@ const calculatePercentile = (arr, percentile) => { return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight; }; +const calculatePercentileUptimeDetails = (arr, percentile) => { + const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime); + const index = (percentile / 100) * (sorted.length - 1); + const lower = Math.floor(index); + const upper = lower + 1; + const weight = index % 1; + if (upper >= sorted.length) return sorted[lower].avgResponseTime; + return ( + sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight + ); +}; + const NormalizeData = (checks, rangeMin, rangeMax) => { if (checks.length > 1) { // Get the 5th and 95th percentile const min = calculatePercentile(checks, 0); const max = calculatePercentile(checks, 95); - const normalizedChecks = checks.map((check) => { const originalResponseTime = check.responseTime; // Normalize the response time between 1 and 100 @@ -40,5 +51,42 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { }); } }; +const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => { + if (checks.length > 1) { + // Get the 5th and 95th percentile + const min = calculatePercentileUptimeDetails(checks, 0); + const max = calculatePercentileUptimeDetails(checks, 95); -export { calculatePercentile, NormalizeData }; + const normalizedChecks = checks.map((check) => { + const originalResponseTime = check.avgResponseTime; + // Normalize the response time between 1 and 100 + let normalizedResponseTime = + rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min); + + // Put a floor on the response times so we don't have extreme outliers + // Better visuals + normalizedResponseTime = Math.max( + rangeMin, + Math.min(rangeMax, normalizedResponseTime) + ); + return { + ...check, + avgResponseTime: normalizedResponseTime, + originalAvgResponseTime: originalResponseTime, + }; + }); + + return normalizedChecks; + } else { + return checks.map((check) => { + return { ...check._doc, originalResponseTime: check.responseTime }; + }); + } +}; + +export { + calculatePercentile, + NormalizeData, + calculatePercentileUptimeDetails, + NormalizeDataUptimeDetails, +}; From 91b282e7d936ea0e18a534bd66835bd3ea8b35f1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 13:43:03 -0800 Subject: [PATCH 15/27] Refactor charts --- .../Charts/MonitorDetailsAreaChart/index.jsx | 35 +- .../Uptime/Details/Charts/CustomLabels.jsx | 51 +++ .../Uptime/Details/Charts/DownBarChart.jsx | 89 +++++ .../Details/Charts/ResponseGaugeChart.jsx | 117 ++++++ .../Uptime/Details/Charts/UpBarChart.jsx | 100 +++++ .../src/Pages/Uptime/Details/Charts/index.jsx | 350 ------------------ Client/src/Pages/Uptime/Details/index.jsx | 47 +-- 7 files changed, 408 insertions(+), 381 deletions(-) create mode 100644 Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx create mode 100644 Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx create mode 100644 Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx create mode 100644 Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Charts/index.jsx diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index c45fa1b72..947f776a4 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -12,12 +12,12 @@ import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz, toTimeStamp } from "../../../Utils/timeUtils"; import "./index.css"; const CustomToolTip = ({ active, payload, label }) => { const uiTimezone = useSelector((state) => state.ui.timezone); - + const dateFormat = label?.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; const theme = useTheme(); if (active && payload && payload.length) { return ( @@ -39,7 +39,11 @@ const CustomToolTip = ({ active, payload, label }) => { fontWeight: 500, }} > - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + {formatDateWithTz( + toTimeStamp(label, dateFormat), + "ddd, MMMM D, YYYY, h:mm A", + uiTimezone + )} { Response Time {" "} - {payload[0].payload.originalResponseTime} + {Math.floor(payload[0].payload.originalAvgResponseTime)} { return null; }; +CustomToolTip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.arrayOf( + PropTypes.shape({ + payload: PropTypes.shape({ + _id: PropTypes.string.isRequired, // Add this line to validate _id + originalResponseTime: PropTypes.number.isRequired, + }).isRequired, + }) + ), + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +}; + const CustomTick = ({ x, y, payload, index }) => { const theme = useTheme(); const uiTimezone = useSelector((state) => state.ui.timezone); - + const dateFormat = payload?.value.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; // Render nothing for the first tick if (index === 0) return null; return ( @@ -103,7 +120,7 @@ const CustomTick = ({ x, y, payload, index }) => { fontSize={11} fontWeight={400} > - {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} + {formatDateWithTz(toTimeStamp(payload?.value, dateFormat), "h:mm a", uiTimezone)} ); }; @@ -168,7 +185,7 @@ const MonitorDetailsAreaChart = ({ checks }) => { } minTickGap={0} axisLine={false} @@ -183,7 +200,7 @@ const MonitorDetailsAreaChart = ({ checks }) => { /> { + const uiTimezone = useSelector((state) => state.ui.timezone); + const formatString = type === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; + const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; + + return ( + <> + + {formatDateWithTz( + toTimeStamp(firstDataPoint._id, formatString), + dateFormat, + uiTimezone + )} + + + {formatDateWithTz( + toTimeStamp(lastDataPoint._id, formatString), + dateFormat, + uiTimezone + )} + + + ); +}; + +CustomLabels.propTypes = { + x: PropTypes.number.isRequired, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + firstDataPoint: PropTypes.object.isRequired, + lastDataPoint: PropTypes.object.isRequired, + type: PropTypes.string.isRequired, +}; + +export default CustomLabels; diff --git a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx new file mode 100644 index 000000000..a851a93ea --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx @@ -0,0 +1,89 @@ +import { memo, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; +import PropTypes from "prop-types"; +import CustomLabels from "./CustomLabels"; + +const DownBarChart = memo(({ stats, type, onBarHover }) => { + const theme = useTheme(); + + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + + return ( + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: 0 }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > + + } + /> + + {stats.downChecks.map((entry, index) => ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalChecks: 0 }); + }} + /> + ))} + + + + ); +}); + +DownBarChart.displayName = "DownBarChart"; +DownBarChart.propTypes = { + stats: PropTypes.shape({ + downChecks: PropTypes.arrayOf(PropTypes.object), + downChecksAggregate: PropTypes.object, + }), + type: PropTypes.string, + onBarHover: PropTypes.func, +}; +export default DownBarChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx b/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx new file mode 100644 index 000000000..29e0cc8bb --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx @@ -0,0 +1,117 @@ +import PropTypes from "prop-types"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts"; + +const ResponseGaugeChart = ({ avgResponseTime }) => { + const theme = useTheme(); + + let max = 1000; // max ms + + const data = [ + { response: max, fill: "transparent", background: false }, + { response: avgResponseTime, background: true }, + ]; + let responseTime = Math.floor(avgResponseTime); + let responseProps = + responseTime <= 200 + ? { + category: "Excellent", + main: theme.palette.success.main, + bg: theme.palette.success.contrastText, + } + : responseTime <= 500 + ? { + category: "Fair", + main: theme.palette.success.main, + bg: theme.palette.success.contrastText, + } + : responseTime <= 600 + ? { + category: "Acceptable", + main: theme.palette.warning.main, + bg: theme.palette.warning.dark, + } + : { + category: "Poor", + main: theme.palette.error.main, + bg: theme.palette.error.light, + }; + + return ( + + + + low + + + high + + + {responseProps.category} + + + {responseTime} ms + + + + + + + + ); +}; + +ResponseGaugeChart.propTypes = { + avgResponseTime: PropTypes.number.isRequired, +}; + +export default ResponseGaugeChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx new file mode 100644 index 000000000..7ee77864d --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx @@ -0,0 +1,100 @@ +import { memo, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; +import PropTypes from "prop-types"; +import CustomLabels from "./CustomLabels"; + +const UpBarChart = memo(({ stats, type, onBarHover }) => { + const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + + const getColorRange = (responseTime) => { + return responseTime < 200 + ? { main: theme.palette.success.main, light: theme.palette.success.light } + : responseTime < 300 + ? { main: theme.palette.warning.main, light: theme.palette.warning.light } + : { main: theme.palette.error.contrastText, light: theme.palette.error.light }; + }; + + return ( + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > + + } + /> + + {stats.upChecks.map((entry, index) => { + let { main, light } = getColorRange(entry.avgResponseTime); + return ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ + time: null, + totalChecks: 0, + groupUptimePercentage: 0, + }); + }} + /> + ); + })} + + + + ); +}); + +// Add display name for the component +UpBarChart.displayName = "UpBarChart"; + +// Validate props using PropTypes +UpBarChart.propTypes = { + stats: PropTypes.shape({ + upChecks: PropTypes.array, + upChecksAggregate: PropTypes.object, + }), + type: PropTypes.string, + onBarHover: PropTypes.func, +}; +export default UpBarChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/index.jsx b/Client/src/Pages/Uptime/Details/Charts/index.jsx deleted file mode 100644 index 8f135196a..000000000 --- a/Client/src/Pages/Uptime/Details/Charts/index.jsx +++ /dev/null @@ -1,350 +0,0 @@ -import { useTheme } from "@emotion/react"; -import PropTypes from "prop-types"; -import { - BarChart, - Bar, - XAxis, - ResponsiveContainer, - Cell, - RadialBarChart, - RadialBar, -} from "recharts"; -import { memo, useMemo, useState } from "react"; -import { useSelector } from "react-redux"; -import { formatDateWithTz, toTimeStamp } from "../../../../Utils/timeUtils"; - -const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { - const uiTimezone = useSelector((state) => state.ui.timezone); - const formatString = type === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; - const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; - - return ( - <> - - {formatDateWithTz( - toTimeStamp(firstDataPoint._id, formatString), - dateFormat, - uiTimezone - )} - - - {formatDateWithTz( - toTimeStamp(lastDataPoint._id, formatString), - dateFormat, - uiTimezone - )} - - - ); -}; - -CustomLabels.propTypes = { - x: PropTypes.number.isRequired, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - firstDataPoint: PropTypes.object.isRequired, - lastDataPoint: PropTypes.object.isRequired, - type: PropTypes.string.isRequired, -}; - -const UpBarChart = memo(({ data, type, onBarHover }) => { - const theme = useTheme(); - - const [chartHovered, setChartHovered] = useState(false); - const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - - const getColorRange = (uptime) => { - return uptime > 0.8 - ? { main: theme.palette.success.main, light: theme.palette.success.light } - : uptime > 0.5 - ? { main: theme.palette.warning.main, light: theme.palette.warning.light } - : { main: theme.palette.error.contrastText, light: theme.palette.error.light }; - }; - - // TODO - REMOVE THIS LATER - - return ( - - { - setChartHovered(true); - onBarHover({ time: null, totalChecks: 0, groupUptimePercentage: 0 }); - }} - onMouseLeave={() => { - setChartHovered(false); - setHoveredBarIndex(null); - onBarHover(null); - }} - > - - } - /> - - {data.map((entry, index) => { - let { main, light } = getColorRange(entry.groupUptimePercentage); - return ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ - time: null, - totalChecks: 0, - groupUptimePercentage: 0, - }); - }} - /> - ); - })} - - - - ); -}); - -// Add display name for the component -UpBarChart.displayName = "UpBarChart"; - -// Validate props using PropTypes -UpBarChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.object), - type: PropTypes.string, - onBarHover: PropTypes.func, -}; -export { UpBarChart }; - -const DownBarChart = memo(({ data, type, onBarHover }) => { - const theme = useTheme(); - - const [chartHovered, setChartHovered] = useState(false); - const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - - return ( - - { - setChartHovered(true); - onBarHover({ time: null, totalChecks: 0 }); - }} - onMouseLeave={() => { - setChartHovered(false); - setHoveredBarIndex(null); - onBarHover(null); - }} - > - - } - /> - - {data.map((entry, index) => ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ time: null, totalChecks: 0 }); - }} - /> - ))} - - - - ); -}); - -DownBarChart.displayName = "DownBarChart"; -DownBarChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.object), - type: PropTypes.string, - onBarHover: PropTypes.func, -}; -export { DownBarChart }; - -const ResponseGaugeChart = ({ avgResponseTime }) => { - const theme = useTheme(); - - let max = 1000; // max ms - - const data = [ - { response: max, fill: "transparent", background: false }, - { response: avgResponseTime, background: true }, - ]; - let responseTime = Math.floor(avgResponseTime); - let responseProps = - responseTime <= 200 - ? { - category: "Excellent", - main: theme.palette.success.main, - bg: theme.palette.success.contrastText, - } - : responseTime <= 500 - ? { - category: "Fair", - main: theme.palette.success.main, - bg: theme.palette.success.contrastText, - } - : responseTime <= 600 - ? { - category: "Acceptable", - main: theme.palette.warning.main, - bg: theme.palette.warning.dark, - } - : { - category: "Poor", - main: theme.palette.error.main, - bg: theme.palette.error.light, - }; - - return ( - - - - low - - - high - - - {responseProps.category} - - - {responseTime} ms - - - - - - - - ); -}; - -ResponseGaugeChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export { ResponseGaugeChart }; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 93c1b99d7..45a99095e 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -17,7 +17,6 @@ import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import { ChartBox } from "./styled"; -import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts"; import SkeletonLayout from "./skeleton"; import "./index.css"; import useUtils from "../utils"; @@ -26,7 +25,9 @@ import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; import StatBox from "../../../Components/StatBox"; import { toTimeStamp } from "../../../Utils/timeUtils"; - +import UpBarChart from "./Charts/UpBarChart"; +import DownBarChart from "./Charts/DownBarChart"; +import ResponseGaugeChart from "./Charts/ResponseGaugeChart"; /** * Details page component displaying monitor details and related information. * @component @@ -54,7 +55,6 @@ const DetailsPage = () => { dateRange: dateRange, normalize: true, }); - console.log(res?.data?.data); setMonitor(res?.data?.data ?? {}); } catch (error) { logger.error(error); @@ -105,10 +105,6 @@ const DetailsPage = () => { const [hoveredUptimeData, setHoveredUptimeData] = useState(null); const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); - useEffect(() => { - console.log(hoveredUptimeData); - }, [hoveredUptimeData]); - const BREADCRUMBS = [ { name: "uptime", path: "/uptime" }, { name: "details", path: `/uptime/${monitorId}` }, @@ -226,17 +222,17 @@ const DetailsPage = () => { - {monitor?.aggregateData?.latestResponseTime} + {monitor?.stats?.latestResponseTime} {"ms"} } @@ -313,7 +309,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.groupUpChecks[0]?.upChecksCount ?? 0)} + : (monitor.stats?.totalChecks ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { )} - Uptime Percentage + + {hoveredUptimeData !== null + ? "Avg Response Time" + : "Uptime Percentage"} + {hoveredUptimeData !== null - ? Math.floor(hoveredUptimeData.groupUptimePercentage * 100) + ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) : Math.floor( - (monitor?.groupUpChecks[0]?.overallUptimePercentage ?? 0) * + ((monitor?.stats?.upChecksAggregate.totalChecks ?? 0) / + (monitor?.stats?.totalChecks ?? 1)) * 100 )} - % + + {hoveredUptimeData !== null ? " ms" : " %"} + @@ -365,7 +368,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.groupDownChecks[0]?.downChecksCount ?? 0)} + : (monitor.stats?.downChecksAggregate.totalChecks ?? 0)} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( @@ -388,7 +391,7 @@ const DetailsPage = () => { )} @@ -401,7 +404,7 @@ const DetailsPage = () => { Average Response Time @@ -414,7 +417,7 @@ const DetailsPage = () => { Response Times - {/* */} + { History - {/* */} + /> From a466e91a45e3818c8716d863d8eb785e6f256d30 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 13:46:56 -0800 Subject: [PATCH 16/27] remove old query --- Server/db/mongo/modules/monitorModule.js | 265 ----------------------- 1 file changed, 265 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 27bb409b3..d36fb71d5 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -322,63 +322,6 @@ const getUptimeDetailsById = async (req) => { const { dateRange, normalize } = req.query; const dates = getDateRange(dateRange); - const currentTime = new Date(); - - // Build pipeline - // const aggregateResults = await Check.aggregate([ - // { - // $match: { monitorId: monitor._id }, - // }, - // { - // $sort: { createdAt: 1 }, - // }, - // { - // $group: { - // _id: null, - // latestCheck: { $last: "$$ROOT" }, - // lastFalseCheck: { - // $last: { - // $cond: [{ $eq: ["$status", false] }, "$$ROOT", null], - // }, - // }, - // firstCheck: { $first: "$$ROOT" }, - // averageResponseTime: { $avg: "$responseTime" }, - // upChecks: { - // $sum: { - // $cond: [{ $eq: ["$status", true] }, 1, 0], - // }, - // }, - // totalChecks: { $sum: 1 }, - // }, - // }, - // { - // $project: { - // timeSinceLastCheck: { - // $subtract: [currentTime, "$latestCheck.createdAt"], - // }, - // latestResponseTime: "$latestCheck.responseTime", - // averageResponseTime: "$averageResponseTime", - // latestStatus: "$latestCheck.status", - // uptimeDuration: { - // $cond: [ - // { $eq: ["$latestCheck.status", false] }, - // 0, - // { - // $subtract: [ - // currentTime, - // { - // $ifNull: ["$lastFalseCheck.createdAt", "$firstCheck.createdAt"], - // }, - // ], - // }, - // ], - // }, - // uptimeDecimal: { - // $divide: ["$upChecks", "$totalChecks"], - // }, - // }, - // }, - // ]); const formatLookup = { day: "%Y-%m-%d-%H", @@ -387,214 +330,6 @@ const getUptimeDetailsById = async (req) => { }; const dateString = formatLookup[dateRange]; - - // const groupedChecks = await Check.aggregate([ - // { - // $match: { - // monitorId: monitor._id, - // }, - // }, - // { - // $sort: { - // createdAt: 1, - // }, - // }, - // { - // $facet: { - // aggregateData: [ - // { - // $group: { - // _id: null, - // avgResponseTime: { - // $avg: "$responseTime", - // }, - // firstCheck: { - // $first: "$$ROOT", - // }, - // lastCheck: { - // $last: "$$ROOT", - // }, - // }, - // }, - // ], - // uptimeDuration: [ - // { - // $match: { - // status: false, - // }, - // }, - // { - // $sort: { - // createdAt: 1, - // }, - // }, - // { - // $group: { - // _id: null, - // lastFalseCheck: { - // $last: "$$ROOT", - // }, - // }, - // }, - // ], - // upChecksAggregate: [ - // { - // $match: { - // status: true, - // createdAt: { $gte: dates.start, $lte: dates.end }, - // }, - // }, - // { - // $group: { - // _id: null, - // avgResponseTime: { - // $avg: "$responseTime", - // }, - // totalChecks: { - // $sum: 1, - // }, - // }, - // }, - // ], - // upChecks: [ - // { - // $match: { - // status: true, - // createdAt: { $gte: dates.start, $lte: dates.end }, - // }, - // }, - // { - // $group: { - // _id: { - // $dateToString: { - // format: dateString, - // date: "$createdAt", - // }, - // }, - // totalChecks: { - // $sum: 1, - // }, - // avgResponseTime: { - // $avg: "$responseTime", - // }, - // }, - // }, - // ], - // downChecksAggregate: [ - // { - // $match: { - // status: false, - // createdAt: { $gte: dates.start, $lte: dates.end }, - // }, - // }, - // { - // $group: { - // _id: null, - // avgResponseTime: { - // $avg: "$responseTime", - // }, - // totalChecks: { - // $sum: 1, - // }, - // }, - // }, - // ], - // downChecks: [ - // { - // $match: { - // status: false, - // createdAt: { $gte: dates.start, $lte: dates.end }, - // }, - // }, - // { - // $group: { - // _id: { - // $dateToString: { - // format: dateString, - // date: "$createdAt", - // }, - // }, - // totalChecks: { - // $sum: 1, - // }, - // avgResponseTime: { - // $avg: "$responseTime", - // }, - // }, - // }, - // ], - // }, - // }, - // { - // $project: { - // avgResponseTime: { - // $arrayElemAt: ["$aggregateData.avgResponseTime", 0], - // }, - // timeSinceLastCheck: { - // $let: { - // vars: { - // lastCheck: { - // $arrayElemAt: ["$aggregateData.lastCheck", 0], - // }, - // }, - // in: { - // $cond: [ - // { - // $ifNull: ["$$lastCheck", false], - // }, - // { - // $subtract: [new Date(), "$$lastCheck.createdAt"], - // }, - // 0, - // ], - // }, - // }, - // }, - // timeSinceLastFalseCheck: { - // $let: { - // vars: { - // lastFalseCheck: { - // $arrayElemAt: ["$uptimeDuration.lastFalseCheck", 0], - // }, - // firstCheck: { - // $arrayElemAt: ["$aggregateData.firstCheck", 0], - // }, - // }, - // in: { - // $cond: [ - // { - // $ifNull: ["$$lastFalseCheck", false], - // }, - // { - // $subtract: [new Date(), "$$lastFalseCheck.createdAt"], - // }, - // { - // $cond: [ - // { - // $ifNull: ["$$firstCheck", false], - // }, - // { - // $subtract: [new Date(), "$$firstCheck.createdAt"], - // }, - // 0, - // ], - // }, - // ], - // }, - // }, - // }, - // upChecksAggregate: { - // $arrayElemAt: ["$upChecksAggregate", 0], - // }, - // upChecks: "$upChecks", - // downChecksAggregate: { - // $arrayElemAt: ["$downChecksAggregate", 0], - // }, - // downChecks: "$downChecks", - // }, - // }, - // ]); - const monitorData = await Check.aggregate([ { $match: { From e29b55408ff34c869725187b96c9f184aadcaba7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 16:39:50 -0800 Subject: [PATCH 17/27] Fixed color for upbarchart: --- Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx index 7ee77864d..08c8d778e 100644 --- a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx @@ -14,7 +14,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { ? { main: theme.palette.success.main, light: theme.palette.success.light } : responseTime < 300 ? { main: theme.palette.warning.main, light: theme.palette.warning.light } - : { main: theme.palette.error.contrastText, light: theme.palette.error.light }; + : { main: theme.palette.error.main, light: theme.palette.error.light }; }; return ( From 58c416f909c5485753221a40b9e20fd63f0fb156 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 31 Dec 2024 16:40:16 -0800 Subject: [PATCH 18/27] use upchecksaggregate total checks instead of total checks --- Client/src/Pages/Uptime/Details/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 45a99095e..eb4020aab 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -309,7 +309,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.stats?.totalChecks ?? 0)} + : (monitor.stats?.upChecksAggregate.totalChecks ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( Date: Tue, 31 Dec 2024 16:40:44 -0800 Subject: [PATCH 19/27] add two mising date clauses to details query --- Server/db/mongo/modules/monitorModule.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index d36fb71d5..be70d887e 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -322,7 +322,7 @@ const getUptimeDetailsById = async (req) => { const { dateRange, normalize } = req.query; const dates = getDateRange(dateRange); - + console.log(dates); const formatLookup = { day: "%Y-%m-%d-%H", week: "%Y-%m-%d-%H", @@ -447,6 +447,7 @@ const getUptimeDetailsById = async (req) => { { $match: { status: true, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -491,6 +492,7 @@ const getUptimeDetailsById = async (req) => { { $match: { status: false, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { From 24f5c54fe5aee822048467a6acb4f1072dbef80a Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:25:14 -0500 Subject: [PATCH 20/27] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b39ad91b..6e19ddf00 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ **If you would like to support us, please consider giving it a ⭐, and think about contributing or providing feedback.** -![Frame 34](https://github.com/user-attachments/assets/4bf57845-3f47-4759-835b-285a5486191d) +Frame 34 + + ![](https://img.shields.io/github/license/bluewave-labs/checkmate) From ca258da7f075af51d4c7ff5bdf8f44285800dacd Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Wed, 1 Jan 2025 08:55:47 -0500 Subject: [PATCH 21/27] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6e19ddf00..0476f2fd7 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,21 @@ If you have any questions, suggestions or comments, please use our [Discord chan - Infrastructure monitoring (memory, disk usage, CPU performance etc) - requires [Capture](https://github.com/bluewave-labs/capture) - Docker monitoring - Ping monitoring +- SSL monitoring - Incidents at a glance - E-mail notifications - Scheduled maintenance -- Can monitor 1000+ servers at the same time on a moderate server **Short term roadmap:** -- Global (distributed) uptime checking on Solana network -- Status pages -- Better notification options (think Discord, Telegram, Slack) +- Port monitoring (**complete**, waiting to be deployed to stable version) +- Global (distributed) uptime checking on Solana network (**in progress**) +- Status pages (**in progress**) +- Better notification options (Webhooks, Discord, Telegram, Slack) - More configuration options +- Translations +- Tagging/grouping monitors - DNS monitoring -- SSL monitoring -- Port monitoring ## 🏗️ Screenshots From dafdb03ce05c31fe419e7208010df540293ed200 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 17:06:49 -0800 Subject: [PATCH 22/27] safe variable access --- Client/src/Pages/Uptime/Details/index.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index eb4020aab..1b2800139 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -309,7 +309,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.stats?.upChecksAggregate.totalChecks ?? 0)} + : (monitor.stats?.upChecksAggregate?.totalChecks ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { {hoveredUptimeData !== null ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) : Math.floor( - ((monitor?.stats?.upChecksAggregate.totalChecks ?? 0) / + ((monitor?.stats?.upChecksAggregate?.totalChecks ?? 0) / (monitor?.stats?.totalChecks ?? 1)) * 100 )} @@ -368,7 +368,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.stats?.downChecksAggregate.totalChecks ?? 0)} + : (monitor.stats?.downChecksAggregate?.totalChecks ?? 0)} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( @@ -404,7 +404,7 @@ const DetailsPage = () => { Average Response Time From 86d11c7277a404da59555dd340367520b810f92f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 17:11:26 -0800 Subject: [PATCH 23/27] remove console.log --- 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 be70d887e..705cdc753 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -322,7 +322,6 @@ const getUptimeDetailsById = async (req) => { const { dateRange, normalize } = req.query; const dates = getDateRange(dateRange); - console.log(dates); const formatLookup = { day: "%Y-%m-%d-%H", week: "%Y-%m-%d-%H", From 69f7cf2ab14aa77c52be84bd2cbff8eb44e89942 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 17:11:38 -0800 Subject: [PATCH 24/27] fix check spread --- Server/utils/dataUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index 25f26d439..fd10a4257 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -79,7 +79,7 @@ const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => { return normalizedChecks; } else { return checks.map((check) => { - return { ...check._doc, originalResponseTime: check.responseTime }; + return { ...check, originalResponseTime: check.responseTime }; }); } }; From e7cf127fde4c10af20063f29b9cf2a84c8cffd49 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 17:20:00 -0800 Subject: [PATCH 25/27] Fix tooltip and proptypes --- .../Charts/MonitorDetailsAreaChart/index.jsx | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 947f776a4..980d72c23 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -20,6 +20,9 @@ const CustomToolTip = ({ active, payload, label }) => { const dateFormat = label?.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; const theme = useTheme(); if (active && payload && payload.length) { + const responseTime = payload[0]?.payload?.originalAvgResponseTime + ? payload[0]?.payload?.originalAvgResponseTime + : (payload[0]?.payload?.avgResponseTime ?? 0); return ( { Response Time {" "} - {Math.floor(payload[0].payload.originalAvgResponseTime)} + {Math.floor(responseTime)} { const theme = useTheme(); @@ -215,15 +219,4 @@ MonitorDetailsAreaChart.propTypes = { checks: PropTypes.array, }; -CustomToolTip.propTypes = { - active: PropTypes.bool, - payload: PropTypes.arrayOf( - PropTypes.shape({ - payload: PropTypes.shape({ - originalAvgResponseTime: PropTypes.number, - }).isRequired, - }) - ), - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; export default MonitorDetailsAreaChart; From b332bed14e882d32f1fe527fdd7984dc7bd85172 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 1 Jan 2025 17:27:12 -0800 Subject: [PATCH 26/27] modify date format to ISO standard, remove toTimeStamp method --- .../Charts/MonitorDetailsAreaChart/index.jsx | 12 +++--------- .../Pages/Uptime/Details/Charts/CustomLabels.jsx | 15 +++------------ Client/src/Pages/Uptime/Details/index.jsx | 11 ++--------- Client/src/Utils/timeUtils.js | 6 ------ Server/db/mongo/modules/monitorModule.js | 6 +++--- 5 files changed, 11 insertions(+), 39 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 980d72c23..2fefd3daa 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -12,12 +12,11 @@ import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; -import { formatDateWithTz, toTimeStamp } from "../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../Utils/timeUtils"; import "./index.css"; const CustomToolTip = ({ active, payload, label }) => { const uiTimezone = useSelector((state) => state.ui.timezone); - const dateFormat = label?.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; const theme = useTheme(); if (active && payload && payload.length) { const responseTime = payload[0]?.payload?.originalAvgResponseTime @@ -42,11 +41,7 @@ const CustomToolTip = ({ active, payload, label }) => { fontWeight: 500, }} > - {formatDateWithTz( - toTimeStamp(label, dateFormat), - "ddd, MMMM D, YYYY, h:mm A", - uiTimezone - )} + {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} { const theme = useTheme(); const uiTimezone = useSelector((state) => state.ui.timezone); - const dateFormat = payload?.value.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; // Render nothing for the first tick if (index === 0) return null; return ( @@ -124,7 +118,7 @@ const CustomTick = ({ x, y, payload, index }) => { fontSize={11} fontWeight={400} > - {formatDateWithTz(toTimeStamp(payload?.value, dateFormat), "h:mm a", uiTimezone)} + {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} ); }; diff --git a/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx b/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx index cfbe9d234..10494830f 100644 --- a/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx @@ -1,10 +1,9 @@ import PropTypes from "prop-types"; import { useSelector } from "react-redux"; -import { formatDateWithTz, toTimeStamp } from "../../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils"; const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { const uiTimezone = useSelector((state) => state.ui.timezone); - const formatString = type === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; return ( @@ -16,11 +15,7 @@ const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) textAnchor="start" fontSize={11} > - {formatDateWithTz( - toTimeStamp(firstDataPoint._id, formatString), - dateFormat, - uiTimezone - )} + {formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)} - {formatDateWithTz( - toTimeStamp(lastDataPoint._id, formatString), - dateFormat, - uiTimezone - )} + {formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)} ); diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 1b2800139..7a2aa6756 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -24,7 +24,6 @@ import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils" import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; import StatBox from "../../../Components/StatBox"; -import { toTimeStamp } from "../../../Utils/timeUtils"; import UpBarChart from "./Charts/UpBarChart"; import DownBarChart from "./Charts/DownBarChart"; import ResponseGaugeChart from "./Charts/ResponseGaugeChart"; @@ -320,10 +319,7 @@ const DetailsPage = () => { color={theme.palette.text.tertiary} > {formatDateWithTz( - toTimeStamp( - hoveredUptimeData._id, - dateRange === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH" - ), + hoveredUptimeData._id, dateFormat, uiTimezone )} @@ -380,10 +376,7 @@ const DetailsPage = () => { color={theme.palette.text.tertiary} > {formatDateWithTz( - toTimeStamp( - hoveredIncidentsData._id, - dateRange === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH" - ), + hoveredIncidentsData._id, dateFormat, uiTimezone )} diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 04b51d780..1a84d0ea5 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -75,12 +75,6 @@ export const formatDurationSplit = (ms) => { : { time: 0, format: "seconds" }; }; -export const toTimeStamp = (date, format) => { - const dayJsDate = dayjs.utc(date, format); - const timestamp = dayJsDate.valueOf(); - return timestamp; -}; - export const formatDate = (date, customOptions) => { const options = { year: "numeric", diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 705cdc753..8dbfb5dc1 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -323,9 +323,9 @@ const getUptimeDetailsById = async (req) => { const { dateRange, normalize } = req.query; const dates = getDateRange(dateRange); const formatLookup = { - day: "%Y-%m-%d-%H", - week: "%Y-%m-%d-%H", - month: "%Y-%m-%d", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT%H:00:00Z", + month: "%Y-%m-%dT00:00:00Z", }; const dateString = formatLookup[dateRange]; From 2dbf324615749d2e8c6aba8a6dbb0833204b568d Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Thu, 2 Jan 2025 08:20:49 -0500 Subject: [PATCH 27/27] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0476f2fd7..35e3f0fd5 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,11 @@ -**Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.** -**Checkmate is on [GitHub trending](https://github.com/trending) and #1 trending for JavaScript apps on Fri Dec 13th & 15th** +# **We're opening our $5000 grant funding announcement soon, powered by Checkmate and [UpRock](https://uprock.com) - check [our web page](https://checkmate.so) for preliminary details.** -**If you would like to support us, please consider giving it a ⭐, and think about contributing or providing feedback.** +**If you would like to support us, please consider giving it a ⭐, and think about contributing or providing feedback. Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.** Frame 34 - - ![](https://img.shields.io/github/license/bluewave-labs/checkmate) ![](https://img.shields.io/github/repo-size/bluewave-labs/checkmate) ![](https://img.shields.io/github/commit-activity/m/bluewave-labs/checkmate)