From f046f250ec924521058f10aaa6e1e7ffc3367aae Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 17:10:42 -0400 Subject: [PATCH 01/12] Revamped header section --- Client/src/Layouts/HomeLayout/index.css | 2 - Client/src/Layouts/HomeLayout/index.jsx | 10 +- Client/src/Pages/Incidents/index.jsx | 2 +- Client/src/Pages/Monitors/Configure/index.jsx | 1 + Client/src/Pages/Monitors/Details/index.css | 7 - Client/src/Pages/Monitors/Details/index.jsx | 210 ++++++++++++++---- Client/src/Pages/Monitors/Home/index.jsx | 16 -- Client/src/Utils/Theme/darkTheme.js | 15 +- Client/src/Utils/Theme/lightTheme.js | 9 + Client/src/assets/icons/certificate.svg | 3 + Client/src/index.css | 4 + 11 files changed, 205 insertions(+), 74 deletions(-) create mode 100644 Client/src/assets/icons/certificate.svg diff --git a/Client/src/Layouts/HomeLayout/index.css b/Client/src/Layouts/HomeLayout/index.css index 1434ff9d9..855bf33fc 100644 --- a/Client/src/Layouts/HomeLayout/index.css +++ b/Client/src/Layouts/HomeLayout/index.css @@ -3,9 +3,7 @@ } .home-layout { - display: flex; position: relative; - gap: var(--env-var-spacing-2); min-height: 100vh; max-width: 1400px; margin: 0 auto; diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx index 92d763f0c..fd92c59fa 100644 --- a/Client/src/Layouts/HomeLayout/index.jsx +++ b/Client/src/Layouts/HomeLayout/index.jsx @@ -1,6 +1,6 @@ import Sidebar from "../../Components/Sidebar"; import { Outlet } from "react-router"; -import { Box } from "@mui/material"; +import { Box, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import "./index.css"; @@ -10,10 +10,14 @@ const HomeLayout = () => { return ( - + - + ); }; diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index 5d756298e..3152cf506 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -83,7 +83,7 @@ const Incidents = () => { }; return ( - + {loading ? ( ) : ( diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 483a2fd79..2e719a31a 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -254,6 +254,7 @@ const Configure = () => { color="secondary" loading={isLoading} sx={{ + border: "none", backgroundColor: theme.palette.background.main, px: theme.spacing(5), mr: theme.spacing(6), diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 59ffc5d85..108f09b21 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -1,13 +1,6 @@ -.monitor-details h1.MuiTypography-root { - font-size: var(--env-var-font-size-large-plus); - font-weight: 600; -} .monitor-details h2.MuiTypography-root { font-size: var(--env-var-font-size-large); } -.monitor-details h2.MuiTypography-root { - font-weight: 600; -} .monitor-details button.MuiButtonBase-root { height: var(--env-var-height-2); line-height: 1; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 616f18657..922231036 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -3,8 +3,10 @@ import { useEffect, useState, useCallback } from "react"; import { Box, Button, + Popover, Skeleton, Stack, + Tooltip, Typography, useTheme, } from "@mui/material"; @@ -13,12 +15,14 @@ import { useNavigate, useParams } from "react-router-dom"; import { networkService } from "../../../main"; import { logger } from "../../../Utils/Logger"; import { + formatDate, formatDuration, formatDurationRounded, } from "../../../Utils/timeUtils"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import ButtonGroup from "@mui/material/ButtonGroup"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; +import CertificateIcon from "../../../assets/icons/certificate.svg?react"; import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; @@ -154,6 +158,14 @@ const DetailsPage = ({ isAdmin }) => { const [certificateExpiry, setCertificateExpiry] = useState("N/A"); const navigate = useNavigate(); + const [anchorEl, setAnchorEl] = useState(null); + const openCertificate = (event) => { + setAnchorEl(event.currentTarget); + }; + const closeCertificate = () => { + setAnchorEl(null); + }; + const fetchMonitor = useCallback(async () => { try { const res = await networkService.getStatsByMonitorId( @@ -186,7 +198,16 @@ const DetailsPage = ({ isAdmin }) => { authToken, monitorId ); - setCertificateExpiry(res?.data?.data?.certificateDate ?? "N/A"); + + let [month, day, year] = res?.data?.data?.certificateDate.split("/"); + const date = new Date(year, month - 1, day); + + setCertificateExpiry( + formatDate(date, { + hour: undefined, + minute: undefined, + }) ?? "N/A" + ); } catch (error) { console.error(error); } @@ -207,65 +228,171 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + - - {monitor.url?.replace(/^https?:\/\//, "") || "..."} + {monitor.name} - - - Your site is {monitor?.status ? "up" : "down"}. - {" "} - Checking every {formatDurationRounded(monitor?.interval)}. - + + + + + + {monitor.url?.replace(/^https?:\/\//, "") || "..."} + + + Checking every {formatDurationRounded(monitor?.interval)}. + + - {isAdmin && ( - - )} + + + + + Certificate Expiry + + + {certificateExpiry} + + + {isAdmin && ( + + )} + { value={`${formatDurationRounded(monitor?.lastChecked)} ago`} /> - { borderRadius={theme.shape.borderRadius} sx={{ height: "250px" }} > - + /> */} History - + {/* */} diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index 318a9da20..e5c9a765f 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -43,22 +43,6 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState.isLoading && monitorState.monitors.length === 0; - const now = new Date(); - const hour = now.getHours(); - - let greeting = ""; - let emoji = ""; - if (hour < 12) { - greeting = "morning"; - emoji = "πŸŒ…"; - } else if (hour < 18) { - greeting = "afternoon"; - emoji = "🌞"; - } else { - greeting = "evening"; - emoji = "πŸŒ™"; - } - return ( {loading ? ( diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 15df7c1b8..657f071e4 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -10,10 +10,10 @@ const text = { const background = { main: "#151518", alt: "#09090b", - fill: "#2e2e2e", + fill: "#2D2D33", accent: "#18181a", }; -const border = { light: "#27272a", dark: "#2c2c2c" }; +const border = { light: "#27272a", dark: "#36363e" }; const fontFamilyDefault = '"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif'; @@ -25,7 +25,7 @@ const darkTheme = createTheme({ palette: { mode: "dark", primary: { main: "#1570ef" }, - secondary: { main: "#2e2e2e" }, + secondary: { main: "#2D2D33" }, text: text, background: background, border: border, @@ -92,6 +92,15 @@ const darkTheme = createTheme({ backgroundColor: theme.palette.secondary.main, }, }, + { + props: (props) => + props.variant === "contained" && props.color === "secondary", + style: { + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.dark, + }, + }, ], fontWeight: 400, borderRadius: 4, diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index bb83d09b2..ef38a2abc 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -89,6 +89,15 @@ const lightTheme = createTheme({ backgroundColor: theme.palette.secondary.main, }, }, + { + props: (props) => + props.variant === "contained" && props.color === "secondary", + style: { + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + }, + }, ], fontWeight: 400, borderRadius: 4, diff --git a/Client/src/assets/icons/certificate.svg b/Client/src/assets/icons/certificate.svg new file mode 100644 index 000000000..a985bef9f --- /dev/null +++ b/Client/src/assets/icons/certificate.svg @@ -0,0 +1,3 @@ + + + diff --git a/Client/src/index.css b/Client/src/index.css index 105776bd8..740a22b7a 100644 --- a/Client/src/index.css +++ b/Client/src/index.css @@ -4,6 +4,10 @@ box-sizing: border-box; } +html { + scroll-behavior: smooth; +} + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; font-weight: 400; From e5b347e14b18f7a0baad93347da6e9d52b8fdc99 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 18:46:51 -0400 Subject: [PATCH 02/12] Revamped chart cards 1/2 --- Client/src/Pages/Monitors/Details/index.css | 7 - Client/src/Pages/Monitors/Details/index.jsx | 122 +++++++++++++++--- Client/src/Pages/Monitors/Details/styled.jsx | 61 +++++++++ .../assets/icons/average-response-icon.svg | 3 + .../src/assets/icons/response-time-icon.svg | 3 + Client/src/assets/icons/uptime-icon.svg | 3 + 6 files changed, 171 insertions(+), 28 deletions(-) create mode 100644 Client/src/Pages/Monitors/Details/styled.jsx create mode 100644 Client/src/assets/icons/average-response-icon.svg create mode 100644 Client/src/assets/icons/response-time-icon.svg create mode 100644 Client/src/assets/icons/uptime-icon.svg diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 108f09b21..528ec1b92 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -1,10 +1,3 @@ -.monitor-details h2.MuiTypography-root { - font-size: var(--env-var-font-size-large); -} -.monitor-details button.MuiButtonBase-root { - height: var(--env-var-height-2); - line-height: 1; -} .monitor-details p.MuiTypography-root, .monitor-details p.MuiTypography-root span.MuiTypography-root { font-size: var(--env-var-font-size-small-plus); diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 922231036..20979d863 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -23,10 +23,15 @@ import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAr import ButtonGroup from "@mui/material/ButtonGroup"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; import CertificateIcon from "../../../assets/icons/certificate.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 PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import "./index.css"; +import { ChartBox, IconBox } from "./styled"; const StatBox = ({ title, value }) => { const theme = useTheme(); @@ -228,7 +233,7 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + { alignSelf: "flex-end", }} > - - + { )} + + + + + + + + + Showing statistics for past{" "} + {dateRange === "day" + ? "24 hours" + : dateRange === "week" + ? "7 days" + : "30 days"} + . + + + + + + + + + Uptime + + + + Total Checks + 87 + + + Uptime Percentage + + 98.3% + + + + + + + + + + Incidents + + + Total Incidents + 0 + + + + + + + + + Average response time + + + + + + + + + Response Times + + + + ({ + flex: "1 30%", + height: 300, + minWidth: 250, + padding: theme.spacing(8), + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + borderRadius: 4, + backgroundColor: theme.palette.background.main, + "& h2": { + color: theme.palette.text.secondary, + fontSize: 15, + }, + "& p": { + color: theme.palette.text.tertiary, + fontSize: 13, + }, + "& span": { + color: theme.palette.text.primary, + fontSize: 20, + "& span": { + opacity: 0.8, + marginLeft: 2, + fontSize: 15, + }, + }, + "& .MuiStack-root": { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing(6), + }, + "& > .MuiBox-root:not(:first-of-type)": { + marginTop: theme.spacing(8), + }, +})); + +export const IconBox = styled(Box)(({ theme }) => ({ + height: 34, + minWidth: 34, + width: 34, + position: "relative", + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.dark, + borderRadius: 4, + backgroundColor: theme.palette.background.accent, + "& svg": { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 20, + height: 20, + "& path": { + stroke: theme.palette.text.tertiary, + }, + }, +})); diff --git a/Client/src/assets/icons/average-response-icon.svg b/Client/src/assets/icons/average-response-icon.svg new file mode 100644 index 000000000..572e2002f --- /dev/null +++ b/Client/src/assets/icons/average-response-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Client/src/assets/icons/response-time-icon.svg b/Client/src/assets/icons/response-time-icon.svg new file mode 100644 index 000000000..db5b2af38 --- /dev/null +++ b/Client/src/assets/icons/response-time-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Client/src/assets/icons/uptime-icon.svg b/Client/src/assets/icons/uptime-icon.svg new file mode 100644 index 000000000..55706ae71 --- /dev/null +++ b/Client/src/assets/icons/uptime-icon.svg @@ -0,0 +1,3 @@ + + + From 0aa2c795fb38d76af03074bed78e3de979b94ced Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 19:31:20 -0400 Subject: [PATCH 03/12] Revamped chart cards 2/2 --- Client/src/Pages/Monitors/Details/index.css | 5 +- Client/src/Pages/Monitors/Details/index.jsx | 242 ++++++------------- Client/src/Pages/Monitors/Details/styled.jsx | 28 +++ Client/src/assets/icons/history-icon.svg | 3 + 4 files changed, 103 insertions(+), 175 deletions(-) create mode 100644 Client/src/assets/icons/history-icon.svg diff --git a/Client/src/Pages/Monitors/Details/index.css b/Client/src/Pages/Monitors/Details/index.css index 528ec1b92..8b1378917 100644 --- a/Client/src/Pages/Monitors/Details/index.css +++ b/Client/src/Pages/Monitors/Details/index.css @@ -1,4 +1 @@ -.monitor-details p.MuiTypography-root, -.monitor-details p.MuiTypography-root span.MuiTypography-root { - font-size: var(--env-var-font-size-small-plus); -} + diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 20979d863..4c8eced23 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -27,58 +27,12 @@ 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"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import "./index.css"; -import { ChartBox, IconBox } from "./styled"; - -const StatBox = ({ title, value }) => { - const theme = useTheme(); - return ( - - - {title} - - - {value} - - - ); -}; - -StatBox.propTypes = { - title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; +import { AlertBox, ChartBox, IconBox } from "./styled"; /** * Renders a skeleton layout. @@ -233,7 +187,7 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + { { slotProps={{ paper: { sx: { - py: 4, - px: 6, + mt: theme.spacing(4), + py: theme.spacing(2), + px: theme.spacing(4), width: 140, backgroundColor: theme.palette.background.accent, }, @@ -376,14 +332,46 @@ const DetailsPage = ({ isAdmin }) => { )} + + + up for + + {formatDurationRounded(monitor?.uptimeDuration)} + + + + last check + + {formatDurationRounded(monitor?.lastChecked)} + ago + + + + last response time + + {monitor?.latestResponseTime} + ms + + + - + + Showing statistics for past{" "} + {dateRange === "day" + ? "24 hours" + : dateRange === "week" + ? "7 days" + : "30 days"} + . + + - - Showing statistics for past{" "} - {dateRange === "day" - ? "24 hours" - : dateRange === "week" - ? "7 days" - : "30 days"} - . - @@ -466,117 +445,38 @@ const DetailsPage = ({ isAdmin }) => { Response Times + + {/* */} + + + + + + + + + History + + + - - - - - - - Avg. Response Time{" "} - (24-hr) - - } - value={parseFloat(monitor?.avgResponseTime24hours) - .toFixed(2) - .replace(/\.?0+$/, "")} - /> - - Uptime (24-hr) - - } - value={`${parseFloat(monitor?.uptime24Hours) - .toFixed(2) - .replace(/\.?0+$/, "")}%`} - /> - - Uptime (30-day) - - } - value={`${parseFloat(monitor?.uptime30Days) - .toFixed(2) - .replace(/\.?0+$/, "")}%`} - /> - - - - - Response Times - - - - - - - - - {/* */} - - - - - History - - {/* */} - )} diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index a5bec8f39..479c82d0e 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -13,6 +13,7 @@ export const ChartBox = styled(Box)(({ theme }) => ({ "& h2": { color: theme.palette.text.secondary, fontSize: 15, + fontWeight: 500, }, "& p": { color: theme.palette.text.tertiary, @@ -59,3 +60,30 @@ export const IconBox = styled(Box)(({ theme }) => ({ }, }, })); + +export const AlertBox = styled(Box)(({ theme }) => ({ + padding: `${theme.spacing(4)} ${theme.spacing(8)}`, + minWidth: 200, + width: 225, + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + borderRadius: 4, + backgroundColor: theme.palette.background.main, + "& h2": { + fontSize: 13, + fontWeight: 500, + color: theme.palette.text.secondary, + textTransform: "uppercase", + }, + "& p": { + fontSize: 18, + color: theme.palette.text.primary, + marginTop: theme.spacing(2), + "& span": { + opacity: 0.8, + marginLeft: theme.spacing(3), + fontSize: 15, + }, + }, +})); diff --git a/Client/src/assets/icons/history-icon.svg b/Client/src/assets/icons/history-icon.svg new file mode 100644 index 000000000..7343455ec --- /dev/null +++ b/Client/src/assets/icons/history-icon.svg @@ -0,0 +1,3 @@ + + + From 9ae8c93cb0f8abc3feb3580f55c4117e9eb84d2d Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 20:05:02 -0400 Subject: [PATCH 04/12] Added format function that returns a split duration --- Client/src/Pages/Monitors/Details/index.jsx | 36 ++++++++++++++------ Client/src/Pages/Monitors/Details/styled.jsx | 8 ++--- Client/src/Utils/timeUtils.js | 19 ++++++++++- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 4c8eced23..c6413e9fb 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -18,6 +18,7 @@ import { formatDate, formatDuration, formatDurationRounded, + formatDurationSplit, } from "../../../Utils/timeUtils"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import ButtonGroup from "@mui/material/ButtonGroup"; @@ -31,8 +32,8 @@ import HistoryIcon from "../../../assets/icons/history-icon.svg?react"; import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; +import { StatBox, ChartBox, IconBox } from "./styled"; import "./index.css"; -import { AlertBox, ChartBox, IconBox } from "./styled"; /** * Renders a skeleton layout. @@ -174,6 +175,16 @@ const DetailsPage = ({ isAdmin }) => { fetchCertificate(); }, [authToken, monitorId, monitor]); + const splitDuration = (duration) => { + const { time, format } = formatDurationSplit(duration); + return ( + <> + {time} + {format} + + ); + }; + let loading = Object.keys(monitor).length === 0; return ( @@ -187,7 +198,7 @@ const DetailsPage = ({ isAdmin }) => { { name: "details", path: `/monitors/${monitorId}` }, ]} /> - + { anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={closeCertificate} + disableScrollLock + marginThreshold={null} + anchorReference={anchorEl} anchorOrigin={{ vertical: "bottom", horizontal: "center", @@ -332,27 +346,27 @@ const DetailsPage = ({ isAdmin }) => { )} - - + + up for - {formatDurationRounded(monitor?.uptimeDuration)} + {splitDuration(monitor?.uptimeDuration)} - - + + last check - {formatDurationRounded(monitor?.lastChecked)} + {splitDuration(monitor?.lastChecked)} ago - - + + last response time {monitor?.latestResponseTime} ms - + ({ }, })); -export const AlertBox = styled(Box)(({ theme }) => ({ +export const StatBox = styled(Box)(({ theme }) => ({ padding: `${theme.spacing(4)} ${theme.spacing(8)}`, minWidth: 200, - width: 225, + flex: 1, border: 1, borderStyle: "solid", borderColor: theme.palette.border.light, @@ -81,8 +81,8 @@ export const AlertBox = styled(Box)(({ theme }) => ({ color: theme.palette.text.primary, marginTop: theme.spacing(2), "& span": { - opacity: 0.8, - marginLeft: theme.spacing(3), + color: theme.palette.text.tertiary, + marginLeft: theme.spacing(2), fontSize: 15, }, }, diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 2d1a2f180..fa4a27d95 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -43,6 +43,23 @@ export const formatDurationRounded = (ms) => { return time; }; +export const formatDurationSplit = (ms) => { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + return days > 0 + ? { time: days, format: days === 1 ? "day" : "days" } + : hours > 0 + ? { time: hours, format: hours === 1 ? "hour" : "hours" } + : minutes > 0 + ? { time: minutes, format: minutes === 1 ? "minute" : "minutes" } + : seconds > 0 + ? { time: seconds, format: seconds === 1 ? "second" : "seconds" } + : { time: 0, format: "seconds" }; +}; + export const formatDate = (date, customOptions) => { const options = { year: "numeric", @@ -51,7 +68,7 @@ export const formatDate = (date, customOptions) => { hour: "numeric", minute: "numeric", hour12: true, - ...customOptions + ...customOptions, }; // Return the date using the specified options From f1fbbe2e3aa1079ff3d14fd3ec4277af8580880e Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Mon, 2 Sep 2024 20:43:27 -0400 Subject: [PATCH 05/12] Updated color values --- Client/src/Pages/Monitors/Details/index.jsx | 22 ++++++++++++++++---- Client/src/Pages/Monitors/Details/styled.jsx | 2 +- Client/src/Utils/Theme/darkTheme.js | 12 +++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index c6413e9fb..8aef758a0 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -346,9 +346,23 @@ const DetailsPage = ({ isAdmin }) => { )} - - - up for + + + active for {splitDuration(monitor?.uptimeDuration)} @@ -448,7 +462,7 @@ const DetailsPage = ({ isAdmin }) => { - Average response time + Average Response Time diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 1862c1c4e..8e1ee9774 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -64,7 +64,7 @@ export const IconBox = styled(Box)(({ theme }) => ({ export const StatBox = styled(Box)(({ theme }) => ({ padding: `${theme.spacing(4)} ${theme.spacing(8)}`, minWidth: 200, - flex: 1, + width: 225, border: 1, borderStyle: "solid", borderColor: theme.palette.border.light, diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 657f071e4..eca445976 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -39,22 +39,22 @@ const darkTheme = createTheme({ success: { text: "#079455", main: "#45bb7a", - light: "#1e1e1e", - bg: "#27272a", + light: "#1c4428", + bg: "#12261e", }, error: { text: "#f04438", main: "#d32f2f", - light: "#1e1e1e", - bg: "#27272a", + light: "#542426", + bg: "#301a1f", dark: "#932020", border: "#f04438", }, warning: { text: "#e88c30", main: "#FF9F00", - light: "#27272a", - bg: "#1E1E1E", + light: "#272115", + bg: "#624711", border: "#e88c30", }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, From 0be386513fdc5cf285a169ed8ee88215df3960bc Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Tue, 3 Sep 2024 20:00:48 -0400 Subject: [PATCH 06/12] Implemented basic charts --- .../Pages/Monitors/Details/Charts/index.jsx | 92 +++++++++++++++++++ Client/src/Pages/Monitors/Details/index.jsx | 67 +++++++++++++- Client/src/Pages/Monitors/Details/styled.jsx | 4 + Client/src/Utils/NetworkService.js | 25 +++++ 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 Client/src/Pages/Monitors/Details/Charts/index.jsx diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx new file mode 100644 index 000000000..54d9716fa --- /dev/null +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -0,0 +1,92 @@ +import { useTheme } from "@emotion/react"; +import { + BarChart, + Bar, + XAxis, + CartesianGrid, + ResponsiveContainer, +} from "recharts"; +import { formatDate } from "../../../../Utils/timeUtils"; + +const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint }) => { + let options = { + month: "short", + year: undefined, + hour: undefined, + minute: undefined, + }; + + return ( + <> + + {formatDate(new Date(firstDataPoint.day), options)} + + + {formatDate(new Date(lastDataPoint.day), options)} + + + ); +}; + +export const UpBarChart = ({ data, dateRange }) => { + const theme = useTheme(); + + return ( + + + + } + /> + + + + ); +}; + +export const DownBarChart = ({ data }) => { + const theme = useTheme(); + + return ( + + + + } + /> + + + + ); +}; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 8aef758a0..6f717143e 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -33,6 +33,7 @@ import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import { StatBox, ChartBox, IconBox } from "./styled"; +import { DownBarChart, UpBarChart } from "./Charts"; import "./index.css"; /** @@ -186,6 +187,55 @@ const DetailsPage = ({ isAdmin }) => { }; let loading = Object.keys(monitor).length === 0; + + const [aggregateStats, setAggregateStats] = useState({}); + useEffect(() => { + const fetchAggregateStats = async () => { + try { + const res = await networkService.getAggregateStatsById( + authToken, + monitorId, + dateRange + ); + + setAggregateStats(res?.data?.data); + } catch (error) { + console.error(error); + } + }; + fetchAggregateStats(); + }, [monitorId, dateRange]); + + const calculateStats = () => { + let totalChecks = 0; + let totalIncidents = 0; + let totalResponseTime = 0; + let count = 0; + + Object.keys(aggregateStats).forEach((entry) => { + totalChecks += aggregateStats[entry].totalChecks; + totalIncidents += aggregateStats[entry].totalIncidents; + totalResponseTime += aggregateStats[entry].totalIncidents; + count++; + }); + + let uptime = + dateRange === "day" + ? monitor.uptime24Hours + : dateRange === "week" + ? monitor.uptime7Days + : dateRange === "month" && monitor.uptime30Days; + + return { + totalChecks: totalChecks, + totalIncidents: totalIncidents, + averageResponseTime: totalResponseTime / count, + uptime: uptime, + }; + }; + + const monitorStats = calculateStats(); + console.log(aggregateStats); return ( {loading ? ( @@ -431,18 +481,22 @@ const DetailsPage = ({ isAdmin }) => { Uptime - + Total Checks - 87 + + {monitorStats.totalChecks} + Uptime Percentage - 98.3% + {monitorStats.uptime} + % + @@ -451,10 +505,13 @@ const DetailsPage = ({ isAdmin }) => { Incidents - + Total Incidents - 0 + + {monitorStats.totalIncidents} + + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 8e1ee9774..1ed6ca8ee 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -36,6 +36,10 @@ export const ChartBox = styled(Box)(({ theme }) => ({ "& > .MuiBox-root:not(:first-of-type)": { marginTop: theme.spacing(8), }, + "& tspan, & text": { + fontSize: 11, + fill: theme.palette.text.tertiary, + }, })); export const IconBox = styled(Box)(({ theme }) => ({ diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 6dcde82bb..acb1134e4 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -146,6 +146,31 @@ class NetworkService { ); } + /** + * ************************************ + * Gets aggregate stats by monitor ID + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor whose certificate expiry is to be retrieved. + * @returns {Promise} The response from the axios GET request. + * + */ + async getAggregateStatsById(authToken, monitorId, dateRange) { + const params = new URLSearchParams(); + if (dateRange) params.append("dateRange", dateRange); + + return this.axiosInstance.get( + `/monitors/aggregate/${monitorId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + } + ); + } + /** * ************************************ * Updates a single monitor From a192b3d14c396439497dde7859a92e1227719760 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 12:57:08 -0400 Subject: [PATCH 07/12] Added hover state to bars --- Client/src/Pages/Monitors/Configure/index.jsx | 1 - .../Pages/Monitors/Details/Charts/index.jsx | 117 +++++++++++++++--- Client/src/Pages/Monitors/Details/index.jsx | 75 ++++++++--- Client/src/Pages/Monitors/Details/styled.jsx | 12 +- Client/src/Pages/Monitors/Home/StatusBox.jsx | 3 +- Client/src/Pages/Monitors/Home/index.jsx | 9 +- Client/src/Utils/Theme/darkTheme.js | 2 +- Client/src/Utils/greeting.jsx | 6 +- Client/src/Utils/timeUtils.js | 4 +- Client/src/assets/icons/top-right-arrow.svg | 2 +- 10 files changed, 181 insertions(+), 50 deletions(-) diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index 2e719a31a..637c71ddc 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -53,7 +53,6 @@ const Configure = () => { const [monitor, setMonitor] = useState({}); const [errors, setErrors] = useState({}); const { monitorId } = useParams(); - console.log(monitor); const idMap = { "monitor-url": "url", "monitor-name": "name", diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index 54d9716fa..44c7ba163 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -5,35 +5,61 @@ import { XAxis, CartesianGrid, ResponsiveContainer, + Cell, } from "recharts"; import { formatDate } from "../../../../Utils/timeUtils"; +import { useState } from "react"; -const CustomLabels = ({ x, width, height, firstDataPoint, lastDataPoint }) => { +const CustomLabels = ({ + x, + width, + height, + firstDataPoint, + lastDataPoint, + type, +}) => { let options = { month: "short", year: undefined, hour: undefined, minute: undefined, }; + if (type === "day") delete options.hour; return ( <> - - {formatDate(new Date(firstDataPoint.day), options)} + + {formatDate(new Date(firstDataPoint.time), options)} - - {formatDate(new Date(lastDataPoint.day), options)} + + {formatDate(new Date(lastDataPoint.time), options)} ); }; -export const UpBarChart = ({ data, dateRange }) => { +export const UpBarChart = ({ data, type, onBarHover }) => { const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + return ( - - + + { + setChartHovered(true); + onBarHover({ time: null, totalChecks: null }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > { height="100%" firstDataPoint={data[0]} lastDataPoint={data[data.length - 1]} - dateRange={dateRange} + type={type} /> } /> + background={{ fill: "transparent" }} + > + {data.map((entry, index) => ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalChecks: null }); + }} + /> + ))} + ); }; -export const DownBarChart = ({ data }) => { +export const DownBarChart = ({ data, type, onBarHover }) => { const theme = useTheme(); + const [chartHovered, setChartHovered] = useState(false); + const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + return ( - - + + { + setChartHovered(true); + onBarHover({ time: null, totalIncidents: null }); + }} + onMouseLeave={() => { + setChartHovered(false); + setHoveredBarIndex(null); + onBarHover(null); + }} + > { height="100%" firstDataPoint={data[0]} lastDataPoint={data[data.length - 1]} + type={type} /> } /> + background={{ fill: "transparent" }} + > + {data.map((entry, index) => ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ time: null, totalIncidents: null }); + }} + /> + ))} + ); diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 6f717143e..f3239fb42 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -210,13 +210,11 @@ const DetailsPage = ({ isAdmin }) => { let totalChecks = 0; let totalIncidents = 0; let totalResponseTime = 0; - let count = 0; Object.keys(aggregateStats).forEach((entry) => { totalChecks += aggregateStats[entry].totalChecks; totalIncidents += aggregateStats[entry].totalIncidents; - totalResponseTime += aggregateStats[entry].totalIncidents; - count++; + totalResponseTime += aggregateStats[entry].avgResponseTime; }); let uptime = @@ -229,13 +227,17 @@ const DetailsPage = ({ isAdmin }) => { return { totalChecks: totalChecks, totalIncidents: totalIncidents, - averageResponseTime: totalResponseTime / count, - uptime: uptime, + averageResponseTime: + Math.floor((totalResponseTime / aggregateStats.length) * 100) / 100, + uptime: Math.floor(uptime), }; }; const monitorStats = calculateStats(); - console.log(aggregateStats); + + const [hoveredUptimeData, setHoveredUptimeData] = useState(null); + const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); + return ( {loading ? ( @@ -344,7 +346,6 @@ const DetailsPage = ({ isAdmin }) => { onClose={closeCertificate} disableScrollLock marginThreshold={null} - anchorReference={anchorEl} anchorOrigin={{ vertical: "bottom", horizontal: "center", @@ -481,12 +482,31 @@ const DetailsPage = ({ isAdmin }) => { Uptime - - + + Total Checks - {monitorStats.totalChecks} + {hoveredUptimeData !== null + ? hoveredUptimeData.totalChecks || 0 + : monitorStats.totalChecks} + {hoveredUptimeData !== null && + hoveredUptimeData.time !== null && ( + + {formatDate(new Date(hoveredUptimeData.time), { + month: "short", + year: undefined, + minute: undefined, + hour: dateRange === "day" ? "numeric" : undefined, + })} + + )} Uptime Percentage @@ -496,7 +516,11 @@ const DetailsPage = ({ isAdmin }) => { - + @@ -505,13 +529,36 @@ const DetailsPage = ({ isAdmin }) => { Incidents - + Total Incidents - {monitorStats.totalIncidents} + {hoveredIncidentsData !== null + ? hoveredIncidentsData.totalIncidents || 0 + : monitorStats.totalIncidents} + {hoveredIncidentsData !== null && + hoveredIncidentsData.time !== null && ( + + {formatDate(new Date(hoveredIncidentsData.time), { + month: "short", + year: undefined, + minute: undefined, + hour: dateRange === "day" ? "numeric" : undefined, + })} + + )} - + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 1ed6ca8ee..3c178cb00 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -1,7 +1,8 @@ -import { Box, styled } from "@mui/material"; +import { Box, Stack, styled } from "@mui/material"; -export const ChartBox = styled(Box)(({ theme }) => ({ +export const ChartBox = styled(Stack)(({ theme }) => ({ flex: "1 30%", + gap: theme.spacing(8), height: 300, minWidth: 250, padding: theme.spacing(8), @@ -30,16 +31,15 @@ export const ChartBox = styled(Box)(({ theme }) => ({ }, "& .MuiStack-root": { flexDirection: "row", - alignItems: "center", gap: theme.spacing(6), }, - "& > .MuiBox-root:not(:first-of-type)": { - marginTop: theme.spacing(8), - }, "& tspan, & text": { fontSize: 11, fill: theme.palette.text.tertiary, }, + "& path": { + transition: "fill 300ms ease", + }, })); export const IconBox = styled(Box)(({ theme }) => ({ diff --git a/Client/src/Pages/Monitors/Home/StatusBox.jsx b/Client/src/Pages/Monitors/Home/StatusBox.jsx index 08b2e88d9..ece8cde16 100644 --- a/Client/src/Pages/Monitors/Home/StatusBox.jsx +++ b/Client/src/Pages/Monitors/Home/StatusBox.jsx @@ -48,8 +48,7 @@ const StatusBox = ({ title, value }) => { borderColor={theme.palette.border.light} borderRadius={theme.shape.borderRadius} backgroundColor={theme.palette.background.main} - px={theme.spacing(12)} - py={theme.spacing(8)} + p={theme.spacing(8)} overflow="hidden" sx={{ "&:hover": { diff --git a/Client/src/Pages/Monitors/Home/index.jsx b/Client/src/Pages/Monitors/Home/index.jsx index e5c9a765f..64c01b7ac 100644 --- a/Client/src/Pages/Monitors/Home/index.jsx +++ b/Client/src/Pages/Monitors/Home/index.jsx @@ -44,7 +44,7 @@ const Monitors = ({ isAdmin }) => { let loading = monitorState.isLoading && monitorState.monitors.length === 0; return ( - + {loading ? ( ) : ( @@ -79,7 +79,7 @@ const Monitors = ({ isAdmin }) => { {monitorState.monitors?.length !== 0 && ( <> @@ -89,8 +89,7 @@ const Monitors = ({ isAdmin }) => { { { lineHeight={1} fontWeight={500} color={theme.palette.text.primary} + mb={theme.spacing(1)} > { {append} β€” Here’s an overview of your {type} monitors. diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index fa4a27d95..f9f916d04 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -72,5 +72,7 @@ export const formatDate = (date, customOptions) => { }; // Return the date using the specified options - return date.toLocaleString("en-US", options); + return date + .toLocaleString("en-US", options) + .replace(/\b(AM|PM)\b/g, (match) => match.toLowerCase()); }; diff --git a/Client/src/assets/icons/top-right-arrow.svg b/Client/src/assets/icons/top-right-arrow.svg index 471b9454c..d569cdd7e 100644 --- a/Client/src/assets/icons/top-right-arrow.svg +++ b/Client/src/assets/icons/top-right-arrow.svg @@ -1,3 +1,3 @@ - + From 38fddf5b9b47aa687506406dffbb41096b73a26b Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 16:41:23 -0400 Subject: [PATCH 08/12] Added color range to uptime chart --- Client/src/Components/Animated/PulseDot.jsx | 4 +- .../Pages/Monitors/Details/Charts/index.jsx | 61 +++++++++------ Client/src/Pages/Monitors/Details/index.jsx | 76 +++++-------------- Client/src/Pages/Monitors/Details/styled.jsx | 3 + Client/src/Pages/PageSpeed/Details/index.jsx | 2 +- 5 files changed, 62 insertions(+), 84 deletions(-) diff --git a/Client/src/Components/Animated/PulseDot.jsx b/Client/src/Components/Animated/PulseDot.jsx index 38e956b50..9ac536fe6 100644 --- a/Client/src/Components/Animated/PulseDot.jsx +++ b/Client/src/Components/Animated/PulseDot.jsx @@ -41,8 +41,8 @@ const PulseDot = ({ color }) => { "&::after": { content: `""`, position: "absolute", - width: "6px", - height: "6px", + width: "7px", + height: "7px", borderRadius: "50%", backgroundColor: "white", top: "50%", diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index 44c7ba163..2927a6cc0 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -44,6 +44,14 @@ export const UpBarChart = ({ data, type, onBarHover }) => { const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + const getColorRange = (uptime) => { + return uptime > 80 + ? { main: theme.palette.success.main, light: theme.palette.success.light } + : uptime > 50 + ? { main: theme.palette.warning.main, light: theme.palette.warning.light } + : { main: theme.palette.error.text, light: theme.palette.error.light }; + }; + return ( { data={data} onMouseEnter={() => { setChartHovered(true); - onBarHover({ time: null, totalChecks: null }); + onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 }); }} onMouseLeave={() => { setChartHovered(false); @@ -78,29 +86,32 @@ export const UpBarChart = ({ data, type, onBarHover }) => { /> - {data.map((entry, index) => ( - { - setHoveredBarIndex(index); - onBarHover(entry); - }} - onMouseLeave={() => { - setHoveredBarIndex(null); - onBarHover({ time: null, totalChecks: null }); - }} - /> - ))} + {data.map((entry, index) => { + let { main, light } = getColorRange(entry.uptimePercentage); + return ( + { + setHoveredBarIndex(index); + onBarHover(entry); + }} + onMouseLeave={() => { + setHoveredBarIndex(null); + onBarHover({ + time: null, + totalChecks: 0, + uptimePercentage: 0, + }); + }} + /> + ); + })} @@ -121,7 +132,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { data={data} onMouseEnter={() => { setChartHovered(true); - onBarHover({ time: null, totalIncidents: null }); + onBarHover({ time: null, totalIncidents: 0 }); }} onMouseLeave={() => { setChartHovered(false); @@ -147,7 +158,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { /> {data.map((entry, index) => ( @@ -166,7 +177,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { }} onMouseLeave={() => { setHoveredBarIndex(null); - onBarHover({ time: null, totalIncidents: null }); + onBarHover({ time: null, totalIncidents: 0 }); }} /> ))} diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 4d9917586..46d4772d7 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -118,57 +118,9 @@ const DetailsPage = ({ isAdmin }) => { let loading = Object.keys(monitor).length === 0; - const [aggregateStats, setAggregateStats] = useState({}); - useEffect(() => { - const fetchAggregateStats = async () => { - try { - const res = await networkService.getAggregateStatsById( - authToken, - monitorId, - dateRange - ); - - setAggregateStats(res?.data?.data); - } catch (error) { - console.error(error); - } - }; - fetchAggregateStats(); - }, [monitorId, dateRange]); - - const calculateStats = () => { - let totalChecks = 0; - let totalIncidents = 0; - let totalResponseTime = 0; - - Object.keys(aggregateStats).forEach((entry) => { - totalChecks += aggregateStats[entry].totalChecks; - totalIncidents += aggregateStats[entry].totalIncidents; - totalResponseTime += aggregateStats[entry].avgResponseTime; - }); - - let uptime = - dateRange === "day" - ? monitor.uptime24Hours - : dateRange === "week" - ? monitor.uptime7Days - : dateRange === "month" && monitor.uptime30Days; - - return { - totalChecks: totalChecks, - totalIncidents: totalIncidents, - averageResponseTime: - Math.floor((totalResponseTime / aggregateStats.length) * 100) / 100, - uptime: Math.floor(uptime), - }; - }; - - const monitorStats = calculateStats(); - const [hoveredUptimeData, setHoveredUptimeData] = useState(null); const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); - const statusColor = { true: theme.palette.success.main, false: theme.palette.error.main, @@ -181,6 +133,8 @@ const DetailsPage = ({ isAdmin }) => { undefined: "Pending...", }; + console.log(monitor); + return ( {loading ? ( @@ -339,7 +293,13 @@ const DetailsPage = ({ isAdmin }) => { { Total Checks {hoveredUptimeData !== null - ? hoveredUptimeData.totalChecks || 0 - : monitorStats.totalChecks} + ? hoveredUptimeData.totalChecks + : monitor?.periodTotalChecks} {hoveredUptimeData !== null && hoveredUptimeData.time !== null && ( @@ -450,13 +410,17 @@ const DetailsPage = ({ isAdmin }) => { Uptime Percentage - {monitorStats.uptime} + {hoveredUptimeData !== null + ? Math.floor( + hoveredUptimeData.uptimePercentage * 10 + ) / 10 + : Math.floor(monitor?.periodUptime * 10) / 10} % @@ -472,8 +436,8 @@ const DetailsPage = ({ isAdmin }) => { Total Incidents {hoveredIncidentsData !== null - ? hoveredIncidentsData.totalIncidents || 0 - : monitorStats.totalIncidents} + ? hoveredIncidentsData.totalIncidents + : monitor?.periodIncidents} {hoveredIncidentsData !== null && hoveredIncidentsData.time !== null && ( @@ -494,7 +458,7 @@ const DetailsPage = ({ isAdmin }) => { )} diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 3c178cb00..6c3f8eb04 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -33,6 +33,9 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ flexDirection: "row", gap: theme.spacing(6), }, + "& .MuiStack-root:first-of-type": { + alignItems: "center", + }, "& tspan, & text": { fontSize: 11, fill: theme.palette.text.tertiary, diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 29c0b72e0..4004e1715 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -213,7 +213,7 @@ const PageSpeedDetails = () => { monitorId, "desc", 50, - null, + "day", null, null ); From 2a1afc8610d8ecf7391ff98c413b30b8e7537983 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 19:17:12 -0400 Subject: [PATCH 09/12] Implemented basic gauge chart --- .../Charts/MonitorDetails60MinChart/index.jsx | 53 ----------- .../Pages/Monitors/Details/Charts/index.jsx | 90 ++++++++++++++++++- Client/src/Pages/Monitors/Details/index.jsx | 9 +- Client/src/Pages/Monitors/Details/styled.jsx | 1 - Server/db/mongo/modules/monitorModule.js | 19 +++- 5 files changed, 109 insertions(+), 63 deletions(-) delete mode 100644 Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx diff --git a/Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx b/Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx deleted file mode 100644 index 57cb344e7..000000000 --- a/Client/src/Components/Charts/MonitorDetails60MinChart/index.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import { BarChart, Bar, Cell, ReferenceLine, Label } from "recharts"; - -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; - -const MonitorDetails60MinChart = ({ data }) => { - const theme = useTheme(); - - const labelStyle = { - fontSize: "10px", - fill: theme.palette.text.tertiary, - }; - - const color = { - true: theme.palette.success.main, - false: theme.palette.error.text, - undefined: theme.palette.unresolved.main, - }; - return ( - - - {data.map((check, index) => ( - - ))} - - - - - - - - - ); -}; - -MonitorDetails60MinChart.propTypes = { - data: PropTypes.array.isRequired, -}; - -export default MonitorDetails60MinChart; diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index 2927a6cc0..c390a3699 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -6,6 +6,8 @@ import { CartesianGrid, ResponsiveContainer, Cell, + RadialBarChart, + RadialBar, } from "recharts"; import { formatDate } from "../../../../Utils/timeUtils"; import { useState } from "react"; @@ -28,10 +30,10 @@ const CustomLabels = ({ return ( <> - + {formatDate(new Date(firstDataPoint.time), options)} - + {formatDate(new Date(lastDataPoint.time), options)} @@ -186,3 +188,87 @@ export const DownBarChart = ({ data, type, onBarHover }) => { ); }; + +export const ResponseGaugeChart = ({ data }) => { + const theme = useTheme(); + + let max = 1000; // max ms + data = [{ response: max, fill: "transparent", background: false }, ...data]; + + let responseTime = Math.floor(data[1].response); + let responseProps = + responseTime <= 200 + ? { + category: "Excellent", + main: theme.palette.success.main, + bg: theme.palette.success.bg, + } + : responseTime <= 500 + ? { + category: "Fair", + main: theme.palette.success.main, + bg: theme.palette.success.bg, + } + : responseTime <= 600 + ? { + category: "Acceptable", + main: theme.palette.warning.main, + bg: theme.palette.warning.bg, + } + : { + category: "Poor", + main: theme.palette.error.text, + bg: theme.palette.error.bg, + }; + + return ( + + + + low + + + high + + + {responseProps.category} + + + {responseTime}{" "} + ms + + + + + + + + ); +}; diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 46d4772d7..dff958dd6 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -32,7 +32,7 @@ import PaginationTable from "./PaginationTable"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; import { StatBox, ChartBox, IconBox } from "./styled"; -import { DownBarChart, UpBarChart } from "./Charts"; +import { DownBarChart, ResponseGaugeChart, UpBarChart } from "./Charts"; import SkeletonLayout from "./skeleton"; import "./index.css"; @@ -133,8 +133,6 @@ const DetailsPage = ({ isAdmin }) => { undefined: "Pending...", }; - console.log(monitor); - return ( {loading ? ( @@ -463,7 +461,7 @@ const DetailsPage = ({ isAdmin }) => { onBarHover={setHoveredIncidentsData} /> - + @@ -472,6 +470,9 @@ const DetailsPage = ({ isAdmin }) => { Average Response Time + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index 6c3f8eb04..b8de5edfc 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -37,7 +37,6 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ alignItems: "center", }, "& tspan, & text": { - fontSize: 11, fill: theme.palette.text.tertiary, }, "& path": { diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index ab867ff47..9451b6187 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -182,8 +182,9 @@ const getMonitorStatsById = async (req) => { if (monitor.type === "http" || monitor.type === "ping") { // HTTP/PING Specific stats - monitorStats.avgResponseTime = getAverageResponseTime(checksForDateRange); - monitorStats.uptime = getUptimePercentage(checksForDateRange); + monitorStats.periodAvgResponseTime = + getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = getUptimePercentage(checksForDateRange); // Aggregate data let groupedChecks; @@ -198,11 +199,21 @@ const getMonitorStatsById = async (req) => { acc[time].checks.push(check); return acc; }, {}); + } else { + groupedChecks = checksForDateRange.reduce((acc, check) => { + const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); } // Map grouped checks to stats const aggregateData = Object.values(groupedChecks).map((group) => { const totalChecks = group.checks.length; + const uptimePercentage = getUptimePercentage(group.checks); const totalIncidents = group.checks.filter( (check) => check.status === false ).length; @@ -212,6 +223,7 @@ const getMonitorStatsById = async (req) => { return { time: group.time, + uptimePercentage, totalChecks, totalIncidents, avgResponseTime, @@ -220,7 +232,8 @@ const getMonitorStatsById = async (req) => { monitorStats.aggregateData = aggregateData; } - monitorStats.incidents = getIncidents(checksForDateRange); + monitorStats.periodIncidents = getIncidents(checksForDateRange); + monitorStats.periodTotalChecks = checksForDateRange.length; // If more than numToDisplay checks, pick every nth check From 7e01006cef0438b40b224794b7d4d603a119f7a5 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 21:09:18 -0400 Subject: [PATCH 10/12] Added gradient to area chart --- .../Charts/MonitorDetailsAreaChart/index.jsx | 20 +++++++++++++++-- .../Pages/Monitors/Details/Charts/index.jsx | 22 ++++++++++++------- Client/src/Pages/Monitors/Details/index.jsx | 8 +++---- Server/db/mongo/modules/monitorModule.js | 2 +- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 4e5d1d7b0..5b6b0dd74 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -64,6 +64,8 @@ const MonitorDetailsAreaChart = ({ checks }) => { }); }; + const theme = useTheme(); + return ( { bottom: 0, }} > + + + + + + { diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index c390a3699..b0382f958 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -54,12 +54,15 @@ export const UpBarChart = ({ data, type, onBarHover }) => { : { main: theme.palette.error.text, light: theme.palette.error.light }; }; + // TODO - REMOVE THIS LATER + let reversedData = [...data].reverse(); + return ( { setChartHovered(true); onBarHover({ time: null, totalChecks: 0, uptimePercentage: 0 }); @@ -80,8 +83,8 @@ export const UpBarChart = ({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={data[0]} - lastDataPoint={data[data.length - 1]} + firstDataPoint={reversedData[0]} + lastDataPoint={reversedData[reversedData.length - 1]} type={type} /> } @@ -91,7 +94,7 @@ export const UpBarChart = ({ data, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {data.map((entry, index) => { + {reversedData.map((entry, index) => { let { main, light } = getColorRange(entry.uptimePercentage); return ( { const [chartHovered, setChartHovered] = useState(false); const [hoveredBarIndex, setHoveredBarIndex] = useState(null); + // TODO - REMOVE THIS LATER + let reversedData = [...data].reverse(); + return ( { setChartHovered(true); onBarHover({ time: null, totalIncidents: 0 }); @@ -152,8 +158,8 @@ export const DownBarChart = ({ data, type, onBarHover }) => { y={0} width="100%" height="100%" - firstDataPoint={data[0]} - lastDataPoint={data[data.length - 1]} + firstDataPoint={reversedData[0]} + lastDataPoint={reversedData[reversedData.length - 1]} type={type} /> } @@ -163,7 +169,7 @@ export const DownBarChart = ({ data, type, onBarHover }) => { maxBarSize={7} background={{ fill: "transparent" }} > - {data.map((entry, index) => ( + {reversedData.map((entry, index) => ( { Response Times - - {/* */} - + { // Normalize checks if requested if (normalize !== undefined) { - const normailzedChecks = NormalizeData(checksForDateRange, 1, 100); + const normailzedChecks = NormalizeData(nthChecks, 1, 100); monitorStats.checks = normailzedChecks; } else { monitorStats.checks = nthChecks; From f689b304c86231968439c54aeb35d47a7669c4cf Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 21:41:16 -0400 Subject: [PATCH 11/12] Fixed area chart tooltip --- .../Charts/MonitorDetailsAreaChart/index.jsx | 61 +++++++++++++------ Client/src/Pages/Monitors/Details/index.jsx | 8 ++- Client/src/Pages/Monitors/Details/styled.jsx | 4 +- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 5b6b0dd74..68fdb1cd4 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; import { AreaChart, Area, XAxis, Tooltip, ResponsiveContainer } from "recharts"; -import { Box, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import "./index.css"; @@ -16,14 +16,15 @@ const CustomToolTip = ({ active, payload, label }) => { border: 1, borderColor: theme.palette.border.dark, borderRadius: theme.shape.borderRadius, - py: theme.spacing(6), - px: theme.spacing(8), + py: theme.spacing(2), + px: theme.spacing(4), }} > {new Date(label).toLocaleDateString("en-US", { @@ -38,15 +39,39 @@ const CustomToolTip = ({ active, payload, label }) => { hour12: true, // AM/PM format })} - - Response Time (ms): {payload[0].payload.originalResponseTime} - {" "} + + + + + Response Time + {" "} + + {payload[0].payload.originalResponseTime} + + {" "} + ms + + + + {/* Display original value */} ); @@ -67,10 +92,10 @@ const MonitorDetailsAreaChart = ({ checks }) => { const theme = useTheme(); return ( - + { } /> { data={[{ response: monitor?.periodAvgResponseTime }]} /> - + diff --git a/Client/src/Pages/Monitors/Details/styled.jsx b/Client/src/Pages/Monitors/Details/styled.jsx index b8de5edfc..99112802d 100644 --- a/Client/src/Pages/Monitors/Details/styled.jsx +++ b/Client/src/Pages/Monitors/Details/styled.jsx @@ -16,11 +16,11 @@ export const ChartBox = styled(Stack)(({ theme }) => ({ fontSize: 15, fontWeight: 500, }, - "& p": { + "& .MuiBox-root:not(.area-tooltip) p": { color: theme.palette.text.tertiary, fontSize: 13, }, - "& span": { + "& .MuiBox-root > span": { color: theme.palette.text.primary, fontSize: 20, "& span": { From 25c08ad9a40060d4624d65d7552aa7daa2271b85 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 4 Sep 2024 21:59:16 -0400 Subject: [PATCH 12/12] Fixed key props --- Client/src/Pages/Monitors/Details/Charts/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/Monitors/Details/Charts/index.jsx b/Client/src/Pages/Monitors/Details/Charts/index.jsx index b0382f958..1d0bb3bf2 100644 --- a/Client/src/Pages/Monitors/Details/Charts/index.jsx +++ b/Client/src/Pages/Monitors/Details/Charts/index.jsx @@ -98,7 +98,7 @@ export const UpBarChart = ({ data, type, onBarHover }) => { let { main, light } = getColorRange(entry.uptimePercentage); return ( { > {reversedData.map((entry, index) => (