From ef13af3884d0de25907da82a8ac1af2d967e95ea Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 2 Jan 2025 14:07:38 -0800 Subject: [PATCH 01/70] Update x-axis labels for uptime monitor details chart --- .../Charts/MonitorDetailsAreaChart/index.jsx | 36 ++++++++++++------- Client/src/Pages/Uptime/Details/index.jsx | 5 ++- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 2fefd3daa..3256a53d6 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -14,8 +14,14 @@ import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; import { formatDateWithTz } from "../../../Utils/timeUtils"; import "./index.css"; - -const CustomToolTip = ({ active, payload, label }) => { +// ddd, MMMM D, YYYY, h:mm A +const CustomToolTip = ({ active, payload, label, dateRange }) => { + const formatLookup = { + day: "ddd. MMMM D, YYYY, hh:mm A", + week: "ddd. MMMM D, YYYY, hh:mm A", + month: "ddd. MMMM D, YYYY", + }; + const format = formatLookup[dateRange]; const uiTimezone = useSelector((state) => state.ui.timezone); const theme = useTheme(); if (active && payload && payload.length) { @@ -41,7 +47,7 @@ const CustomToolTip = ({ active, payload, label }) => { fontWeight: 500, }} > - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + {formatDateWithTz(label, format, uiTimezone)} { +const CustomTick = ({ x, y, payload, index, dateRange }) => { + const formatLookup = { + day: "h:mm A", + week: "MM/D, h:mm A", + month: "ddd. M/D", + }; + const format = formatLookup[dateRange]; const theme = useTheme(); - const uiTimezone = useSelector((state) => state.ui.timezone); - // Render nothing for the first tick - if (index === 0) return null; return ( { fontSize={11} fontWeight={400} > - {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} + {formatDateWithTz(payload?.value, format, uiTimezone)} ); }; @@ -128,9 +138,10 @@ CustomTick.propTypes = { y: PropTypes.number, payload: PropTypes.object, index: PropTypes.number, + dateRange: PropTypes.string, }; -const MonitorDetailsAreaChart = ({ checks }) => { +const MonitorDetailsAreaChart = ({ checks, dateRange }) => { const theme = useTheme(); const memoizedChecks = useMemo(() => checks, [checks[0]]); const [isHovered, setIsHovered] = useState(false); @@ -184,16 +195,14 @@ const MonitorDetailsAreaChart = ({ checks }) => { } - minTickGap={0} + tick={} axisLine={false} tickLine={false} height={20} - interval="equidistantPreserveStart" /> } + content={} wrapperStyle={{ pointerEvents: "none" }} /> { MonitorDetailsAreaChart.propTypes = { checks: PropTypes.array, + dateRange: PropTypes.string, }; export default MonitorDetailsAreaChart; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 7a2aa6756..680a7f690 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -410,7 +410,10 @@ const DetailsPage = () => { Response Times - + Date: Thu, 2 Jan 2025 14:22:31 -0800 Subject: [PATCH 02/70] Refactor date format lookup to util functions, refactor x-axis for infra details charts --- .../Charts/MonitorDetailsAreaChart/index.jsx | 20 +++++-------- .../Charts/Utils/chartUtilFunctions.js | 25 +++++++++++++++++ .../Components/Charts/Utils/chartUtils.jsx | 28 +++++++++++++++---- .../Pages/Infrastructure/Details/index.jsx | 15 ++++++---- 4 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 Client/src/Components/Charts/Utils/chartUtilFunctions.js diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 3256a53d6..91cb1bf22 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -13,15 +13,14 @@ import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { + tooltipDateFormatLookup, + tickDateFormatLookup, +} from "../Utils/chartUtilFunctions"; + import "./index.css"; -// ddd, MMMM D, YYYY, h:mm A const CustomToolTip = ({ active, payload, label, dateRange }) => { - const formatLookup = { - day: "ddd. MMMM D, YYYY, hh:mm A", - week: "ddd. MMMM D, YYYY, hh:mm A", - month: "ddd. MMMM D, YYYY", - }; - const format = formatLookup[dateRange]; + const format = tooltipDateFormatLookup(dateRange); const uiTimezone = useSelector((state) => state.ui.timezone); const theme = useTheme(); if (active && payload && payload.length) { @@ -111,12 +110,7 @@ CustomToolTip.propTypes = { dateRange: PropTypes.string, }; const CustomTick = ({ x, y, payload, index, dateRange }) => { - const formatLookup = { - day: "h:mm A", - week: "MM/D, h:mm A", - month: "ddd. M/D", - }; - const format = formatLookup[dateRange]; + const format = tickDateFormatLookup(dateRange); const theme = useTheme(); const uiTimezone = useSelector((state) => state.ui.timezone); return ( diff --git a/Client/src/Components/Charts/Utils/chartUtilFunctions.js b/Client/src/Components/Charts/Utils/chartUtilFunctions.js new file mode 100644 index 000000000..70f5ac9c3 --- /dev/null +++ b/Client/src/Components/Charts/Utils/chartUtilFunctions.js @@ -0,0 +1,25 @@ +export const tooltipDateFormatLookup = (dateRange) => { + const dateFormatLookup = { + day: "ddd. MMMM D, YYYY, hh:mm A", + week: "ddd. MMMM D, YYYY, hh:mm A", + month: "ddd. MMMM D, YYYY", + }; + const format = dateFormatLookup[dateRange]; + if (format === undefined) { + return ""; + } + return format; +}; + +export const tickDateFormatLookup = (dateRange) => { + const tickFormatLookup = { + day: "h:mm A", + week: "MM/D, h:mm A", + month: "ddd. M/D", + }; + const format = tickFormatLookup[dateRange]; + if (format === undefined) { + return ""; + } + return format; +}; diff --git a/Client/src/Components/Charts/Utils/chartUtils.jsx b/Client/src/Components/Charts/Utils/chartUtils.jsx index 1d82f10b4..1776c75d9 100644 --- a/Client/src/Components/Charts/Utils/chartUtils.jsx +++ b/Client/src/Components/Charts/Utils/chartUtils.jsx @@ -4,7 +4,7 @@ import { useTheme } from "@mui/material"; import { Text } from "recharts"; import { formatDateWithTz } from "../../../Utils/timeUtils"; import { Box, Stack, Typography } from "@mui/material"; - +import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions"; /** * Custom tick component for rendering time with timezone. * @@ -15,9 +15,10 @@ import { Box, Stack, Typography } from "@mui/material"; * @param {number} props.index - The index of the tick. * @returns {JSX.Element} The rendered tick component. */ -export const TzTick = ({ x, y, payload, index }) => { +export const TzTick = ({ x, y, payload, index, dateRange }) => { const theme = useTheme(); const uiTimezone = useSelector((state) => state.ui.timezone); + const format = tickDateFormatLookup(dateRange); return ( { fontSize={11} fontWeight={400} > - {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} + {formatDateWithTz(payload?.value, format, uiTimezone)} ); }; @@ -37,6 +38,7 @@ TzTick.propTypes = { y: PropTypes.number, payload: PropTypes.object, index: PropTypes.number, + dateRange: PropTypes.string, }; /** @@ -109,9 +111,12 @@ export const InfrastructureTooltip = ({ yIdx = -1, yLabel, dotColor, + dateRange, }) => { const uiTimezone = useSelector((state) => state.ui.timezone); const theme = useTheme(); + + const format = tooltipDateFormatLookup(dateRange); if (active && payload && payload.length) { const [hardwareType, metric] = yKey.split("."); return ( @@ -133,7 +138,7 @@ export const InfrastructureTooltip = ({ fontWeight: 500, }} > - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + {formatDateWithTz(label, format, uiTimezone)} { +export const TemperatureTooltip = ({ + active, + payload, + label, + keys, + dotColor, + dateRange, +}) => { const uiTimezone = useSelector((state) => state.ui.timezone); const theme = useTheme(); + const format = tooltipDateFormatLookup(dateRange); const formatCoreKey = (key) => { return key.replace(/^core(\d+)$/, "Core $1"); }; @@ -213,7 +227,7 @@ export const TemperatureTooltip = ({ active, payload, label, keys, dotColor }) = fontWeight: 500, }} > - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + {formatDateWithTz(label, format, uiTimezone)} @@ -273,4 +287,6 @@ TemperatureTooltip.propTypes = { PropTypes.string, PropTypes.number, ]), + dotColor: PropTypes.string, + dateRange: PropTypes.string, }; diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 29eddb9fc..5a0f59d5c 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -13,9 +13,8 @@ import { useNavigate } from "react-router-dom"; import Empty from "./empty"; import { logger } from "../../../Utils/Logger"; import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils"; +import { TzTick, PercentTick } from "../../../Components/Charts/Utils/chartUtils"; import { - TzTick, - PercentTick, InfrastructureTooltip, TemperatureTooltip, } from "../../../Components/Charts/Utils/chartUtils"; @@ -414,12 +413,13 @@ const InfrastructureDetails = () => { yLabel: "Memory usage", yDomain: [0, 1], yTick: , - xTick: , + xTick: , toolTip: ( ), }, @@ -433,12 +433,13 @@ const InfrastructureDetails = () => { yLabel: "CPU usage", yDomain: [0, 1], yTick: , - xTick: , + xTick: , toolTip: ( ), }, @@ -450,7 +451,7 @@ const InfrastructureDetails = () => { gradientStartColor: theme.palette.error.main, heading: "CPU Temperature", yLabel: "Temperature", - xTick: , + xTick: , yDomain: [ 0, Math.max( @@ -462,6 +463,7 @@ const InfrastructureDetails = () => { ), }, @@ -476,13 +478,14 @@ const InfrastructureDetails = () => { yLabel: "Disk Usage", yDomain: [0, 1], yTick: , - xTick: , + xTick: , toolTip: ( ), })) || []), From 83dc96887d80ea0d06700b16054ed2f1b52e5432 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 10:31:38 -0800 Subject: [PATCH 03/70] Create a reusable table component --- Client/src/Components/Table/index.jsx | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Client/src/Components/Table/index.jsx diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx new file mode 100644 index 000000000..f8cbdead1 --- /dev/null +++ b/Client/src/Components/Table/index.jsx @@ -0,0 +1,102 @@ +import { + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, +} from "@mui/material"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; + +/** + * @typedef {Object} Header + * @property {number|string} id - The unique identifier for the header. + * @property {React.ReactNode} content - The content to display in the header cell. + * @property {Function} render - A function to render the cell content for a given row. + */ + +/** + * @typedef {Object} Config + * @property {Function} onRowClick - A function to be called when a row is clicked, receiving the row data as an argument. + * @property {Object} rowSX - Style object for the table row. + */ + +/** + * DataTable component renders a table with headers and data. + * + * @param {Object} props - The component props. + * @param {Header[]} props.headers - An array of header objects, each containing an `id`, `content`, and `render` function. + * @param {Array} props.data - An array of data objects, each representing a row. + * @returns {JSX.Element} The rendered table component. + */ + +const DataTable = ({ headers, data, config }) => { + const theme = useTheme(); + + if ((headers?.length ?? 0) === 0 || (data?.length ?? 0) === 0) { + return "No data"; + } + + return ( + + + + + {headers.map((header, index) => ( + + {header.content} + + ))} + + + + {data.map((row) => { + return ( + config.onRowClick(row)} + > + {headers.map((header, index) => { + return ( + + {header.render(row)} + + ); + })} + + ); + })} + +
+
+ ); +}; + +DataTable.propTypes = { + headers: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + content: PropTypes.node.isRequired, + render: PropTypes.func.isRequired, + }) + ).isRequired, + data: PropTypes.array.isRequired, + config: PropTypes.shape({ + onRowClick: PropTypes.func.isRequired, + rowSX: PropTypes.object, + }).isRequired, +}; + +export default DataTable; From 3d067e8030ed5a0f1aada11c71bd129f3a8c47e3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 10:36:53 -0800 Subject: [PATCH 04/70] make config optional, safe SX access --- Client/src/Components/Table/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx index f8cbdead1..1edbc3386 100644 --- a/Client/src/Components/Table/index.jsx +++ b/Client/src/Components/Table/index.jsx @@ -62,8 +62,8 @@ const DataTable = ({ headers, data, config }) => { return ( config.onRowClick(row)} + sx={config?.rowSX ?? {}} + onClick={() => config?.onRowClick(row)} > {headers.map((header, index) => { return ( @@ -96,7 +96,7 @@ DataTable.propTypes = { config: PropTypes.shape({ onRowClick: PropTypes.func.isRequired, rowSX: PropTypes.object, - }).isRequired, + }), }; export default DataTable; From c192d7024ecc79d6a294d91beac4b7053f49f012 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 11:14:23 -0800 Subject: [PATCH 05/70] Add empty view to table --- Client/src/Components/Table/index.jsx | 52 ++++++++++++++++----------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx index 1edbc3386..361d32762 100644 --- a/Client/src/Components/Table/index.jsx +++ b/Client/src/Components/Table/index.jsx @@ -35,7 +35,7 @@ import { useTheme } from "@emotion/react"; const DataTable = ({ headers, data, config }) => { const theme = useTheme(); - if ((headers?.length ?? 0) === 0 || (data?.length ?? 0) === 0) { + if ((headers?.length ?? 0) === 0) { return "No data"; } @@ -58,26 +58,37 @@ const DataTable = ({ headers, data, config }) => { - {data.map((row) => { - return ( - config?.onRowClick(row)} + {data.length === 0 ? ( + + - {headers.map((header, index) => { - return ( - - {header.render(row)} - - ); - })} - - ); - })} + {config.emptyView} + + + ) : ( + data.map((row) => { + return ( + config?.onRowClick(row)} + > + {headers.map((header, index) => { + return ( + + {header.render(row)} + + ); + })} + + ); + }) + )} @@ -96,6 +107,7 @@ DataTable.propTypes = { config: PropTypes.shape({ onRowClick: PropTypes.func.isRequired, rowSX: PropTypes.object, + emptyView: PropTypes.node, }), }; From c6ef46133cddb72d6a7c91e538d9da3f6b8165f4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 11:17:55 -0800 Subject: [PATCH 06/70] safe data access, add default empty view --- Client/src/Components/Table/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx index 361d32762..570460d30 100644 --- a/Client/src/Components/Table/index.jsx +++ b/Client/src/Components/Table/index.jsx @@ -32,7 +32,7 @@ import { useTheme } from "@emotion/react"; * @returns {JSX.Element} The rendered table component. */ -const DataTable = ({ headers, data, config }) => { +const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => { const theme = useTheme(); if ((headers?.length ?? 0) === 0) { @@ -58,7 +58,7 @@ const DataTable = ({ headers, data, config }) => { - {data.length === 0 ? ( + {(data?.length ?? 0 === 0) ? ( Date: Fri, 3 Jan 2025 12:09:05 -0800 Subject: [PATCH 07/70] Fix conditional rendering of empty view --- Client/src/Components/Table/index.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx index 570460d30..3cef178f1 100644 --- a/Client/src/Components/Table/index.jsx +++ b/Client/src/Components/Table/index.jsx @@ -34,7 +34,6 @@ import { useTheme } from "@emotion/react"; const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => { const theme = useTheme(); - if ((headers?.length ?? 0) === 0) { return "No data"; } @@ -58,7 +57,7 @@ const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => { - {(data?.length ?? 0 === 0) ? ( + {(data?.length ?? 0) === 0 ? ( Date: Fri, 3 Jan 2025 12:21:33 -0800 Subject: [PATCH 08/70] Use reusable table in team page --- .../TabPanels/Account/TeamPanel.jsx | 103 ++++++++---------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index bbc6c4305..73d63e4b9 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -7,11 +7,10 @@ import { credentials } from "../../../Validation/validation"; import { networkService } from "../../../main"; import { createToast } from "../../../Utils/toastUtils"; import { useSelector } from "react-redux"; -import BasicTable from "../../BasicTable"; import Select from "../../Inputs/Select"; import LoadingButton from "@mui/lab/LoadingButton"; import { GenericDialog } from "../../Dialog/genericDialog"; - +import DataTable from "../../Table/"; /** * TeamPanel component manages the organization and team members, * providing functionalities like renaming the organization, managing team members, @@ -21,34 +20,47 @@ import { GenericDialog } from "../../Dialog/genericDialog"; */ const TeamPanel = () => { - const roleMap = { - superadmin: "Super admin", - admin: "Admin", - user: "Team member", - demo: "Demo User", - }; - const theme = useTheme(); const SPACING_GAP = theme.spacing(12); - const { authToken, user } = useSelector((state) => state.auth); - //TODO - // const [orgStates, setOrgStates] = useState({ - // name: "Bluewave Labs", - // isEdit: false, - // }); + const { authToken } = useSelector((state) => state.auth); const [toInvite, setToInvite] = useState({ email: "", role: ["0"], }); - const [tableData, setTableData] = useState({}); + const [data, setData] = useState([]); const [members, setMembers] = useState([]); const [filter, setFilter] = useState("all"); const [isDisabled, setIsDisabled] = useState(true); const [errors, setErrors] = useState({}); const [isSendingInvite, setIsSendingInvite] = useState(false); + const headers = [ + { + id: "name", + content: "Name", + render: (row) => { + return ( + + + {row.firstName + " " + row.lastName} + + + Created {new Date(row.createdAt).toLocaleDateString()} + + + ); + }, + }, + { id: "email", content: "Email", render: (row) => row.email }, + { + id: "role", + content: "Role", + render: (row) => row.role, + }, + ]; + useEffect(() => { const fetchTeam = async () => { try { @@ -67,6 +79,12 @@ const TeamPanel = () => { }, [authToken]); useEffect(() => { + const ROLE_MAP = { + superadmin: "Super admin", + admin: "Admin", + user: "Team member", + demo: "Demo User", + }; let team = members; if (filter !== "all") team = members.filter((member) => { @@ -76,42 +94,14 @@ const TeamPanel = () => { return member.role.includes(filter); }); - const data = { - cols: [ - { id: 1, name: "NAME" }, - { id: 2, name: "EMAIL" }, - { id: 3, name: "ROLE" }, - ], - rows: team?.map((member, idx) => { - const roles = member.role.map((role) => roleMap[role]).join(","); - return { - id: member._id, - data: [ - { - id: idx, - data: ( - - - {member.firstName + " " + member.lastName} - - - Created {new Date(member.createdAt).toLocaleDateString()} - - - ), - }, - { id: idx + 1, data: member.email }, - { - id: idx + 2, - data: roles, - }, - ], - }; - }), - }; + team = team.map((member) => ({ + ...member, + id: member._id, + role: member.role.map((role) => ROLE_MAP[role]).join(","), + })); + setData(team); + }, [filter, members]); - setTableData(data); - }, [filter, members, roleMap, theme]); useEffect(() => { setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === ""); }, [errors, toInvite.email]); @@ -248,12 +238,11 @@ const TeamPanel = () => { Invite a team member
- From c69389db2c692f52f7fb99ca5dcc655aedd376c5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 12:21:59 -0800 Subject: [PATCH 09/70] use reusable table in incidents page --- .../Pages/Incidents/IncidentTable/index.jsx | 102 ++++++++---------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index 1d0be7666..b4ef9fdb8 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -1,17 +1,5 @@ import PropTypes from "prop-types"; -import { - TableContainer, - Table, - TableHead, - TableRow, - TableCell, - TableBody, - Pagination, - PaginationItem, - Paper, - Typography, - Box, -} from "@mui/material"; +import { Pagination, PaginationItem, Typography, Box } from "@mui/material"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; @@ -27,7 +15,7 @@ import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?re import { HttpStatusLabel } from "../../../Components/HttpStatusLabel"; import { Empty } from "./Empty/Empty"; import { IncidentSkeleton } from "./Skeleton/Skeleton"; - +import DataTable from "../../../Components/Table"; const IncidentTable = ({ monitors, selectedMonitor, filter }) => { const uiTimezone = useSelector((state) => state.ui.timezone); @@ -106,6 +94,46 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { }); }; + const headers = [ + { + id: "monitorName", + content: "Monitor Name", + render: (row) => monitors[row.monitorId]?.name ?? "N/A", + }, + { + id: "status", + content: "Status", + render: (row) => { + const status = row.status === true ? "up" : "down"; + return ( + + ); + }, + }, + { + id: "dateTime", + content: "Date & Time", + render: (row) => { + const formattedDate = formatDateWithTz( + row.createdAt, + "YYYY-MM-DD HH:mm:ss A", + uiTimezone + ); + return formattedDate; + }, + }, + { + id: "statusCode", + content: "Status Code", + render: (row) => , + }, + { id: "message", content: "Message", render: (row) => row.message }, + ]; + let paginationComponent = <>; if (checksCount > paginationController.rowsPerPage) { paginationComponent = ( @@ -166,47 +194,11 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
) : ( <> - - - - - Monitor Name - Status - Date & Time - Status Code - Message - - - - {checks.map((check) => { - const status = check.status === true ? "up" : "down"; - const formattedDate = formatDateWithTz( - check.createdAt, - "YYYY-MM-DD HH:mm:ss A", - uiTimezone - ); - - return ( - - {monitors[check.monitorId]?.name} - - - - {formattedDate} - - - - {check.message} - - ); - })} - -
-
+ + {paginationComponent} )} From c504fbbad6929fb52449b4deb461ed02eb98f0af Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 12:22:24 -0800 Subject: [PATCH 10/70] use reusable table in infrastructure page --- Client/src/Pages/Infrastructure/index.jsx | 237 +++++++++------------- 1 file changed, 92 insertions(+), 145 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index aba4369e6..cf66763cb 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -8,23 +8,12 @@ import SkeletonLayout from "./skeleton"; import Fallback from "../../Components/Fallback"; // import GearIcon from "../../Assets/icons/settings-bold.svg?react"; import CPUChipIcon from "../../assets/icons/cpu-chip.svg?react"; -import { - Box, - Button, - IconButton, - Paper, - Stack, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, -} from "@mui/material"; +import DataTable from "../../Components/Table"; +import { Box, Button, IconButton, Stack } from "@mui/material"; import Breadcrumbs from "../../Components/Breadcrumbs"; import { StatusLabel } from "../../Components/Label"; import { Heading } from "../../Components/Heading"; -import { Pagination } from "./components/TablePagination"; +import { Pagination } from "../../Components/Table/TablePagination/index.jsx"; // import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice"; import { networkService } from "../../Utils/NetworkService.js"; import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx"; @@ -32,42 +21,8 @@ import Host from "../Uptime/Home/host.jsx"; import { useIsAdmin } from "../../Hooks/useIsAdmin.js"; import { InfrastructureMenu } from "./components/Menu"; -const columns = [ - { label: "Host" }, - { label: "Status" }, - { label: "Frequency" }, - { label: "CPU" }, - { label: "Mem" }, - { label: "Disk" }, - { label: "Actions" }, -]; - const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; -/* TODO -Create reusable table component. -It should receive as a parameter the following object: -tableData = [ - columns = [ - { - id: example, - label: Example Extendable, - align: "center" | "left" (default) - } - ], - rows: [ - { - **Number of keys will be equal to number of columns** - key1: string, - key2: number, - key3: Component - } - ] -] -Apply to Monitor Table, and Account/Team. -Analyze existing BasicTable -*/ - /** * This is the Infrastructure monitoring page. This is a work in progress * @@ -139,6 +94,81 @@ function Infrastructure() { fetchMonitors(); } + const columns = [ + { label: "Host" }, + { label: "Status" }, + { label: "Frequency" }, + { label: "CPU" }, + { label: "Mem" }, + { label: "Disk" }, + { label: "Actions" }, + ]; + + const headers = [ + { + id: "host", + content: "Host", + render: (row) => ( + + ), + }, + { + id: "status", + content: "Status", + render: (row) => ( + + ), + }, + { + id: "frequency", + content: "Frequency", + render: (row) => ( + + + {row.processor} + + ), + }, + { id: "cpu", content: "CPU", render: (row) => }, + { id: "mem", content: "Mem", render: (row) => }, + { id: "disk", content: "Disk", render: (row) => }, + { + id: "actions", + content: "Actions", + render: (row) => ( + + + + ), + }, + ]; + const monitorsAsRows = monitors.map((monitor) => { const processor = ((monitor.checks[0]?.cpu?.usage_frequency ?? 0) / 1000).toFixed(2) + " GHz"; @@ -237,103 +267,20 @@ function Infrastructure() { {totalMonitors}
- - - - - {columns.map((column, index) => ( - - {column.label} - - ))} - - - - {monitorsAsRows.map((row) => { - return ( - openDetails(row.id)} - sx={{ - cursor: "pointer", - "&:hover": { - backgroundColor: theme.palette.background.accent, - }, - }} - > - {/* TODO iterate over column and get column id, applying row[column.id] */} - - - - - - - - - - {row.processor} - - - - - - - - - - - - - {/* Get ActionsMenu from Monitor Table and create a component */} - - - {/* */} - - - - ); - })} - -
-
+ + openDetails(row.id), + }} + headers={headers} + data={monitorsAsRows} + /> Date: Fri, 3 Jan 2025 12:23:08 -0800 Subject: [PATCH 11/70] Use reuseable table in Uptime home page --- .../Home/UptimeTable/Skeleton/index.jsx | 63 ++-- .../Pages/Uptime/Home/UptimeTable/index.jsx | 307 +++++++++--------- 2 files changed, 197 insertions(+), 173 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx index 22d885c97..c17f6d74b 100644 --- a/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx @@ -1,32 +1,47 @@ -import { Skeleton, TableCell, TableRow } from "@mui/material"; +import { Skeleton } from "@mui/material"; +import DataTable from "../../../../../Components/Table"; const ROWS_NUMBER = 7; const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i); -const TableBodySkeleton = () => { +const TableSkeleton = () => { /* TODO Skeleton does not follow light and dark theme */ + + const headers = [ + { + id: "name", + + content: "Host", + + render: () => , + }, + { + id: "status", + content: "Status", + render: () => , + }, + { + id: "responseTime", + content: "Response Time", + render: () => , + }, + { + id: "type", + content: "Type", + render: () => , + }, + { + id: "actions", + content: "Actions", + render: () => , + }, + ]; + return ( - <> - {ROWS_ARRAY.map((row) => ( - - - - - - - - - - - - - - - - - - ))} - + ); }; -export { TableBodySkeleton }; +export { TableSkeleton }; diff --git a/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx index a19da2a43..0aa0eef90 100644 --- a/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx @@ -10,27 +10,18 @@ import { logger } from "../../../../Utils/Logger"; import { jwtDecode } from "jwt-decode"; import { networkService } from "../../../../main"; -import { - TableContainer, - Table, - TableHead, - TableRow, - TableCell, - TableBody, - Paper, - Box, - CircularProgress, -} from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import ActionsMenu from "../actionsMenu"; import Host from "../host"; import { StatusLabel } from "../../../../Components/Label"; -import { TableBodySkeleton } from "./Skeleton"; +import { TableSkeleton } from "./Skeleton"; import BarChart from "../../../../Components/Charts/BarChart"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; -import { Pagination } from "../../../Infrastructure/components/TablePagination"; +import { Pagination } from "../../../../Components/Table/TablePagination"; +import DataTable from "../../../../Components/Table"; const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching, handlePause }) => { const theme = useTheme(); @@ -46,7 +37,95 @@ const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching, handlePaus const [monitorCount, setMonitorCount] = useState(0); const [updateTrigger, setUpdateTrigger] = useState(false); const [sort, setSort] = useState({}); + const [data, setData] = useState([]); const prevFilter = useRef(filter); + const headers = [ + { + id: "name", + content: ( + handleSort("name")}> + Host + + {sort.order === "asc" ? ( + + ) : ( + + )} + + + ), + render: (row) => ( + + ), + }, + { + id: "status", + content: ( + handleSort("status")} + > + {" "} + Status + + {sort.order === "asc" ? ( + + ) : ( + + )} + + + ), + render: (row) => { + const status = determineState(row.monitor); + return ( + + ); + }, + }, + { + id: "responseTime", + content: "Response Time", + render: (row) => , + }, + { + id: "type", + content: "Type", + render: (row) => ( + {row.monitor.type} + ), + }, + { + id: "actions", + content: "Actions", + render: (row) => ( + + ), + }, + ]; const handleRowUpdate = () => { setUpdateTrigger((prev) => !prev); @@ -144,7 +223,41 @@ const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching, handlePaus setMonitors(res?.data?.data?.monitors ?? []); setMonitorCount(res?.data?.data?.monitorCount ?? 0); }; - /* TODO Apply component basic table? */ + + useEffect(() => { + const mappedMonitors = monitors.map((monitor) => { + let uptimePercentage = ""; + let percentageColor = theme.palette.percentage.uptimeExcellent; + + // Determine uptime percentage and color based on the monitor's uptimePercentage value + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.percentage.uptimePoor + : monitor.uptimePercentage < 0.5 + ? theme.palette.percentage.uptimeFair + : monitor.uptimePercentage < 0.75 + ? theme.palette.percentage.uptimeGood + : theme.palette.percentage.uptimeExcellent; + } + + return { + id: monitor._id, + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + monitor: monitor, + }; + }); + setData(mappedMonitors); + }, [monitors, theme]); + return ( {isSearching && ( @@ -177,141 +290,37 @@ const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching, handlePaus )} - - - - - handleSort("name")} - > - - Host - - {sort.order === "asc" ? ( - - ) : ( - - )} - - - - handleSort("status")} - > - {" "} - - {" "} - Status - - {sort.order === "asc" ? ( - - ) : ( - - )} - - - - Response Time - Type - Actions - - - - {/* TODO add empty state. Check if is searching, and empty => skeleton. Is empty, not searching => skeleton */} - {monitors.length > 0 ? ( - monitors.map((monitor) => { - let uptimePercentage = ""; - let percentageColor = theme.palette.percentage.uptimeExcellent; - - // Determine uptime percentage and color based on the monitor's uptimePercentage value - if (monitor.uptimePercentage !== undefined) { - uptimePercentage = - monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); - - percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.percentage.uptimePoor - : monitor.uptimePercentage < 0.5 - ? theme.palette.percentage.uptimeFair - : monitor.uptimePercentage < 0.75 - ? theme.palette.percentage.uptimeGood - : theme.palette.percentage.uptimeExcellent; - } - - const params = { - url: monitor.url, - title: monitor.name, - percentage: uptimePercentage, - percentageColor, - status: determineState(monitor), - }; - - return ( - { - navigate(`/uptime/${monitor._id}`); - }} - > - - - - - - - - - - - {monitor.type} - - - - - - ); - }) - ) : ( - - )} - -
-
+ {/* + This is the original SX for the row, doesn't match infrastructure table + rowSX: { + cursor: "pointer", + "&:hover": { + filter: "brightness(.75)", + opacity: 0.75, + transition: "filter 0.3s ease, opacity 0.3s ease", + }, + }, + */} + {monitors.length > 0 ? ( + { + navigate(`/uptime/${row.id}`); + }, + emptyView: "No monitors found", + }} + /> + ) : ( + + )} Date: Fri, 3 Jan 2025 12:24:06 -0800 Subject: [PATCH 12/70] Move pagination components from Infrastrucutre directory to reusable table directory --- .../Table}/TablePagination/Actions/index.jsx | 8 ++++---- .../Table}/TablePagination/index.jsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename Client/src/{Pages/Infrastructure/components => Components/Table}/TablePagination/Actions/index.jsx (85%) rename Client/src/{Pages/Infrastructure/components => Components/Table}/TablePagination/index.jsx (97%) diff --git a/Client/src/Pages/Infrastructure/components/TablePagination/Actions/index.jsx b/Client/src/Components/Table/TablePagination/Actions/index.jsx similarity index 85% rename from Client/src/Pages/Infrastructure/components/TablePagination/Actions/index.jsx rename to Client/src/Components/Table/TablePagination/Actions/index.jsx index c684da145..c607fcb8b 100644 --- a/Client/src/Pages/Infrastructure/components/TablePagination/Actions/index.jsx +++ b/Client/src/Components/Table/TablePagination/Actions/index.jsx @@ -1,9 +1,9 @@ import PropTypes from "prop-types"; import { Box, Button } from "@mui/material"; -import LeftArrowDouble from "../../../../../assets/icons/left-arrow-double.svg?react"; -import RightArrowDouble from "../../../../../assets/icons/right-arrow-double.svg?react"; -import LeftArrow from "../../../../../assets/icons/left-arrow.svg?react"; -import RightArrow from "../../../../../assets/icons/right-arrow.svg?react"; +import LeftArrowDouble from "../../../../assets/icons/left-arrow-double.svg?react"; +import RightArrowDouble from "../../../../assets/icons/right-arrow-double.svg?react"; +import LeftArrow from "../../../../assets/icons/left-arrow.svg?react"; +import RightArrow from "../../../../assets/icons/right-arrow.svg?react"; TablePaginationActions.propTypes = { count: PropTypes.number.isRequired, diff --git a/Client/src/Pages/Infrastructure/components/TablePagination/index.jsx b/Client/src/Components/Table/TablePagination/index.jsx similarity index 97% rename from Client/src/Pages/Infrastructure/components/TablePagination/index.jsx rename to Client/src/Components/Table/TablePagination/index.jsx index bb618add8..4435454bc 100644 --- a/Client/src/Pages/Infrastructure/components/TablePagination/index.jsx +++ b/Client/src/Components/Table/TablePagination/index.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; import { Stack, TablePagination, Typography } from "@mui/material"; import { TablePaginationActions } from "./Actions"; -import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react"; +import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react"; Pagination.propTypes = { monitorCount: PropTypes.number.isRequired, // Total number of items for pagination. From 8dd1dc8ccaa5d7b369d98eda40b7087c44d4d29e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 3 Jan 2025 12:41:14 -0800 Subject: [PATCH 13/70] Removed unused BasicTable --- Client/src/Components/BasicTable/index.css | 130 -------- Client/src/Components/BasicTable/index.jsx | 335 --------------------- 2 files changed, 465 deletions(-) delete mode 100644 Client/src/Components/BasicTable/index.css delete mode 100644 Client/src/Components/BasicTable/index.jsx diff --git a/Client/src/Components/BasicTable/index.css b/Client/src/Components/BasicTable/index.css deleted file mode 100644 index 3c1b0f2cd..000000000 --- a/Client/src/Components/BasicTable/index.css +++ /dev/null @@ -1,130 +0,0 @@ -.MuiTable-root .host { - width: fit-content; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} -.MuiTable-root .host span { - font-size: 11px; -} - -.MuiTable-root .label { - line-height: 1; - border-radius: var(--env-var-radius-2); - padding: 7px; - font-size: var(--env-var-font-size-small-plus); -} - -.MuiPaper-root:has(table.MuiTable-root) { - box-shadow: none; -} -.MuiTable-root .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root { - border: none; -} - -.MuiTable-root .MuiTableHead-root .MuiTableCell-root, -.MuiTable-root .MuiTableBody-root .MuiTableCell-root { - font-size: var(--env-var-font-size-medium); -} -.MuiTable-root .MuiTableHead-root .MuiTableCell-root { - padding: var(--env-var-spacing-1); - font-weight: 500; -} -.MuiTable-root .MuiTableHead-root span { - display: inline-block; - height: 17px; - width: 20px; - overflow: hidden; - margin-bottom: -3px; - margin-left: 3px; -} -.MuiTable-root .MuiTableHead-root span svg { - width: 20px; - height: 20px; -} -.MuiTable-root .MuiTableBody-root .MuiTableCell-root { - padding: var(--env-var-spacing-1); -} -.MuiTable-root .MuiTableBody-root .MuiTableRow-root { - height: 50px; -} - -.MuiPaper-root + .MuiPagination-root { - border-radius: var(--env-var-radius-1); - padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); -} -.MuiPaper-root + .MuiPagination-root ul { - justify-content: center; -} -.MuiPaper-root + .MuiPagination-root button { - font-size: var(--env-var-font-size-medium); - font-weight: 500; -} -.MuiPaper-root + .MuiPagination-root ul li:first-child { - margin-right: auto; -} -.MuiPaper-root + .MuiPagination-root ul li:last-child { - margin-left: auto; -} -.MuiPaper-root + .MuiPagination-root ul li:first-child button { - padding: 0 var(--env-var-spacing-1) 0 var(--env-var-spacing-1-plus); -} -.MuiPaper-root + .MuiPagination-root ul li:last-child button { - padding: 0 var(--env-var-spacing-1-plus) 0 var(--env-var-spacing-1); -} -.MuiPaper-root + .MuiPagination-root ul li:first-child button::after, -.MuiPaper-root + .MuiPagination-root ul li:last-child button::before { - position: relative; - display: inline-block; -} -.MuiPaper-root + .MuiPagination-root ul li:first-child button::after { - content: "Previous"; - margin-left: 15px; -} -.MuiPaper-root + .MuiPagination-root ul li:last-child button::before { - content: "Next"; - margin-right: 15px; -} -.MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root { - user-select: none; -} - -.MuiTablePagination-root p { - font-weight: 500; - font-size: var(--env-var-font-size-small-plus); -} -.MuiTablePagination-root .MuiTablePagination-select.MuiSelect-select { - text-align: left; - text-align-last: left; -} -.MuiTablePagination-root button { - min-width: 0; - padding: 4px; - margin-left: 5px; -} -.MuiTablePagination-root svg { - width: 22px; - height: 22px; -} -.MuiTablePagination-root .MuiSelect-icon { - width: 16px; - height: 16px; - top: 50%; - right: 8%; - transform: translateY(-50%); -} -.MuiTablePagination-root button.Mui-disabled { - opacity: 0.4; -} - -.table-container .MuiTable-root .MuiTableHead-root .MuiTableCell-root { - text-transform: uppercase; - opacity: 0.8; - font-size: var(--env-var-font-size-small-plus); - font-weight: 400; -} -.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type), -.monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type) { - padding-left: var(--env-var-spacing-1); - padding-right: var(--env-var-spacing-1); -} diff --git a/Client/src/Components/BasicTable/index.jsx b/Client/src/Components/BasicTable/index.jsx deleted file mode 100644 index cfc3030b0..000000000 --- a/Client/src/Components/BasicTable/index.jsx +++ /dev/null @@ -1,335 +0,0 @@ -import PropTypes from "prop-types"; -import { useState, useEffect } from "react"; -import { useTheme } from "@emotion/react"; -import { - TableContainer, - Paper, - Table, - TableHead, - TableRow, - TableCell, - TableBody, - TablePagination, - Box, - Typography, - Stack, - Button, -} from "@mui/material"; -import { useDispatch, useSelector } from "react-redux"; -import { setRowsPerPage } from "../../Features/UI/uiSlice"; -import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react"; -import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react"; -import LeftArrow from "../../assets/icons/left-arrow.svg?react"; -import RightArrow from "../../assets/icons/right-arrow.svg?react"; -import SelectorVertical from "../../assets/icons/selector-vertical.svg?react"; -import "./index.css"; -/** - * Component for pagination actions (first, previous, next, last). - * - * @component - * @param {Object} props - * @param {number} props.count - Total number of items. - * @param {number} props.page - Current page number. - * @param {number} props.rowsPerPage - Number of rows per page. - * @param {function} props.onPageChange - Callback function to handle page change. - * - * @returns {JSX.Element} Pagination actions component. - */ -const TablePaginationActions = (props) => { - const { count, page, rowsPerPage, onPageChange } = props; - - const handleFirstPageButtonClick = (event) => { - onPageChange(event, 0); - }; - const handleBackButtonClick = (event) => { - onPageChange(event, page - 1); - }; - const handleNextButtonClick = (event) => { - onPageChange(event, page + 1); - }; - const handleLastPageButtonClick = (event) => { - onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); - }; - - return ( - - - - - - - ); -}; - -TablePaginationActions.propTypes = { - count: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, - rowsPerPage: PropTypes.number.isRequired, - onPageChange: PropTypes.func.isRequired, -}; - -/** - * BasicTable Component - * Renders a table with optional pagination. - * - * @component - * @param {Object} props - Component props. - * @param {Object} props.data - Data for the table including columns and rows. - * @param {Array} props.data.cols - Array of objects for column headers. - * @param {number} props.data.cols[].id - Unique identifier for the column. - * @param {string} props.data.cols[].name - Name of the column to display as header. - * @param {Array} props.data.rows - Array of row objects. - * @param {number} props.data.rows[].id - Unique identifier for the row. - * @param {Array} props.data.rows[].data - Array of cell data objects for the row. - * @param {number} props.data.rows[].data[].id - Unique identifier for the cell. - * @param {JSX.Element} props.data.rows[].data[].data - The content to display in the cell. - * @param {function} props.data.rows.data.handleClick - Function to call when the row is clicked. - * @param {boolean} [props.paginated=false] - Flag to enable pagination. - * @param {boolean} [props.reversed=false] - Flag to enable reverse order. - * @param {number} props.rowsPerPage- Number of rows per page (table). - * @param {string} props.emptyMessage - Message to display when there is no data. - * @example - * const data = { - * cols: [ - * { id: 1, name: "First Col" }, - * { id: 2, name: "Second Col" }, - * { id: 3, name: "Third Col" }, - * { id: 4, name: "Fourth Col" }, - * ], - * rows: [ - * { - * id: 1, - * data: [ - * { id: 1, data:
Data for Row 1 Col 1
}, - * { id: 2, data:
Data for Row 1 Col 2
}, - * { id: 3, data:
Data for Row 1 Col 3
}, - * { id: 4, data:
Data for Row 1 Col 4
}, - * ], - * }, - * { - * id: 2, - * data: [ - * { id: 5, data:
Data for Row 2 Col 1
}, - * { id: 6, data:
Data for Row 2 Col 2
}, - * { id: 7, data:
Data for Row 2 Col 3
}, - * { id: 8, data:
Data for Row 2 Col 4
}, - * ], - * }, - * ], - * }; - * - * - */ - -const BasicTable = ({ data, paginated, reversed, table, emptyMessage = "No data" }) => { - const DEFAULT_ROWS_PER_PAGE = 5; - const theme = useTheme(); - const dispatch = useDispatch(); - const uiState = useSelector((state) => state.ui); - let rowsPerPage = uiState?.[table]?.rowsPerPage ?? DEFAULT_ROWS_PER_PAGE; - const [page, setPage] = useState(0); - - useEffect(() => { - setPage(0); - }, [data]); - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event) => { - dispatch( - setRowsPerPage({ - value: parseInt(event.target.value, 10), - table: table, - }) - ); - setPage(0); - }; - - let displayData = []; - - if (data && data.rows) { - let rows = reversed ? [...data.rows].reverse() : data.rows; - displayData = paginated - ? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - : rows; - } - - if (!data || !data.cols || !data.rows) { - return
No data
; - } - - /** - * Helper function to calculate the range of displayed rows. - * @returns {string} - */ - const getRange = () => { - let start = page * rowsPerPage + 1; - let end = Math.min(page * rowsPerPage + rowsPerPage, data.rows.length); - return `${start} - ${end}`; - }; - - return ( - <> - - - - - {data.cols.map((col) => ( - {col.name} - ))} - - - - {displayData.map((row) => { - return ( - - {row.data.map((cell) => { - return {cell.data}; - })} - - ); - })} - {displayData.length === 0 && ( - - - {emptyMessage} - - - )} - -
-
- {paginated && ( - - - Showing {getRange()} of {data.rows.length} monitor(s) - - - `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` - } - slotProps={{ - select: { - MenuProps: { - keepMounted: true, - PaperProps: { - className: "pagination-dropdown", - sx: { - mt: 0, - mb: theme.spacing(2), - }, - }, - transformOrigin: { vertical: "bottom", horizontal: "left" }, - anchorOrigin: { vertical: "top", horizontal: "left" }, - sx: { mt: theme.spacing(-2) }, - }, - inputProps: { id: "pagination-dropdown" }, - IconComponent: SelectorVertical, - sx: { - ml: theme.spacing(4), - mr: theme.spacing(12), - minWidth: theme.spacing(20), - textAlign: "left", - "&.Mui-focused > div": { - backgroundColor: theme.palette.background.main, - }, - }, - }, - }} - sx={{ - mt: theme.spacing(6), - color: theme.palette.text.secondary, - "& svg path": { - stroke: theme.palette.text.tertiary, - strokeWidth: 1.3, - }, - "& .MuiSelect-select": { - border: 1, - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - }, - }} - /> - - )} - - ); -}; - -BasicTable.propTypes = { - data: PropTypes.object.isRequired, - paginated: PropTypes.bool, - reversed: PropTypes.bool, - rowPerPage: PropTypes.number, - table: PropTypes.string, - emptyMessage: PropTypes.string, -}; - -export default BasicTable; From 4398010a9f9672cc071529e701ada70a20fd07a7 Mon Sep 17 00:00:00 2001 From: Jasneet Singh Date: Mon, 6 Jan 2025 16:38:58 -0500 Subject: [PATCH 14/70] Inform Admin incase of server issue. --- Client/src/Utils/NetworkService.js | 45 +++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 4812947d0..edef40f0b 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -1,14 +1,20 @@ +import { toast } from 'react-toastify'; import axios from "axios"; const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const FALLBACK_BASE_URL = "http://localhost:5000/api/v1"; import { clearAuthState } from "../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice"; + class NetworkService { constructor(store, dispatch, navigate) { this.store = store; this.dispatch = dispatch; this.navigate = navigate; let baseURL = BASE_URL; + this.lastToastTimestamp = 0; + this.errorOccurred = false; + this.user = this.store.getState().auth?.user; + this.isAdmin = (this.user?.role?.includes("admin") ?? false) || (this.user?.role?.includes("superadmin") ?? false); this.axiosInstance = axios.create(); this.setBaseUrl(baseURL); this.unsubscribe = store.subscribe(() => { @@ -21,15 +27,17 @@ class NetworkService { baseURL = FALLBACK_BASE_URL; } this.setBaseUrl(baseURL); - }); + }); this.axiosInstance.interceptors.response.use( - (response) => response, - (error) => { - if (error.response && error.response.status === 401) { - dispatch(clearAuthState()); - dispatch(clearUptimeMonitorState()); - navigate("/login"); + (response) => { + if (this.isAdmin && this.errorOccurred && response.status >= 200 && response.status < 300) { + this.debounceToast("Checkmate server is running properly.", "success"); + this.errorOccurred = false;// Reset error flag if a successful response is received after errors } + return response; + }, + (error) => { + this.handleError(error); return Promise.reject(error); } ); @@ -39,6 +47,29 @@ class NetworkService { this.axiosInstance.defaults.baseURL = url; }; + handleError(error) { + if (error.response) { + const status = error.response.status; + if (status === 401) { + this.dispatch(clearAuthState()); + this.dispatch(clearUptimeMonitorState()); + this.navigate("/login"); + } else if (this.isAdmin && status >= 500) { + this.debounceToast("Checkmate server is not running or has issues. Please check.", "error"); + this.errorOccurred = true; + } + } + } + + debounceToast(message, type, debounceDuration = 5000) { + const now = Date.now(); + if (now - this.lastToastTimestamp > debounceDuration) { + this.lastToastTimestamp = now; + type === "success" ? toast.success(message) : toast.error(message); + } + } + + cleanup() { if (this.unsubscribe) { this.unsubscribe(); From ca42e2608f752da9e508cc83aadd62eb69e58682 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 6 Jan 2025 14:24:08 -0800 Subject: [PATCH 15/70] Fix incorrect uptime check count, refactor out query pipelines --- Client/src/Pages/Uptime/Details/index.jsx | 8 +- Server/db/mongo/modules/monitorModule.js | 532 +----------------- .../db/mongo/modules/monitorModuleQueries.js | 525 +++++++++++++++++ 3 files changed, 541 insertions(+), 524 deletions(-) create mode 100644 Server/db/mongo/modules/monitorModuleQueries.js diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 7a2aa6756..e161027fa 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -308,7 +308,9 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.stats?.upChecksAggregate?.totalChecks ?? 0)} + : (monitor.stats?.upChecks?.reduce((count, checkGroup) => { + return count + checkGroup.totalChecks; + }, 0) ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.stats?.downChecksAggregate?.totalChecks ?? 0)} + : (monitor.stats?.downChecks?.reduce((count, checkGroup) => { + return count + checkGroup.totalChecks; + }, 0) ?? 0)} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 1143b9eeb..3a511eb12 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -8,7 +8,10 @@ import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUt import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; - +import { + buildUptimeDetailsPipeline, + buildHardwareDetailsPipeline, +} from "./monitorModuleQueries.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -336,272 +339,9 @@ const getUptimeDetailsById = async (req) => { }; const dateString = formatLookup[dateRange]; - const monitorData = await Check.aggregate([ - { - $match: { - monitorId: monitor._id, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $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, - lastFalseCheck: { - $last: "$$ROOT", - }, - }, - }, - ], - 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: { - status: true, - }, - }, - { - $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", - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - downChecksAggregate: [ - { - $match: { - status: false, - }, - }, - { - $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", - }, - }, - }, - { - $sort: { _id: 1 }, - }, - ], - }, - }, - { - $project: { - avgResponseTime: { - $arrayElemAt: ["$aggregateData.avgResponseTime", 0], - }, - totalChecks: { - $arrayElemAt: ["$aggregateData.totalChecks", 0], - }, - latestResponseTime: { - $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 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, - ], - }, - ], - }, - }, - }, - groupChecks: "$groupChecks", - groupAggregate: { - $arrayElemAt: ["$groupAggregate", 0], - }, - upChecksAggregate: { - $arrayElemAt: ["$upChecksAggregate", 0], - }, - upChecks: "$upChecks", - downChecksAggregate: { - $arrayElemAt: ["$downChecksAggregate", 0], - }, - downChecks: "$downChecks", - }, - }, - ]); + const monitorData = await Check.aggregate( + buildUptimeDetailsPipeline(monitor, dates, dateString) + ); const normalizedGroupChecks = NormalizeDataUptimeDetails( monitorData[0].groupChecks, @@ -715,261 +455,9 @@ const getHardwareDetailsById = async (req) => { month: "%Y-%m-%dT00:00:00Z", }; const dateString = formatLookup[dateRange]; - const hardwareStats = await HardwareCheck.aggregate([ - { - $match: { - monitorId: monitor._id, - createdAt: { $gte: dates.start, $lte: dates.end }, - }, - }, - { - $sort: { - createdAt: 1, - }, - }, - { - $facet: { - aggregateData: [ - { - $group: { - _id: null, - latestCheck: { - $last: "$$ROOT", - }, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - upChecks: [ - { - $match: { - status: true, - }, - }, - { - $group: { - _id: null, - totalChecks: { - $sum: 1, - }, - }, - }, - ], - checks: [ - { - $limit: 1, - }, - { - $project: { - diskCount: { - $size: "$disk", - }, - }, - }, - { - $lookup: { - from: "hardwarechecks", - let: { - diskCount: "$diskCount", - }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { $eq: ["$monitorId", monitor._id] }, - { $gte: ["$createdAt", dates.start] }, - { $lte: ["$createdAt", dates.end] }, - ], - }, - }, - }, - { - $group: { - _id: { - $dateToString: { - format: dateString, - date: "$createdAt", - }, - }, - avgCpuUsage: { - $avg: "$cpu.usage_percent", - }, - avgMemoryUsage: { - $avg: "$memory.usage_percent", - }, - avgTemperatures: { - $push: { - $ifNull: ["$cpu.temperature", [0]], - }, - }, - disks: { - $push: "$disk", - }, - }, - }, - { - $project: { - _id: 1, - avgCpuUsage: 1, - avgMemoryUsage: 1, - avgTemperature: { - $map: { - input: { - $range: [ - 0, - { - $size: { - // Handle null temperatures array - $ifNull: [ - { $arrayElemAt: ["$avgTemperatures", 0] }, - [0], // Default to single-element array if null - ], - }, - }, - ], - }, - as: "index", - in: { - $avg: { - $map: { - input: "$avgTemperatures", - as: "tempArray", - in: { - $ifNull: [ - { $arrayElemAt: ["$$tempArray", "$$index"] }, - 0, // Default to 0 if element is null - ], - }, - }, - }, - }, - }, - }, - disks: { - $map: { - input: { - $range: [0, "$$diskCount"], - }, - as: "diskIndex", - in: { - name: { - $concat: [ - "disk", - { - $toString: "$$diskIndex", - }, - ], - }, - readSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.read_speed_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - writeSpeed: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.write_speed_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - totalBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.total_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - freeBytes: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.free_bytes", - "$$diskIndex", - ], - }, - }, - }, - }, - usagePercent: { - $avg: { - $map: { - input: "$disks", - as: "diskArray", - in: { - $arrayElemAt: [ - "$$diskArray.usage_percent", - "$$diskIndex", - ], - }, - }, - }, - }, - }, - }, - }, - }, - }, - ], - as: "hourlyStats", - }, - }, - { - $unwind: "$hourlyStats", - }, - { - $replaceRoot: { - newRoot: "$hourlyStats", - }, - }, - ], - }, - }, - { - $project: { - aggregateData: { - $arrayElemAt: ["$aggregateData", 0], - }, - upChecks: { - $arrayElemAt: ["$upChecks", 0], - }, - checks: { - $sortArray: { - input: "$checks", - sortBy: { _id: 1 }, - }, - }, - }, - }, - ]); + const hardwareStats = await HardwareCheck.aggregate( + buildHardwareDetailsPipeline(monitor, dates, dateString) + ); const monitorStats = { ...monitor.toObject(), diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js new file mode 100644 index 000000000..ccafbcbdc --- /dev/null +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -0,0 +1,525 @@ +const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { + return [ + { + $match: { + monitorId: monitor._id, + }, + }, + { + $sort: { + createdAt: 1, + }, + }, + { + $facet: { + aggregateData: [ + { + $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, + lastFalseCheck: { + $last: "$$ROOT", + }, + }, + }, + ], + 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: { + status: true, + }, + }, + { + $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", + }, + }, + }, + { + $sort: { _id: 1 }, + }, + ], + downChecksAggregate: [ + { + $match: { + status: false, + }, + }, + { + $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", + }, + }, + }, + { + $sort: { _id: 1 }, + }, + ], + }, + }, + { + $project: { + avgResponseTime: { + $arrayElemAt: ["$aggregateData.avgResponseTime", 0], + }, + totalChecks: { + $arrayElemAt: ["$aggregateData.totalChecks", 0], + }, + latestResponseTime: { + $arrayElemAt: ["$aggregateData.lastCheck.responseTime", 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, + ], + }, + ], + }, + }, + }, + groupChecks: "$groupChecks", + groupAggregate: { + $arrayElemAt: ["$groupAggregate", 0], + }, + upChecksAggregate: { + $arrayElemAt: ["$upChecksAggregate", 0], + }, + upChecks: "$upChecks", + downChecksAggregate: { + $arrayElemAt: ["$downChecksAggregate", 0], + }, + downChecks: "$downChecks", + }, + }, + ]; +}; + +const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { + return [ + { + $match: { + monitorId: monitor._id, + createdAt: { $gte: dates.start, $lte: dates.end }, + }, + }, + { + $sort: { + createdAt: 1, + }, + }, + { + $facet: { + aggregateData: [ + { + $group: { + _id: null, + latestCheck: { + $last: "$$ROOT", + }, + totalChecks: { + $sum: 1, + }, + }, + }, + ], + upChecks: [ + { + $match: { + status: true, + }, + }, + { + $group: { + _id: null, + totalChecks: { + $sum: 1, + }, + }, + }, + ], + checks: [ + { + $limit: 1, + }, + { + $project: { + diskCount: { + $size: "$disk", + }, + }, + }, + { + $lookup: { + from: "hardwarechecks", + let: { + diskCount: "$diskCount", + }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { $eq: ["$monitorId", monitor._id] }, + { $gte: ["$createdAt", dates.start] }, + { $lte: ["$createdAt", dates.end] }, + ], + }, + }, + }, + { + $group: { + _id: { + $dateToString: { + format: dateString, + date: "$createdAt", + }, + }, + avgCpuUsage: { + $avg: "$cpu.usage_percent", + }, + avgMemoryUsage: { + $avg: "$memory.usage_percent", + }, + avgTemperatures: { + $push: { + $ifNull: ["$cpu.temperature", [0]], + }, + }, + disks: { + $push: "$disk", + }, + }, + }, + { + $project: { + _id: 1, + avgCpuUsage: 1, + avgMemoryUsage: 1, + avgTemperature: { + $map: { + input: { + $range: [ + 0, + { + $size: { + // Handle null temperatures array + $ifNull: [ + { $arrayElemAt: ["$avgTemperatures", 0] }, + [0], // Default to single-element array if null + ], + }, + }, + ], + }, + as: "index", + in: { + $avg: { + $map: { + input: "$avgTemperatures", + as: "tempArray", + in: { + $ifNull: [ + { $arrayElemAt: ["$$tempArray", "$$index"] }, + 0, // Default to 0 if element is null + ], + }, + }, + }, + }, + }, + }, + disks: { + $map: { + input: { + $range: [0, "$$diskCount"], + }, + as: "diskIndex", + in: { + name: { + $concat: [ + "disk", + { + $toString: "$$diskIndex", + }, + ], + }, + readSpeed: { + $avg: { + $map: { + input: "$disks", + as: "diskArray", + in: { + $arrayElemAt: [ + "$$diskArray.read_speed_bytes", + "$$diskIndex", + ], + }, + }, + }, + }, + writeSpeed: { + $avg: { + $map: { + input: "$disks", + as: "diskArray", + in: { + $arrayElemAt: [ + "$$diskArray.write_speed_bytes", + "$$diskIndex", + ], + }, + }, + }, + }, + totalBytes: { + $avg: { + $map: { + input: "$disks", + as: "diskArray", + in: { + $arrayElemAt: [ + "$$diskArray.total_bytes", + "$$diskIndex", + ], + }, + }, + }, + }, + freeBytes: { + $avg: { + $map: { + input: "$disks", + as: "diskArray", + in: { + $arrayElemAt: ["$$diskArray.free_bytes", "$$diskIndex"], + }, + }, + }, + }, + usagePercent: { + $avg: { + $map: { + input: "$disks", + as: "diskArray", + in: { + $arrayElemAt: [ + "$$diskArray.usage_percent", + "$$diskIndex", + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + ], + as: "hourlyStats", + }, + }, + { + $unwind: "$hourlyStats", + }, + { + $replaceRoot: { + newRoot: "$hourlyStats", + }, + }, + ], + }, + }, + { + $project: { + aggregateData: { + $arrayElemAt: ["$aggregateData", 0], + }, + upChecks: { + $arrayElemAt: ["$upChecks", 0], + }, + checks: { + $sortArray: { + input: "$checks", + sortBy: { _id: 1 }, + }, + }, + }, + }, + ]; +}; + +export { buildUptimeDetailsPipeline, buildHardwareDetailsPipeline }; From 9e1f50bc8e023acbab6aa0cd18e3fafe348ff842 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 6 Jan 2025 15:24:16 -0800 Subject: [PATCH 16/70] remove unused var --- Client/src/Pages/Infrastructure/index.jsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index cf66763cb..be7d7e9ed 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -94,16 +94,6 @@ function Infrastructure() { fetchMonitors(); } - const columns = [ - { label: "Host" }, - { label: "Status" }, - { label: "Frequency" }, - { label: "CPU" }, - { label: "Mem" }, - { label: "Disk" }, - { label: "Actions" }, - ]; - const headers = [ { id: "host", From 3ff9b19abf2ddaef6bac6556e3fefdfa066bdd93 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 7 Jan 2025 00:35:23 +0000 Subject: [PATCH 17/70] Update dependency bullmq to v5.34.7 --- 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 4baed7570..8c02aed27 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.6", + "bullmq": "5.34.7", "cors": "^2.8.5", "dockerode": "4.0.3", "dotenv": "^16.4.5", @@ -1744,9 +1744,9 @@ } }, "node_modules/bullmq": { - "version": "5.34.6", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.6.tgz", - "integrity": "sha512-pRCYyO9RlkQWxdmKlrNnUthyFwurYXRYLVXD1YIx+nCCdhAOiHatD8FDHbsT/w2I31c0NWoMcfZiIGuipiF7Lg==", + "version": "5.34.7", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.7.tgz", + "integrity": "sha512-Z6nKi8skD9n5/ErddTngeeEd8dRvZCu8wgZqpeH9zK0DlyTxYKceTnkGab3FkA+U9mzlqa0z34ZKGMbn4GWdgQ==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index 5ee5e08ad..08cf3e872 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.6", + "bullmq": "5.34.7", "cors": "^2.8.5", "dockerode": "4.0.3", "dotenv": "^16.4.5", From e2d5388a3006d3c058339a253a39fbec6c9fc18c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 10:12:41 -0800 Subject: [PATCH 18/70] remove monitors from summary, unused --- .../src/Pages/Uptime/CreateUptime/index.jsx | 4 +- Client/src/Pages/Uptime/Home/index.jsx | 14 ++-- Server/controllers/monitorController.js | 12 ++-- Server/db/mongo/modules/monitorModule.js | 42 ++++++----- Server/routes/monitorRoute.js | 2 +- Server/service/jobQueue.js | 70 +++++++++++-------- Server/service/settingsService.js | 2 +- Server/validation/joi.js | 8 +-- 8 files changed, 87 insertions(+), 67 deletions(-) diff --git a/Client/src/Pages/Uptime/CreateUptime/index.jsx b/Client/src/Pages/Uptime/CreateUptime/index.jsx index c5a9df770..3499a9c73 100644 --- a/Client/src/Pages/Uptime/CreateUptime/index.jsx +++ b/Client/src/Pages/Uptime/CreateUptime/index.jsx @@ -58,7 +58,7 @@ const CreateMonitor = () => { }; const { user, authToken } = useSelector((state) => state.auth); - const { monitors, isLoading } = useSelector((state) => state.uptimeMonitors); + const { isLoading } = useSelector((state) => state.uptimeMonitors); const dispatch = useDispatch(); const navigate = useNavigate(); const theme = useTheme(); @@ -205,7 +205,7 @@ const CreateMonitor = () => { } }; fetchMonitor(); - }, [monitorId, authToken, monitors, dispatch, navigate]); + }, [monitorId, authToken, dispatch, navigate]); return ( diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 4fc57a7a0..a542c72ff 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -20,7 +20,7 @@ const UptimeMonitors = () => { const theme = useTheme(); const navigate = useNavigate(); const isAdmin = useIsAdmin(); - const uptimeMonitorsState = useSelector((state) => state.uptimeMonitors); + const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); const dispatch = useDispatch({}); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); @@ -35,9 +35,9 @@ const UptimeMonitors = () => { //TODO bring fetching to this component, like on pageSpeed - const loading = uptimeMonitorsState?.isLoading; + const loading = isLoading; - const totalMonitors = uptimeMonitorsState?.monitorsSummary?.monitorCounts?.total; + const totalMonitors = monitorsSummary?.monitorCounts?.total; const hasMonitors = totalMonitors > 0; const noMonitors = !hasMonitors; @@ -86,20 +86,20 @@ const UptimeMonitors = () => { > diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 5e16b3b18..f5e7994b2 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -5,8 +5,8 @@ import { createMonitorBodyValidation, getMonitorURLByQueryValidation, editMonitorBodyValidation, - getMonitorsAndSummaryByTeamIdParamValidation, - getMonitorsAndSummaryByTeamIdQueryValidation, + getMonitorsSummaryByTeamIdParamValidation, + getMonitorsSummaryByTeamIdQueryValidation, getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, @@ -217,10 +217,10 @@ class MonitorController { * @returns {Object} The response object with a success status, a message, and the data containing the monitors and summary for the team. * @throws {Error} If there is an error during the process, especially if there is a validation error (422). */ - getMonitorsAndSummaryByTeamId = async (req, res, next) => { + getMonitorsSummaryByTeamId = async (req, res, next) => { try { - await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query); + await getMonitorsSummaryByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsSummaryByTeamIdQueryValidation.validateAsync(req.query); } catch (error) { next(handleValidationError(error, SERVICE_NAME)); return; @@ -229,7 +229,7 @@ class MonitorController { try { const { teamId } = req.params; const { type } = req.query; - const monitorsSummary = await this.db.getMonitorsAndSummaryByTeamId(teamId, type); + const monitorsSummary = await this.db.getMonitorsSummaryByTeamId(teamId, type); return res.status(200).json({ success: true, msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 1143b9eeb..6bbb8840a 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -1025,24 +1025,32 @@ const getMonitorById = async (monitorId) => { * @throws {Error} */ -const getMonitorsAndSummaryByTeamId = async (teamId, type) => { +const getMonitorsSummaryByTeamId = async (teamId, type) => { try { - const monitors = await Monitor.find({ teamId, type }); - const monitorCounts = monitors.reduce( - (acc, monitor) => { - if (monitor.status === true) { - acc.up += 1; - } else if (monitor.status === false) { - acc.down += 1; - } else if (monitor.isActive === false) { - acc.paused += 1; - } - return acc; + const monitorCounts = await Monitor.aggregate([ + { + $match: { + type: { $in: type }, + }, }, - { up: 0, down: 0, paused: 0 } - ); - monitorCounts.total = monitors.length; - return { monitors, monitorCounts }; + { + $facet: { + total: [{ $count: "count" }], + up: [{ $match: { status: true } }, { $count: "count" }], + down: [{ $match: { status: false } }, { $count: "count" }], + paused: [{ $match: { isActive: false } }, { $count: "count" }], + }, + }, + { + $project: { + total: { $arrayElemAt: ["$total.count", 0] }, + up: { $arrayElemAt: ["$up.count", 0] }, + down: { $arrayElemAt: ["$down.count", 0] }, + paused: { $arrayElemAt: ["$paused.count", 0] }, + }, + }, + ]); + return { monitorCounts: monitorCounts[0] }; } catch (error) { error.service = SERVICE_NAME; error.method = "getMonitorsAndSummaryByTeamId"; @@ -1263,7 +1271,7 @@ export { getMonitorStatsById, getMonitorById, getUptimeDetailsById, - getMonitorsAndSummaryByTeamId, + getMonitorsSummaryByTeamId, getMonitorsByTeamId, createMonitor, deleteMonitor, diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index d47351486..f3af8e806 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -32,7 +32,7 @@ class MonitorRoutes { this.router.get("/:monitorId", this.monitorController.getMonitorById); this.router.get( "/team/summary/:teamId", - this.monitorController.getMonitorsAndSummaryByTeamId + this.monitorController.getMonitorsSummaryByTeamId ); this.router.get("/team/:teamId", this.monitorController.getMonitorsByTeamId); diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index fb5c306b1..177ca34c5 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -218,38 +218,50 @@ class NewJobQueue { concurrency: 5, }); - worker.on("active", (job) => { - this.logger.info({ - message: `Worker started processing job: ${job.id}`, - service: SERVICE_NAME, - method: "createWorker", - }); - }); + // worker.on("active", (job) => { + // this.logger.info({ + // message: `Worker started processing job: ${job.id}`, + // service: SERVICE_NAME, + // method: "createWorker", + // }); + // }); - worker.on("completed", (job) => { - this.logger.info({ - message: `Worker completed job: ${job.id}`, - service: SERVICE_NAME, - method: "createWorker", - }); - }); + // worker.on("completed", (job) => { + // this.logger.info({ + // message: `Worker completed job: ${job.id}`, + // service: SERVICE_NAME, + // method: "createWorker", + // }); + // }); - worker.on("failed", (job, err) => { - this.logger.error({ - message: `Worker failed job: ${job.id}`, - service: SERVICE_NAME, - method: "createWorker", - stack: err.stack, - }); - }); + // // Log job progress updates + // worker.on("progress", (job, progress) => { + // this.logger.info({ + // message: `Job progress: ${job.id}`, + // service: SERVICE_NAME, + // method: "createWorker", + // details: `Progress: ${progress}%`, + // }); + // }); - worker.on("stalled", (jobId) => { - this.logger.warn({ - message: `Worker stalled job: ${jobId}`, - service: SERVICE_NAME, - method: "createWorker", - }); - }); + // // Log when a job fails + // worker.on("failed", (job, err) => { + // this.logger.error({ + // message: `Worker failed job: ${job.id}`, + // service: SERVICE_NAME, + // method: "createWorker", + // details: `Error: ${err.message}`, + // stack: err.stack, + // }); + // }); + + // worker.on("stalled", (jobId) => { + // this.logger.warn({ + // message: `Worker stalled job: ${jobId}`, + // service: SERVICE_NAME, + // method: "createWorker", + // }); + // }); return worker; } diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js index fb85939ca..24931f6ce 100644 --- a/Server/service/settingsService.js +++ b/Server/service/settingsService.js @@ -2,7 +2,7 @@ const SERVICE_NAME = "SettingsService"; import dotenv from "dotenv"; dotenv.config(); const envConfig = { - logLevel: undefined, + logLevel: process.env.LOG_LEVEL, apiBaseUrl: undefined, clientHost: process.env.CLIENT_HOST, jwtSecret: process.env.JWT_SECRET, diff --git a/Server/validation/joi.js b/Server/validation/joi.js index dbffef937..825c90cdb 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -136,11 +136,11 @@ const getMonitorByIdQueryValidation = joi.object({ normalize: joi.boolean(), }); -const getMonitorsAndSummaryByTeamIdParamValidation = joi.object({ +const getMonitorsSummaryByTeamIdParamValidation = joi.object({ teamId: joi.string().required(), }); -const getMonitorsAndSummaryByTeamIdQueryValidation = joi.object({ +const getMonitorsSummaryByTeamIdQueryValidation = joi.object({ type: joi .alternatives() .try( @@ -467,8 +467,8 @@ export { createMonitorBodyValidation, getMonitorByIdParamValidation, getMonitorByIdQueryValidation, - getMonitorsAndSummaryByTeamIdParamValidation, - getMonitorsAndSummaryByTeamIdQueryValidation, + getMonitorsSummaryByTeamIdParamValidation, + getMonitorsSummaryByTeamIdQueryValidation, getMonitorsByTeamIdValidation, getMonitorsByTeamIdQueryValidation, getMonitorStatsByIdParamValidation, From de513c226c70cff36e69c26a72e4952a944844da Mon Sep 17 00:00:00 2001 From: Kalyan Bhowmick <“kbhowmick067@gmail.com”> Date: Tue, 7 Jan 2025 23:48:12 +0530 Subject: [PATCH 19/70] Query updated for Incidents page filter --- Server/db/mongo/modules/checkModule.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index 773d6ef1e..f8c1a7581 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -175,9 +175,10 @@ const getTeamChecks = async (req) => { case "all": break; case "down": + checksQuery.status = false; break; case "resolve": - checksQuery.statusCode = 5000; + checksQuery.status = true; break; default: logger.warn({ From 290740d58e2e27e6dfb4c34bc3a7cf82f928b0db Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:24:47 -0800 Subject: [PATCH 20/70] Add getting monitors by team id to redux slice to make use of loading state --- .../UptimeMonitors/uptimeMonitorsSlice.js | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 78250c19a..8782948ae 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -78,12 +78,12 @@ export const getUptimeMonitorById = createAsyncThunk( } ); -export const getUptimeMonitorsByTeamId = createAsyncThunk( - "monitors/getMonitorsByTeamId", +export const getUptimeSummaryByTeamId = createAsyncThunk( + "monitors/getSummaryByTeamId", async (token, thunkApi) => { const user = jwtDecode(token); try { - const res = await networkService.getMonitorsAndSummaryByTeamId({ + const res = await networkService.getMonitorsSummaryByTeamId({ authToken: token, teamId: user.teamId, types: ["http", "ping", "docker", "port"], @@ -102,6 +102,38 @@ export const getUptimeMonitorsByTeamId = createAsyncThunk( } ); +export const getUptimeMonitorsByTeamId = createAsyncThunk( + "monitors/getMonitorsByTeamId", + async (config, thunkApi) => { + try { + const res = await networkService.getMonitorsByTeamId({ + authToken: config.authToken, + teamId: config.teamId, + limit: 25, + types: ["http", "ping", "docker", "port"], + status: null, + checkOrder: "desc", + normalize: true, + page: config.page, + rowsPerPage: config.rowsPerPage, + filter: config.filter, + field: config.sort.field, + order: config.sort.order, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +); + export const updateUptimeMonitor = createAsyncThunk( "monitors/updateMonitor", async (data, thunkApi) => { @@ -258,16 +290,33 @@ const uptimeMonitorsSlice = createSlice({ extraReducers: (builder) => { builder // ***************************************************** - // Monitors by teamId + // Summary by teamId // ***************************************************** + .addCase(getUptimeSummaryByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(getUptimeSummaryByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.msg; + state.monitorsSummary = action.payload.data; + }) + .addCase(getUptimeSummaryByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Getting uptime summary failed"; + }) + + // ***************************************************** + // Monitors by teamId + // ***************************************************** .addCase(getUptimeMonitorsByTeamId.pending, (state) => { state.isLoading = true; }) .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => { state.isLoading = false; - state.success = action.payload.msg; - state.monitorsSummary = action.payload.data; + state.success = action.payload.success; + state.msg = action.payload.msg; }) .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => { state.isLoading = false; @@ -276,7 +325,6 @@ const uptimeMonitorsSlice = createSlice({ ? action.payload.msg : "Getting uptime monitors failed"; }) - // ***************************************************** // Create Monitor // ***************************************************** From 33b3143298a298b8b00f33a8badedd88e581b657 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:27:55 -0800 Subject: [PATCH 21/70] use correct name for redux action --- Client/src/Pages/Uptime/Configure/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Uptime/Configure/index.jsx b/Client/src/Pages/Uptime/Configure/index.jsx index 32532d462..76c030776 100644 --- a/Client/src/Pages/Uptime/Configure/index.jsx +++ b/Client/src/Pages/Uptime/Configure/index.jsx @@ -11,7 +11,7 @@ import { updateUptimeMonitor, pauseUptimeMonitor, getUptimeMonitorById, - getUptimeMonitorsByTeamId, + getUptimeSummaryByTeamId, deleteUptimeMonitor, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import TextInput from "../../../Components/Inputs/TextInput"; @@ -157,7 +157,7 @@ const Configure = () => { const action = await dispatch(updateUptimeMonitor({ authToken, monitor: monitor })); if (action.meta.requestStatus === "fulfilled") { createToast({ body: "Monitor updated successfully!" }); - dispatch(getUptimeMonitorsByTeamId(authToken)); + dispatch(getUptimeSummaryByTeamId(authToken)); } else { createToast({ body: "Failed to update monitor." }); } From 37207da9fa58034a6acb71a778260a394ce5749f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:28:46 -0800 Subject: [PATCH 22/70] Rename get method in network service --- Client/src/Utils/NetworkService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 4812947d0..cc70b0f69 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -127,7 +127,7 @@ class NetworkService { * @param {Array} config.types - Array of monitor types * @returns {Promise} The response from the axios POST request. */ - async getMonitorsAndSummaryByTeamId(config) { + async getMonitorsSummaryByTeamId(config) { const params = new URLSearchParams(); if (config.types) { From 8bc05e442480ed349772cd1063b7e1a35e7ee8e4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:29:47 -0800 Subject: [PATCH 23/70] Rewrite uptime home page for simplicity, move queries to parent --- Client/src/Pages/Uptime/Home/index.jsx | 218 ++++++++++++++++++++----- 1 file changed, 180 insertions(+), 38 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index a542c72ff..616cfeeee 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -1,46 +1,180 @@ -import "./index.css"; -import { useEffect, useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import { useNavigate } from "react-router-dom"; -import { useTheme } from "@emotion/react"; -import { Box, Button, Stack } from "@mui/material"; -import PropTypes from "prop-types"; +// Components +import { Box, Stack, Button } from "@mui/material"; +import Greeting from "../../../Utils/greeting"; import SkeletonLayout from "./skeleton"; import Fallback from "./fallback"; import StatusBox from "./StatusBox"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import Greeting from "../../../Utils/greeting"; -import { CurrentMonitoring } from "./CurrentMonitoring"; +import UptimeDataTable from "./UptimeDataTable"; +import { Pagination } from "../../../Components/Table/TablePagination"; + +// Utils +import { useTheme } from "@emotion/react"; +import { useEffect, useState, useCallback, useMemo, useRef } from "react"; +import { + getUptimeSummaryByTeamId, + getUptimeMonitorsByTeamId, +} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useSelector, useDispatch } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { createToast } from "../../../Utils/toastUtils"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import useDebounce from "../../../Utils/debounce"; const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; +const useWhyDidYouUpdate = (name, props) => { + const previousProps = useRef(); + + useEffect(() => { + if (previousProps.current) { + const allKeys = Object.keys({ ...previousProps.current, ...props }); + const changesObj = {}; + + allKeys.forEach((key) => { + if (previousProps.current[key] !== props[key]) { + changesObj[key] = { + from: previousProps.current[key], + to: props[key], + }; + } + }); + + if (Object.keys(changesObj).length) { + console.log("[why-did-you-update]", name, changesObj); + } + } + + previousProps.current = props; + }); +}; + const UptimeMonitors = () => { + // Redux state + const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); + const authState = useSelector((state) => state.auth); + const { rowsPerPage } = useSelector((state) => state.ui.monitors); + + // Local state + const [monitors, setMonitors] = useState([]); + const [sort, setSort] = useState({}); + const [search, setSearch] = useState(""); + const [page, setPage] = useState(0); + const [isSearching, setIsSearching] = useState(false); + const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); + + // Utils + const debouncedFilter = useDebounce(search, 500); + const dispatch = useDispatch(); const theme = useTheme(); const navigate = useNavigate(); const isAdmin = useIsAdmin(); - const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); - const authState = useSelector((state) => state.auth); - const dispatch = useDispatch({}); - const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); - const handlePause = () => { + const fetchParams = useMemo( + () => ({ + authToken: authState.authToken, + teamId: authState.user.teamId, + sort: { field: sort.field, order: sort.order }, + filter: debouncedFilter, + page, + rowsPerPage, + }), + [authState.authToken, authState.user.teamId, sort, debouncedFilter, page, rowsPerPage] + ); + + useWhyDidYouUpdate("UptimeMonitors", { + isLoading, + monitorsSummary, + authState, + rowsPerPage, + monitors, + sort, + search, + page, + isSearching, + monitorUpdateTrigger, + fetchParams, + }); + + const getMonitorWithPercentage = useCallback((monitor, theme) => { + let uptimePercentage = ""; + let percentageColor = theme.palette.percentage.uptimeExcellent; + + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.percentage.uptimePoor + : monitor.uptimePercentage < 0.5 + ? theme.palette.percentage.uptimeFair + : monitor.uptimePercentage < 0.75 + ? theme.palette.percentage.uptimeGood + : theme.palette.percentage.uptimeExcellent; + } + + return { + id: monitor._id, + name: monitor.name, + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + monitor: monitor, + }; + }, []); + + const fetchMonitors = useCallback(async () => { + try { + const action = await dispatch(getUptimeMonitorsByTeamId(fetchParams)); + if (action.payload.success) { + const { monitors } = action.payload.data; + const mappedMonitors = monitors.map((monitor) => + getMonitorWithPercentage(monitor, theme) + ); + setMonitors(mappedMonitors); + } else { + // TODO: Check for other errors? + throw new Error("Error fetching monitors"); + } + } catch (error) { + createToast({ + body: "Error fetching monitors", + }); + } finally { + setIsSearching(false); + } + }, [fetchParams, dispatch, getMonitorWithPercentage, theme]); + + useEffect(() => { + dispatch(getUptimeSummaryByTeamId(authState.authToken)); + fetchMonitors(); + }, [fetchMonitors, monitorUpdateTrigger, authState.authToken, dispatch]); + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + dispatch( + setRowsPerPage({ + value: parseInt(event.target.value, 10), + table: "monitors", + }) + ); + setPage(0); + }; + + const triggerUpdate = () => { setMonitorUpdateTrigger((prev) => !prev); }; - useEffect(() => { - dispatch(getUptimeMonitorsByTeamId(authState.authToken)); - }, [authState.authToken, dispatch, monitorUpdateTrigger]); - - //TODO bring fetching to this component, like on pageSpeed - - const loading = isLoading; - const totalMonitors = monitorsSummary?.monitorCounts?.total; - const hasMonitors = totalMonitors > 0; - const noMonitors = !hasMonitors; const canAddMonitor = isAdmin && hasMonitors; return ( @@ -72,11 +206,9 @@ const UptimeMonitors = () => { - {loading ? ( - - ) : ( + { <> - {noMonitors && } + {!isLoading && !hasMonitors && } {hasMonitors && ( <> { value={monitorsSummary?.monitorCounts?.paused ?? 0} /> - + )} - )} + } ); }; -UptimeMonitors.propTypes = { - isAdmin: PropTypes.bool, -}; export default UptimeMonitors; From e070ef472eebb17ffcf1f747bbe81b1a77778b31 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:30:27 -0800 Subject: [PATCH 24/70] Create new UptimeDataTable component to hold search, table, and pagination --- .../Home/UptimeDataTable/Skeleton/index.jsx | 47 ++++ .../Uptime/Home/UptimeDataTable/index.jsx | 231 ++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx new file mode 100644 index 000000000..c17f6d74b --- /dev/null +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx @@ -0,0 +1,47 @@ +import { Skeleton } from "@mui/material"; +import DataTable from "../../../../../Components/Table"; +const ROWS_NUMBER = 7; +const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i); + +const TableSkeleton = () => { + /* TODO Skeleton does not follow light and dark theme */ + + const headers = [ + { + id: "name", + + content: "Host", + + render: () => , + }, + { + id: "status", + content: "Status", + render: () => , + }, + { + id: "responseTime", + content: "Response Time", + render: () => , + }, + { + id: "type", + content: "Type", + render: () => , + }, + { + id: "actions", + content: "Actions", + render: () => , + }, + ]; + + return ( + + ); +}; + +export { TableSkeleton }; diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx new file mode 100644 index 000000000..4739b37de --- /dev/null +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -0,0 +1,231 @@ +// Components +import { Box, Stack, CircularProgress } from "@mui/material"; +import Search from "../../../../Components/Inputs/Search"; +import { Heading } from "../../../../Components/Heading"; +import DataTable from "../../../../Components/Table"; +import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; +import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; +import Host from "../host"; +import { StatusLabel } from "../../../../Components/Label"; +import BarChart from "../../../../Components/Charts/BarChart"; +import ActionsMenu from "../actionsMenu"; + +// Utils +import { useTheme } from "@emotion/react"; +import useDebounce from "../../../../Utils/debounce"; +import { useState } from "react"; +import useUtils from "../../utils"; +import { memo } from "react"; +import { useNavigate } from "react-router-dom"; + +const UptimeDataTable = ({ + isAdmin, + isLoading, + monitors, + monitorCount, + sort, + setSort, + search, + setSearch, + isSearching, + setIsSearching, + triggerUpdate, +}) => { + const { determineState } = useUtils(); + + const theme = useTheme(); + const navigate = useNavigate(); + + //Utils + const debouncedFilter = useDebounce(search, 500); + const handleSearch = (value) => { + setIsSearching(true); + setSearch(value); + }; + + const handleSort = (field) => { + let order = ""; + if (sort.field !== field) { + order = "desc"; + } else { + order = sort.order === "asc" ? "desc" : "asc"; + } + setSort({ field, order }); + }; + + const headers = [ + { + id: "name", + content: ( + handleSort("name")}> + Host + + {sort.order === "asc" ? ( + + ) : ( + + )} + + + ), + render: (row) => ( + + ), + }, + { + id: "status", + content: ( + handleSort("status")} + > + {" "} + Status + + {sort.order === "asc" ? ( + + ) : ( + + )} + + + ), + render: (row) => { + const status = determineState(row.monitor); + return ( + + ); + }, + }, + { + id: "responseTime", + content: "Response Time", + render: (row) => , + }, + { + id: "type", + content: "Type", + render: (row) => ( + {row.monitor.type} + ), + }, + { + id: "actions", + content: "Actions", + render: (row) => ( + + ), + }, + ]; + + return ( + + + Uptime monitors + + + {monitorCount} + + + + + + + {isSearching && ( + <> + + + + + + )} + { + navigate(`/uptime/${row.id}`); + }, + emptyView: "No monitors found", + }} + /> + + + ); +}; + +const MemoizedUptimeDataTable = memo(UptimeDataTable); +export default MemoizedUptimeDataTable; From 89d0a74ade40c159e6cf80d20bdc5ef67c918750 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:31:25 -0800 Subject: [PATCH 25/70] Use new table component, remove old table --- .../Uptime/Home/CurrentMonitoring/index.jsx | 71 ---- .../Home/UptimeTable/Skeleton/index.jsx | 47 --- .../Pages/Uptime/Home/UptimeTable/index.jsx | 345 ------------------ Client/src/Pages/Uptime/Home/actionsMenu.jsx | 4 +- Client/src/Routes/index.jsx | 1 + 5 files changed, 4 insertions(+), 464 deletions(-) delete mode 100644 Client/src/Pages/Uptime/Home/CurrentMonitoring/index.jsx delete mode 100644 Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx delete mode 100644 Client/src/Pages/Uptime/Home/UptimeTable/index.jsx diff --git a/Client/src/Pages/Uptime/Home/CurrentMonitoring/index.jsx b/Client/src/Pages/Uptime/Home/CurrentMonitoring/index.jsx deleted file mode 100644 index 2d9426137..000000000 --- a/Client/src/Pages/Uptime/Home/CurrentMonitoring/index.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { Box, Stack } from "@mui/material"; -import Search from "../../../../Components/Inputs/Search"; -import MemoizedMonitorTable from "../UptimeTable"; -import { useState } from "react"; -import useDebounce from "../../../../Utils/debounce"; -import PropTypes from "prop-types"; -import { Heading } from "../../../../Components/Heading"; - -const CurrentMonitoring = ({ totalMonitors, monitors, isAdmin, handlePause }) => { - const theme = useTheme(); - const [search, setSearch] = useState(""); - const [isSearching, setIsSearching] = useState(false); - const debouncedFilter = useDebounce(search, 500); - const handleSearch = (value) => { - setIsSearching(true); - setSearch(value); - }; - return ( - - - Uptime monitors - - - {totalMonitors} - - - - - - - - ); -}; - -CurrentMonitoring.propTypes = { - handlePause: PropTypes.func, - totalMonitors: PropTypes.number, - monitors: PropTypes.array, - isAdmin: PropTypes.bool, -}; - -export { CurrentMonitoring }; diff --git a/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx deleted file mode 100644 index c17f6d74b..000000000 --- a/Client/src/Pages/Uptime/Home/UptimeTable/Skeleton/index.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Skeleton } from "@mui/material"; -import DataTable from "../../../../../Components/Table"; -const ROWS_NUMBER = 7; -const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i); - -const TableSkeleton = () => { - /* TODO Skeleton does not follow light and dark theme */ - - const headers = [ - { - id: "name", - - content: "Host", - - render: () => , - }, - { - id: "status", - content: "Status", - render: () => , - }, - { - id: "responseTime", - content: "Response Time", - render: () => , - }, - { - id: "type", - content: "Type", - render: () => , - }, - { - id: "actions", - content: "Actions", - render: () => , - }, - ]; - - return ( - - ); -}; - -export { TableSkeleton }; diff --git a/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx deleted file mode 100644 index 0aa0eef90..000000000 --- a/Client/src/Pages/Uptime/Home/UptimeTable/index.jsx +++ /dev/null @@ -1,345 +0,0 @@ -import PropTypes from "prop-types"; -import { useState, useEffect, memo, useCallback, useRef } from "react"; -import { useNavigate } from "react-router"; -import { useDispatch, useSelector } from "react-redux"; -import { useTheme } from "@emotion/react"; -import useUtils from "../../utils"; - -import { setRowsPerPage } from "../../../../Features/UI/uiSlice"; -import { logger } from "../../../../Utils/Logger"; -import { jwtDecode } from "jwt-decode"; -import { networkService } from "../../../../main"; - -import { Box, CircularProgress } from "@mui/material"; -import ActionsMenu from "../actionsMenu"; -import Host from "../host"; -import { StatusLabel } from "../../../../Components/Label"; -import { TableSkeleton } from "./Skeleton"; -import BarChart from "../../../../Components/Charts/BarChart"; - -import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; -import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; - -import { Pagination } from "../../../../Components/Table/TablePagination"; -import DataTable from "../../../../Components/Table"; - -const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching, handlePause }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const dispatch = useDispatch(); - const { determineState } = useUtils(); - - const { rowsPerPage } = useSelector((state) => state.ui.monitors); - const authState = useSelector((state) => state.auth); - - const [page, setPage] = useState(0); - const [monitors, setMonitors] = useState([]); - const [monitorCount, setMonitorCount] = useState(0); - const [updateTrigger, setUpdateTrigger] = useState(false); - const [sort, setSort] = useState({}); - const [data, setData] = useState([]); - const prevFilter = useRef(filter); - const headers = [ - { - id: "name", - content: ( - handleSort("name")}> - Host - - {sort.order === "asc" ? ( - - ) : ( - - )} - - - ), - render: (row) => ( - - ), - }, - { - id: "status", - content: ( - handleSort("status")} - > - {" "} - Status - - {sort.order === "asc" ? ( - - ) : ( - - )} - - - ), - render: (row) => { - const status = determineState(row.monitor); - return ( - - ); - }, - }, - { - id: "responseTime", - content: "Response Time", - render: (row) => , - }, - { - id: "type", - content: "Type", - render: (row) => ( - {row.monitor.type} - ), - }, - { - id: "actions", - content: "Actions", - render: (row) => ( - - ), - }, - ]; - - const handleRowUpdate = () => { - setUpdateTrigger((prev) => !prev); - }; - - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event) => { - dispatch( - setRowsPerPage({ - value: parseInt(event.target.value, 10), - table: "monitors", - }) - ); - setPage(0); - }; - - const fetchPage = useCallback(async () => { - try { - const { authToken } = authState; - const user = jwtDecode(authToken); - const res = await networkService.getMonitorsByTeamId({ - authToken, - teamId: user.teamId, - limit: 25, - types: ["http", "ping", "docker", "port"], - status: null, - checkOrder: "desc", - normalize: true, - page: page, - rowsPerPage: rowsPerPage, - filter: filter, - field: sort.field, - order: sort.order, - }); - setMonitors(res?.data?.data?.monitors ?? []); - setMonitorCount(res?.data?.data?.monitorCount ?? 0); - } catch (error) { - logger.error(error); - } finally { - setIsSearching(false); - } - }, [authState, page, rowsPerPage, filter, sort, setIsSearching]); - - useEffect(() => { - fetchPage(); - }, [ - updateTrigger, - authState, - page, - rowsPerPage, - filter, - sort, - setIsSearching, - fetchPage, - ]); - - // Listen for changes in filter, if new value reset the page - useEffect(() => { - if (prevFilter.current !== filter) { - setPage(0); - fetchPage(); - } - prevFilter.current = filter; - }, [filter, fetchPage]); - - const handleSort = async (field) => { - let order = ""; - if (sort.field !== field) { - order = "desc"; - } else { - order = sort.order === "asc" ? "desc" : "asc"; - } - setSort({ field, order }); - - const { authToken } = authState; - const user = jwtDecode(authToken); - - const res = await networkService.getMonitorsByTeamId({ - authToken, - teamId: user.teamId, - limit: 25, - types: ["http", "ping"], - status: null, - checkOrder: "desc", - normalize: true, - page: page, - rowsPerPage: rowsPerPage, - filter: null, - field: field, - order: order, - }); - setMonitors(res?.data?.data?.monitors ?? []); - setMonitorCount(res?.data?.data?.monitorCount ?? 0); - }; - - useEffect(() => { - const mappedMonitors = monitors.map((monitor) => { - let uptimePercentage = ""; - let percentageColor = theme.palette.percentage.uptimeExcellent; - - // Determine uptime percentage and color based on the monitor's uptimePercentage value - if (monitor.uptimePercentage !== undefined) { - uptimePercentage = - monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); - - percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.percentage.uptimePoor - : monitor.uptimePercentage < 0.5 - ? theme.palette.percentage.uptimeFair - : monitor.uptimePercentage < 0.75 - ? theme.palette.percentage.uptimeGood - : theme.palette.percentage.uptimeExcellent; - } - - return { - id: monitor._id, - url: monitor.url, - title: monitor.name, - percentage: uptimePercentage, - percentageColor, - monitor: monitor, - }; - }); - setData(mappedMonitors); - }, [monitors, theme]); - - return ( - - {isSearching && ( - <> - - - - - - )} - {/* - This is the original SX for the row, doesn't match infrastructure table - rowSX: { - cursor: "pointer", - "&:hover": { - filter: "brightness(.75)", - opacity: 0.75, - transition: "filter 0.3s ease, opacity 0.3s ease", - }, - }, - */} - {monitors.length > 0 ? ( - { - navigate(`/uptime/${row.id}`); - }, - emptyView: "No monitors found", - }} - /> - ) : ( - - )} - - - ); -}; - -MonitorTable.propTypes = { - isAdmin: PropTypes.bool, - filter: PropTypes.string, - setIsSearching: PropTypes.func, - isSearching: PropTypes.bool, - setMonitorUpdateTrigger: PropTypes.func, - handlePause: PropTypes.func, -}; - -const MemoizedMonitorTable = memo(MonitorTable); -export default MemoizedMonitorTable; diff --git a/Client/src/Pages/Uptime/Home/actionsMenu.jsx b/Client/src/Pages/Uptime/Home/actionsMenu.jsx index 01452b718..bda27bf15 100644 --- a/Client/src/Pages/Uptime/Home/actionsMenu.jsx +++ b/Client/src/Pages/Uptime/Home/actionsMenu.jsx @@ -34,7 +34,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => if (action.meta.requestStatus === "fulfilled") { setIsOpen(false); // close modal dispatch(getUptimeMonitorsByTeamId(authState.authToken)); - updateCallback(); + updateRowCallback(); createToast({ body: "Monitor deleted successfully." }); } else { createToast({ body: "Failed to delete monitor." }); @@ -169,6 +169,8 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => {isAdmin && ( { + closeMenu(e); + e.stopPropagation(); handlePause(e); }} diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 7d444f2ab..b5f4dd376 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -48,6 +48,7 @@ const Routes = () => { path="/uptime" element={} /> + } From 18988807749a59f6c3f7e112da4e9c313ae3a7a5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:34:18 -0800 Subject: [PATCH 26/70] remove utility hook --- Client/src/Pages/Uptime/Home/index.jsx | 40 -------------------------- 1 file changed, 40 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 616cfeeee..9a2c7784a 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -24,32 +24,6 @@ import useDebounce from "../../../Utils/debounce"; const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; -const useWhyDidYouUpdate = (name, props) => { - const previousProps = useRef(); - - useEffect(() => { - if (previousProps.current) { - const allKeys = Object.keys({ ...previousProps.current, ...props }); - const changesObj = {}; - - allKeys.forEach((key) => { - if (previousProps.current[key] !== props[key]) { - changesObj[key] = { - from: previousProps.current[key], - to: props[key], - }; - } - }); - - if (Object.keys(changesObj).length) { - console.log("[why-did-you-update]", name, changesObj); - } - } - - previousProps.current = props; - }); -}; - const UptimeMonitors = () => { // Redux state const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); @@ -83,20 +57,6 @@ const UptimeMonitors = () => { [authState.authToken, authState.user.teamId, sort, debouncedFilter, page, rowsPerPage] ); - useWhyDidYouUpdate("UptimeMonitors", { - isLoading, - monitorsSummary, - authState, - rowsPerPage, - monitors, - sort, - search, - page, - isSearching, - monitorUpdateTrigger, - fetchParams, - }); - const getMonitorWithPercentage = useCallback((monitor, theme) => { let uptimePercentage = ""; let percentageColor = theme.palette.percentage.uptimeExcellent; From 0cc5c732c7cf6fd67541ff57a1e2b61eafc8a7c3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:45:18 -0800 Subject: [PATCH 27/70] Fix sort arrow positioning --- .../Uptime/Home/UptimeDataTable/index.jsx | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index 4739b37de..d174c535f 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -17,6 +17,7 @@ import { useState } from "react"; import useUtils from "../../utils"; import { memo } from "react"; import { useNavigate } from "react-router-dom"; +import "../index.css"; const UptimeDataTable = ({ isAdmin, @@ -57,9 +58,15 @@ const UptimeDataTable = ({ { id: "name", content: ( - handleSort("name")}> + handleSort("name")} + > Host - )} - - + + ), render: (row) => ( handleSort("status")} > {" "} Status - )} - - + + ), render: (row) => { const status = determineState(row.monitor); From 787bb092354096bb453967d4a038e4da3347949b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 14:52:10 -0800 Subject: [PATCH 28/70] fix pagination icon positioning --- Client/src/Components/Table/TablePagination/index.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Client/src/Components/Table/TablePagination/index.jsx b/Client/src/Components/Table/TablePagination/index.jsx index 4435454bc..a2c7ad067 100644 --- a/Client/src/Components/Table/TablePagination/index.jsx +++ b/Client/src/Components/Table/TablePagination/index.jsx @@ -94,6 +94,13 @@ function Pagination({ "&.Mui-focused > div": { backgroundColor: theme.palette.background.main, }, + "& .MuiSelect-icon": { + // Add this style override + position: "absolute", + right: 0, + top: "50%", + transform: "translateY(-50%)", + }, }, }, }} From 0b803da5fb93d2242d4451bb1cac20b0afd3df26 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 7 Jan 2025 16:27:35 -0800 Subject: [PATCH 29/70] rewrite query to use aggregation pipeline --- Server/db/mongo/modules/monitorModule.js | 144 ++++++++++++++++++----- Server/utils/dataUtils.js | 3 +- 2 files changed, 115 insertions(+), 32 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index c296de34b..84c7ff971 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -12,6 +12,7 @@ import { buildUptimeDetailsPipeline, buildHardwareDetailsPipeline, } from "./monitorModuleQueries.js"; +import { ObjectId } from "mongodb"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -29,6 +30,15 @@ const CHECK_MODEL_LOOKUP = { hardware: HardwareCheck, }; +const MODEL_TYPE_MAPPING = { + http: "checks", + ping: "checks", + docker: "checks", + port: "checks", + pagespeed: "pagespeedchecks", + hardware: "hardwarechecks", +}; + /** * Get all monitors * @async @@ -570,6 +580,7 @@ const getMonitorsByTeamId = async (req, res) => { } = req.query; const monitorQuery = { teamId: req.params.teamId }; + const monitorCount = await Monitor.countDocuments(monitorQuery); if (type !== undefined) { monitorQuery.type = Array.isArray(type) ? { $in: type } : type; @@ -582,46 +593,117 @@ const getMonitorsByTeamId = async (req, res) => { { url: { $regex: filter, $options: "i" } }, ]; } - const monitorCount = await Monitor.countDocuments(monitorQuery); // Pagination const skip = page && rowsPerPage ? page * rowsPerPage : 0; // Build Sort option - const sort = field ? { [field]: order === "asc" ? 1 : -1 } : {}; + const sort = { [field]: order === "asc" ? 1 : -1 }; - const monitors = await Monitor.find(monitorQuery) - .skip(skip) - .limit(rowsPerPage) - .sort(sort); - - // Early return if limit is set to -1, indicating we don't want any checks - if (limit === "-1") { - return { monitors, monitorCount }; + const matchStage = { teamId: new ObjectId(req.params.teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } + if (filter !== undefined) { + matchStage.$or = [ + { name: { $regex: filter, $options: "i" } }, + { url: { $regex: filter, $options: "i" } }, + ]; } - // Map each monitor to include its associated checks - const monitorsWithChecks = await Promise.all( - monitors.map(async (monitor) => { - let model = CHECK_MODEL_LOOKUP[monitor.type]; - // Checks are order newest -> oldest - let checks = await model - .find({ - monitorId: monitor._id, - ...(status && { status }), - }) - .sort({ createdAt: checkOrder === "asc" ? 1 : -1 }) + let result = await Monitor.aggregate([ + { $match: matchStage }, + { $skip: parseInt(skip) }, + ...(rowsPerPage ? [{ $limit: parseInt(rowsPerPage) }] : []), + { $sort: sort }, + { + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + ...(status && { status }), + }, + }, + { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, + { $limit: parseInt(limit) || 0 }, + ], + as: "standardchecks", + }, + }, + { + $lookup: { + from: "pagespeedchecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + ...(status && { status }), + }, + }, + { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, + { $limit: parseInt(limit) || 0 }, + ], + as: "pagespeedchecks", + }, + }, + { + $lookup: { + from: "hardwarechecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + ...(status && { status }), + }, + }, + { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, + { $limit: parseInt(limit) || 0 }, + ], + as: "hardwarechecks", + }, + }, + { + $addFields: { + checks: { + $switch: { + branches: [ + { + case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, + then: "$standardchecks", + }, + { + case: { $eq: ["$type", "pagespeed"] }, + then: "$pagespeedchecks", + }, + { + case: { $eq: ["$type", "hardware"] }, + then: "$hardwarechecks", + }, + ], + default: [], + }, + }, + }, + }, + { + $project: { + standardchecks: 0, + pagespeedchecks: 0, + hardwarechecks: 0, + }, + }, + ]); + result = result.map((monitor) => { + monitor.checks = NormalizeData(monitor.checks, 10, 100); + return monitor; + }); - .limit(limit || 0); - - //Normalize checks if requested - if (normalize !== undefined) { - checks = NormalizeData(checks, 10, 100); - } - return { ...monitor.toObject(), checks }; - }) - ); - return { monitors: monitorsWithChecks, monitorCount }; + return { monitors: result, monitorCount }; } catch (error) { error.service = SERVICE_NAME; error.method = "getMonitorsByTeamId"; diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index fd10a4257..8fbb22821 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -26,6 +26,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { const min = calculatePercentile(checks, 0); const max = calculatePercentile(checks, 95); const normalizedChecks = checks.map((check) => { + console.log(check); const originalResponseTime = check.responseTime; // Normalize the response time between 1 and 100 let normalizedResponseTime = @@ -38,7 +39,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { Math.min(rangeMax, normalizedResponseTime) ); return { - ...check._doc, + ...check, responseTime: normalizedResponseTime, originalResponseTime: originalResponseTime, }; From 0e4cf8cded5e2cbbf882a0f662019434987b6c98 Mon Sep 17 00:00:00 2001 From: Kalyan Bhowmick <“kbhowmick067@gmail.com”> Date: Wed, 8 Jan 2025 10:30:22 +0530 Subject: [PATCH 30/70] resolve query reverted --- Server/db/mongo/modules/checkModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index f8c1a7581..0a552012d 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -178,7 +178,7 @@ const getTeamChecks = async (req) => { checksQuery.status = false; break; case "resolve": - checksQuery.status = true; + checksQuery.statusCode = 5000; break; default: logger.warn({ From dffbce1de1a9c31647a0517b6ef901ad0bc6c5d4 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 8 Jan 2025 05:48:23 +0000 Subject: [PATCH 31/70] Update dependency bullmq to v5.34.8 --- 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 8c02aed27..30306bf29 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.7", + "bullmq": "5.34.8", "cors": "^2.8.5", "dockerode": "4.0.3", "dotenv": "^16.4.5", @@ -1744,9 +1744,9 @@ } }, "node_modules/bullmq": { - "version": "5.34.7", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.7.tgz", - "integrity": "sha512-Z6nKi8skD9n5/ErddTngeeEd8dRvZCu8wgZqpeH9zK0DlyTxYKceTnkGab3FkA+U9mzlqa0z34ZKGMbn4GWdgQ==", + "version": "5.34.8", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.8.tgz", + "integrity": "sha512-id5mmPg3K8tNXQ9VVlmUxBSeLmliIWUrB8Hd5c62PFrIiHywz4TN1PEqU6OWvYXEvoFCr8/BlnbE4JCrGqPVmg==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index 08cf3e872..222f95f16 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.7", + "bullmq": "5.34.8", "cors": "^2.8.5", "dockerode": "4.0.3", "dotenv": "^16.4.5", From dc43c0486c6de95b70c5fa2842e343518a9c4f26 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Wed, 8 Jan 2025 01:22:33 -0500 Subject: [PATCH 32/70] Add OpenSSF best practices badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b0f158451..8e9f34fb7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ ![](https://img.shields.io/github/languages/top/bluewave-labs/checkmate) ![](https://img.shields.io/github/issues/bluewave-labs/checkmate) ![](https://img.shields.io/github/issues-pr/bluewave-labs/checkmate) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) +

Checkmate

From ef56283fa9547e8cecc6bed1ac2ef245f308df8f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 10:05:34 -0800 Subject: [PATCH 33/70] Fix merge conflict, remove unused dispatch --- Client/src/Pages/Uptime/Configure/index.jsx | 2 -- Client/src/Pages/Uptime/Home/index.jsx | 8 +------- Server/db/mongo/modules/monitorModule.js | 9 --------- Server/utils/dataUtils.js | 1 - 4 files changed, 1 insertion(+), 19 deletions(-) diff --git a/Client/src/Pages/Uptime/Configure/index.jsx b/Client/src/Pages/Uptime/Configure/index.jsx index 76c030776..6cef97e0f 100644 --- a/Client/src/Pages/Uptime/Configure/index.jsx +++ b/Client/src/Pages/Uptime/Configure/index.jsx @@ -11,7 +11,6 @@ import { updateUptimeMonitor, pauseUptimeMonitor, getUptimeMonitorById, - getUptimeSummaryByTeamId, deleteUptimeMonitor, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import TextInput from "../../../Components/Inputs/TextInput"; @@ -157,7 +156,6 @@ const Configure = () => { const action = await dispatch(updateUptimeMonitor({ authToken, monitor: monitor })); if (action.meta.requestStatus === "fulfilled") { createToast({ body: "Monitor updated successfully!" }); - dispatch(getUptimeSummaryByTeamId(authToken)); } else { createToast({ body: "Failed to update monitor." }); } diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 50ab2572b..7e1f056ca 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -14,7 +14,6 @@ import { getUptimeSummaryByTeamId, getUptimeMonitorsByTeamId, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useSelector, useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; @@ -26,15 +25,13 @@ const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; const UptimeMonitors = () => { // Redux state - const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); - const authState = useSelector((state) => state.auth); - const { rowsPerPage } = useSelector((state) => state.ui.monitors); // Local state const [monitors, setMonitors] = useState([]); const [sort, setSort] = useState({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); const [isSearching, setIsSearching] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); @@ -46,9 +43,6 @@ const UptimeMonitors = () => { const isAdmin = useIsAdmin(); const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); - const dispatch = useDispatch({}); - const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); - const fetchParams = useMemo( () => ({ diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 84c7ff971..457e1ad87 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -30,15 +30,6 @@ const CHECK_MODEL_LOOKUP = { hardware: HardwareCheck, }; -const MODEL_TYPE_MAPPING = { - http: "checks", - ping: "checks", - docker: "checks", - port: "checks", - pagespeed: "pagespeedchecks", - hardware: "hardwarechecks", -}; - /** * Get all monitors * @async diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index 8fbb22821..008578158 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -26,7 +26,6 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { const min = calculatePercentile(checks, 0); const max = calculatePercentile(checks, 95); const normalizedChecks = checks.map((check) => { - console.log(check); const originalResponseTime = check.responseTime; // Normalize the response time between 1 and 100 let normalizedResponseTime = From 35796dccd470a95648aa52cbad500787a87940f0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 10:06:50 -0800 Subject: [PATCH 34/70] add normalize check --- Server/db/mongo/modules/monitorModule.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 457e1ad87..189243295 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -689,10 +689,12 @@ const getMonitorsByTeamId = async (req, res) => { }, }, ]); - result = result.map((monitor) => { - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); + if (normalize) { + result = result.map((monitor) => { + monitor.checks = NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + } return { monitors: result, monitorCount }; } catch (error) { From 2694f836c7c5d3c0971164c921ebccc608967f86 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 10:17:51 -0800 Subject: [PATCH 35/70] fix destructuring --- Server/db/mongo/modules/monitorModule.js | 1 - Server/utils/dataUtils.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 189243295..aa4cafbb7 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -695,7 +695,6 @@ const getMonitorsByTeamId = async (req, res) => { return monitor; }); } - return { monitors: result, monitorCount }; } catch (error) { error.service = SERVICE_NAME; diff --git a/Server/utils/dataUtils.js b/Server/utils/dataUtils.js index 008578158..ca6ca66df 100644 --- a/Server/utils/dataUtils.js +++ b/Server/utils/dataUtils.js @@ -47,7 +47,7 @@ const NormalizeData = (checks, rangeMin, rangeMax) => { return normalizedChecks; } else { return checks.map((check) => { - return { ...check._doc, originalResponseTime: check.responseTime }; + return { ...check, originalResponseTime: check.responseTime }; }); } }; From 8d8082bb8303dcc06d78bba9132d617d93e6dd92 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 11:53:13 -0800 Subject: [PATCH 36/70] use redux state for pagination --- Client/src/Pages/Uptime/Home/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 7e1f056ca..3dffb3ac9 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -14,6 +14,7 @@ import { getUptimeSummaryByTeamId, getUptimeMonitorsByTeamId, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useSelector, useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; @@ -25,13 +26,12 @@ const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; const UptimeMonitors = () => { // Redux state - + const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage); // Local state const [monitors, setMonitors] = useState([]); const [sort, setSort] = useState({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); const [isSearching, setIsSearching] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); From cf5152d0ec6f95af55a0901724967d1fad074139 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 12:01:28 -0800 Subject: [PATCH 37/70] remove unused dispatch from pagespeed --- Client/src/Pages/PageSpeed/index.jsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index b18148aa8..483e2b0da 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -1,8 +1,7 @@ import { Box, Button, Grid, Stack } from "@mui/material"; import { useEffect, useState } from "react"; import { useTheme } from "@emotion/react"; -import { useDispatch, useSelector } from "react-redux"; -import { getPageSpeedByTeamId } from "../../Features/PageSpeedMonitor/pageSpeedMonitorSlice"; +import { useSelector } from "react-redux"; import Fallback from "../../Components/Fallback"; import "./index.css"; import { useNavigate } from "react-router"; @@ -15,16 +14,12 @@ import { Heading } from "../../Components/Heading"; import { useIsAdmin } from "../../Hooks/useIsAdmin"; const PageSpeed = () => { const theme = useTheme(); - const dispatch = useDispatch(); const navigate = useNavigate(); const isAdmin = useIsAdmin(); const { user, authToken } = useSelector((state) => state.auth); const [isLoading, setIsLoading] = useState(true); const [monitors, setMonitors] = useState([]); const [monitorCount, setMonitorCount] = useState(0); - useEffect(() => { - dispatch(getPageSpeedByTeamId(authToken)); - }, [authToken, dispatch]); useEffect(() => { const fetchMonitors = async () => { From 5da542b99e4c1d90050938df15c4d2a8b7771e35 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 12:01:43 -0800 Subject: [PATCH 38/70] formatting --- .../src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index 6cd91ac28..b2da2cbe6 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -40,7 +40,7 @@ export const checkEndpointResolution = createAsyncThunk( const res = await networkService.checkEndpointResolution({ authToken: authToken, monitorURL: monitorURL, - }) + }); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -53,7 +53,7 @@ export const checkEndpointResolution = createAsyncThunk( return thunkApi.rejectWithValue(payload); } } -) +); export const getPagespeedMonitorById = createAsyncThunk( "monitors/getMonitorById", @@ -88,7 +88,6 @@ export const getPageSpeedByTeamId = createAsyncThunk( teamId: user.teamId, types: ["pagespeed"], }); - return res.data; } catch (error) { if (error.response && error.response.data) { From 7cfc06b2dacaa0758ef35a39838b63dd5eee8bf1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:40:57 -0800 Subject: [PATCH 39/70] Remove summary route --- Server/routes/monitorRoute.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index f3af8e806..92b529417 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -17,6 +17,7 @@ class MonitorRoutes { "/hardware/details/:monitorId", this.monitorController.getHardwareDetailsById ); + this.router.get( "/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById @@ -30,10 +31,7 @@ class MonitorRoutes { ); }); this.router.get("/:monitorId", this.monitorController.getMonitorById); - this.router.get( - "/team/summary/:teamId", - this.monitorController.getMonitorsSummaryByTeamId - ); + this.router.get("/team/:teamId", this.monitorController.getMonitorsByTeamId); this.router.get( From 8f572ba01d32872470ebd1a73c3cfb2fb07ec22d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:42:08 -0800 Subject: [PATCH 40/70] Remove summary controller method, rewrite getMonitosByTeamId --- Server/controllers/monitorController.js | 97 ++++++------------------- 1 file changed, 22 insertions(+), 75 deletions(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f5e7994b2..686661a00 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -1,13 +1,11 @@ import { getMonitorByIdParamValidation, getMonitorByIdQueryValidation, - getMonitorsByTeamIdValidation, + getMonitorsByTeamIdParamValidation, + getMonitorsByTeamIdQueryValidation, createMonitorBodyValidation, getMonitorURLByQueryValidation, editMonitorBodyValidation, - getMonitorsSummaryByTeamIdParamValidation, - getMonitorsSummaryByTeamIdQueryValidation, - getMonitorsByTeamIdQueryValidation, pauseMonitorParamValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, @@ -204,77 +202,6 @@ class MonitorController { } }; - /** - * Retrieves all monitors and a summary for a team based on the team ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.teamId - The ID of the team. - * @property {Object} req.query - The query parameters of the request. - * @property {string} req.query.type - The type of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the data containing the monitors and summary for the team. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - getMonitorsSummaryByTeamId = async (req, res, next) => { - try { - await getMonitorsSummaryByTeamIdParamValidation.validateAsync(req.params); - await getMonitorsSummaryByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const { teamId } = req.params; - const { type } = req.query; - const monitorsSummary = await this.db.getMonitorsSummaryByTeamId(teamId, type); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), - data: monitorsSummary, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId")); - } - }; - - /** - * Retrieves all monitors associated with a team by the team's ID. - * @async - * @param {Object} req - The Express request object. - * @property {Object} req.params - The parameters of the request. - * @property {string} req.params.teamId - The ID of the team. - * @property {Object} req.query - The query parameters of the request. - * @param {Object} res - The Express response object. - * @param {function} next - The next middleware function. - * @returns {Object} The response object with a success status, a message, and the data containing the monitors for the team. - * @throws {Error} If there is an error during the process, especially if there is a validation error (422). - */ - getMonitorsByTeamId = async (req, res, next) => { - try { - await getMonitorsByTeamIdValidation.validateAsync(req.params); - await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - - try { - const teamId = req.params.teamId; - const monitors = await this.db.getMonitorsByTeamId(req, res); - return res.status(200).json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(teamId), - data: monitors, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); - next(error); - } - }; - /** * Creates a new monitor and adds it to the job queue. * @async @@ -564,6 +491,26 @@ class MonitorController { next(handleError(error, SERVICE_NAME, "addDemoMonitors")); } }; + + getMonitorsByTeamId = async (req, res, next) => { + try { + await getMonitorsByTeamIdParamValidation.validateAsync(req.params); + await getMonitorsByTeamIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + } + + try { + const monitors = await this.db.getMonitorsByTeamId(req); + return res.status(200).json({ + success: true, + msg: "good", + data: monitors, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay")); + } + }; } export default MonitorController; From 87f9c35c74e3b934589a1a56cb61f26461146d16 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:42:43 -0800 Subject: [PATCH 41/70] remove unused validation, update monitors by team ID validation --- Server/validation/joi.js | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 825c90cdb..d30235048 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -136,32 +136,12 @@ const getMonitorByIdQueryValidation = joi.object({ normalize: joi.boolean(), }); -const getMonitorsSummaryByTeamIdParamValidation = joi.object({ - teamId: joi.string().required(), -}); - -const getMonitorsSummaryByTeamIdQueryValidation = joi.object({ - type: joi - .alternatives() - .try( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port"), - joi - .array() - .items( - joi.string().valid("http", "ping", "pagespeed", "docker", "hardware", "port") - ) - ), -}); - -const getMonitorsByTeamIdValidation = joi.object({ +const getMonitorsByTeamIdParamValidation = joi.object({ teamId: joi.string().required(), }); const getMonitorsByTeamIdQueryValidation = joi.object({ - status: joi.boolean(), - checkOrder: joi.string().valid("asc", "desc"), limit: joi.number(), - normalize: joi.boolean(), type: joi .alternatives() .try( @@ -467,9 +447,7 @@ export { createMonitorBodyValidation, getMonitorByIdParamValidation, getMonitorByIdQueryValidation, - getMonitorsSummaryByTeamIdParamValidation, - getMonitorsSummaryByTeamIdQueryValidation, - getMonitorsByTeamIdValidation, + getMonitorsByTeamIdParamValidation, getMonitorsByTeamIdQueryValidation, getMonitorStatsByIdParamValidation, getMonitorStatsByIdQueryValidation, From 979110dd7709493992ad1e4f713ed5523f91e818 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:44:24 -0800 Subject: [PATCH 42/70] Rewrite team monitors query --- Server/db/mongo/modules/monitorModule.js | 329 ++++++++++------------- 1 file changed, 144 insertions(+), 185 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index aa4cafbb7..77c290c52 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -505,202 +505,162 @@ const getMonitorById = async (monitorId) => { } }; -/** - * Get monitors and Summary by TeamID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ +const getMonitorsByTeamId = async (req) => { + let { limit, type, page, rowsPerPage, filter, field, order } = req.query; -const getMonitorsSummaryByTeamId = async (teamId, type) => { - try { - const monitorCounts = await Monitor.aggregate([ - { - $match: { - type: { $in: type }, - }, - }, - { - $facet: { - total: [{ $count: "count" }], - up: [{ $match: { status: true } }, { $count: "count" }], - down: [{ $match: { status: false } }, { $count: "count" }], - paused: [{ $match: { isActive: false } }, { $count: "count" }], - }, - }, - { - $project: { - total: { $arrayElemAt: ["$total.count", 0] }, - up: { $arrayElemAt: ["$up.count", 0] }, - down: { $arrayElemAt: ["$down.count", 0] }, - paused: { $arrayElemAt: ["$paused.count", 0] }, - }, - }, - ]); - return { monitorCounts: monitorCounts[0] }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; + // Parse ints + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + + // Build the match stage + const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; } -}; -/** - * Get monitors by TeamID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getMonitorsByTeamId = async (req, res) => { - try { - let { - limit, - type, - status, - checkOrder, - normalize, - page, - rowsPerPage, - filter, - field, - order, - } = req.query; + const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const monitorQuery = { teamId: req.params.teamId }; - const monitorCount = await Monitor.countDocuments(monitorQuery); + const sort = { [field]: order === "asc" ? 1 : -1 }; - if (type !== undefined) { - monitorQuery.type = Array.isArray(type) ? { $in: type } : type; - } - // Add filter if provided - // $options: "i" makes the search case-insensitive - if (filter !== undefined) { - monitorQuery.$or = [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ]; - } + if (filter !== undefined) { + matchStage.$or = [ + { name: { $regex: filter, $options: "i" } }, + { url: { $regex: filter, $options: "i" } }, + ]; + } - // Pagination - const skip = page && rowsPerPage ? page * rowsPerPage : 0; - - // Build Sort option - const sort = { [field]: order === "asc" ? 1 : -1 }; - - const matchStage = { teamId: new ObjectId(req.params.teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - if (filter !== undefined) { - matchStage.$or = [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ]; - } - - let result = await Monitor.aggregate([ - { $match: matchStage }, - { $skip: parseInt(skip) }, - ...(rowsPerPage ? [{ $limit: parseInt(rowsPerPage) }] : []), - { $sort: sort }, - { - $lookup: { - from: "checks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - ...(status && { status }), - }, - }, - { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, - { $limit: parseInt(limit) || 0 }, - ], - as: "standardchecks", - }, - }, - { - $lookup: { - from: "pagespeedchecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - ...(status && { status }), - }, - }, - { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, - { $limit: parseInt(limit) || 0 }, - ], - as: "pagespeedchecks", - }, - }, - { - $lookup: { - from: "hardwarechecks", - let: { monitorId: "$_id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - ...(status && { status }), - }, - }, - { $sort: { createdAt: checkOrder === "asc" ? 1 : -1 } }, - { $limit: parseInt(limit) || 0 }, - ], - as: "hardwarechecks", - }, - }, - { - $addFields: { - checks: { - $switch: { - branches: [ - { - case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, - then: "$standardchecks", + const results = await Monitor.aggregate([ + { $match: matchStage }, + { + $facet: { + summary: [ + { + $group: { + _id: null, + totalMonitors: { $sum: 1 }, + upMonitors: { + $sum: { + $cond: [{ $eq: ["$status", true] }, 1, 0], }, - { - case: { $eq: ["$type", "pagespeed"] }, - then: "$pagespeedchecks", + }, + downMonitors: { + $sum: { + $cond: [{ $eq: ["$status", false] }, 1, 0], }, - { - case: { $eq: ["$type", "hardware"] }, - then: "$hardwarechecks", + }, + pausedMonitors: { + $sum: { + $cond: [{ $eq: ["$isActive", false] }, 1, 0], }, - ], - default: [], + }, }, }, - }, + { + $project: { + _id: 0, + }, + }, + ], + monitors: [ + { $sort: sort }, + { $skip: skip }, + ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), + { + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "standardchecks", + }, + }, + { + $lookup: { + from: "pagespeedchecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "pagespeedchecks", + }, + }, + { + $lookup: { + from: "hardwarechecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "hardwarechecks", + }, + }, + { + $addFields: { + checks: { + $switch: { + branches: [ + { + case: { $in: ["$type", ["http", "ping", "docker", "port"]] }, + then: "$standardchecks", + }, + { + case: { $eq: ["$type", "pagespeed"] }, + then: "$pagespeedchecks", + }, + { + case: { $eq: ["$type", "hardware"] }, + then: "$hardwarechecks", + }, + ], + default: [], + }, + }, + }, + }, + { + $project: { + standardchecks: 0, + pagespeedchecks: 0, + hardwarechecks: 0, + }, + }, + ], }, - { - $project: { - standardchecks: 0, - pagespeedchecks: 0, - hardwarechecks: 0, - }, + }, + { + $project: { + summary: { $arrayElemAt: ["$summary", 0] }, + monitors: 1, }, - ]); - if (normalize) { - result = result.map((monitor) => { - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); - } - return { monitors: result, monitorCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByTeamId"; - throw error; - } + }, + ]); + + let { monitors, summary } = results[0]; + monitors = monitors.map((monitor) => { + monitor.checks = NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + return { monitors, summary }; }; /** @@ -832,9 +792,8 @@ export { getAllMonitorsWithUptimeStats, getMonitorStatsById, getMonitorById, - getUptimeDetailsById, - getMonitorsSummaryByTeamId, getMonitorsByTeamId, + getUptimeDetailsById, createMonitor, deleteMonitor, deleteAllMonitors, From 1805ca8d172ae114b87be2ee5f6164d9da3df970 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:45:34 -0800 Subject: [PATCH 43/70] Remove unnecessary params from getMonitorsByTeamId --- Client/src/Utils/NetworkService.js | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index cc70b0f69..58f3fcb5a 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -157,9 +157,6 @@ class NetworkService { * @param {string} config.teamId - The ID of the team whose monitors are to be retrieved. * @param {number} [config.limit] - The maximum number of checks to retrieve. 0 for all, -1 for none * @param {Array} [config.types] - The types of monitors to retrieve. - * @param {string} [config.status] - The status of the monitors to retrieve. - * @param {string} [config.checkOrder] - The order in which to sort the retrieved monitors. - * @param {boolean} [config.normalize] - Whether to normalize the retrieved monitors. * @param {number} [config.page] - The page number for pagination. * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. * @param {string} [config.filter] - The filter to apply to the monitors. @@ -167,21 +164,10 @@ class NetworkService { * @param {string} [config.order] - The order in which to sort the field. * @returns {Promise} The response from the axios GET request. */ + async getMonitorsByTeamId(config) { - const { - authToken, - teamId, - limit, - types, - status, - checkOrder, - normalize, - page, - rowsPerPage, - filter, - field, - order, - } = config; + const { authToken, teamId, limit, types, page, rowsPerPage, filter, field, order } = + config; const params = new URLSearchParams(); @@ -191,9 +177,6 @@ class NetworkService { params.append("type", type); }); } - if (status) params.append("status", status); - if (checkOrder) params.append("checkOrder", checkOrder); - if (normalize) params.append("normalize", normalize); if (page) params.append("page", page); if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); if (filter) params.append("filter", filter); From e616df276630230e1a366e344575d4f303da25ba Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:46:19 -0800 Subject: [PATCH 44/70] Remove unused thunks from uptime slice --- .../UptimeMonitors/uptimeMonitorsSlice.js | 92 ------------------- 1 file changed, 92 deletions(-) diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 8782948ae..7ea34b9ea 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -78,62 +78,6 @@ export const getUptimeMonitorById = createAsyncThunk( } ); -export const getUptimeSummaryByTeamId = createAsyncThunk( - "monitors/getSummaryByTeamId", - async (token, thunkApi) => { - const user = jwtDecode(token); - try { - const res = await networkService.getMonitorsSummaryByTeamId({ - authToken: token, - teamId: user.teamId, - types: ["http", "ping", "docker", "port"], - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const getUptimeMonitorsByTeamId = createAsyncThunk( - "monitors/getMonitorsByTeamId", - async (config, thunkApi) => { - try { - const res = await networkService.getMonitorsByTeamId({ - authToken: config.authToken, - teamId: config.teamId, - limit: 25, - types: ["http", "ping", "docker", "port"], - status: null, - checkOrder: "desc", - normalize: true, - page: config.page, - rowsPerPage: config.rowsPerPage, - filter: config.filter, - field: config.sort.field, - order: config.sort.order, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - export const updateUptimeMonitor = createAsyncThunk( "monitors/updateMonitor", async (data, thunkApi) => { @@ -289,42 +233,6 @@ const uptimeMonitorsSlice = createSlice({ }, extraReducers: (builder) => { builder - // ***************************************************** - // Summary by teamId - // ***************************************************** - - .addCase(getUptimeSummaryByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeSummaryByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.msg; - state.monitorsSummary = action.payload.data; - }) - .addCase(getUptimeSummaryByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Getting uptime summary failed"; - }) - - // ***************************************************** - // Monitors by teamId - // ***************************************************** - .addCase(getUptimeMonitorsByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Getting uptime monitors failed"; - }) // ***************************************************** // Create Monitor // ***************************************************** From bde4269fd163c12bdd2cd7bbe097f6e92728ddda Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 13:46:49 -0800 Subject: [PATCH 45/70] use new query in infra page --- Client/src/Pages/Infrastructure/index.jsx | 29 ++++++++++------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index be7d7e9ed..d9ef7528e 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { /* useDispatch, */ useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; @@ -50,12 +50,13 @@ function Infrastructure() { setRowsPerPage(parseInt(event.target.value)); setPage(0); }; - const [monitorState, setMonitorState] = useState({ monitors: [], total: 0 }); + const [monitors, setMonitors] = useState([]); + const [summary, setSummary] = useState({}); const { authToken } = useSelector((state) => state.auth); const user = jwtDecode(authToken); - const fetchMonitors = async () => { + const fetchMonitors = useCallback(async () => { try { setIsLoading(true); const response = await networkService.getMonitorsByTeamId({ @@ -63,29 +64,23 @@ function Infrastructure() { teamId: user.teamId, limit: 1, types: ["hardware"], - status: null, - checkOrder: "desc", - normalize: true, page: page, rowsPerPage: rowsPerPage, }); - setMonitorState({ - monitors: response?.data?.data?.monitors ?? [], - total: response?.data?.data?.monitorCount ?? 0, - }); + setMonitors(response?.data?.data?.monitors ?? []); + setSummary(response?.data?.data?.summary ?? {}); } catch (error) { console.error(error); } finally { setIsLoading(false); } - }; + }, [page, rowsPerPage, authToken, user.teamId]); useEffect(() => { fetchMonitors(); - }, [page, rowsPerPage]); + }, [fetchMonitors]); const { determineState } = useUtils(); - const { monitors, total: totalMonitors } = monitorState; // do it here function openDetails(id) { navigate(`/infrastructure/${id}`); @@ -191,7 +186,7 @@ function Infrastructure() { }; }); - let isActuallyLoading = isLoading && monitorState.monitors?.length === 0; + let isActuallyLoading = isLoading && monitors?.length === 0; return ( {isActuallyLoading ? ( - ) : monitorState.monitors?.length !== 0 ? ( + ) : monitors?.length !== 0 ? ( @@ -254,7 +249,7 @@ function Infrastructure() { borderColor={theme.palette.border.light} backgroundColor={theme.palette.background.accent} > - {totalMonitors} + {summary?.totalMonitors ?? 0} @@ -272,7 +267,7 @@ function Infrastructure() { data={monitorsAsRows} /> Date: Wed, 8 Jan 2025 13:47:20 -0800 Subject: [PATCH 46/70] use new query in PageSpeed page --- Client/src/Pages/PageSpeed/card.jsx | 2 +- Client/src/Pages/PageSpeed/index.jsx | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Client/src/Pages/PageSpeed/card.jsx b/Client/src/Pages/PageSpeed/card.jsx index a08a4fcd1..d5cb4afed 100644 --- a/Client/src/Pages/PageSpeed/card.jsx +++ b/Client/src/Pages/PageSpeed/card.jsx @@ -267,7 +267,7 @@ const Card = ({ monitor }) => { sx={{ gridColumnStart: 1, gridColumnEnd: 4 }} > diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index 483e2b0da..98761be6f 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -19,7 +19,7 @@ const PageSpeed = () => { const { user, authToken } = useSelector((state) => state.auth); const [isLoading, setIsLoading] = useState(true); const [monitors, setMonitors] = useState([]); - const [monitorCount, setMonitorCount] = useState(0); + const [summary, setSummary] = useState({}); useEffect(() => { const fetchMonitors = async () => { @@ -30,9 +30,6 @@ const PageSpeed = () => { teamId: user.teamId, limit: 10, types: ["pagespeed"], - status: null, - checkOrder: "desc", - normalize: true, page: null, rowsPerPage: null, filter: null, @@ -41,7 +38,7 @@ const PageSpeed = () => { }); if (res?.data?.data?.monitors) { setMonitors(res.data.data.monitors); - setMonitorCount(res.data.data.monitorCount); + setSummary(res.data.data.summary); } } catch (error) { console.log(error); @@ -113,7 +110,7 @@ const PageSpeed = () => { borderColor={theme.palette.border.light} backgroundColor={theme.palette.background.accent} > - {monitorCount} + {summary?.totalMonitors ?? 0}
Date: Wed, 8 Jan 2025 13:47:42 -0800 Subject: [PATCH 47/70] Use new query in Uptime page --- .../Uptime/Home/UptimeDataTable/index.jsx | 57 +++++++++++++++++-- Client/src/Pages/Uptime/Home/actionsMenu.jsx | 12 +++- Client/src/Pages/Uptime/Home/index.jsx | 56 ++++++++++-------- 3 files changed, 93 insertions(+), 32 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index d174c535f..faca0c612 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -12,13 +12,42 @@ import ActionsMenu from "../actionsMenu"; // Utils import { useTheme } from "@emotion/react"; -import useDebounce from "../../../../Utils/debounce"; -import { useState } from "react"; import useUtils from "../../utils"; import { memo } from "react"; import { useNavigate } from "react-router-dom"; import "../index.css"; +import PropTypes from "prop-types"; +/** + * UptimeDataTable displays a table of uptime monitors with sorting, searching, and action capabilities + * @param {Object} props - Component props + * @param {boolean} props.isAdmin - Whether the current user has admin privileges + * @param {boolean} props.isLoading - Loading state of the table + * @param {Array<{ + * _id: string, + * url: string, + * title: string, + * percentage: number, + * percentageColor: string, + * monitor: { + * _id: string, + * type: string, + * checks: Array + * } + * }>} props.monitors - Array of monitor objects to display + * @param {number} props.monitorCount - Total count of monitors + * @param {Object} props.sort - Current sort configuration + * @param {string} props.sort.field - Field to sort by + * @param {'asc'|'desc'} props.sort.order - Sort direction + * @param {Function} props.setSort - Callback to update sort configuration + * @param {string} props.search - Current search query + * @param {Function} props.setSearch - Callback to update search query + * @param {boolean} props.isSearching - Whether a search is in progress + * @param {Function} props.setIsSearching - Callback to update search state + * @param {Function} props.setIsLoading - Callback to update loading state + * @param {Function} props.triggerUpdate - Callback to trigger a data refresh + * @returns {JSX.Element} Rendered component + */ const UptimeDataTable = ({ isAdmin, isLoading, @@ -30,6 +59,7 @@ const UptimeDataTable = ({ setSearch, isSearching, setIsSearching, + setIsLoading, triggerUpdate, }) => { const { determineState } = useUtils(); @@ -37,8 +67,6 @@ const UptimeDataTable = ({ const theme = useTheme(); const navigate = useNavigate(); - //Utils - const debouncedFilter = useDebounce(search, 500); const handleSearch = (value) => { setIsSearching(true); setSearch(value); @@ -146,6 +174,7 @@ const UptimeDataTable = ({ monitor={row.monitor} isAdmin={isAdmin} updateRowCallback={triggerUpdate} + setIsLoading={setIsLoading} pauseCallback={triggerUpdate} /> ), @@ -187,7 +216,7 @@ const UptimeDataTable = ({
- {isSearching && ( + {(isSearching || isLoading) && ( <> { +const ActionsMenu = ({ + monitor, + isAdmin, + updateRowCallback, + pauseCallback, + setIsLoading, +}) => { const [anchorEl, setAnchorEl] = useState(null); const [actions, setActions] = useState({}); const [isOpen, setIsOpen] = useState(false); @@ -33,7 +38,6 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => ); if (action.meta.requestStatus === "fulfilled") { setIsOpen(false); // close modal - dispatch(getUptimeMonitorsByTeamId(authState.authToken)); updateRowCallback(); createToast({ body: "Monitor deleted successfully." }); } else { @@ -43,6 +47,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateRowCallback, pauseCallback }) => const handlePause = async () => { try { + setIsLoading(true); const action = await dispatch( pauseUptimeMonitor({ authToken, monitorId: monitor._id }) ); @@ -223,6 +228,7 @@ ActionsMenu.propTypes = { isAdmin: PropTypes.bool, updateRowCallback: PropTypes.func, pauseCallback: PropTypes.func, + setIsLoading: PropTypes.func, }; export default ActionsMenu; diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 3dffb3ac9..391be0aed 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -9,11 +9,7 @@ import { Pagination } from "../../../Components/Table/TablePagination"; // Utils import { useTheme } from "@emotion/react"; -import { useEffect, useState, useCallback, useMemo, useRef } from "react"; -import { - getUptimeSummaryByTeamId, - getUptimeMonitorsByTeamId, -} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import { useEffect, useState, useCallback, useMemo } from "react"; import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useSelector, useDispatch } from "react-redux"; @@ -21,6 +17,7 @@ import { useNavigate } from "react-router-dom"; import { createToast } from "../../../Utils/toastUtils"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import useDebounce from "../../../Utils/debounce"; +import { networkService } from "../../../main"; const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; @@ -33,7 +30,9 @@ const UptimeMonitors = () => { const [search, setSearch] = useState(""); const [page, setPage] = useState(0); const [isSearching, setIsSearching] = useState(false); + const [isLoading, setIsLoading] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); + const [monitorsSummary, setMonitorsSummary] = useState({}); // Utils const debouncedFilter = useDebounce(search, 500); @@ -41,7 +40,6 @@ const UptimeMonitors = () => { const theme = useTheme(); const navigate = useNavigate(); const isAdmin = useIsAdmin(); - const { isLoading, monitorsSummary } = useSelector((state) => state.uptimeMonitors); const authState = useSelector((state) => state.auth); const fetchParams = useMemo( @@ -89,30 +87,38 @@ const UptimeMonitors = () => { const fetchMonitors = useCallback(async () => { try { - const action = await dispatch(getUptimeMonitorsByTeamId(fetchParams)); - if (action.payload.success) { - const { monitors } = action.payload.data; - const mappedMonitors = monitors.map((monitor) => - getMonitorWithPercentage(monitor, theme) - ); - setMonitors(mappedMonitors); - } else { - // TODO: Check for other errors? - throw new Error("Error fetching monitors"); - } + setIsLoading(true); + const config = fetchParams; + const res = await networkService.getMonitorsByTeamId({ + authToken: config.authToken, + teamId: config.teamId, + limit: 25, + types: ["http", "ping", "docker", "port"], + page: config.page, + rowsPerPage: config.rowsPerPage, + filter: config.filter, + field: config.sort.field, + order: config.sort.order, + }); + const { monitors, summary } = res.data.data; + const mappedMonitors = monitors.map((monitor) => + getMonitorWithPercentage(monitor, theme) + ); + setMonitors(mappedMonitors); + setMonitorsSummary(summary); } catch (error) { createToast({ body: "Error fetching monitors", }); } finally { + setIsLoading(false); setIsSearching(false); } - }, [fetchParams, dispatch, getMonitorWithPercentage, theme]); + }, [fetchParams, getMonitorWithPercentage, theme]); useEffect(() => { - dispatch(getUptimeSummaryByTeamId(authState.authToken)); fetchMonitors(); - }, [fetchMonitors, monitorUpdateTrigger, authState.authToken, dispatch]); + }, [fetchMonitors, monitorUpdateTrigger]); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -131,7 +137,7 @@ const UptimeMonitors = () => { const triggerUpdate = () => { setMonitorUpdateTrigger((prev) => !prev); }; - const totalMonitors = monitorsSummary?.monitorCounts?.total; + const totalMonitors = monitorsSummary.totalMonitors; const hasMonitors = totalMonitors > 0; const canAddMonitor = isAdmin && hasMonitors; @@ -176,19 +182,20 @@ const UptimeMonitors = () => { > { setSearch={setSearch} isSearching={isSearching} setIsSearching={setIsSearching} + setIsLoading={setIsLoading} triggerUpdate={triggerUpdate} /> Date: Wed, 8 Jan 2025 14:03:23 -0800 Subject: [PATCH 48/70] readd skeleton --- Client/src/Pages/Uptime/Home/index.jsx | 84 ++++++++++++++------------ 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 391be0aed..4ccacd81e 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -173,48 +173,52 @@ const UptimeMonitors = () => { { <> {!isLoading && !hasMonitors && } - {hasMonitors && ( - <> - - + ) : ( + hasMonitors && ( + <> + + + + + + - - - - - - + + ) )} } From 40fbe4a8229cc0c286a3418d7d5e2eec9efada91 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 14:17:29 -0800 Subject: [PATCH 49/70] move filtering to the monitors facet --- Client/src/Pages/Uptime/Home/index.jsx | 4 ++-- Server/db/mongo/modules/monitorModule.js | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 4ccacd81e..449c7dee3 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -137,8 +137,8 @@ const UptimeMonitors = () => { const triggerUpdate = () => { setMonitorUpdateTrigger((prev) => !prev); }; - const totalMonitors = monitorsSummary.totalMonitors; - const hasMonitors = totalMonitors > 0; + const totalMonitors = monitorsSummary?.totalMonitors ?? 0; + const hasMonitors = monitorsSummary?.totalMonitors ?? 0; const canAddMonitor = isAdmin && hasMonitors; return ( diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 77c290c52..7b554ff32 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -523,13 +523,6 @@ const getMonitorsByTeamId = async (req) => { const sort = { [field]: order === "asc" ? 1 : -1 }; - if (filter !== undefined) { - matchStage.$or = [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ]; - } - const results = await Monitor.aggregate([ { $match: matchStage }, { @@ -563,6 +556,18 @@ const getMonitorsByTeamId = async (req) => { }, ], monitors: [ + ...(filter !== undefined + ? [ + { + $match: { + $or: [ + { name: { $regex: filter, $options: "i" } }, + { url: { $regex: filter, $options: "i" } }, + ], + }, + }, + ] + : []), { $sort: sort }, { $skip: skip }, ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), From de01a430a3f23c1627e2ff75fa5223aa375b893e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 15:13:35 -0800 Subject: [PATCH 50/70] fix unnecessary rerenders --- Client/src/Components/Inputs/Search/index.jsx | 1 + .../Uptime/Home/UptimeDataTable/index.jsx | 67 +++++++++++++------ Client/src/Pages/Uptime/Home/index.jsx | 6 +- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index 9a1995726..2491020f0 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -132,6 +132,7 @@ const Search = ({ } return filtered; }} + getOptionKey={(option) => option.id} renderOption={(props, option) => { const { key, ...optionProps } = props; return ( diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index faca0c612..d79f4e270 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -13,10 +13,47 @@ import ActionsMenu from "../actionsMenu"; // Utils import { useTheme } from "@emotion/react"; import useUtils from "../../utils"; -import { memo } from "react"; +import { useState, memo, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import "../index.css"; import PropTypes from "prop-types"; +import { useTraceUpdate } from "../../../../Hooks/useTraceUpdate"; + +const SearchComponent = memo( + ({ monitors, debouncedSearch, onSearchChange, setIsSearching }) => { + const [localSearch, setLocalSearch] = useState(debouncedSearch); + const handleSearch = useCallback( + (value) => { + setIsSearching(true); + setLocalSearch(value); + onSearchChange(value); + }, + [onSearchChange, setIsSearching] + ); + + return ( + + + + ); + } +); +SearchComponent.displayName = "SearchComponent"; +SearchComponent.propTypes = { + monitors: PropTypes.array, + debouncedSearch: PropTypes.string, + onSearchChange: PropTypes.func, + setIsSearching: PropTypes.func, +}; /** * UptimeDataTable displays a table of uptime monitors with sorting, searching, and action capabilities @@ -55,7 +92,7 @@ const UptimeDataTable = ({ monitorCount, sort, setSort, - search, + debouncedSearch, setSearch, isSearching, setIsSearching, @@ -67,11 +104,6 @@ const UptimeDataTable = ({ const theme = useTheme(); const navigate = useNavigate(); - const handleSearch = (value) => { - setIsSearching(true); - setSearch(value); - }; - const handleSort = (field) => { let order = ""; if (sort.field !== field) { @@ -202,18 +234,13 @@ const UptimeDataTable = ({ > {monitorCount} - - - + + {(isSearching || isLoading) && ( @@ -280,7 +307,7 @@ UptimeDataTable.propTypes = { order: PropTypes.oneOf(["asc", "desc"]), }), setSort: PropTypes.func, - search: PropTypes.string, + debouncedSearch: PropTypes.string, setSearch: PropTypes.func, isSearching: PropTypes.bool, setIsSearching: PropTypes.func, diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 449c7dee3..4c5656b2c 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -134,9 +134,9 @@ const UptimeMonitors = () => { setPage(0); }; - const triggerUpdate = () => { + const triggerUpdate = useCallback(() => { setMonitorUpdateTrigger((prev) => !prev); - }; + }, []); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; const hasMonitors = monitorsSummary?.totalMonitors ?? 0; const canAddMonitor = isAdmin && hasMonitors; @@ -203,7 +203,7 @@ const UptimeMonitors = () => { monitorCount={totalMonitors} sort={sort} setSort={setSort} - search={search} + debouncedSearch={debouncedFilter} setSearch={setSearch} isSearching={isSearching} setIsSearching={setIsSearching} From 6d82cb7eb2087b6d5ac0d1aff6e634df2de2e0b2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 8 Jan 2025 15:22:06 -0800 Subject: [PATCH 51/70] remove missing import --- Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index d79f4e270..31b6182fc 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -17,7 +17,6 @@ import { useState, memo, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import "../index.css"; import PropTypes from "prop-types"; -import { useTraceUpdate } from "../../../../Hooks/useTraceUpdate"; const SearchComponent = memo( ({ monitors, debouncedSearch, onSearchChange, setIsSearching }) => { From 83b43d060a3270a19296bca330e7fd95e4f4667f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Jan 2025 15:21:09 +0000 Subject: [PATCH 52/70] Update material-ui monorepo to v7.23.6 --- Client/package-lock.json | 56 +++++++++++++--------------------------- Client/package.json | 4 +-- 2 files changed, 20 insertions(+), 40 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 843e964f6..272af35dd 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -15,8 +15,8 @@ "@mui/lab": "6.0.0-beta.22", "@mui/material": "6.3.1", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.23.5", - "@mui/x-date-pickers": "7.23.3", + "@mui/x-data-grid": "7.23.6", + "@mui/x-date-pickers": "7.23.6", "@reduxjs/toolkit": "2.5.0", "axios": "^1.7.4", "dayjs": "1.11.13", @@ -1390,15 +1390,15 @@ "license": "MIT" }, "node_modules/@mui/x-charts": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.23.2.tgz", - "integrity": "sha512-wLeogvQZZtyrAOdG06mDzIQSHBSAB09Uy16AYRUcMxVObi7Fs0i3TJUMpQHMYz1/1DvE1u8zstDgVpVfk8/iCA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.23.6.tgz", + "integrity": "sha512-25kH01gwE5I0d8kWYZEpXZ9zHVHKSAVDiGwhteqagHtO6x/dEfqbMnjGuaNruCWppJR6wIylLKi/tuiOSpIy2A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", "@mui/x-charts-vendor": "7.20.0", - "@mui/x-internals": "7.23.0", + "@mui/x-internals": "7.23.6", "@react-spring/rafz": "^9.7.5", "@react-spring/web": "^9.7.5", "clsx": "^2.1.1", @@ -1448,14 +1448,14 @@ } }, "node_modules/@mui/x-data-grid": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.23.5.tgz", - "integrity": "sha512-JmwdfaegpwO9Ei3PYCKy1FFip9AcdMGzZ0VTqzWE93pvDBVGxs/MZKT0g/8PYHJ6yzA5sBHHBxFN8sKfs7kVsg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.23.6.tgz", + "integrity": "sha512-NcCZH99ZBLRlCcfLwwhCkVowNZHgjy0XZ/c6EuTRMSZl1UqF8ouwitP1ZfAa1idDIWCHFhxo446U/93aGMqOyQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.23.5", + "@mui/x-internals": "7.23.6", "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^5.1.1" @@ -1484,35 +1484,15 @@ } } }, - "node_modules/@mui/x-data-grid/node_modules/@mui/x-internals": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.5.tgz", - "integrity": "sha512-PS6p9qL7otbQ2edSF83GgTicssE0Q84Ta+X/5tSwoCnToEKClka1Wc/cXlsjhRVLmoqz8uTqaiNcZAgnyQWNYQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@mui/x-date-pickers": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.3.tgz", - "integrity": "sha512-bjTYX/QzD5ZhVZNNnastMUS3j2Hy4p4IXmJgPJ0vKvQBvUdfEO+ZF42r3PJNNde0FVT1MmTzkmdTlz0JZ6ukdw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.23.6.tgz", + "integrity": "sha512-jt6rEAYLju3NZe3y2S+I5KcTiSHV79FW0jeNUEUTceg1qsPzseHbND66k3zVF0hO3N2oZtLtPywof6vN5Doe+Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.23.0", + "@mui/x-internals": "7.23.6", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1531,7 +1511,7 @@ "@mui/material": "^5.15.14 || ^6.0.0", "@mui/system": "^5.15.14 || ^6.0.0", "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", "dayjs": "^1.10.7", "luxon": "^3.0.2", "moment": "^2.29.4", @@ -1571,9 +1551,9 @@ } }, "node_modules/@mui/x-internals": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.0.tgz", - "integrity": "sha512-bPclKpqUiJYIHqmTxSzMVZi6MH51cQsn5U+8jskaTlo3J4QiMeCYJn/gn7YbeR9GOZFp8hetyHjoQoVHKRXCig==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.6.tgz", + "integrity": "sha512-hT1Pa4PNCnxwiauPbYMC3p4DiEF1x05Iu4C1MtC/jMJ1LtthymLmTuQ6ZQ53/R9FeqK6sYd6A6noR+vNMjp5DA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", diff --git a/Client/package.json b/Client/package.json index e4853715a..d3ed1741d 100644 --- a/Client/package.json +++ b/Client/package.json @@ -18,8 +18,8 @@ "@mui/lab": "6.0.0-beta.22", "@mui/material": "6.3.1", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.23.5", - "@mui/x-date-pickers": "7.23.3", + "@mui/x-data-grid": "7.23.6", + "@mui/x-date-pickers": "7.23.6", "@reduxjs/toolkit": "2.5.0", "axios": "^1.7.4", "dayjs": "1.11.13", From d76f02ba51d41a586b0a87c89bedbc9f5500c264 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 10:41:33 -0800 Subject: [PATCH 53/70] repalce limit param of -1 with null --- Client/src/Pages/Incidents/index.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 31f853e34..064ed5b85 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -29,7 +29,7 @@ const Incidents = () => { const res = await networkService.getMonitorsByTeamId({ authToken: authState.authToken, teamId: authState.user.teamId, - limit: -1, + limit: null, types: null, status: null, checkOrder: null, @@ -40,7 +40,6 @@ const Incidents = () => { field: null, order: null, }); - // Reduce to a lookup object for 0(1) lookup if (res?.data?.data?.monitors?.length > 0) { const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => { acc[monitor._id] = monitor; @@ -56,7 +55,7 @@ const Incidents = () => { } }; fetchMonitors(); - }, [authState]); + }, [authState, monitorId]); useEffect(() => {}, [monitors]); From e8aecb4041dd269f0bae5f97db2b7858fd7b8cf6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 10:42:01 -0800 Subject: [PATCH 54/70] fix method name --- Server/controllers/monitorController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 686661a00..839f2bb13 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -508,7 +508,7 @@ class MonitorController { data: monitors, }); } catch (error) { - next(handleError(error, SERVICE_NAME, "getMonitorsForDisplay")); + next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId")); } }; } From ef23648941e530fb48301afd98680e18cad1a3af Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 10:42:25 -0800 Subject: [PATCH 55/70] ommit checks from query if limit is null --- Server/db/mongo/modules/monitorModule.js | 95 ++++++++++++++---------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 7b554ff32..745aecf88 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -508,7 +508,6 @@ const getMonitorById = async (monitorId) => { const getMonitorsByTeamId = async (req) => { let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - // Parse ints limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -571,54 +570,67 @@ const getMonitorsByTeamId = async (req) => { { $sort: sort }, { $skip: skip }, ...(rowsPerPage ? [{ $limit: rowsPerPage }] : []), - { - $lookup: { - from: "checks", - let: { monitorId: "$_id" }, - pipeline: [ + ...(limit + ? [ { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $lookup: { + from: "checks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "standardchecks", }, }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "standardchecks", - }, - }, - { - $lookup: { - from: "pagespeedchecks", - let: { monitorId: "$_id" }, - pipeline: [ + ] + : []), + ...(limit + ? [ { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $lookup: { + from: "pagespeedchecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "pagespeedchecks", }, }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "pagespeedchecks", - }, - }, - { - $lookup: { - from: "hardwarechecks", - let: { monitorId: "$_id" }, - pipeline: [ + ] + : []), + ...(limit + ? [ { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, + $lookup: { + from: "hardwarechecks", + let: { monitorId: "$_id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + ...(limit ? [{ $limit: limit }] : []), + ], + as: "hardwarechecks", }, }, - { $sort: { createdAt: -1 } }, - ...(limit ? [{ $limit: limit }] : []), - ], - as: "hardwarechecks", - }, - }, + ] + : []), + { $addFields: { checks: { @@ -662,6 +674,9 @@ const getMonitorsByTeamId = async (req) => { let { monitors, summary } = results[0]; monitors = monitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } monitor.checks = NormalizeData(monitor.checks, 10, 100); return monitor; }); From c1d6433e6cf17a4e624d3bb15635e47298520203 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 12:01:51 -0800 Subject: [PATCH 56/70] use jobScheduler instead of deprecatred repeatable --- Server/service/jobQueue.js | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 177ca34c5..91a74f6be 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -8,6 +8,7 @@ const QUEUE_LOOKUP = { docker: "uptime", pagespeed: "pagespeed", }; +const getSchedulerId = (monitor) => `scheduler:${monitor.type}:${monitor._id}`; import { successMessages, errorMessages } from "../utils/messages.js"; class NewJobQueue { @@ -440,27 +441,29 @@ class NewJobQueue { throw new Error(`Queue for ${monitor.type} not found`); } - // build job options - const jobOptions = { - attempts: 1, - backoff: { - type: "exponential", - delay: 1000, + const jobTemplate = { + name: jobName, + data: monitor, + opts: { + attempts: 1, + backoff: { + type: "exponential", + delay: 1000, + }, + removeOnComplete: true, + removeOnFail: false, + timeout: 1 * 60 * 1000, }, - removeOnComplete: true, - removeOnFail: false, - timeout: 1 * 60 * 1000, }; - // Execute job immediately - await queue.add(jobName, monitor, jobOptions); - await queue.add(jobName, monitor, { - ...jobOptions, - repeat: { - every: monitor?.interval ?? 60000, - immediately: false, - }, - }); + const schedulerId = getSchedulerId(monitor); + await queue.upsertJobScheduler( + schedulerId, + { every: monitor?.interval ?? 60000 }, + jobTemplate + ); + + await queue.upsert; const workerStats = await this.getWorkerStats(queue); await this.scaleWorkers(workerStats, queue); @@ -490,9 +493,9 @@ class NewJobQueue { async deleteJob(monitor) { try { const queue = this.queues[QUEUE_LOOKUP[monitor.type]]; - const wasDeleted = await queue.removeRepeatable(monitor._id, { - every: monitor.interval, - }); + const schedulerId = getSchedulerId(monitor); + const wasDeleted = await queue.removeJobScheduler(schedulerId); + if (wasDeleted === true) { this.logger.info({ message: successMessages.JOB_QUEUE_DELETE_JOB, From 74f23c17923f779d2920aea361a69ea2151d0616 Mon Sep 17 00:00:00 2001 From: Jasneet Singh Date: Thu, 9 Jan 2025 17:38:08 -0500 Subject: [PATCH 57/70] Address pr comments --- Client/src/Utils/NetworkService.js | 43 ++++++++++++------------------ 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index edef40f0b..3388237da 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -1,20 +1,16 @@ -import { toast } from 'react-toastify'; import axios from "axios"; const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const FALLBACK_BASE_URL = "http://localhost:5000/api/v1"; import { clearAuthState } from "../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice"; - +import { createToast } from "./toastUtils"; + class NetworkService { constructor(store, dispatch, navigate) { this.store = store; this.dispatch = dispatch; this.navigate = navigate; let baseURL = BASE_URL; - this.lastToastTimestamp = 0; - this.errorOccurred = false; - this.user = this.store.getState().auth?.user; - this.isAdmin = (this.user?.role?.includes("admin") ?? false) || (this.user?.role?.includes("superadmin") ?? false); this.axiosInstance = axios.create(); this.setBaseUrl(baseURL); this.unsubscribe = store.subscribe(() => { @@ -27,15 +23,9 @@ class NetworkService { baseURL = FALLBACK_BASE_URL; } this.setBaseUrl(baseURL); - }); + }); this.axiosInstance.interceptors.response.use( - (response) => { - if (this.isAdmin && this.errorOccurred && response.status >= 200 && response.status < 300) { - this.debounceToast("Checkmate server is running properly.", "success"); - this.errorOccurred = false;// Reset error flag if a successful response is received after errors - } - return response; - }, + (response) => response, (error) => { this.handleError(error); return Promise.reject(error); @@ -54,22 +44,23 @@ class NetworkService { this.dispatch(clearAuthState()); this.dispatch(clearUptimeMonitorState()); this.navigate("/login"); - } else if (this.isAdmin && status >= 500) { - this.debounceToast("Checkmate server is not running or has issues. Please check.", "error"); - this.errorOccurred = true; + } else if (status >= 500) { + // Display toast for server errors to all users + createToast({ + variant: "error", + body: "Checkmate server is not running or has issues. Please check.", + }); } - } - } - - debounceToast(message, type, debounceDuration = 5000) { - const now = Date.now(); - if (now - this.lastToastTimestamp > debounceDuration) { - this.lastToastTimestamp = now; - type === "success" ? toast.success(message) : toast.error(message); + } else if (error.request) { + // Show a toast informing the user the server didn't respond + createToast({ + variant: "error", + body: "The server did not respond. Please check your network or try again later.", + }); } } - + cleanup() { if (this.unsubscribe) { this.unsubscribe(); From 5bb1e64f25d7eb84c61cb823a6eb9b0f86a7e474 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 9 Jan 2025 23:07:07 +0000 Subject: [PATCH 58/70] Update dependency mongoose to v8.9.4 --- Server/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index 30306bf29..b6d9c3ca3 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -5774,9 +5774,9 @@ } }, "node_modules/mongoose": { - "version": "8.9.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.3.tgz", - "integrity": "sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==", + "version": "8.9.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.4.tgz", + "integrity": "sha512-DndoI01aV/q40P9DiYDXsYjhj8vZjmmuFwcC3Tro5wFznoE1z6Fe2JgMnbLR6ghglym5ziYizSfAJykp+UPZWg==", "license": "MIT", "dependencies": { "bson": "^6.10.1", From cd3767a04065de99396be7b7461d0927b20c9429 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 9 Jan 2025 20:50:08 -0800 Subject: [PATCH 59/70] new duration algorithm --- Server/db/mongo/modules/monitorModule.js | 1 + .../db/mongo/modules/monitorModuleQueries.js | 53 +++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 745aecf88..d66cd310f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -353,6 +353,7 @@ const getUptimeDetailsById = async (req) => { const monitorStats = { ...monitor.toObject(), stats: { + uptimeStreak: monitorData[0].uptimeStreak, avgResponseTime: monitorData[0].avgResponseTime, totalChecks: monitorData[0].totalChecks, timeSinceLastCheck: monitorData[0].timeSinceLastCheck, diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index ccafbcbdc..18914c0c4 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -31,22 +31,43 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - uptimeDuration: [ - { - $match: { - status: false, - }, - }, + uptimeStreak: [ { $sort: { - createdAt: 1, + createdAt: -1, }, }, { $group: { _id: null, - lastFalseCheck: { - $last: "$$ROOT", + checks: { $push: "$$ROOT" }, + }, + }, + { + $project: { + streak: { + $reduce: { + input: "$checks", + initialValue: { checks: [], foundFalse: false }, + in: { + $cond: [ + { + $and: [ + { $not: "$$value.foundFalse" }, // Haven't found a false yet + { $eq: ["$$this.status", true] }, // Current check is true + ], + }, + { + checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, + foundFalse: false, + }, + { + checks: "$$value.checks", + foundFalse: true, // Mark that we found a false + }, + ], + }, + }, }, }, }, @@ -188,6 +209,20 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, { $project: { + uptimeStreak: { + $cond: [ + { $eq: [{ $size: { $first: "$uptimeStreak.streak.checks" } }, 0] }, + 0, + { + $subtract: [ + new Date(), + { + $last: { $first: "$uptimeStreak.streak.checks.createdAt" }, + }, + ], + }, + ], + }, avgResponseTime: { $arrayElemAt: ["$aggregateData.avgResponseTime", 0], }, From 59c6f39ea9a8ca7032b96a80cc085d9059b6e77a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 09:46:22 -0800 Subject: [PATCH 60/70] Remove incomplete line --- Server/service/jobQueue.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index 91a74f6be..5a9ab335c 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -463,8 +463,6 @@ class NewJobQueue { jobTemplate ); - await queue.upsert; - const workerStats = await this.getWorkerStats(queue); await this.scaleWorkers(workerStats, queue); } catch (error) { From 12cc20a852169b96182e7ad14ffaf5e2647b2c6e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 12:12:33 -0800 Subject: [PATCH 61/70] refactor uptime details query --- .../Uptime/Details/Charts/DownBarChart.jsx | 17 ++-- .../Uptime/Details/Charts/UpBarChart.jsx | 15 ++-- Client/src/Pages/Uptime/Details/index.jsx | 24 +++--- Server/db/mongo/modules/monitorModule.js | 21 ++--- .../db/mongo/modules/monitorModuleQueries.js | 83 +++++++------------ 5 files changed, 61 insertions(+), 99 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx index a851a93ea..bfc99937a 100644 --- a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx @@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; import CustomLabels from "./CustomLabels"; -const DownBarChart = memo(({ stats, type, onBarHover }) => { +const DownBarChart = memo(({ monitor, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); @@ -19,7 +19,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0 }); @@ -40,8 +40,10 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={stats.downChecks?.[0] ?? {}} - lastDataPoint={stats.downChecks?.[stats.downChecks.length - 1] ?? {}} + firstDataPoint={monitor.groupedDownChecks?.[0] ?? {}} + lastDataPoint={ + monitor.groupedDownChecks?.[monitor.groupedDownChecks.length - 1] ?? {} + } type={type} /> } @@ -51,7 +53,7 @@ const DownBarChart = memo(({ stats, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {stats.downChecks.map((entry, index) => ( + {monitor.groupedDownChecks.map((entry, index) => ( { DownBarChart.displayName = "DownBarChart"; DownBarChart.propTypes = { - stats: PropTypes.shape({ - downChecks: PropTypes.arrayOf(PropTypes.object), - downChecksAggregate: PropTypes.object, + monitor: PropTypes.shape({ + groupedDownChecks: PropTypes.arrayOf(PropTypes.object), }), type: PropTypes.string, onBarHover: PropTypes.func, diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx index 08c8d778e..091b9fccd 100644 --- a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx @@ -4,7 +4,7 @@ import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; import PropTypes from "prop-types"; import CustomLabels from "./CustomLabels"; -const UpBarChart = memo(({ stats, type, onBarHover }) => { +const UpBarChart = memo(({ monitor, type, onBarHover }) => { const theme = useTheme(); const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); @@ -26,7 +26,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); @@ -47,8 +47,8 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={stats.upChecks[0]} - lastDataPoint={stats.upChecks[stats.upChecks.length - 1]} + firstDataPoint={monitor.groupedUpChecks[0]} + lastDataPoint={monitor.groupedUpChecks[monitor.groupedUpChecks.length - 1]} type={type} /> } @@ -58,7 +58,7 @@ const UpBarChart = memo(({ stats, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {stats.upChecks.map((entry, index) => { + {monitor.groupedUpChecks.map((entry, index) => { let { main, light } = getColorRange(entry.avgResponseTime); return ( { - {monitor?.stats?.latestResponseTime} + {monitor?.latestResponseTime} {"ms"} } @@ -308,7 +308,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.stats?.upChecks?.reduce((count, checkGroup) => { + : (monitor?.groupedUpChecks?.reduce((count, checkGroup) => { return count + checkGroup.totalChecks; }, 0) ?? 0)} @@ -338,8 +338,8 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) : Math.floor( - ((monitor?.stats?.upChecksAggregate?.totalChecks ?? 0) / - (monitor?.stats?.totalChecks ?? 1)) * + ((monitor?.upChecks?.totalChecks ?? 0) / + (monitor?.totalChecks ?? 1)) * 100 )} @@ -349,7 +349,7 @@ const DetailsPage = () => { @@ -366,7 +366,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.stats?.downChecks?.reduce((count, checkGroup) => { + : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { return count + checkGroup.totalChecks; }, 0) ?? 0)} @@ -388,7 +388,7 @@ const DetailsPage = () => { )} @@ -400,9 +400,7 @@ const DetailsPage = () => { Average Response Time - + { Response Times diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index d66cd310f..b191bd1bc 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -340,32 +340,21 @@ const getUptimeDetailsById = async (req) => { }; const dateString = formatLookup[dateRange]; - const monitorData = await Check.aggregate( + const results = await Check.aggregate( buildUptimeDetailsPipeline(monitor, dates, dateString) ); + const monitorData = results[0]; const normalizedGroupChecks = NormalizeDataUptimeDetails( - monitorData[0].groupChecks, + monitorData.groupedChecks, 10, 100 ); const monitorStats = { ...monitor.toObject(), - stats: { - uptimeStreak: monitorData[0].uptimeStreak, - avgResponseTime: monitorData[0].avgResponseTime, - 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, - downChecks: monitorData[0].downChecks, - }, + ...monitorData, + groupedChecks: normalizedGroupChecks, }; return monitorStats; diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index 18914c0c4..cdbcd51d8 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -19,9 +19,6 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { avgResponseTime: { $avg: "$responseTime", }, - firstCheck: { - $first: "$$ROOT", - }, lastCheck: { $last: "$$ROOT", }, @@ -53,14 +50,16 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { $cond: [ { $and: [ - { $not: "$$value.foundFalse" }, // Haven't found a false yet - { $eq: ["$$this.status", true] }, // Current check is true + { $not: "$$value.foundFalse" }, // stop reducing if a false check has been found + { $eq: ["$$this.status", true] }, // continue reducing if current check true ], }, + // true case { checks: { $concatArrays: ["$$value.checks", ["$$this"]] }, - foundFalse: false, + foundFalse: false, // Add the check to the streak }, + // false case { checks: "$$value.checks", foundFalse: true, // Mark that we found a false @@ -72,7 +71,9 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - groupChecks: [ + // For the response time chart, should return checks for date window + // Grouped by: {day: hour}, {week: day}, {month: day} + groupedChecks: [ { $match: { createdAt: { $gte: dates.start, $lte: dates.end }, @@ -100,7 +101,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - groupAggregate: [ + // Average response time for the date window + groupAvgResponseTime: [ { $match: { createdAt: { $gte: dates.start, $lte: dates.end }, @@ -115,10 +117,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecksAggregate: [ + // All UpChecks for the date window + upChecks: [ { $match: { status: true, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -133,7 +137,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - upChecks: [ + // Up checks grouped by: {day: hour}, {week: day}, {month: day} + groupedUpChecks: [ { $match: { status: true, @@ -160,10 +165,12 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { $sort: { _id: 1 }, }, ], - downChecksAggregate: [ + // All down checks for the date window + downChecks: [ { $match: { status: false, + createdAt: { $gte: dates.start, $lte: dates.end }, }, }, { @@ -178,7 +185,8 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, ], - downChecks: [ + // Down checks grouped by: {day: hour}, {week: day}, {month: day} for the date window + groupedDownChecks: [ { $match: { status: false, @@ -252,51 +260,18 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - 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, - ], - }, - ], - }, - }, + groupedChecks: "$groupedChecks", + groupedAvgResponseTime: { + $arrayElemAt: ["$groupAvgResponseTime", 0], }, - groupChecks: "$groupChecks", - groupAggregate: { - $arrayElemAt: ["$groupAggregate", 0], + upChecks: { + $arrayElemAt: ["$upChecks", 0], }, - upChecksAggregate: { - $arrayElemAt: ["$upChecksAggregate", 0], + groupedUpChecks: "$groupedUpChecks", + downChecks: { + $arrayElemAt: ["$downChecks", 0], }, - upChecks: "$upChecks", - downChecksAggregate: { - $arrayElemAt: ["$downChecksAggregate", 0], - }, - downChecks: "$downChecks", + groupedDownChecks: "$groupedDownChecks", }, }, ]; From 367d3602c8edfee84c5bb44fa99667e21ebb6dc5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 13:23:19 -0800 Subject: [PATCH 62/70] add array of all monitors to response for search --- Server/db/mongo/modules/monitorModule.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index b191bd1bc..df80855bd 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -545,6 +545,15 @@ const getMonitorsByTeamId = async (req) => { }, ], monitors: [ + { $sort: sort }, + { + $project: { + _id: 1, + name: 1, + }, + }, + ], + filteredMonitors: [ ...(filter !== undefined ? [ { @@ -657,20 +666,21 @@ const getMonitorsByTeamId = async (req) => { { $project: { summary: { $arrayElemAt: ["$summary", 0] }, + filteredMonitors: 1, monitors: 1, }, }, ]); - let { monitors, summary } = results[0]; - monitors = monitors.map((monitor) => { + let { monitors, filteredMonitors, summary } = results[0]; + filteredMonitors = filteredMonitors.map((monitor) => { if (!monitor.checks) { return monitor; } monitor.checks = NormalizeData(monitor.checks, 10, 100); return monitor; }); - return { monitors, summary }; + return { monitors, filteredMonitors, summary }; }; /** From cd14659a59a9f2865e7713da91a953aa45a5c0f6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 13:23:32 -0800 Subject: [PATCH 63/70] Use all monitors for search component instead of filtered monitors --- Client/src/Components/Inputs/Search/index.jsx | 1 - Client/src/Pages/Infrastructure/index.jsx | 2 +- Client/src/Pages/PageSpeed/index.jsx | 4 ++-- .../src/Pages/Uptime/Home/UptimeDataTable/index.jsx | 4 +++- Client/src/Pages/Uptime/Home/index.jsx | 11 +++++++---- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index 2491020f0..46c46de3e 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -56,7 +56,6 @@ const Search = ({ disabled, }) => { const theme = useTheme(); - return ( { field: null, order: null, }); - if (res?.data?.data?.monitors) { - setMonitors(res.data.data.monitors); + if (res?.data?.data?.filteredMonitors) { + setMonitors(res.data.data.filteredMonitors); setSummary(res.data.data.summary); } } catch (error) { diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index 31b6182fc..a184eb09f 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -88,6 +88,7 @@ const UptimeDataTable = ({ isAdmin, isLoading, monitors, + filteredMonitors, monitorCount, sort, setSort, @@ -274,7 +275,7 @@ const UptimeDataTable = ({ )} { // Redux state const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage); // Local state - const [monitors, setMonitors] = useState([]); const [sort, setSort] = useState({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); const [isSearching, setIsSearching] = useState(false); const [isLoading, setIsLoading] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); + const [monitors, setMonitors] = useState([]); + const [filteredMonitors, setFilteredMonitors] = useState([]); const [monitorsSummary, setMonitorsSummary] = useState({}); // Utils @@ -100,11 +101,12 @@ const UptimeMonitors = () => { field: config.sort.field, order: config.sort.order, }); - const { monitors, summary } = res.data.data; - const mappedMonitors = monitors.map((monitor) => + const { monitors, filteredMonitors, summary } = res.data.data; + const mappedMonitors = filteredMonitors.map((monitor) => getMonitorWithPercentage(monitor, theme) ); - setMonitors(mappedMonitors); + setMonitors(monitors); + setFilteredMonitors(mappedMonitors); setMonitorsSummary(summary); } catch (error) { createToast({ @@ -199,6 +201,7 @@ const UptimeMonitors = () => { Date: Sat, 11 Jan 2025 01:09:50 +0000 Subject: [PATCH 64/70] Update eslint monorepo to v9.18.0 --- Server/package-lock.json | 141 ++++++--------------------------------- 1 file changed, 21 insertions(+), 120 deletions(-) diff --git a/Server/package-lock.json b/Server/package-lock.json index b6d9c3ca3..de0a4ef1d 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -229,10 +229,11 @@ "dev": true }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -323,10 +324,11 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -341,11 +343,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, + "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -3067,18 +3071,19 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -3212,21 +3217,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3238,74 +3228,12 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -3313,15 +3241,6 @@ "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3335,24 +3254,6 @@ "node": "*" } }, - "node_modules/eslint/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", From c61f08b31c0ab553df61386f9a9f3c95b1eb3565 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 Jan 2025 20:06:01 -0800 Subject: [PATCH 65/70] Add a default sort order if a field to sort by is not specified --- Server/db/mongo/modules/monitorModule.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index df80855bd..4c60e0f22 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -501,7 +501,10 @@ const getMonitorsByTeamId = async (req) => { limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); - + if (field === undefined) { + field = "name"; + order = "asc"; + } // Build the match stage const matchStage = { teamId: ObjectId.createFromHexString(req.params.teamId) }; if (type !== undefined) { @@ -511,7 +514,6 @@ const getMonitorsByTeamId = async (req) => { const skip = page && rowsPerPage ? page * rowsPerPage : 0; const sort = { [field]: order === "asc" ? 1 : -1 }; - const results = await Monitor.aggregate([ { $match: matchStage }, { From 4a655b0328d45648ddb21f3960d63a063923bf21 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Fri, 10 Jan 2025 23:48:24 -0500 Subject: [PATCH 66/70] Newreleases.io + fix star numbers --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8e9f34fb7..260e34196 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # **We're opening our $5000 grant funding announcement soon, in partnership with [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 click on "watch" so you can latest news from us. Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum.** +- If you would like to support us, please consider giving it a ⭐ and click on "watch". +- Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum. +- Need a ping when there's a new release? Use [Newreleases](https://newreleases.io/), a free service to track releases. ![Frame 34](https://github.com/user-attachments/assets/00db1520-24b6-42f2-aca2-a1c9794677da) @@ -14,7 +16,6 @@ ![](https://img.shields.io/github/issues-pr/bluewave-labs/checkmate) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9901/badge)](https://www.bestpractices.dev/projects/9901) -

Checkmate

An open source uptime and infrastructure monitoring application

@@ -83,11 +84,12 @@ If you have any questions, suggestions or comments, please use our [Discord chan - [MUI (React framework)](https://mui.com/) - [Node.js](https://nodejs.org/en) - [MongoDB](https://mongodb.com) +- [Recharts](https://recharts.org) - Lots of other open source components! ## 🤝 Contributing -We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 2700 stars and attracted almost 40 contributors from around the globe. So, don’t hold back — jump in, contribute and learn with us! +We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 3300 stars and attracted 40+ contributors from around the globe. So, don’t hold back — jump in, contribute and learn with us! Here's how you can contribute: From 4fab3f871371a9a7b1416048747141ac8f7eed25 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Sat, 11 Jan 2025 02:32:49 -0500 Subject: [PATCH 67/70] Delete grant_funding.md as we are referencing to https://checkmate.so --- grant_funding.md | 83 ------------------------------------------------ 1 file changed, 83 deletions(-) delete mode 100644 grant_funding.md diff --git a/grant_funding.md b/grant_funding.md deleted file mode 100644 index e53698b4a..000000000 --- a/grant_funding.md +++ /dev/null @@ -1,83 +0,0 @@ -

Announcing USD 5K Grant Prize for Distributed Uptime Management Development

- -### January 9, 2025, [Checkmate](https://github.com/bluewave-labs/checkmate) & [UpRock](https://uprock.com/) - - -**Checkmate** and **UpRock** are excited to announce a USD 5,000 grant prize funding opportunity to support the development of the next-generation distributed uptime manager. UpRock, a DePIN platform with over 2 million devices worldwide, provides the foundation for Checkmate to monitor server uptime from across the globe effectively. - -This grant funding aims to empower developers to contribute innovative solutions to enhance Checkmate's functionality and usability for distributed uptime management, powered by **UpRock**. - -## Program details - -* **Submission start date:** January 9, 2025 -* **Submission deadline:** January 20, 2025 -* **Announcement of prewinners:** January 25, 2025 -* **Total prize pool:** USD 5,000 (subject to change depending on the quality or number submissions) -* **Completion timeline:** A maximum of 30 days after pre-selection - -## What we’re looking for - -* Participants are encouraged to submit ideas and features that: -* Enhance Checkmate & UpRock's distributed uptime monitoring capabilities. -* Simplify deployment for businesses and scale effectively. -* Deliver innovative value-adds to Checkmate and UpRock platform. -* Improve UI/UX consistency, performance, and monitoring reliability. - -Here are some of the sample ideas you can select from, or come up with your own. - -* **Project 1 (DePIN sourced intelligence):** Build a global internet speed survey using 3M devices. This dashboard will implement the underlying Checkmate & UpRock infrastructure and build maps and tables for: 1. Upload speed, 2. Download speed, 3. Ping numbers. -* **Project 2:** Build a notification system that will cover Discord, Slack, Telegram and webhooks. -* **Project 3:** Implement a i18n library for Checkmate. The translation framework will include modifying the source code and using an open source translation manager online so other translators can also contribute. This will help reach out to more developers to build DePIN features on top of Checkmate. -* **Project 4:** A plugin system and implementation for Checkmate to help add more UpRock platform functionality. This plugin system will also be used to add more plugins/features on top of Checkmate easily. -* **Project 5 (Checkmate CLI):** When installed via brew or curl, this CLI will help admins manage the monitoring setup directly from the command line. Admin will be able to list, check, trigger the monitors from the command line easily or change password of the users, or create/invite a new user. -* **Project 6:** Ability to tag or group monitors. Each monitor will have its own tagging, and the admin will be able to filter monitors in the table. -* **Project 7:** Deploy Checkmate and write a blog post about it on dev.to or Hackernoon. Bonus points if you market it on social media. - -## How to participate - -1. **Submit your ideas:** Fill out our online form to propose enhancements and features for Checkmate powered by UpRock, until January 20, 2025. You can submit a project based on the ideas above, or you can also come up with your own enhancement projects. -2. S**election criteria:** Prewinners will be selected based on functionality, design and impact. -3. **Implementation phase:** Prewinners will have 21 days to develop and submit their code contributions starting from January 25, 2025. -4. **Quality requirements:** Submitted code must: - - Be high quality and free from bugs. - - Pass extensive QA testing - - Adhere to Checkmate’s UI design and development standards. -5. **Grant disbursement:** Upon code acceptance via pull, winners will receive USD 1,000 each, sent to their bank account or Paypal. - -## Timeline - -- **January 10, 2025:** Grant announcement and form open for submissions. -- **January 20, 2025:** Submission deadline -- **January 25, 2025:** Winners announced. -- **21 days post-selection:** Code development and submission period. -- **Grant payment:** Issued in 14 days upon PR acceptance. - -## Rules - -- **Cap enforcement:** All projects submitted to the Checkmate & UpRock grant program will be subject to a strict funding cap of $1000 per project. This rule ensures that funding is distributed fairly among contributors, encouraging a diverse range of submissions. Applicants are encouraged to scope their projects accordingly to stay within this limit. Proposals exceeding this amount may either be revised or considered for partial funding. -- **Multiple submissions:** Contributors are welcome to submit multiple projects, provided each submission adheres to the specified funding cap and size criteria. This approach allows developers to showcase various ideas, features, and improvements without being constrained to a single proposal. Each project will be evaluated independently, ensuring fair consideration regardless of other submissions by the same contributor. -- **Partial funding:** In cases where a proposal exceeds the budget limit or includes additional features that extend beyond the core requirements, partial funding may be offered. Contributors will receive feedback and recommendations to adjust their project scope to align with the funding guidelines. This flexibility ensures that high-value features and enhancements can still be supported, even if full funding is not feasible. -- **Feedback process:** To support continuous improvement, contributors will receive constructive feedback on their submissions, whether accepted or not. Feedback will focus on areas such as code quality, design patterns, documentation and usability. Developers can use this input to refine their proposals, submit follow-ups, or enhance their future contributions. -- **Final review and recognition:** A portion of the total funding, $500 (10%), will be reserved for recognizing outstanding contributions across all categories. This award, labeled the "Best Overall Contribution" prize, will be given to a project that demonstrates exceptional innovation, technical quality, and impact. The selection process will be based on the same evaluation criteria as other submissions, with additional emphasis on originality and long-term value to the Checkmate & UpRock ecosystem. - -## Funding tiers -- **4-5 small size projects** - - Examples: Usability fixes, UI enhancements, updating test cases, writing command line interfaces or documentation updates. - - Reward: $100–$300 -- **4-5 medium size projects** - - Examples: New monitoring modules/additions, API enhancements, performance improvements or feature additions. - - Reward: $300–$600 -- **1-2 large projects** - - Examples: Integrations with 3rd party / external tools, major scalability enhancements, advanced analytics features, complex monitoring capabilities, better graphics/charts. - - Reward: $600–$1000 - -## Evaluation criteria - -- **Innovation (30%):** Uniqueness and creativity of the solution. -- **Contribution value (30%):** Impact on usability, performance, or scalability. -- **Code quality & design (40%):** Clean, documented and tested code with working examples showing screenshots and/or videos. - -## Ready to innovate for distributed uptime? - -Don’t miss this opportunity to shape the future of distributed uptime monitoring with Checkmate and UpRock! Submit your ideas today and join us in building cutting-edge solutions for global server management. -Visit Link to submit your entry. From 65428526347d212a3fbd5882bc9d09d5faed3132 Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Sat, 11 Jan 2025 03:34:25 -0500 Subject: [PATCH 68/70] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 260e34196..0927290b1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# **We're opening our $5000 grant funding announcement soon, in partnership with [UpRock](https://uprock.com) - check [our web page](https://checkmate.so) for preliminary details.** +# **Announcing $5000 grant funding in partnership with [UpRock](https://uprock.com) - check [our web page](https://checkmate.so) for funding ideas** - If you would like to support us, please consider giving it a ⭐ and click on "watch". - Need support or have a suggestion? Check our [Discord channel](https://discord.gg/NAb6H3UTjK) or [Discussions](https://github.com/bluewave-labs/checkmate/discussions) forum. From 3b9c747b7606f8d2ce4c7cb1eeaf9df26eac6b22 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 11 Jan 2025 22:16:36 +0000 Subject: [PATCH 69/70] Update dependency eslint-plugin-react-refresh to v0.4.18 --- Client/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 272af35dd..d14da647e 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -3619,9 +3619,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz", - "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==", + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz", + "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==", "dev": true, "license": "MIT", "peerDependencies": { From 2f2977cface8a3dfc6281f7e3fdfb7fbc83d4e40 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sun, 12 Jan 2025 16:54:16 +0000 Subject: [PATCH 70/70] Update dependency bullmq to v5.34.9 --- 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 b6d9c3ca3..2062e5c45 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.8", + "bullmq": "5.34.9", "cors": "^2.8.5", "dockerode": "4.0.3", "dotenv": "^16.4.5", @@ -1744,9 +1744,9 @@ } }, "node_modules/bullmq": { - "version": "5.34.8", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.8.tgz", - "integrity": "sha512-id5mmPg3K8tNXQ9VVlmUxBSeLmliIWUrB8Hd5c62PFrIiHywz4TN1PEqU6OWvYXEvoFCr8/BlnbE4JCrGqPVmg==", + "version": "5.34.9", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.9.tgz", + "integrity": "sha512-olCiFMy9o+pUpBcb/RguS5uUwbma+E80cYgDcG5p7ksvWpoyoEmxq5FOUQcobeTQwO7R9PEIvVsel5yewt0mlw==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index 222f95f16..1937f9ca1 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.8", + "bullmq": "5.34.9", "cors": "^2.8.5", "dockerode": "4.0.3", "dotenv": "^16.4.5",