diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index c45fa1b72..947f776a4 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -12,12 +12,12 @@ import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useMemo, useState } from "react"; import { useSelector } from "react-redux"; -import { formatDateWithTz } from "../../../Utils/timeUtils"; +import { formatDateWithTz, toTimeStamp } from "../../../Utils/timeUtils"; import "./index.css"; const CustomToolTip = ({ active, payload, label }) => { const uiTimezone = useSelector((state) => state.ui.timezone); - + const dateFormat = label?.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; const theme = useTheme(); if (active && payload && payload.length) { return ( @@ -39,7 +39,11 @@ const CustomToolTip = ({ active, payload, label }) => { fontWeight: 500, }} > - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + {formatDateWithTz( + toTimeStamp(label, dateFormat), + "ddd, MMMM D, YYYY, h:mm A", + uiTimezone + )} { Response Time {" "} - {payload[0].payload.originalResponseTime} + {Math.floor(payload[0].payload.originalAvgResponseTime)} { return null; }; +CustomToolTip.propTypes = { + active: PropTypes.bool, + payload: PropTypes.arrayOf( + PropTypes.shape({ + payload: PropTypes.shape({ + _id: PropTypes.string.isRequired, // Add this line to validate _id + originalResponseTime: PropTypes.number.isRequired, + }).isRequired, + }) + ), + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), +}; + const CustomTick = ({ x, y, payload, index }) => { const theme = useTheme(); const uiTimezone = useSelector((state) => state.ui.timezone); - + const dateFormat = payload?.value.length === 10 ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; // Render nothing for the first tick if (index === 0) return null; return ( @@ -103,7 +120,7 @@ const CustomTick = ({ x, y, payload, index }) => { fontSize={11} fontWeight={400} > - {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} + {formatDateWithTz(toTimeStamp(payload?.value, dateFormat), "h:mm a", uiTimezone)} ); }; @@ -168,7 +185,7 @@ const MonitorDetailsAreaChart = ({ checks }) => { } minTickGap={0} axisLine={false} @@ -183,7 +200,7 @@ const MonitorDetailsAreaChart = ({ checks }) => { /> { + const uiTimezone = useSelector((state) => state.ui.timezone); + const formatString = type === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; + const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; + + return ( + <> + + {formatDateWithTz( + toTimeStamp(firstDataPoint._id, formatString), + dateFormat, + uiTimezone + )} + + + {formatDateWithTz( + toTimeStamp(lastDataPoint._id, formatString), + dateFormat, + uiTimezone + )} + + + ); +}; + +CustomLabels.propTypes = { + x: PropTypes.number.isRequired, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + firstDataPoint: PropTypes.object.isRequired, + lastDataPoint: PropTypes.object.isRequired, + type: PropTypes.string.isRequired, +}; + +export default CustomLabels; diff --git a/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx new file mode 100644 index 000000000..a851a93ea --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Charts/DownBarChart.jsx @@ -0,0 +1,89 @@ +import { memo, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; +import PropTypes from "prop-types"; +import CustomLabels from "./CustomLabels"; + +const DownBarChart = memo(({ stats, type, onBarHover }) => { + const theme = useTheme(); + + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + + return ( + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: 0 }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > + + } + /> + + {stats.downChecks.map((entry, index) => ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalChecks: 0 }); + }} + /> + ))} + + + + ); +}); + +DownBarChart.displayName = "DownBarChart"; +DownBarChart.propTypes = { + stats: PropTypes.shape({ + downChecks: PropTypes.arrayOf(PropTypes.object), + downChecksAggregate: PropTypes.object, + }), + type: PropTypes.string, + onBarHover: PropTypes.func, +}; +export default DownBarChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx b/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx new file mode 100644 index 000000000..29e0cc8bb --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Charts/ResponseGaugeChart.jsx @@ -0,0 +1,117 @@ +import PropTypes from "prop-types"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, RadialBarChart, RadialBar, Cell } from "recharts"; + +const ResponseGaugeChart = ({ avgResponseTime }) => { + const theme = useTheme(); + + let max = 1000; // max ms + + const data = [ + { response: max, fill: "transparent", background: false }, + { response: avgResponseTime, background: true }, + ]; + let responseTime = Math.floor(avgResponseTime); + let responseProps = + responseTime <= 200 + ? { + category: "Excellent", + main: theme.palette.success.main, + bg: theme.palette.success.contrastText, + } + : responseTime <= 500 + ? { + category: "Fair", + main: theme.palette.success.main, + bg: theme.palette.success.contrastText, + } + : responseTime <= 600 + ? { + category: "Acceptable", + main: theme.palette.warning.main, + bg: theme.palette.warning.dark, + } + : { + category: "Poor", + main: theme.palette.error.main, + bg: theme.palette.error.light, + }; + + return ( + + + + low + + + high + + + {responseProps.category} + + + {responseTime} ms + + + + + + + + ); +}; + +ResponseGaugeChart.propTypes = { + avgResponseTime: PropTypes.number.isRequired, +}; + +export default ResponseGaugeChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx new file mode 100644 index 000000000..7ee77864d --- /dev/null +++ b/Client/src/Pages/Uptime/Details/Charts/UpBarChart.jsx @@ -0,0 +1,100 @@ +import { memo, useState } from "react"; +import { useTheme } from "@mui/material"; +import { ResponsiveContainer, BarChart, XAxis, Bar, Cell } from "recharts"; +import PropTypes from "prop-types"; +import CustomLabels from "./CustomLabels"; + +const UpBarChart = memo(({ stats, type, onBarHover }) => { + const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + + const getColorRange = (responseTime) => { + return responseTime < 200 + ? { main: theme.palette.success.main, light: theme.palette.success.light } + : responseTime < 300 + ? { main: theme.palette.warning.main, light: theme.palette.warning.light } + : { main: theme.palette.error.contrastText, light: theme.palette.error.light }; + }; + + return ( + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: 0, avgResponseTime: 0 }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > + + } + /> + + {stats.upChecks.map((entry, index) => { + let { main, light } = getColorRange(entry.avgResponseTime); + return ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ + time: null, + totalChecks: 0, + groupUptimePercentage: 0, + }); + }} + /> + ); + })} + + + + ); +}); + +// Add display name for the component +UpBarChart.displayName = "UpBarChart"; + +// Validate props using PropTypes +UpBarChart.propTypes = { + stats: PropTypes.shape({ + upChecks: PropTypes.array, + upChecksAggregate: PropTypes.object, + }), + type: PropTypes.string, + onBarHover: PropTypes.func, +}; +export default UpBarChart; diff --git a/Client/src/Pages/Uptime/Details/Charts/index.jsx b/Client/src/Pages/Uptime/Details/Charts/index.jsx deleted file mode 100644 index 8f135196a..000000000 --- a/Client/src/Pages/Uptime/Details/Charts/index.jsx +++ /dev/null @@ -1,350 +0,0 @@ -import { useTheme } from "@emotion/react"; -import PropTypes from "prop-types"; -import { - BarChart, - Bar, - XAxis, - ResponsiveContainer, - Cell, - RadialBarChart, - RadialBar, -} from "recharts"; -import { memo, useMemo, useState } from "react"; -import { useSelector } from "react-redux"; -import { formatDateWithTz, toTimeStamp } from "../../../../Utils/timeUtils"; - -const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint, type }) => { - const uiTimezone = useSelector((state) => state.ui.timezone); - const formatString = type === "month" ? "YYYY-MM-DD" : "YYYY-MM-DD-HH"; - const dateFormat = type === "day" ? "MMM D, h:mm A" : "MMM D"; - - return ( - <> - - {formatDateWithTz( - toTimeStamp(firstDataPoint._id, formatString), - dateFormat, - uiTimezone - )} - - - {formatDateWithTz( - toTimeStamp(lastDataPoint._id, formatString), - dateFormat, - uiTimezone - )} - - - ); -}; - -CustomLabels.propTypes = { - x: PropTypes.number.isRequired, - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - firstDataPoint: PropTypes.object.isRequired, - lastDataPoint: PropTypes.object.isRequired, - type: PropTypes.string.isRequired, -}; - -const UpBarChart = memo(({ data, type, onBarHover }) => { - const theme = useTheme(); - - const [chartHovered, setChartHovered] = useState(false); - const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - - const getColorRange = (uptime) => { - return uptime > 0.8 - ? { main: theme.palette.success.main, light: theme.palette.success.light } - : uptime > 0.5 - ? { main: theme.palette.warning.main, light: theme.palette.warning.light } - : { main: theme.palette.error.contrastText, light: theme.palette.error.light }; - }; - - // TODO - REMOVE THIS LATER - - return ( - - { - setChartHovered(true); - onBarHover({ time: null, totalChecks: 0, groupUptimePercentage: 0 }); - }} - onMouseLeave={() => { - setChartHovered(false); - setHoveredBarIndex(null); - onBarHover(null); - }} - > - - } - /> - - {data.map((entry, index) => { - let { main, light } = getColorRange(entry.groupUptimePercentage); - return ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ - time: null, - totalChecks: 0, - groupUptimePercentage: 0, - }); - }} - /> - ); - })} - - - - ); -}); - -// Add display name for the component -UpBarChart.displayName = "UpBarChart"; - -// Validate props using PropTypes -UpBarChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.object), - type: PropTypes.string, - onBarHover: PropTypes.func, -}; -export { UpBarChart }; - -const DownBarChart = memo(({ data, type, onBarHover }) => { - const theme = useTheme(); - - const [chartHovered, setChartHovered] = useState(false); - const [hoveredBarIndex, setHoveredBarIndex] = useState(null); - - return ( - - { - setChartHovered(true); - onBarHover({ time: null, totalChecks: 0 }); - }} - onMouseLeave={() => { - setChartHovered(false); - setHoveredBarIndex(null); - onBarHover(null); - }} - > - - } - /> - - {data.map((entry, index) => ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ time: null, totalChecks: 0 }); - }} - /> - ))} - - - - ); -}); - -DownBarChart.displayName = "DownBarChart"; -DownBarChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.object), - type: PropTypes.string, - onBarHover: PropTypes.func, -}; -export { DownBarChart }; - -const ResponseGaugeChart = ({ avgResponseTime }) => { - const theme = useTheme(); - - let max = 1000; // max ms - - const data = [ - { response: max, fill: "transparent", background: false }, - { response: avgResponseTime, background: true }, - ]; - let responseTime = Math.floor(avgResponseTime); - let responseProps = - responseTime <= 200 - ? { - category: "Excellent", - main: theme.palette.success.main, - bg: theme.palette.success.contrastText, - } - : responseTime <= 500 - ? { - category: "Fair", - main: theme.palette.success.main, - bg: theme.palette.success.contrastText, - } - : responseTime <= 600 - ? { - category: "Acceptable", - main: theme.palette.warning.main, - bg: theme.palette.warning.dark, - } - : { - category: "Poor", - main: theme.palette.error.main, - bg: theme.palette.error.light, - }; - - return ( - - - - low - - - high - - - {responseProps.category} - - - {responseTime} ms - - - - - - - - ); -}; - -ResponseGaugeChart.propTypes = { - data: PropTypes.arrayOf(PropTypes.object).isRequired, -}; - -export { ResponseGaugeChart }; diff --git a/Client/src/Pages/Uptime/Details/index.jsx b/Client/src/Pages/Uptime/Details/index.jsx index 93c1b99d7..45a99095e 100644 --- a/Client/src/Pages/Uptime/Details/index.jsx +++ b/Client/src/Pages/Uptime/Details/index.jsx @@ -17,7 +17,6 @@ import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import { ChartBox } from "./styled"; -import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts"; import SkeletonLayout from "./skeleton"; import "./index.css"; import useUtils from "../utils"; @@ -26,7 +25,9 @@ import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import IconBox from "../../../Components/IconBox"; import StatBox from "../../../Components/StatBox"; import { toTimeStamp } from "../../../Utils/timeUtils"; - +import UpBarChart from "./Charts/UpBarChart"; +import DownBarChart from "./Charts/DownBarChart"; +import ResponseGaugeChart from "./Charts/ResponseGaugeChart"; /** * Details page component displaying monitor details and related information. * @component @@ -54,7 +55,6 @@ const DetailsPage = () => { dateRange: dateRange, normalize: true, }); - console.log(res?.data?.data); setMonitor(res?.data?.data ?? {}); } catch (error) { logger.error(error); @@ -105,10 +105,6 @@ const DetailsPage = () => { const [hoveredUptimeData, setHoveredUptimeData] = useState(null); const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); - useEffect(() => { - console.log(hoveredUptimeData); - }, [hoveredUptimeData]); - const BREADCRUMBS = [ { name: "uptime", path: "/uptime" }, { name: "details", path: `/uptime/${monitorId}` }, @@ -226,17 +222,17 @@ const DetailsPage = () => { - {monitor?.aggregateData?.latestResponseTime} + {monitor?.stats?.latestResponseTime} {"ms"} } @@ -313,7 +309,7 @@ const DetailsPage = () => { {hoveredUptimeData !== null ? hoveredUptimeData.totalChecks - : (monitor.groupUpChecks[0]?.upChecksCount ?? 0)} + : (monitor.stats?.totalChecks ?? 0)} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( { )} - Uptime Percentage + + {hoveredUptimeData !== null + ? "Avg Response Time" + : "Uptime Percentage"} + {hoveredUptimeData !== null - ? Math.floor(hoveredUptimeData.groupUptimePercentage * 100) + ? Math.floor(hoveredUptimeData?.avgResponseTime ?? 0) : Math.floor( - (monitor?.groupUpChecks[0]?.overallUptimePercentage ?? 0) * + ((monitor?.stats?.upChecksAggregate.totalChecks ?? 0) / + (monitor?.stats?.totalChecks ?? 1)) * 100 )} - % + + {hoveredUptimeData !== null ? " ms" : " %"} + @@ -365,7 +368,7 @@ const DetailsPage = () => { {hoveredIncidentsData !== null ? hoveredIncidentsData.totalChecks - : (monitor.groupDownChecks[0]?.downChecksCount ?? 0)} + : (monitor.stats?.downChecksAggregate.totalChecks ?? 0)} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( @@ -388,7 +391,7 @@ const DetailsPage = () => { )} @@ -401,7 +404,7 @@ const DetailsPage = () => { Average Response Time @@ -414,7 +417,7 @@ const DetailsPage = () => { Response Times - {/* */} + { History - {/* */} + />