From a011d8302055161dd9832e3b525ab8967adb95c0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 24 Jan 2025 13:08:33 -0800 Subject: [PATCH 001/106] Create circular count component --- Client/src/Components/CircularCount/index.jsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Client/src/Components/CircularCount/index.jsx diff --git a/Client/src/Components/CircularCount/index.jsx b/Client/src/Components/CircularCount/index.jsx new file mode 100644 index 000000000..5c09ad162 --- /dev/null +++ b/Client/src/Components/CircularCount/index.jsx @@ -0,0 +1,29 @@ +import { Box } from "@mui/material"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +const CircularCount = ({ count }) => { + const theme = useTheme(); + return ( + + {count} + + ); +}; + +CircularCount.propTypes = { + count: PropTypes.number, +}; + +export default CircularCount; From a18b8172635805e7cb42901d5c069c095cdd6e07 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 24 Jan 2025 13:09:05 -0800 Subject: [PATCH 002/106] refactor components out to Components dir --- .../Home/Components/ActionsMenu/index.jsx | 240 ++++++++++++++++ .../Uptime/Home/Components/Host/index.jsx | 64 +++++ .../Home/Components/LoadingSpinner/index.jsx | 46 +++ .../Home/Components/SearchComponent/index.jsx | 43 +++ .../Uptime/Home/Components/Skeleton/index.jsx | 61 ++++ .../Home/Components/StatusBoxes/index.jsx | 36 +++ .../Home/Components/StatusBoxes/skeleton.jsx | 31 +++ .../Home/Components/StatusBoxes/statusBox.jsx | 107 +++++++ .../Home/Components/UptimeDataTable/index.jsx | 263 ++++++++++++++++++ .../Components/UptimeDataTable/skeleton.jsx | 21 ++ 10 files changed, 912 insertions(+) create mode 100644 Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/Host/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx create mode 100644 Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx b/Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx new file mode 100644 index 000000000..f0561d6e6 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx @@ -0,0 +1,240 @@ +import { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { useTheme } from "@emotion/react"; +import { useNavigate } from "react-router-dom"; +import { createToast } from "../../../../../Utils/toastUtils"; +import { logger } from "../../../../../Utils/Logger"; +import { IconButton, Menu, MenuItem } from "@mui/material"; +import { + deleteUptimeMonitor, + pauseUptimeMonitor, +} from "../../../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import Settings from "../../../../../assets/icons/settings-bold.svg?react"; +import PropTypes from "prop-types"; +import Dialog from "../../../../../Components/Dialog"; + +const ActionsMenu = ({ + monitor, + isAdmin, + updateRowCallback, + pauseCallback, + setIsLoading, +}) => { + const [anchorEl, setAnchorEl] = useState(null); + const [actions, setActions] = useState({}); + const [isOpen, setIsOpen] = useState(false); + const dispatch = useDispatch(); + const theme = useTheme(); + const authState = useSelector((state) => state.auth); + const authToken = authState.authToken; + const { isLoading } = useSelector((state) => state.uptimeMonitors); + + const handleRemove = async (event) => { + event.preventDefault(); + event.stopPropagation(); + let monitor = { _id: actions.id }; + const action = await dispatch( + deleteUptimeMonitor({ authToken: authState.authToken, monitor }) + ); + if (action.meta.requestStatus === "fulfilled") { + setIsOpen(false); // close modal + updateRowCallback(); + createToast({ body: "Monitor deleted successfully." }); + } else { + createToast({ body: "Failed to delete monitor." }); + } + }; + + const handlePause = async () => { + try { + setIsLoading(true); + const action = await dispatch( + pauseUptimeMonitor({ authToken, monitorId: monitor._id }) + ); + if (pauseUptimeMonitor.fulfilled.match(action)) { + const state = action?.payload?.data.isActive === false ? "paused" : "resumed"; + createToast({ body: `Monitor ${state} successfully.` }); + pauseCallback(); + } else { + throw new Error(action?.error?.message ?? "Failed to pause monitor."); + } + } catch (error) { + logger.error("Error pausing monitor:", monitor._id, error); + createToast({ body: "Failed to pause monitor." }); + } + }; + + const openMenu = (event, id, url) => { + event.preventDefault(); + event.stopPropagation(); + setAnchorEl(event.currentTarget); + setActions({ id: id, url: url }); + }; + + const openRemove = (e) => { + closeMenu(e); + setIsOpen(true); + }; + + const closeMenu = (e) => { + e.stopPropagation(); + setAnchorEl(null); + }; + + const navigate = useNavigate(); + return ( + <> + { + event.stopPropagation(); + openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url); + }} + sx={{ + "&:focus": { + outline: "none", + }, + "& svg path": { + stroke: theme.palette.primary.contrastTextTertiary, + }, + }} + > + + + + closeMenu(e)} + disableScrollLock + slotProps={{ + paper: { + sx: { + "& ul": { + p: theme.spacing(2.5), + backgroundColor: theme.palette.primary.main, + }, + "& li": { m: 0, color: theme.palette.primary.contrastTextSecondary }, + /* + This should not be set automatically on the last of type + "& li:last-of-type": { + color: theme.palette.error.main, + }, */ + }, + }, + }} + > + {actions.url !== null ? ( + { + closeMenu(e); + e.stopPropagation(); + window.open(actions.url, "_blank", "noreferrer"); + }} + > + Open site + + ) : ( + "" + )} + { + e.stopPropagation(); + navigate(`/uptime/${actions.id}`); + }} + > + Details + + {/* TODO - pass monitor id to Incidents page */} + { + e.stopPropagation(); + navigate(`/incidents/${actions.id}`); + }} + > + Incidents + + {isAdmin && ( + { + e.stopPropagation(); + + navigate(`/uptime/configure/${actions.id}`); + }} + > + Configure + + )} + {isAdmin && ( + { + e.stopPropagation(); + navigate(`/uptime/create/${actions.id}`); + }} + > + Clone + + )} + {isAdmin && ( + { + closeMenu(e); + + e.stopPropagation(); + handlePause(e); + }} + > + {monitor?.isActive === true ? "Pause" : "Resume"} + + )} + {isAdmin && ( + { + e.stopPropagation(); + openRemove(e); + }} + sx={{ "&.MuiButtonBase-root": { color: theme.palette.error.main } }} + > + Remove + + )} + + { + e.stopPropagation(); + setIsOpen(false); + }} + confirmationButtonLabel="Delete" + /* Do we need stop propagation? */ + onConfirm={(e) => { + e.stopPropagation(); + handleRemove(e); + }} + isLoading={isLoading} + modelTitle="modal-delete-monitor" + modelDescription="delete-monitor-confirmation" + /> + + ); +}; + +ActionsMenu.propTypes = { + monitor: PropTypes.shape({ + _id: PropTypes.string, + url: PropTypes.string, + type: PropTypes.string, + isActive: PropTypes.bool, + }).isRequired, + isAdmin: PropTypes.bool, + updateRowCallback: PropTypes.func, + pauseCallback: PropTypes.func, + setIsLoading: PropTypes.func, +}; + +export default ActionsMenu; diff --git a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx new file mode 100644 index 000000000..ec78875f4 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx @@ -0,0 +1,64 @@ +import { Box, Typography } from "@mui/material"; +import PropTypes from "prop-types"; +/** + * Host component. + * This subcomponent receives a params object and displays the host details. + * + * @component + * @param {Object} params - An object containing the following properties: + * @param {string} params.url - The URL of the host. + * @param {string} params.title - The name of the host. + * @param {string} params.percentageColor - The color of the percentage text. + * @param {number} params.percentage - The percentage to display. + * @returns {React.ElementType} Returns a div element with the host details. + */ +const Host = ({ url, title, percentageColor, percentage }) => { + const noTitle = title === undefined || title === url; + return ( + + + {title} + + {percentageColor && percentage && ( + + {percentage}% + + )} + {!noTitle && {url}} + + ); +}; + +Host.propTypes = { + title: PropTypes.string, + percentageColor: PropTypes.string, + percentage: PropTypes.string, + url: PropTypes.string, +}; + +export default Host; diff --git a/Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx b/Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx new file mode 100644 index 000000000..a649473af --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx @@ -0,0 +1,46 @@ +import { CircularProgress, Box } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; +const LoadingSpinner = ({ shouldRender }) => { + const theme = useTheme(); + if (shouldRender === false) { + return; + } + + return ( + <> + + + + + + ); +}; + +LoadingSpinner.propTypes = { + shouldRender: PropTypes.bool, +}; + +export default LoadingSpinner; diff --git a/Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx b/Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx new file mode 100644 index 000000000..d93e6672b --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx @@ -0,0 +1,43 @@ +import { useState } from "react"; +import Search from "../../../../../Components/Inputs/Search"; +import { Box } from "@mui/material"; +import useDebounce from "../../Hooks/useDebounce"; +import { useEffect } from "react"; +import PropTypes from "prop-types"; + +const SearchComponent = ({ monitors, onSearchChange, setIsSearching }) => { + const [localSearch, setLocalSearch] = useState(""); + const debouncedSearch = useDebounce(localSearch, 500); + useEffect(() => { + onSearchChange(debouncedSearch); + setIsSearching(false); + }, [debouncedSearch, onSearchChange, setIsSearching]); + + const handleSearch = (value) => { + setLocalSearch(value); + setIsSearching(true); + }; + + return ( + + + + ); +}; + +SearchComponent.propTypes = { + monitors: PropTypes.array, + onSearchChange: PropTypes.func, + setIsSearching: PropTypes.func, +}; + +export default SearchComponent; diff --git a/Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx new file mode 100644 index 000000000..4e55be536 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx @@ -0,0 +1,61 @@ +import { Skeleton, Stack } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +/** + * Renders a skeleton layout. + * + * @returns {JSX.Element} + */ +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + <> + + + + + + + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx new file mode 100644 index 000000000..ac9eca47f --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx @@ -0,0 +1,36 @@ +import PropTypes from "prop-types"; +import { Stack } from "@mui/material"; +import StatusBox from "./statusBox"; +import { useTheme } from "@emotion/react"; +import SkeletonLayout from "./skeleton"; + +const StatusBoxes = ({ shouldRender, monitorsSummary }) => { + const theme = useTheme(); + if (!shouldRender) return ; + return ( + + + + + + ); +}; + +StatusBoxes.propTypes = { + monitorsSummary: PropTypes.object.isRequired, +}; + +export default StatusBoxes; diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx new file mode 100644 index 000000000..df6e08ccd --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx @@ -0,0 +1,31 @@ +import { Skeleton, Stack } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +const SkeletonLayout = () => { + const theme = useTheme(); + return ( + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx new file mode 100644 index 000000000..b157857ea --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx @@ -0,0 +1,107 @@ +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography } from "@mui/material"; +import Arrow from "../../../../../assets/icons/top-right-arrow.svg?react"; +import Background from "../../../../../assets/Images/background-grid.svg?react"; +import ClockSnooze from "../../../../../assets/icons/clock-snooze.svg?react"; + +const StatusBox = ({ title, value }) => { + const theme = useTheme(); + + let sharedStyles = { + position: "absolute", + right: 8, + opacity: 0.5, + "& svg path": { stroke: theme.palette.primary.contrastTextTertiary }, + }; + + let color; + let icon; + if (title === "up") { + color = theme.palette.success.lowContrast; + icon = ( + + + + ); + } else if (title === "down") { + color = theme.palette.error.lowContrast; + icon = ( + + + + ); + } else if (title === "paused") { + color = theme.palette.warning.lowContrast; + icon = ( + + + + ); + } + + return ( + + + + + + + + {title} + + {icon} + + + {value} + + + # + + + + + ); +}; + +StatusBox.propTypes = { + title: PropTypes.oneOf(["up", "down", "paused"]).isRequired, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, +}; + +export default StatusBox; diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx new file mode 100644 index 000000000..eaf08046d --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx @@ -0,0 +1,263 @@ +// Components +import { Box, Stack, CircularProgress } from "@mui/material"; +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"; +import { useState } from "react"; +import SearchComponent from "../SearchComponent"; +import CircularCount from "../../../../../Components/CircularCount"; +import LoadingSpinner from "../LoadingSpinner"; +import UptimeDataTableSkeleton from "./skeleton"; +// Utils +import { useTheme } from "@emotion/react"; +import useUtils from "../../Hooks/useUtils"; +import { useNavigate } from "react-router-dom"; +import PropTypes from "prop-types"; + +const MonitorDataTable = ({ shouldRender, isSearching, headers, filteredMonitors }) => { + const theme = useTheme(); + const navigate = useNavigate(); + + if (!shouldRender) return null; + return ( + + + { + navigate(`/uptime/${row.id}`); + }, + emptyView: "No monitors found", + }} + /> + + ); +}; + +/** + * 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.setIsLoading - Callback to update loading state + * @param {Function} props.triggerUpdate - Callback to trigger a data refresh + * @returns {JSX.Element} Rendered component + */ +const UptimeDataTable = (props) => { + // Utils + + const { + isAdmin, + setIsLoading, + monitors, + filteredMonitors, + monitorCount, + sort, + setSort, + setSearch, + triggerUpdate, + monitorsAreLoading, + } = props; + const { determineState } = useUtils(); + const theme = useTheme(); + const navigate = useNavigate(); + + // Local state + const [isSearching, setIsSearching] = useState(false); + // Handlers + 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) => ( + + ), + }, + ]; + console.log("rendering"); + return ( + + + Uptime monitors + + + + + + + + ); +}; + +UptimeDataTable.propTypes = { + isSearching: PropTypes.bool, + setIsSearching: PropTypes.func, + setSort: PropTypes.func, + setSearch: PropTypes.func, + setIsLoading: PropTypes.func, + triggerUpdate: PropTypes.func, + debouncedSearch: PropTypes.string, + onSearchChange: PropTypes.func, + isAdmin: PropTypes.bool, + isLoading: PropTypes.bool, + monitors: PropTypes.array, + filteredMonitors: PropTypes.array, + monitorCount: PropTypes.number, + sort: PropTypes.shape({ + field: PropTypes.string, + order: PropTypes.oneOf(["asc", "desc"]), + }), +}; + +export default UptimeDataTable; diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx new file mode 100644 index 000000000..303eb48c8 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx @@ -0,0 +1,21 @@ +import { Skeleton } from "@mui/material"; +import PropTypes from "prop-types"; + +const UptimeDataTableSkeleton = ({ shouldRender }) => { + if (!shouldRender) return null; + + return ( + + ); +}; + +UptimeDataTableSkeleton.propTypes = { + shouldRender: PropTypes.bool.isRequired, +}; + +export default UptimeDataTableSkeleton; From 0234cab8b576a1e801db182bc04380238b936049 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 24 Jan 2025 13:09:57 -0800 Subject: [PATCH 003/106] refactor uptime home page --- Client/src/Components/StatBox/index.jsx | 2 +- .../Pages/Infrastructure/Details/index.jsx | 2 +- Client/src/Pages/Infrastructure/index.jsx | 4 +- .../src/Pages/PageSpeed/Configure/index.jsx | 2 +- Client/src/Pages/PageSpeed/Details/index.jsx | 2 +- Client/src/Pages/PageSpeed/card.jsx | 2 +- Client/src/Pages/Uptime/Details/index.jsx | 2 +- .../Pages/Uptime/Home/Hooks/useDebounce.jsx | 18 + .../Uptime/Home/Hooks/useMonitorFetch.jsx | 98 ++++++ .../{utils.jsx => Home/Hooks/useUtils.jsx} | 0 Client/src/Pages/Uptime/Home/StatusBox.jsx | 106 ------ .../Home/UptimeDataTable/Skeleton/index.jsx | 47 --- .../Uptime/Home/UptimeDataTable/index.jsx | 325 ------------------ Client/src/Pages/Uptime/Home/actionsMenu.jsx | 240 ------------- Client/src/Pages/Uptime/Home/fallback.jsx | 59 ---- Client/src/Pages/Uptime/Home/host.jsx | 64 ---- Client/src/Pages/Uptime/Home/index.css | 6 - Client/src/Pages/Uptime/Home/index.jsx | 279 +++++---------- Client/src/Pages/Uptime/Home/skeleton.jsx | 61 ---- Server/controllers/monitorController.js | 2 +- Server/db/mongo/modules/monitorModule.js | 32 +- 21 files changed, 243 insertions(+), 1110 deletions(-) create mode 100644 Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx create mode 100644 Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx rename Client/src/Pages/Uptime/{utils.jsx => Home/Hooks/useUtils.jsx} (100%) delete mode 100644 Client/src/Pages/Uptime/Home/StatusBox.jsx delete mode 100644 Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx delete mode 100644 Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx delete mode 100644 Client/src/Pages/Uptime/Home/actionsMenu.jsx delete mode 100644 Client/src/Pages/Uptime/Home/fallback.jsx delete mode 100644 Client/src/Pages/Uptime/Home/host.jsx delete mode 100644 Client/src/Pages/Uptime/Home/index.css delete mode 100644 Client/src/Pages/Uptime/Home/skeleton.jsx diff --git a/Client/src/Components/StatBox/index.jsx b/Client/src/Components/StatBox/index.jsx index 953d378ec..b476a1c33 100644 --- a/Client/src/Components/StatBox/index.jsx +++ b/Client/src/Components/StatBox/index.jsx @@ -1,7 +1,7 @@ import { Box, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import useUtils from "../../Pages/Uptime/utils"; +import useUtils from "../../Pages/Uptime/Home/Hooks/useUtils"; /** * StatBox Component diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index acd1bafb1..5da921e30 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -8,7 +8,7 @@ import AreaChart from "../../../Components/Charts/AreaChart"; import { useSelector } from "react-redux"; import { networkService } from "../../../main"; import PulseDot from "../../../Components/Animated/PulseDot"; -import useUtils from "../../Uptime/utils"; +import useUtils from "../../Uptime/Home/Hooks/useUtils"; import { useNavigate } from "react-router-dom"; import Empty from "./empty"; import { logger } from "../../../Utils/Logger"; diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index 0bf367b99..43768132b 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { /* useDispatch, */ useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; -import useUtils from "../Uptime/utils.jsx"; +import useUtils from "../Uptime/Home/Hooks/useUtils.jsx"; import { jwtDecode } from "jwt-decode"; import SkeletonLayout from "./skeleton"; import Fallback from "../../Components/Fallback"; @@ -17,7 +17,7 @@ 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"; -import Host from "../Uptime/Home/host.jsx"; +import Host from "../Uptime/Home/Components/Host"; import { useIsAdmin } from "../../Hooks/useIsAdmin.js"; import { InfrastructureMenu } from "./components/Menu"; diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx index f7829ef35..eee6dfcc9 100644 --- a/Client/src/Pages/PageSpeed/Configure/index.jsx +++ b/Client/src/Pages/PageSpeed/Configure/index.jsx @@ -23,7 +23,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import LoadingButton from "@mui/lab/LoadingButton"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; import SkeletonLayout from "./skeleton"; -import useUtils from "../../Uptime/utils"; +import useUtils from "../../Uptime/Home/Hooks/useUtils"; import "./index.css"; import Dialog from "../../../Components/Dialog"; diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index c5badbab8..9192faaa9 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -19,7 +19,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import PagespeedDetailsAreaChart from "./Charts/AreaChart"; import Checkbox from "../../../Components/Inputs/Checkbox"; import PieChart from "./Charts/PieChart"; -import useUtils from "../../Uptime/utils"; +import useUtils from "../../Uptime/Home/Hooks/useUtils"; import "./index.css"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import StatBox from "../../../Components/StatBox"; diff --git a/Client/src/Pages/PageSpeed/card.jsx b/Client/src/Pages/PageSpeed/card.jsx index f5b0d7110..f7f6c4249 100644 --- a/Client/src/Pages/PageSpeed/card.jsx +++ b/Client/src/Pages/PageSpeed/card.jsx @@ -7,7 +7,7 @@ import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; import { formatDateWithTz, formatDurationSplit } from "../../Utils/timeUtils"; -import useUtils from "../Uptime/utils"; +import useUtils from "../Uptime/Home/Hooks/useUtils"; import { useState } from "react"; import IconBox from "../../Components/IconBox"; /** diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index d5be671d3..be589c1ba 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -19,7 +19,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import { ChartBox } from "./styled"; import SkeletonLayout from "./skeleton"; import "./index.css"; -import useUtils from "../utils"; +import useUtils from "../Home/Hooks/useUtils"; import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; diff --git a/Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx b/Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx new file mode 100644 index 000000000..11d639f14 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx @@ -0,0 +1,18 @@ +import { useState, useEffect } from "react"; + +const useDebounce = (value, delay) => { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + return debouncedValue; +}; + +export default useDebounce; diff --git a/Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx new file mode 100644 index 000000000..d4b7e5ba7 --- /dev/null +++ b/Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx @@ -0,0 +1,98 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; +import { useTheme } from "@emotion/react"; +const getMonitorWithPercentage = (monitor, theme) => { + let uptimePercentage = ""; + let percentageColor = ""; + + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 ? "0" : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.error.main + : monitor.uptimePercentage < 0.5 + ? theme.palette.warning.main + : monitor.uptimePercentage < 0.75 + ? theme.palette.success.main + : theme.palette.success.main; + } + + return { + id: monitor._id, + name: monitor.name, + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + monitor: monitor, + }; +}; + +export const useMonitorFetch = ({ + authToken, + teamId, + limit, + page, + rowsPerPage, + filter, + field, + order, + triggerUpdate, +}) => { + const [monitorsAreLoading, setMonitorsAreLoading] = useState(false); + const [monitors, setMonitors] = useState([]); + const [filteredMonitors, setFilteredMonitors] = useState([]); + const [monitorsSummary, setMonitorsSummary] = useState({}); + + const theme = useTheme(); + + useEffect(() => { + const fetchMonitors = async () => { + try { + setMonitorsAreLoading(true); + const res = await networkService.getMonitorsByTeamId({ + authToken, + teamId, + limit, + types: ["http", "ping", "docker", "port"], + page, + rowsPerPage, + filter, + field, + order, + }); + const { monitors, filteredMonitors, summary } = res.data.data; + const mappedMonitors = filteredMonitors.map((monitor) => + getMonitorWithPercentage(monitor, theme) + ); + setMonitors(monitors); + setFilteredMonitors(mappedMonitors); + setMonitorsSummary(summary); + } catch (error) { + createToast({ + body: error.message, + }); + } finally { + setMonitorsAreLoading(false); + } + }; + fetchMonitors(); + }, [ + authToken, + teamId, + limit, + field, + filter, + order, + page, + rowsPerPage, + theme, + triggerUpdate, + ]); + return { monitors, filteredMonitors, monitorsSummary, monitorsAreLoading }; +}; + +export default useMonitorFetch; diff --git a/Client/src/Pages/Uptime/utils.jsx b/Client/src/Pages/Uptime/Home/Hooks/useUtils.jsx similarity index 100% rename from Client/src/Pages/Uptime/utils.jsx rename to Client/src/Pages/Uptime/Home/Hooks/useUtils.jsx diff --git a/Client/src/Pages/Uptime/Home/StatusBox.jsx b/Client/src/Pages/Uptime/Home/StatusBox.jsx deleted file mode 100644 index d04ebdf5f..000000000 --- a/Client/src/Pages/Uptime/Home/StatusBox.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import { Box, Stack, Typography } from "@mui/material"; -import Arrow from "../../../assets/icons/top-right-arrow.svg?react"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import ClockSnooze from "../../../assets/icons/clock-snooze.svg?react"; - -const StatusBox = ({ title, value }) => { - const theme = useTheme(); - - let sharedStyles = { - position: "absolute", - right: 8, - opacity: 0.5, - "& svg path": { stroke: theme.palette.primary.contrastTextTertiary }, - }; - - let color; - let icon; - if (title === "up") { - color = theme.palette.success.lowContrast; - icon = ( - - - - ); - } else if (title === "down") { - color = theme.palette.error.lowContrast; - icon = ( - - - - ); - } else if (title === "paused") { - color = theme.palette.warning.lowContrast; - icon = ( - - - - ); - } - - return ( - - - - - - {title} - - {icon} - - {value} - - - # - - - - ); -}; - -StatusBox.propTypes = { - title: PropTypes.oneOf(["up", "down", "paused"]).isRequired, - value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, -}; - -export default StatusBox; diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/Skeleton/index.jsx deleted file mode 100644 index c17f6d74b..000000000 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/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/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx deleted file mode 100644 index 6961c6d5c..000000000 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ /dev/null @@ -1,325 +0,0 @@ -// 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 useUtils from "../../utils"; -import { useState, memo, useCallback } from "react"; -import { useNavigate } from "react-router-dom"; -import "../index.css"; -import PropTypes from "prop-types"; - -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 - * @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, - monitors, - filteredMonitors, - monitorCount, - sort, - setSort, - debouncedSearch, - setSearch, - isSearching, - setIsSearching, - setIsLoading, - triggerUpdate, -}) => { - const { determineState } = useUtils(); - - const theme = useTheme(); - const navigate = useNavigate(); - - 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 - {/* TODO Same as the one in Infrastructure. Create component */} - - {monitorCount} - - - - - - {(isSearching || isLoading) && ( - <> - - - - - - )} - { - navigate(`/uptime/${row.id}`); - }, - emptyView: "No monitors found", - }} - /> - - - ); -}; - -const MemoizedUptimeDataTable = memo(UptimeDataTable); -export default MemoizedUptimeDataTable; - -UptimeDataTable.propTypes = { - isAdmin: PropTypes.bool, - isLoading: PropTypes.bool, - monitors: PropTypes.array, - filteredMonitors: PropTypes.array, - monitorCount: PropTypes.number, - sort: PropTypes.shape({ - field: PropTypes.string, - order: PropTypes.oneOf(["asc", "desc"]), - }), - setSort: PropTypes.func, - debouncedSearch: PropTypes.string, - setSearch: PropTypes.func, - isSearching: PropTypes.bool, - setIsSearching: PropTypes.func, - setIsLoading: PropTypes.func, - triggerUpdate: PropTypes.func, -}; diff --git a/Client/src/Pages/Uptime/Home/actionsMenu.jsx b/Client/src/Pages/Uptime/Home/actionsMenu.jsx deleted file mode 100644 index 493c5f25e..000000000 --- a/Client/src/Pages/Uptime/Home/actionsMenu.jsx +++ /dev/null @@ -1,240 +0,0 @@ -import { useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils"; -import { logger } from "../../../Utils/Logger"; -import { IconButton, Menu, MenuItem } from "@mui/material"; -import { - deleteUptimeMonitor, - pauseUptimeMonitor, -} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import Settings from "../../../assets/icons/settings-bold.svg?react"; -import PropTypes from "prop-types"; -import Dialog from "../../../Components/Dialog"; - -const ActionsMenu = ({ - monitor, - isAdmin, - updateRowCallback, - pauseCallback, - setIsLoading, -}) => { - const [anchorEl, setAnchorEl] = useState(null); - const [actions, setActions] = useState({}); - const [isOpen, setIsOpen] = useState(false); - const dispatch = useDispatch(); - const theme = useTheme(); - const authState = useSelector((state) => state.auth); - const authToken = authState.authToken; - const { isLoading } = useSelector((state) => state.uptimeMonitors); - - const handleRemove = async (event) => { - event.preventDefault(); - event.stopPropagation(); - let monitor = { _id: actions.id }; - const action = await dispatch( - deleteUptimeMonitor({ authToken: authState.authToken, monitor }) - ); - if (action.meta.requestStatus === "fulfilled") { - setIsOpen(false); // close modal - updateRowCallback(); - createToast({ body: "Monitor deleted successfully." }); - } else { - createToast({ body: "Failed to delete monitor." }); - } - }; - - const handlePause = async () => { - try { - setIsLoading(true); - const action = await dispatch( - pauseUptimeMonitor({ authToken, monitorId: monitor._id }) - ); - if (pauseUptimeMonitor.fulfilled.match(action)) { - const state = action?.payload?.data.isActive === false ? "paused" : "resumed"; - createToast({ body: `Monitor ${state} successfully.` }); - pauseCallback(); - } else { - throw new Error(action?.error?.message ?? "Failed to pause monitor."); - } - } catch (error) { - logger.error("Error pausing monitor:", monitor._id, error); - createToast({ body: "Failed to pause monitor." }); - } - }; - - const openMenu = (event, id, url) => { - event.preventDefault(); - event.stopPropagation(); - setAnchorEl(event.currentTarget); - setActions({ id: id, url: url }); - }; - - const openRemove = (e) => { - closeMenu(e); - setIsOpen(true); - }; - - const closeMenu = (e) => { - e.stopPropagation(); - setAnchorEl(null); - }; - - const navigate = useNavigate(); - return ( - <> - { - event.stopPropagation(); - openMenu(event, monitor._id, monitor.type === "ping" ? null : monitor.url); - }} - sx={{ - "&:focus": { - outline: "none", - }, - "& svg path": { - stroke: theme.palette.primary.contrastTextTertiary, - }, - }} - > - - - - closeMenu(e)} - disableScrollLock - slotProps={{ - paper: { - sx: { - "& ul": { - p: theme.spacing(2.5), - backgroundColor: theme.palette.primary.main, - }, - "& li": { m: 0, color: theme.palette.primary.contrastTextSecondary }, - /* - This should not be set automatically on the last of type - "& li:last-of-type": { - color: theme.palette.error.main, - }, */ - }, - }, - }} - > - {actions.url !== null ? ( - { - closeMenu(e); - e.stopPropagation(); - window.open(actions.url, "_blank", "noreferrer"); - }} - > - Open site - - ) : ( - "" - )} - { - e.stopPropagation(); - navigate(`/uptime/${actions.id}`); - }} - > - Details - - {/* TODO - pass monitor id to Incidents page */} - { - e.stopPropagation(); - navigate(`/incidents/${actions.id}`); - }} - > - Incidents - - {isAdmin && ( - { - e.stopPropagation(); - - navigate(`/uptime/configure/${actions.id}`); - }} - > - Configure - - )} - {isAdmin && ( - { - e.stopPropagation(); - navigate(`/uptime/create/${actions.id}`); - }} - > - Clone - - )} - {isAdmin && ( - { - closeMenu(e); - - e.stopPropagation(); - handlePause(e); - }} - > - {monitor?.isActive === true ? "Pause" : "Resume"} - - )} - {isAdmin && ( - { - e.stopPropagation(); - openRemove(e); - }} - sx={{ "&.MuiButtonBase-root": { color: theme.palette.error.main } }} - > - Remove - - )} - - { - e.stopPropagation(); - setIsOpen(false); - }} - confirmationButtonLabel="Delete" - /* Do we need stop propagation? */ - onConfirm={(e) => { - e.stopPropagation(); - handleRemove(e); - }} - isLoading={isLoading} - modelTitle="modal-delete-monitor" - modelDescription="delete-monitor-confirmation" - /> - - ); -}; - -ActionsMenu.propTypes = { - monitor: PropTypes.shape({ - _id: PropTypes.string, - url: PropTypes.string, - type: PropTypes.string, - isActive: PropTypes.bool, - }).isRequired, - isAdmin: PropTypes.bool, - updateRowCallback: PropTypes.func, - pauseCallback: PropTypes.func, - setIsLoading: PropTypes.func, -}; - -export default ActionsMenu; diff --git a/Client/src/Pages/Uptime/Home/fallback.jsx b/Client/src/Pages/Uptime/Home/fallback.jsx deleted file mode 100644 index 2606ca95f..000000000 --- a/Client/src/Pages/Uptime/Home/fallback.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router-dom"; -import { useSelector } from "react-redux"; -import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react"; -import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react"; -import PropTypes from "prop-types"; - -const Fallback = ({ isAdmin }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const mode = useSelector((state) => state.ui.mode); - - return ( - - - {mode === "light" ? : } - - - No monitors found to display - - - It looks like you don’t have any monitors set up yet. - - {isAdmin && ( - - )} - - ); -}; - -Fallback.propTypes = { - isAdmin: PropTypes.bool, -}; - -export default Fallback; diff --git a/Client/src/Pages/Uptime/Home/host.jsx b/Client/src/Pages/Uptime/Home/host.jsx deleted file mode 100644 index ec78875f4..000000000 --- a/Client/src/Pages/Uptime/Home/host.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Box, Typography } from "@mui/material"; -import PropTypes from "prop-types"; -/** - * Host component. - * This subcomponent receives a params object and displays the host details. - * - * @component - * @param {Object} params - An object containing the following properties: - * @param {string} params.url - The URL of the host. - * @param {string} params.title - The name of the host. - * @param {string} params.percentageColor - The color of the percentage text. - * @param {number} params.percentage - The percentage to display. - * @returns {React.ElementType} Returns a div element with the host details. - */ -const Host = ({ url, title, percentageColor, percentage }) => { - const noTitle = title === undefined || title === url; - return ( - - - {title} - - {percentageColor && percentage && ( - - {percentage}% - - )} - {!noTitle && {url}} - - ); -}; - -Host.propTypes = { - title: PropTypes.string, - percentageColor: PropTypes.string, - percentage: PropTypes.string, - url: PropTypes.string, -}; - -export default Host; diff --git a/Client/src/Pages/Uptime/Home/index.css b/Client/src/Pages/Uptime/Home/index.css deleted file mode 100644 index adb1d8674..000000000 --- a/Client/src/Pages/Uptime/Home/index.css +++ /dev/null @@ -1,6 +0,0 @@ -/* TODO take these from here and declare using emotion. Plus, the values should live in the theme */ -.monitors .MuiStack-root > button:not(.MuiIconButton-root) { - font-size: var(--env-var-font-size-medium); - height: var(--env-var-height-2); - min-width: fit-content; -} diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 446702ebb..c300118e0 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -1,128 +1,70 @@ // Components -import { Box, Stack, Button } from "@mui/material"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; import Greeting from "../../../Utils/greeting"; -import SkeletonLayout from "./skeleton"; -import Fallback from "./fallback"; -import StatusBox from "./StatusBox"; -import UptimeDataTable from "./UptimeDataTable"; +import StatusBoxes from "./Components/StatusBoxes"; +import UptimeDataTable from "./Components/UptimeDataTable"; import Pagination from "../../../Components/Table/TablePagination"; +// MUI Components +import { Stack, Box, Button } from "@mui/material"; + // Utils -import { useTheme } from "@emotion/react"; -import { useEffect, useState, useCallback, useMemo } from "react"; -import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import { useState, useCallback } from "react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import { useSelector, useDispatch } from "react-redux"; +import { useTheme } from "@emotion/react"; 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"; +import useMonitorFetch from "./Hooks/useMonitorFetch"; +import { useSelector, useDispatch } from "react-redux"; +import { setRowsPerPage } from "../../../Features/UI/uiSlice"; +import PropTypes from "prop-types"; const BREADCRUMBS = [{ name: `Uptime`, path: "/uptime" }]; +const CreateMonitorButton = ({ shouldRender }) => { + // Utils + const navigate = useNavigate(); + if (shouldRender === false) { + return; + } + + return ( + + + + ); +}; + +CreateMonitorButton.propTypes = { + shouldRender: PropTypes.bool, +}; + const UptimeMonitors = () => { // Redux state + const { authToken, user } = useSelector((state) => state.auth); const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage); + // Local state - const [sort, setSort] = useState({}); const [search, setSearch] = useState(""); const [page, setPage] = useState(0); + const [sort, setSort] = useState({}); 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 - const debouncedFilter = useDebounce(search, 500); - const dispatch = useDispatch(); const theme = useTheme(); - const navigate = useNavigate(); const isAdmin = useIsAdmin(); + const dispatch = useDispatch(); - const authState = useSelector((state) => state.auth); - - 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] - ); - - const getMonitorWithPercentage = useCallback((monitor, theme) => { - let uptimePercentage = ""; - let percentageColor = ""; - - if (monitor.uptimePercentage !== undefined) { - uptimePercentage = - monitor.uptimePercentage === 0 - ? "0" - : (monitor.uptimePercentage * 100).toFixed(2); - - percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 - ? theme.palette.warning.main - : monitor.uptimePercentage < 0.75 - ? theme.palette.success.main - : theme.palette.success.main; - } - - return { - id: monitor._id, - name: monitor.name, - url: monitor.url, - title: monitor.name, - percentage: uptimePercentage, - percentageColor, - monitor: monitor, - }; - }, []); - - const fetchMonitors = useCallback(async () => { - try { - 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, filteredMonitors, summary } = res.data.data; - const mappedMonitors = filteredMonitors.map((monitor) => - getMonitorWithPercentage(monitor, theme) - ); - setMonitors(monitors); - setFilteredMonitors(mappedMonitors); - setMonitorsSummary(summary); - } catch (error) { - createToast({ - body: error.message, - }); - } finally { - setIsLoading(false); - setIsSearching(false); - } - }, [fetchParams, getMonitorWithPercentage, theme]); - - useEffect(() => { - fetchMonitors(); - }, [fetchMonitors, monitorUpdateTrigger]); - + // Handlers const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -140,103 +82,58 @@ const UptimeMonitors = () => { const triggerUpdate = useCallback(() => { setMonitorUpdateTrigger((prev) => !prev); }, []); + + const teamId = user.teamId; + + const { monitorsAreLoading, monitors, filteredMonitors, monitorsSummary } = + useMonitorFetch({ + authToken, + teamId, + limit: 25, + page, + rowsPerPage: rowsPerPage, + filter: search, + field: sort.field, + order: sort.order, + triggerUpdate: monitorUpdateTrigger, + }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; - const hasMonitors = totalMonitors > 0; - const canAddMonitor = isAdmin && hasMonitors; return ( - - - - {canAddMonitor && ( - <> - - - )} - - - - { - <> - {!isLoading && !hasMonitors && } - {isLoading ? ( - - ) : ( - hasMonitors && ( - <> - - - - - - - - - ) - )} - - } + + + + + + ); }; diff --git a/Client/src/Pages/Uptime/Home/skeleton.jsx b/Client/src/Pages/Uptime/Home/skeleton.jsx deleted file mode 100644 index 4e55be536..000000000 --- a/Client/src/Pages/Uptime/Home/skeleton.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Skeleton, Stack } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 86c0fb840..ccd8ec26f 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -436,7 +436,7 @@ class MonitorController { monitor.isActive = !monitor.isActive; monitor.status = undefined; monitor.save(); - return res.ssuccess({ + return res.success({ msg: monitor.isActive ? successMessages.MONITOR_RESUME : successMessages.MONITOR_PAUSE, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 4c60e0f22..cad9bd68f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -497,7 +497,7 @@ const getMonitorById = async (monitorId) => { const getMonitorsByTeamId = async (req) => { let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - + console.log("req.query", req.query); limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -512,8 +512,16 @@ const getMonitorsByTeamId = async (req) => { } const skip = page && rowsPerPage ? page * rowsPerPage : 0; - const sort = { [field]: order === "asc" ? 1 : -1 }; + + console.log("limit", limit); + console.log("page", page); + console.log("rowsPerPage", rowsPerPage); + console.log("filter", filter); + console.log("field", field); + console.log("order", order); + console.log("skip", skip); + console.log("sort", sort); const results = await Monitor.aggregate([ { $match: matchStage }, { @@ -675,6 +683,7 @@ const getMonitorsByTeamId = async (req) => { ]); let { monitors, filteredMonitors, summary } = results[0]; + console.log("filteredMonitors", filteredMonitors); filteredMonitors = filteredMonitors.map((monitor) => { if (!monitor.checks) { return monitor; @@ -839,3 +848,22 @@ export { groupChecksByTime, calculateGroupStats, }; + +// limit 25 +// page 1 +// rowsPerPage 25 +// filter undefined +// field name +// order asc +// skip 25 +// sort { name: 1 } +// filteredMonitors [] + +// limit 25 +// page NaN +// rowsPerPage 25 +// filter undefined +// field name +// order asc +// skip 0 +// sort { name: 1 } From be6d2f25ea29e984d43088ec33cc865a68333448 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 24 Jan 2025 16:22:14 -0800 Subject: [PATCH 004/106] rationalize host component --- .../Uptime/Home/Components/Host/index.jsx | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx index ec78875f4..9238f9889 100644 --- a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx @@ -1,5 +1,6 @@ -import { Box, Typography } from "@mui/material"; +import { Stack, Box, Typography } from "@mui/material"; import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; /** * Host component. * This subcomponent receives a params object and displays the host details. @@ -13,44 +14,43 @@ import PropTypes from "prop-types"; * @returns {React.ElementType} Returns a div element with the host details. */ const Host = ({ url, title, percentageColor, percentage }) => { - const noTitle = title === undefined || title === url; + const theme = useTheme(); + console.log(url, title); return ( - - + {title} - - {percentageColor && percentage && ( - - {percentage}% - - )} - {!noTitle && {url}} - + {percentageColor && percentage && ( + <> + + + {percentage}% + + + )} + + {url} + ); }; From 2818ce89affefd3c8db6cd36094de8b705bc354c Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Sun, 26 Jan 2025 22:32:04 +0800 Subject: [PATCH 005/106] Adjust count box style --- Client/src/Pages/Infrastructure/index.jsx | 2 +- Client/src/Pages/PageSpeed/index.jsx | 10 ++++++++-- Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index fbfae6a3c..e39d92347 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -243,7 +243,7 @@ function Infrastructure() { backgroundColor={theme.palette.tertiary.main} sx={{ padding: ".25em .75em", - borderRadius: "50%", + borderRadius: "calc(infinity * 1px)", fontSize: "12px", fontWeight: 500, }} diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index c15ed5296..7fb45d479 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -106,9 +106,15 @@ const PageSpeed = () => { component="span" className="current-monitors-counter" color={theme.palette.primary.contrastText} - border={1} - borderColor={theme.palette.primary.lowContrast} + border={2} + borderColor={theme.palette.accent.main} backgroundColor={theme.palette.tertiary.main} + sx={{ + padding: ".25em .75em", + borderRadius: "calc(infinity * 1px)", + fontSize: "12px", + fontWeight: 500, + }} > {summary?.totalMonitors ?? 0} diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index 6961c6d5c..e108f6904 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -234,7 +234,7 @@ const UptimeDataTable = ({ backgroundColor={theme.palette.tertiary.main} sx={{ padding: ".25em .75em", - borderRadius: "50%", + borderRadius: "calc(infinity * 1px)", fontSize: "12px", fontWeight: 500, }} From 086a900613b838fb9bec5bde35b5ac9fe6bf9a25 Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Sun, 26 Jan 2025 22:48:45 +0800 Subject: [PATCH 006/106] Fix Error Component Stack --- Client/src/Pages/Infrastructure/index.jsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index e39d92347..0246537df 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -137,13 +137,11 @@ function Infrastructure() { id: "actions", content: "Actions", render: (row) => ( - - - + ), }, ]; From 000bb9929cfce4da1c366931e053d11185b04fb7 Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Sun, 26 Jan 2025 23:05:18 +0800 Subject: [PATCH 007/106] Fix sidebar sticky error --- Client/src/Pages/Infrastructure/Details/index.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index acd1bafb1..a4270297d 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -625,8 +625,7 @@ const InfrastructureDetails = () => { Date: Sun, 26 Jan 2025 21:37:52 +0000 Subject: [PATCH 008/106] fix(deps): update dependency @reduxjs/toolkit to v2.5.1 --- Client/package-lock.json | 8 ++++---- Client/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index abbc57323..7d2a2427c 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -17,7 +17,7 @@ "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.24.1", "@mui/x-date-pickers": "7.24.1", - "@reduxjs/toolkit": "2.5.0", + "@reduxjs/toolkit": "2.5.1", "axios": "^1.7.4", "dayjs": "1.11.13", "joi": "17.13.3", @@ -1691,9 +1691,9 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz", - "integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.1.tgz", + "integrity": "sha512-UHhy3p0oUpdhnSxyDjaRDYaw8Xra75UiLbCiRozVPHjfDwNYkh0TsVm/1OmTW8Md+iDAJmYPWUKMvsMc2GtpNg==", "license": "MIT", "dependencies": { "immer": "^10.0.3", diff --git a/Client/package.json b/Client/package.json index 006bdd081..1e2647a03 100644 --- a/Client/package.json +++ b/Client/package.json @@ -20,7 +20,7 @@ "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.24.1", "@mui/x-date-pickers": "7.24.1", - "@reduxjs/toolkit": "2.5.0", + "@reduxjs/toolkit": "2.5.1", "axios": "^1.7.4", "dayjs": "1.11.13", "joi": "17.13.3", From 3193ede1b192a9e2904e1de733632a9da18c31eb Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:15:00 -0500 Subject: [PATCH 009/106] Add port monitoring --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c37adfc6a..3dd59ef8f 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ If you have any questions, suggestions or comments, please use our [Discord chan - Docker monitoring - Ping monitoring - SSL monitoring +- Port monitoring - Incidents at a glance - E-mail notifications - Scheduled maintenance **Short term roadmap:** -- Port monitoring (**complete**, waiting to be deployed to stable version) https://github.com/bluewave-labs/Checkmate/issues/1476 - Global (distributed) uptime checking on Solana network (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1593 - Status pages (**in progress**) https://github.com/bluewave-labs/Checkmate/issues/1131 - Translations (i18n) (**in progress**) From 9a250e14603e29731145bba8d6f120c21470401d Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Mon, 27 Jan 2025 08:21:46 +0800 Subject: [PATCH 010/106] Use 10000px instead of infinity --- Client/src/Pages/Infrastructure/index.jsx | 2 +- Client/src/Pages/PageSpeed/index.jsx | 2 +- Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index 0246537df..f9abb16f3 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -241,7 +241,7 @@ function Infrastructure() { backgroundColor={theme.palette.tertiary.main} sx={{ padding: ".25em .75em", - borderRadius: "calc(infinity * 1px)", + borderRadius: "10000px", fontSize: "12px", fontWeight: 500, }} diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx index 7fb45d479..c3d37e2c0 100644 --- a/Client/src/Pages/PageSpeed/index.jsx +++ b/Client/src/Pages/PageSpeed/index.jsx @@ -111,7 +111,7 @@ const PageSpeed = () => { backgroundColor={theme.palette.tertiary.main} sx={{ padding: ".25em .75em", - borderRadius: "calc(infinity * 1px)", + borderRadius: "10000px", fontSize: "12px", fontWeight: 500, }} diff --git a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx index e108f6904..29d276159 100644 --- a/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/UptimeDataTable/index.jsx @@ -234,7 +234,7 @@ const UptimeDataTable = ({ backgroundColor={theme.palette.tertiary.main} sx={{ padding: ".25em .75em", - borderRadius: "calc(infinity * 1px)", + borderRadius: "10000px", fontSize: "12px", fontWeight: 500, }} From aa0f0621f909b1e5ff2cb16379b8b0e0583b3932 Mon Sep 17 00:00:00 2001 From: Jasneet Singh Date: Sun, 26 Jan 2025 20:50:12 -0500 Subject: [PATCH 011/106] Move "Checks to perform" to the top of "General settings" --- .../src/Pages/Uptime/CreateUptime/index.jsx | 93 +++++++++---------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/Client/src/Pages/Uptime/CreateUptime/index.jsx b/Client/src/Pages/Uptime/CreateUptime/index.jsx index 762d8e764..9086c87af 100644 --- a/Client/src/Pages/Uptime/CreateUptime/index.jsx +++ b/Client/src/Pages/Uptime/CreateUptime/index.jsx @@ -235,53 +235,6 @@ const CreateMonitor = () => { monitor - - - - General settings - - Here you can select the URL of the host, together with the type of monitor. - - - - : null - } - label={monitorTypeMaps[monitor.type].label || "URL to monitor"} - https={https} - placeholder={monitorTypeMaps[monitor.type].placeholder || ""} - value={monitor.url} - onChange={(event) => handleChange(event, "url")} - error={errors["url"] ? true : false} - helperText={errors["url"]} - /> - handleChange(event, "port")} - error={errors["port"] ? true : false} - helperText={errors["port"]} - hidden={monitor.type !== "port"} - /> - handleChange(event, "name")} - error={errors["name"] ? true : false} - helperText={errors["name"]} - /> - - Checks to perform @@ -363,6 +316,52 @@ const CreateMonitor = () => { )} + + + General settings + + Here you can select the URL of the host, together with the type of monitor. + + + + : null + } + label={monitorTypeMaps[monitor.type].label || "URL to monitor"} + https={https} + placeholder={monitorTypeMaps[monitor.type].placeholder || ""} + value={monitor.url} + onChange={(event) => handleChange(event, "url")} + error={errors["url"] ? true : false} + helperText={errors["url"]} + /> + handleChange(event, "port")} + error={errors["port"] ? true : false} + helperText={errors["port"]} + hidden={monitor.type !== "port"} + /> + handleChange(event, "name")} + error={errors["name"] ? true : false} + helperText={errors["name"]} + /> + + Incident notifications From 22cae67ffef202892d0e4cb235a99232645dc8e0 Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Mon, 27 Jan 2025 11:37:44 +0800 Subject: [PATCH 012/106] Fix redundant instantiation of NetworkService --- Client/src/Utils/NetworkServiceProvider.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Client/src/Utils/NetworkServiceProvider.jsx b/Client/src/Utils/NetworkServiceProvider.jsx index 9fb65f1ca..12eab48a9 100644 --- a/Client/src/Utils/NetworkServiceProvider.jsx +++ b/Client/src/Utils/NetworkServiceProvider.jsx @@ -1,14 +1,16 @@ import { useDispatch } from "react-redux"; import { useNavigate } from "react-router"; import { setNetworkService } from "./NetworkService"; -import NetworkService from "./NetworkService"; +import NetworkService, { networkService } from "./NetworkService"; import { store } from "../store"; const NetworkServiceProvider = ({ children }) => { const dispatch = useDispatch(); const navigate = useNavigate(); - const networkService = new NetworkService(store, dispatch, navigate); - setNetworkService(networkService); + if (!networkService) { + const networkService = new NetworkService(store, dispatch, navigate); + setNetworkService(networkService); + } return children; }; From 7c889dde4788d904218ba90f612e30348a0e7905 Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Mon, 27 Jan 2025 12:21:46 +0800 Subject: [PATCH 013/106] Avoid shadow the networkService --- Client/src/Utils/NetworkServiceProvider.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/src/Utils/NetworkServiceProvider.jsx b/Client/src/Utils/NetworkServiceProvider.jsx index 12eab48a9..684152e69 100644 --- a/Client/src/Utils/NetworkServiceProvider.jsx +++ b/Client/src/Utils/NetworkServiceProvider.jsx @@ -8,8 +8,7 @@ const NetworkServiceProvider = ({ children }) => { const dispatch = useDispatch(); const navigate = useNavigate(); if (!networkService) { - const networkService = new NetworkService(store, dispatch, navigate); - setNetworkService(networkService); + setNetworkService(new NetworkService(store, dispatch, navigate)); } return children; }; From e19dffb186d61594732a1ed56687275cb4003d3c Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Mon, 27 Jan 2025 12:50:53 +0800 Subject: [PATCH 014/106] Fix infrastructure white page error cause by theme color --- Client/src/Pages/Infrastructure/index.jsx | 14 ++++++-------- Client/src/Pages/Uptime/Home/index.jsx | 4 +--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index f9abb16f3..869d0d13c 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -156,14 +156,12 @@ function Infrastructure() { const uptimePercentage = ((monitor?.uptimePercentage ?? 0) * 100) .toFixed(2) .toString(); - const percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 - ? theme.palette.percentage.uptimeFair - : monitor.uptimePercentage < 0.75 - ? theme.palette.percentage.uptimeGood - : theme.palette.success.lowContrast; + const percentageColor = monitor.uptimePercentage < 0.25 + ? theme.palette.error.main + : monitor.uptimePercentage < 0.5 + ? theme.palette.warning.main + : theme.palette.success.main; + return { id: monitor._id, name: monitor.name, diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 446702ebb..ae4213a7f 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -71,9 +71,7 @@ const UptimeMonitors = () => { ? theme.palette.error.main : monitor.uptimePercentage < 0.5 ? theme.palette.warning.main - : monitor.uptimePercentage < 0.75 - ? theme.palette.success.main - : theme.palette.success.main; + : theme.palette.success.main; } return { From 4624a51a80a55401fdf93f6f6c684a244be576d5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 07:11:19 -0800 Subject: [PATCH 015/106] remove console.log --- Client/src/Pages/Uptime/Home/Components/Host/index.jsx | 1 - .../src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx | 1 - 2 files changed, 2 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx index 9238f9889..91b7c418b 100644 --- a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx @@ -15,7 +15,6 @@ import { useTheme } from "@emotion/react"; */ const Host = ({ url, title, percentageColor, percentage }) => { const theme = useTheme(); - console.log(url, title); return ( { ), }, ]; - console.log("rendering"); return ( Date: Mon, 27 Jan 2025 07:15:07 -0800 Subject: [PATCH 016/106] remove console log --- Server/controllers/distributedUptimeController.js | 1 - Server/db/mongo/modules/monitorModule.js | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/Server/controllers/distributedUptimeController.js b/Server/controllers/distributedUptimeController.js index ce4ee2df5..5215c2cce 100644 --- a/Server/controllers/distributedUptimeController.js +++ b/Server/controllers/distributedUptimeController.js @@ -8,7 +8,6 @@ class DistributedUptimeController { async resultsCallback(req, res, next) { try { - console.log(req.body); res.status(200).json({ message: "OK" }); } catch (error) { throw handleError(error, SERVICE_NAME, "resultsCallback"); diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index cad9bd68f..fdbd86477 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -497,7 +497,6 @@ const getMonitorById = async (monitorId) => { const getMonitorsByTeamId = async (req) => { let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - console.log("req.query", req.query); limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -513,15 +512,6 @@ const getMonitorsByTeamId = async (req) => { const skip = page && rowsPerPage ? page * rowsPerPage : 0; const sort = { [field]: order === "asc" ? 1 : -1 }; - - console.log("limit", limit); - console.log("page", page); - console.log("rowsPerPage", rowsPerPage); - console.log("filter", filter); - console.log("field", field); - console.log("order", order); - console.log("skip", skip); - console.log("sort", sort); const results = await Monitor.aggregate([ { $match: matchStage }, { From 3259425c64f8b2405649cd9188f775ee6c684384 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 07:18:02 -0800 Subject: [PATCH 017/106] update border radius of circular count --- Client/src/Components/CircularCount/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Components/CircularCount/index.jsx b/Client/src/Components/CircularCount/index.jsx index 5c09ad162..8350a8c44 100644 --- a/Client/src/Components/CircularCount/index.jsx +++ b/Client/src/Components/CircularCount/index.jsx @@ -12,7 +12,7 @@ const CircularCount = ({ count }) => { backgroundColor={theme.palette.tertiary.main} sx={{ padding: ".25em .75em", - borderRadius: "50%", + borderRadius: "50rem", fontSize: "12px", fontWeight: 500, }} From 65b104f38ab318bdeac5acbb3e604792a2951c94 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 07:27:53 -0800 Subject: [PATCH 018/106] remove console log, add back commented out container height --- Client/src/Pages/Infrastructure/Details/index.jsx | 3 ++- Server/db/mongo/modules/monitorModule.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index cb408b9f2..07fbd7c06 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -625,7 +625,8 @@ const InfrastructureDetails = () => { { ]); let { monitors, filteredMonitors, summary } = results[0]; - console.log("filteredMonitors", filteredMonitors); filteredMonitors = filteredMonitors.map((monitor) => { if (!monitor.checks) { return monitor; From dba266bcd2d826897513e6e89eff1c6d8fe6dd5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20=C5=9Ei=C5=9Fmano=C4=9Flu?= Date: Mon, 27 Jan 2025 19:25:35 +0300 Subject: [PATCH 019/106] chore(npm): Remove package-lock.json and add '.npmrc' to disable lockfile generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mert Şişmanoğlu --- .npmrc | 1 + package-lock.json | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 .npmrc delete mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index f71a78c1f..000000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "bluewave-uptime", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From 7292af1902135a734636a14459b90d4c06e58fd1 Mon Sep 17 00:00:00 2001 From: Aryaman Kumar Sharma Date: Mon, 27 Jan 2025 22:48:14 +0530 Subject: [PATCH 020/106] Fix: Update UptimeDataTable to reflect HTTP(s) monitor types --- .../Pages/Uptime/Home/Components/UptimeDataTable/index.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx index 4ab605587..936a6ebed 100644 --- a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx @@ -190,7 +190,9 @@ const UptimeDataTable = (props) => { id: "type", content: "Type", render: (row) => ( - {row.monitor.type} + + {row.monitor.type === "http" ? "HTTP(s)" : row.monitor.type} + ), }, { From d65a5d001a58ed3bff553fba2c5c169ce10f586e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 09:36:39 -0800 Subject: [PATCH 021/106] Add status param to getChecks requests --- Client/src/Utils/NetworkService.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index c73890218..d040ff2fd 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -614,6 +614,7 @@ class NetworkService { if (config.filter) params.append("filter", config.filter); if (config.page) params.append("page", config.page); if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); + if (config.status !== undefined) params.append("status", config.status); return this.axiosInstance.get(`/checks/${config.monitorId}?${params.toString()}`, { headers: { Authorization: `Bearer ${config.authToken}` }, @@ -646,6 +647,7 @@ class NetworkService { if (config.filter) params.append("filter", config.filter); if (config.page) params.append("page", config.page); if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); + if (config.status !== undefined) params.append("status", config.status); return this.axiosInstance.get(`/checks/team/${config.teamId}?${params.toString()}`, { headers: { Authorization: `Bearer ${config.authToken}` }, }); From c7c9835e9c4d0236468f1077c43d23228b865908 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 09:36:52 -0800 Subject: [PATCH 022/106] Add validation for status param --- Server/validation/joi.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 8f366a113..9f20eb0d1 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -292,6 +292,7 @@ const getChecksQueryValidation = joi.object({ filter: joi.string().valid("all", "down", "resolve"), page: joi.number(), rowsPerPage: joi.number(), + status: joi.boolean(), }); const getTeamChecksParamValidation = joi.object({ @@ -305,6 +306,7 @@ const getTeamChecksQueryValidation = joi.object({ filter: joi.string().valid("all", "down", "resolve"), page: joi.number(), rowsPerPage: joi.number(), + status: joi.boolean(), }); const deleteChecksParamValidation = joi.object({ From cb624f1f294259877d25297953d27fd4482b4b69 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 09:37:29 -0800 Subject: [PATCH 023/106] conditionally add status to the match stage of the aggreagation pipeline --- Server/db/mongo/modules/checkModule.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index 7965e7c30..3d158a812 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -70,13 +70,14 @@ const createCheck = async (checkData) => { const getChecksByMonitor = async (req) => { try { const { monitorId } = req.params; - let { sortOrder, dateRange, filter, page, rowsPerPage } = req.query; + let { sortOrder, dateRange, filter, page, rowsPerPage, status } = req.query; + status = typeof status !== "undefined" ? false : undefined; page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); // Match const matchStage = { monitorId: ObjectId.createFromHexString(monitorId), - status: false, + ...(typeof status !== "undefined" && { status }), ...(dateRangeLookup[dateRange] && { createdAt: { $gte: dateRangeLookup[dateRange], @@ -111,7 +112,6 @@ const getChecksByMonitor = async (req) => { if (page && rowsPerPage) { skip = page * rowsPerPage; } - const checks = await Check.aggregate([ { $match: matchStage }, { $sort: { createdAt: sortOrder } }, From 875c13b5feb92d5d8506dcdb02daa801c3bff8c4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 09:37:39 -0800 Subject: [PATCH 024/106] Update incident table queries --- Client/src/Pages/Incidents/IncidentTable/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index ae9a3ae2a..15d65cadd 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -39,6 +39,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter, dateRange }) => { if (selectedMonitor === "0") { res = await networkService.getChecksByTeam({ authToken: authToken, + status: false, teamId: user.teamId, sortOrder: "desc", limit: null, @@ -50,6 +51,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter, dateRange }) => { } else { res = await networkService.getChecksByMonitor({ authToken: authToken, + status: false, monitorId: selectedMonitor, sortOrder: "desc", limit: null, From 94084acd865a32ea93109f1c03689475c87a53e6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 10:00:58 -0800 Subject: [PATCH 025/106] iniital commit --- ...eMonitorFetch.jsx => useMonitorsFetch.jsx} | 0 Client/src/Pages/Uptime/Home/index.jsx | 4 +- .../NewDetails/Hooks/useCertificateFetch.jsx | 45 +++++++++++++++++ .../NewDetails/Hooks/useMonitorFetch.jsx | 34 +++++++++++++ Client/src/Pages/Uptime/NewDetails/index.jsx | 48 +++++++++++++++++++ Client/src/Routes/index.jsx | 4 +- 6 files changed, 132 insertions(+), 3 deletions(-) rename Client/src/Pages/Uptime/Home/Hooks/{useMonitorFetch.jsx => useMonitorsFetch.jsx} (100%) create mode 100644 Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/Home/Hooks/useMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Hooks/useMonitorFetch.jsx rename to Client/src/Pages/Uptime/Home/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index c300118e0..2427d7e61 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -13,7 +13,7 @@ import { useState, useCallback } from "react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import useMonitorFetch from "./Hooks/useMonitorFetch"; +import useMonitorsFetch from "./Hooks/useMonitorsFetch"; import { useSelector, useDispatch } from "react-redux"; import { setRowsPerPage } from "../../../Features/UI/uiSlice"; import PropTypes from "prop-types"; @@ -86,7 +86,7 @@ const UptimeMonitors = () => { const teamId = user.teamId; const { monitorsAreLoading, monitors, filteredMonitors, monitorsSummary } = - useMonitorFetch({ + useMonitorsFetch({ authToken, teamId, limit: 25, diff --git a/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx b/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx new file mode 100644 index 000000000..33d2d71c5 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx @@ -0,0 +1,45 @@ +import { logger } from "../../../../Utils/Logger"; +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { formatDateWithTz } from "../../../../Utils/DateUtils"; + +const useCertificateFetch = ({ + monitor, + authToken, + monitorId, + certificateDateFormat, + uiTimezone, +}) => { + const [certificateExpiry, setCertificateExpiry] = useState("N/A"); + const [certificateIsLoading, setCertificateIsLoading] = useState(false); + + useEffect(() => { + const fetchCertificate = async () => { + if (monitor?.type !== "http") { + return; + } + + try { + setCertificateIsLoading(true); + const res = await networkService.getCertificateExpiry({ + authToken: authToken, + monitorId: monitorId, + }); + if (res?.data?.data?.certificateDate) { + const date = res.data.data.certificateDate; + setCertificateExpiry( + formatDateWithTz(date, certificateDateFormat, uiTimezone) ?? "N/A" + ); + } + } catch (error) { + setCertificateExpiry("N/A"); + logger.error(error); + } finally { + setCertificateIsLoading(false); + } + }; + }, [authToken, monitorId]); + return { certificateExpiry, certificateIsLoading }; +}; + +export default useCertificateFetch; diff --git a/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx new file mode 100644 index 000000000..e7ed825f2 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { logger } from "../../../../Utils/Logger"; +import { useNavigate } from "react-router-dom"; + +export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => { + const [monitorIsLoading, setMonitorsIsLoading] = useState(false); + const [monitor, setMonitor] = useState([]); + const navigate = useNavigate(); + + useEffect(() => { + const fetchMonitors = async () => { + try { + setMonitorsIsLoading(true); + const res = await networkService.getUptimeDetailsById({ + authToken: authToken, + monitorId: monitorId, + dateRange: dateRange, + normalize: true, + }); + setMonitor(res?.data?.data ?? {}); + } catch (error) { + logger.error(error); + navigate("/not-found", { replace: true }); + } finally { + setMonitorsIsLoading(false); + } + }; + fetchMonitors(); + }, [authToken, dateRange, monitorId, navigate]); + return { monitor, monitorIsLoading }; +}; + +export default useMonitorFetch; diff --git a/Client/src/Pages/Uptime/NewDetails/index.jsx b/Client/src/Pages/Uptime/NewDetails/index.jsx new file mode 100644 index 000000000..3467f8228 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/index.jsx @@ -0,0 +1,48 @@ +// Components +import Breadcrumbs from "../../../Components/Breadcrumbs"; + +// MUI Components +import { Stack } from "@mui/material"; + +// Utils +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useMonitorFetch } from "./Hooks/useMonitorFetch"; +// Constants +const BREADCRUMBS = [ + { name: "uptime", path: "/uptime" }, + { name: "details", path: "" }, + // { name: "details", path: `/uptime/${monitorId}` }, Is this needed? We can't click on this anywy +]; + +const certificateDateFormat = "MMM D, YYYY h A"; + +const UptimeDetails = () => { + // Redux state + const { authToken } = useSelector((state) => state.auth); + const uiTimezone = useSelector((state) => state.ui.timezone); + + // Local state + const [dateRange, setDateRange] = useState("day"); + + // Utils + const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D"; + const { monitorId } = useParams(); + + const { monitor, monitorIsLoading } = useMonitorFetch({ + authToken, + monitorId, + dateRange, + }); + + console.log(monitor); + + return ( + + + + ); +}; + +export default UptimeDetails; diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index b5f4dd376..6ac5624a9 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -19,6 +19,8 @@ import SetNewPassword from "../Pages/Auth/SetNewPassword"; import NewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; import ProtectedRoute from "../Components/ProtectedRoute"; import Details from "../Pages/Uptime/Details"; + +import UptimeDetails from "../Pages/Uptime/NewDetails"; import Maintenance from "../Pages/Maintenance"; import Configure from "../Pages/Uptime/Configure"; import PageSpeed from "../Pages/PageSpeed"; @@ -55,7 +57,7 @@ const Routes = () => { /> } + element={} /> Date: Mon, 27 Jan 2025 10:05:17 -0800 Subject: [PATCH 026/106] Add certificate expiry hook --- .../NewDetails/Hooks/useCertificateFetch.jsx | 5 +++-- Client/src/Pages/Uptime/NewDetails/index.jsx | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx b/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx index 33d2d71c5..9e1c3b44f 100644 --- a/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx +++ b/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx @@ -1,7 +1,7 @@ import { logger } from "../../../../Utils/Logger"; import { useEffect, useState } from "react"; import { networkService } from "../../../../main"; -import { formatDateWithTz } from "../../../../Utils/DateUtils"; +import { formatDateWithTz } from "../../../../Utils/timeUtils"; const useCertificateFetch = ({ monitor, @@ -38,7 +38,8 @@ const useCertificateFetch = ({ setCertificateIsLoading(false); } }; - }, [authToken, monitorId]); + fetchCertificate(); + }, [authToken, monitorId, certificateDateFormat, uiTimezone, monitor]); return { certificateExpiry, certificateIsLoading }; }; diff --git a/Client/src/Pages/Uptime/NewDetails/index.jsx b/Client/src/Pages/Uptime/NewDetails/index.jsx index 3467f8228..5494146e6 100644 --- a/Client/src/Pages/Uptime/NewDetails/index.jsx +++ b/Client/src/Pages/Uptime/NewDetails/index.jsx @@ -5,10 +5,11 @@ import Breadcrumbs from "../../../Components/Breadcrumbs"; import { Stack } from "@mui/material"; // Utils -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; -import { useMonitorFetch } from "./Hooks/useMonitorFetch"; +import useMonitorFetch from "./Hooks/useMonitorFetch"; +import useCertificateFetch from "./Hooks/useCertificateFetch"; // Constants const BREADCRUMBS = [ { name: "uptime", path: "/uptime" }, @@ -36,8 +37,16 @@ const UptimeDetails = () => { dateRange, }); - console.log(monitor); + const { certificateExpiry, certificateIsLoading } = useCertificateFetch({ + monitor, + authToken, + monitorId, + certificateDateFormat, + uiTimezone, + }); + console.log(monitor); + console.log(certificateExpiry); return ( From 66e61fc8be568679fc930e4f30c802facaa2c086 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 13:16:26 -0800 Subject: [PATCH 027/106] Add Dot component --- Client/src/Components/Dot/index.jsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Client/src/Components/Dot/index.jsx diff --git a/Client/src/Components/Dot/index.jsx b/Client/src/Components/Dot/index.jsx new file mode 100644 index 000000000..0fd51730f --- /dev/null +++ b/Client/src/Components/Dot/index.jsx @@ -0,0 +1,23 @@ +import PropTypes from "prop-types"; + +const Dot = ({ color = "gray", size = "4px" }) => { + return ( + + ); +}; + +Dot.propTypes = { + color: PropTypes.string, + size: PropTypes.string, +}; + +export default Dot; From 5622b3b47eeb5f96bf7e3972b99f47b2ead9aded Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 13:17:05 -0800 Subject: [PATCH 028/106] use dayjs to parse dates --- Client/src/Utils/timeUtils.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 1a84d0ea5..2fd6c6654 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -1,4 +1,5 @@ import dayjs from "dayjs"; +import duration from "dayjs/plugin/duration"; import utc from "dayjs/plugin/utc"; import timezone from "dayjs/plugin/timezone"; import customParseFormat from "dayjs/plugin/customParseFormat"; @@ -6,6 +7,7 @@ import customParseFormat from "dayjs/plugin/customParseFormat"; dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(customParseFormat); +dayjs.extend(duration); export const MS_PER_SECOND = 1000; export const MS_PER_MINUTE = 60 * MS_PER_SECOND; @@ -75,6 +77,23 @@ export const formatDurationSplit = (ms) => { : { time: 0, format: "seconds" }; }; +export const getHumanReadableDuration = (ms) => { + const durationObj = dayjs.duration(ms); + if (durationObj.asDays() >= 1) { + const days = Math.floor(durationObj.asDays()); + return { time: days, units: days === 1 ? "day" : "days" }; + } else if (durationObj.asHours() >= 1) { + const hours = Math.floor(durationObj.asHours()); + return { time: hours, units: hours === 1 ? "hour" : "hours" }; + } else if (durationObj.asMinutes() >= 1) { + const minutes = Math.floor(durationObj.asMinutes()); + return { time: minutes, units: minutes === 1 ? "minute" : "minutes" }; + } else { + const seconds = Math.floor(durationObj.asSeconds()); + return { time: seconds, units: seconds === 1 ? "second" : "seconds" }; + } +}; + export const formatDate = (date, customOptions) => { const options = { year: "numeric", From 9381429f40daf50d59b586ee7a38fe2b5e7432a7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 13:17:23 -0800 Subject: [PATCH 029/106] Use Dot component in host --- .../Pages/Uptime/Home/Components/Host/index.jsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx index 91b7c418b..c2f082f3a 100644 --- a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/Host/index.jsx @@ -1,6 +1,7 @@ -import { Stack, Box, Typography } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; +import Dot from "../../../../../Components/Dot"; /** * Host component. * This subcomponent receives a params object and displays the host details. @@ -26,16 +27,7 @@ const Host = ({ url, title, percentageColor, percentage }) => { {title} {percentageColor && percentage && ( <> - + Date: Mon, 27 Jan 2025 13:17:48 -0800 Subject: [PATCH 030/106] Consistent spacing in home page --- Client/src/Pages/Uptime/Home/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Home/index.jsx index 2427d7e61..ec9e7d71c 100644 --- a/Client/src/Pages/Uptime/Home/index.jsx +++ b/Client/src/Pages/Uptime/Home/index.jsx @@ -102,7 +102,7 @@ const UptimeMonitors = () => { return ( From 671d297c2d5368f90cda43407941d2b1b1d73720 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 13:18:05 -0800 Subject: [PATCH 031/106] refactor details --- .../Components/ChartBoxes/index.jsx | 134 ++++++++++++++++++ .../NewDetails/Components/Charts/ChartBox.jsx | 74 ++++++++++ .../Components/Charts/CustomLabels.jsx | 42 ++++++ .../Components/Charts/DownBarChart.jsx | 92 ++++++++++++ .../Components/Charts/ResponseGaugeChart.jsx | 117 +++++++++++++++ .../Components/Charts/ResponseTimeChart.jsx | 18 +++ .../Components/Charts/UpBarChart.jsx | 109 ++++++++++++++ .../Components/ConfigButton/index.jsx | 35 +++++ .../Components/MonitorHeader/index.jsx | 43 ++++++ .../Components/ResponseTable/index.jsx | 0 .../Components/StatusBoxes/index.jsx | 72 ++++++++++ .../Components/TimeFramePicker/index.jsx | 44 ++++++ .../NewDetails/Hooks/useMonitorFetch.jsx | 2 +- Client/src/Pages/Uptime/NewDetails/index.jsx | 37 ++++- 14 files changed, 814 insertions(+), 5 deletions(-) create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/CustomLabels.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/DownBarChart.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/UpBarChart.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/ConfigButton/index.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/ResponseTable/index.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx create mode 100644 Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx diff --git a/Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx new file mode 100644 index 000000000..afe93d028 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx @@ -0,0 +1,134 @@ +// Components +import { Stack, Typography, Box } from "@mui/material"; +import ChartBox from "../Charts/ChartBox"; +import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react"; +import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react"; +import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react"; +import UpBarChart from "../Charts/UpBarChart"; +import DownBarChart from "../Charts/DownBarChart"; +import ResponseGaugeChart from "../Charts/ResponseGaugeChart"; + +// Utils +import { formatDateWithTz } from "../../../../../Utils/timeUtils"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; + +const ChartBoxes = ({ + monitor, + dateRange, + uiTimezone, + dateFormat, + hoveredUptimeData, + setHoveredUptimeData, + hoveredIncidentsData, + setHoveredIncidentsData, +}) => { + const theme = useTheme(); + return ( + + } + header="Uptime" + > + + + Total Checks + + {hoveredUptimeData !== null + ? hoveredUptimeData.totalChecks + : (monitor?.groupedUpChecks?.reduce((count, checkGroup) => { + return count + checkGroup.totalChecks; + }, 0) ?? 0)} + + {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( + + {formatDateWithTz(hoveredUptimeData._id, dateFormat, uiTimezone)} + + )} + + + + {hoveredUptimeData !== null ? "Avg Response Time" : "Uptime Percentage"} + + + {hoveredUptimeData !== null + ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) + : Math.floor( + ((monitor?.upChecks?.totalChecks ?? 0) / + (monitor?.totalChecks ?? 1)) * + 100 + )} + + {hoveredUptimeData !== null ? " ms" : " %"} + + + + + + + } + header="Incidents" + > + + + {hoveredIncidentsData !== null + ? hoveredIncidentsData.totalChecks + : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { + return count + checkGroup.totalChecks; + }, 0) ?? 0)} + + {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( + + {formatDateWithTz(hoveredIncidentsData._id, dateFormat, uiTimezone)} + + )} + + + + } + header="Average Response Time" + > + + + + ); +}; + +export default ChartBoxes; + +ChartBoxes.propTypes = { + monitor: PropTypes.object.isRequired, + dateRange: PropTypes.string.isRequired, + uiTimezone: PropTypes.string.isRequired, + dateFormat: PropTypes.string.isRequired, + hoveredUptimeData: PropTypes.object, + setHoveredUptimeData: PropTypes.func, + hoveredIncidentsData: PropTypes.object, + setHoveredIncidentsData: PropTypes.func, +}; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx new file mode 100644 index 000000000..e62949004 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx @@ -0,0 +1,74 @@ +import { Stack, Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import IconBox from "../../../../../Components/IconBox"; +import PropTypes from "prop-types"; +const ChartBox = ({ children, icon, header }) => { + const theme = useTheme(); + return ( + span": { + color: theme.palette.primary.contrastText, + fontSize: 20, + "& span": { + opacity: 0.8, + marginLeft: 2, + fontSize: 15, + }, + }, + "& .MuiStack-root": { + flexDirection: "row", + gap: theme.spacing(6), + }, + "& .MuiStack-root:first-of-type": { + alignItems: "center", + }, + "& tspan, & text": { + fill: theme.palette.primary.contrastTextTertiary, + }, + "& path": { + transition: "fill 300ms ease, stroke-width 400ms ease", + }, + }} + > + + {icon} + {header} + + + {children} + + ); +}; + +export default ChartBox; + +ChartBox.propTypes = { + children: PropTypes.node, + icon: PropTypes.node.isRequired, + header: PropTypes.string.isRequired, +}; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/CustomLabels.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/CustomLabels.jsx new file mode 100644 index 000000000..8df33953f --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/Charts/CustomLabels.jsx @@ -0,0 +1,42 @@ +import PropTypes from "prop-types"; +import { useSelector } from "react-redux"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils"; + +const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { + const uiTimezone = useSelector((state) => state.ui.timezone); + const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; + + return ( + <> + + {formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)} + + + {formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)} + + + ); +}; + +CustomLabels.propTypes = { + x: PropTypes.number.isRequired, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + firstDataPoint: PropTypes.object, + lastDataPoint: PropTypes.object, + type: PropTypes.string.isRequired, +}; + +export default CustomLabels; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/DownBarChart.jsx new file mode 100644 index 000000000..dcd3a06be --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/Charts/DownBarChart.jsx @@ -0,0 +1,92 @@ +import { memo, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; +import PropTypes from "prop-types"; +import CustomLabels from "./CustomLabels"; + +const DownBarChart = memo(({ monitor, type, onBarHover }) => { + const theme = useTheme(); + + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + + return ( + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: 0 }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > + + } + /> + + {monitor?.groupedDownChecks?.map((entry, index) => { + return ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalChecks: 0 }); + }} + /> + ); + })} + + + + ); +}); + +DownBarChart.displayName = "DownBarChart"; +DownBarChart.propTypes = { + monitor: PropTypes.shape({ + groupedDownChecks: PropTypes.arrayOf(PropTypes.object), + }), + type: PropTypes.string, + onBarHover: PropTypes.func, +}; +export default DownBarChart; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx new file mode 100644 index 000000000..e94af8441 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx @@ -0,0 +1,117 @@ +import PropTypes from "prop-types"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts"; + +const ResponseGaugeChart = ({ avgResponseTime }) => { + const theme = useTheme(); + + let max = 1000; // max ms + + const data = [ + { response: max, fill: "transparent", background: false }, + { response: avgResponseTime, background: true }, + ]; + let responseTime = Math.floor(avgResponseTime); + let responseProps = + responseTime <= 200 + ? { + category: "Excellent", + main: theme.palette.success.main, + bg: theme.palette.success.contrastText, + } + : responseTime <= 500 + ? { + category: "Fair", + main: theme.palette.success.main, + bg: theme.palette.success.contrastText, + } + : responseTime <= 600 + ? { + category: "Acceptable", + main: theme.palette.warning.main, + bg: theme.palette.warning.lowContrast, + } + : { + category: "Poor", + main: theme.palette.error.main, + bg: theme.palette.error.contrastText, + }; + + return ( + + + + low + + + high + + + {responseProps.category} + + + {responseTime} ms + + + + + + + + ); +}; + +ResponseGaugeChart.propTypes = { + avgResponseTime: PropTypes.number.isRequired, +}; + +export default ResponseGaugeChart; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx new file mode 100644 index 000000000..c28163394 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx @@ -0,0 +1,18 @@ +import ChartBox from "./ChartBox"; +import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart"; +import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; +const ResponseTImeChart = ({ monitor, dateRange }) => { + return ( + } + header="Response Times" + > + + + ); +}; + +export default ResponseTImeChart; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/UpBarChart.jsx new file mode 100644 index 000000000..1df79c26e --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/Charts/UpBarChart.jsx @@ -0,0 +1,109 @@ +import { memo, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; +import PropTypes from "prop-types"; +import CustomLabels from "./CustomLabels"; + +const getThemeColor = (responseTime) => { + if (responseTime < 200) { + return "success"; + } else if (responseTime < 300) { + return "warning"; + } else { + return "error"; + } +}; + +const UpBarChart = memo(({ monitor, type, onBarHover }) => { + const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + + return ( + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > + + } + /> + + {monitor?.groupedUpChecks?.map((entry, index) => { + const themeColor = getThemeColor(entry.avgResponseTime); + return ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ + time: null, + totalChecks: 0, + groupUptimePercentage: 0, + }); + }} + /> + ); + })} + + + + ); +}); + +// Add display name for the component +UpBarChart.displayName = "UpBarChart"; + +// Validate props using PropTypes +UpBarChart.propTypes = { + monitor: PropTypes.shape({ + groupedUpChecks: PropTypes.array, + }), + type: PropTypes.string, + onBarHover: PropTypes.func, +}; +export default UpBarChart; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/ConfigButton/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/ConfigButton/index.jsx new file mode 100644 index 000000000..9981b4e1c --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/ConfigButton/index.jsx @@ -0,0 +1,35 @@ +import { Button, Box } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import { useNavigate } from "react-router-dom"; +import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react"; + +const ConfigButton = ({ shouldRender, monitorId }) => { + const theme = useTheme(); + const navigate = useNavigate(); + + if (!shouldRender) return null; + + return ( + + + + ); +}; + +export default ConfigButton; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx new file mode 100644 index 000000000..5802fd82d --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx @@ -0,0 +1,43 @@ +import { Stack, Typography } from "@mui/material"; +import PulseDot from "../../../../../Components/Animated/PulseDot"; +import Dot from "../../../../../Components/Dot"; +import { useTheme } from "@emotion/react"; +import useUtils from "../../../Home/Hooks/useUtils"; +import { formatDurationRounded } from "../../../../../Utils/timeUtils"; +import ConfigButton from "../ConfigButton"; + +const MonitorHeader = ({ monitor }) => { + const theme = useTheme(); + const { statusColor, statusMsg, determineState } = useUtils(); + + return ( + + + {monitor.name} + + + + {monitor?.url?.replace(/^https?:\/\//, "") || "..."} + + + + Checking every {formatDurationRounded(monitor?.interval)}. + + + + + + ); +}; + +export default MonitorHeader; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/ResponseTable/index.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx new file mode 100644 index 000000000..e994dd63b --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx @@ -0,0 +1,72 @@ +// Components +import { Stack, Typography } from "@mui/material"; +import StatBox from "../../../../../Components/StatBox"; + +// Utils +import { useTheme } from "@mui/material/styles"; +import useUtils from "../../../Home/Hooks/useUtils"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; + +const StatusBoxes = ({ monitor, certificateExpiry }) => { + const theme = useTheme(); + const { time: streakTime, units: streakUnits } = getHumanReadableDuration( + monitor?.uptimeStreak + ); + + const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration( + monitor?.timeSinceLastCheck + ); + + const { determineState } = useUtils(); + return ( + + + {streakTime} + {streakUnits} + + } + /> + + {lastCheckTime} + {lastCheckUnits} + {"ago"} + + } + /> + + {monitor?.latestResponseTime} + {"ms"} + + } + /> + + {certificateExpiry} + + } + /> + + ); +}; + +export default StatusBoxes; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx new file mode 100644 index 000000000..ffb581e23 --- /dev/null +++ b/Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx @@ -0,0 +1,44 @@ +import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; +import { useTheme } from "@emotion/react"; +const TimeFramePicker = ({ dateRange, setDateRange }) => { + const theme = useTheme(); + return ( + + + Showing statistics for past{" "} + {dateRange === "day" ? "24 hours" : dateRange === "week" ? "7 days" : "30 days"}. + + + + + + + + ); +}; + +export default TimeFramePicker; diff --git a/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx index e7ed825f2..067545f51 100644 --- a/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx +++ b/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => { const [monitorIsLoading, setMonitorsIsLoading] = useState(false); - const [monitor, setMonitor] = useState([]); + const [monitor, setMonitor] = useState({}); const navigate = useNavigate(); useEffect(() => { diff --git a/Client/src/Pages/Uptime/NewDetails/index.jsx b/Client/src/Pages/Uptime/NewDetails/index.jsx index 5494146e6..eca12ec57 100644 --- a/Client/src/Pages/Uptime/NewDetails/index.jsx +++ b/Client/src/Pages/Uptime/NewDetails/index.jsx @@ -1,6 +1,10 @@ // Components import Breadcrumbs from "../../../Components/Breadcrumbs"; - +import MonitorHeader from "./Components/MonitorHeader"; +import StatusBoxes from "./Components/StatusBoxes"; +import TimeFramePicker from "./Components/TimeFramePicker"; +import ChartBoxes from "./Components/ChartBoxes"; +import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; // MUI Components import { Stack } from "@mui/material"; @@ -8,6 +12,7 @@ import { Stack } from "@mui/material"; import { useState } from "react"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; +import { useTheme } from "@emotion/react"; import useMonitorFetch from "./Hooks/useMonitorFetch"; import useCertificateFetch from "./Hooks/useCertificateFetch"; // Constants @@ -26,10 +31,13 @@ const UptimeDetails = () => { // Local state const [dateRange, setDateRange] = useState("day"); + const [hoveredUptimeData, setHoveredUptimeData] = useState(null); + const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); // Utils const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D"; const { monitorId } = useParams(); + const theme = useTheme(); const { monitor, monitorIsLoading } = useMonitorFetch({ authToken, @@ -45,11 +53,32 @@ const UptimeDetails = () => { uiTimezone, }); - console.log(monitor); - console.log(certificateExpiry); return ( - + + + + + + ); }; From 36d46c246e093771efac1bdcabf3ecbb0bedbab1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 14:55:44 -0800 Subject: [PATCH 032/106] Add pagination label to prop types --- Client/src/Components/Table/TablePagination/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/Client/src/Components/Table/TablePagination/index.jsx b/Client/src/Components/Table/TablePagination/index.jsx index 61e89232f..518c18599 100644 --- a/Client/src/Components/Table/TablePagination/index.jsx +++ b/Client/src/Components/Table/TablePagination/index.jsx @@ -5,6 +5,7 @@ import { TablePaginationActions } from "./Actions"; import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react"; Pagination.propTypes = { + paginationLabel: PropTypes.string, // Label for the pagination. itemCount: PropTypes.number.isRequired, // Total number of items for pagination. page: PropTypes.number.isRequired, // Current page index. rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page. From cb0ec0e1826462303d891ce6e302b56b74efbadb Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 14:55:55 -0800 Subject: [PATCH 033/106] finish refactor --- .../Uptime/Details/Charts/CustomLabels.jsx | 42 -- .../Uptime/Details/Charts/DownBarChart.jsx | 92 --- .../Uptime/Details/Charts/UpBarChart.jsx | 107 ---- .../Components/ChartBoxes/index.jsx | 8 +- .../Components/ChartBoxes/skeleton.jsx | 30 + .../Components/Charts/ChartBox.jsx | 4 +- .../Components/Charts/CustomLabels.jsx | 0 .../Components/Charts/DownBarChart.jsx | 0 .../Charts/ResponseGaugeChart.jsx | 0 .../Components/Charts/ResponseTimeChart.jsx | 7 +- .../Charts/ResponseTimeChartSkeleton.jsx | 12 + .../Components/Charts/UpBarChart.jsx | 0 .../Components/ConfigButton/index.jsx | 0 .../Components/MonitorHeader/index.jsx | 16 +- .../Components/MonitorHeader/skeleton.jsx | 23 + .../Components/ResponseTable/index.jsx | 88 +++ .../Components/ResponseTable/skeleton.jsx | 13 + .../Components/StatusBoxes/index.jsx | 8 +- .../Components/StatusBoxes/skeleton.jsx | 30 + .../Components/TimeFramePicker/index.jsx | 9 +- .../Components/TimeFramePicker/skeleton.jsx | 23 + .../Hooks/useCertificateFetch.jsx | 0 .../Uptime/Details/Hooks/useChecksFetch.jsx | 45 ++ .../Hooks/useMonitorFetch.jsx | 0 .../Uptime/Details/PaginationTable/index.jsx | 163 ----- Client/src/Pages/Uptime/Details/index.css | 0 Client/src/Pages/Uptime/Details/index.jsx | 582 ++++-------------- Client/src/Pages/Uptime/Details/skeleton.jsx | 120 ---- Client/src/Pages/Uptime/Details/styled.jsx | 45 -- .../Components/Charts/ResponseGaugeChart.jsx | 117 ---- .../Components/ResponseTable/index.jsx | 0 Client/src/Pages/Uptime/NewDetails/index.jsx | 86 --- Client/src/Routes/index.jsx | 9 +- 33 files changed, 428 insertions(+), 1251 deletions(-) delete mode 100644 Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/ChartBoxes/index.jsx (97%) create mode 100644 Client/src/Pages/Uptime/Details/Components/ChartBoxes/skeleton.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/Charts/ChartBox.jsx (95%) rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/Charts/CustomLabels.jsx (100%) rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/Charts/DownBarChart.jsx (100%) rename Client/src/Pages/Uptime/Details/{ => Components}/Charts/ResponseGaugeChart.jsx (100%) rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/Charts/ResponseTimeChart.jsx (70%) create mode 100644 Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/Charts/UpBarChart.jsx (100%) rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/ConfigButton/index.jsx (100%) rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/MonitorHeader/index.jsx (75%) create mode 100644 Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx create mode 100644 Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx create mode 100644 Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/StatusBoxes/index.jsx (90%) create mode 100644 Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Components/TimeFramePicker/index.jsx (85%) create mode 100644 Client/src/Pages/Uptime/Details/Components/TimeFramePicker/skeleton.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Hooks/useCertificateFetch.jsx (100%) create mode 100644 Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx rename Client/src/Pages/Uptime/{NewDetails => Details}/Hooks/useMonitorFetch.jsx (100%) delete mode 100644 Client/src/Pages/Uptime/Details/PaginationTable/index.jsx delete mode 100644 Client/src/Pages/Uptime/Details/index.css delete mode 100644 Client/src/Pages/Uptime/Details/skeleton.jsx delete mode 100644 Client/src/Pages/Uptime/Details/styled.jsx delete mode 100644 Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx delete mode 100644 Client/src/Pages/Uptime/NewDetails/Components/ResponseTable/index.jsx delete mode 100644 Client/src/Pages/Uptime/NewDetails/index.jsx diff --git a/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx b/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx deleted file mode 100644 index 10494830f..000000000 --- a/Client/src/Pages/Uptime/Details/Charts/CustomLabels.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from "prop-types"; -import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../../Utils/timeUtils"; - -const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { - const uiTimezone = useSelector((state) => state.ui.timezone); - const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; - - return ( - <> - - {formatDateWithTz(firstDataPoint._id, dateFormat, uiTimezone)} - - - {formatDateWithTz(lastDataPoint._id, dateFormat, uiTimezone)} - - - ); -}; - -CustomLabels.propTypes = { - x: PropTypes.number.isRequired, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - firstDataPoint: PropTypes.object.isRequired, - lastDataPoint: PropTypes.object.isRequired, - type: PropTypes.string.isRequired, -}; - -export default CustomLabels; diff --git a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx deleted file mode 100644 index ae2adda0f..000000000 --- a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx +++ /dev/null @@ -1,92 +0,0 @@ -import { memo, useState } from "react"; -import { useTheme } from "@mui/material"; -import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; -import PropTypes from "prop-types"; -import CustomLabels from "./CustomLabels"; - -const DownBarChart = memo(({ monitor, type, onBarHover }) => { - const theme = useTheme(); - - const [chartHovered, setChartHovered] = useState(false); - const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - - return ( - - { - setChartHovered(true); - onBarHover({ time: null, totalChecks: 0 }); - }} - onMouseLeave={() => { - setChartHovered(false); - setHoveredBarIndex(null); - onBarHover(null); - }} - > - - } - /> - - {monitor.groupedDownChecks.map((entry, index) => { - return ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ time: null, totalChecks: 0 }); - }} - /> - ); - })} - - - - ); -}); - -DownBarChart.displayName = "DownBarChart"; -DownBarChart.propTypes = { - monitor: PropTypes.shape({ - groupedDownChecks: PropTypes.arrayOf(PropTypes.object), - }), - type: PropTypes.string, - onBarHover: PropTypes.func, -}; -export default DownBarChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx deleted file mode 100644 index 223dc8eef..000000000 --- a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx +++ /dev/null @@ -1,107 +0,0 @@ -import { memo, useState } from "react"; -import { useTheme } from "@mui/material"; -import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; -import PropTypes from "prop-types"; -import CustomLabels from "./CustomLabels"; - -const getThemeColor = (responseTime) => { - if (responseTime < 200) { - return "success"; - } else if (responseTime < 300) { - return "warning"; - } else { - return "error"; - } -}; - -const UpBarChart = memo(({ monitor, type, onBarHover }) => { - const theme = useTheme(); - const [chartHovered, setChartHovered] = useState(false); - const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - - return ( - - { - setChartHovered(true); - onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); - }} - onMouseLeave={() => { - setChartHovered(false); - setHoveredBarIndex(null); - onBarHover(null); - }} - > - - } - /> - - {monitor.groupedUpChecks.map((entry, index) => { - const themeColor = getThemeColor(entry.avgResponseTime); - return ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ - time: null, - totalChecks: 0, - groupUptimePercentage: 0, - }); - }} - /> - ); - })} - - - - ); -}); - -// Add display name for the component -UpBarChart.displayName = "UpBarChart"; - -// Validate props using PropTypes -UpBarChart.propTypes = { - monitor: PropTypes.shape({ - groupedUpChecks: PropTypes.array, - }), - type: PropTypes.string, - onBarHover: PropTypes.func, -}; -export default UpBarChart; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx similarity index 97% rename from Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx rename to Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx index afe93d028..d9e581267 100644 --- a/Client/src/Pages/Uptime/NewDetails/Components/ChartBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx @@ -7,13 +7,14 @@ import AverageResponseIcon from "../../../../../assets/icons/average-response-ic import UpBarChart from "../Charts/UpBarChart"; import DownBarChart from "../Charts/DownBarChart"; import ResponseGaugeChart from "../Charts/ResponseGaugeChart"; - +import SkeletonLayout from "./skeleton"; // Utils import { formatDateWithTz } from "../../../../../Utils/timeUtils"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; const ChartBoxes = ({ + shouldRender = true, monitor, dateRange, uiTimezone, @@ -24,6 +25,11 @@ const ChartBoxes = ({ setHoveredIncidentsData, }) => { const theme = useTheme(); + + if (!shouldRender) { + return ; + } + return ( { + const theme = useTheme(); + return ( + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx similarity index 95% rename from Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx rename to Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx index e62949004..672134781 100644 --- a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ChartBox.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import IconBox from "../../../../../Components/IconBox"; import PropTypes from "prop-types"; -const ChartBox = ({ children, icon, header }) => { +const ChartBox = ({ children, icon, header, height = "300px" }) => { const theme = useTheme(); return ( { justifyContent: "space-between", flex: "1 30%", gap: theme.spacing(8), - height: 300, + height, minWidth: 250, padding: theme.spacing(8), border: 1, diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/CustomLabels.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx similarity index 100% rename from Client/src/Pages/Uptime/NewDetails/Components/Charts/CustomLabels.jsx rename to Client/src/Pages/Uptime/Details/Components/Charts/CustomLabels.jsx diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/NewDetails/Components/Charts/DownBarChart.jsx rename to Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx diff --git a/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx rename to Client/src/Pages/Uptime/Details/Components/Charts/ResponseGaugeChart.jsx diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx similarity index 70% rename from Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx rename to Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index c28163394..73ffc035e 100644 --- a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseTimeChart.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,7 +1,12 @@ import ChartBox from "./ChartBox"; import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart"; import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; -const ResponseTImeChart = ({ monitor, dateRange }) => { +import SkeletonLayout from "./ResponseTimeChartSkeleton"; +const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => { + if (!shouldRender) { + return ; + } + return ( } diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx new file mode 100644 index 000000000..a70757e36 --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChartSkeleton.jsx @@ -0,0 +1,12 @@ +import { Skeleton } from "@mui/material"; +const ResponseTimeChartSkeleton = () => { + return ( + + ); +}; + +export default ResponseTimeChartSkeleton; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx similarity index 100% rename from Client/src/Pages/Uptime/NewDetails/Components/Charts/UpBarChart.jsx rename to Client/src/Pages/Uptime/Details/Components/Charts/UpBarChart.jsx diff --git a/Client/src/Pages/Uptime/NewDetails/Components/ConfigButton/index.jsx b/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/NewDetails/Components/ConfigButton/index.jsx rename to Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx diff --git a/Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx similarity index 75% rename from Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx rename to Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx index 5802fd82d..fddf9c3e5 100644 --- a/Client/src/Pages/Uptime/NewDetails/Components/MonitorHeader/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx @@ -5,10 +5,16 @@ import { useTheme } from "@emotion/react"; import useUtils from "../../../Home/Hooks/useUtils"; import { formatDurationRounded } from "../../../../../Utils/timeUtils"; import ConfigButton from "../ConfigButton"; +import SkeletonLayout from "./skeleton"; +import PropTypes from "prop-types"; -const MonitorHeader = ({ monitor }) => { +const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => { const theme = useTheme(); const { statusColor, statusMsg, determineState } = useUtils(); + console.log(shouldRender); + if (!shouldRender) { + return ; + } return ( { ); }; +MonitorHeader.propTypes = { + shouldRender: PropTypes.bool, + isAdmin: PropTypes.bool, + monitor: PropTypes.object, +}; + export default MonitorHeader; diff --git a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx new file mode 100644 index 000000000..64dc0547f --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx @@ -0,0 +1,23 @@ +import { Stack, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx new file mode 100644 index 000000000..a510b9bf1 --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx @@ -0,0 +1,88 @@ +import ChartBox from "../Charts/ChartBox"; +import PropTypes from "prop-types"; +import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react"; +import Table from "../../../../../Components/Table"; +import TablePagination from "../../../../../Components/Table/TablePagination"; +import { StatusLabel } from "../../../../../Components/Label"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils"; +import SkeletonLayout from "./skeleton"; +const ResponseTable = ({ + shouldRender = true, + checks, + checksCount, + uiTimezone, + page, + setPage, + rowsPerPage, + setRowsPerPage, +}) => { + if (!shouldRender) { + return ; + } + + const headers = [ + { + id: "status", + content: "Status", + render: (row) => { + const status = row.status === true ? "up" : "down"; + + return ( + + ); + }, + }, + { + id: "date", + content: "Date & Time", + render: (row) => + formatDateWithTz(row.createdAt, "ddd, MMMM D, YYYY, HH:mm A", uiTimezone), + }, + { + id: "statusCode", + content: "Status code", + render: (row) => (row.statusCode ? row.statusCode : "N/A"), + }, + { + id: "message", + content: "Message", + render: (row) => row.message, + }, + ]; + + return ( + } + header="Response Times" + height="100%" + > + + + + ); +}; + +ResponseTable.propTypes = { + checks: PropTypes.array.isRequired, + checksCount: PropTypes.number.isRequired, + uiTimezone: PropTypes.string.isRequired, + page: PropTypes.number.isRequired, + setPage: PropTypes.func.isRequired, + rowsPerPage: PropTypes.number.isRequired, + setRowsPerPage: PropTypes.func.isRequired, +}; + +export default ResponseTable; diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx b/Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx new file mode 100644 index 000000000..2665ffaf1 --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Components/ResponseTable/skeleton.jsx @@ -0,0 +1,13 @@ +import { Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx similarity index 90% rename from Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx rename to Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx index e994dd63b..55bcf34da 100644 --- a/Client/src/Pages/Uptime/NewDetails/Components/StatusBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx @@ -1,13 +1,16 @@ // Components import { Stack, Typography } from "@mui/material"; import StatBox from "../../../../../Components/StatBox"; - +import SkeletonLayout from "./skeleton"; // Utils import { useTheme } from "@mui/material/styles"; import useUtils from "../../../Home/Hooks/useUtils"; import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; -const StatusBoxes = ({ monitor, certificateExpiry }) => { +const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { + if (!shouldRender) { + return ; + } const theme = useTheme(); const { time: streakTime, units: streakUnits } = getHumanReadableDuration( monitor?.uptimeStreak @@ -18,6 +21,7 @@ const StatusBoxes = ({ monitor, certificateExpiry }) => { ); const { determineState } = useUtils(); + return ( { + const theme = useTheme(); + return ( + + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx b/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx similarity index 85% rename from Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx rename to Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx index ffb581e23..ade677604 100644 --- a/Client/src/Pages/Uptime/NewDetails/Components/TimeFramePicker/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx @@ -1,7 +1,14 @@ import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; import { useTheme } from "@emotion/react"; -const TimeFramePicker = ({ dateRange, setDateRange }) => { +import SkeletonLayout from "./skeleton"; + +const TimeFramePicker = ({ shouldRender = true, dateRange, setDateRange }) => { const theme = useTheme(); + + if (!shouldRender) { + return ; + } + return ( { + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/NewDetails/Hooks/useCertificateFetch.jsx rename to Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx diff --git a/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx new file mode 100644 index 000000000..2a50c2172 --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx @@ -0,0 +1,45 @@ +import { useState } from "react"; +import { useEffect } from "react"; +import { logger } from "../../../../Utils/Logger"; +import { networkService } from "../../../../main"; + +export const useChecksFetch = ({ + authToken, + monitorId, + dateRange, + page, + rowsPerPage, +}) => { + const [checks, setChecks] = useState([]); + const [checksCount, setChecksCount] = useState(0); + const [checksAreLoading, setChecksAreLoading] = useState(false); + + useEffect(() => { + const fetchChecks = async () => { + try { + setChecksAreLoading(true); + const res = await networkService.getChecksByMonitor({ + authToken: authToken, + monitorId: monitorId, + sortOrder: "desc", + limit: null, + dateRange: dateRange, + filter: null, + page: page, + rowsPerPage: rowsPerPage, + }); + setChecks(res.data.data.checks); + setChecksCount(res.data.data.checksCount); + } catch (error) { + logger.error(error); + } finally { + setChecksAreLoading(false); + } + }; + fetchChecks(); + }, [authToken, monitorId, dateRange, page, rowsPerPage]); + + return { checks, checksCount, checksAreLoading }; +}; + +export default useChecksFetch; diff --git a/Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/NewDetails/Hooks/useMonitorFetch.jsx rename to Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx diff --git a/Client/src/Pages/Uptime/Details/PaginationTable/index.jsx b/Client/src/Pages/Uptime/Details/PaginationTable/index.jsx deleted file mode 100644 index 93e9532c5..000000000 --- a/Client/src/Pages/Uptime/Details/PaginationTable/index.jsx +++ /dev/null @@ -1,163 +0,0 @@ -import PropTypes from "prop-types"; -import { - TableContainer, - Table, - TableHead, - TableRow, - TableCell, - TableBody, - PaginationItem, - Pagination, - Paper, -} from "@mui/material"; - -import { useState, useEffect } from "react"; -import { useSelector } from "react-redux"; -import { networkService } from "../../../../main"; -import { StatusLabel } from "../../../../Components/Label"; -import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; -import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; -import { logger } from "../../../../Utils/Logger"; -import { formatDateWithTz } from "../../../../Utils/timeUtils"; -import { useTheme } from "@emotion/react"; -const PaginationTable = ({ monitorId, dateRange }) => { - const theme = useTheme(); - const { authToken } = useSelector((state) => state.auth); - const [checks, setChecks] = useState([]); - const [checksCount, setChecksCount] = useState(0); - const [paginationController, setPaginationController] = useState({ - page: 0, - rowsPerPage: 5, - }); - const uiTimezone = useSelector((state) => state.ui.timezone); - - useEffect(() => { - setPaginationController((prevPaginationController) => ({ - ...prevPaginationController, - page: 0, - })); - }, [dateRange]); - - useEffect(() => { - const fetchPage = async () => { - try { - const res = await networkService.getChecksByMonitor({ - authToken: authToken, - monitorId: monitorId, - sortOrder: "desc", - limit: null, - dateRange: dateRange, - filter: null, - page: paginationController.page, - rowsPerPage: paginationController.rowsPerPage, - }); - setChecks(res.data.data.checks); - setChecksCount(res.data.data.checksCount); - } catch (error) { - logger.error(error); - } - }; - fetchPage(); - }, [ - authToken, - monitorId, - dateRange, - paginationController.page, - paginationController.rowsPerPage, - ]); - - const handlePageChange = (_, newPage) => { - setPaginationController({ - ...paginationController, - page: newPage - 1, // 0-indexed - }); - }; - - let paginationComponent = <>; - if (checksCount > paginationController.rowsPerPage) { - paginationComponent = ( - ( - - )} - /> - ); - } - - return ( - <> - -
- - - Status - Date & Time - Status Code - Message - - - - {checks.map((check) => { - const status = check.status === true ? "up" : "down"; - - return ( - - - - - - {formatDateWithTz( - check.createdAt, - "ddd, MMMM D, YYYY, HH:mm A", - uiTimezone - )} - - {check.statusCode ? check.statusCode : "N/A"} - {check.message} - - ); - })} - -
- - {paginationComponent} - - ); -}; - -PaginationTable.propTypes = { - monitorId: PropTypes.string.isRequired, - dateRange: PropTypes.string.isRequired, -}; - -export default PaginationTable; diff --git a/Client/src/Pages/Uptime/Details/index.css b/Client/src/Pages/Uptime/Details/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index be589c1ba..81265eb56 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -1,476 +1,128 @@ -import PropTypes from "prop-types"; -import { useEffect, useState, useCallback } from "react"; -import { Box, Button, Stack, Tooltip, Typography, useTheme } from "@mui/material"; -import { useSelector } from "react-redux"; -import { useNavigate, useParams } from "react-router-dom"; -import { networkService } from "../../../main"; -import { logger } from "../../../Utils/Logger"; -import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; -import ButtonGroup from "@mui/material/ButtonGroup"; -import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; -import UptimeIcon from "../../../assets/icons/uptime-icon.svg?react"; -import ResponseTimeIcon from "../../../assets/icons/response-time-icon.svg?react"; -import AverageResponseIcon from "../../../assets/icons/average-response-icon.svg?react"; -import IncidentsIcon from "../../../assets/icons/incidents.svg?react"; -import HistoryIcon from "../../../assets/icons/history-icon.svg?react"; -import PaginationTable from "./PaginationTable"; +// Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import PulseDot from "../../../Components/Animated/PulseDot"; -import { ChartBox } from "./styled"; -import SkeletonLayout from "./skeleton"; -import "./index.css"; -import useUtils from "../Home/Hooks/useUtils"; -import { formatDateWithTz, formatDurationSplit } from "../../../Utils/timeUtils"; -import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import IconBox from "../../../Components/IconBox"; -import StatBox from "../../../Components/StatBox"; -import UpBarChart from "./Charts/UpBarChart"; -import DownBarChart from "./Charts/DownBarChart"; -import ResponseGaugeChart from "./Charts/ResponseGaugeChart"; -/** - * Details page component displaying monitor details and related information. - * @component - */ -const DetailsPage = () => { - const theme = useTheme(); - const { statusColor, statusMsg, determineState } = useUtils(); - const isAdmin = useIsAdmin(); - const [monitor, setMonitor] = useState({}); - const { monitorId } = useParams(); - const { authToken } = useSelector((state) => state.auth); - const [dateRange, setDateRange] = useState("day"); - const [certificateExpiry, setCertificateExpiry] = useState("N/A"); - const navigate = useNavigate(); +import MonitorHeader from "./Components/MonitorHeader"; +import StatusBoxes from "./Components/StatusBoxes"; +import TimeFramePicker from "./Components/TimeFramePicker"; +import ChartBoxes from "./Components/ChartBoxes"; +import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; +import ResponseTable from "./Components/ResponseTable"; +// MUI Components +import { Stack } from "@mui/material"; - const certificateDateFormat = "MMM D, YYYY h A"; - const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D"; +// Utils +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useTheme } from "@emotion/react"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import useMonitorFetch from "./Hooks/useMonitorFetch"; +import useCertificateFetch from "./Hooks/useCertificateFetch"; +import useChecksFetch from "./Hooks/useChecksFetch"; + +// Constants +const BREADCRUMBS = [ + { name: "uptime", path: "/uptime" }, + { name: "details", path: "" }, + // { name: "details", path: `/uptime/${monitorId}` }, Is this needed? We can't click on this anywy +]; + +const certificateDateFormat = "MMM D, YYYY h A"; + +const UptimeDetails = () => { + // Redux state + const { authToken } = useSelector((state) => state.auth); const uiTimezone = useSelector((state) => state.ui.timezone); - const fetchMonitor = useCallback(async () => { - try { - const res = await networkService.getUptimeDetailsById({ - authToken: authToken, - monitorId: monitorId, - dateRange: dateRange, - normalize: true, - }); - setMonitor(res?.data?.data ?? {}); - } catch (error) { - logger.error(error); - navigate("/not-found", { replace: true }); - } - }, [authToken, monitorId, navigate, dateRange]); - - useEffect(() => { - fetchMonitor(); - }, [fetchMonitor]); - - useEffect(() => { - const fetchCertificate = async () => { - if (monitor?.type !== "http") { - return; - } - try { - const res = await networkService.getCertificateExpiry({ - authToken: authToken, - monitorId: monitorId, - }); - if (res?.data?.data?.certificateDate) { - const date = res.data.data.certificateDate; - setCertificateExpiry( - formatDateWithTz(date, certificateDateFormat, uiTimezone) ?? "N/A" - ); - } - } catch (error) { - setCertificateExpiry("N/A"); - console.error(error); - } - }; - fetchCertificate(); - }, [authToken, monitorId, monitor, uiTimezone, dateFormat]); - - const splitDuration = (duration) => { - const { time, format } = formatDurationSplit(duration); - return ( - <> - {time} - {format} - - ); - }; - - let loading = Object.keys(monitor).length === 0; - + // Local state + const [dateRange, setDateRange] = useState("day"); const [hoveredUptimeData, setHoveredUptimeData] = useState(null); const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(5); + + // Utils + const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D"; + const { monitorId } = useParams(); + const theme = useTheme(); + const isAdmin = useIsAdmin(); + + const { monitor, monitorIsLoading } = useMonitorFetch({ + authToken, + monitorId, + dateRange, + }); + + const { certificateExpiry, certificateIsLoading } = useCertificateFetch({ + monitor, + authToken, + monitorId, + certificateDateFormat, + uiTimezone, + }); + + const { checks, checksCount, checksAreLoading } = useChecksFetch({ + authToken, + monitorId, + dateRange, + page, + rowsPerPage, + }); + + // Handlers + const handlePageChange = (_, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(event.target.value); + }; - const BREADCRUMBS = [ - { name: "uptime", path: "/uptime" }, - { name: "details", path: `/uptime/${monitorId}` }, - ]; return ( - - {loading ? ( - - ) : ( - <> - - - - - - {monitor.name} - - - {/* TODO there is a tooltip at BarChart component. Wrap the Tooltip on our own component */} - - - - - - - {monitor.url?.replace(/^https?:\/\//, "") || "..."} - - - {/* Checking every {formatDurationRounded(monitor?.interval)}. */} - - - - - {isAdmin && ( - - )} - - - - - - - {monitor?.latestResponseTime} - {"ms"} - - } - /> - - {certificateExpiry} - - } - /> - - - - - Showing statistics for past{" "} - {dateRange === "day" - ? "24 hours" - : dateRange === "week" - ? "7 days" - : "30 days"} - . - - - - - - - - - - - - - - Uptime - - - - Total Checks - - {hoveredUptimeData !== null - ? hoveredUptimeData.totalChecks - : (monitor?.groupedUpChecks?.reduce((count, checkGroup) => { - return count + checkGroup.totalChecks; - }, 0) ?? 0)} - - {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( - - {formatDateWithTz( - hoveredUptimeData._id, - dateFormat, - uiTimezone - )} - - )} - - - - {hoveredUptimeData !== null - ? "Avg Response Time" - : "Uptime Percentage"} - - - {hoveredUptimeData !== null - ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) - : Math.floor( - ((monitor?.upChecks?.totalChecks ?? 0) / - (monitor?.totalChecks ?? 1)) * - 100 - )} - - {hoveredUptimeData !== null ? " ms" : " %"} - - - - - - - - - - - - Incidents - - - Total Incidents - - {hoveredIncidentsData !== null - ? hoveredIncidentsData.totalChecks - : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { - return count + checkGroup.totalChecks; - }, 0) ?? 0)} - - {hoveredIncidentsData !== null && - hoveredIncidentsData.time !== null && ( - - {formatDateWithTz( - hoveredIncidentsData._id, - dateFormat, - uiTimezone - )} - - )} - - - - - - - - - Average Response Time - - - - - - - - - Response Times - - - - - - - - - - History - - - - - - - - - )} - + + + + + + + + + ); }; -DetailsPage.propTypes = { - isAdmin: PropTypes.bool, -}; -export default DetailsPage; +export default UptimeDetails; diff --git a/Client/src/Pages/Uptime/Details/skeleton.jsx b/Client/src/Pages/Uptime/Details/skeleton.jsx deleted file mode 100644 index 0bdc03ca8..000000000 --- a/Client/src/Pages/Uptime/Details/skeleton.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Box, Skeleton, Stack, useTheme } from "@mui/material"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Details/styled.jsx b/Client/src/Pages/Uptime/Details/styled.jsx deleted file mode 100644 index 164fee457..000000000 --- a/Client/src/Pages/Uptime/Details/styled.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Stack, styled } from "@mui/material"; - -export const ChartBox = styled(Stack)(({ theme }) => ({ - flex: "1 30%", - gap: theme.spacing(8), - height: 300, - minWidth: 250, - padding: theme.spacing(8), - border: 1, - borderStyle: "solid", - borderColor: theme.palette.primary.lowContrast, - borderRadius: 4, - backgroundColor: theme.palette.primary.main, - "& h2": { - color: theme.palette.primary.contrastTextSecondary, - fontSize: 15, - fontWeight: 500, - }, - "& .MuiBox-root:not(.area-tooltip) p": { - color: theme.palette.primary.contrastTextTertiary, - fontSize: 13, - }, - "& .MuiBox-root > span": { - color: theme.palette.primary.contrastText, - fontSize: 20, - "& span": { - opacity: 0.8, - marginLeft: 2, - fontSize: 15, - }, - }, - "& .MuiStack-root": { - flexDirection: "row", - gap: theme.spacing(6), - }, - "& .MuiStack-root:first-of-type": { - alignItems: "center", - }, - "& tspan, & text": { - fill: theme.palette.primary.contrastTextTertiary, - }, - "& path": { - transition: "fill 300ms ease, stroke-width 400ms ease", - }, -})); diff --git a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx b/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx deleted file mode 100644 index e94af8441..000000000 --- a/Client/src/Pages/Uptime/NewDetails/Components/Charts/ResponseGaugeChart.jsx +++ /dev/null @@ -1,117 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@mui/material"; -import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts"; - -const ResponseGaugeChart = ({ avgResponseTime }) => { - const theme = useTheme(); - - let max = 1000; // max ms - - const data = [ - { response: max, fill: "transparent", background: false }, - { response: avgResponseTime, background: true }, - ]; - let responseTime = Math.floor(avgResponseTime); - let responseProps = - responseTime <= 200 - ? { - category: "Excellent", - main: theme.palette.success.main, - bg: theme.palette.success.contrastText, - } - : responseTime <= 500 - ? { - category: "Fair", - main: theme.palette.success.main, - bg: theme.palette.success.contrastText, - } - : responseTime <= 600 - ? { - category: "Acceptable", - main: theme.palette.warning.main, - bg: theme.palette.warning.lowContrast, - } - : { - category: "Poor", - main: theme.palette.error.main, - bg: theme.palette.error.contrastText, - }; - - return ( - - - - low - - - high - - - {responseProps.category} - - - {responseTime} ms - - - - - - - - ); -}; - -ResponseGaugeChart.propTypes = { - avgResponseTime: PropTypes.number.isRequired, -}; - -export default ResponseGaugeChart; diff --git a/Client/src/Pages/Uptime/NewDetails/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/NewDetails/Components/ResponseTable/index.jsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/Client/src/Pages/Uptime/NewDetails/index.jsx b/Client/src/Pages/Uptime/NewDetails/index.jsx deleted file mode 100644 index eca12ec57..000000000 --- a/Client/src/Pages/Uptime/NewDetails/index.jsx +++ /dev/null @@ -1,86 +0,0 @@ -// Components -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorHeader from "./Components/MonitorHeader"; -import StatusBoxes from "./Components/StatusBoxes"; -import TimeFramePicker from "./Components/TimeFramePicker"; -import ChartBoxes from "./Components/ChartBoxes"; -import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; -// MUI Components -import { Stack } from "@mui/material"; - -// Utils -import { useState } from "react"; -import { useParams } from "react-router-dom"; -import { useSelector } from "react-redux"; -import { useTheme } from "@emotion/react"; -import useMonitorFetch from "./Hooks/useMonitorFetch"; -import useCertificateFetch from "./Hooks/useCertificateFetch"; -// Constants -const BREADCRUMBS = [ - { name: "uptime", path: "/uptime" }, - { name: "details", path: "" }, - // { name: "details", path: `/uptime/${monitorId}` }, Is this needed? We can't click on this anywy -]; - -const certificateDateFormat = "MMM D, YYYY h A"; - -const UptimeDetails = () => { - // Redux state - const { authToken } = useSelector((state) => state.auth); - const uiTimezone = useSelector((state) => state.ui.timezone); - - // Local state - const [dateRange, setDateRange] = useState("day"); - const [hoveredUptimeData, setHoveredUptimeData] = useState(null); - const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); - - // Utils - const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D"; - const { monitorId } = useParams(); - const theme = useTheme(); - - const { monitor, monitorIsLoading } = useMonitorFetch({ - authToken, - monitorId, - dateRange, - }); - - const { certificateExpiry, certificateIsLoading } = useCertificateFetch({ - monitor, - authToken, - monitorId, - certificateDateFormat, - uiTimezone, - }); - - return ( - - - - - - - - - ); -}; - -export default UptimeDetails; diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 6ac5624a9..575c108cf 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -6,7 +6,9 @@ import NotFound from "../Pages/NotFound"; import Login from "../Pages/Auth/Login/Login"; import Register from "../Pages/Auth/Register/Register"; import Account from "../Pages/Account"; -import Monitors from "../Pages/Uptime/Home"; +import Uptime from "../Pages/Uptime/Home"; +import UptimeDetails from "../Pages/Uptime/Details"; + import CreateMonitor from "../Pages/Uptime/CreateUptime"; import CreateInfrastructureMonitor from "../Pages/Infrastructure/CreateMonitor"; import Incidents from "../Pages/Incidents"; @@ -18,9 +20,6 @@ import CheckEmail from "../Pages/Auth/CheckEmail"; import SetNewPassword from "../Pages/Auth/SetNewPassword"; import NewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; import ProtectedRoute from "../Components/ProtectedRoute"; -import Details from "../Pages/Uptime/Details"; - -import UptimeDetails from "../Pages/Uptime/NewDetails"; import Maintenance from "../Pages/Maintenance"; import Configure from "../Pages/Uptime/Configure"; import PageSpeed from "../Pages/PageSpeed"; @@ -48,7 +47,7 @@ const Routes = () => { /> } + element={} /> Date: Mon, 27 Jan 2025 14:58:37 -0800 Subject: [PATCH 034/106] Add missing prop validation, cleanup --- .../Uptime/Details/Components/Charts/ChartBox.jsx | 1 + .../Components/Charts/ResponseTimeChart.jsx | 8 ++++++++ .../Details/Components/ConfigButton/index.jsx | 7 ++++++- .../Details/Components/ResponseTable/index.jsx | 1 + .../Details/Components/StatusBoxes/index.jsx | 15 +++++++++++---- .../Details/Components/TimeFramePicker/index.jsx | 7 +++++++ 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx index 672134781..3d4d73421 100644 --- a/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx @@ -71,4 +71,5 @@ ChartBox.propTypes = { children: PropTypes.node, icon: PropTypes.node.isRequired, header: PropTypes.string.isRequired, + height: PropTypes.string, }; diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index 73ffc035e..79e9473ef 100644 --- a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -2,6 +2,8 @@ import ChartBox from "./ChartBox"; import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart"; import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; import SkeletonLayout from "./ResponseTimeChartSkeleton"; +import PropTypes from "prop-types"; + const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => { if (!shouldRender) { return ; @@ -20,4 +22,10 @@ const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => { ); }; +ResponseTImeChart.propTypes = { + shouldRender: PropTypes.bool, + monitor: PropTypes.object, + dateRange: PropTypes.string, +}; + export default ResponseTImeChart; diff --git a/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx b/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx index 9981b4e1c..467655abf 100644 --- a/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx @@ -2,7 +2,7 @@ import { Button, Box } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react"; - +import PropTypes from "prop-types"; const ConfigButton = ({ shouldRender, monitorId }) => { const theme = useTheme(); const navigate = useNavigate(); @@ -32,4 +32,9 @@ const ConfigButton = ({ shouldRender, monitorId }) => { ); }; +ConfigButton.propTypes = { + shouldRender: PropTypes.bool, + monitorId: PropTypes.string, +}; + export default ConfigButton; diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx index a510b9bf1..56ea8eca7 100644 --- a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx @@ -76,6 +76,7 @@ const ResponseTable = ({ }; ResponseTable.propTypes = { + shouldRender: PropTypes.bool, checks: PropTypes.array.isRequired, checksCount: PropTypes.number.isRequired, uiTimezone: PropTypes.string.isRequired, diff --git a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx index 55bcf34da..21922537f 100644 --- a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx @@ -6,12 +6,15 @@ import SkeletonLayout from "./skeleton"; import { useTheme } from "@mui/material/styles"; import useUtils from "../../../Home/Hooks/useUtils"; import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; - +import PropTypes from "prop-types"; const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { + // Utils + const theme = useTheme(); + const { determineState } = useUtils(); + if (!shouldRender) { return ; } - const theme = useTheme(); const { time: streakTime, units: streakUnits } = getHumanReadableDuration( monitor?.uptimeStreak ); @@ -20,8 +23,6 @@ const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { monitor?.timeSinceLastCheck ); - const { determineState } = useUtils(); - return ( { ); }; +StatusBoxes.propTypes = { + shouldRender: PropTypes.bool, + monitor: PropTypes.object, + certificateExpiry: PropTypes.string, +}; + export default StatusBoxes; diff --git a/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx b/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx index ade677604..789030282 100644 --- a/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/index.jsx @@ -1,6 +1,7 @@ import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; import { useTheme } from "@emotion/react"; import SkeletonLayout from "./skeleton"; +import PropTypes from "prop-types"; const TimeFramePicker = ({ shouldRender = true, dateRange, setDateRange }) => { const theme = useTheme(); @@ -48,4 +49,10 @@ const TimeFramePicker = ({ shouldRender = true, dateRange, setDateRange }) => { ); }; +TimeFramePicker.propTypes = { + shouldRender: PropTypes.bool, + dateRange: PropTypes.string, + setDateRange: PropTypes.func, +}; + export default TimeFramePicker; From fe4f0fae355b69498ed09cd093c144ed322529ba Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 28 Jan 2025 01:32:04 +0000 Subject: [PATCH 035/106] Update dependency recharts to v2.15.1 --- Client/package-lock.json | 20 ++++++++++---------- Client/package.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 7d2a2427c..597b38a9c 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -28,7 +28,7 @@ "react-router": "^6.23.0", "react-router-dom": "^6.23.1", "react-toastify": "^10.0.5", - "recharts": "2.15.0", + "recharts": "2.15.1", "redux-persist": "6.0.0", "vite-plugin-svgr": "^4.2.0" }, @@ -5479,9 +5479,9 @@ } }, "node_modules/react-smooth": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", - "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", "license": "MIT", "dependencies": { "fast-equals": "^5.0.1", @@ -5489,8 +5489,8 @@ "react-transition-group": "^4.4.5" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-toastify": { @@ -5523,16 +5523,16 @@ } }, "node_modules/recharts": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz", - "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz", + "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", - "react-smooth": "^4.0.0", + "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" diff --git a/Client/package.json b/Client/package.json index 1e2647a03..3192e1f3b 100644 --- a/Client/package.json +++ b/Client/package.json @@ -31,7 +31,7 @@ "react-router": "^6.23.0", "react-router-dom": "^6.23.1", "react-toastify": "^10.0.5", - "recharts": "2.15.0", + "recharts": "2.15.1", "redux-persist": "6.0.0", "vite-plugin-svgr": "^4.2.0" }, From 128745019dce0b3179854153f256fa8d6fe5d0e8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 28 Jan 2025 12:59:08 +0000 Subject: [PATCH 036/106] Update dependency bullmq to v5.38.0 --- 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 d5a5ea747..fdbd6c19c 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.37.0", + "bullmq": "5.38.0", "cors": "^2.8.5", "dockerode": "4.0.4", "dotenv": "^16.4.5", @@ -1748,9 +1748,9 @@ } }, "node_modules/bullmq": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.37.0.tgz", - "integrity": "sha512-h/wf979+9uROyYpB8oTE44Py6JERCluCSd+ZFpCZlPsYh+wxAkqrfHsHHHKBgsNJp9odWLIY4SG+280EXzXiCQ==", + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.38.0.tgz", + "integrity": "sha512-X6ApLQlSmX0LQ58xCpCoXE2nN57y8H9LD4EApJl9+8E6+FNWdKy6gykL8ofB9x4TcZeeGJhZOUZJAuhRV721bw==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index 9d92a2010..0f63d97fe 100644 --- a/Server/package.json +++ b/Server/package.json @@ -18,7 +18,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "5.1.1", - "bullmq": "5.37.0", + "bullmq": "5.38.0", "cors": "^2.8.5", "dockerode": "4.0.4", "dotenv": "^16.4.5", From 66de46f695e460016db7d720358a586ca233a49a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 10:58:03 -0800 Subject: [PATCH 037/106] Remove basic table component --- Client/src/Components/BasicTable/index.jsx | 360 --------------------- 1 file changed, 360 deletions(-) delete mode 100644 Client/src/Components/BasicTable/index.jsx diff --git a/Client/src/Components/BasicTable/index.jsx b/Client/src/Components/BasicTable/index.jsx deleted file mode 100644 index b93c584da..000000000 --- a/Client/src/Components/BasicTable/index.jsx +++ /dev/null @@ -1,360 +0,0 @@ -/* TODO - -Should we delete this? -*/ - -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.primary.main, - }, - }, - }, - }} - sx={{ - mt: theme.spacing(6), - color: theme.palette.primary.contrastTextSecondary, - "& svg path": { - stroke: theme.palette.primary.contrastTextTertiary, - strokeWidth: 1.3, - }, - "& .MuiSelect-select": { - border: 1, - borderColor: theme.palette.primary.lowContrast, - 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 e67b9ecb09231e7c454c2518f6ea823eb08b7204 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 10:58:19 -0800 Subject: [PATCH 038/106] update fallback component --- Client/src/Components/Fallback/index.jsx | 105 +++++++++++++---------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx index 61840452f..48d9e2198 100644 --- a/Client/src/Components/Fallback/index.jsx +++ b/Client/src/Components/Fallback/index.jsx @@ -26,58 +26,69 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => const mode = useSelector((state) => state.ui.mode); return ( - - {mode === "light" ? ( - - ) : ( - - )} - - - - + ) : ( + + )} + - {vowelStart ? "An" : "A"} {title} is used to: - - {checks.map((check, index) => ( - - ))} + +
+ + + {vowelStart ? "An" : "A"} {title} is used to: + + {checks.map((check, index) => ( + + ))} + + {/* TODO - display a different fallback if user is not an admin*/} + {isAdmin && ( + + )} - {/* TODO - display a different fallback if user is not an admin*/} - {isAdmin && ( - - )} - +
); }; From ac669d8fe14bd982424dbf26bd0308f4c8501706 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 11:03:15 -0800 Subject: [PATCH 039/106] standardize directory structure --- .../Components}/CustomThreshold/index.jsx | 4 ++-- .../Pages/Infrastructure/{CreateMonitor => Create}/index.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename Client/src/Pages/Infrastructure/{CreateMonitor => Create/Components}/CustomThreshold/index.jsx (96%) rename Client/src/Pages/Infrastructure/{CreateMonitor => Create}/index.jsx (99%) diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx b/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx similarity index 96% rename from Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx rename to Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx index 3220582a8..922cb46d8 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/CustomThreshold/index.jsx +++ b/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx @@ -1,6 +1,6 @@ import { Box, Stack, Typography } from "@mui/material"; -import TextInput from "../../../../Components/Inputs/TextInput"; -import Checkbox from "../../../../Components/Inputs/Checkbox"; +import TextInput from "../../../../../Components/Inputs/TextInput"; +import Checkbox from "../../../../../Components/Inputs/Checkbox"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; diff --git a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx b/Client/src/Pages/Infrastructure/Create/index.jsx similarity index 99% rename from Client/src/Pages/Infrastructure/CreateMonitor/index.jsx rename to Client/src/Pages/Infrastructure/Create/index.jsx index c51b56360..464bdeceb 100644 --- a/Client/src/Pages/Infrastructure/CreateMonitor/index.jsx +++ b/Client/src/Pages/Infrastructure/Create/index.jsx @@ -22,7 +22,7 @@ import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; import { createToast } from "../../../Utils/toastUtils"; import Checkbox from "../../../Components/Inputs/Checkbox"; import Select from "../../../Components/Inputs/Select"; -import { CustomThreshold } from "./CustomThreshold"; +import { CustomThreshold } from "./Components/CustomThreshold"; const SELECT_VALUES = [ { _id: 0.25, name: "15 seconds" }, From 98409991831a3c356de84821062d44196d3058c9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 11:03:46 -0800 Subject: [PATCH 040/106] standardize uptime directory structure, use shared components --- .../Uptime/{CreateUptime => Create}/index.css | 0 .../Uptime/{CreateUptime => Create}/index.jsx | 0 .../Home/Components/StatusBoxes/index.jsx | 2 +- .../Home/Components/UptimeDataTable/index.jsx | 16 +++++++------- Client/src/Pages/Uptime/Home/index.jsx | 21 +++++++++++++++++-- 5 files changed, 28 insertions(+), 11 deletions(-) rename Client/src/Pages/Uptime/{CreateUptime => Create}/index.css (100%) rename Client/src/Pages/Uptime/{CreateUptime => Create}/index.jsx (100%) diff --git a/Client/src/Pages/Uptime/CreateUptime/index.css b/Client/src/Pages/Uptime/Create/index.css similarity index 100% rename from Client/src/Pages/Uptime/CreateUptime/index.css rename to Client/src/Pages/Uptime/Create/index.css diff --git a/Client/src/Pages/Uptime/CreateUptime/index.jsx b/Client/src/Pages/Uptime/Create/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/CreateUptime/index.jsx rename to Client/src/Pages/Uptime/Create/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx index ac9eca47f..aa7b7ee0d 100644 --- a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx @@ -30,7 +30,7 @@ const StatusBoxes = ({ shouldRender, monitorsSummary }) => { }; StatusBoxes.propTypes = { - monitorsSummary: PropTypes.object.isRequired, + monitorsSummary: PropTypes.object, }; export default StatusBoxes; diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx index 936a6ebed..e96705cc1 100644 --- a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx @@ -12,7 +12,9 @@ import { useState } from "react"; import SearchComponent from "../SearchComponent"; import CircularCount from "../../../../../Components/CircularCount"; import LoadingSpinner from "../LoadingSpinner"; +import MonitorCountHeader from "../../../../../Components/MonitorCountHeader"; import UptimeDataTableSkeleton from "./skeleton"; + // Utils import { useTheme } from "@emotion/react"; import useUtils from "../../Hooks/useUtils"; @@ -214,21 +216,19 @@ const UptimeDataTable = (props) => { flex={1} py={theme.spacing(8)} > - - Uptime monitors - - + { }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; + if (!isLoading && monitors?.length === 0) { + return ( + + ); + } + return ( - + Date: Tue, 28 Jan 2025 11:04:11 -0800 Subject: [PATCH 041/106] Refactor pagespeed page --- .../Components/CreateMonitorHeader/index.jsx | 33 ++++ .../CreateMonitorHeader/skeleton.jsx | 19 +++ .../Components/MonitorCountHeader/index.jsx | 40 +++++ .../MonitorCountHeader/skeleton.jsx | 26 +++ .../{CreatePageSpeed => Create}/index.css | 0 .../{CreatePageSpeed => Create}/index.jsx | 0 .../Components/Card/index.jsx} | 11 +- .../Monitors/Components/MonitorGrid/index.jsx | 22 +++ .../Monitors/Hooks/useMonitorsFetch.jsx | 43 +++++ Client/src/Pages/PageSpeed/Monitors/index.jsx | 66 ++++++++ Client/src/Pages/PageSpeed/index.css | 8 - Client/src/Pages/PageSpeed/index.jsx | 153 ------------------ Client/src/Pages/PageSpeed/skeleton.jsx | 73 --------- Client/src/Routes/index.jsx | 67 ++++---- 14 files changed, 295 insertions(+), 266 deletions(-) create mode 100644 Client/src/Components/CreateMonitorHeader/index.jsx create mode 100644 Client/src/Components/CreateMonitorHeader/skeleton.jsx create mode 100644 Client/src/Components/MonitorCountHeader/index.jsx create mode 100644 Client/src/Components/MonitorCountHeader/skeleton.jsx rename Client/src/Pages/PageSpeed/{CreatePageSpeed => Create}/index.css (100%) rename Client/src/Pages/PageSpeed/{CreatePageSpeed => Create}/index.jsx (100%) rename Client/src/Pages/PageSpeed/{card.jsx => Monitors/Components/Card/index.jsx} (94%) create mode 100644 Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx create mode 100644 Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx create mode 100644 Client/src/Pages/PageSpeed/Monitors/index.jsx delete mode 100644 Client/src/Pages/PageSpeed/index.css delete mode 100644 Client/src/Pages/PageSpeed/index.jsx delete mode 100644 Client/src/Pages/PageSpeed/skeleton.jsx diff --git a/Client/src/Components/CreateMonitorHeader/index.jsx b/Client/src/Components/CreateMonitorHeader/index.jsx new file mode 100644 index 000000000..b8fcbc39a --- /dev/null +++ b/Client/src/Components/CreateMonitorHeader/index.jsx @@ -0,0 +1,33 @@ +import { Stack, Button } from "@mui/material"; +import { useNavigate } from "react-router-dom"; +import PropTypes from "prop-types"; +import SkeletonLayout from "./skeleton"; + +const CreateMonitorHeader = ({ isAdmin, shouldRender, path }) => { + const navigate = useNavigate(); + if (!isAdmin) return null; + if (!shouldRender) return ; + return ( + + + + ); +}; + +export default CreateMonitorHeader; + +CreateMonitorHeader.propTypes = { + isAdmin: PropTypes.bool.isRequired, + shouldRender: PropTypes.bool.isRequired, + path: PropTypes.string.isRequired, +}; diff --git a/Client/src/Components/CreateMonitorHeader/skeleton.jsx b/Client/src/Components/CreateMonitorHeader/skeleton.jsx new file mode 100644 index 000000000..7583f7849 --- /dev/null +++ b/Client/src/Components/CreateMonitorHeader/skeleton.jsx @@ -0,0 +1,19 @@ +import { Stack, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Components/MonitorCountHeader/index.jsx b/Client/src/Components/MonitorCountHeader/index.jsx new file mode 100644 index 000000000..f7c31a5a0 --- /dev/null +++ b/Client/src/Components/MonitorCountHeader/index.jsx @@ -0,0 +1,40 @@ +import { Stack } from "@mui/material"; +import { Heading } from "../Heading"; +import CircularCount from "../CircularCount"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import SkeletonLayout from "./skeleton"; + +const MonitorCountHeader = ({ + shouldRender = true, + monitorCount, + heading, + sx, + children, +}) => { + const theme = useTheme(); + if (!shouldRender) return ; + + return ( + + {heading} + + {children} + + ); +}; + +MonitorCountHeader.propTypes = { + shouldRender: PropTypes.bool, + monitorCount: PropTypes.number, + heading: PropTypes.string, + children: PropTypes.node, + sx: PropTypes.object, +}; + +export default MonitorCountHeader; diff --git a/Client/src/Components/MonitorCountHeader/skeleton.jsx b/Client/src/Components/MonitorCountHeader/skeleton.jsx new file mode 100644 index 000000000..9b4971c32 --- /dev/null +++ b/Client/src/Components/MonitorCountHeader/skeleton.jsx @@ -0,0 +1,26 @@ +import { Stack, Skeleton } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +const SkeletonLayout = () => { + const theme = useTheme(); + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.css b/Client/src/Pages/PageSpeed/Create/index.css similarity index 100% rename from Client/src/Pages/PageSpeed/CreatePageSpeed/index.css rename to Client/src/Pages/PageSpeed/Create/index.css diff --git a/Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx b/Client/src/Pages/PageSpeed/Create/index.jsx similarity index 100% rename from Client/src/Pages/PageSpeed/CreatePageSpeed/index.jsx rename to Client/src/Pages/PageSpeed/Create/index.jsx diff --git a/Client/src/Pages/PageSpeed/card.jsx b/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx similarity index 94% rename from Client/src/Pages/PageSpeed/card.jsx rename to Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx index f7f6c4249..fe05da514 100644 --- a/Client/src/Pages/PageSpeed/card.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx @@ -1,15 +1,16 @@ import PropTypes from "prop-types"; -import PageSpeedIcon from "../../assets/icons/page-speed.svg?react"; -import { StatusLabel } from "../../Components/Label"; +// import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react"; +import PageSpeedIcon from "../../../../../assets/icons/page-speed.svg?react"; +import { StatusLabel } from "../../../../../Components/Label"; import { Box, Grid, Stack, Typography } from "@mui/material"; import { useNavigate } from "react-router"; import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; -import { formatDateWithTz, formatDurationSplit } from "../../Utils/timeUtils"; -import useUtils from "../Uptime/Home/Hooks/useUtils"; +import { formatDateWithTz, formatDurationSplit } from "../../../../../Utils/timeUtils"; +import useUtils from "../../../../Uptime/Home/Hooks/useUtils"; import { useState } from "react"; -import IconBox from "../../Components/IconBox"; +import IconBox from "../../../../../Components/IconBox"; /** * CustomToolTip displays a tooltip with formatted date and score information. * @param {Object} props diff --git a/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx new file mode 100644 index 000000000..0e61c9c15 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx @@ -0,0 +1,22 @@ +import { Grid, Grid2 } from "@mui/material"; +import Card from "../Card"; + +const MonitorGrid = ({ shouldRender, monitors }) => { + if (!shouldRender) return null; + + return ( + + {monitors?.map((monitor) => ( + + ))} + + ); +}; + +export default MonitorGrid; diff --git a/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx new file mode 100644 index 000000000..06d423acc --- /dev/null +++ b/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; + +const useMonitorsFetch = ({ authToken, teamId }) => { + const [isLoading, setIsLoading] = useState(true); + const [monitors, setMonitors] = useState([]); + const [summary, setSummary] = useState({}); + + useEffect(() => { + const fetchMonitors = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorsByTeamId({ + authToken: authToken, + teamId: teamId, + limit: 10, + types: ["pagespeed"], + page: null, + rowsPerPage: null, + filter: null, + field: null, + order: null, + }); + if (res?.data?.data?.filteredMonitors) { + setMonitors(res.data.data.filteredMonitors); + setSummary(res.data.data.summary); + } + } catch (error) { + createToast({ + body: error.message, + }); + } finally { + setIsLoading(false); + } + }; + + fetchMonitors(); + }, [authToken, teamId]); + return { isLoading, monitors, summary }; +}; + +export default useMonitorsFetch; diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx new file mode 100644 index 000000000..9376d07ce --- /dev/null +++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx @@ -0,0 +1,66 @@ +// Components +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import { Stack, Box } from "@mui/material"; +import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; +import MonitorCountHeader from "../../../Components/MonitorCountHeader"; +import MonitorGrid from "./Components/MonitorGrid"; +import Fallback from "../../../Components/Fallback"; + +// Utils +import { useTheme } from "@emotion/react"; +import { useSelector } from "react-redux"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import useMonitorsFetch from "./Hooks/useMonitorsFetch"; + +// Constants +const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; + +const NewPageSpeed = () => { + const theme = useTheme(); + const isAdmin = useIsAdmin(); + const { user, authToken } = useSelector((state) => state.auth); + + const { isLoading, monitors, summary } = useMonitorsFetch({ + authToken: authToken, + teamId: user.teamId, + }); + + if (!isLoading && monitors?.length === 0) { + return ( + + ); + } + + return ( + + + + + + + ); +}; + +export default NewPageSpeed; diff --git a/Client/src/Pages/PageSpeed/index.css b/Client/src/Pages/PageSpeed/index.css deleted file mode 100644 index 3741a6f0f..000000000 --- a/Client/src/Pages/PageSpeed/index.css +++ /dev/null @@ -1,8 +0,0 @@ -.page-speed .label { - padding: 7px; - height: 24px; - font-size: var(--env-var-font-size-small); -} -.page-speed:not(:has([class*="fallback__"])) button { - min-height: 34px; -} diff --git a/Client/src/Pages/PageSpeed/index.jsx b/Client/src/Pages/PageSpeed/index.jsx deleted file mode 100644 index c3d37e2c0..000000000 --- a/Client/src/Pages/PageSpeed/index.jsx +++ /dev/null @@ -1,153 +0,0 @@ -import { Box, Button, Grid, Stack } from "@mui/material"; -import { useEffect, useState } from "react"; -import { useTheme } from "@emotion/react"; -import { useSelector } from "react-redux"; -import Fallback from "../../Components/Fallback"; -import "./index.css"; -import { useNavigate } from "react-router"; -import PropTypes from "prop-types"; -import Breadcrumbs from "../../Components/Breadcrumbs"; -import SkeletonLayout from "./skeleton"; -import Card from "./card"; -import { networkService } from "../../main"; -import { Heading } from "../../Components/Heading"; -import { useIsAdmin } from "../../Hooks/useIsAdmin"; -const PageSpeed = () => { - const theme = useTheme(); - const navigate = useNavigate(); - const isAdmin = useIsAdmin(); - const { user, authToken } = useSelector((state) => state.auth); - const [isLoading, setIsLoading] = useState(true); - const [monitors, setMonitors] = useState([]); - const [summary, setSummary] = useState({}); - - useEffect(() => { - const fetchMonitors = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorsByTeamId({ - authToken: authToken, - teamId: user.teamId, - limit: 10, - types: ["pagespeed"], - page: null, - rowsPerPage: null, - filter: null, - field: null, - order: null, - }); - if (res?.data?.data?.filteredMonitors) { - setMonitors(res.data.data.filteredMonitors); - setSummary(res.data.data.summary); - } - } catch (error) { - console.log(error); - } finally { - setIsLoading(false); - } - }; - - fetchMonitors(); - }, [authToken, user.teamId]); - - // will show skeletons only on initial load - // since monitor state is being added to redux persist, there's no reason to display skeletons on every render - let isActuallyLoading = isLoading && monitors?.length === 0; - return ( - [class*="fallback__"])': { - position: "relative", - border: 1, - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - borderStyle: "dashed", - backgroundColor: theme.palette.primary.main, - overflow: "hidden", - }, - }} - > - {isActuallyLoading ? ( - - ) : monitors?.length !== 0 ? ( - - - - - {isAdmin && ( - - )} - - - - PageSpeed monitors - {/* TODO Correct the class current-monitors-counter, there are some unnecessary things there */} - - {summary?.totalMonitors ?? 0} - - - - {monitors?.map((monitor) => ( - - ))} - - - ) : ( - - )} - - ); -}; -PageSpeed.propTypes = { - isAdmin: PropTypes.bool, -}; - -export default PageSpeed; diff --git a/Client/src/Pages/PageSpeed/skeleton.jsx b/Client/src/Pages/PageSpeed/skeleton.jsx deleted file mode 100644 index b6d29dc38..000000000 --- a/Client/src/Pages/PageSpeed/skeleton.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 575c108cf..df0af2618 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -1,36 +1,49 @@ import { Navigate, Route, Routes as LibRoutes } from "react-router"; import HomeLayout from "../Components/Layouts/HomeLayout"; import { Infrastructure } from "../Pages/Infrastructure"; -import InfrastructureDetails from "../Pages/Infrastructure/Details"; import NotFound from "../Pages/NotFound"; -import Login from "../Pages/Auth/Login/Login"; -import Register from "../Pages/Auth/Register/Register"; -import Account from "../Pages/Account"; + +// Auth +import AuthLogin from "../Pages/Auth/Login/Login"; +import AuthRegister from "../Pages/Auth/Register/Register"; +import AuthForgotPassword from "../Pages/Auth/ForgotPassword"; +import AuthCheckEmail from "../Pages/Auth/CheckEmail"; +import AuthSetNewPassword from "../Pages/Auth/SetNewPassword"; +import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; + +// Uptime import Uptime from "../Pages/Uptime/Home"; import UptimeDetails from "../Pages/Uptime/Details"; +import UptimeCreate from "../Pages/Uptime/Create"; +import UptimeConfigure from "../Pages/Uptime/Configure"; + +// PageSpeed +// import PageSpeed from "../Pages/PageSpeed"; +import PageSpeed from "../Pages/PageSpeed/Monitors"; +import PageSpeedCreate from "../Pages/PageSpeed/Create"; +import PageSpeedDetails from "../Pages/PageSpeed/Details"; +import PageSpeedConfigure from "../Pages/PageSpeed/Configure"; + +// Infrastructure +import InfrastructureCreate from "../Pages/Infrastructure/Create"; +import InfrastructureDetails from "../Pages/Infrastructure/Details"; -import CreateMonitor from "../Pages/Uptime/CreateUptime"; -import CreateInfrastructureMonitor from "../Pages/Infrastructure/CreateMonitor"; import Incidents from "../Pages/Incidents"; import Status from "../Pages/Status"; import Integrations from "../Pages/Integrations"; + +// Settings +import Account from "../Pages/Account"; import Settings from "../Pages/Settings"; -import ForgotPassword from "../Pages/Auth/ForgotPassword"; -import CheckEmail from "../Pages/Auth/CheckEmail"; -import SetNewPassword from "../Pages/Auth/SetNewPassword"; -import NewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; -import ProtectedRoute from "../Components/ProtectedRoute"; + import Maintenance from "../Pages/Maintenance"; -import Configure from "../Pages/Uptime/Configure"; -import PageSpeed from "../Pages/PageSpeed"; -import CreatePageSpeed from "../Pages/PageSpeed/CreatePageSpeed"; + +import ProtectedRoute from "../Components/ProtectedRoute"; import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance"; -import PageSpeedDetails from "../Pages/PageSpeed/Details"; -import PageSpeedConfigure from "../Pages/PageSpeed/Configure"; import withAdminCheck from "../Components/HOC/withAdminCheck"; const Routes = () => { - const AdminCheckedRegister = withAdminCheck(Register); + const AdminCheckedRegister = withAdminCheck(AuthRegister); return ( { } + element={} /> { /> } + element={} /> { /> } + element={} /> { /> } + element={} /> { } + element={} /> { } + element={} /> } + element={} /> } + element={} /> } + element={} /> } + element={} /> Date: Tue, 28 Jan 2025 11:05:06 -0800 Subject: [PATCH 042/106] standardize directory names --- Client/src/Components/StatBox/index.jsx | 2 +- .../src/Pages/Infrastructure/Details/index.jsx | 2 +- Client/src/Pages/Infrastructure/index.jsx | 17 +++++++++-------- Client/src/Pages/PageSpeed/Configure/index.jsx | 2 +- Client/src/Pages/PageSpeed/Details/index.jsx | 2 +- .../Monitors/Components/Card/index.jsx | 2 +- .../Details/Components/MonitorHeader/index.jsx | 2 +- .../Details/Components/StatusBoxes/index.jsx | 2 +- .../Components/ActionsMenu/index.jsx | 0 .../Components/Host/index.jsx | 0 .../Components/LoadingSpinner/index.jsx | 0 .../Components/SearchComponent/index.jsx | 0 .../Components/Skeleton/index.jsx | 0 .../Components/StatusBoxes/index.jsx | 0 .../Components/StatusBoxes/skeleton.jsx | 0 .../Components/StatusBoxes/statusBox.jsx | 0 .../Components/UptimeDataTable/index.jsx | 0 .../Components/UptimeDataTable/skeleton.jsx | 0 .../{Home => Monitors}/Hooks/useDebounce.jsx | 0 .../Hooks/useMonitorsFetch.jsx | 0 .../{Home => Monitors}/Hooks/useUtils.jsx | 0 .../Pages/Uptime/{Home => Monitors}/index.jsx | 0 Client/src/Routes/index.jsx | 2 +- 23 files changed, 17 insertions(+), 16 deletions(-) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/ActionsMenu/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/Host/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/LoadingSpinner/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/SearchComponent/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/Skeleton/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/StatusBoxes/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/StatusBoxes/skeleton.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/StatusBoxes/statusBox.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/UptimeDataTable/index.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Components/UptimeDataTable/skeleton.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Hooks/useDebounce.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Hooks/useMonitorsFetch.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/Hooks/useUtils.jsx (100%) rename Client/src/Pages/Uptime/{Home => Monitors}/index.jsx (100%) diff --git a/Client/src/Components/StatBox/index.jsx b/Client/src/Components/StatBox/index.jsx index b476a1c33..4b1fd2454 100644 --- a/Client/src/Components/StatBox/index.jsx +++ b/Client/src/Components/StatBox/index.jsx @@ -1,7 +1,7 @@ import { Box, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -import useUtils from "../../Pages/Uptime/Home/Hooks/useUtils"; +import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils"; /** * StatBox Component diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 07fbd7c06..9d6b09330 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -8,7 +8,7 @@ import AreaChart from "../../../Components/Charts/AreaChart"; import { useSelector } from "react-redux"; import { networkService } from "../../../main"; import PulseDot from "../../../Components/Animated/PulseDot"; -import useUtils from "../../Uptime/Home/Hooks/useUtils"; +import useUtils from "../../Uptime/Monitors/Hooks/useUtils"; import { useNavigate } from "react-router-dom"; import Empty from "./empty"; import { logger } from "../../../Utils/Logger"; diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx index 3f295d434..91367ce22 100644 --- a/Client/src/Pages/Infrastructure/index.jsx +++ b/Client/src/Pages/Infrastructure/index.jsx @@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from "react"; import { useNavigate } from "react-router-dom"; import { /* useDispatch, */ useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; -import useUtils from "../Uptime/Home/Hooks/useUtils.jsx"; +import useUtils from "../Uptime/Monitors/Hooks/useUtils.jsx"; import { jwtDecode } from "jwt-decode"; import SkeletonLayout from "./skeleton"; import Fallback from "../../Components/Fallback"; @@ -17,7 +17,7 @@ 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"; -import Host from "../Uptime/Home/Components/Host"; +import Host from "../Uptime/Monitors/Components/Host/index.jsx"; import { useIsAdmin } from "../../Hooks/useIsAdmin.js"; import { InfrastructureMenu } from "./components/Menu"; @@ -156,12 +156,13 @@ function Infrastructure() { const uptimePercentage = ((monitor?.uptimePercentage ?? 0) * 100) .toFixed(2) .toString(); - const percentageColor = monitor.uptimePercentage < 0.25 - ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 - ? theme.palette.warning.main - : theme.palette.success.main; - + const percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.error.main + : monitor.uptimePercentage < 0.5 + ? theme.palette.warning.main + : theme.palette.success.main; + return { id: monitor._id, name: monitor.name, diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx index eee6dfcc9..a76f496cb 100644 --- a/Client/src/Pages/PageSpeed/Configure/index.jsx +++ b/Client/src/Pages/PageSpeed/Configure/index.jsx @@ -23,7 +23,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import LoadingButton from "@mui/lab/LoadingButton"; import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; import SkeletonLayout from "./skeleton"; -import useUtils from "../../Uptime/Home/Hooks/useUtils"; +import useUtils from "../../Uptime/Monitors/Hooks/useUtils"; import "./index.css"; import Dialog from "../../../Components/Dialog"; diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 9192faaa9..4c4bceb1b 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -19,7 +19,7 @@ import PulseDot from "../../../Components/Animated/PulseDot"; import PagespeedDetailsAreaChart from "./Charts/AreaChart"; import Checkbox from "../../../Components/Inputs/Checkbox"; import PieChart from "./Charts/PieChart"; -import useUtils from "../../Uptime/Home/Hooks/useUtils"; +import useUtils from "../../Uptime/Monitors/Hooks/useUtils"; import "./index.css"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import StatBox from "../../../Components/StatBox"; diff --git a/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx b/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx index fe05da514..42abca268 100644 --- a/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/Components/Card/index.jsx @@ -8,7 +8,7 @@ import { useTheme } from "@emotion/react"; import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts"; import { useSelector } from "react-redux"; import { formatDateWithTz, formatDurationSplit } from "../../../../../Utils/timeUtils"; -import useUtils from "../../../../Uptime/Home/Hooks/useUtils"; +import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils"; import { useState } from "react"; import IconBox from "../../../../../Components/IconBox"; /** diff --git a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx index fddf9c3e5..53d4c4dfa 100644 --- a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import PulseDot from "../../../../../Components/Animated/PulseDot"; import Dot from "../../../../../Components/Dot"; import { useTheme } from "@emotion/react"; -import useUtils from "../../../Home/Hooks/useUtils"; +import useUtils from "../../../Monitors/Hooks/useUtils"; import { formatDurationRounded } from "../../../../../Utils/timeUtils"; import ConfigButton from "../ConfigButton"; import SkeletonLayout from "./skeleton"; diff --git a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx index 21922537f..3f483889b 100644 --- a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx @@ -4,7 +4,7 @@ import StatBox from "../../../../../Components/StatBox"; import SkeletonLayout from "./skeleton"; // Utils import { useTheme } from "@mui/material/styles"; -import useUtils from "../../../Home/Hooks/useUtils"; +import useUtils from "../../../Monitors/Hooks/useUtils"; import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; import PropTypes from "prop-types"; const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { diff --git a/Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/ActionsMenu/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/ActionsMenu/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/ActionsMenu/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/Host/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/Host/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/Host/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/Host/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/LoadingSpinner/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/LoadingSpinner/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/SearchComponent/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/Skeleton/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/Skeleton/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/StatusBoxes/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx b/Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/StatusBoxes/skeleton.jsx rename to Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx b/Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/StatusBoxes/statusBox.jsx rename to Client/src/Pages/Uptime/Monitors/Components/StatusBoxes/statusBox.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/UptimeDataTable/index.jsx rename to Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx diff --git a/Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Components/UptimeDataTable/skeleton.jsx rename to Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx diff --git a/Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx b/Client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Hooks/useDebounce.jsx rename to Client/src/Pages/Uptime/Monitors/Hooks/useDebounce.jsx diff --git a/Client/src/Pages/Uptime/Home/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Hooks/useMonitorsFetch.jsx rename to Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/Uptime/Home/Hooks/useUtils.jsx b/Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/Hooks/useUtils.jsx rename to Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx diff --git a/Client/src/Pages/Uptime/Home/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx similarity index 100% rename from Client/src/Pages/Uptime/Home/index.jsx rename to Client/src/Pages/Uptime/Monitors/index.jsx diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index df0af2618..a4df5b855 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -12,7 +12,7 @@ import AuthSetNewPassword from "../Pages/Auth/SetNewPassword"; import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed"; // Uptime -import Uptime from "../Pages/Uptime/Home"; +import Uptime from "../Pages/Uptime/Monitors"; import UptimeDetails from "../Pages/Uptime/Details"; import UptimeCreate from "../Pages/Uptime/Create"; import UptimeConfigure from "../Pages/Uptime/Configure"; From 5880993565b04a9a91e63b3bb878ed0e34540377 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 11:06:24 -0800 Subject: [PATCH 043/106] fix PageSpeed name --- Client/src/Pages/PageSpeed/Monitors/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx index 9376d07ce..228e522ff 100644 --- a/Client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx @@ -15,7 +15,7 @@ import useMonitorsFetch from "./Hooks/useMonitorsFetch"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; -const NewPageSpeed = () => { +const PageSpeed = () => { const theme = useTheme(); const isAdmin = useIsAdmin(); const { user, authToken } = useSelector((state) => state.auth); @@ -63,4 +63,4 @@ const NewPageSpeed = () => { ); }; -export default NewPageSpeed; +export default PageSpeed; From ff17092e8a9d111ba6fec81f8cd15cba3142b413 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 15:35:52 -0800 Subject: [PATCH 044/106] fix fallback condtion for Uptime --- Client/src/Pages/Uptime/Monitors/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx index 852ef2b4f..d60b5c495 100644 --- a/Client/src/Pages/Uptime/Monitors/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/index.jsx @@ -100,7 +100,7 @@ const UptimeMonitors = () => { }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; - if (!isLoading && monitors?.length === 0) { + if (!isLoading && !monitorsAreLoading && monitors?.length === 0) { return ( Date: Tue, 28 Jan 2025 15:37:01 -0800 Subject: [PATCH 045/106] Passing as a child of breaks search wehn MonitorCountHeader rerenders --- .../Components/UptimeDataTable/index.jsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx index e96705cc1..1940c5543 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -1,6 +1,5 @@ // Components -import { Box, Stack, CircularProgress } from "@mui/material"; -import { Heading } from "../../../../../Components/Heading"; +import { Box, Stack } from "@mui/material"; import DataTable from "../../../../../Components/Table"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; @@ -12,8 +11,8 @@ import { useState } from "react"; import SearchComponent from "../SearchComponent"; import CircularCount from "../../../../../Components/CircularCount"; import LoadingSpinner from "../LoadingSpinner"; -import MonitorCountHeader from "../../../../../Components/MonitorCountHeader"; import UptimeDataTableSkeleton from "./skeleton"; +import { Heading } from "../../../../../Components/Heading"; // Utils import { useTheme } from "@emotion/react"; @@ -216,19 +215,21 @@ const UptimeDataTable = (props) => { flex={1} py={theme.spacing(8)} > - + Uptime monitors + - + Date: Tue, 28 Jan 2025 15:37:49 -0800 Subject: [PATCH 046/106] fix key error in Search --- Client/src/Components/Inputs/Search/index.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index 1dff6b532..95930b0ec 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -139,7 +139,9 @@ const Search = ({ } return filtered; }} - getOptionKey={(option) => option.id} + getOptionKey={(option) => { + return option._id; + }} renderOption={(props, option) => { const { key, ...optionProps } = props; return ( @@ -196,6 +198,7 @@ const Search = ({ }; Search.propTypes = { + label: PropTypes.string, id: PropTypes.string, multiple: PropTypes.bool, options: PropTypes.array.isRequired, From 577c0b5d196d0406aae6bd60fe6b14a72decb509 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 15:38:14 -0800 Subject: [PATCH 047/106] Safe property access in fallback --- Client/src/Components/Fallback/index.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx index 48d9e2198..e5029d276 100644 --- a/Client/src/Components/Fallback/index.jsx +++ b/Client/src/Components/Fallback/index.jsx @@ -32,12 +32,14 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => border={1} borderColor={theme.palette.primary.lowContrast} borderRadius={theme.shape.borderRadius} - borderStyle="dashed" backgroundColor={theme.palette.primary.main} overflow="hidden" + sx={{ + borderStyle: "dashed", + }} > @@ -68,7 +70,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => > {vowelStart ? "An" : "A"} {title} is used to: - {checks.map((check, index) => ( + {checks?.map((check, index) => ( Date: Tue, 28 Jan 2025 15:38:43 -0800 Subject: [PATCH 048/106] fix incorrect variant type in --- Client/src/Utils/toastUtils.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Utils/toastUtils.jsx b/Client/src/Utils/toastUtils.jsx index 5920d06a7..dbec9117e 100644 --- a/Client/src/Utils/toastUtils.jsx +++ b/Client/src/Utils/toastUtils.jsx @@ -12,7 +12,7 @@ import Alert from "../Components/Alert"; */ export const createToast = ({ - variant = "secondary", + variant = "info", title, body, hasIcon = false, From 784dc4da634dcb9d0f3a36582c56a91682ec61e4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 15:42:09 -0800 Subject: [PATCH 049/106] Refactor reusable components out of Uptime Details --- Client/src/Components/StatusBoxes/index.jsx | 28 +++++++ .../src/Components/StatusBoxes/skeleton.jsx | 31 ++++++++ .../Details/Components/ChartBoxes/index.jsx | 8 +- .../Details/Components/Charts/ChartBox.jsx | 75 ------------------- .../Components/Charts/ResponseTimeChart.jsx | 2 +- .../Details/Components/ConfigButton/index.jsx | 40 ---------- .../Components/MonitorHeader/index.jsx | 55 -------------- .../Components/MonitorHeader/skeleton.jsx | 23 ------ .../Components/ResponseTable/index.jsx | 2 +- .../Components/StatusBoxes/skeleton.jsx | 30 -------- .../index.jsx | 35 +++------ Client/src/Pages/Uptime/Details/index.jsx | 8 +- 12 files changed, 80 insertions(+), 257 deletions(-) create mode 100644 Client/src/Components/StatusBoxes/index.jsx create mode 100644 Client/src/Components/StatusBoxes/skeleton.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx delete mode 100644 Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx rename Client/src/Pages/Uptime/Details/Components/{StatusBoxes => UptimeStatusBoxes}/index.jsx (73%) diff --git a/Client/src/Components/StatusBoxes/index.jsx b/Client/src/Components/StatusBoxes/index.jsx new file mode 100644 index 000000000..6e09d0cba --- /dev/null +++ b/Client/src/Components/StatusBoxes/index.jsx @@ -0,0 +1,28 @@ +// Components +import { Stack } from "@mui/material"; +import SkeletonLayout from "./skeleton"; +// Utils +import { useTheme } from "@mui/material/styles"; +import PropTypes from "prop-types"; +const StatusBoxes = ({ shouldRender, children }) => { + const theme = useTheme(); + if (!shouldRender) { + return ; + } + + return ( + + {children} + + ); +}; + +StatusBoxes.propTypes = { + shouldRender: PropTypes.bool, + children: PropTypes.node, +}; + +export default StatusBoxes; diff --git a/Client/src/Components/StatusBoxes/skeleton.jsx b/Client/src/Components/StatusBoxes/skeleton.jsx new file mode 100644 index 000000000..b0f04ff3f --- /dev/null +++ b/Client/src/Components/StatusBoxes/skeleton.jsx @@ -0,0 +1,31 @@ +import { Stack, Skeleton } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; +const SkeletonLayout = ({ numBoxes }) => { + const theme = useTheme(); + return ( + + {Array.from({ length: numBoxes }).map((_, index) => { + const width = `${100 / numBoxes}%`; + return ( + + ); + })} + + ); +}; + +SkeletonLayout.propTypes = { + numBoxes: PropTypes.number, +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx index d9e581267..5565529fb 100644 --- a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx @@ -1,6 +1,6 @@ // Components import { Stack, Typography, Box } from "@mui/material"; -import ChartBox from "../Charts/ChartBox"; +import ChartBox from "../../../../../Components/Charts/ChartBox"; import UptimeIcon from "../../../../../assets/icons/uptime-icon.svg?react"; import IncidentsIcon from "../../../../../assets/icons/incidents.svg?react"; import AverageResponseIcon from "../../../../../assets/icons/average-response-icon.svg?react"; @@ -40,7 +40,10 @@ const ChartBoxes = ({ icon={} header="Uptime" > - + Total Checks @@ -129,6 +132,7 @@ const ChartBoxes = ({ export default ChartBoxes; ChartBoxes.propTypes = { + shouldRender: PropTypes.bool, monitor: PropTypes.object.isRequired, dateRange: PropTypes.string.isRequired, uiTimezone: PropTypes.string.isRequired, diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx deleted file mode 100644 index 3d4d73421..000000000 --- a/Client/src/Pages/Uptime/Details/Components/Charts/ChartBox.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import IconBox from "../../../../../Components/IconBox"; -import PropTypes from "prop-types"; -const ChartBox = ({ children, icon, header, height = "300px" }) => { - const theme = useTheme(); - return ( - span": { - color: theme.palette.primary.contrastText, - fontSize: 20, - "& span": { - opacity: 0.8, - marginLeft: 2, - fontSize: 15, - }, - }, - "& .MuiStack-root": { - flexDirection: "row", - gap: theme.spacing(6), - }, - "& .MuiStack-root:first-of-type": { - alignItems: "center", - }, - "& tspan, & text": { - fill: theme.palette.primary.contrastTextTertiary, - }, - "& path": { - transition: "fill 300ms ease, stroke-width 400ms ease", - }, - }} - > - - {icon} - {header} - - - {children} - - ); -}; - -export default ChartBox; - -ChartBox.propTypes = { - children: PropTypes.node, - icon: PropTypes.node.isRequired, - header: PropTypes.string.isRequired, - height: PropTypes.string, -}; diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index 79e9473ef..8be1125c2 100644 --- a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -1,4 +1,4 @@ -import ChartBox from "./ChartBox"; +import ChartBox from "../../../../../Components/Charts/ChartBox"; import MonitorDetailsAreaChart from "../../../../../Components/Charts/MonitorDetailsAreaChart"; import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; import SkeletonLayout from "./ResponseTimeChartSkeleton"; diff --git a/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx b/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx deleted file mode 100644 index 467655abf..000000000 --- a/Client/src/Pages/Uptime/Details/Components/ConfigButton/index.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Button, Box } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router-dom"; -import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react"; -import PropTypes from "prop-types"; -const ConfigButton = ({ shouldRender, monitorId }) => { - const theme = useTheme(); - const navigate = useNavigate(); - - if (!shouldRender) return null; - - return ( - - - - ); -}; - -ConfigButton.propTypes = { - shouldRender: PropTypes.bool, - monitorId: PropTypes.string, -}; - -export default ConfigButton; diff --git a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx deleted file mode 100644 index 53d4c4dfa..000000000 --- a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/index.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import PulseDot from "../../../../../Components/Animated/PulseDot"; -import Dot from "../../../../../Components/Dot"; -import { useTheme } from "@emotion/react"; -import useUtils from "../../../Monitors/Hooks/useUtils"; -import { formatDurationRounded } from "../../../../../Utils/timeUtils"; -import ConfigButton from "../ConfigButton"; -import SkeletonLayout from "./skeleton"; -import PropTypes from "prop-types"; - -const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => { - const theme = useTheme(); - const { statusColor, statusMsg, determineState } = useUtils(); - console.log(shouldRender); - if (!shouldRender) { - return ; - } - - return ( - - - {monitor.name} - - - - {monitor?.url?.replace(/^https?:\/\//, "") || "..."} - - - - Checking every {formatDurationRounded(monitor?.interval)}. - - - - - - ); -}; - -MonitorHeader.propTypes = { - shouldRender: PropTypes.bool, - isAdmin: PropTypes.bool, - monitor: PropTypes.object, -}; - -export default MonitorHeader; diff --git a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx b/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx deleted file mode 100644 index 64dc0547f..000000000 --- a/Client/src/Pages/Uptime/Details/Components/MonitorHeader/skeleton.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; - -const SkeletonLayout = () => { - return ( - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx index 56ea8eca7..1f5a19203 100644 --- a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx @@ -1,4 +1,4 @@ -import ChartBox from "../Charts/ChartBox"; +import ChartBox from "../../../../../Components/Charts/ChartBox"; import PropTypes from "prop-types"; import HistoryIcon from "../../../../../assets/icons/history-icon.svg?react"; import Table from "../../../../../Components/Table"; diff --git a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx b/Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx deleted file mode 100644 index 993b410c8..000000000 --- a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/skeleton.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; -import { useTheme } from "@emotion/react"; -const SkeletonLayout = () => { - const theme = useTheme(); - return ( - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx similarity index 73% rename from Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx rename to Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index 3f483889b..ad4620396 100644 --- a/Client/src/Pages/Uptime/Details/Components/StatusBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -1,20 +1,13 @@ -// Components -import { Stack, Typography } from "@mui/material"; +import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; -import SkeletonLayout from "./skeleton"; -// Utils -import { useTheme } from "@mui/material/styles"; -import useUtils from "../../../Monitors/Hooks/useUtils"; import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; -import PropTypes from "prop-types"; -const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { - // Utils +import { useTheme } from "@mui/material/styles"; +import { Typography } from "@mui/material"; +import useUtils from "../../../Monitors/Hooks/useUtils"; + +const UptimeStatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { const theme = useTheme(); const { determineState } = useUtils(); - - if (!shouldRender) { - return ; - } const { time: streakTime, units: streakUnits } = getHumanReadableDuration( monitor?.uptimeStreak ); @@ -22,12 +15,8 @@ const StatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration( monitor?.timeSinceLastCheck ); - return ( - + { } /> - + ); }; -StatusBoxes.propTypes = { - shouldRender: PropTypes.bool, - monitor: PropTypes.object, - certificateExpiry: PropTypes.string, -}; - -export default StatusBoxes; +export default UptimeStatusBoxes; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 81265eb56..33583b0cb 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -1,11 +1,11 @@ // Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import MonitorHeader from "./Components/MonitorHeader"; -import StatusBoxes from "./Components/StatusBoxes"; +import MonitorStatusHeader from "../../../Components/MonitorStatusHeader"; import TimeFramePicker from "./Components/TimeFramePicker"; import ChartBoxes from "./Components/ChartBoxes"; import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; import ResponseTable from "./Components/ResponseTable"; +import UptimeStatusBoxes from "./Components/UptimeStatusBoxes"; // MUI Components import { Stack } from "@mui/material"; @@ -80,12 +80,12 @@ const UptimeDetails = () => { return ( - - Date: Tue, 28 Jan 2025 15:42:29 -0800 Subject: [PATCH 050/106] fix speedometer name --- .../assets/icons/{spedometer-icon.svg => speedometer-icon.svg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Client/src/assets/icons/{spedometer-icon.svg => speedometer-icon.svg} (100%) diff --git a/Client/src/assets/icons/spedometer-icon.svg b/Client/src/assets/icons/speedometer-icon.svg similarity index 100% rename from Client/src/assets/icons/spedometer-icon.svg rename to Client/src/assets/icons/speedometer-icon.svg From ac2cf490828cfca3f7774b352bd720e96ccdde35 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 15:42:46 -0800 Subject: [PATCH 051/106] Refactor pagespeed details --- .../src/Components/Charts/ChartBox/index.jsx | 90 ++++ .../src/Components/Charts/LegendBox/index.jsx | 42 ++ .../ConfigButton/index.jsx | 40 ++ .../Components/MonitorStatusHeader/index.jsx | 54 ++ .../MonitorStatusHeader/skeleton.jsx | 23 + .../{ => Components}/Charts/AreaChart.jsx | 15 +- .../Components/Charts/AreaChartLegend.jsx | 56 ++ .../{ => Components}/Charts/PieChart.jsx | 6 +- .../Components/Charts/PieChartLegend.jsx | 96 ++++ .../Details/Components/EmptyView/index.jsx | 0 .../Components/PageSpeedAreaChart/index.jsx | 45 ++ .../Components/PageSpeedStatusBoxes/index.jsx | 41 ++ .../Components/PerformanceReport/index.jsx | 45 ++ .../Details/Hooks/useMonitorFetch.jsx | 40 ++ Client/src/Pages/PageSpeed/Details/index.css | 18 - Client/src/Pages/PageSpeed/Details/index.jsx | 477 +++--------------- .../src/Pages/PageSpeed/Details/skeleton.jsx | 93 ---- Client/src/Pages/PageSpeed/Details/styled.jsx | 42 -- Client/src/Pages/PageSpeed/Monitors/index.jsx | 2 +- 19 files changed, 643 insertions(+), 582 deletions(-) create mode 100644 Client/src/Components/Charts/ChartBox/index.jsx create mode 100644 Client/src/Components/Charts/LegendBox/index.jsx create mode 100644 Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx create mode 100644 Client/src/Components/MonitorStatusHeader/index.jsx create mode 100644 Client/src/Components/MonitorStatusHeader/skeleton.jsx rename Client/src/Pages/PageSpeed/Details/{ => Components}/Charts/AreaChart.jsx (95%) create mode 100644 Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx rename Client/src/Pages/PageSpeed/Details/{ => Components}/Charts/PieChart.jsx (99%) create mode 100644 Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx create mode 100644 Client/src/Pages/PageSpeed/Details/Components/EmptyView/index.jsx create mode 100644 Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx create mode 100644 Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx create mode 100644 Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx create mode 100644 Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx delete mode 100644 Client/src/Pages/PageSpeed/Details/index.css delete mode 100644 Client/src/Pages/PageSpeed/Details/skeleton.jsx delete mode 100644 Client/src/Pages/PageSpeed/Details/styled.jsx diff --git a/Client/src/Components/Charts/ChartBox/index.jsx b/Client/src/Components/Charts/ChartBox/index.jsx new file mode 100644 index 000000000..eed3efd0d --- /dev/null +++ b/Client/src/Components/Charts/ChartBox/index.jsx @@ -0,0 +1,90 @@ +import { Stack, Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import IconBox from "../../IconBox"; +import PropTypes from "prop-types"; + +const ChartBox = ({ + children, + icon, + header, + height = "300px", + justifyContent = "space-between", + Legend, + borderRadiusRight = 4, +}) => { + const theme = useTheme(); + return ( + + span": { + color: theme.palette.primary.contrastText, + fontSize: 20, + "& span": { + opacity: 0.8, + marginLeft: 2, + fontSize: 15, + }, + }, + + "& tspan, & text": { + fill: theme.palette.primary.contrastTextTertiary, + }, + "& path": { + transition: "fill 300ms ease, stroke-width 400ms ease", + }, + }} + > + + {icon} + {header} + + {children} + + {Legend && Legend} + + ); +}; + +export default ChartBox; + +ChartBox.propTypes = { + children: PropTypes.node, + icon: PropTypes.node.isRequired, + header: PropTypes.string.isRequired, + height: PropTypes.string, +}; diff --git a/Client/src/Components/Charts/LegendBox/index.jsx b/Client/src/Components/Charts/LegendBox/index.jsx new file mode 100644 index 000000000..ac14669e3 --- /dev/null +++ b/Client/src/Components/Charts/LegendBox/index.jsx @@ -0,0 +1,42 @@ +import { Stack, Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import IconBox from "../../IconBox"; +import PropTypes from "prop-types"; + +const LegendBox = ({ children, icon, header, sx }) => { + const theme = useTheme(); + return ( + + + {icon} + {header} + + {children} + + ); +}; + +LegendBox.propTypes = { + children: PropTypes.node, + icon: PropTypes.node, + header: PropTypes.string, +}; + +export default LegendBox; diff --git a/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx b/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx new file mode 100644 index 000000000..fc6407f31 --- /dev/null +++ b/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx @@ -0,0 +1,40 @@ +import { Button, Box } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import { useNavigate } from "react-router-dom"; +import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; +import PropTypes from "prop-types"; +const ConfigButton = ({ shouldRender, monitorId }) => { + const theme = useTheme(); + const navigate = useNavigate(); + + if (!shouldRender) return null; + + return ( + + + + ); +}; + +ConfigButton.propTypes = { + shouldRender: PropTypes.bool, + monitorId: PropTypes.string, +}; + +export default ConfigButton; diff --git a/Client/src/Components/MonitorStatusHeader/index.jsx b/Client/src/Components/MonitorStatusHeader/index.jsx new file mode 100644 index 000000000..6a7188775 --- /dev/null +++ b/Client/src/Components/MonitorStatusHeader/index.jsx @@ -0,0 +1,54 @@ +import { Stack, Typography } from "@mui/material"; +import PulseDot from "../Animated/PulseDot"; +import Dot from "../Dot"; +import { useTheme } from "@emotion/react"; +import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils"; +import { formatDurationRounded } from "../../Utils/timeUtils"; +import ConfigButton from "./ConfigButton"; +import SkeletonLayout from "./skeleton"; +import PropTypes from "prop-types"; + +const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => { + const theme = useTheme(); + const { statusColor, statusMsg, determineState } = useUtils(); + if (!shouldRender) { + return ; + } + + return ( + + + {monitor.name} + + + + {monitor?.url?.replace(/^https?:\/\//, "") || "..."} + + + + Checking every {formatDurationRounded(monitor?.interval)}. + + + + + + ); +}; + +MonitorHeader.propTypes = { + shouldRender: PropTypes.bool, + isAdmin: PropTypes.bool, + monitor: PropTypes.object, +}; + +export default MonitorHeader; diff --git a/Client/src/Components/MonitorStatusHeader/skeleton.jsx b/Client/src/Components/MonitorStatusHeader/skeleton.jsx new file mode 100644 index 000000000..64dc0547f --- /dev/null +++ b/Client/src/Components/MonitorStatusHeader/skeleton.jsx @@ -0,0 +1,23 @@ +import { Stack, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx similarity index 95% rename from Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx rename to Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx index a854eb094..40fec0ab6 100644 --- a/Client/src/Pages/PageSpeed/Details/Charts/AreaChart.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChart.jsx @@ -11,7 +11,7 @@ import { import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { Box, Stack, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../../Utils/timeUtils"; +import { formatDateWithTz } from "../../../../../Utils/timeUtils"; import { useSelector } from "react-redux"; const config = { @@ -211,10 +211,13 @@ CustomTick.propTypes = { * @returns {JSX.Element} The area chart component. */ -const PagespeedDetailsAreaChart = ({ data, interval, metrics }) => { +const PageSpeedAreaChart = ({ data, monitor, metrics }) => { const theme = useTheme(); const [isHovered, setIsHovered] = useState(false); - const memoizedData = useMemo(() => processDataWithGaps(data, interval), [data[0]]); + const memoizedData = useMemo( + () => processDataWithGaps(data, monitor.interval), + [data[0]] + ); const filteredConfig = Object.keys(config).reduce((result, key) => { if (metrics[key]) { @@ -310,7 +313,7 @@ const PagespeedDetailsAreaChart = ({ data, interval, metrics }) => { ); }; -PagespeedDetailsAreaChart.propTypes = { +PageSpeedAreaChart.propTypes = { data: PropTypes.arrayOf( PropTypes.shape({ createdAt: PropTypes.string.isRequired, @@ -320,8 +323,8 @@ PagespeedDetailsAreaChart.propTypes = { seo: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, }) ).isRequired, - interval: PropTypes.number.isRequired, + monitor: PropTypes.object.isRequired, metrics: PropTypes.object, }; -export default PagespeedDetailsAreaChart; +export default PageSpeedAreaChart; diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx new file mode 100644 index 000000000..1d3d5ddef --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/AreaChartLegend.jsx @@ -0,0 +1,56 @@ +import { Box, Typography, Divider } from "@mui/material"; +import Checkbox from "../../../../../Components/Inputs/Checkbox"; +import MetricsIcon from "../../../../../assets/icons/ruler-icon.svg?react"; +import LegendBox from "../../../../../Components/Charts/LegendBox"; + +import { useTheme } from "@emotion/react"; + +const AreaChartLegend = ({ metrics, handleMetrics }) => { + const theme = useTheme(); + return ( + } + header="Metrics" + > + + + Shown + + + + handleMetrics("accessibility")} + /> + + handleMetrics("bestPractices")} + /> + + handleMetrics("performance")} + /> + + handleMetrics("seo")} + /> + + + ); +}; + +export default AreaChartLegend; diff --git a/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx similarity index 99% rename from Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx rename to Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx index 193841e7e..aad697f9f 100644 --- a/Client/src/Pages/PageSpeed/Details/Charts/PieChart.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx @@ -115,6 +115,10 @@ const weights = { const PieChart = ({ audits }) => { const theme = useTheme(); + const [highlightedItem, setHighLightedItem] = useState(null); + const [expand, setExpand] = useState(false); + + if (typeof audits === "undefined") return null; /** * Retrieves color properties based on the performance value. @@ -216,8 +220,6 @@ const PieChart = ({ audits }) => { const pieData = getPieData(audits); const colorMap = getColors(performance); - const [highlightedItem, setHighLightedItem] = useState(null); - const [expand, setExpand] = useState(false); return ( setExpand(false)}> {expand ? ( diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx new file mode 100644 index 000000000..c7612821f --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -0,0 +1,96 @@ +import { Stack, Box, Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import LegendBox from "../../../../../Components/Charts/LegendBox"; +import SpeedometerIcon from "../../../../../assets/icons/speedometer-icon.svg?react"; +import PropTypes from "prop-types"; + +const PieChartLegend = ({ audits }) => { + const theme = useTheme(); + + if (typeof audits === "undefined") return null; + return ( + } + header="Performance metrics" + sx={{ flex: 1 }} + > + {Object.keys(audits).map((key) => { + if (key === "_id") return; + + let audit = audits[key]; + let score = audit.score * 100; + let bg = + score >= 90 + ? theme.palette.success.main + : score >= 50 + ? theme.palette.warning.main + : score >= 0 + ? theme.palette.error.main + : theme.palette.tertiary.main; + + // Find the position where the number ends and the unit begins + const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/); + let value; + let unit; + if (match) { + value = match[1]; + match[2] === "s" ? (unit = "seconds") : (unit = match[2]); + } else { + value = audit.displayValue; + } + + return ( + + + + {audit.title} + + + {value} + + {unit} + + + + + + ); + })} + + ); +}; + +PieChartLegend.propTypes = { + audits: PropTypes.object, +}; + +export default PieChartLegend; diff --git a/Client/src/Pages/PageSpeed/Details/Components/EmptyView/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/EmptyView/index.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx new file mode 100644 index 000000000..1046a8ca3 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -0,0 +1,45 @@ +import ChartBox from "../../../../../Components/Charts/ChartBox"; +import AreaChart from "../Charts/AreaChart"; +import AreaChartLegend from "../Charts/AreaChartLegend"; +import ScoreIcon from "../../../../../assets/icons/monitor-graph-line.svg?react"; +import { Stack } from "@mui/material"; + +import { useTheme } from "@emotion/react"; + +const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) => { + const theme = useTheme(); + if (typeof monitor === "undefined") { + return null; + } + + const data = monitor?.checks ? [...monitor.checks].reverse() : []; + + return ( + + } + header="Score history" + height="100%" + borderRadiusRight={16} + Legend={ + + } + > + + + + ); +}; + +export default PageSpeedAreaChart; diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx new file mode 100644 index 000000000..5bce18df6 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedStatusBoxes/index.jsx @@ -0,0 +1,41 @@ +import StatusBoxes from "../../../../../Components/StatusBoxes"; +import StatBox from "../../../../../Components/StatBox"; +import { Typography } from "@mui/material"; +import { getHumanReadableDuration } from "../../../../../Utils/timeUtils"; + +const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => { + const { time: uptimeDuration, units: uptimeUnits } = getHumanReadableDuration( + monitor?.uptimeDuration + ); + + const { time: lastCheckTime, units: lastCheckUnits } = getHumanReadableDuration( + monitor?.lastChecked + ); + + return ( + + + {uptimeDuration} + {uptimeUnits} + ago + + } + /> + + {lastCheckTime} + {lastCheckUnits} + ago + + } + /> + + ); +}; + +export default PageSpeedStatusBoxes; diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx new file mode 100644 index 000000000..8f34dbf19 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -0,0 +1,45 @@ +import ChartBox from "../../../../../Components/Charts/ChartBox"; +import PerformanceIcon from "../../../../../assets/icons/performance-report.svg?react"; +import PieChart from "../Charts/PieChart"; +import { Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import PieChartLegend from "../Charts/PieChartLegend"; + +const PerformanceReport = ({ audits }) => { + const theme = useTheme(); + return ( + } + header="Performance report" + Legend={} + borderRadiusRight={16} + > + + + Values are estimated and may vary.{" "} + + See calculator + + + + ); +}; + +export default PerformanceReport; diff --git a/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx b/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx new file mode 100644 index 000000000..340d4cf5e --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx @@ -0,0 +1,40 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { logger } from "../../../../Utils/Logger"; +import { useNavigate } from "react-router-dom"; +const useMonitorFetch = ({ authToken, monitorId }) => { + const navigate = useNavigate(); + + const [monitor, setMonitor] = useState(undefined); + const [audits, setAudits] = useState(undefined); + const [isLoading, setIsLoading] = useState(true); + useEffect(() => { + const fetchMonitor = async () => { + try { + setIsLoading(true); + const res = await networkService.getStatsByMonitorId({ + authToken: authToken, + monitorId: monitorId, + sortOrder: "desc", + limit: 50, + dateRange: "day", + numToDisplay: null, + normalize: null, + }); + setMonitor(res?.data?.data ?? undefined); + setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined); + } catch (error) { + logger.error(logger); + navigate("/not-found", { replace: true }); + } finally { + setIsLoading(false); + } + }; + + fetchMonitor(); + }, [authToken, monitorId, navigate]); + + return { monitor, audits, isLoading }; +}; + +export { useMonitorFetch }; diff --git a/Client/src/Pages/PageSpeed/Details/index.css b/Client/src/Pages/PageSpeed/Details/index.css deleted file mode 100644 index 0d0699b3d..000000000 --- a/Client/src/Pages/PageSpeed/Details/index.css +++ /dev/null @@ -1,18 +0,0 @@ -.page-speed-details button.MuiButtonBase-root { - min-height: 34px; -} - -.page-speed-details .MuiPieArcLabel-root { - font-size: var(--env-var-font-size-small-plus); - transition: fill 300ms ease; -} -.page-speed-details .MuiPieArcLabel-faded { - fill: rgba(0, 0, 0, 0.3); -} -.page-speed-details .pie-label, -.page-speed-details .pie-value-label { - transition: all 400ms ease; -} -.page-speed-details .MuiPieArc-root { - stroke: inherit; -} diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 4c4bceb1b..1a4cbc7e4 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -1,75 +1,37 @@ -import PropTypes from "prop-types"; -import { Box, Button, Divider, Stack, Tooltip, Typography } from "@mui/material"; -import { useEffect, useState } from "react"; -import { useTheme } from "@emotion/react"; -import { useNavigate, useParams } from "react-router-dom"; -import { useSelector } from "react-redux"; -import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils"; -import { ChartBox } from "./styled"; -import { logger } from "../../../Utils/Logger"; -import { networkService } from "../../../main"; -import SkeletonLayout from "./skeleton"; -import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; -import SpedometerIcon from "../../../assets/icons/spedometer-icon.svg?react"; -import MetricsIcon from "../../../assets/icons/ruler-icon.svg?react"; -import ScoreIcon from "../../../assets/icons/monitor-graph-line.svg?react"; -import PerformanceIcon from "../../../assets/icons/performance-report.svg?react"; +// Components +import { Stack, Typography } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; -import PulseDot from "../../../Components/Animated/PulseDot"; -import PagespeedDetailsAreaChart from "./Charts/AreaChart"; -import Checkbox from "../../../Components/Inputs/Checkbox"; -import PieChart from "./Charts/PieChart"; -import useUtils from "../../Uptime/Monitors/Hooks/useUtils"; -import "./index.css"; +import MonitorStatusHeader from "../../../Components/MonitorStatusHeader"; +import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes"; +import PageSpeedAreaChart from "./Components/PageSpeedAreaChart"; +import PerformanceReport from "./Components/PerformanceReport"; +import Fallback from "../../../Components/Fallback"; +// Utils +import { useTheme } from "@emotion/react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; -import StatBox from "../../../Components/StatBox"; -import IconBox from "../../../Components/IconBox"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useMonitorFetch } from "./Hooks/useMonitorFetch"; +import { useState } from "react"; +// Constants +const BREADCRUMBS = [ + { name: "pagespeed", path: "/pagespeed" }, + { name: "details", path: `` }, + // { name: "details", path: `/pagespeed/${monitorId}` }, // Not needed? +]; const PageSpeedDetails = () => { const theme = useTheme(); - const navigate = useNavigate(); const isAdmin = useIsAdmin(); - const { statusColor, pagespeedStatusMsg, determineState } = useUtils(); - const [monitor, setMonitor] = useState({}); - const [audits, setAudits] = useState({}); const { monitorId } = useParams(); const { authToken } = useSelector((state) => state.auth); - useEffect(() => { - const fetchMonitor = async () => { - try { - const res = await networkService.getStatsByMonitorId({ - authToken: authToken, - monitorId: monitorId, - sortOrder: "desc", - limit: 50, - dateRange: "day", - numToDisplay: null, - normalize: null, - }); - setMonitor(res?.data?.data ?? {}); - setAudits(res?.data?.data?.checks?.[0]?.audits ?? {}); - } catch (error) { - logger.error(logger); - navigate("/not-found", { replace: true }); - } - }; + const { monitor, audits, isLoading } = useMonitorFetch({ + authToken, + monitorId, + }); - fetchMonitor(); - }, [monitorId, authToken, navigate]); - - let loading = Object.keys(monitor).length === 0; - const data = monitor?.checks ? [...monitor.checks].reverse() : []; - - const splitDuration = (duration) => { - const { time, format } = formatDurationSplit(duration); - return ( - <> - {time} - {format} - - ); - }; + console.log(monitor, audits, isLoading); const [metrics, setMetrics] = useState({ accessibility: true, @@ -77,371 +39,46 @@ const PageSpeedDetails = () => { performance: true, seo: true, }); + const handleMetrics = (id) => { setMetrics((prev) => ({ ...prev, [id]: !prev[id] })); }; + if (isLoading) { + return "funk"; + } + + if (typeof audits === "undefined") { + return "No Data"; + } + return ( - - {loading ? ( - - ) : ( - <> - - - - - {monitor.name} - - - - - - - - - {monitor?.url} - - - Checking every {formatDurationRounded(monitor?.interval)}. - - - - {isAdmin && ( - - )} - - - - {splitDuration(monitor?.uptimeDuration)} - ago - - } - /> - - {splitDuration(monitor?.lastChecked)} - ago - - } - /> - - - - Showing statistics for past 24 hours. - - - - - - - Score history - - - - - - - - - - Metrics - - - - - Shown - - - - handleMetrics("accessibility")} - /> - - handleMetrics("bestPractices")} - /> - - handleMetrics("performance")} - /> - - handleMetrics("seo")} - /> - - - - - - - - - - - Performance report - - - - - Values are estimated and may vary.{" "} - - See calculator - - - - - - - - - Performance Metrics - - - {Object.keys(audits).map((key) => { - if (key === "_id") return; - - let audit = audits[key]; - let score = audit.score * 100; - let bg = - score >= 90 - ? theme.palette.success.main - : score >= 50 - ? theme.palette.warning.main - : score >= 0 - ? theme.palette.error.contrastText - : theme.palette.tertiary.main; - - // Find the position where the number ends and the unit begins - const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/); - let value; - let unit; - if (match) { - value = match[1]; - match[2] === "s" ? (unit = "seconds") : (unit = match[2]); - } else { - value = audit.displayValue; - } - - return ( - - - - {audit.title} - - - {value} - - {unit} - - - - - - ); - })} - - - - - )} + + + + + + Showing statistics for past 24 hours. + + + ); }; -PageSpeedDetails.propTypes = { - isAdmin: PropTypes.bool, - push: PropTypes.func, -}; - export default PageSpeedDetails; diff --git a/Client/src/Pages/PageSpeed/Details/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/skeleton.jsx deleted file mode 100644 index 1d55a5039..000000000 --- a/Client/src/Pages/PageSpeed/Details/skeleton.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - return ( - <> - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/Details/styled.jsx b/Client/src/Pages/PageSpeed/Details/styled.jsx deleted file mode 100644 index 291d0c6f2..000000000 --- a/Client/src/Pages/PageSpeed/Details/styled.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Stack, styled } from "@mui/material"; - -export const ChartBox = styled(Stack)(({ theme }) => ({ - display: "grid", - minHeight: 300, - minWidth: 250, - border: 1, - borderStyle: "solid", - borderColor: theme.palette.primary.lowContrast, - borderRadius: 4, - borderTopRightRadius: 16, - borderBottomRightRadius: 16, - backgroundColor: theme.palette.primary.main, - "& h2": { - color: theme.palette.primary.contrastTextSecondary, - fontSize: 15, - fontWeight: 500, - }, - "& p": { color: theme.palette.primary.contrastTextTertiary }, - "& > :nth-of-type(1)": { - gridColumn: 1, - gridRow: 1, - height: "fit-content", - paddingTop: theme.spacing(8), - paddingLeft: theme.spacing(8), - }, - "& > :nth-of-type(2)": { gridColumn: 1, gridRow: 2 }, - "& > :nth-of-type(3)": { - gridColumn: 2, - gridRow: "span 2", - padding: theme.spacing(8), - borderLeft: 1, - borderLeftStyle: "solid", - borderLeftColor: theme.palette.primary.lowContrast, - borderRadius: 16, - backgroundColor: theme.palette.primary.main, - background: `linear-gradient(325deg, ${theme.palette.tertiary.main} 20%, ${theme.palette.primary.main} 45%)`, - }, - "& path": { - transition: "stroke-width 400ms ease", - }, -})); diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx index 228e522ff..e921bc15a 100644 --- a/Client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx @@ -1,6 +1,6 @@ // Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import { Stack, Box } from "@mui/material"; +import { Stack } from "@mui/material"; import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; import MonitorCountHeader from "../../../Components/MonitorCountHeader"; import MonitorGrid from "./Components/MonitorGrid"; From a33c0c16a003c1aa2aa64a5039a022df768614e2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 28 Jan 2025 15:51:58 -0800 Subject: [PATCH 052/106] Add skeletons and loading conditions --- .../Components/PageSpeedAreaChart/index.jsx | 14 ++++++++++++++ .../Components/PageSpeedAreaChart/skeleton.jsx | 17 +++++++++++++++++ .../Components/PerformanceReport/index.jsx | 14 ++++++++++++-- .../Components/PerformanceReport/skeleton.jsx | 17 +++++++++++++++++ Client/src/Pages/PageSpeed/Details/index.jsx | 13 ++++--------- 5 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx create mode 100644 Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index 1046a8ca3..c818d04a3 100644 --- a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -1,13 +1,20 @@ import ChartBox from "../../../../../Components/Charts/ChartBox"; import AreaChart from "../Charts/AreaChart"; import AreaChartLegend from "../Charts/AreaChartLegend"; +import SkeletonLayout from "./skeleton"; import ScoreIcon from "../../../../../assets/icons/monitor-graph-line.svg?react"; import { Stack } from "@mui/material"; +import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) => { const theme = useTheme(); + + if (!shouldRender) { + return ; + } + if (typeof monitor === "undefined") { return null; } @@ -42,4 +49,11 @@ const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) = ); }; +PageSpeedAreaChart.propTypes = { + shouldRender: PropTypes.bool, + monitor: PropTypes.object, + metrics: PropTypes.object, + handleMetrics: PropTypes.func, +}; + export default PageSpeedAreaChart; diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx new file mode 100644 index 000000000..b793908d4 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/skeleton.jsx @@ -0,0 +1,17 @@ +import { Box, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx index 8f34dbf19..2db40e126 100644 --- a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -4,9 +4,19 @@ import PieChart from "../Charts/PieChart"; import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import PieChartLegend from "../Charts/PieChartLegend"; - -const PerformanceReport = ({ audits }) => { +import SkeletonLayout from "./skeleton"; +const PerformanceReport = ({ shouldRender, audits }) => { + console.log("shouldRender", shouldRender); const theme = useTheme(); + + if (!shouldRender) { + return ; + } + + if (typeof audits === "undefined") { + return null; + } + return ( } diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx new file mode 100644 index 000000000..b793908d4 --- /dev/null +++ b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/skeleton.jsx @@ -0,0 +1,17 @@ +import { Box, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 1a4cbc7e4..43eb0ed7b 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -44,14 +44,6 @@ const PageSpeedDetails = () => { setMetrics((prev) => ({ ...prev, [id]: !prev[id] })); }; - if (isLoading) { - return "funk"; - } - - if (typeof audits === "undefined") { - return "No Data"; - } - return ( @@ -76,7 +68,10 @@ const PageSpeedDetails = () => { metrics={metrics} handleMetrics={handleMetrics} /> - + ); }; From b90cda80bc7fda9f0c8f1dcb5565febd4b741b92 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 29 Jan 2025 05:26:33 +0000 Subject: [PATCH 053/106] Update dependency bullmq to v5.39.0 --- 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 fdbd6c19c..54fb6d236 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.38.0", + "bullmq": "5.39.0", "cors": "^2.8.5", "dockerode": "4.0.4", "dotenv": "^16.4.5", @@ -1748,9 +1748,9 @@ } }, "node_modules/bullmq": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.38.0.tgz", - "integrity": "sha512-X6ApLQlSmX0LQ58xCpCoXE2nN57y8H9LD4EApJl9+8E6+FNWdKy6gykL8ofB9x4TcZeeGJhZOUZJAuhRV721bw==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.39.0.tgz", + "integrity": "sha512-aS+wtZsjW4/9mv2iZRynkSvEXCFS5h3Ko+OgArJco5rEgTE7v/TehDKv6gYEsKVbavOgiNlUNjgEhWhrzDSLBg==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index 0f63d97fe..d8eeee5df 100644 --- a/Server/package.json +++ b/Server/package.json @@ -18,7 +18,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "5.1.1", - "bullmq": "5.38.0", + "bullmq": "5.39.0", "cors": "^2.8.5", "dockerode": "4.0.4", "dotenv": "^16.4.5", From 611dead71ed324c2d593e9672a82278c5010822f Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:48:43 -0500 Subject: [PATCH 054/106] Add blank space --- Client/src/Components/Fallback/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx index e5029d276..36c0d3eb1 100644 --- a/Client/src/Components/Fallback/index.jsx +++ b/Client/src/Components/Fallback/index.jsx @@ -86,7 +86,7 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) => sx={{ alignSelf: "center" }} onClick={() => navigate(link)} > - Let's create your first{title} + Let's create your first {title} )} From 566f75634553be67ade0479ee7c5887ef8f6d062 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 29 Jan 2025 17:04:10 +0000 Subject: [PATCH 055/106] Update material-ui monorepo --- Client/package-lock.json | 80 ++++++++++++++++++++-------------------- Client/package.json | 6 +-- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 597b38a9c..5240bec9b 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -11,9 +11,9 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "6.4.1", - "@mui/lab": "6.0.0-beta.24", - "@mui/material": "6.4.1", + "@mui/icons-material": "6.4.2", + "@mui/lab": "6.0.0-beta.25", + "@mui/material": "6.4.2", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.24.1", "@mui/x-date-pickers": "7.24.1", @@ -1103,9 +1103,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.1.tgz", - "integrity": "sha512-SfDLWMV5b5oXgDf3NTa2hCTPC1d2defhDH2WgFKmAiejC4mSfXYbyi+AFCLzpizauXhgBm8OaZy9BHKnrSpahQ==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.2.tgz", + "integrity": "sha512-Qmod9fHsFWrtLxdSkZ4iDLRz2AUKt3C2ZEimuY+qKlQGVKJDNS5DuSlNOAgqfHFDq8mzB17ATN6HFcThwJlvUw==", "license": "MIT", "funding": { "type": "opencollective", @@ -1113,9 +1113,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz", - "integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.2.tgz", + "integrity": "sha512-uwsH1KRmxkJwK3NZyo1xL9pEduL16ftCnzYBYjd6nPNtm05QAoIc0aqedS9tqDV+Ab3q5C04HHOVsMDDv1EBpg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -1128,7 +1128,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.4.1", + "@mui/material": "^6.4.2", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -1139,16 +1139,16 @@ } }, "node_modules/@mui/lab": { - "version": "6.0.0-beta.24", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.24.tgz", - "integrity": "sha512-Off0rx2065TfGu5Z8E4OduNiVAqE9p9A8N1268hoXOcx7HzARxJsDFLRKvWgpPMqmGEvR8DVaHkcF2N90UB15A==", + "version": "6.0.0-beta.25", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.25.tgz", + "integrity": "sha512-Dkh+gFp30Mbqg9A6VY1fOqP3amsgylYZLKb7fbO80GMecSfx+tb+HU5IJghYrhiR6JaY3nEK1AOxMO0kPydaUw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", "@mui/base": "5.0.0-beta.69", - "@mui/system": "^6.4.1", + "@mui/system": "^6.4.2", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.2", "clsx": "^2.1.1", "prop-types": "^15.8.1" }, @@ -1162,8 +1162,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material": "^6.4.1", - "@mui/material-pigment-css": "^6.4.1", + "@mui/material": "^6.4.2", + "@mui/material-pigment-css": "^6.4.2", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1184,16 +1184,16 @@ } }, "node_modules/@mui/material": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz", - "integrity": "sha512-MFBfia6UiKxyoLeGkAh8M15bkeDmfnsUTMRJd/vTQue6YQ8AQ6lw9HqDthyYghzDEWIvZO/lQQzLrZE8XwNJLA==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.2.tgz", + "integrity": "sha512-9jKr53KbAJyyBRx8LRmX7ATXHlGtxVQdPgm1uyXMoEPMVkSJW1yO3vFgfYoDbGx4ZHcCNuWa4FkFIPWVt9fghA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.1", - "@mui/system": "^6.4.1", + "@mui/core-downloads-tracker": "^6.4.2", + "@mui/system": "^6.4.2", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.2", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -1212,7 +1212,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.1", + "@mui/material-pigment-css": "^6.4.2", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1239,13 +1239,13 @@ "license": "MIT" }, "node_modules/@mui/private-theming": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.1.tgz", - "integrity": "sha512-DcT7mwK89owwgcEuiE7w458te4CIjHbYWW6Kn6PiR6eLtxBsoBYphA968uqsQAOBQDpbYxvkuFLwhgk4bxoN/Q==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.2.tgz", + "integrity": "sha512-2CkQT0gNlogM50qGTBJgWA7hPPx4AeH8RE2xJa+PHtIOowiVPX52ZsQ0e7Ho18DAqEbkngQ6Uju037ER+TCY5A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.2", "prop-types": "^15.8.1" }, "engines": { @@ -1266,9 +1266,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", - "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.2.tgz", + "integrity": "sha512-cgjQK2bkllSYoWUBv93ALhCPJ0NhfO3NctsBf13/b4XSeQVfKPBAnR+P9mNpdFMa5a5RWwtWuBD3cZ5vktsN+g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1300,16 +1300,16 @@ } }, "node_modules/@mui/system": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.1.tgz", - "integrity": "sha512-rgQzgcsHCTtzF9MZ+sL0tOhf2ZBLazpjrujClcb4Siju5lTrK0xX4PsiropActzCemNfM+mOu+0jezAVnfRK8g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.2.tgz", + "integrity": "sha512-wQbaPCtsxNsM5nR+NZpkFJBKVKH03GQnAjlkKENM8JQqGdWcRyM3f4fJZgzzNdIFpSQw4wpAQKnhfHkjf3d6yQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.1", - "@mui/styled-engine": "^6.4.0", + "@mui/private-theming": "^6.4.2", + "@mui/styled-engine": "^6.4.2", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.1", + "@mui/utils": "^6.4.2", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1354,9 +1354,9 @@ } }, "node_modules/@mui/utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.1.tgz", - "integrity": "sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.2.tgz", + "integrity": "sha512-5NkhzlJkmR5+5RSs/Irqin1GPy2Z8vbLk/UzQrH9FEAnm6OA9SvuXjzgklxUs7N65VwEkGpKK1jMZ5K84hRdzQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", diff --git a/Client/package.json b/Client/package.json index 3192e1f3b..b2e15c02d 100644 --- a/Client/package.json +++ b/Client/package.json @@ -14,9 +14,9 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "6.4.1", - "@mui/lab": "6.0.0-beta.24", - "@mui/material": "6.4.1", + "@mui/icons-material": "6.4.2", + "@mui/lab": "6.0.0-beta.25", + "@mui/material": "6.4.2", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.24.1", "@mui/x-date-pickers": "7.24.1", From a90ed64a6f8d5ab4f6b07297da5344467de2d416 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:37:49 -0800 Subject: [PATCH 056/106] fix monitorStatusHeader name, safe property access --- Client/src/Components/MonitorStatusHeader/index.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Client/src/Components/MonitorStatusHeader/index.jsx b/Client/src/Components/MonitorStatusHeader/index.jsx index 6a7188775..c246a928f 100644 --- a/Client/src/Components/MonitorStatusHeader/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/index.jsx @@ -8,7 +8,7 @@ import ConfigButton from "./ConfigButton"; import SkeletonLayout from "./skeleton"; import PropTypes from "prop-types"; -const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => { +const MonitorStatusHeader = ({ shouldRender = true, isAdmin, monitor }) => { const theme = useTheme(); const { statusColor, statusMsg, determineState } = useUtils(); if (!shouldRender) { @@ -21,7 +21,7 @@ const MonitorHeader = ({ shouldRender = true, isAdmin, monitor }) => { justifyContent="space-between" > - {monitor.name} + {monitor?.name} { ); }; -MonitorHeader.propTypes = { +MonitorStatusHeader.propTypes = { shouldRender: PropTypes.bool, isAdmin: PropTypes.bool, monitor: PropTypes.object, }; -export default MonitorHeader; +export default MonitorStatusHeader; From cadb9bb9510eb5b172d5f4e15315ede2b8a67e96 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:38:16 -0800 Subject: [PATCH 057/106] provide default values for pagination --- Client/src/Components/Table/TablePagination/index.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Client/src/Components/Table/TablePagination/index.jsx b/Client/src/Components/Table/TablePagination/index.jsx index 518c18599..014e2405f 100644 --- a/Client/src/Components/Table/TablePagination/index.jsx +++ b/Client/src/Components/Table/TablePagination/index.jsx @@ -6,8 +6,8 @@ import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react" Pagination.propTypes = { paginationLabel: PropTypes.string, // Label for the pagination. - itemCount: PropTypes.number.isRequired, // Total number of items for pagination. - page: PropTypes.number.isRequired, // Current page index. + itemCount: PropTypes.number, // Total number of items for pagination. + page: PropTypes.number, // Current page index. rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page. handleChangePage: PropTypes.func.isRequired, // Function to handle page changes. handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page. @@ -29,9 +29,9 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25]; */ function Pagination({ paginationLabel, - itemCount, - page, - rowsPerPage, + itemCount = 0, + page = 0, + rowsPerPage = 5, handleChangePage, handleChangeRowsPerPage, }) { From 65c909e1664921c37c36f09f0be9a424263b989c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:38:34 -0800 Subject: [PATCH 058/106] provide default props for Table --- Client/src/Components/Table/index.jsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx index 7a7048ab8..54e47ed1f 100644 --- a/Client/src/Components/Table/index.jsx +++ b/Client/src/Components/Table/index.jsx @@ -32,7 +32,14 @@ import { useTheme } from "@emotion/react"; * @returns {JSX.Element} The rendered table component. */ -const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => { +const DataTable = ({ + headers = [], + data = [], + config = { + emptyView: "No data", + onRowClick: () => {}, + }, +}) => { const theme = useTheme(); if ((headers?.length ?? 0) === 0) { return "No data"; @@ -117,9 +124,9 @@ DataTable.propTypes = { render: PropTypes.func.isRequired, }) ).isRequired, - data: PropTypes.array.isRequired, + data: PropTypes.array, config: PropTypes.shape({ - onRowClick: PropTypes.func.isRequired, + onRowClick: PropTypes.func, rowSX: PropTypes.object, emptyView: PropTypes.node, }), From 6788b5672e3cd05924503711fb3af408b429aae1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:39:17 -0800 Subject: [PATCH 059/106] add ref to stop initial search --- .../Uptime/Monitors/Components/SearchComponent/index.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx index d93e6672b..749f921a8 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/Components/SearchComponent/index.jsx @@ -2,13 +2,18 @@ import { useState } from "react"; import Search from "../../../../../Components/Inputs/Search"; import { Box } from "@mui/material"; import useDebounce from "../../Hooks/useDebounce"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import PropTypes from "prop-types"; -const SearchComponent = ({ monitors, onSearchChange, setIsSearching }) => { +const SearchComponent = ({ monitors = [], onSearchChange, setIsSearching }) => { + const isFirstRender = useRef(true); const [localSearch, setLocalSearch] = useState(""); const debouncedSearch = useDebounce(localSearch, 500); useEffect(() => { + if (isFirstRender.current === true) { + isFirstRender.current = false; + return; + } onSearchChange(debouncedSearch); setIsSearching(false); }, [debouncedSearch, onSearchChange, setIsSearching]); From ef28f8c38b208724b1824f5cd93fa451f56eebe8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:39:47 -0800 Subject: [PATCH 060/106] simplify UptimeDataTable --- .../Components/UptimeDataTable/index.jsx | 121 ++++++------------ .../Components/UptimeDataTable/skeleton.jsx | 9 +- 2 files changed, 41 insertions(+), 89 deletions(-) diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 1940c5543..8a9fdcbe9 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -7,12 +7,9 @@ import Host from "../Host"; import { StatusLabel } from "../../../../../Components/Label"; import BarChart from "../../../../../Components/Charts/BarChart"; import ActionsMenu from "../ActionsMenu"; -import { useState } from "react"; -import SearchComponent from "../SearchComponent"; -import CircularCount from "../../../../../Components/CircularCount"; + import LoadingSpinner from "../LoadingSpinner"; import UptimeDataTableSkeleton from "./skeleton"; -import { Heading } from "../../../../../Components/Heading"; // Utils import { useTheme } from "@emotion/react"; @@ -20,35 +17,6 @@ import useUtils from "../../Hooks/useUtils"; import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; -const MonitorDataTable = ({ shouldRender, isSearching, headers, filteredMonitors }) => { - const theme = useTheme(); - const navigate = useNavigate(); - - if (!shouldRender) return null; - return ( - - - { - navigate(`/uptime/${row.id}`); - }, - emptyView: "No monitors found", - }} - /> - - ); -}; - /** * UptimeDataTable displays a table of uptime monitors with sorting, searching, and action capabilities * @param {Object} props - Component props @@ -78,34 +46,29 @@ const MonitorDataTable = ({ shouldRender, isSearching, headers, filteredMonitors * @param {Function} props.triggerUpdate - Callback to trigger a data refresh * @returns {JSX.Element} Rendered component */ -const UptimeDataTable = (props) => { +const UptimeDataTable = ({ + isAdmin, + isSearching, + setIsLoading, + filteredMonitors, + sort, + setSort, + triggerUpdate, + monitorsAreLoading, +}) => { // Utils - - const { - isAdmin, - setIsLoading, - monitors, - filteredMonitors, - monitorCount, - sort, - setSort, - setSearch, - triggerUpdate, - monitorsAreLoading, - } = props; + const navigate = useNavigate(); const { determineState } = useUtils(); const theme = useTheme(); - const navigate = useNavigate(); // Local state - const [isSearching, setIsSearching] = useState(false); // Handlers const handleSort = (field) => { let order = ""; - if (sort.field !== field) { + if (sort?.field !== field) { order = "desc"; } else { - order = sort.order === "asc" ? "desc" : "asc"; + order = sort?.order === "asc" ? "desc" : "asc"; } setSort({ field, order }); }; @@ -124,10 +87,10 @@ const UptimeDataTable = (props) => { - {sort.order === "asc" ? ( + {sort?.order === "asc" ? ( ) : ( @@ -160,10 +123,10 @@ const UptimeDataTable = (props) => { - {sort.order === "asc" ? ( + {sort?.order === "asc" ? ( ) : ( @@ -210,41 +173,37 @@ const UptimeDataTable = (props) => { ), }, ]; - return ( - - - Uptime monitors - - - - ; + } + + return ( + + + { + navigate(`/uptime/${row.id}`); + }, + emptyView: "No monitors found", + }} /> - ); }; UptimeDataTable.propTypes = { isSearching: PropTypes.bool, - setIsSearching: PropTypes.func, setSort: PropTypes.func, setSearch: PropTypes.func, setIsLoading: PropTypes.func, diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx index 303eb48c8..e3c4fa2e2 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx +++ b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx @@ -1,9 +1,6 @@ import { Skeleton } from "@mui/material"; -import PropTypes from "prop-types"; - -const UptimeDataTableSkeleton = ({ shouldRender }) => { - if (!shouldRender) return null; +const UptimeDataTableSkeleton = () => { return ( { ); }; -UptimeDataTableSkeleton.propTypes = { - shouldRender: PropTypes.bool.isRequired, -}; - export default UptimeDataTableSkeleton; From 12711e87ee558c15f4e1f500887c4aaaf3fa4ef4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:40:27 -0800 Subject: [PATCH 061/106] set initial state explicitly to for consistency --- Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx | 6 +++--- Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx index d4b7e5ba7..12fcd9923 100644 --- a/Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx +++ b/Client/src/Pages/Uptime/Monitors/Hooks/useMonitorsFetch.jsx @@ -43,9 +43,9 @@ export const useMonitorFetch = ({ triggerUpdate, }) => { const [monitorsAreLoading, setMonitorsAreLoading] = useState(false); - const [monitors, setMonitors] = useState([]); - const [filteredMonitors, setFilteredMonitors] = useState([]); - const [monitorsSummary, setMonitorsSummary] = useState({}); + const [monitors, setMonitors] = useState(undefined); + const [filteredMonitors, setFilteredMonitors] = useState(undefined); + const [monitorsSummary, setMonitorsSummary] = useState(undefined); const theme = useTheme(); diff --git a/Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx b/Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx index 0e5fc62ec..ff3288cdf 100644 --- a/Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx +++ b/Client/src/Pages/Uptime/Monitors/Hooks/useUtils.jsx @@ -2,6 +2,7 @@ import { useTheme } from "@mui/material"; const useUtils = () => { const determineState = (monitor) => { + if (typeof monitor === "undefined") return "pending"; if (monitor.isActive === false) return "paused"; if (monitor?.status === undefined) return "pending"; return monitor?.status == true ? "up" : "down"; From a9851787e8bcd344882a851dd59f3474958b59a6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:41:06 -0800 Subject: [PATCH 062/106] Use MonitorCountHeader, initialze state to undefined' --- Client/src/Pages/Uptime/Monitors/index.jsx | 32 ++++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx index d60b5c495..f8d78ad52 100644 --- a/Client/src/Pages/Uptime/Monitors/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/index.jsx @@ -6,6 +6,10 @@ import UptimeDataTable from "./Components/UptimeDataTable"; import Pagination from "../../../Components/Table/TablePagination"; import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; import Fallback from "../../../Components/Fallback"; +import SearchComponent from "./Components/SearchComponent"; + +import MonitorCountHeader from "../../../Components/MonitorCountHeader"; + // MUI Components import { Stack, Box, Button } from "@mui/material"; @@ -53,9 +57,9 @@ const UptimeMonitors = () => { const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage); // Local state - const [search, setSearch] = useState(""); - const [page, setPage] = useState(0); - const [sort, setSort] = useState({}); + const [search, setSearch] = useState(undefined); + const [page, setPage] = useState(undefined); + const [sort, setSort] = useState(undefined); const [isSearching, setIsSearching] = useState(false); const [isLoading, setIsLoading] = useState(false); const [monitorUpdateTrigger, setMonitorUpdateTrigger] = useState(false); @@ -85,21 +89,19 @@ const UptimeMonitors = () => { }, []); const teamId = user.teamId; - const { monitorsAreLoading, monitors, filteredMonitors, monitorsSummary } = useMonitorsFetch({ authToken, teamId, limit: 25, - page, + page: page, rowsPerPage: rowsPerPage, filter: search, - field: sort.field, - order: sort.order, + field: sort?.field, + order: sort?.order, triggerUpdate: monitorUpdateTrigger, }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; - if (!isLoading && !monitorsAreLoading && monitors?.length === 0) { return ( { monitorsSummary={monitorsSummary} shouldRender={!monitorsAreLoading} /> + + + 0 && !monitorsAreLoading} + monitorCount={totalMonitors} + heading={"Uptime monitors"} + > + + { setSort={setSort} setSearch={setSearch} isSearching={isSearching} - setIsSearching={setIsSearching} monitorsAreLoading={monitorsAreLoading} triggerUpdate={triggerUpdate} /> From cfb5c7ae5d0ae77e2d5d7d503f019082761022a1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:41:32 -0800 Subject: [PATCH 063/106] Update hooks to use undefined for initial state --- Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx | 2 +- Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx | 4 ++-- Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx index 9e1c3b44f..ce0b4f7af 100644 --- a/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx +++ b/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx @@ -10,7 +10,7 @@ const useCertificateFetch = ({ certificateDateFormat, uiTimezone, }) => { - const [certificateExpiry, setCertificateExpiry] = useState("N/A"); + const [certificateExpiry, setCertificateExpiry] = useState(undefined); const [certificateIsLoading, setCertificateIsLoading] = useState(false); useEffect(() => { diff --git a/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx index 2a50c2172..541c29f6e 100644 --- a/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx +++ b/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx @@ -10,8 +10,8 @@ export const useChecksFetch = ({ page, rowsPerPage, }) => { - const [checks, setChecks] = useState([]); - const [checksCount, setChecksCount] = useState(0); + const [checks, setChecks] = useState(undefined); + const [checksCount, setChecksCount] = useState(undefined); const [checksAreLoading, setChecksAreLoading] = useState(false); useEffect(() => { diff --git a/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx index 067545f51..ca4643fd4 100644 --- a/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx +++ b/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => { const [monitorIsLoading, setMonitorsIsLoading] = useState(false); - const [monitor, setMonitor] = useState({}); + const [monitor, setMonitor] = useState(undefined); const navigate = useNavigate(); useEffect(() => { From 4c3b348642d8b11dbf3366ec12dd4512bb7594e9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:42:02 -0800 Subject: [PATCH 064/106] safe param access --- .../Pages/Uptime/Details/Components/ChartBoxes/index.jsx | 4 ++-- .../Pages/Uptime/Details/Components/Charts/DownBarChart.jsx | 2 +- .../Uptime/Details/Components/Charts/ResponseTimeChart.jsx | 2 +- .../Pages/Uptime/Details/Components/ResponseTable/index.jsx | 6 +++--- .../Uptime/Details/Components/UptimeStatusBoxes/index.jsx | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx index 5565529fb..aa40442a9 100644 --- a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx @@ -123,7 +123,7 @@ const ChartBoxes = ({ icon={} header="Average Response Time" > - + ); @@ -133,7 +133,7 @@ export default ChartBoxes; ChartBoxes.propTypes = { shouldRender: PropTypes.bool, - monitor: PropTypes.object.isRequired, + monitor: PropTypes.object, dateRange: PropTypes.string.isRequired, uiTimezone: PropTypes.string.isRequired, dateFormat: PropTypes.string.isRequired, diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx index dcd3a06be..5d6c48b58 100644 --- a/Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/DownBarChart.jsx @@ -19,7 +19,7 @@ const DownBarChart = memo(({ monitor, type, onBarHover }) => { { setChartHovered(true); onBarHover({ time: null, totalChecks: 0 }); diff --git a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx index 8be1125c2..c262d4cce 100644 --- a/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx +++ b/Client/src/Pages/Uptime/Details/Components/Charts/ResponseTimeChart.jsx @@ -15,7 +15,7 @@ const ResponseTImeChart = ({ shouldRender = true, monitor, dateRange }) => { header="Response Times" > diff --git a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx index 1f5a19203..c03115af7 100644 --- a/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ResponseTable/index.jsx @@ -8,7 +8,7 @@ import { formatDateWithTz } from "../../../../../Utils/timeUtils"; import SkeletonLayout from "./skeleton"; const ResponseTable = ({ shouldRender = true, - checks, + checks = [], checksCount, uiTimezone, page, @@ -77,8 +77,8 @@ const ResponseTable = ({ ResponseTable.propTypes = { shouldRender: PropTypes.bool, - checks: PropTypes.array.isRequired, - checksCount: PropTypes.number.isRequired, + checks: PropTypes.array, + checksCount: PropTypes.number, uiTimezone: PropTypes.string.isRequired, page: PropTypes.number.isRequired, setPage: PropTypes.func.isRequired, diff --git a/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx index ad4620396..5597ce2ac 100644 --- a/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/UptimeStatusBoxes/index.jsx @@ -8,6 +8,7 @@ import useUtils from "../../../Monitors/Hooks/useUtils"; const UptimeStatusBoxes = ({ shouldRender, monitor, certificateExpiry }) => { const theme = useTheme(); const { determineState } = useUtils(); + const { time: streakTime, units: streakUnits } = getHumanReadableDuration( monitor?.uptimeStreak ); From 80a16ef61a255fd4e1cb90e959261df2f0aac024 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:42:40 -0800 Subject: [PATCH 065/106] safe param access --- .../Details/Components/Charts/PieChart.jsx | 4 +- .../Components/Charts/PieChartLegend.jsx | 130 +++++++++--------- .../Components/PageSpeedAreaChart/index.jsx | 4 - .../Components/PerformanceReport/index.jsx | 6 +- Client/src/Pages/PageSpeed/Details/index.jsx | 5 +- 5 files changed, 69 insertions(+), 80 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx index aad697f9f..c9204c38a 100644 --- a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChart.jsx @@ -118,8 +118,6 @@ const PieChart = ({ audits }) => { const [highlightedItem, setHighLightedItem] = useState(null); const [expand, setExpand] = useState(false); - if (typeof audits === "undefined") return null; - /** * Retrieves color properties based on the performance value. * @@ -166,6 +164,8 @@ const PieChart = ({ audits }) => { */ let performance = 0; const getPieData = (audits) => { + if (typeof audits === "undefined") return undefined; + let data = []; let startAngle = 0; const padding = 3; // padding between arcs diff --git a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx index c7612821f..010716b88 100644 --- a/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/Charts/PieChartLegend.jsx @@ -7,84 +7,84 @@ import PropTypes from "prop-types"; const PieChartLegend = ({ audits }) => { const theme = useTheme(); - if (typeof audits === "undefined") return null; return ( } header="Performance metrics" sx={{ flex: 1 }} > - {Object.keys(audits).map((key) => { - if (key === "_id") return; + {typeof audits !== "undefined" && + Object.keys(audits).map((key) => { + if (key === "_id") return; - let audit = audits[key]; - let score = audit.score * 100; - let bg = - score >= 90 - ? theme.palette.success.main - : score >= 50 - ? theme.palette.warning.main - : score >= 0 - ? theme.palette.error.main - : theme.palette.tertiary.main; + let audit = audits[key]; + let score = audit.score * 100; + let bg = + score >= 90 + ? theme.palette.success.main + : score >= 50 + ? theme.palette.warning.main + : score >= 0 + ? theme.palette.error.main + : theme.palette.tertiary.main; - // Find the position where the number ends and the unit begins - const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/); - let value; - let unit; - if (match) { - value = match[1]; - match[2] === "s" ? (unit = "seconds") : (unit = match[2]); - } else { - value = audit.displayValue; - } + // Find the position where the number ends and the unit begins + const match = audit.displayValue.match(/(\d+\.?\d*)\s*([a-zA-Z]+)/); + let value; + let unit; + if (match) { + value = match[1]; + match[2] === "s" ? (unit = "seconds") : (unit = match[2]); + } else { + value = audit.displayValue; + } - return ( - - - - {audit.title} - - - {value} + return ( + + + + {audit.title} + - {unit} + {value} + + {unit} + - - - - - ); - })} + + + + ); + })} ); }; diff --git a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx index c818d04a3..582648e23 100644 --- a/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/PageSpeedAreaChart/index.jsx @@ -15,10 +15,6 @@ const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) = return ; } - if (typeof monitor === "undefined") { - return null; - } - const data = monitor?.checks ? [...monitor.checks].reverse() : []; return ( diff --git a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx index 2db40e126..cd220a73c 100644 --- a/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/Components/PerformanceReport/index.jsx @@ -5,18 +5,14 @@ import { Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import PieChartLegend from "../Charts/PieChartLegend"; import SkeletonLayout from "./skeleton"; + const PerformanceReport = ({ shouldRender, audits }) => { - console.log("shouldRender", shouldRender); const theme = useTheme(); if (!shouldRender) { return ; } - if (typeof audits === "undefined") { - return null; - } - return ( } diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 43eb0ed7b..6fa4aa82c 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -1,11 +1,10 @@ // Components -import { Stack, Typography } from "@mui/material"; +import { Stack, Typography, Skeleton } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import MonitorStatusHeader from "../../../Components/MonitorStatusHeader"; import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes"; import PageSpeedAreaChart from "./Components/PageSpeedAreaChart"; import PerformanceReport from "./Components/PerformanceReport"; -import Fallback from "../../../Components/Fallback"; // Utils import { useTheme } from "@emotion/react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; @@ -31,8 +30,6 @@ const PageSpeedDetails = () => { monitorId, }); - console.log(monitor, audits, isLoading); - const [metrics, setMetrics] = useState({ accessibility: true, bestPractices: true, From 0feb2053a113d46cef0e64ea2d32acfb9c03105a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 11:42:59 -0800 Subject: [PATCH 066/106] remove null return --- .../Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx b/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx index 0e61c9c15..6266ebdc9 100644 --- a/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/Components/MonitorGrid/index.jsx @@ -2,8 +2,6 @@ import { Grid, Grid2 } from "@mui/material"; import Card from "../Card"; const MonitorGrid = ({ shouldRender, monitors }) => { - if (!shouldRender) return null; - return ( Date: Wed, 29 Jan 2025 12:31:19 -0800 Subject: [PATCH 067/106] move fallback css to fallback css file --- .../src/Components/Layouts/HomeLayout/index.css | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/Client/src/Components/Layouts/HomeLayout/index.css b/Client/src/Components/Layouts/HomeLayout/index.css index e08904f8e..ef6f38abd 100644 --- a/Client/src/Components/Layouts/HomeLayout/index.css +++ b/Client/src/Components/Layouts/HomeLayout/index.css @@ -26,20 +26,3 @@ min-height: calc(100vh - var(--env-var-spacing-2) * 2); flex: 1; } - -.home-layout > div:has(> [class*="fallback__"]) .background-pattern-svg { - position: absolute; - top: 0; - left: 50%; - transform: translate(-50%, -50%); - z-index: 0; - - width: 100%; - max-width: 800px; - height: 100%; - max-height: 800px; - - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} From d2d2e87184b3836a1c96b45113230cb1644c4aac Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 12:31:43 -0800 Subject: [PATCH 068/106] Add a network fallback component --- Client/src/Components/Fallback/index.css | 16 ++++ .../Components/NetworkErrorFallback/index.jsx | 85 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 Client/src/Components/NetworkErrorFallback/index.jsx diff --git a/Client/src/Components/Fallback/index.css b/Client/src/Components/Fallback/index.css index 9b4d6f653..cad46300b 100644 --- a/Client/src/Components/Fallback/index.css +++ b/Client/src/Components/Fallback/index.css @@ -19,6 +19,22 @@ font-size: var(--env-var-font-size-medium); } +[class*="fallback__"] .background-pattern-svg { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -50%); + z-index: 0; + + width: 100%; + max-width: 800px; + height: 100%; + max-height: 800px; + + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} .fallback__status > .MuiStack-root { margin-left: var(--env-var-spacing-2); } diff --git a/Client/src/Components/NetworkErrorFallback/index.jsx b/Client/src/Components/NetworkErrorFallback/index.jsx new file mode 100644 index 000000000..1039b256b --- /dev/null +++ b/Client/src/Components/NetworkErrorFallback/index.jsx @@ -0,0 +1,85 @@ +import { useTheme } from "@emotion/react"; +import { Box, Stack, Typography } from "@mui/material"; +import Skeleton from "../../assets/Images/create-placeholder.svg?react"; +import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react"; +import Background from "../../assets/Images/background-grid.svg?react"; +import { useSelector } from "react-redux"; + +/** + * Fallback component to display a fallback UI for network errors + * + * @returns {JSX.Element} The rendered fallback UI. + */ + +const NetworkErrorFallback = () => { + const theme = useTheme(); + const mode = useSelector((state) => state.ui.mode); + + return ( + + + {mode === "light" ? ( + + ) : ( + + )} + + + + + + Network error + + Please check your connection + + + + ); +}; + +export default NetworkErrorFallback; From 4559215316c07d55f4ebfb19d5e7a2e913abce14 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 12:32:09 -0800 Subject: [PATCH 069/106] add network error state to hooks --- .../Monitors/Hooks/useMonitorsFetch.jsx | 4 ++- Client/src/Pages/PageSpeed/Monitors/index.jsx | 7 +++- .../Monitors/Hooks/useMonitorsFetch.jsx | 10 +++++- Client/src/Pages/Uptime/Monitors/index.jsx | 35 ++++++++++++------- Client/src/Utils/NetworkService.js | 1 - 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx index 06d423acc..72bfbfc35 100644 --- a/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/Hooks/useMonitorsFetch.jsx @@ -6,6 +6,7 @@ const useMonitorsFetch = ({ authToken, teamId }) => { const [isLoading, setIsLoading] = useState(true); const [monitors, setMonitors] = useState([]); const [summary, setSummary] = useState({}); + const [networkError, setNetworkError] = useState(false); useEffect(() => { const fetchMonitors = async () => { @@ -27,6 +28,7 @@ const useMonitorsFetch = ({ authToken, teamId }) => { setSummary(res.data.data.summary); } } catch (error) { + setNetworkError(true); createToast({ body: error.message, }); @@ -37,7 +39,7 @@ const useMonitorsFetch = ({ authToken, teamId }) => { fetchMonitors(); }, [authToken, teamId]); - return { isLoading, monitors, summary }; + return { isLoading, monitors, summary, networkError }; }; export default useMonitorsFetch; diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx index e921bc15a..a0c24d48e 100644 --- a/Client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx @@ -11,6 +11,7 @@ import { useTheme } from "@emotion/react"; import { useSelector } from "react-redux"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import useMonitorsFetch from "./Hooks/useMonitorsFetch"; +import NetworkErrorFallback from "../../../Components/NetworkErrorFallback"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; @@ -20,11 +21,15 @@ const PageSpeed = () => { const isAdmin = useIsAdmin(); const { user, authToken } = useSelector((state) => state.auth); - const { isLoading, monitors, summary } = useMonitorsFetch({ + const { isLoading, monitors, summary, networkError } = useMonitorsFetch({ authToken: authToken, teamId: user.teamId, }); + if (networkError === true) { + return ; + } + if (!isLoading && monitors?.length === 0) { return ( { }, []); const teamId = user.teamId; - const { monitorsAreLoading, monitors, filteredMonitors, monitorsSummary } = - useMonitorsFetch({ - authToken, - teamId, - limit: 25, - page: page, - rowsPerPage: rowsPerPage, - filter: search, - field: sort?.field, - order: sort?.order, - triggerUpdate: monitorUpdateTrigger, - }); + const { + monitorsAreLoading, + monitors, + filteredMonitors, + monitorsSummary, + networkError, + } = useMonitorsFetch({ + authToken, + teamId, + limit: 25, + page: page, + rowsPerPage: rowsPerPage, + filter: search, + field: sort?.field, + order: sort?.order, + triggerUpdate: monitorUpdateTrigger, + }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; - if (!isLoading && !monitorsAreLoading && monitors?.length === 0) { + + if (networkError) return ; + + if (!isLoading && !monitorsAreLoading && totalMonitors === 0) { return ( Date: Wed, 29 Jan 2025 15:04:48 -0800 Subject: [PATCH 070/106] Changed NetworkErrorFallback to a generic callback --- .../GenericFallback/NetworkError.jsx | 20 +++++++++++++++++++ .../index.jsx | 17 +++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 Client/src/Components/GenericFallback/NetworkError.jsx rename Client/src/Components/{NetworkErrorFallback => GenericFallback}/index.jsx (81%) diff --git a/Client/src/Components/GenericFallback/NetworkError.jsx b/Client/src/Components/GenericFallback/NetworkError.jsx new file mode 100644 index 000000000..b26037a57 --- /dev/null +++ b/Client/src/Components/GenericFallback/NetworkError.jsx @@ -0,0 +1,20 @@ +import { Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +const NetworkError = () => { + const theme = useTheme(); + return ( + <> + + Network error + + Please check your connection + + ); +}; + +export default NetworkError; diff --git a/Client/src/Components/NetworkErrorFallback/index.jsx b/Client/src/Components/GenericFallback/index.jsx similarity index 81% rename from Client/src/Components/NetworkErrorFallback/index.jsx rename to Client/src/Components/GenericFallback/index.jsx index 1039b256b..af9ed2ea4 100644 --- a/Client/src/Components/NetworkErrorFallback/index.jsx +++ b/Client/src/Components/GenericFallback/index.jsx @@ -1,5 +1,5 @@ import { useTheme } from "@emotion/react"; -import { Box, Stack, Typography } from "@mui/material"; +import { Box, Stack } from "@mui/material"; import Skeleton from "../../assets/Images/create-placeholder.svg?react"; import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react"; import Background from "../../assets/Images/background-grid.svg?react"; @@ -11,13 +11,13 @@ import { useSelector } from "react-redux"; * @returns {JSX.Element} The rendered fallback UI. */ -const NetworkErrorFallback = () => { +const GenericFallback = ({ children }) => { const theme = useTheme(); const mode = useSelector((state) => state.ui.mode); return ( { maxWidth={"300px"} zIndex={1} > - - Network error - - Please check your connection + {children} ); }; -export default NetworkErrorFallback; +export default GenericFallback; From 6c3b026199b93b1092cbb0f007b252f7f4884f00 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 15:05:45 -0800 Subject: [PATCH 071/106] Add a skeleton for table component --- Client/src/Components/Table/skeleton.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Client/src/Components/Table/skeleton.jsx diff --git a/Client/src/Components/Table/skeleton.jsx b/Client/src/Components/Table/skeleton.jsx new file mode 100644 index 000000000..062d39156 --- /dev/null +++ b/Client/src/Components/Table/skeleton.jsx @@ -0,0 +1,14 @@ +import { Skeleton } from "@mui/material"; + +const TableSkeleton = () => { + return ( + + ); +}; + +export default TableSkeleton; From 3c93c6ffc3d49cba1f501e8f2eec9666ae3eef32 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 15:06:02 -0800 Subject: [PATCH 072/106] Add network operation hooks --- .../Pages/Incidents/Hooks/useChecksFetch.jsx | 60 +++++++++++++++++++ .../Incidents/Hooks/useMonitorsFetch.jsx | 51 ++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx create mode 100644 Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx diff --git a/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx b/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx new file mode 100644 index 000000000..57444ad05 --- /dev/null +++ b/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx @@ -0,0 +1,60 @@ +import { useState, useEffect } from "react"; +import { networkService } from "../../../main"; +import { createToast } from "../../../Utils/toastUtils"; +import { useSelector } from "react-redux"; +const useChecksFetch = ({ selectedMonitor, filter, dateRange, page, rowsPerPage }) => { + //Redux + const { authToken, user } = useSelector((state) => state.auth); + + //Local + const [isLoading, setIsLoading] = useState(false); + const [networkError, setNetworkError] = useState(false); + const [checks, setChecks] = useState(undefined); + const [checksCount, setChecksCount] = useState(undefined); + + useEffect(() => { + const fetchChecks = async () => { + try { + setIsLoading(true); + let res; + + if (selectedMonitor === "0") { + res = await networkService.getChecksByTeam({ + authToken: authToken, + status: false, + teamId: user.teamId, + sortOrder: "desc", + limit: null, + dateRange, + filter: filter, + page: page, + rowsPerPage: rowsPerPage, + }); + } else { + res = await networkService.getChecksByMonitor({ + authToken: authToken, + status: false, + monitorId: selectedMonitor, + sortOrder: "desc", + limit: null, + dateRange, + filter: filter, + page, + rowsPerPage, + }); + } + setChecks(res.data.data.checks); + setChecksCount(res.data.data.checksCount); + } catch (error) { + setNetworkError(true); + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchChecks(); + }, [authToken, user, dateRange, page, rowsPerPage, filter, selectedMonitor]); + return { isLoading, networkError, checks, checksCount }; +}; + +export default useChecksFetch; diff --git a/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx new file mode 100644 index 000000000..0ffd18571 --- /dev/null +++ b/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx @@ -0,0 +1,51 @@ +import { useState, useEffect } from "react"; +import { networkService } from "../../../main"; +import { createToast } from "../../../Utils/toastUtils"; +const useMonitorsFetch = ({ authToken, teamId }) => { + //Local state + const [isLoading, setIsLoading] = useState(false); + const [networkError, setNetworkError] = useState(false); + + const [monitors, setMonitors] = useState(undefined); + + useEffect(() => { + const fetchMonitors = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorsByTeamId({ + authToken, + teamId, + limit: null, + types: null, + status: null, + checkOrder: null, + normalize: null, + page: null, + rowsPerPage: null, + filter: null, + field: null, + order: null, + }); + if (res?.data?.data?.monitors?.length > 0) { + const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => { + acc[monitor._id] = monitor; + return acc; + }, {}); + setMonitors(monitorLookup); + } + } catch (error) { + setNetworkError(true); + createToast({ + body: error.message, + }); + } finally { + setIsLoading(false); + } + }; + + fetchMonitors(); + }, [authToken, teamId]); + return { isLoading, monitors, networkError }; +}; + +export { useMonitorsFetch }; From c1144a4432feed0b989a3a6cde9a938fa817bbb5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 15:06:30 -0800 Subject: [PATCH 073/106] update proptypes --- Client/src/Components/Table/TablePagination/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Components/Table/TablePagination/index.jsx b/Client/src/Components/Table/TablePagination/index.jsx index 014e2405f..8427f4270 100644 --- a/Client/src/Components/Table/TablePagination/index.jsx +++ b/Client/src/Components/Table/TablePagination/index.jsx @@ -8,7 +8,7 @@ Pagination.propTypes = { paginationLabel: PropTypes.string, // Label for the pagination. itemCount: PropTypes.number, // Total number of items for pagination. page: PropTypes.number, // Current page index. - rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page. + rowsPerPage: PropTypes.number, // Number of rows displayed per page. handleChangePage: PropTypes.func.isRequired, // Function to handle page changes. handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page. }; From 02d93fdcd494f1fad0b37fc07e725ab1c7023358 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 29 Jan 2025 15:06:41 -0800 Subject: [PATCH 074/106] Refactor incidents --- .../Components/IncidentTable/index.jsx | 126 +++++++++ .../Components/OptionsHeader/index.jsx | 162 ++++++++++++ .../Components/OptionsHeader/skeleton.jsx | 11 + .../Incidents/IncidentTable/Empty/Empty.jsx | 32 --- .../IncidentTable/Skeleton/Skeleton.jsx | 21 -- .../Pages/Incidents/IncidentTable/index.jsx | 187 ------------- Client/src/Pages/Incidents/index.jsx | 248 ++++-------------- Client/src/Pages/Incidents/skeleton.jsx | 50 ---- Client/src/Pages/PageSpeed/Monitors/index.jsx | 17 +- .../Components/UptimeDataTable/index.jsx | 4 +- .../Components/UptimeDataTable/skeleton.jsx | 14 - Client/src/Pages/Uptime/Monitors/index.jsx | 21 +- Client/src/Routes/index.jsx | 2 +- Client/src/Utils/NetworkService.js | 1 - 14 files changed, 383 insertions(+), 513 deletions(-) create mode 100644 Client/src/Pages/Incidents/Components/IncidentTable/index.jsx create mode 100644 Client/src/Pages/Incidents/Components/OptionsHeader/index.jsx create mode 100644 Client/src/Pages/Incidents/Components/OptionsHeader/skeleton.jsx delete mode 100644 Client/src/Pages/Incidents/IncidentTable/Empty/Empty.jsx delete mode 100644 Client/src/Pages/Incidents/IncidentTable/Skeleton/Skeleton.jsx delete mode 100644 Client/src/Pages/Incidents/IncidentTable/index.jsx delete mode 100644 Client/src/Pages/Incidents/skeleton.jsx delete mode 100644 Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx diff --git a/Client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/Client/src/Pages/Incidents/Components/IncidentTable/index.jsx new file mode 100644 index 000000000..ba7a03e05 --- /dev/null +++ b/Client/src/Pages/Incidents/Components/IncidentTable/index.jsx @@ -0,0 +1,126 @@ +//Components +import Table from "../../../../Components/Table"; +import TableSkeleton from "../../../../Components/Table/skeleton"; +import Pagination from "../../../../Components/Table/TablePagination"; +import { StatusLabel } from "../../../../Components/Label"; +import { HttpStatusLabel } from "../../../../Components/HttpStatusLabel"; +import GenericFallback from "../../../../Components/GenericFallback"; +import NetworkError from "../../../../Components/GenericFallback/NetworkError"; + +//Utils +import { formatDateWithTz } from "../../../../Utils/timeUtils"; +import { useSelector } from "react-redux"; +import { useState } from "react"; +import useChecksFetch from "../../Hooks/useChecksFetch"; +import PropTypes from "prop-types"; + +const IncidentTable = ({ + shouldRender, + monitors, + selectedMonitor, + filter, + dateRange, +}) => { + //Redux state + const uiTimezone = useSelector((state) => state.ui.timezone); + + //Local state + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const { isLoading, networkError, checks, checksCount } = useChecksFetch({ + selectedMonitor, + filter, + dateRange, + page, + rowsPerPage, + }); + + //Handlers + const handleChangePage = (_, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(event.target.value); + }; + + 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 }, + ]; + + if (!shouldRender || isLoading) return ; + + if (networkError) { + return ( + + + + ); + } + + if (!isLoading && typeof checksCount === "undefined") { + return No incidents recorded; + } + + return ( + <> + + + + ); +}; + +IncidentTable.propTypes = { + shouldRender: PropTypes.bool, + monitors: PropTypes.object, + selectedMonitor: PropTypes.string, + filter: PropTypes.string, + dateRange: PropTypes.string, +}; +export default IncidentTable; diff --git a/Client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/Client/src/Pages/Incidents/Components/OptionsHeader/index.jsx new file mode 100644 index 000000000..967acfed5 --- /dev/null +++ b/Client/src/Pages/Incidents/Components/OptionsHeader/index.jsx @@ -0,0 +1,162 @@ +// Components +import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; +import Select from "../../../../Components/Inputs/Select"; +import PropTypes from "prop-types"; + +//Utils +import { useTheme } from "@emotion/react"; +import SkeletonLayout from "./skeleton"; + +const OptionsHeader = ({ + shouldRender, + selectedMonitor = 0, + setSelectedMonitor, + monitors, + filter = "all", + setFilter, + dateRange = "hour", + setDateRange, +}) => { + const theme = useTheme(); + const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : []; + + if (!shouldRender) return ; + + return ( + + + + Incidents for + + - - - - Filter by: - - - - - - - - - - Show: - - - - - - - - - - - - )} + + + + ); }; diff --git a/Client/src/Pages/Incidents/skeleton.jsx b/Client/src/Pages/Incidents/skeleton.jsx deleted file mode 100644 index 51848a108..000000000 --- a/Client/src/Pages/Incidents/skeleton.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx index a0c24d48e..470a33f18 100644 --- a/Client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx @@ -1,6 +1,6 @@ // Components import Breadcrumbs from "../../../Components/Breadcrumbs"; -import { Stack } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; import MonitorCountHeader from "../../../Components/MonitorCountHeader"; import MonitorGrid from "./Components/MonitorGrid"; @@ -11,7 +11,7 @@ import { useTheme } from "@emotion/react"; import { useSelector } from "react-redux"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import useMonitorsFetch from "./Hooks/useMonitorsFetch"; -import NetworkErrorFallback from "../../../Components/NetworkErrorFallback"; +import GenericFallback from "../../../Components/GenericFallback"; // Constants const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }]; @@ -27,7 +27,18 @@ const PageSpeed = () => { }); if (networkError === true) { - return ; + return ( + + + Network error + + Please check your connection + + ); } if (!isLoading && monitors?.length === 0) { diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 8a9fdcbe9..2824f80d6 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -9,7 +9,7 @@ import BarChart from "../../../../../Components/Charts/BarChart"; import ActionsMenu from "../ActionsMenu"; import LoadingSpinner from "../LoadingSpinner"; -import UptimeDataTableSkeleton from "./skeleton"; +import TableSkeleton from "../../../../../Components/Table/skeleton"; // Utils import { useTheme } from "@emotion/react"; @@ -175,7 +175,7 @@ const UptimeDataTable = ({ ]; if (monitorsAreLoading) { - return ; + return ; } return ( diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx deleted file mode 100644 index e3c4fa2e2..000000000 --- a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/skeleton.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Skeleton } from "@mui/material"; - -const UptimeDataTableSkeleton = () => { - return ( - - ); -}; - -export default UptimeDataTableSkeleton; diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx index 1356d9e7c..31dcc93be 100644 --- a/Client/src/Pages/Uptime/Monitors/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/index.jsx @@ -6,13 +6,13 @@ import UptimeDataTable from "./Components/UptimeDataTable"; import Pagination from "../../../Components/Table/TablePagination"; import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; import Fallback from "../../../Components/Fallback"; -import NetworkErrorFallback from "../../../Components/NetworkErrorFallback"; +import GenericFallback from "../../../Components/GenericFallback"; import SearchComponent from "./Components/SearchComponent"; import MonitorCountHeader from "../../../Components/MonitorCountHeader"; // MUI Components -import { Stack, Box, Button } from "@mui/material"; +import { Stack, Box, Button, Typography } from "@mui/material"; // Utils import { useState, useCallback } from "react"; @@ -109,8 +109,21 @@ const UptimeMonitors = () => { }); const totalMonitors = monitorsSummary?.totalMonitors ?? 0; - if (networkError) return ; - + if (networkError) { + return ( + + {" "} + + Network error + + Please check your connection + + ); + } if (!isLoading && !monitorsAreLoading && totalMonitors === 0) { return ( Date: Thu, 30 Jan 2025 14:42:29 +0000 Subject: [PATCH 075/106] Update dependency bullmq to v5.39.1 --- 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 54fb6d236..2ba0976bd 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.39.0", + "bullmq": "5.39.1", "cors": "^2.8.5", "dockerode": "4.0.4", "dotenv": "^16.4.5", @@ -1748,9 +1748,9 @@ } }, "node_modules/bullmq": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.39.0.tgz", - "integrity": "sha512-aS+wtZsjW4/9mv2iZRynkSvEXCFS5h3Ko+OgArJco5rEgTE7v/TehDKv6gYEsKVbavOgiNlUNjgEhWhrzDSLBg==", + "version": "5.39.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.39.1.tgz", + "integrity": "sha512-z6ovgX3mMly0L9F3dStCuiMQOk3bqiwqgr83mbjRlX5yGRcStwP9kZsrO0a5VrjPuU0oUgwmWXv37jcSbGaERQ==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", diff --git a/Server/package.json b/Server/package.json index d8eeee5df..0b826fd39 100644 --- a/Server/package.json +++ b/Server/package.json @@ -18,7 +18,7 @@ "dependencies": { "axios": "^1.7.2", "bcrypt": "5.1.1", - "bullmq": "5.39.0", + "bullmq": "5.39.1", "cors": "^2.8.5", "dockerode": "4.0.4", "dotenv": "^16.4.5", From 645018c9fbdc968cbb1fe2cdf1bb9ddc17f346c4 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 30 Jan 2025 16:25:37 +0000 Subject: [PATCH 076/106] Update dependency react-router to v6.29.0 --- Client/package-lock.json | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 5240bec9b..0a355a046 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -5447,12 +5447,12 @@ } }, "node_modules/react-router": { - "version": "6.28.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.2.tgz", - "integrity": "sha512-BgFY7+wEGVjHCiqaj2XiUBQ1kkzfg6UoKYwEe0wv+FF+HNPCxtS/MVPvLAPH++EsuCMReZl9RYVGqcHLk5ms3A==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz", + "integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.21.1" + "@remix-run/router": "1.22.0" }, "engines": { "node": ">=14.0.0" @@ -5478,6 +5478,30 @@ "react-dom": ">=16.8" } }, + "node_modules/react-router-dom/node_modules/react-router": { + "version": "6.28.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.2.tgz", + "integrity": "sha512-BgFY7+wEGVjHCiqaj2XiUBQ1kkzfg6UoKYwEe0wv+FF+HNPCxtS/MVPvLAPH++EsuCMReZl9RYVGqcHLk5ms3A==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.21.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router/node_modules/@remix-run/router": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", + "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/react-smooth": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", From 1626ddc3319264a6c2adeee7e6e943775677dcd3 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 30 Jan 2025 20:36:53 +0000 Subject: [PATCH 077/106] Update dependency react-router-dom to v6.29.0 --- Client/package-lock.json | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 0a355a046..802375170 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -1715,9 +1715,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.1.tgz", - "integrity": "sha512-KeBYSwohb8g4/wCcnksvKTYlg69O62sQeLynn2YE+5z7JWEj95if27kclW9QqbrlsQ2DINI8fjbV3zyuKfwjKg==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", + "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -5462,13 +5462,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.28.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.2.tgz", - "integrity": "sha512-O81EWqNJWqvlN/a7eTudAdQm0TbI7hw+WIi7OwwMcTn5JMyZ0ibTFNGz+t+Lju0df4LcqowCegcrK22lB1q9Kw==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz", + "integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.21.1", - "react-router": "6.28.2" + "@remix-run/router": "1.22.0", + "react-router": "6.29.0" }, "engines": { "node": ">=14.0.0" @@ -5478,30 +5478,6 @@ "react-dom": ">=16.8" } }, - "node_modules/react-router-dom/node_modules/react-router": { - "version": "6.28.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.2.tgz", - "integrity": "sha512-BgFY7+wEGVjHCiqaj2XiUBQ1kkzfg6UoKYwEe0wv+FF+HNPCxtS/MVPvLAPH++EsuCMReZl9RYVGqcHLk5ms3A==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.21.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router/node_modules/@remix-run/router": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", - "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/react-smooth": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", From de7f0a119339491ffe9f2114045a603c2f42188f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 30 Jan 2025 14:23:05 -0800 Subject: [PATCH 078/106] rename CreateMonitor header to match other headers --- .../{CreateMonitorHeader => MonitorCreateHeader}/index.jsx | 2 +- .../{CreateMonitorHeader => MonitorCreateHeader}/skeleton.jsx | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Client/src/Components/{CreateMonitorHeader => MonitorCreateHeader}/index.jsx (90%) rename Client/src/Components/{CreateMonitorHeader => MonitorCreateHeader}/skeleton.jsx (100%) diff --git a/Client/src/Components/CreateMonitorHeader/index.jsx b/Client/src/Components/MonitorCreateHeader/index.jsx similarity index 90% rename from Client/src/Components/CreateMonitorHeader/index.jsx rename to Client/src/Components/MonitorCreateHeader/index.jsx index b8fcbc39a..03dcc57e5 100644 --- a/Client/src/Components/CreateMonitorHeader/index.jsx +++ b/Client/src/Components/MonitorCreateHeader/index.jsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import PropTypes from "prop-types"; import SkeletonLayout from "./skeleton"; -const CreateMonitorHeader = ({ isAdmin, shouldRender, path }) => { +const CreateMonitorHeader = ({ isAdmin, shouldRender = true, path }) => { const navigate = useNavigate(); if (!isAdmin) return null; if (!shouldRender) return ; diff --git a/Client/src/Components/CreateMonitorHeader/skeleton.jsx b/Client/src/Components/MonitorCreateHeader/skeleton.jsx similarity index 100% rename from Client/src/Components/CreateMonitorHeader/skeleton.jsx rename to Client/src/Components/MonitorCreateHeader/skeleton.jsx From cdc407fd21191a6311eb45815fefec40cfa67c40 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 30 Jan 2025 14:23:42 -0800 Subject: [PATCH 079/106] update route --- Client/src/Routes/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 36c930ace..555c335f7 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -1,6 +1,5 @@ import { Navigate, Route, Routes as LibRoutes } from "react-router"; import HomeLayout from "../Components/Layouts/HomeLayout"; -import { Infrastructure } from "../Pages/Infrastructure"; import NotFound from "../Pages/NotFound"; // Auth @@ -24,6 +23,7 @@ import PageSpeedDetails from "../Pages/PageSpeed/Details"; import PageSpeedConfigure from "../Pages/PageSpeed/Configure"; // Infrastructure +import Infrastructure from "../Pages/Infrastructure/Monitors"; import InfrastructureCreate from "../Pages/Infrastructure/Create"; import InfrastructureDetails from "../Pages/Infrastructure/Details"; From 016cd6976349750e68e12018d0c2f3c93c684395 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 30 Jan 2025 14:24:46 -0800 Subject: [PATCH 080/106] Add shouldRender prop to table --- Client/src/Components/Table/index.jsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Client/src/Components/Table/index.jsx b/Client/src/Components/Table/index.jsx index 54e47ed1f..b716038e9 100644 --- a/Client/src/Components/Table/index.jsx +++ b/Client/src/Components/Table/index.jsx @@ -7,6 +7,7 @@ import { TableHead, TableRow, } from "@mui/material"; +import SkeletonLayout from "./skeleton"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; @@ -33,6 +34,7 @@ import { useTheme } from "@emotion/react"; */ const DataTable = ({ + shouldRender = true, headers = [], data = [], config = { @@ -41,6 +43,10 @@ const DataTable = ({ }, }) => { const theme = useTheme(); + if (!shouldRender) { + return ; + } + if ((headers?.length ?? 0) === 0) { return "No data"; } @@ -117,6 +123,7 @@ const DataTable = ({ }; DataTable.propTypes = { + shouldRender: PropTypes.bool, headers: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, From 880195efde8aece9e9086902d6c7ca7cd232cc6a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 30 Jan 2025 14:26:18 -0800 Subject: [PATCH 081/106] set initial loading state to true, skip one render --- Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx | 2 +- Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx b/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx index 57444ad05..614006792 100644 --- a/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx +++ b/Client/src/Pages/Incidents/Hooks/useChecksFetch.jsx @@ -7,7 +7,7 @@ const useChecksFetch = ({ selectedMonitor, filter, dateRange, page, rowsPerPage const { authToken, user } = useSelector((state) => state.auth); //Local - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [networkError, setNetworkError] = useState(false); const [checks, setChecks] = useState(undefined); const [checksCount, setChecksCount] = useState(undefined); diff --git a/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx b/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx index 0ffd18571..20043e80d 100644 --- a/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx +++ b/Client/src/Pages/Incidents/Hooks/useMonitorsFetch.jsx @@ -3,7 +3,7 @@ import { networkService } from "../../../main"; import { createToast } from "../../../Utils/toastUtils"; const useMonitorsFetch = ({ authToken, teamId }) => { //Local state - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [networkError, setNetworkError] = useState(false); const [monitors, setMonitors] = useState(undefined); From b928210f8a26829ba676c71796a61ff7c9d660de Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 30 Jan 2025 14:27:11 -0800 Subject: [PATCH 082/106] reactor infrastructure monitors page --- .../Components/MonitorsTable/index.jsx | 134 ++++++++ .../Components/MonitorsTableMenu}/index.jsx | 8 +- .../Monitors/Hooks/useMonitorFetch.jsx | 45 +++ .../Pages/Infrastructure/Monitors/index.jsx | 110 +++++++ Client/src/Pages/Infrastructure/index.jsx | 295 ------------------ Client/src/Pages/PageSpeed/Monitors/index.jsx | 2 +- Client/src/Pages/Uptime/Monitors/index.jsx | 5 +- 7 files changed, 297 insertions(+), 302 deletions(-) create mode 100644 Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx rename Client/src/Pages/Infrastructure/{components/Menu => Monitors/Components/MonitorsTableMenu}/index.jsx (93%) create mode 100644 Client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx create mode 100644 Client/src/Pages/Infrastructure/Monitors/index.jsx delete mode 100644 Client/src/Pages/Infrastructure/index.jsx diff --git a/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx b/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx new file mode 100644 index 000000000..f44b844a7 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTable/index.jsx @@ -0,0 +1,134 @@ +// Components +import DataTable from "../../../../../Components/Table"; +import Host from "../../../../Uptime/Monitors/Components/Host"; +import { StatusLabel } from "../../../../../Components/Label"; +import { Stack } from "@mui/material"; +import { InfrastructureMenu } from "../MonitorsTableMenu"; +// Assets +import CPUChipIcon from "../../../../../assets/icons/cpu-chip.svg?react"; +import CustomGauge from "../../../../../Components/Charts/CustomGauge"; + +// Utils +import { useTheme } from "@emotion/react"; +import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils"; +import { useNavigate } from "react-router-dom"; + +const MonitorsTable = ({ shouldRender, monitors, isAdmin, handleActionMenuDelete }) => { + // Utils + const theme = useTheme(); + const { determineState } = useUtils(); + const navigate = useNavigate(); + + // Handlers + const openDetails = (id) => { + navigate(`/infrastructure/${id}`); + }; + 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 data = monitors?.map((monitor) => { + const processor = + ((monitor.checks[0]?.cpu?.frequency ?? 0) / 1000).toFixed(2) + " GHz"; + const cpu = (monitor?.checks[0]?.cpu.usage_percent ?? 0) * 100; + const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100; + const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100; + const status = determineState(monitor); + const uptimePercentage = ((monitor?.uptimePercentage ?? 0) * 100) + .toFixed(2) + .toString(); + const percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.error.main + : monitor.uptimePercentage < 0.5 + ? theme.palette.warning.main + : theme.palette.success.main; + + return { + id: monitor._id, + name: monitor.name, + url: monitor.url, + processor, + cpu, + mem, + disk, + status, + uptimePercentage, + percentageColor, + }; + }); + + return ( + openDetails(row.id), + }} + /> + ); +}; + +export default MonitorsTable; diff --git a/Client/src/Pages/Infrastructure/components/Menu/index.jsx b/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx similarity index 93% rename from Client/src/Pages/Infrastructure/components/Menu/index.jsx rename to Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx index f036b9d86..39a9d18cb 100644 --- a/Client/src/Pages/Infrastructure/components/Menu/index.jsx +++ b/Client/src/Pages/Infrastructure/Monitors/Components/MonitorsTableMenu/index.jsx @@ -4,12 +4,12 @@ import { useRef, useState } from "react"; import { useSelector } from "react-redux"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../Utils/toastUtils"; +import { createToast } from "../../../../../Utils/toastUtils"; import { IconButton, Menu, MenuItem } from "@mui/material"; -import Settings from "../../../../assets/icons/settings-bold.svg?react"; +import Settings from "../../../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import Dialog from "../../../../Components/Dialog"; -import { networkService } from "../../../../Utils/NetworkService.js"; +import Dialog from "../../../../../Components/Dialog"; +import { networkService } from "../../../../../Utils/NetworkService.js"; /** * InfrastructureMenu Component diff --git a/Client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx new file mode 100644 index 000000000..8e316b728 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Monitors/Hooks/useMonitorFetch.jsx @@ -0,0 +1,45 @@ +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; + +const useMonitorFetch = ({ page, rowsPerPage, updateTrigger }) => { + // Redux state + const { authToken, user } = useSelector((state) => state.auth); + + // Local state + const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [monitors, setMonitors] = useState(undefined); + const [summary, setSummary] = useState(undefined); + + useEffect(() => { + const fetchMonitors = async () => { + try { + const response = await networkService.getMonitorsByTeamId({ + authToken, + teamId: user.teamId, + limit: 1, + types: ["hardware"], + page: page, + rowsPerPage: rowsPerPage, + }); + setMonitors(response?.data?.data?.filteredMonitors ?? []); + setSummary(response?.data?.data?.summary ?? {}); + } catch (error) { + setNetworkError(true); + createToast({ + body: error.message, + }); + } finally { + setIsLoading(false); + } + }; + + fetchMonitors(); + }, [page, rowsPerPage, authToken, user.teamId, updateTrigger]); + + return { monitors, summary, isLoading, networkError }; +}; + +export { useMonitorFetch }; diff --git a/Client/src/Pages/Infrastructure/Monitors/index.jsx b/Client/src/Pages/Infrastructure/Monitors/index.jsx new file mode 100644 index 000000000..6418dfd12 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Monitors/index.jsx @@ -0,0 +1,110 @@ +// Components +import { Stack, Typography } from "@mui/material"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import MonitorCountHeader from "../../../Components/MonitorCountHeader"; +import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; +import MonitorsTable from "./Components/MonitorsTable"; +import Pagination from "../../..//Components/Table/TablePagination"; +import GenericFallback from "../../../Components/GenericFallback"; +import Fallback from "../../../Components/Fallback"; +// Utils +import { useTheme } from "@emotion/react"; +import { useMonitorFetch } from "./Hooks/useMonitorFetch"; +import { useState } from "react"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +// Constants +const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; + +const InfrastructureMonitors = () => { + // Redux state + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(5); + const [updateTrigger, setUpdateTrigger] = useState(false); + + // Utils + const theme = useTheme(); + const isAdmin = useIsAdmin(); + + // Handlers + const handleActionMenuDelete = () => { + setUpdateTrigger(!updateTrigger); + }; + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(event.target.value); + }; + + const { monitors, summary, isLoading, networkError } = useMonitorFetch({ + page, + rowsPerPage, + updateTrigger, + }); + + if (networkError === true) { + return ( + + + Network error + + Please check your connection + + ); + } + + if (!isLoading && monitors?.length === 0) { + return ( + + ); + } + + console.log(isLoading); + return ( + + + + + + + + ); +}; + +export default InfrastructureMonitors; diff --git a/Client/src/Pages/Infrastructure/index.jsx b/Client/src/Pages/Infrastructure/index.jsx deleted file mode 100644 index 91367ce22..000000000 --- a/Client/src/Pages/Infrastructure/index.jsx +++ /dev/null @@ -1,295 +0,0 @@ -import { useEffect, useState, useCallback } from "react"; -import { useNavigate } from "react-router-dom"; -import { /* useDispatch, */ useSelector } from "react-redux"; -import { useTheme } from "@emotion/react"; -import useUtils from "../Uptime/Monitors/Hooks/useUtils.jsx"; -import { jwtDecode } from "jwt-decode"; -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 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/Table/TablePagination/index.jsx"; -// import { getInfrastructureMonitorsByTeamId } from "../../Features/InfrastructureMonitors/infrastructureMonitorsSlice"; -import { networkService } from "../../Utils/NetworkService.js"; -import CustomGauge from "../../Components/Charts/CustomGauge/index.jsx"; -import Host from "../Uptime/Monitors/Components/Host/index.jsx"; -import { useIsAdmin } from "../../Hooks/useIsAdmin.js"; -import { InfrastructureMenu } from "./components/Menu"; - -const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }]; - -/** - * This is the Infrastructure monitoring page. This is a work in progress - * - * @param - Define params. - * @returns {JSX.Element} The infrastructure monitoring page. - */ - -function Infrastructure() { - /* Adding this custom hook so we can avoid using the HOC approach that can lower performance (we are calling the admin logic N times on initializing the project. using a custom hook will cal it ass needed ) */ - const isAdmin = useIsAdmin(); - const theme = useTheme(); - const [isLoading, setIsLoading] = useState(true); - - const navigate = useNavigate(); - const navigateToCreate = () => navigate("/infrastructure/create"); - - const [page, setPage] = useState(0); - /* TODO refactor this, so it is not aware of the MUI implementation. First argument only exists because of MUI. This should require onlu the new page. Adapting for MUI should happen inside of table pagination component */ - const handleChangePage = (_, newPage) => { - setPage(newPage); - }; - - const [rowsPerPage, setRowsPerPage] = useState(5); - const handleChangeRowsPerPage = (event) => { - setRowsPerPage(parseInt(event.target.value)); - setPage(0); - }; - const [monitors, setMonitors] = useState([]); - const [summary, setSummary] = useState({}); - - const { authToken } = useSelector((state) => state.auth); - const user = jwtDecode(authToken); - - const fetchMonitors = useCallback(async () => { - try { - setIsLoading(true); - const response = await networkService.getMonitorsByTeamId({ - authToken, - teamId: user.teamId, - limit: 1, - types: ["hardware"], - page: page, - rowsPerPage: rowsPerPage, - }); - setMonitors(response?.data?.data?.filteredMonitors ?? []); - setSummary(response?.data?.data?.summary ?? {}); - } catch (error) { - console.error(error); - } finally { - setIsLoading(false); - } - }, [page, rowsPerPage, authToken, user.teamId]); - - useEffect(() => { - fetchMonitors(); - }, [fetchMonitors]); - - const { determineState } = useUtils(); - // do it here - function openDetails(id) { - navigate(`/infrastructure/${id}`); - } - function handleActionMenuDelete() { - fetchMonitors(); - } - - 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?.frequency ?? 0) / 1000).toFixed(2) + " GHz"; - const cpu = (monitor?.checks[0]?.cpu.usage_percent ?? 0) * 100; - const mem = (monitor?.checks[0]?.memory.usage_percent ?? 0) * 100; - const disk = (monitor?.checks[0]?.disk[0]?.usage_percent ?? 0) * 100; - const status = determineState(monitor); - const uptimePercentage = ((monitor?.uptimePercentage ?? 0) * 100) - .toFixed(2) - .toString(); - const percentageColor = - monitor.uptimePercentage < 0.25 - ? theme.palette.error.main - : monitor.uptimePercentage < 0.5 - ? theme.palette.warning.main - : theme.palette.success.main; - - return { - id: monitor._id, - name: monitor.name, - url: monitor.url, - processor, - cpu, - mem, - disk, - status, - uptimePercentage, - percentageColor, - }; - }); - - let isActuallyLoading = isLoading && monitors?.length === 0; - return ( - [class*="fallback__"])': { - position: "relative", - border: 1, - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - borderStyle: "dashed", - backgroundColor: theme.palette.primary.main, - overflow: "hidden", - }, - }} - > - {isActuallyLoading ? ( - - ) : monitors?.length !== 0 ? ( - - - - - {isAdmin && ( - - )} - - - - - Infrastructure monitors - {/* TODO Same as the one in UptimaDataTable. Create component */} - - {summary?.totalMonitors ?? 0} - - - - openDetails(row.id), - }} - headers={headers} - data={monitorsAsRows} - /> - - - - - ) : ( - - )} - - ); -} - -export { Infrastructure }; diff --git a/Client/src/Pages/PageSpeed/Monitors/index.jsx b/Client/src/Pages/PageSpeed/Monitors/index.jsx index 470a33f18..ea3ed93af 100644 --- a/Client/src/Pages/PageSpeed/Monitors/index.jsx +++ b/Client/src/Pages/PageSpeed/Monitors/index.jsx @@ -1,7 +1,7 @@ // Components import Breadcrumbs from "../../../Components/Breadcrumbs"; import { Stack, Typography } from "@mui/material"; -import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; +import CreateMonitorHeader from "../../../Components/MonitorCreateHeader"; import MonitorCountHeader from "../../../Components/MonitorCountHeader"; import MonitorGrid from "./Components/MonitorGrid"; import Fallback from "../../../Components/Fallback"; diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx index 31dcc93be..9f4b8c942 100644 --- a/Client/src/Pages/Uptime/Monitors/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/index.jsx @@ -4,7 +4,7 @@ import Greeting from "../../../Utils/greeting"; import StatusBoxes from "./Components/StatusBoxes"; import UptimeDataTable from "./Components/UptimeDataTable"; import Pagination from "../../../Components/Table/TablePagination"; -import CreateMonitorHeader from "../../../Components/CreateMonitorHeader"; +import CreateMonitorHeader from "../../../Components/MonitorCreateHeader"; import Fallback from "../../../Components/Fallback"; import GenericFallback from "../../../Components/GenericFallback"; import SearchComponent from "./Components/SearchComponent"; @@ -107,7 +107,7 @@ const UptimeMonitors = () => { order: sort?.order, triggerUpdate: monitorUpdateTrigger, }); - const totalMonitors = monitorsSummary?.totalMonitors ?? 0; + const totalMonitors = monitorsSummary?.totalMonitors; if (networkError) { return ( @@ -135,6 +135,7 @@ const UptimeMonitors = () => { /> ); } + console.log({ isLoading }); return ( Date: Thu, 30 Jan 2025 14:31:36 -0800 Subject: [PATCH 083/106] replace default value of 0 for --- Client/src/Pages/Uptime/Monitors/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx index 9f4b8c942..962530d41 100644 --- a/Client/src/Pages/Uptime/Monitors/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/index.jsx @@ -107,7 +107,7 @@ const UptimeMonitors = () => { order: sort?.order, triggerUpdate: monitorUpdateTrigger, }); - const totalMonitors = monitorsSummary?.totalMonitors; + const totalMonitors = monitorsSummary?.totalMonitors ?? 0; if (networkError) { return ( From 62f2888f2a9d35c83a4bd626ac0e5f11cb7f51f3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 07:09:22 -0800 Subject: [PATCH 084/106] fix proptype --- Client/src/Components/MonitorCreateHeader/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Components/MonitorCreateHeader/index.jsx b/Client/src/Components/MonitorCreateHeader/index.jsx index 03dcc57e5..01a102b7d 100644 --- a/Client/src/Components/MonitorCreateHeader/index.jsx +++ b/Client/src/Components/MonitorCreateHeader/index.jsx @@ -28,6 +28,6 @@ export default CreateMonitorHeader; CreateMonitorHeader.propTypes = { isAdmin: PropTypes.bool.isRequired, - shouldRender: PropTypes.bool.isRequired, + shouldRender: PropTypes.bool, path: PropTypes.string.isRequired, }; From 6c2298b976b0fdd7e41b7dcee838a603f5f0ea40 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 07:32:49 -0800 Subject: [PATCH 085/106] add empty view for uptime details --- Client/src/Pages/Uptime/Details/index.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 33583b0cb..e8311ebb1 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -6,8 +6,9 @@ import ChartBoxes from "./Components/ChartBoxes"; import ResponseTimeChart from "./Components/Charts/ResponseTimeChart"; import ResponseTable from "./Components/ResponseTable"; import UptimeStatusBoxes from "./Components/UptimeStatusBoxes"; +import GenericFallback from "../../../Components/GenericFallback"; // MUI Components -import { Stack } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; // Utils import { useState } from "react"; @@ -77,6 +78,14 @@ const UptimeDetails = () => { setRowsPerPage(event.target.value); }; + if (!monitorIsLoading && !checksAreLoading && checksCount === 0) { + return ( + + There is no history for this monitor yet. + + ); + } + return ( From 22b20409fc4f592013d484e02f64d4622543ac88 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 07:33:29 -0800 Subject: [PATCH 086/106] update queries to return consistent values --- Server/db/mongo/modules/checkModule.js | 8 ++++++-- Server/db/mongo/modules/monitorModule.js | 1 + .../db/mongo/modules/monitorModuleQueries.js | 18 ++++++++++-------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index 3d158a812..6450b78a2 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -123,8 +123,12 @@ const getChecksByMonitor = async (req) => { }, { $project: { - checksCount: { $arrayElemAt: ["$summary.checksCount", 0] }, - checks: "$checks", + checksCount: { + $ifNull: [{ $arrayElemAt: ["$summary.checksCount", 0] }, 0], + }, + checks: { + $ifNull: ["$checks", []], + }, }, }, ]); diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 9ff80e7bf..af4ea7e9f 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -340,6 +340,7 @@ const getUptimeDetailsById = async (req) => { }; const dateString = formatLookup[dateRange]; + const results = await Check.aggregate( buildUptimeDetailsPipeline(monitor, dates, dateString) ); diff --git a/Server/db/mongo/modules/monitorModuleQueries.js b/Server/db/mongo/modules/monitorModuleQueries.js index cdbcd51d8..094e1f932 100644 --- a/Server/db/mongo/modules/monitorModuleQueries.js +++ b/Server/db/mongo/modules/monitorModuleQueries.js @@ -218,18 +218,20 @@ const buildUptimeDetailsPipeline = (monitor, dates, dateString) => { { $project: { uptimeStreak: { - $cond: [ - { $eq: [{ $size: { $first: "$uptimeStreak.streak.checks" } }, 0] }, - 0, - { - $subtract: [ - new Date(), + $let: { + vars: { + checks: { $ifNull: [{ $first: "$uptimeStreak.streak.checks" }, []] }, + }, + in: { + $cond: [ + { $eq: [{ $size: "$$checks" }, 0] }, + 0, { - $last: { $first: "$uptimeStreak.streak.checks.createdAt" }, + $subtract: [new Date(), { $last: "$$checks.createdAt" }], }, ], }, - ], + }, }, avgResponseTime: { $arrayElemAt: ["$aggregateData.avgResponseTime", 0], From 00a9630e299646c162be64001dbbe013bb5ee51c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 10:06:28 -0800 Subject: [PATCH 087/106] Add reusable TimeFrameHeader component --- .../MonitorTimeFrameHeader/index.jsx | 72 +++++++++++++++++++ .../MonitorTimeFrameHeader/skeleton.jsx | 23 ++++++ 2 files changed, 95 insertions(+) create mode 100644 Client/src/Components/MonitorTimeFrameHeader/index.jsx create mode 100644 Client/src/Components/MonitorTimeFrameHeader/skeleton.jsx diff --git a/Client/src/Components/MonitorTimeFrameHeader/index.jsx b/Client/src/Components/MonitorTimeFrameHeader/index.jsx new file mode 100644 index 000000000..e43d63494 --- /dev/null +++ b/Client/src/Components/MonitorTimeFrameHeader/index.jsx @@ -0,0 +1,72 @@ +import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import SkeletonLayout from "./skeleton"; +import PropTypes from "prop-types"; + +const MonitorTimeFrameHeader = ({ + shouldRender = true, + hasDateRange = true, + dateRange, + setDateRange, +}) => { + const theme = useTheme(); + + if (!shouldRender) { + return ; + } + + let timeFramePicker = null; + + if (hasDateRange) { + timeFramePicker = ( + + + + + + ); + } + + return ( + + + Showing statistics for past{" "} + {dateRange === "day" ? "24 hours" : dateRange === "week" ? "7 days" : "30 days"}. + + {timeFramePicker} + + ); +}; + +MonitorTimeFrameHeader.propTypes = { + shouldRender: PropTypes.bool, + hasDateRange: PropTypes.bool, + dateRange: PropTypes.string, + setDateRange: PropTypes.func, +}; + +export default MonitorTimeFrameHeader; diff --git a/Client/src/Components/MonitorTimeFrameHeader/skeleton.jsx b/Client/src/Components/MonitorTimeFrameHeader/skeleton.jsx new file mode 100644 index 000000000..d7c395d54 --- /dev/null +++ b/Client/src/Components/MonitorTimeFrameHeader/skeleton.jsx @@ -0,0 +1,23 @@ +import { Stack, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + ); +}; + +export default SkeletonLayout; From 4d8f0b72873bfeeadf654faeafd8a170677f6fe5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 10:06:57 -0800 Subject: [PATCH 088/106] conditionally render time frame picker --- .../Components/MonitorStatusHeader/ConfigButton/index.jsx | 7 ++++--- Client/src/Components/MonitorStatusHeader/index.jsx | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx b/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx index fc6407f31..c966abf33 100644 --- a/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx +++ b/Client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx @@ -3,7 +3,7 @@ import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -const ConfigButton = ({ shouldRender, monitorId }) => { +const ConfigButton = ({ shouldRender = true, monitorId, path }) => { const theme = useTheme(); const navigate = useNavigate(); @@ -14,7 +14,7 @@ const ConfigButton = ({ shouldRender, monitorId }) => { - - - - - ); -}; - -TimeFramePicker.propTypes = { - shouldRender: PropTypes.bool, - dateRange: PropTypes.string, - setDateRange: PropTypes.func, -}; - -export default TimeFramePicker; diff --git a/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/skeleton.jsx b/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/skeleton.jsx deleted file mode 100644 index d7c395d54..000000000 --- a/Client/src/Pages/Uptime/Details/Components/TimeFramePicker/skeleton.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; - -const SkeletonLayout = () => { - return ( - - - - - ); -}; - -export default SkeletonLayout; From 0b3cd9ed174f5ddc0daa96ca8f5511f97775d357 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 10:07:59 -0800 Subject: [PATCH 091/106] Add empty veiw to PageSpeed Details, fix config path --- Client/src/Pages/PageSpeed/Details/index.jsx | 34 ++++++++++++++++---- Client/src/Pages/Uptime/Details/index.jsx | 6 ++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 6fa4aa82c..c52c1c817 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -1,10 +1,12 @@ // Components -import { Stack, Typography, Skeleton } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; +import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; import MonitorStatusHeader from "../../../Components/MonitorStatusHeader"; import PageSpeedStatusBoxes from "./Components/PageSpeedStatusBoxes"; import PageSpeedAreaChart from "./Components/PageSpeedAreaChart"; import PerformanceReport from "./Components/PerformanceReport"; +import GenericFallback from "../../../Components/GenericFallback"; // Utils import { useTheme } from "@emotion/react"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; @@ -41,10 +43,28 @@ const PageSpeedDetails = () => { setMetrics((prev) => ({ ...prev, [id]: !prev[id] })); }; + // Empty view, displayed when loading is complete and there are no checks + if (!isLoading && monitor?.checks?.length === 0) { + return ( + + + + + There is no history for this monitor yet. + + + ); + } + return ( { shouldRender={!isLoading} monitor={monitor} /> - - Showing statistics for past 24 hours. - + + { { monitor={monitor} certificateExpiry={certificateExpiry} /> - From 9515477fd7b440e127fa6bc1fe07435c1200cb42 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 10:12:06 -0800 Subject: [PATCH 092/106] Add header to empty view --- Client/src/Pages/Uptime/Details/index.jsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index e8311ebb1..335b173ef 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -78,11 +78,21 @@ const UptimeDetails = () => { setRowsPerPage(event.target.value); }; + // Empty view, displayed when loading is complete and there are no checks if (!monitorIsLoading && !checksAreLoading && checksCount === 0) { return ( - - There is no history for this monitor yet. - + + + + + There is no history for this monitor yet. + + ); } @@ -90,6 +100,7 @@ const UptimeDetails = () => { Date: Fri, 31 Jan 2025 10:14:43 -0800 Subject: [PATCH 093/106] remove console log --- Client/src/Pages/Uptime/Monitors/index.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/Client/src/Pages/Uptime/Monitors/index.jsx b/Client/src/Pages/Uptime/Monitors/index.jsx index 962530d41..0784b46a8 100644 --- a/Client/src/Pages/Uptime/Monitors/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/index.jsx @@ -112,7 +112,6 @@ const UptimeMonitors = () => { if (networkError) { return ( - {" "} { /> ); } - console.log({ isLoading }); return ( Date: Fri, 31 Jan 2025 20:34:32 +0000 Subject: [PATCH 094/106] Update dependency mongoose to v8.9.6 --- 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 2ba0976bd..0c7b32f8c 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -5571,9 +5571,9 @@ } }, "node_modules/mongoose": { - "version": "8.9.5", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.5.tgz", - "integrity": "sha512-SPhOrgBm0nKV3b+IIHGqpUTOmgVL5Z3OO9AwkFEmvOZznXTvplbomstCnPOGAyungtRXE5pJTgKpKcZTdjeESg==", + "version": "8.9.6", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.6.tgz", + "integrity": "sha512-ipLvXwNPVuuuq5H3lnSD0lpaRH3DlCoC6emnMVJvweTwxU29uxDJWxMsNpERDQt8JMvYF1HGVuTK+Id2BlQLCA==", "license": "MIT", "dependencies": { "bson": "^6.10.1", From a265d0e60c008244e08852dd21891c8e7deaf51b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 31 Jan 2025 20:34:40 +0000 Subject: [PATCH 095/106] Update material-ui monorepo to v7.25.0 --- Client/package-lock.json | 34 +++++++++++++++++----------------- Client/package.json | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Client/package-lock.json b/Client/package-lock.json index 802375170..72bd51752 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -15,8 +15,8 @@ "@mui/lab": "6.0.0-beta.25", "@mui/material": "6.4.2", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.24.1", - "@mui/x-date-pickers": "7.24.1", + "@mui/x-data-grid": "7.25.0", + "@mui/x-date-pickers": "7.25.0", "@reduxjs/toolkit": "2.5.1", "axios": "^1.7.4", "dayjs": "1.11.13", @@ -1390,15 +1390,15 @@ "license": "MIT" }, "node_modules/@mui/x-charts": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.24.1.tgz", - "integrity": "sha512-OdTS/nXaANPe4AoUFIDD4LlID8kK/00q+uqVOCkVClEvFQeAkj3pBaghdS4hY7rVqsCgsm+yOStQVJa9G2MR+Q==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.25.0.tgz", + "integrity": "sha512-+DhnojHrVTt8RsTgq8AztzdFpW1kzOgiBdo0Pkl0DyxVdaKELC5QaetFwim9nIxT2zmU/RmiBcOU+qbqmQpFNA==", "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.24.1", + "@mui/x-internals": "7.25.0", "@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.24.1", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.24.1.tgz", - "integrity": "sha512-4sYTbMwsDotuTd2Cwa2JGTPXPWQs8RGJvocAKnIsNOzNdZNMrikE//HO35snriK8s4dauAApY7RVbeisjpVT+A==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.25.0.tgz", + "integrity": "sha512-e9ZLbCgnDiADFiDyXo91ucZFHEMkKBNpwpkaTq5KohzefJfMpMQjTEbJeueSfBG2G1Q1Am2TPeBqrNeReIA7RQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.24.1", + "@mui/x-internals": "7.25.0", "clsx": "^2.1.1", "prop-types": "^15.8.1", "reselect": "^5.1.1" @@ -1485,14 +1485,14 @@ } }, "node_modules/@mui/x-date-pickers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.24.1.tgz", - "integrity": "sha512-ykQugMQHuQKBk3kViW/r0PpubtHQOlrd54bgbdafgTMCeM2VpXvv4zimzOu5IGnM6wEN8hupC7EXZbkrT6x46w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.25.0.tgz", + "integrity": "sha512-t62OSFAKwj7KYQ8KcwTuKj6OgDuLQPSe4QUJcKDzD9rEhRIJVRUw2x27gBSdcls4l0PTrba19TghvDxCZprriw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.24.1", + "@mui/x-internals": "7.25.0", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1551,9 +1551,9 @@ } }, "node_modules/@mui/x-internals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.24.1.tgz", - "integrity": "sha512-9BvJzpLJnS9BDphvkiv6v0QOLxbnu8jhwcexFjtCQ2ZyxtVuVsWzGZ2npT9sGOil7+eaFDmWnJtea/tgrPvSwQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.25.0.tgz", + "integrity": "sha512-tBUN54YznAkmtCIRAOl35Kgl0MjFDIjUbzIrbWRgVSIR3QJ8bXnVSkiRBi+P91SZEl9+ZW0rDj+osq7xFJV0kg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.7", diff --git a/Client/package.json b/Client/package.json index b2e15c02d..441b79824 100644 --- a/Client/package.json +++ b/Client/package.json @@ -18,8 +18,8 @@ "@mui/lab": "6.0.0-beta.25", "@mui/material": "6.4.2", "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.24.1", - "@mui/x-date-pickers": "7.24.1", + "@mui/x-data-grid": "7.25.0", + "@mui/x-date-pickers": "7.25.0", "@reduxjs/toolkit": "2.5.1", "axios": "^1.7.4", "dayjs": "1.11.13", From fa809d7bfdbf276e0f82f03808243fd24ead8bb8 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 13:43:04 -0800 Subject: [PATCH 096/106] update skeleton layout --- Client/src/Components/StatusBoxes/index.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Client/src/Components/StatusBoxes/index.jsx b/Client/src/Components/StatusBoxes/index.jsx index 6e09d0cba..a2c627d19 100644 --- a/Client/src/Components/StatusBoxes/index.jsx +++ b/Client/src/Components/StatusBoxes/index.jsx @@ -4,15 +4,21 @@ import SkeletonLayout from "./skeleton"; // Utils import { useTheme } from "@mui/material/styles"; import PropTypes from "prop-types"; -const StatusBoxes = ({ shouldRender, children }) => { +const StatusBoxes = ({ shouldRender, flexWrap = "nowrap", children }) => { const theme = useTheme(); if (!shouldRender) { - return ; + return ( + + ); } return ( {children} From b8042d6d005869e25cbdc35f99ebd3e5398dade4 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 13:43:28 -0800 Subject: [PATCH 097/106] update skeleton component --- Client/src/Components/StatusBoxes/skeleton.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Client/src/Components/StatusBoxes/skeleton.jsx b/Client/src/Components/StatusBoxes/skeleton.jsx index b0f04ff3f..bc8b18251 100644 --- a/Client/src/Components/StatusBoxes/skeleton.jsx +++ b/Client/src/Components/StatusBoxes/skeleton.jsx @@ -1,21 +1,22 @@ import { Stack, Skeleton } from "@mui/material"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -const SkeletonLayout = ({ numBoxes }) => { +const SkeletonLayout = ({ numBoxes, flexWrap }) => { const theme = useTheme(); return ( {Array.from({ length: numBoxes }).map((_, index) => { - const width = `${100 / numBoxes}%`; + const width = `${200 / numBoxes}%`; return ( ); @@ -25,6 +26,7 @@ const SkeletonLayout = ({ numBoxes }) => { }; SkeletonLayout.propTypes = { + flexWrap: PropTypes.string, numBoxes: PropTypes.number, }; From 460a597e12cdb465c2b3464d324bd94a6c62ca64 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 13:43:57 -0800 Subject: [PATCH 098/106] Refactor infrastructure details --- .../Components/CustomThreshold/index.jsx | 2 +- .../AreaChartBoxes/InfraAreaChart.jsx | 37 + .../Components/AreaChartBoxes/index.jsx | 138 ++++ .../Components/AreaChartBoxes/skeleton.jsx | 25 + .../Components/BaseContainer/index.jsx | 43 + .../Details/Components/GaugeBoxes/Gauge.jsx | 62 ++ .../Details/Components/GaugeBoxes/index.jsx | 80 ++ .../Components/GaugeBoxes/skeleton.jsx | 26 + .../Details/Components/StatusBoxes/index.jsx | 109 +++ .../Hooks/useHardwareMonitorsFetch.jsx | 38 + .../Details/Hooks/useHardwareUtils.jsx | 137 ++++ .../Pages/Infrastructure/Details/empty.jsx | 36 - .../Pages/Infrastructure/Details/index.jsx | 753 ++---------------- .../Pages/Infrastructure/Monitors/index.jsx | 1 - Client/src/Pages/Infrastructure/skeleton.jsx | 73 -- 15 files changed, 782 insertions(+), 778 deletions(-) create mode 100644 Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx create mode 100644 Client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx delete mode 100644 Client/src/Pages/Infrastructure/Details/empty.jsx delete mode 100644 Client/src/Pages/Infrastructure/skeleton.jsx diff --git a/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx b/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx index 922cb46d8..d91e899c5 100644 --- a/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx +++ b/Client/src/Pages/Infrastructure/Create/Components/CustomThreshold/index.jsx @@ -117,7 +117,7 @@ CustomThreshold.propTypes = { fieldName: PropTypes.string, fieldValue: PropTypes.string.isRequired, onFieldChange: PropTypes.func.isRequired, - onFieldBlur: PropTypes.func.isRequired, + onFieldBlur: PropTypes.func, alertUnit: PropTypes.string.isRequired, infrastructureMonitor: PropTypes.object.isRequired, errors: PropTypes.object.isRequired, diff --git a/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx b/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx new file mode 100644 index 000000000..9544bcf2a --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart.jsx @@ -0,0 +1,37 @@ +// Components +import { Typography } from "@mui/material"; +import BaseContainer from "../BaseContainer"; +import AreaChart from "../../../../../Components/Charts/AreaChart"; +// Utils +import { useTheme } from "@emotion/react"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +const InfraAreaChart = ({ config }) => { + const theme = useTheme(); + const { getDimensions } = useHardwareUtils(); + return ( + + + {config.heading} + + + + ); +}; + +export default InfraAreaChart; diff --git a/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx b/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx new file mode 100644 index 000000000..4052b11b5 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/index.jsx @@ -0,0 +1,138 @@ +// Components +import { Stack } from "@mui/material"; +import InfraAreaChart from "./InfraAreaChart"; +import SkeletonLayout from "./skeleton"; + +// Utils +import { + PercentTick, + TzTick, + InfrastructureTooltip, + TemperatureTooltip, +} from "../../../../../Components/Charts/Utils/chartUtils"; +import { useTheme } from "@emotion/react"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +const AreaChartBoxes = ({ shouldRender, monitor, dateRange }) => { + const theme = useTheme(); + const { buildTemps } = useHardwareUtils(); + + if (!shouldRender) { + return ; + } + + const { stats } = monitor ?? {}; + const { checks } = stats; + + let latestCheck = checks[0]; + const { temps, tempKeys } = buildTemps(checks); + + const configs = [ + { + type: "memory", + data: checks, + dataKeys: ["avgMemoryUsage"], + heading: "Memory usage", + strokeColor: theme.palette.accent.main, // CAIO_REVIEW + gradientStartColor: theme.palette.accent.main, // CAIO_REVIEW + yLabel: "Memory usage", + yDomain: [0, 1], + yTick: , + xTick: , + toolTip: ( + + ), + }, + { + type: "cpu", + data: checks, + dataKeys: ["avgCpuUsage"], + heading: "CPU usage", + strokeColor: theme.palette.success.main, + gradientStartColor: theme.palette.success.main, + yLabel: "CPU usage", + yDomain: [0, 1], + yTick: , + xTick: , + toolTip: ( + + ), + }, + { + type: "temperature", + data: temps, + dataKeys: tempKeys, + strokeColor: theme.palette.error.main, + gradientStartColor: theme.palette.error.main, + heading: "CPU Temperature", + yLabel: "Temperature", + xTick: , + yDomain: [ + 0, + Math.max(Math.max(...temps.flatMap((t) => tempKeys.map((k) => t[k]))) * 1.1, 200), + ], + toolTip: ( + + ), + }, + ...(latestCheck?.disks?.map((disk, idx) => ({ + type: "disk", + data: checks, + diskIndex: idx, + dataKeys: [`disks[${idx}].usagePercent`], + heading: `Disk${idx} usage`, + strokeColor: theme.palette.warning.main, + gradientStartColor: theme.palette.warning.main, + yLabel: "Disk Usage", + yDomain: [0, 1], + yTick: , + xTick: , + toolTip: ( + + ), + })) || []), + ]; + + return ( + *": { + flexBasis: `calc(50% - ${theme.spacing(8)})`, + maxWidth: `calc(50% - ${theme.spacing(8)})`, + }, + }} + > + {configs.map((config) => ( + + ))} + + ); +}; + +export default AreaChartBoxes; diff --git a/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx b/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx new file mode 100644 index 000000000..f787b88ec --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/AreaChartBoxes/skeleton.jsx @@ -0,0 +1,25 @@ +import { Stack, Skeleton } from "@mui/material"; +import { useTheme } from "@emotion/react"; +const SkeletonLayout = () => { + const theme = useTheme(); + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx b/Client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx new file mode 100644 index 000000000..246769751 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/BaseContainer/index.jsx @@ -0,0 +1,43 @@ +/** + * Renders a base box with consistent styling + * @param {Object} props - Component properties + * @param {React.ReactNode} props.children - Child components to render inside the box + * @param {Object} props.sx - Additional styling for the box + * @returns {React.ReactElement} Styled box component + */ + +// Components +import { Box } from "@mui/material"; + +// Utils +import { useTheme } from "@emotion/react"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import PropTypes from "prop-types"; + +const BaseContainer = ({ children, sx = {} }) => { + const theme = useTheme(); + const { getDimensions } = useHardwareUtils(); + return ( + + {children} + + ); +}; + +BaseContainer.propTypes = { + children: PropTypes.node.isRequired, + sx: PropTypes.object, +}; + +export default BaseContainer; diff --git a/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx b/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx new file mode 100644 index 000000000..b1202682d --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/Gauge.jsx @@ -0,0 +1,62 @@ +// Components +import CustomGauge from "../../../../../Components/Charts/CustomGauge"; +import BaseContainer from "../BaseContainer"; +import { Stack, Typography, Box } from "@mui/material"; +// Utils +import { useTheme } from "@emotion/react"; +import PropTypes from "prop-types"; + +const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => { + const theme = useTheme(); + + return ( + + + + {heading} + + + {metricOne} + {valueOne} + + + {metricTwo} + {valueTwo} + + + + + ); +}; + +Gauge.propTypes = { + value: PropTypes.number, + heading: PropTypes.string, + metricOne: PropTypes.string, + valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + metricTwo: PropTypes.string, + valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), +}; + +export default Gauge; diff --git a/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx b/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx new file mode 100644 index 000000000..1d2e044ce --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/index.jsx @@ -0,0 +1,80 @@ +// Components +import { Stack } from "@mui/material"; +import Gauge from "./Gauge"; +import SkeletonLayout from "./skeleton"; + +// Utils +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; +import { useTheme } from "@emotion/react"; + +const Gauges = ({ shouldRender, monitor }) => { + const { decimalToPercentage, formatBytes } = useHardwareUtils(); + const theme = useTheme(); + + if (!shouldRender) { + return ; + } + + const { stats } = monitor ?? {}; + let latestCheck = stats?.aggregateData?.latestCheck; + const memoryUsagePercent = latestCheck?.memory?.usage_percent ?? 0; + const memoryUsedBytes = latestCheck?.memory?.used_bytes ?? 0; + const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0; + const cpuUsagePercent = latestCheck?.cpu?.usage_percent ?? 0; + const cpuPhysicalCores = latestCheck?.cpu?.physical_core ?? 0; + const cpuFrequency = latestCheck?.cpu?.frequency ?? 0; + + const gauges = [ + { + type: "memory", + value: decimalToPercentage(memoryUsagePercent), + heading: "Memory usage", + metricOne: "Used", + valueOne: formatBytes(memoryUsedBytes, true), + metricTwo: "Total", + valueTwo: formatBytes(memoryTotalBytes, true), + }, + { + type: "cpu", + value: decimalToPercentage(cpuUsagePercent), + heading: "CPU usage", + metricOne: "Cores", + valueOne: cpuPhysicalCores ?? 0, + metricTwo: "Frequency", + valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`, + }, + ...(latestCheck?.disk ?? []).map((disk, idx) => ({ + type: "disk", + diskIndex: idx, + value: decimalToPercentage(disk.usage_percent), + heading: `Disk${idx} usage`, + metricOne: "Used", + valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true), + metricTwo: "Total", + valueTwo: formatBytes(disk.total_bytes, true), + })), + ]; + + return ( + + {gauges.map((gauge) => { + return ( + + ); + })} + + ); +}; + +export default Gauges; diff --git a/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx b/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx new file mode 100644 index 000000000..347a52901 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/GaugeBoxes/skeleton.jsx @@ -0,0 +1,26 @@ +import { Stack, Skeleton } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +const SkeletonLayout = () => { + const theme = useTheme(); + + return ( + + {Array.from({ length: 3 }).map((_, idx) => { + return ( + + ); + })} + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx b/Client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx new file mode 100644 index 000000000..aa7ab97ae --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Components/StatusBoxes/index.jsx @@ -0,0 +1,109 @@ +// Components +import { Stack, Typography } from "@mui/material"; +import StatusBoxes from "../../../../../Components/StatusBoxes"; +import StatBox from "../../../../../Components/StatBox"; + +//Utils +import useUtils from "../../../../../Pages/Uptime/Monitors/Hooks/useUtils"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; + +const InfraStatBoxes = ({ shouldRender, monitor }) => { + // Utils + const { formatBytes } = useHardwareUtils(); + const { statusStyles, determineState } = useUtils(); + + const { stats, uptimePercentage } = monitor ?? {}; + const latestCheck = stats?.aggregateData?.latestCheck; + + // Get data from latest check + const physicalCores = latestCheck?.cpu?.physical_core ?? 0; + const logicalCores = latestCheck?.cpu?.logical_core ?? 0; + const cpuFrequency = latestCheck?.cpu?.frequency ?? 0; + const cpuTemperature = + latestCheck?.cpu?.temperature?.length > 0 + ? latestCheck.cpu.temperature.reduce((acc, curr) => acc + curr, 0) / + latestCheck.cpu.temperature.length + : 0; + const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0; + const diskTotalBytes = latestCheck?.disk[0]?.total_bytes ?? 0; + const os = latestCheck?.host?.os ?? undefined; + const platform = latestCheck?.host?.platform ?? undefined; + const osPlatform = + typeof os === "undefined" && typeof platform === "undefined" + ? undefined + : `${os} ${platform}`; + + return ( + + + + {physicalCores} + cores + + } + /> + + {logicalCores} + cores + + } + /> + + {(cpuFrequency / 1000).toFixed(2)} + Ghz + + } + /> + + {cpuTemperature.toFixed(2)} + C + + } + /> + + + + {(uptimePercentage * 100).toFixed(2)} + % + + } + /> + + + ); +}; + +export default InfraStatBoxes; diff --git a/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx b/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx new file mode 100644 index 000000000..49972debb --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareMonitorsFetch.jsx @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import { networkService } from "../../../../main"; + +const useHardwareMonitorsFetch = ({ monitorId, dateRange }) => { + const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [monitor, setMonitor] = useState(undefined); + + const { authToken } = useSelector((state) => state.auth); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await networkService.getHardwareDetailsByMonitorId({ + authToken: authToken, + monitorId: monitorId, + dateRange: dateRange, + }); + response.data.data; + setMonitor(response.data.data); + } catch (error) { + setNetworkError(true); + } finally { + setIsLoading(false); + } + }; + fetchData(); + }, [monitorId, dateRange, authToken]); + + return { + isLoading, + networkError, + monitor, + }; +}; + +export { useHardwareMonitorsFetch }; diff --git a/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx new file mode 100644 index 000000000..bc540dfe3 --- /dev/null +++ b/Client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx @@ -0,0 +1,137 @@ +import { Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; + +// Constants +const BASE_BOX_PADDING_VERTICAL = 4; +const BASE_BOX_PADDING_HORIZONTAL = 8; +const TYPOGRAPHY_PADDING = 8; +const CHART_CONTAINER_HEIGHT = 300; + +const useHardwareUtils = () => { + const theme = useTheme(); + + const getDimensions = () => { + const totalTypographyPadding = parseInt(theme.spacing(TYPOGRAPHY_PADDING), 10) * 2; + const totalChartContainerPadding = + parseInt(theme.spacing(BASE_BOX_PADDING_VERTICAL), 10) * 2; + return { + baseBoxPaddingVertical: BASE_BOX_PADDING_VERTICAL, + baseBoxPaddingHorizontal: BASE_BOX_PADDING_HORIZONTAL, + totalContainerPadding: parseInt(theme.spacing(BASE_BOX_PADDING_VERTICAL), 10) * 2, + areaChartHeight: + CHART_CONTAINER_HEIGHT - totalChartContainerPadding - totalTypographyPadding, + }; + }; + + const formatBytes = (bytes, space = false) => { + if (bytes === undefined || bytes === null) + return ( + <> + {0} + {space ? " " : ""} + GB + + ); + if (typeof bytes !== "number") + return ( + <> + {0} + {space ? " " : ""} + GB + + ); + if (bytes === 0) + return ( + <> + {0} + {space ? " " : ""} + GB + + ); + + const GB = bytes / (1024 * 1024 * 1024); + const MB = bytes / (1024 * 1024); + + if (GB >= 1) { + return ( + <> + {Number(GB.toFixed(0))} + {space ? " " : ""} + GB + + ); + } else { + return ( + <> + {Number(MB.toFixed(0))} + MB + + ); + } + }; + + /** + * Converts a decimal value to a percentage + * + * @function decimalToPercentage + * @param {number} value - Decimal value to convert + * @returns {number} Percentage representation + * + * @example + * decimalToPercentage(0.75) // Returns 75 + * decimalToPercentage(null) // Returns 0 + */ + const decimalToPercentage = (value) => { + if (value === null || value === undefined) return 0; + return value * 100; + }; + + const buildTemps = (checks) => { + let numCores = 1; + if (checks === null) return { temps: [], tempKeys: [] }; + + for (const check of checks) { + if (check?.avgTemperature?.length > numCores) { + numCores = check.avgTemperature.length; + break; + } + } + const temps = checks.map((check) => { + // If there's no data, set the temperature to 0 + if ( + check?.avgTemperature?.length === 0 || + check?.avgTemperature === undefined || + check?.avgTemperature === null + ) { + check.avgTemperature = Array(numCores).fill(0); + } + const res = check?.avgTemperature?.reduce( + (acc, cur, idx) => { + acc[`core${idx + 1}`] = cur; + return acc; + }, + { + _id: check._id, + } + ); + return res; + }); + if (temps.length === 0 || !temps[0]) { + return { temps: [], tempKeys: [] }; + } + + return { + tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "_id"), + temps, + }; + }; + + return { + formatBytes, + decimalToPercentage, + buildTemps, + getDimensions, + }; +}; + +export { useHardwareUtils }; diff --git a/Client/src/Pages/Infrastructure/Details/empty.jsx b/Client/src/Pages/Infrastructure/Details/empty.jsx deleted file mode 100644 index ee3cdb4df..000000000 --- a/Client/src/Pages/Infrastructure/Details/empty.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useTheme } from "@emotion/react"; -import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react"; -import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react"; -import { Box, Typography, Stack } from "@mui/material"; -import PropTypes from "prop-types"; -import { useSelector } from "react-redux"; -const Empty = ({ styles }) => { - const theme = useTheme(); - const mode = useSelector((state) => state.ui.mode); - return ( - - - {mode === "light" ? : } - - Your infrastructure dashboard will show here - - Hang tight! Data is loading - - - - ); -}; - -Empty.propTypes = { - styles: PropTypes.object, - mode: PropTypes.string, -}; - -export default Empty; diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 9d6b09330..8e566c664 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -1,681 +1,100 @@ -import { useParams } from "react-router-dom"; -import { useEffect, useState } from "react"; +// Components +import { Stack, Typography } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; -import { Button, ButtonGroup, Stack, Box, Typography } from "@mui/material"; +import MonitorStatusHeader from "../../../Components/MonitorStatusHeader"; +import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; +import StatusBoxes from "./Components/StatusBoxes"; +import GaugeBoxes from "./Components/GaugeBoxes"; +import AreaChartBoxes from "./Components/AreaChartBoxes"; +import GenericFallback from "../../../Components/GenericFallback"; + +// Utils import { useTheme } from "@emotion/react"; -import CustomGauge from "../../../Components/Charts/CustomGauge"; -import AreaChart from "../../../Components/Charts/AreaChart"; -import { useSelector } from "react-redux"; -import { networkService } from "../../../main"; -import PulseDot from "../../../Components/Animated/PulseDot"; -import useUtils from "../../Uptime/Monitors/Hooks/useUtils"; -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 { - InfrastructureTooltip, - TemperatureTooltip, -} from "../../../Components/Charts/Utils/chartUtils"; -import PropTypes from "prop-types"; -import StatBox from "../../../Components/StatBox"; +import { useIsAdmin } from "../../../Hooks/useIsAdmin"; +import { useHardwareMonitorsFetch } from "./Hooks/useHardwareMonitorsFetch"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; -const BASE_BOX_PADDING_VERTICAL = 4; -const BASE_BOX_PADDING_HORIZONTAL = 8; -const TYPOGRAPHY_PADDING = 8; -/** - * Converts bytes to gigabytes - * @param {number} bytes - Number of bytes to convert - * @returns {number} Converted value in gigabytes - */ -const formatBytes = (bytes, space = false) => { - if (bytes === undefined || bytes === null) - return ( - <> - {0} - {space ? " " : ""} - GB - - ); - if (typeof bytes !== "number") - return ( - <> - {0} - {space ? " " : ""} - GB - - ); - if (bytes === 0) - return ( - <> - {0} - {space ? " " : ""} - GB - - ); +// Constants +const BREADCRUMBS = [ + { name: "infrastructure monitors", path: "/infrastructure" }, + { name: "details", path: "" }, +]; +const InfrastructureDetails = () => { + // Redux state - const GB = bytes / (1024 * 1024 * 1024); - const MB = bytes / (1024 * 1024); + // Local state + const [dateRange, setDateRange] = useState("day"); - if (GB >= 1) { + // Utils + const theme = useTheme(); + const isAdmin = useIsAdmin(); + const { monitorId } = useParams(); + + const { isLoading, networkError, monitor } = useHardwareMonitorsFetch({ + monitorId, + dateRange, + }); + + if (networkError === true) { return ( - <> - {Number(GB.toFixed(0))} - {space ? " " : ""} - GB - - ); - } else { - return ( - <> - {Number(MB.toFixed(0))} - MB - + + + Network error + + Please check your connection + ); } -}; -/** - * Converts a decimal value to a percentage - * - * @function decimalToPercentage - * @param {number} value - Decimal value to convert - * @returns {number} Percentage representation - * - * @example - * decimalToPercentage(0.75) // Returns 75 - * decimalToPercentage(null) // Returns 0 - */ -const decimalToPercentage = (value) => { - if (value === null || value === undefined) return 0; - return value * 100; -}; - -/** - * Renders a base box with consistent styling - * @param {Object} props - Component properties - * @param {React.ReactNode} props.children - Child components to render inside the box - * @param {Object} props.sx - Additional styling for the box - * @returns {React.ReactElement} Styled box component - */ -const BaseBox = ({ children, sx = {} }) => { - const theme = useTheme(); - return ( - - {children} - - ); -}; - -BaseBox.propTypes = { - children: PropTypes.node.isRequired, - sx: PropTypes.object, -}; - -/** - * Renders a gauge box with usage visualization - * @param {Object} props - Component properties - * @param {number} props.value - Percentage value for gauge - * @param {string} props.heading - Box heading - * @param {string} props.metricOne - First metric label - * @param {string} props.valueOne - First metric value - * @param {string} props.metricTwo - Second metric label - * @param {string} props.valueTwo - Second metric value - * @returns {React.ReactElement} Gauge box component - */ -const GaugeBox = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => { - const theme = useTheme(); + // if (!isLoading && monitor?.stats?.checks?.length === 0) { + // return ( + // + // + // + // + // No data found for this monitor + // + // + // ); + // } return ( - - - - {heading} - - - {metricOne} - {valueOne} - - - {metricTwo} - {valueTwo} - - - - - ); -}; - -GaugeBox.propTypes = { - value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - heading: PropTypes.string.isRequired, - metricOne: PropTypes.string.isRequired, - valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]) - .isRequired, - metricTwo: PropTypes.string.isRequired, - valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.element]) - .isRequired, -}; - -/** - * Renders the infrastructure details page - * @returns {React.ReactElement} Infrastructure details page component - */ -const InfrastructureDetails = () => { - const navigate = useNavigate(); - const theme = useTheme(); - const { monitorId } = useParams(); - const navList = [ - { name: "infrastructure monitors", path: "/infrastructure" }, - { name: "details", path: `/infrastructure/${monitorId}` }, - ]; - const [monitor, setMonitor] = useState(null); - const { authToken } = useSelector((state) => state.auth); - const [dateRange, setDateRange] = useState("day"); - const { statusColor, statusStyles, determineState } = useUtils(); - // These calculations are needed because ResponsiveContainer - // doesn't take padding of parent/siblings into account - // when calculating height. - const chartContainerHeight = 300; - const totalChartContainerPadding = - parseInt(theme.spacing(BASE_BOX_PADDING_VERTICAL), 10) * 2; - const totalTypographyPadding = parseInt(theme.spacing(TYPOGRAPHY_PADDING), 10) * 2; - const areaChartHeight = - (chartContainerHeight - totalChartContainerPadding - totalTypographyPadding) * 0.95; - // end height calculations - - const buildStatBoxes = (stats, uptime) => { - if (Object.keys(stats).length === 0) return []; - let latestCheck = stats?.aggregateData?.latestCheck ?? null; - if (latestCheck === null) return []; - - // Extract values from latest check - const physicalCores = latestCheck?.cpu?.physical_core ?? 0; - const logicalCores = latestCheck?.cpu?.logical_core ?? 0; - const cpuFrequency = latestCheck?.cpu?.frequency ?? 0; - const cpuTemperature = - latestCheck?.cpu?.temperature?.length > 0 - ? latestCheck.cpu.temperature.reduce((acc, curr) => acc + curr, 0) / - latestCheck.cpu.temperature.length - : 0; - const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0; - const diskTotalBytes = latestCheck?.disk[0]?.total_bytes ?? 0; - const os = latestCheck?.host?.os ?? null; - const platform = latestCheck?.host?.platform ?? null; - const osPlatform = os === null && platform === null ? null : `${os} ${platform}`; - return [ - { - id: 7, - sx: statusStyles[determineState(monitor)], - heading: "Status", - subHeading: monitor?.status === true ? "Active" : "Inactive", - }, - { - id: 0, - heading: "CPU (Physical)", - subHeading: ( - <> - {physicalCores} - cores - - ), - }, - { - id: 1, - heading: "CPU (Logical)", - subHeading: ( - <> - {logicalCores} - cores - - ), - }, - { - id: 2, - heading: "CPU Frequency", - subHeading: ( - <> - {(cpuFrequency / 1000).toFixed(2)} - Ghz - - ), - }, - { - id: 3, - heading: "Average CPU Temperature", - subHeading: ( - <> - {cpuTemperature.toFixed(2)} - C - - ), - }, - { - id: 4, - heading: "Memory", - subHeading: formatBytes(memoryTotalBytes), - }, - { - id: 5, - heading: "Disk", - subHeading: formatBytes(diskTotalBytes), - }, - { - id: 6, - heading: "Uptime", - subHeading: ( - <> - {(uptime * 100).toFixed(2)} - % - - ), - }, - - { - id: 8, - heading: "OS", - subHeading: osPlatform, - }, - ]; - }; - - const buildGaugeBoxConfigs = (stats) => { - if (Object.keys(stats).length === 0) return []; - - let latestCheck = stats?.aggregateData?.latestCheck ?? null; - if (latestCheck === null) return []; - - // Extract values from latest check - const memoryUsagePercent = latestCheck?.memory?.usage_percent ?? 0; - const memoryUsedBytes = latestCheck?.memory?.used_bytes ?? 0; - const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0; - const cpuUsagePercent = latestCheck?.cpu?.usage_percent ?? 0; - const cpuPhysicalCores = latestCheck?.cpu?.physical_core ?? 0; - const cpuFrequency = latestCheck?.cpu?.frequency ?? 0; - return [ - { - type: "memory", - value: decimalToPercentage(memoryUsagePercent), - heading: "Memory usage", - metricOne: "Used", - valueOne: formatBytes(memoryUsedBytes, true), - metricTwo: "Total", - valueTwo: formatBytes(memoryTotalBytes, true), - }, - { - type: "cpu", - value: decimalToPercentage(cpuUsagePercent), - heading: "CPU usage", - metricOne: "Cores", - valueOne: cpuPhysicalCores ?? 0, - metricTwo: "Frequency", - valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`, - }, - ...(latestCheck?.disk ?? []).map((disk, idx) => ({ - type: "disk", - diskIndex: idx, - value: decimalToPercentage(disk.usage_percent), - heading: `Disk${idx} usage`, - metricOne: "Used", - valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true), - metricTwo: "Total", - valueTwo: formatBytes(disk.total_bytes, true), - })), - ]; - }; - - const buildTemps = (checks) => { - let numCores = 1; - if (checks === null) return { temps: [], tempKeys: [] }; - - for (const check of checks) { - if (check?.avgTemperature?.length > numCores) { - numCores = check.avgTemperature.length; - break; - } - } - const temps = checks.map((check) => { - // If there's no data, set the temperature to 0 - if ( - check?.avgTemperature?.length === 0 || - check?.avgTemperature === undefined || - check?.avgTemperature === null - ) { - check.avgTemperature = Array(numCores).fill(0); - } - const res = check?.avgTemperature?.reduce( - (acc, cur, idx) => { - acc[`core${idx + 1}`] = cur; - return acc; - }, - { - _id: check._id, - } - ); - return res; - }); - if (temps.length === 0 || !temps[0]) { - return { temps: [], tempKeys: [] }; - } - - return { - tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "_id"), - temps, - }; - }; - - const buildAreaChartConfigs = (checks) => { - let latestCheck = checks[0] ?? null; - if (latestCheck === null) return []; - const { temps, tempKeys } = buildTemps(checks); - return [ - { - type: "memory", - data: checks, - dataKeys: ["avgMemoryUsage"], - heading: "Memory usage", - strokeColor: theme.palette.accent.main, // CAIO_REVIEW - gradientStartColor: theme.palette.accent.main, // CAIO_REVIEW - yLabel: "Memory usage", - yDomain: [0, 1], - yTick: , - xTick: , - toolTip: ( - - ), - }, - { - type: "cpu", - data: checks, - dataKeys: ["avgCpuUsage"], - heading: "CPU usage", - strokeColor: theme.palette.success.main, - gradientStartColor: theme.palette.success.main, - yLabel: "CPU usage", - yDomain: [0, 1], - yTick: , - xTick: , - toolTip: ( - - ), - }, - { - type: "temperature", - data: temps, - dataKeys: tempKeys, - strokeColor: theme.palette.error.main, - gradientStartColor: theme.palette.error.main, - heading: "CPU Temperature", - yLabel: "Temperature", - xTick: , - yDomain: [ - 0, - Math.max( - Math.max(...temps.flatMap((t) => tempKeys.map((k) => t[k]))) * 1.1, - 200 - ), - ], - toolTip: ( - - ), - }, - ...(latestCheck?.disks?.map((disk, idx) => ({ - type: "disk", - data: checks, - diskIndex: idx, - dataKeys: [`disks[${idx}].usagePercent`], - heading: `Disk${idx} usage`, - strokeColor: theme.palette.warning.main, - gradientStartColor: theme.palette.warning.main, - yLabel: "Disk Usage", - yDomain: [0, 1], - yTick: , - xTick: , - toolTip: ( - - ), - })) || []), - ]; - }; - - // Fetch data - useEffect(() => { - const fetchData = async () => { - try { - const response = await networkService.getHardwareDetailsByMonitorId({ - authToken: authToken, - monitorId: monitorId, - dateRange: dateRange, - }); - response.data.data; - setMonitor(response.data.data); - } catch (error) { - navigate("/not-found", { replace: true }); - logger.error(error); - } - }; - fetchData(); - }, [authToken, monitorId, dateRange, navigate]); - - const statBoxConfigs = buildStatBoxes( - monitor?.stats ?? {}, - monitor?.uptimePercentage ?? "Unknown" - ); - const gaugeBoxConfigs = buildGaugeBoxConfigs(monitor?.stats ?? {}); - const areaChartConfigs = buildAreaChartConfigs(monitor?.stats?.checks ?? []); - const lastChecked = - Date.now() - new Date(monitor?.stats?.aggregateData?.latestCheck?.createdAt); - return ( - - - {monitor?.stats.checks?.length > 0 ? ( - - - - - - - {monitor.name} - - {monitor.url || "..."} - - - Checking every {formatDurationRounded(monitor?.interval)} - - - Last checked {formatDurationSplit(lastChecked).time}{" "} - {formatDurationSplit(lastChecked).format} ago - - - - - {statBoxConfigs.map((statBox) => ( - - ))} - - - - {gaugeBoxConfigs.map((config) => { - return ( - - ); - })} - - - - Showing statistics for past{" "} - {dateRange === "day" - ? "24 hours" - : dateRange === "week" - ? "7 days" - : "30 days"} - . - - - - - - - - *": { - flexBasis: `calc(50% - ${theme.spacing(8)})`, - maxWidth: `calc(50% - ${theme.spacing(8)})`, - }, - }} - > - {areaChartConfigs.map((config) => { - return ( - - - {config.heading} - - - - ); - })} - - - ) : ( - - )} - + + + + + + + + ); }; diff --git a/Client/src/Pages/Infrastructure/Monitors/index.jsx b/Client/src/Pages/Infrastructure/Monitors/index.jsx index 6418dfd12..00eb48330 100644 --- a/Client/src/Pages/Infrastructure/Monitors/index.jsx +++ b/Client/src/Pages/Infrastructure/Monitors/index.jsx @@ -75,7 +75,6 @@ const InfrastructureMonitors = () => { ); } - console.log(isLoading); return ( diff --git a/Client/src/Pages/Infrastructure/skeleton.jsx b/Client/src/Pages/Infrastructure/skeleton.jsx deleted file mode 100644 index b6d29dc38..000000000 --- a/Client/src/Pages/Infrastructure/skeleton.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; From 49c58ce2ae123548bf6d54a546c57dbe75f4470b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 13:44:36 -0800 Subject: [PATCH 099/106] fix pagespeed loading states, add network error fallback --- .../Details/Hooks/useMonitorFetch.jsx | 8 ++++---- Client/src/Pages/PageSpeed/Details/index.jsx | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx b/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx index 340d4cf5e..1d15f4a42 100644 --- a/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx +++ b/Client/src/Pages/PageSpeed/Details/Hooks/useMonitorFetch.jsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { networkService } from "../../../../main"; -import { logger } from "../../../../Utils/Logger"; +import { createToast } from "../../../../Utils/toastUtils"; import { useNavigate } from "react-router-dom"; const useMonitorFetch = ({ authToken, monitorId }) => { const navigate = useNavigate(); @@ -8,10 +8,10 @@ const useMonitorFetch = ({ authToken, monitorId }) => { const [monitor, setMonitor] = useState(undefined); const [audits, setAudits] = useState(undefined); const [isLoading, setIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); useEffect(() => { const fetchMonitor = async () => { try { - setIsLoading(true); const res = await networkService.getStatsByMonitorId({ authToken: authToken, monitorId: monitorId, @@ -24,8 +24,8 @@ const useMonitorFetch = ({ authToken, monitorId }) => { setMonitor(res?.data?.data ?? undefined); setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined); } catch (error) { - logger.error(logger); - navigate("/not-found", { replace: true }); + setNetworkError(true); + createToast({ body: error.message }); } finally { setIsLoading(false); } diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index c52c1c817..b2e593482 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -27,7 +27,7 @@ const PageSpeedDetails = () => { const { monitorId } = useParams(); const { authToken } = useSelector((state) => state.auth); - const { monitor, audits, isLoading } = useMonitorFetch({ + const { monitor, audits, isLoading, networkError } = useMonitorFetch({ authToken, monitorId, }); @@ -39,10 +39,26 @@ const PageSpeedDetails = () => { seo: true, }); + // Handlers const handleMetrics = (id) => { setMetrics((prev) => ({ ...prev, [id]: !prev[id] })); }; + if (networkError === true) { + return ( + + + Network error + + Please check your connection + + ); + } + // Empty view, displayed when loading is complete and there are no checks if (!isLoading && monitor?.checks?.length === 0) { return ( From f218b6059157d1669ccd29a3e36114dc5a99da6f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 31 Jan 2025 13:45:07 -0800 Subject: [PATCH 100/106] fix uptime chart legends, set initial loading state to true to avoid extra render --- .../Details/Components/ChartBoxes/index.jsx | 41 ++++++++++--------- .../Uptime/Details/Hooks/useMonitorFetch.jsx | 3 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx index aa40442a9..77fffd497 100644 --- a/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx +++ b/Client/src/Pages/Uptime/Details/Components/ChartBoxes/index.jsx @@ -41,6 +41,7 @@ const ChartBoxes = ({ header="Uptime" > @@ -93,26 +94,28 @@ const ChartBoxes = ({ icon={} header="Incidents" > - - - {hoveredIncidentsData !== null - ? hoveredIncidentsData.totalChecks - : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { - return count + checkGroup.totalChecks; - }, 0) ?? 0)} - - {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( - - {formatDateWithTz(hoveredIncidentsData._id, dateFormat, uiTimezone)} + + + + {hoveredIncidentsData !== null + ? hoveredIncidentsData.totalChecks + : (monitor?.groupedDownChecks?.reduce((count, checkGroup) => { + return count + checkGroup.totalChecks; + }, 0) ?? 0)} - )} - + {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( + + {formatDateWithTz(hoveredIncidentsData._id, dateFormat, uiTimezone)} + + )} + + { - const [monitorIsLoading, setMonitorsIsLoading] = useState(false); + const [monitorIsLoading, setMonitorsIsLoading] = useState(true); const [monitor, setMonitor] = useState(undefined); const navigate = useNavigate(); useEffect(() => { const fetchMonitors = async () => { try { - setMonitorsIsLoading(true); const res = await networkService.getUptimeDetailsById({ authToken: authToken, monitorId: monitorId, From 9b727e6dc077f2e0e8ba4692028cd51c3483fa0e Mon Sep 17 00:00:00 2001 From: "Gorkem Cetin (BWL)" <167266851+gorkem-bwl@users.noreply.github.com> Date: Fri, 31 Jan 2025 22:49:53 -0500 Subject: [PATCH 101/106] Create addReview.mjml --- Server/templates/addReview.mjml | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Server/templates/addReview.mjml diff --git a/Server/templates/addReview.mjml b/Server/templates/addReview.mjml new file mode 100644 index 000000000..22adbe8b0 --- /dev/null +++ b/Server/templates/addReview.mjml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + Message from Checkmate Service + + + + + + +

Hello {{name}}!

+

+We hope you’re finding Checkmate helpful in monitoring your infrastructure. Your support means a lot to us, and we truly appreciate having you as part of our community. +

+

+If you’re happy with Checkmate, we’d love to hear about your experience! Leaving a review on G2 helps others discover Checkmate and supports our ongoing improvements. +

+G2 Link: TBD +

+Thank you for taking the time to share your thoughts - we greatly appreciate it! +

+Checkmate Team +
+
+ + + +

This email was sent by Checkmate.

+
+
+
+
+
From 067667da1eea4c63505964cc4c32aa06e88bae01 Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Sat, 1 Feb 2025 22:14:27 +0800 Subject: [PATCH 102/106] Fix validation error of container id --- Client/src/Pages/Uptime/Create/index.jsx | 16 +++++++---- Client/src/Validation/validation.js | 34 ++++++++++++++---------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/Client/src/Pages/Uptime/Create/index.jsx b/Client/src/Pages/Uptime/Create/index.jsx index 9086c87af..a0fcce43b 100644 --- a/Client/src/Pages/Uptime/Create/index.jsx +++ b/Client/src/Pages/Uptime/Create/index.jsx @@ -88,7 +88,7 @@ const CreateMonitor = () => { ? `http${https ? "s" : ""}://` + monitor.url : monitor.url, port: monitor.type === "port" ? monitor.port : undefined, - name: monitor.name === "" ? monitor.url : monitor.name, + name: monitor.name || monitor.url.substring(0, 50), type: monitor.type, interval: monitor.interval * MS_PER_MINUTE, }; @@ -122,7 +122,7 @@ const CreateMonitor = () => { form = { ...form, - description: form.name, + description: monitor.name || monitor.url, teamId: user.teamId, userId: user._id, notifications: monitor.notifications, @@ -138,17 +138,23 @@ const CreateMonitor = () => { const handleChange = (event, formName) => { const { value } = event.target; - setMonitor({ + + const newMonitor = { ...monitor, [formName]: value, - }); + }; + if (formName === 'type') { + newMonitor.url = ''; + } + setMonitor(newMonitor); const { error } = monitorValidation.validate( - { [formName]: value }, + { type: monitor.type, [formName]: value }, { abortEarly: false } ); setErrors((prev) => ({ ...prev, + url: undefined, ...(error ? { [formName]: error.details[0].message } : { [formName]: undefined }), })); }; diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index e40a9c174..ec9245cdb 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -91,13 +91,17 @@ const credentials = joi.object({ }); const monitorValidation = joi.object({ - url: joi - .string() - .trim() - .custom((value, helpers) => { - // Regex from https://gist.github.com/dperini/729294 - var urlRegex = new RegExp( - "^" + + url: joi.when('type', { + is: 'docker', + then: joi.string() + .trim().regex(/^[a-z0-9]{64}$/), + otherwise: joi + .string() + .trim() + .custom((value, helpers) => { + // Regex from https://gist.github.com/dperini/729294 + var urlRegex = new RegExp( + "^" + // protocol identifier (optional) // short syntax // still required "(?:(?:https?|ftp):\\/\\/)?" + @@ -130,18 +134,20 @@ const monitorValidation = joi.object({ // resource path (optional) "(?:[/?#]\\S*)?" + "$", - "i" - ); - if (!urlRegex.test(value)) { - return helpers.error("string.invalidUrl"); - } + "i" + ); + if (!urlRegex.test(value)) { + return helpers.error("string.invalidUrl"); + } - return value; - }) + return value; + }) + }) .messages({ "string.empty": "This field is required.", "string.uri": "The URL you provided is not valid.", "string.invalidUrl": "Please enter a valid URL with optional port", + "string.pattern.base": "Please enter a valid container ID.", }), port: joi.number(), name: joi.string().trim().max(50).allow("").messages({ From 58d22dfa9a51918c165294e795df35a515f799cb Mon Sep 17 00:00:00 2001 From: YinDongfang <1136005348@qq.com> Date: Sat, 1 Feb 2025 23:01:53 +0800 Subject: [PATCH 103/106] Fix input style error while edit uptime monitor --- Client/src/Pages/Uptime/Configure/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Configure/index.jsx b/Client/src/Pages/Uptime/Configure/index.jsx index bdb90891b..4f281a763 100644 --- a/Client/src/Pages/Uptime/Configure/index.jsx +++ b/Client/src/Pages/Uptime/Configure/index.jsx @@ -347,7 +347,7 @@ const Configure = () => { } + startAdornment={monitor?.type === "http" && } id="monitor-url" label="URL to monitor" placeholder="google.com" From 9d2a18b6751b4bccbccd81f7ac7d46a3c5e83f29 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 1 Feb 2025 07:58:51 -0800 Subject: [PATCH 104/106] update empty view message for Uptime and Pagespeed --- Client/src/Pages/PageSpeed/Details/index.jsx | 2 +- Client/src/Pages/Uptime/Details/index.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index b2e593482..b40a4aca0 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -70,7 +70,7 @@ const PageSpeedDetails = () => { monitor={monitor} /> - There is no history for this monitor yet. + There is no check history for this monitor yet.
); diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 5839a9943..f1e4e4c66 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -90,7 +90,7 @@ const UptimeDetails = () => { monitor={monitor} /> - There is no history for this monitor yet. + There is no check history for this monitor yet.
); From cdce6b95ccb5c19c1bb857edc9d788ae65f6f665 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 1 Feb 2025 07:59:40 -0800 Subject: [PATCH 105/106] update empty view message for Infra monitors --- .../Pages/Infrastructure/Details/index.jsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Client/src/Pages/Infrastructure/Details/index.jsx b/Client/src/Pages/Infrastructure/Details/index.jsx index 8e566c664..f686fa9ff 100644 --- a/Client/src/Pages/Infrastructure/Details/index.jsx +++ b/Client/src/Pages/Infrastructure/Details/index.jsx @@ -51,22 +51,22 @@ const InfrastructureDetails = () => { ); } - // if (!isLoading && monitor?.stats?.checks?.length === 0) { - // return ( - // - // - // - // - // No data found for this monitor - // - // - // ); - // } + if (!isLoading && monitor?.stats?.checks?.length === 0) { + return ( + + + + + No check history for htis monitor yet. + + + ); + } return ( From 513246e2b4c7935a6d0473d512b2b3945e172cdb Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sat, 1 Feb 2025 08:13:21 -0800 Subject: [PATCH 106/106] add a network error view for Uptime details --- .../Details/Hooks/useCertificateFetch.jsx | 8 +++---- .../Uptime/Details/Hooks/useChecksFetch.jsx | 15 ++++++------- .../Uptime/Details/Hooks/useMonitorFetch.jsx | 13 ++++++------ Client/src/Pages/Uptime/Details/index.jsx | 21 ++++++++++++++++--- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx index ce0b4f7af..6a820ff6f 100644 --- a/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx +++ b/Client/src/Pages/Uptime/Details/Hooks/useCertificateFetch.jsx @@ -11,7 +11,7 @@ const useCertificateFetch = ({ uiTimezone, }) => { const [certificateExpiry, setCertificateExpiry] = useState(undefined); - const [certificateIsLoading, setCertificateIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { const fetchCertificate = async () => { @@ -20,7 +20,7 @@ const useCertificateFetch = ({ } try { - setCertificateIsLoading(true); + setIsLoading(true); const res = await networkService.getCertificateExpiry({ authToken: authToken, monitorId: monitorId, @@ -35,12 +35,12 @@ const useCertificateFetch = ({ setCertificateExpiry("N/A"); logger.error(error); } finally { - setCertificateIsLoading(false); + setIsLoading(false); } }; fetchCertificate(); }, [authToken, monitorId, certificateDateFormat, uiTimezone, monitor]); - return { certificateExpiry, certificateIsLoading }; + return [certificateExpiry, isLoading]; }; export default useCertificateFetch; diff --git a/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx index 541c29f6e..8f57f63c0 100644 --- a/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx +++ b/Client/src/Pages/Uptime/Details/Hooks/useChecksFetch.jsx @@ -1,8 +1,7 @@ import { useState } from "react"; import { useEffect } from "react"; -import { logger } from "../../../../Utils/Logger"; import { networkService } from "../../../../main"; - +import { createToast } from "../../../../Utils/toastUtils"; export const useChecksFetch = ({ authToken, monitorId, @@ -12,12 +11,13 @@ export const useChecksFetch = ({ }) => { const [checks, setChecks] = useState(undefined); const [checksCount, setChecksCount] = useState(undefined); - const [checksAreLoading, setChecksAreLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [networkError, setNetworkError] = useState(false); useEffect(() => { const fetchChecks = async () => { try { - setChecksAreLoading(true); + setIsLoading(true); const res = await networkService.getChecksByMonitor({ authToken: authToken, monitorId: monitorId, @@ -31,15 +31,16 @@ export const useChecksFetch = ({ setChecks(res.data.data.checks); setChecksCount(res.data.data.checksCount); } catch (error) { - logger.error(error); + setNetworkError(true); + createToast({ body: error.message }); } finally { - setChecksAreLoading(false); + setIsLoading(false); } }; fetchChecks(); }, [authToken, monitorId, dateRange, page, rowsPerPage]); - return { checks, checksCount, checksAreLoading }; + return [checks, checksCount, isLoading, networkError]; }; export default useChecksFetch; diff --git a/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx b/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx index 4b45b56fb..06d833d13 100644 --- a/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx +++ b/Client/src/Pages/Uptime/Details/Hooks/useMonitorFetch.jsx @@ -1,10 +1,11 @@ import { useEffect, useState } from "react"; import { networkService } from "../../../../main"; -import { logger } from "../../../../Utils/Logger"; import { useNavigate } from "react-router-dom"; +import { createToast } from "../../../../Utils/toastUtils"; export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => { - const [monitorIsLoading, setMonitorsIsLoading] = useState(true); + const [networkError, setNetworkError] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [monitor, setMonitor] = useState(undefined); const navigate = useNavigate(); @@ -19,15 +20,15 @@ export const useMonitorFetch = ({ authToken, monitorId, dateRange }) => { }); setMonitor(res?.data?.data ?? {}); } catch (error) { - logger.error(error); - navigate("/not-found", { replace: true }); + setNetworkError(true); + createToast({ body: error.message }); } finally { - setMonitorsIsLoading(false); + setIsLoading(false); } }; fetchMonitors(); }, [authToken, dateRange, monitorId, navigate]); - return { monitor, monitorIsLoading }; + return [monitor, isLoading, networkError]; }; export default useMonitorFetch; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index f1e4e4c66..3063ba27a 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -47,13 +47,13 @@ const UptimeDetails = () => { const theme = useTheme(); const isAdmin = useIsAdmin(); - const { monitor, monitorIsLoading } = useMonitorFetch({ + const [monitor, monitorIsLoading, monitorNetworkError] = useMonitorFetch({ authToken, monitorId, dateRange, }); - const { certificateExpiry, certificateIsLoading } = useCertificateFetch({ + const [certificateExpiry, certificateIsLoading] = useCertificateFetch({ monitor, authToken, monitorId, @@ -61,7 +61,7 @@ const UptimeDetails = () => { uiTimezone, }); - const { checks, checksCount, checksAreLoading } = useChecksFetch({ + const [checks, checksCount, checksAreLoading, checksNetworkError] = useChecksFetch({ authToken, monitorId, dateRange, @@ -78,6 +78,21 @@ const UptimeDetails = () => { setRowsPerPage(event.target.value); }; + if (monitorNetworkError || checksNetworkError) { + return ( + + + Network error + + Please check your connection + + ); + } + // Empty view, displayed when loading is complete and there are no checks if (!monitorIsLoading && !checksAreLoading && checksCount === 0) { return (