From 94084acd865a32ea93109f1c03689475c87a53e6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 27 Jan 2025 10:00:58 -0800 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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 | 570 ++++-------------- 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, 422 insertions(+), 1245 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 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"; + +// 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 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(); +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); + + // Local state const [dateRange, setDateRange] = useState("day"); - const [certificateExpiry, setCertificateExpiry] = useState("N/A"); - const navigate = useNavigate(); + const [hoveredUptimeData, setHoveredUptimeData] = useState(null); + const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(5); - const certificateDateFormat = "MMM D, YYYY h A"; + // Utils const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D"; - const uiTimezone = useSelector((state) => state.ui.timezone); + const { monitorId } = useParams(); + const theme = useTheme(); + const isAdmin = useIsAdmin(); - 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]); + const { monitor, monitorIsLoading } = useMonitorFetch({ + authToken, + monitorId, + dateRange, + }); - useEffect(() => { - fetchMonitor(); - }, [fetchMonitor]); + const { certificateExpiry, certificateIsLoading } = useCertificateFetch({ + monitor, + authToken, + monitorId, + certificateDateFormat, + uiTimezone, + }); - 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 { checks, checksCount, checksAreLoading } = useChecksFetch({ + authToken, + monitorId, + dateRange, + page, + rowsPerPage, + }); - const splitDuration = (duration) => { - const { time, format } = formatDurationSplit(duration); - return ( - <> - {time} - {format} - - ); + // Handlers + const handlePageChange = (_, newPage) => { + setPage(newPage); }; - let loading = Object.keys(monitor).length === 0; - - const [hoveredUptimeData, setHoveredUptimeData] = useState(null); - const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); + 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 10/10] 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;