diff --git a/Client/src/Pages/Uptime/Monitors/Components/ActionsMenu/index.jsx b/Client/src/Components/ActionsMenu/index.jsx similarity index 94% rename from Client/src/Pages/Uptime/Monitors/Components/ActionsMenu/index.jsx rename to Client/src/Components/ActionsMenu/index.jsx index f0561d6e6..a14e347fe 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/ActionsMenu/index.jsx +++ b/Client/src/Components/ActionsMenu/index.jsx @@ -2,16 +2,16 @@ import { useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../../../Utils/toastUtils"; -import { logger } from "../../../../../Utils/Logger"; +import { createToast } from "../../Utils/toastUtils"; +import { logger } from "../../Utils/Logger"; import { IconButton, Menu, MenuItem } from "@mui/material"; import { deleteUptimeMonitor, pauseUptimeMonitor, -} from "../../../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import Settings from "../../../../../assets/icons/settings-bold.svg?react"; +} from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; +import Settings from "../../assets/icons/settings-bold.svg?react"; import PropTypes from "prop-types"; -import Dialog from "../../../../../Components/Dialog"; +import Dialog from "../../Components/Dialog"; const ActionsMenu = ({ monitor, diff --git a/Client/src/Pages/Uptime/Monitors/Components/Host/index.jsx b/Client/src/Components/Host/index.jsx similarity index 96% rename from Client/src/Pages/Uptime/Monitors/Components/Host/index.jsx rename to Client/src/Components/Host/index.jsx index c2f082f3a..051e60331 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/Host/index.jsx +++ b/Client/src/Components/Host/index.jsx @@ -1,7 +1,7 @@ import { Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import Dot from "../../../../../Components/Dot"; +import Dot from "../Dot"; /** * Host component. * This subcomponent receives a params object and displays the host details. diff --git a/Client/src/Hooks/useMonitorUtils.js b/Client/src/Hooks/useMonitorUtils.js new file mode 100644 index 000000000..1e235af68 --- /dev/null +++ b/Client/src/Hooks/useMonitorUtils.js @@ -0,0 +1,45 @@ +import { useCallback } from "react"; + +const useMonitorUtils = () => { + const getMonitorWithPercentage = useCallback((monitor, theme) => { + let uptimePercentage = ""; + let percentageColor = ""; + + if (monitor.uptimePercentage !== undefined) { + uptimePercentage = + monitor.uptimePercentage === 0 + ? "0" + : (monitor.uptimePercentage * 100).toFixed(2); + + percentageColor = + monitor.uptimePercentage < 0.25 + ? theme.palette.error.main + : monitor.uptimePercentage < 0.5 + ? theme.palette.warning.main + : monitor.uptimePercentage < 0.75 + ? theme.palette.success.main + : theme.palette.success.main; + } + + return { + id: monitor._id, + name: monitor.name, + url: monitor.url, + title: monitor.name, + percentage: uptimePercentage, + percentageColor, + monitor: monitor, + }; + }, []); + + const determineState = useCallback((monitor) => { + if (typeof monitor === "undefined") return "pending"; + if (monitor.isActive === false) return "paused"; + if (monitor?.status === undefined) return "pending"; + return monitor?.status == true ? "up" : "down"; + }, []); + + return { getMonitorWithPercentage, determineState }; +}; + +export { useMonitorUtils }; diff --git a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx index 56d2f9618..7a4c4b210 100644 --- a/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx +++ b/Client/src/Pages/DistributedUptime/Details/Components/DistributedUptimeResponseChart/index.jsx @@ -8,6 +8,8 @@ import { ResponsiveContainer, Text, } from "recharts"; +import ChartBox from "../../../../../Components/Charts/ChartBox"; +import ResponseTimeIcon from "../../../../../assets/icons/response-time-icon.svg?react"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useState } from "react"; @@ -22,67 +24,73 @@ const CustomToolTip = ({ active, payload, label }) => { ? payload[0]?.payload?.originalAvgResponseTime : (payload[0]?.payload?.avgResponseTime ?? 0); return ( - } + header="Response Times" + sx={{ padding: 0 }} > - - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} - - - - - + + + - Response Time - {" "} - - {Math.floor(responseTime)} - {" "} - ms + Response Time + {" "} + + {Math.floor(responseTime)} + + {" "} + ms + - - + + + {/* Display original value */} - {/* Display original value */} - + ); } return null; diff --git a/Client/src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx new file mode 100644 index 000000000..53f36542f --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Details/Components/MonitorHeader/index.jsx @@ -0,0 +1,23 @@ +import { Stack, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import PropTypes from "prop-types"; + +const MonitorHeader = ({ monitor }) => { + const theme = useTheme(); + return ( + + + {monitor.name} + + Distributed Uptime Monitoring powered by DePIN + + + + ); +}; + +MonitorHeader.propTypes = { + monitor: PropTypes.object, +}; + +export default MonitorHeader; diff --git a/Client/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx b/Client/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx new file mode 100644 index 000000000..a4fd3b03d --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Details/Components/StatBoxes/index.jsx @@ -0,0 +1,62 @@ +// Components +import { Stack } from "@mui/material"; +import StatBox from "../../../../../Components/StatBox"; +import LastUpdate from "../LastUpdate"; +import UptLogo from "../../../../../assets/icons/upt_logo.png"; + +// Utils +import { useTheme } from "@mui/material/styles"; +import PropTypes from "prop-types"; + +const StatBoxes = ({ monitor, lastUpdateTrigger }) => { + const theme = useTheme(); + + return ( + + + + + } + /> + + } + /> + + + + ); +}; + +StatBoxes.propTypes = { + monitor: PropTypes.object, + lastUpdateTrigger: PropTypes.number, +}; + +export default StatBoxes; diff --git a/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx b/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx new file mode 100644 index 000000000..409e9cf89 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Details/Hooks/useSubscribeToDetails.jsx @@ -0,0 +1,119 @@ +import { useState, useEffect, useRef } from "react"; +import { networkService } from "../../../../main"; +import { useSelector } from "react-redux"; + +const getRandomDevice = () => { + const manufacturers = { + Apple: ["iPhone 15 Pro Max", "iPhone 15", "iPhone 14 Pro", "iPhone 14", "iPhone 13"], + Samsung: [ + "Galaxy S23 Ultra", + "Galaxy S23+", + "Galaxy S23", + "Galaxy Z Fold5", + "Galaxy Z Flip5", + ], + Google: ["Pixel 8 Pro", "Pixel 8", "Pixel 7a", "Pixel 7", "Pixel 6a"], + OnePlus: [ + "OnePlus 11", + "OnePlus 10T", + "OnePlus Nord 3", + "OnePlus 10 Pro", + "OnePlus Nord 2T", + ], + Xiaomi: ["13 Pro", "13", "Redmi Note 12", "POCO F5", "Redmi 12"], + Huawei: ["P60 Pro", "Mate X3", "Nova 11", "P50 Pro", "Mate 50"], + Sony: ["Xperia 1 V", "Xperia 5 V", "Xperia 10 V", "Xperia Pro-I", "Xperia 1 IV"], + Motorola: ["Edge 40 Pro", "Edge 40", "G84", "G54", "Razr 40 Ultra"], + ASUS: [ + "ROG Phone 7", + "Zenfone 10", + "ROG Phone 6", + "Zenfone 9", + "ROG Phone 7 Ultimate", + ], + }; + + const manufacturerNames = Object.keys(manufacturers); + const randomManufacturer = + manufacturerNames[Math.floor(Math.random() * manufacturerNames.length)]; + + const models = manufacturers[randomManufacturer]; + const randomModel = models[Math.floor(Math.random() * models.length)]; + + return { + manufacturer: randomManufacturer, + model: randomModel, + }; +}; +const useSubscribeToDetails = ({ monitorId, dateRange }) => { + const [isLoading, setIsLoading] = useState(true); + const [connectionStatus, setConnectionStatus] = useState(undefined); + const [retryCount, setRetryCount] = useState(0); + const [networkError, setNetworkError] = useState(false); + const [monitor, setMonitor] = useState(undefined); + const [lastUpdateTrigger, setLastUpdateTrigger] = useState(0); + const [devices, setDevices] = useState(Array.from({ length: 5 }, getRandomDevice)); + const authToken = useSelector((state) => state.auth.token); + + const prevDateRangeRef = useRef(dateRange); + + useEffect(() => { + try { + const cleanup = networkService.subscribeToDistributedUptimeDetails({ + authToken, + monitorId, + dateRange: dateRange, + normalize: true, + onUpdate: (data) => { + if (networkError === true) { + setNetworkError(false); + } + setLastUpdateTrigger(Date.now()); + const latestChecksWithDevice = data?.monitor?.latestChecks.map((check, idx) => { + check.device = devices[idx]; + return check; + }); + const monitorWithDevice = { + ...data.monitor, + latestChecks: latestChecksWithDevice, + }; + setMonitor(monitorWithDevice); + }, + onOpen: () => { + setConnectionStatus("up"); + setRetryCount(0); // Reset retry count on successful connection + }, + onError: () => { + setNetworkError(true); + setConnectionStatus("down"); + }, + }); + return cleanup; + } catch (error) { + setNetworkError(true); + } finally { + setIsLoading(false); + } + }, [ + authToken, + dateRange, + monitorId, + retryCount, + setConnectionStatus, + networkError, + devices, + ]); + + useEffect(() => { + const hasDateRangeChanged = prevDateRangeRef.current !== dateRange; + prevDateRangeRef.current = dateRange; // Update the ref to the current dateRange + + if (!hasDateRangeChanged) { + setDevices(Array.from({ length: 5 }, getRandomDevice)); + } + }, [dateRange]); + + return [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger]; +}; + +export default useSubscribeToDetails; diff --git a/Client/src/Pages/DistributedUptime/Details/index.jsx b/Client/src/Pages/DistributedUptime/Details/index.jsx index 6c921063f..ab39ec2ec 100644 --- a/Client/src/Pages/DistributedUptime/Details/index.jsx +++ b/Client/src/Pages/DistributedUptime/Details/index.jsx @@ -1,324 +1,102 @@ //Components import DistributedUptimeMap from "./Components/DistributedUptimeMap"; import Breadcrumbs from "../../../Components/Breadcrumbs"; -import { Stack, Typography, Box, Button, ButtonGroup } from "@mui/material"; -import ChartBox from "../../../Components/Charts/ChartBox"; -import StatBox from "../../../Components/StatBox"; -import ResponseTimeIcon from "../../../assets/icons/response-time-icon.svg?react"; +import { Stack, Typography } from "@mui/material"; import DeviceTicker from "./Components/DeviceTicker"; import DistributedUptimeResponseChart from "./Components/DistributedUptimeResponseChart"; -import UptLogo from "../../../assets/icons/upt_logo.png"; -import LastUpdate from "./Components/LastUpdate"; import NextExpectedCheck from "./Components/NextExpectedCheck"; import Footer from "./Components/Footer"; +import StatBoxes from "./Components/StatBoxes"; +import MonitorHeader from "./Components/MonitorHeader"; +import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; +import GenericFallback from "../../../Components/GenericFallback"; + //Utils -import { networkService } from "../../../main"; -import { useSelector } from "react-redux"; import { useTheme } from "@mui/material/styles"; -import { useEffect, useState, useCallback, useRef } from "react"; +import { useState } from "react"; import { useParams } from "react-router-dom"; - -//Constants -const BASE_BOX_PADDING_VERTICAL = 8; -const BASE_BOX_PADDING_HORIZONTAL = 8; -const MAX_RETRIES = 10; -const RETRY_DELAY = 5000; - -function getRandomDevice() { - const manufacturers = { - Apple: ["iPhone 15 Pro Max", "iPhone 15", "iPhone 14 Pro", "iPhone 14", "iPhone 13"], - Samsung: [ - "Galaxy S23 Ultra", - "Galaxy S23+", - "Galaxy S23", - "Galaxy Z Fold5", - "Galaxy Z Flip5", - ], - Google: ["Pixel 8 Pro", "Pixel 8", "Pixel 7a", "Pixel 7", "Pixel 6a"], - OnePlus: [ - "OnePlus 11", - "OnePlus 10T", - "OnePlus Nord 3", - "OnePlus 10 Pro", - "OnePlus Nord 2T", - ], - Xiaomi: ["13 Pro", "13", "Redmi Note 12", "POCO F5", "Redmi 12"], - Huawei: ["P60 Pro", "Mate X3", "Nova 11", "P50 Pro", "Mate 50"], - Sony: ["Xperia 1 V", "Xperia 5 V", "Xperia 10 V", "Xperia Pro-I", "Xperia 1 IV"], - Motorola: ["Edge 40 Pro", "Edge 40", "G84", "G54", "Razr 40 Ultra"], - ASUS: [ - "ROG Phone 7", - "Zenfone 10", - "ROG Phone 6", - "Zenfone 9", - "ROG Phone 7 Ultimate", - ], - }; - - const manufacturerNames = Object.keys(manufacturers); - const randomManufacturer = - manufacturerNames[Math.floor(Math.random() * manufacturerNames.length)]; - - const models = manufacturers[randomManufacturer]; - const randomModel = models[Math.floor(Math.random() * models.length)]; - - return { - manufacturer: randomManufacturer, - model: randomModel, - }; -} - -// export const StatBox = ({ heading, value, img, altTxt }) => { -// const theme = useTheme(); -// return ( -// -// {img && ( -// {altTxt} -// )} -// -// {heading} -// {value} -// -// -// ); -// }; +import useSubscribeToDetails from "./Hooks/useSubscribeToDetails"; const DistributedUptimeDetails = () => { - // Redux State - const { authToken } = useSelector((state) => state.auth); - const { mode } = useSelector((state) => state.ui); + const { monitorId } = useParams(); // Local State - // const [hoveredUptimeData, setHoveredUptimeData] = useState(null); - // const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null); - const [retryCount, setRetryCount] = useState(0); - const [connectionStatus, setConnectionStatus] = useState("down"); - const [lastUpdateTrigger, setLastUpdateTrigger] = useState(Date.now()); const [dateRange, setDateRange] = useState("day"); - const [monitor, setMonitor] = useState(null); - const [devices, setDevices] = useState([]); - - // Refs - const prevDateRangeRef = useRef(dateRange); // Utils const theme = useTheme(); - const { monitorId } = useParams(); - + const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] = + useSubscribeToDetails({ monitorId, dateRange }); // Constants const BREADCRUMBS = [ { name: "Distributed Uptime", path: "/distributed-uptime" }, { name: "Details", path: `/distributed-uptime/${monitorId}` }, ]; - useEffect(() => { - const hasDateRangeChanged = prevDateRangeRef.current !== dateRange; - prevDateRangeRef.current = dateRange; // Update the ref to the current dateRange + if (networkError) { + return ( + + + Network error + + Please check your connection + + ); + } - if (!hasDateRangeChanged) { - setDevices(Array.from({ length: 5 }, getRandomDevice)); - } - }, [dateRange]); - - const connectToService = useCallback(() => { - return networkService.subscribeToDistributedUptimeDetails({ - authToken, - monitorId, - dateRange: dateRange, - normalize: true, - onUpdate: (data) => { - setLastUpdateTrigger(Date.now()); - const latestChecksWithDevice = data?.monitor?.latestChecks.map((check, idx) => { - check.device = devices[idx]; - return check; - }); - const monitorWithDevice = { - ...data.monitor, - latestChecks: latestChecksWithDevice, - }; - setMonitor(monitorWithDevice); - }, - onOpen: () => { - setConnectionStatus("up"); - setRetryCount(0); // Reset retry count on successful connection - }, - onError: () => { - setConnectionStatus("down"); - console.log("Error, attempting reconnect..."); - - if (retryCount < MAX_RETRIES) { - setTimeout(() => { - setRetryCount((prev) => prev + 1); - connectToService(); - }, RETRY_DELAY); - } else { - console.log("Max retries reached"); - } - }, - }); - }, [authToken, monitorId, dateRange, retryCount, devices]); - - useEffect(() => { - const devices = Array.from({ length: 5 }, getRandomDevice); - const cleanup = connectToService(devices); - return cleanup; - }, [connectToService]); + if (typeof monitor === "undefined" || monitor.totalChecks === 0) { + return ( + + + + There is no check history for this monitor yet. + + + ); + } return ( - monitor && ( + + + + + + + - - {monitor?.url !== "https://jup.ag/" && - monitor?.url !== "https://explorer.solana.com/" && ( - - - {monitor.name} - - - )} - - - Distributed Uptime Monitoring powered by DePIN - - - - - - - } - /> - - } - /> - - - - - - - - - Showing statistics for past{" "} - {dateRange === "day" - ? "24 hours" - : dateRange === "week" - ? "7 days" - : "30 days"} - . - - - - - - - - } - header="Response Times" - sx={{ padding: 0 }} - > - - - - - - -