diff --git a/client/index.html b/client/index.html index 0b0f675b4..4f949edbb 100644 --- a/client/index.html +++ b/client/index.html @@ -18,7 +18,7 @@
diff --git a/client/src/App.jsx b/client/src/App.tsx similarity index 74% rename from client/src/App.jsx rename to client/src/App.tsx index da83f9061..ef3625cad 100644 --- a/client/src/App.jsx +++ b/client/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, type CSSProperties } from "react"; import { useSelector } from "react-redux"; import "react-toastify/dist/ReactToastify.css"; import { ToastContainer } from "react-toastify"; @@ -7,18 +7,17 @@ import lightTheme from "./Utils/Theme/lightTheme"; import darkTheme from "./Utils/Theme/darkTheme"; import { CssBaseline, GlobalStyles } from "@mui/material"; import { logger } from "./Utils/Logger"; // Import the logger -import { networkService } from "./main"; import { Routes } from "./Routes"; import AppLayout from "@/Components/v1/Layouts/AppLayout"; +import type { RootState } from "@/Types/state"; function App() { - const mode = useSelector((state) => state.ui.mode); + const mode = useSelector((state: RootState) => state.ui.mode); // Cleanup useEffect(() => { return () => { logger.cleanup(); - networkService.cleanup(); }; }, []); @@ -30,6 +29,7 @@ function App() { ); diff --git a/client/src/Components/v1/ActionsMenu/index.jsx b/client/src/Components/v1/ActionsMenu/index.jsx deleted file mode 100644 index ea0d029b8..000000000 --- a/client/src/Components/v1/ActionsMenu/index.jsx +++ /dev/null @@ -1,228 +0,0 @@ -// Components -import IconButton from "@mui/material/IconButton"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; -import Icon from "../Icon"; -import Dialog from "../Dialog/index.jsx"; - -// Utils -import { useState } from "react"; -import { useTheme } from "@emotion/react"; -import { useNavigate } from "react-router-dom"; -import { createToast } from "../../../Utils/toastUtils.jsx"; - -import PropTypes from "prop-types"; -import { usePauseMonitor, useDeleteMonitor } from "../../../Hooks/monitorHooks.js"; - -const ActionsMenu = ({ - monitor, - isAdmin, - updateRowCallback, - pauseCallback, - setIsLoading = () => {}, -}) => { - const [anchorEl, setAnchorEl] = useState(null); - const [actions, setActions] = useState({}); - const [isOpen, setIsOpen] = useState(false); - const theme = useTheme(); - const [pauseMonitor, isPausing, error] = usePauseMonitor(); - const [deleteMonitor, isDeleting] = useDeleteMonitor(); - - const handleRemove = async (event) => { - event.preventDefault(); - event.stopPropagation(); - let monitor = { id: actions.id }; - await deleteMonitor({ monitor }); - updateRowCallback(); - }; - - const handlePause = async () => { - try { - setIsLoading(true); - await pauseMonitor({ monitorId: monitor.id }); - pauseCallback(); - } catch (error) { - createToast({ body: "Failed to pause monitor." }); - } finally { - setIsLoading(false); - } - }; - - const openMenu = (event, id, url) => { - event.preventDefault(); - event.stopPropagation(); - setAnchorEl(event.currentTarget); - setActions({ id: id, url: url }); - }; - - const openRemove = (e) => { - closeMenu(e); - setIsOpen(true); - }; - - const closeMenu = (e) => { - e.stopPropagation(); - setAnchorEl(null); - }; - - const navigate = useNavigate(); - return ( - <> - { - event.stopPropagation(); - openMenu(event, monitor.id, monitor.type === "ping" ? null : monitor.url); - }} - sx={{ - "&:focus": { - outline: "none", - }, - "& svg path": { - stroke: theme.palette.primary.contrastTextTertiary, - }, - }} - > - - - - closeMenu(e)} - disableScrollLock - slotProps={{ - paper: { - sx: { - "& ul": { - p: theme.spacing(2.5), - backgroundColor: theme.palette.primary.main, - }, - "& li": { m: 0, color: theme.palette.primary.contrastTextSecondary }, - /* - This should not be set automatically on the last of type - "& li:last-of-type": { - color: theme.palette.error.main, - }, */ - }, - }, - }} - > - {actions.url !== null ? ( - { - closeMenu(e); - e.stopPropagation(); - window.open(actions.url, "_blank", "noreferrer"); - }} - > - Open site - - ) : ( - "" - )} - { - e.stopPropagation(); - navigate(`/uptime/${actions.id}`); - }} - > - Details - - {/* TODO - pass monitor id to Incidents page */} - { - e.stopPropagation(); - navigate(`/incidents/${actions.id}`); - }} - > - Incidents - - {isAdmin && ( - { - e.stopPropagation(); - - navigate(`/uptime/configure/${actions.id}`); - }} - > - Configure - - )} - {isAdmin && ( - { - e.stopPropagation(); - navigate(`/uptime/create/${actions.id}`); - }} - > - Clone - - )} - {isAdmin && ( - { - closeMenu(e); - - e.stopPropagation(); - handlePause(e); - }} - > - {monitor?.isActive === true ? "Pause" : "Resume"} - - )} - {isAdmin && ( - { - e.stopPropagation(); - openRemove(e); - }} - sx={{ "&.MuiButtonBase-root": { color: theme.palette.error.main } }} - > - Remove - - )} - - { - e.stopPropagation(); - setIsOpen(false); - }} - confirmationButtonLabel="Delete" - /* Do we need stop propagation? */ - onConfirm={(e) => { - console.log(e); - e.stopPropagation(); - handleRemove(e); - }} - isLoading={isDeleting} - modelTitle="modal-delete-monitor" - modelDescription="delete-monitor-confirmation" - /> - - ); -}; - -ActionsMenu.propTypes = { - monitor: PropTypes.shape({ - id: PropTypes.string, - url: PropTypes.string, - type: PropTypes.string, - isActive: PropTypes.bool, - }).isRequired, - isAdmin: PropTypes.bool, - updateRowCallback: PropTypes.func, - pauseCallback: PropTypes.func, - setIsLoading: PropTypes.func, -}; - -export default ActionsMenu; diff --git a/client/src/Components/v1/Animated/PulseDot.jsx b/client/src/Components/v1/Animated/PulseDot.jsx deleted file mode 100644 index 83513d56e..000000000 --- a/client/src/Components/v1/Animated/PulseDot.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import PropTypes from "prop-types"; -import { Box, Stack, useTheme } from "@mui/material"; -// import useUtils from "../../Pages/Uptime/utils"; - -/** - * A component that renders a pulsating dot with a specified color. - * - * @component - * @example - * // Example usage: - * - * - * @param {Object} props - * @param {string} props.color - The color of the dot. - * @returns {JSX.Element} The PulseDot component. - */ - -const PulseDot = ({ color }) => { - const theme = useTheme(); - // const { statusToTheme } = useUtils(); - /* TODO refactor so it gets status and gets theme color. Then uses theme.palette.[themeColor].lowContrast */ - /* const themeColor = statusToTheme[status]; */ - - return ( - - - - ); -}; - -PulseDot.propTypes = { - color: PropTypes.string.isRequired, -}; - -export default PulseDot; diff --git a/client/src/Components/v1/Buttons/RoundGradientButton.jsx b/client/src/Components/v1/Buttons/RoundGradientButton.jsx deleted file mode 100644 index ff22be955..000000000 --- a/client/src/Components/v1/Buttons/RoundGradientButton.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import Button from "@mui/material/Button"; -import { styled } from "@mui/material/styles"; - -const RoundGradientButton = styled(Button)(({ theme }) => ({ - position: "relative", - border: "5px solid transparent", - backgroundClip: "padding-box", - borderRadius: 30, - fontSize: "1.2rem", - color: theme.palette.primary.contrastText, - backgroundColor: theme.palette.background.main, - - "&:after": { - position: "absolute", - top: -3, - left: -3, - right: -3, - bottom: -3, - background: - theme.palette.mode === "dark" - ? "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)" - : "linear-gradient(90deg, #842bd2, #ff5451, #8c52ff)", - content: '""', - zIndex: -1, - borderRadius: 30, - }, -})); - -export default RoundGradientButton; diff --git a/client/src/Components/v1/Charts/AreaChart/index.jsx b/client/src/Components/v1/Charts/AreaChart/index.jsx deleted file mode 100644 index dd5a900d6..000000000 --- a/client/src/Components/v1/Charts/AreaChart/index.jsx +++ /dev/null @@ -1,214 +0,0 @@ -/** - * CustomAreaChart component for rendering an area chart with optional gradient and custom ticks. - * - * @param {Object} props - The properties object. - * @param {Array} props.data - The data array for the chart. - * @param {Array} props.dataKeys - An array of data keys to be plotted as separate areas. - * @param {string} props.xKey - The key for the x-axis data. - * @param {string} [props.yKey] - The key for the y-axis data (optional). - * @param {Object} [props.xTick] - Custom tick component for the x-axis. - * @param {Object} [props.yTick] - Custom tick component for the y-axis. - * @param {string} [props.strokeColor] - The base stroke color for the areas. - * If not provided, uses a predefined color palette. - * @param {string} [props.fillColor] - The base fill color for the areas. - * @param {boolean} [props.gradient=false] - Whether to apply a gradient fill to the areas. - * @param {string} [props.gradientDirection="vertical"] - The direction of the gradient. - * @param {string} [props.gradientStartColor] - The start color of the gradient. - * Defaults to the area's stroke color if not provided. - * @param {string} [props.gradientEndColor] - The end color of the gradient. - * @param {Object} [props.customTooltip] - Custom tooltip component for the chart. - * @param {string|number} [props.height="100%"] - Height of the chart container. - * - * @returns {JSX.Element} The rendered area chart component. - * - * @example - * // Single series chart - * - * - * @example - * // Multi-series chart with custom tooltip - * } - * yTick={} - * gradient={true} - * customTooltip={({ active, payload, label }) => ( - * - * )} - * /> - */ - -import { - AreaChart, - Area, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, -} from "recharts"; -import { createGradient } from "../Utils/gradientUtils.jsx"; -import PropTypes from "prop-types"; -import { useTheme } from "@mui/material"; -import { useId } from "react"; -import { Fragment } from "react"; - -const CustomAreaChart = ({ - data, - dataKeys, - xKey, - xDomain, - yKey, - yDomain, - xTick, - yTick, - strokeColor, - fillColor, - gradient = false, - gradientDirection = "vertical", - gradientStartColor, - gradientEndColor, - customTooltip, - height = "100%", -}) => { - const theme = useTheme(); - const uniqueId = useId(); - - const AREA_COLORS = [ - // Blues - "#3182bd", // Deep blue - "#6baed6", // Medium blue - "#9ecae1", // Light blue - - // Greens - "#74c476", // Soft green - "#a1d99b", // Light green - "#c7e9c0", // Pale green - - // Oranges - "#fdae6b", // Warm orange - "#fdd0a2", // Light orange - "#feedde", // Pale orange - - // Purples - "#9467bd", // Lavender - "#a55194", // Deep magenta - "#c994c7", // Soft magenta - - // Reds - "#ff9896", // Soft red - "#de2d26", // Deep red - "#fc9272", // Medium red - - // Cyans/Teals - "#17becf", // Cyan - "#7fcdbb", // Teal - "#a1dab4", // Light teal - - // Yellows - "#fec44f", // Mustard - "#fee391", // Light yellow - "#ffffd4", // Pale yellow - - // Additional colors - "#e377c2", // Soft pink - "#bcbd22", // Olive - "#2ca02c", // Vibrant green - ]; - - return ( - - - - - - - {dataKeys.map((dataKey, index) => { - const gradientId = `gradient-${uniqueId}-${index}`; - - return ( - - {gradient === true && - createGradient({ - id: gradientId, - startColor: gradientStartColor || AREA_COLORS[index], - endColor: gradientEndColor, - direction: gradientDirection, - })} - - - ); - })} - {customTooltip ? ( - - ) : ( - - )} - - - ); -}; - -CustomAreaChart.propTypes = { - data: PropTypes.array.isRequired, - dataKeys: PropTypes.array.isRequired, - xTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself - yTick: PropTypes.object, // Recharts takes an instance of component, so we can't pass the component itself - xKey: PropTypes.string.isRequired, - xDomain: PropTypes.array, - yKey: PropTypes.string, - yDomain: PropTypes.array, - fillColor: PropTypes.string, - strokeColor: PropTypes.string, - gradient: PropTypes.bool, - gradientDirection: PropTypes.string, - gradientStartColor: PropTypes.string, - gradientEndColor: PropTypes.string, - customTooltip: PropTypes.object, - height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), -}; - -export default CustomAreaChart; diff --git a/client/src/Components/v1/Charts/BarChart/index.css b/client/src/Components/v1/Charts/BarChart/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Components/v1/Charts/BarChart/index.jsx b/client/src/Components/v1/Charts/BarChart/index.jsx deleted file mode 100644 index 77b836cae..000000000 --- a/client/src/Components/v1/Charts/BarChart/index.jsx +++ /dev/null @@ -1,181 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js"; -import { useEffect, useState } from "react"; -import "./index.css"; -import { useSelector } from "react-redux"; - -/* TODO add prop validation and jsdocs */ -const BarChart = ({ checks = [] }) => { - const theme = useTheme(); - const [animate, setAnimate] = useState(false); - const uiTimezone = useSelector((state) => state.ui.timezone); - - useEffect(() => { - setAnimate(true); - }); - - // set responseTime to average if there's only one check - if (checks.length === 1) { - checks[0] = { ...checks[0], responseTime: 50 }; - } - - if (checks.length !== 25) { - const placeholders = Array(25 - checks.length).fill("placeholder"); - checks = [...checks, ...placeholders]; - } - - return ( - event.stopPropagation()} - sx={{ - cursor: "default", - }} - > - {checks.map((check, index) => - check === "placeholder" ? ( - /* TODO what is the purpose of this box? */ - // CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars - // even if there are less than 25 checks - - ) : ( - - - {formatDateWithTz( - check.updatedAt, - "ddd, MMMM D, YYYY, HH:mm A", - uiTimezone - )} - - - - - - Response Time - - - {check.originalResponseTime} - - {" "} - ms - - - - - - } - placement="top" - key={`check-${check?.id}`} - slotProps={{ - popper: { - className: "bar-tooltip", - modifiers: [ - { - name: "offset", - options: { - offset: [0, -10], - }, - }, - ], - sx: { - "& .MuiTooltip-tooltip": { - backgroundColor: theme.palette.secondary.main, - border: 1, - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shape.boxShadow, - px: theme.spacing(4), - py: theme.spacing(3), - }, - "& .MuiTooltip-tooltip p": { - /* TODO Font size should point to theme */ - fontSize: 12, - color: theme.palette.secondary.contrastText, - fontWeight: 500, - }, - "& .MuiTooltip-tooltip span": { - /* TODO Font size should point to theme */ - fontSize: 11, - color: theme.palette.secondary.contrastText, - fontWeight: 600, - }, - }, - }, - }} - > - .MuiBox-root": { - filter: "brightness(0.8)", - }, */ - }} - > - - - - ) - )} - - ); -}; - -export default BarChart; diff --git a/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx b/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx deleted file mode 100644 index c1b60632d..000000000 --- a/client/src/Components/v1/Charts/ChartBox/EmptyView.jsx +++ /dev/null @@ -1,106 +0,0 @@ -// Components -import { Typography, Stack } from "@mui/material"; -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox/index.jsx"; - -/** - * `EmptyView` is a functional React component that displays an empty state view with an optional icon, header, and message. - * - * @component - * @param {Object} props - The properties that define the `EmptyView` component. - * @param {React.ReactNode} [props.icon] - An optional icon to display at the top of the empty view. - * @param {string} [props.header] - An optional header text displayed next to the icon. - * @param {string} [props.message="No Data"] - The message to be displayed in the empty view. - * @param {'h1' | 'h2' | 'h3'} [props.headingLevel="h2"] - The heading level for the message text. - * @param {string} [props.justifyContent="flex-start"] - The CSS `justify-content` value to align elements vertically. - * @param {string} [props.height="100%"] - The height of the empty view container. - * - * @example - * // Example usage of EmptyView component: - * } - * header="Average Response Time" - * message="No Response Time Available" - * headingLevel="h2" - * justifyContent="center" - * height="50%" - * /> - * - * @returns {React.Element} The `EmptyView` component with customizable icon, header, and message. - */ - -const EmptyView = ({ - icon, - header, - message = "No Data", - headingLevel = "h2", - justifyContent = "flex-start", - height = "100%", -}) => { - const theme = useTheme(); - return ( - - - - {icon && {icon}} - {header && {header}} - - - {message} - - - - ); -}; - -EmptyView.propTypes = { - message: PropTypes.string, - icon: PropTypes.node, - header: PropTypes.string, - headingLevel: PropTypes.oneOf(["h1", "h2", "h3"]), - justifyContent: PropTypes.string, - height: PropTypes.string, -}; - -export default EmptyView; diff --git a/client/src/Components/v1/Charts/ChartBox/index.jsx b/client/src/Components/v1/Charts/ChartBox/index.jsx deleted file mode 100644 index be2faf8ed..000000000 --- a/client/src/Components/v1/Charts/ChartBox/index.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox/index.jsx"; -import EmptyView from "./EmptyView.jsx"; -import PropTypes from "prop-types"; - -const ChartBox = ({ - children, - icon, - header, - height = "300px", - justifyContent = "space-between", - Legend, - borderRadiusRight = 4, - sx, - noDataMessage, - isEmpty = false, -}) => { - const theme = useTheme(); - if (isEmpty) { - return ( - - ); - } - return ( - - span": { - color: theme.palette.primary.contrastText, - fontSize: 20, - "& span": { - opacity: 0.8, - marginLeft: 2, - fontSize: 15, - }, - }, - - "& tspan, & text": { - fill: theme.palette.primary.contrastTextTertiary, - }, - "& path": { - transition: "fill 300ms ease, stroke-width 400ms ease", - }, - }} - > - - {icon && {icon}} - {header && {header}} - - {children} - - {Legend && Legend} - - ); -}; - -export default ChartBox; - -ChartBox.propTypes = { - children: PropTypes.node, - icon: PropTypes.node, - header: PropTypes.string, - height: PropTypes.string, - noDataMessage: PropTypes.string, - isEmpty: PropTypes.bool, -}; diff --git a/client/src/Components/v1/Charts/CustomGauge/index.css b/client/src/Components/v1/Charts/CustomGauge/index.css deleted file mode 100644 index 1b64a6b9a..000000000 --- a/client/src/Components/v1/Charts/CustomGauge/index.css +++ /dev/null @@ -1,14 +0,0 @@ -.radial-chart { - position: relative; - display: inline-block; -} - -.radial-chart-base { - opacity: 0.3; -} - -.radial-chart-progress { - transform: rotate(-90deg); - transform-origin: center; - transition: stroke-dashoffset 1.5s ease-in-out; -} diff --git a/client/src/Components/v1/Charts/CustomGauge/index.jsx b/client/src/Components/v1/Charts/CustomGauge/index.jsx deleted file mode 100644 index 614b3774c..000000000 --- a/client/src/Components/v1/Charts/CustomGauge/index.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { useEffect, useState, useMemo } from "react"; -import Stack from "@mui/material/Stack"; -import { Box, Typography } from "@mui/material"; -import PropTypes from "prop-types"; -import "./index.css"; -import CircularProgress from "@mui/material/CircularProgress"; - -const MINIMUM_VALUE = 0; -const MAXIMUM_VALUE = 100; - -/** - * A Performant SVG based circular gauge - * - * @component - * @param {Object} props - Component properties - * @param {number} [props.progress=0] - Progress percentage (0-100) - * @param {number} [props.radius=60] - Radius of the gauge circle - * @param {number} [props.strokeWidth=15] - Width of the gauge stroke - * @param {number} [props.precision=1] - Precision of the progress percentage - * @param {string} [props.unit="%"] - Unit of progress - * - * @example - * - * - * @returns {React.ReactElement} Rendered CustomGauge component - */ -const CustomGauge = ({ - isLoading = false, - progress = 0, - radius = 70, - strokeWidth = 15, - precision = 1, - unit = "%", -}) => { - const theme = useTheme(); - // Calculate the length of the stroke for the circle - const { circumference, totalSize, strokeLength } = useMemo( - () => ({ - circumference: 2 * Math.PI * radius, - totalSize: radius * 2 + strokeWidth * 2, - strokeLength: (progress / 100) * (2 * Math.PI * radius), - }), - [radius, strokeWidth, progress] - ); - - // Handle initial animation - const [offset, setOffset] = useState(circumference); - useEffect(() => { - setOffset(circumference); - const timer = setTimeout(() => { - setOffset(circumference - strokeLength); - }, 100); - - return () => clearTimeout(timer); - }, [progress, circumference, strokeLength]); - - const progressWithinRange = Math.max(MINIMUM_VALUE, Math.min(progress, MAXIMUM_VALUE)); - - let fillColor; - if (progressWithinRange < 50) { - fillColor = theme.palette.success.main; - } else if (progressWithinRange < 80) { - fillColor = theme.palette.warning.lowContrast; - } else { - fillColor = theme.palette.error.lowContrast; - } - - if (isLoading) { - return ( - - - - ); - } - - return ( - - - - - - - - {`${progressWithinRange.toFixed(precision)}${unit}`} - - - ); -}; - -export default CustomGauge; - -CustomGauge.propTypes = { - isLoading: PropTypes.bool, - progress: PropTypes.number, - radius: PropTypes.number, - strokeWidth: PropTypes.number, - precision: PropTypes.number, - unit: PropTypes.string, -}; diff --git a/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx deleted file mode 100644 index 8b6c2b165..000000000 --- a/client/src/Components/v1/Charts/DePINStatusPageBarChart/index.jsx +++ /dev/null @@ -1,181 +0,0 @@ -// Components -import { Stack, Box, Tooltip, Typography } from "@mui/material"; - -// Utils -import { useTheme } from "@emotion/react"; -import { useState, useEffect } from "react"; -import PropTypes from "prop-types"; -import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js"; -import { useSelector } from "react-redux"; - -const PlaceholderCheck = ({ daysToShow }) => { - const theme = useTheme(); - - return ( - - ); -}; - -PlaceholderCheck.propTypes = { - daysToShow: PropTypes.number, -}; - -const Check = ({ check, daysToShow }) => { - const [animate, setAnimate] = useState(false); - - const theme = useTheme(); - useEffect(() => { - setAnimate(true); - }, []); - const uiTimezone = useSelector((state) => state.ui.timezone); - - return ( - - - {formatDateWithTz(check._id, "ddd, MMMM D, YYYY", uiTimezone)} - - - - - Uptime percentage - - - {check.upPercentage.toFixed(2)} - - {" "} - % - - - - - - } - placement="top" - key={`check-${check?._id}`} - slotProps={{ - popper: { - className: "bar-tooltip", - modifiers: [ - { - name: "offset", - options: { - offset: [0, -10], - }, - }, - ], - sx: { - "& .MuiTooltip-tooltip": { - backgroundColor: theme.palette.secondary.main, - border: 1, - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shape.boxShadow, - px: theme.spacing(4), - py: theme.spacing(3), - }, - "& .MuiTooltip-tooltip p": { - /* TODO Font size should point to theme */ - fontSize: 12, - color: theme.palette.secondary.contrastText, - fontWeight: 500, - }, - "& .MuiTooltip-tooltip span": { - /* TODO Font size should point to theme */ - fontSize: 11, - color: theme.palette.secondary.contrastText, - fontWeight: 600, - }, - }, - }, - }} - > - - - - - ); -}; - -Check.propTypes = { - check: PropTypes.object, - daysToShow: PropTypes.number, -}; - -const DePINStatusPageBarChart = ({ checks = [], daysToShow = 30 }) => { - if (checks.length !== daysToShow) { - const placeholders = Array(daysToShow - checks.length).fill("placeholder"); - checks = [...checks, ...placeholders]; - } - return ( - - {checks.map((check) => { - if (check === "placeholder") { - return ( - - ); - } - return ( - - ); - })} - - ); -}; - -DePINStatusPageBarChart.propTypes = { - checks: PropTypes.array, - daysToShow: PropTypes.number, -}; - -export default DePINStatusPageBarChart; diff --git a/client/src/Components/v1/Charts/LegendBox/index.jsx b/client/src/Components/v1/Charts/LegendBox/index.jsx deleted file mode 100644 index f19e09105..000000000 --- a/client/src/Components/v1/Charts/LegendBox/index.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import IconBox from "../../IconBox/index.jsx"; -import PropTypes from "prop-types"; - -const LegendBox = ({ children, icon, header, sx }) => { - const theme = useTheme(); - return ( - - - {icon} - {header} - - {children} - - ); -}; - -LegendBox.propTypes = { - children: PropTypes.node, - icon: PropTypes.node, - header: PropTypes.string, -}; - -export default LegendBox; diff --git a/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.css b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx b/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx deleted file mode 100644 index dafca7b40..000000000 --- a/client/src/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx +++ /dev/null @@ -1,222 +0,0 @@ -import PropTypes from "prop-types"; -import { - AreaChart, - Area, - XAxis, - Tooltip, - CartesianGrid, - ResponsiveContainer, - Text, -} from "recharts"; -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/timeUtilsLegacy.js"; -import { - tooltipDateFormatLookup, - tickDateFormatLookup, -} from "../Utils/chartUtilFunctions.js"; - -import "./index.css"; -const CustomToolTip = ({ active, payload, label, dateRange }) => { - const format = tooltipDateFormatLookup(dateRange); - const uiTimezone = useSelector((state) => state.ui.timezone); - const theme = useTheme(); - if (active && payload && payload.length) { - const responseTime = payload[0]?.payload?.originalAvgResponseTime - ? payload[0]?.payload?.originalAvgResponseTime - : (payload[0]?.payload?.avgResponseTime ?? 0); - return ( - - - {formatDateWithTz(label, format, uiTimezone)} - - - - - - Response time: - - - {Math.floor(responseTime)} - - {" "} - ms - - - - - {/* Display original value */} - - ); - } - return null; -}; - -CustomToolTip.propTypes = { - active: PropTypes.bool, - payload: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.number, - payload: PropTypes.shape({ - _id: PropTypes.string, - avgResponseTime: PropTypes.number, - originalAvgResponseTime: PropTypes.number, - }), - }) - ), - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - dateRange: PropTypes.string, -}; -const CustomTick = ({ x, y, payload, dateRange }) => { - const format = tickDateFormatLookup(dateRange); - const theme = useTheme(); - const uiTimezone = useSelector((state) => state.ui.timezone); - return ( - - {formatDateWithTz(payload?.value, format, uiTimezone)} - - ); -}; - -CustomTick.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - payload: PropTypes.object, - index: PropTypes.number, - dateRange: PropTypes.string, -}; - -const MonitorDetailsAreaChart = ({ checks, dateRange }) => { - const theme = useTheme(); - const memoizedChecks = useMemo(() => checks, [checks[0]]); - const [isHovered, setIsHovered] = useState(false); - return ( - - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > - - - - - - - - } - axisLine={false} - tickLine={false} - height={20} - /> - } - wrapperStyle={{ pointerEvents: "none" }} - /> - - - - ); -}; - -MonitorDetailsAreaChart.propTypes = { - checks: PropTypes.array, - dateRange: PropTypes.string, -}; - -export default MonitorDetailsAreaChart; diff --git a/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx b/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx deleted file mode 100644 index 2dc5e61f0..000000000 --- a/client/src/Components/v1/Charts/StatusPageBarChart/index.jsx +++ /dev/null @@ -1,217 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { Box, Stack, Tooltip, Typography } from "@mui/material"; -import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js"; -import { useEffect, useState, forwardRef } from "react"; -import { useSelector } from "react-redux"; -import PropTypes from "prop-types"; - -/** - * A customizable Bar component that renders a colored bar with optional children. - * @component - * - * @param {string} width The width of the bar (e.g., "100px"). - * @param {string} height The height of the bar (e.g., "50px"). - * @param {string} backgroundColor The background color of the bar (e.g., "#FF5733"). - * @param {string} [borderRadius] Optional border radius for the bar (e.g., "8px"). - * @param {node} children The content to be rendered inside the bar. - * @returns {JSX.Element} The Bar component. - */ - -const Bar = forwardRef( - ({ width, height, backgroundColor, borderRadius, children, ...otherProps }, ref) => { - const theme = useTheme(); - - return ( - - {children} - - ); - } -); - -Bar.displayName = "Bar"; - -Bar.propTypes = { - width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, - height: PropTypes.string.isRequired, - backgroundColor: PropTypes.string.isRequired, - borderRadius: PropTypes.string, - children: PropTypes.node, -}; - -/* TODO add prop validation and jsdocs */ -const StatusPageBarChart = ({ checks = [] }) => { - const theme = useTheme(); - const [animate, setAnimate] = useState(false); - const uiTimezone = useSelector((state) => state.ui.timezone); - - const barWidth = { - xs: "calc(60% / 25)", - xl: "calc(40% / 25)", - }; - - useEffect(() => { - setAnimate(true); - }, []); - - // set responseTime to average if there's only one check - if (checks.length === 1) { - checks[0] = { ...checks[0], responseTime: 50 }; - } - - if (checks.length !== 25) { - const placeholders = Array(25 - checks.length).fill("placeholder"); - checks = [...checks, ...placeholders]; - } - - return ( - event.stopPropagation()} - sx={{ - cursor: "default", - }} - > - {checks.map((check, index) => - check === "placeholder" ? ( - /* TODO what is the purpose of this box? */ - // CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars - // even if there are less than 25 checks - - ) : ( - - - {formatDateWithTz( - check.createdAt, - "ddd, MMMM D, YYYY, HH:mm A", - uiTimezone - )} - - - - - - Response Time - - - {check.originalResponseTime} - - {" "} - ms - - - - - - } - placement="top" - key={`check-${check?.id}`} - slotProps={{ - popper: { - className: "bar-tooltip", - modifiers: [ - { - name: "offset", - options: { - offset: [0, -10], - }, - }, - ], - sx: { - "& .MuiTooltip-tooltip": { - backgroundColor: theme.palette.secondary.main, - border: 1, - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shape.boxShadow, - px: theme.spacing(4), - py: theme.spacing(3), - }, - "& .MuiTooltip-tooltip p": { - /* TODO Font size should point to theme */ - fontSize: 12, - color: theme.palette.secondary.contrastText, - fontWeight: 500, - }, - "& .MuiTooltip-tooltip span": { - /* TODO Font size should point to theme */ - fontSize: 11, - color: theme.palette.secondary.contrastText, - fontWeight: 600, - }, - }, - }, - }} - > - - - - - ) - )} - - ); -}; - -export default StatusPageBarChart; diff --git a/client/src/Components/v1/Charts/Utils/chartUtilFunctions.js b/client/src/Components/v1/Charts/Utils/chartUtilFunctions.js deleted file mode 100644 index 2851af93e..000000000 --- a/client/src/Components/v1/Charts/Utils/chartUtilFunctions.js +++ /dev/null @@ -1,27 +0,0 @@ -export const tooltipDateFormatLookup = (dateRange) => { - const dateFormatLookup = { - recent: "ddd. MMMM D, YYYY, hh:mm A", - day: "ddd. MMMM D, YYYY, hh:mm A", - week: "ddd. MMMM D, YYYY, hh:mm A", - month: "ddd. MMMM D, YYYY", - }; - const format = dateFormatLookup[dateRange]; - if (format === undefined) { - return ""; - } - return format; -}; - -export const tickDateFormatLookup = (dateRange) => { - const tickFormatLookup = { - recent: "h:mm A", - day: "h:mm A", - week: "MM/D, h:mm A", - month: "ddd. M/D", - }; - const format = tickFormatLookup[dateRange]; - if (format === undefined) { - return ""; - } - return format; -}; diff --git a/client/src/Components/v1/Charts/Utils/chartUtils.jsx b/client/src/Components/v1/Charts/Utils/chartUtils.jsx deleted file mode 100644 index 7797ba462..000000000 --- a/client/src/Components/v1/Charts/Utils/chartUtils.jsx +++ /dev/null @@ -1,337 +0,0 @@ -import PropTypes from "prop-types"; -import { useSelector } from "react-redux"; -import { useTheme } from "@mui/material"; -import { Text } from "recharts"; -import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js"; -import { Box, Stack, Typography } from "@mui/material"; -import { tickDateFormatLookup, tooltipDateFormatLookup } from "./chartUtilFunctions.js"; -/** - * Custom tick component for rendering time with timezone. - * - * @param {Object} props - The properties object. - * @param {number} props.x - The x-coordinate for the tick. - * @param {number} props.y - The y-coordinate for the tick. - * @param {Object} props.payload - The payload object containing tick data. - * @param {number} props.index - The index of the tick. - * @returns {JSX.Element} The rendered tick component. - */ -export const TzTick = ({ x, y, payload, index, dateRange }) => { - const theme = useTheme(); - const uiTimezone = useSelector((state) => state.ui.timezone); - const format = tickDateFormatLookup(dateRange); - return ( - - {formatDateWithTz(payload?.value, format, uiTimezone)} - - ); -}; - -TzTick.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - payload: PropTypes.object, - index: PropTypes.number, - dateRange: PropTypes.string, -}; - -/** - * Custom tick component for rendering percentage values. - * - * @param {Object} props - The properties object. - * @param {number} props.x - The x-coordinate for the tick. - * @param {number} props.y - The y-coordinate for the tick. - * @param {Object} props.payload - The payload object containing tick data. - * @param {number} props.index - The index of the tick. - * @returns {JSX.Element|null} The rendered tick component or null for the first tick. - */ -export const PercentTick = ({ x, y, payload, index }) => { - const theme = useTheme(); - if (index === 0) return null; - return ( - - {`${(payload?.value * 100).toFixed()}%`} - - ); -}; - -PercentTick.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - payload: PropTypes.object, - index: PropTypes.number, -}; - -/** - * Converts a decimal value to a formatted percentage string. - * - * @param {number} value - The decimal value to convert (e.g., 0.75) - * @returns {string} Formatted percentage string (e.g., "75.00%") or original input if not a number - * - * @example - * getFormattedPercentage(0.7543) // Returns "75.43%" - * getFormattedPercentage(1) // Returns "100.00%" - * getFormattedPercentage("test") // Returns "test" - */ -const getFormattedPercentage = (value) => { - if (typeof value !== "number") return value; - return `${(value * 100).toFixed(2)}%`; -}; - -/** - * Custom tick component for rendering network bytes per second. - * - * @param {Object} props - The properties object. - * @param {number} props.x - The x-coordinate for the tick. - * @param {number} props.y - The y-coordinate for the tick. - * @param {Object} props.payload - The payload object containing tick data. - * @param {number} props.index - The index of the tick. - * @returns {JSX.Element|null} The rendered tick component or null for the first tick. - */ -export const NetworkTick = ({ x, y, payload, index, formatter }) => { - const theme = useTheme(); - if (index === 0) return null; - - if (formatter === undefined) { - formatter = (value, space = false) => { - if (typeof value !== "number") return value; - // need to add space between value and unit - return `${(value / 1024).toFixed(1)}${space ? " " : ""}Kbps`; - }; - } - - return ( - - {formatter(payload?.value, true)} - - ); -}; - -NetworkTick.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - payload: PropTypes.object, - index: PropTypes.number, - formatter: PropTypes.func, -}; - -/** - * Custom tooltip component for displaying infrastructure data. - * - * @param {Object} props - The properties object. - * @param {boolean} props.active - Indicates if the tooltip is active. - * @param {Array} props.payload - The payload array containing tooltip data. - * @param {string} props.label - The label for the tooltip. - * @param {string} props.yKey - The key for the y-axis data. - * @param {string} props.yLabel - The label for the y-axis data. - * @param {string} props.dotColor - The color of the dot in the tooltip. - * @returns {JSX.Element|null} The rendered tooltip component or null if inactive. - */ -export const InfrastructureTooltip = ({ - active, - payload, - label, - yKey, - yIdx = -1, - yLabel, - dotColor, - dateRange, - formatter = getFormattedPercentage, -}) => { - const uiTimezone = useSelector((state) => state.ui.timezone); - const theme = useTheme(); - - const format = tooltipDateFormatLookup(dateRange); - if (active && payload && payload.length) { - const [hardwareType, metric] = yKey.split("."); - return ( - - - {formatDateWithTz(label, format, uiTimezone)} - - - - - - {yIdx >= 0 - ? `${yLabel} ${formatter(payload[0].payload[hardwareType][yIdx][metric])}` - : `${yLabel} ${formatter(payload[0].payload[yKey])}`} - - - - {/* Display original value */} - - ); - } - return null; -}; - -InfrastructureTooltip.propTypes = { - active: PropTypes.bool, - payload: PropTypes.array, - label: PropTypes.oneOfType([ - PropTypes.instanceOf(Date), - PropTypes.string, - PropTypes.number, - ]), - yKey: PropTypes.string, - yIdx: PropTypes.number, - yLabel: PropTypes.string, - dotColor: PropTypes.string, - dateRange: PropTypes.string, -}; - -export const TemperatureTooltip = ({ - active, - payload, - label, - keys, - dotColor, - dateRange, -}) => { - const uiTimezone = useSelector((state) => state.ui.timezone); - const theme = useTheme(); - const format = tooltipDateFormatLookup(dateRange); - const formatCoreKey = (key) => { - return key.replace(/^core(\d+)$/, "Core $1"); - }; - if (active && payload && payload.length) { - return ( - - - {formatDateWithTz(label, format, uiTimezone)} - - - - {keys.map((key) => { - return ( - - - - - - {`${formatCoreKey(key)}: ${payload[0].payload[key]} °C`} - - - - - ); - })} - - - ); - } - return null; -}; - -TemperatureTooltip.propTypes = { - active: PropTypes.bool, - keys: PropTypes.array, - payload: PropTypes.array, - label: PropTypes.oneOfType([ - PropTypes.instanceOf(Date), - PropTypes.string, - PropTypes.number, - ]), - dotColor: PropTypes.string, - dateRange: PropTypes.string, -}; diff --git a/client/src/Components/v1/Charts/Utils/gradientUtils.jsx b/client/src/Components/v1/Charts/Utils/gradientUtils.jsx deleted file mode 100644 index b5920374d..000000000 --- a/client/src/Components/v1/Charts/Utils/gradientUtils.jsx +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Creates an SVG gradient definition for use in charts - * @param {Object} params - The gradient parameters - * @param {string} [params.id="colorUv"] - Unique identifier for the gradient - * @param {string} params.startColor - Starting color of the gradient (hex, rgb, or color name) - * @param {string} params.endColor - Ending color of the gradient (hex, rgb, or color name) - * @param {number} [params.startOpacity=0.8] - Starting opacity (0-1) - * @param {number} [params.endOpacity=0] - Ending opacity (0-1) - * @param {('vertical'|'horizontal')} [params.direction="vertical"] - Direction of the gradient - * @returns {JSX.Element} SVG gradient definition element - * @example - * createCustomGradient({ - * startColor: "#1976D2", - * endColor: "#42A5F5", - * direction: "horizontal" - * }) - */ - -export const createGradient = ({ - id, - startColor, - endColor, - startOpacity = 0.8, - endOpacity = 0, - direction = "vertical", // or "horizontal" -}) => ( - - - - - - -); diff --git a/client/src/Components/v1/Check/Check.jsx b/client/src/Components/v1/Check/Check.jsx deleted file mode 100644 index c40f3de3e..000000000 --- a/client/src/Components/v1/Check/Check.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import "./check.css"; -import PropTypes from "prop-types"; -import Icon from "../Icon"; -import { Box, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * `Check` is a functional React component that displays a check icon and a label. - * - * @component - * @param {Object} props - The properties that define the `Check` component. - * @param {string} props.text - The text to be displayed as the label next to the check icon. - * @param {'info' | 'error' | 'success'} [props.variant='info'] - The variant of the check component, affecting its styling. - * @param {boolean} [props.outlined] - Whether the check icon should be outlined or not. - * - * @example - * // To use this component, import it and use it in your JSX like this: - * - * - * @returns {React.Element} The `Check` component with a check icon and a label, defined by the `text` prop. - */ -const Check = ({ text, noHighlightText, variant = "info", outlined = false }) => { - const theme = useTheme(); - const colors = { - success: theme.palette.success.main, - error: theme.palette.error.main, - info: theme.palette.info.border, - }; - return ( - - {outlined ? ( - - ) : ( - - )} - - {noHighlightText && ( - - {noHighlightText} - - )}{" "} - {text} - - - ); -}; - -Check.propTypes = { - text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - noHighlightText: PropTypes.string, - variant: PropTypes.oneOf(["info", "error", "success"]), - outlined: PropTypes.bool, -}; - -export default Check; diff --git a/client/src/Components/v1/Check/check.css b/client/src/Components/v1/Check/check.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Components/v1/CircularCount/index.jsx b/client/src/Components/v1/CircularCount/index.jsx deleted file mode 100644 index 8350a8c44..000000000 --- a/client/src/Components/v1/CircularCount/index.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Box } from "@mui/material"; -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -const CircularCount = ({ count }) => { - const theme = useTheme(); - return ( - - {count} - - ); -}; - -CircularCount.propTypes = { - count: PropTypes.number, -}; - -export default CircularCount; diff --git a/client/src/Components/v1/Common/AppBar.jsx b/client/src/Components/v1/Common/AppBar.jsx deleted file mode 100644 index 5cd3db5c1..000000000 --- a/client/src/Components/v1/Common/AppBar.jsx +++ /dev/null @@ -1,220 +0,0 @@ -import { useState } from "react"; -import { styled, alpha } from "@mui/material/styles"; -import Box from "@mui/material/Box"; -import AppBar from "@mui/material/AppBar"; -import Toolbar from "@mui/material/Toolbar"; -import Button from "@mui/material/Button"; -import IconButton from "@mui/material/IconButton"; -import Container from "@mui/material/Container"; -import MenuItem from "@mui/material/MenuItem"; -import Drawer from "@mui/material/Drawer"; -import Icon from "../Icon"; -import ThemeSwitch from "../ThemeSwitch/index.jsx"; -import { useTheme } from "@mui/material/styles"; -import { useSelector } from "react-redux"; -import { useLocation, useNavigate } from "react-router-dom"; - -const StyledToolbar = styled(Toolbar)(({ theme, mode }) => ({ - display: "flex", - alignItems: "center", - justifyContent: "space-between", - flexShrink: 0, - borderRadius: `calc(${theme.shape.borderRadius}px + 4px)`, - backdropFilter: "blur(24px)", - border: "1px solid", - borderColor: - mode === "light" - ? alpha(theme.palette.common.black, 0.1) - : alpha(theme.palette.common.white, 0.1), - backgroundColor: - mode === "light" - ? alpha(theme.palette.common.white, 0.4) - : alpha(theme.palette.common.black, 0.4), - boxShadow: theme.shadows[3], - padding: "8px 12px", -})); - -const StyledMenuItem = styled(MenuItem)(({ theme }) => ({ - fontSize: "1.1rem", - margin: theme.spacing(4, 2), -})); - -const AppAppBar = () => { - const [open, setOpen] = useState(false); - const theme = useTheme(); - const mode = useSelector((state) => state.ui.mode); - const location = useLocation(); - const navigate = useNavigate(); - - const logoSrc = - mode === "light" ? "/images/prism-black.png" : "/images/prism-white.png"; - - const toggleDrawer = (newOpen) => () => { - setOpen(newOpen); - }; - - const handleScroll = (id) => { - if (location.pathname === "/") { - const element = document.getElementById(id); - if (element) { - element.scrollIntoView({ behavior: "smooth" }); - } - } else { - navigate(`/#${id}`); - } - }; - - return ( - - - - - Prism Logo - - - - - - - - - {/* - */} - - - - - - - - - - - - - - Features - Testimonials - Highlights - FAQ - - Blog - - {/* - - - - - */} - - - - - - - - ); -}; - -export default AppAppBar; diff --git a/client/src/Components/v1/Common/Footer.jsx b/client/src/Components/v1/Common/Footer.jsx deleted file mode 100644 index 92f01a9ae..000000000 --- a/client/src/Components/v1/Common/Footer.jsx +++ /dev/null @@ -1,255 +0,0 @@ -import * as React from "react"; -import Box from "@mui/material/Box"; -import Container from "@mui/material/Container"; -import IconButton from "@mui/material/IconButton"; -import Link from "@mui/material/Link"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import { FaFacebook, FaLinkedin, FaGithub, FaTwitter, FaEnvelope } from "react-icons/fa"; - -function Copyright() { - return ( - - {"Copyright © "} - - UpRock - -   - {new Date().getFullYear()} - - ); -} - -export default function Footer() { - return ( - - -
- - Privacy Policy - - -  •  - - - Terms of Service - - -
- - - - - - - - - - - - - - - - - -
- - - - Made with ❤️ by  - - UpRock  - - &  - - Bluewave Labs - - - - - Built on  - - - - - - - - - - - - - - - - - - - - -  Solana - - - -
- ); -} diff --git a/client/src/Components/v1/ConfigRow/index.jsx b/client/src/Components/v1/ConfigRow/index.jsx deleted file mode 100644 index 12fea923e..000000000 --- a/client/src/Components/v1/ConfigRow/index.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Box, Stack, Typography } from "@mui/material"; -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import ConfigBox from "../ConfigBox/index.jsx"; - -/** - * ConfigRow is a styled container used to layout content in a row format with specific padding, border, and spacing. - * It serves as the wrapper for ConfigBox, with the left section displaying the title and description, - * and the right section displaying the children. - * - * @component - * @example - * return ( - * - *
Left content (Title + Description)
- *
Right content (Children)
- *
- * ); - */ - -const ConfigRow = ({ title, description, children }) => { - const theme = useTheme(); - - return ( - - - - {title} - - {description && ( - - {description} - - )} - - - {children} - - - ); -}; - -ConfigRow.propTypes = { - title: PropTypes.string.isRequired, - description: PropTypes.string, - children: PropTypes.node, -}; - -export default ConfigRow; diff --git a/client/src/Components/v1/Dot/index.jsx b/client/src/Components/v1/Dot/index.jsx deleted file mode 100644 index 0676d0a6f..000000000 --- a/client/src/Components/v1/Dot/index.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from "prop-types"; - -const Dot = ({ color = "gray", size = "4px", style }) => { - return ( - - ); -}; - -Dot.propTypes = { - color: PropTypes.string, - size: PropTypes.string, -}; - -export default Dot; diff --git a/client/src/Components/v1/Fallback/FallBackActionButtons.jsx b/client/src/Components/v1/Fallback/FallBackActionButtons.jsx deleted file mode 100644 index 144b16501..000000000 --- a/client/src/Components/v1/Fallback/FallBackActionButtons.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Button, Stack } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import { useNavigate } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import PropTypes from "prop-types"; - -const FallbackActionButtons = ({ link, type }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const { t } = useTranslation(); - - return ( - - - {type === "uptimeMonitor" && ( - - )} - - ); -}; -FallbackActionButtons.propTypes = { - title: PropTypes.string.isRequired, - link: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}; - -export default FallbackActionButtons; diff --git a/client/src/Components/v1/Fallback/FallbackBackground.jsx b/client/src/Components/v1/Fallback/FallbackBackground.jsx deleted file mode 100644 index 2182082fd..000000000 --- a/client/src/Components/v1/Fallback/FallbackBackground.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useTheme } from "@emotion/react"; -import Box from "@mui/material/Box"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import OutputAnimation from "../../../assets/Animations/output.gif"; -import DarkmodeOutput from "../../../assets/Animations/darkmodeOutput.gif"; -import { useSelector } from "react-redux"; -const FallbackBackground = () => { - const theme = useTheme(); - const mode = useSelector((state) => state.ui.mode); - return ( - <> - - - - - - ); -}; - -export default FallbackBackground; diff --git a/client/src/Components/v1/Fallback/FallbackCheckList.jsx b/client/src/Components/v1/Fallback/FallbackCheckList.jsx deleted file mode 100644 index befb95185..000000000 --- a/client/src/Components/v1/Fallback/FallbackCheckList.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@mui/material/styles"; -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Check from "../Check/Check.jsx"; -const FallbackCheckList = ({ checks, type }) => { - const theme = useTheme(); - return ( - - {checks?.map((check, index) => ( - - ))} - - ); -}; - -FallbackCheckList.propTypes = { - checks: PropTypes.arrayOf(PropTypes.string).isRequired, - title: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}; - -export default FallbackCheckList; diff --git a/client/src/Components/v1/Fallback/FallbackContainer.jsx b/client/src/Components/v1/Fallback/FallbackContainer.jsx deleted file mode 100644 index 7a2ec5fe0..000000000 --- a/client/src/Components/v1/Fallback/FallbackContainer.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { Box, Stack } from "@mui/material"; -import PropTypes from "prop-types"; - -const FallbackContainer = ({ children, type }) => { - const theme = useTheme(); - return ( - - - {children} - - - ); -}; - -FallbackContainer.propTypes = { - children: PropTypes.node, - type: PropTypes.string, -}; - -export default FallbackContainer; diff --git a/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx b/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx deleted file mode 100644 index 2cf52bd88..000000000 --- a/client/src/Components/v1/Fallback/FallbackPageSpeedWarning.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import Box from "@mui/material/Box"; -import { useTheme } from "@mui/material/styles"; -import Link from "@mui/material/Link"; -import { Link as RouterLink } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import Alert from "../Alert/index.jsx"; -import PropTypes from "prop-types"; - -const renderWarningMessage = (t) => { - return ( - <> - {t("pageSpeedWarning")}{" "} - - {t("pageSpeedLearnMoreLink")} - {" "} - {t("pageSpeedAddApiKey")} - - ); -}; - -const FallbackPageSpeedWarning = ({ settingsData }) => { - const theme = useTheme(); - const { t } = useTranslation(); - return ( - - svg": { - color: theme.palette.warningSecondary.contrastText, - }, - }, - }} - > - {settingsData?.pagespeedKeySet === false && ( - - )} - - - ); -}; - -FallbackPageSpeedWarning.propTypes = { - settingsData: PropTypes.shape({ - pagespeedKeySet: PropTypes.bool, - }), -}; - -export default FallbackPageSpeedWarning; diff --git a/client/src/Components/v1/Fallback/FallbackTitle.jsx b/client/src/Components/v1/Fallback/FallbackTitle.jsx deleted file mode 100644 index 69fee05c6..000000000 --- a/client/src/Components/v1/Fallback/FallbackTitle.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useTheme } from "@mui/material/styles"; -import { Typography } from "@mui/material"; -import PropTypes from "prop-types"; - -const FallbackTitle = ({ title }) => { - const theme = useTheme(); - return ( - - {title} - - ); -}; -FallbackTitle.propTypes = { - title: PropTypes.string.isRequired, -}; -export default FallbackTitle; diff --git a/client/src/Components/v1/Fallback/index.css b/client/src/Components/v1/Fallback/index.css deleted file mode 100644 index 6988fa5bf..000000000 --- a/client/src/Components/v1/Fallback/index.css +++ /dev/null @@ -1,40 +0,0 @@ -[class*="fallback__"] { - width: fit-content; - margin: auto; -} - -[class*="fallback__"] h1.MuiTypography-root { - font-size: var(--env-var-font-size-large); - font-weight: 600; -} - -[class*="fallback__"] button.MuiButtonBase-root, -[class*="fallback__"] .check { - width: max-content; -} - -[class*="fallback__"] button.MuiButtonBase-root { - min-height: 34px; -} - -[class*="fallback__"] .check span.MuiTypography-root, -[class*="fallback__"] button.MuiButtonBase-root { - font-size: var(--env-var-font-size-medium); -} - -[class*="fallback__"] .background-pattern-svg { - position: absolute; - top: 0; - left: 50%; - transform: translate(-50%, -50%); - z-index: 0; - - width: 100%; - max-width: 800px; - height: 100%; - max-height: 800px; - - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} diff --git a/client/src/Components/v1/Fallback/index.jsx b/client/src/Components/v1/Fallback/index.jsx deleted file mode 100644 index 40113f882..000000000 --- a/client/src/Components/v1/Fallback/index.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import { Box, Stack } from "@mui/material"; -import "./index.css"; -import FallbackTitle from "./FallbackTitle.jsx"; -import FallbackCheckList from "./FallbackCheckList.jsx"; -import FallbackActionButtons from "./FallBackActionButtons.jsx"; -import FallbackBackground from "./FallbackBackground.jsx"; -import FallbackContainer from "./FallbackContainer.jsx"; -/** - * Fallback component to display a fallback UI with a title, a list of checks, and a navigation button. - * - * @param {Object} props - The component props. - * @param {string} props.title - The title to be displayed in the fallback UI. - * @param {string} props.type - The type of the fallback (e.g., "pageSpeed", "notifications"). - * @param {Array} props.checks - An array of strings representing the checks to display. - * @param {string} [props.link="/"] - The link to navigate to. - * @param {boolean} [props.showPageSpeedWarning=false] - Whether to show the PageSpeed API warning. - * @returns {JSX.Element} The rendered fallback UI. - */ - -const Fallback = ({ title, checks, link = "/", isAdmin, type, children }) => { - const theme = useTheme(); - return ( - - - - - - - - {isAdmin && ( - - - {children} - - )} - - - ); -}; -Fallback.propTypes = { - title: PropTypes.string.isRequired, - checks: PropTypes.arrayOf(PropTypes.string).isRequired, - link: PropTypes.string, - isAdmin: PropTypes.bool, - showPageSpeedWarning: PropTypes.bool, - type: PropTypes.string.isRequired, - children: PropTypes.node, -}; -export default Fallback; diff --git a/client/src/Components/v1/GenericFallback/NetworkError.jsx b/client/src/Components/v1/GenericFallback/NetworkError.jsx deleted file mode 100644 index ddffa1c31..000000000 --- a/client/src/Components/v1/GenericFallback/NetworkError.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; - -const NetworkError = () => { - const theme = useTheme(); - const { t } = useTranslation(); - - return ( - <> - - {t("common.toasts.networkError")} - - {t("common.toasts.checkConnection")} - - ); -}; - -export default NetworkError; diff --git a/client/src/Components/v1/GenericFallback/index.jsx b/client/src/Components/v1/GenericFallback/index.jsx deleted file mode 100644 index 188c09f37..000000000 --- a/client/src/Components/v1/GenericFallback/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { Box, Stack } from "@mui/material"; -import OutputAnimation from "../../../assets/Animations/output.gif"; -import DarkmodeOutput from "../../../assets/Animations/darkmodeOutput.gif"; -import Background from "../../../assets/Images/background-grid.svg?react"; -import { useSelector } from "react-redux"; - -/** - * Fallback component to display a fallback UI for network errors - * - * @returns {JSX.Element} The rendered fallback UI. - */ - -const GenericFallback = ({ children }) => { - const theme = useTheme(); - const mode = useSelector((state) => state.ui.mode); - - return ( - - - - - - - - {children} - - - - ); -}; - -export default GenericFallback; diff --git a/client/src/Components/v1/HOC/withAdminCheck.jsx b/client/src/Components/v1/HOC/withAdminCheck.jsx index 8646c99b6..6343b2e4e 100644 --- a/client/src/Components/v1/HOC/withAdminCheck.jsx +++ b/client/src/Components/v1/HOC/withAdminCheck.jsx @@ -2,19 +2,19 @@ import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { logger } from "../../../Utils/Logger.js"; -import { networkService } from "../../../main.jsx"; +import { useLazyGet } from "@/Hooks/UseApi"; const withAdminCheck = (WrappedComponent) => { const WithAdminCheck = (props) => { const navigate = useNavigate(); - const [isChecking, setIsChecking] = useState(true); const [superAdminExists, setSuperAdminExists] = useState(false); + const [hasChecked, setHasChecked] = useState(false); + const { get: checkSuperAdmin, loading: isChecking } = useLazyGet(); useEffect(() => { - networkService - .doesSuperAdminExist() + checkSuperAdmin("/auth/users/superadmin") .then((response) => { - if (response?.data?.data === true) { + if (response?.data === true) { navigate("/login"); } else { setSuperAdminExists(false); @@ -24,11 +24,11 @@ const withAdminCheck = (WrappedComponent) => { logger.error(error); }) .finally(() => { - setIsChecking(false); + setHasChecked(true); }); - }, [navigate]); + }, [navigate, checkSuperAdmin]); - if (isChecking) { + if (!hasChecked || isChecking) { return null; } diff --git a/client/src/Components/v1/Heading/index.jsx b/client/src/Components/v1/Heading/index.jsx deleted file mode 100644 index 1a5f1a53b..000000000 --- a/client/src/Components/v1/Heading/index.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Typography } from "@mui/material"; -import PropTypes from "prop-types"; - -/** - * A heading component that renders text with a specific heading level. - * - * @param {Object} props - The properties passed to the Heading component. - * @param {('h1'|'h2'|'h3')} props.component - The heading level for the component. - * @param {Object} props.style - Custom styles to apply to the heading. - * @param {string} props.children - The content to display inside the heading. - * @returns {JSX.Element} The Typography component with specified heading properties. - */ - -function Heading({ component, style, children }) { - return ( - - {children} - - ); -} - -Heading.propTypes = { - component: PropTypes.oneOf(["h1", "h2", "h3"]).isRequired, - style: PropTypes.object, - children: PropTypes.string.isRequired, -}; -export { Heading }; diff --git a/client/src/Components/v1/Host/index.jsx b/client/src/Components/v1/Host/index.jsx deleted file mode 100644 index 6670722c1..000000000 --- a/client/src/Components/v1/Host/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import Dot from "../Dot/index.jsx"; -import { StatusLabel } from "@/Components/v1/Label/index.jsx"; -/** - * Host component. - * This subcomponent receives a params object and displays the host details. - * - * @component - * @param {Object} params - An object containing the following properties: - * @param {string} params.url - The URL of the host. - * @param {string} params.title - The name of the host. - * @param {string} params.percentageColor - The color of the percentage text. - * @param {number} params.percentage - The percentage to display. - * @returns {React.ElementType} Returns a div element with the host details. - */ -const Host = ({ url, title, percentageColor, percentage, showURL, status }) => { - const theme = useTheme(); - return ( - - - - {title} - - - {percentageColor && percentage && ( - <> - - - {percentage}% - - {status && ( - - )} - - )} - - {showURL && {url}} - - ); -}; - -Host.propTypes = { - title: PropTypes.string, - percentageColor: PropTypes.string, - percentage: PropTypes.string, - url: PropTypes.string, - showURL: PropTypes.bool, - status: PropTypes.string, -}; - -export default Host; diff --git a/client/src/Components/v1/HttpStatusLabel/index.jsx b/client/src/Components/v1/HttpStatusLabel/index.jsx deleted file mode 100644 index 2253e323e..000000000 --- a/client/src/Components/v1/HttpStatusLabel/index.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@mui/material"; -import { BaseLabel } from "../Label/index.jsx"; - -/** - * @component - * @param {Object} props - * @param {number} props.status - The http status for the label - * @param {Styles} props.customStyles - CSS Styles passed from parent component - * @returns {JSX.Element} - * @example - * // Render a http status label - * - */ - -const DEFAULT_CODE = 9999; // Default code for unknown status - -const handleStatusCode = (status) => { - if (status) { - return status; - } - return DEFAULT_CODE; -}; - -const getRoundedStatusCode = (status) => { - return Math.floor(status / 100) * 100; -}; - -const HttpStatusLabel = ({ status, customStyles }) => { - const theme = useTheme(); - const colors = { - 400: { - color: theme.palette.warning.main, - borderColor: theme.palette.warning.lowContrast, - }, - 500: { - color: theme.palette.error.main, - borderColor: theme.palette.error.lowContrast, - }, - default: { - color: theme.palette.primary.contrastText, - borderColor: theme.palette.primary.contrastText, - }, - }; - - const statusCode = handleStatusCode(status); - - const { borderColor, color } = - colors[getRoundedStatusCode(statusCode)] || colors.default; - return ( - - ); -}; - -HttpStatusLabel.propTypes = { - status: PropTypes.number, - customStyles: PropTypes.object, -}; - -export { HttpStatusLabel }; diff --git a/client/src/Components/v1/IconBox/index.jsx b/client/src/Components/v1/IconBox/index.jsx deleted file mode 100644 index bb9770e6a..000000000 --- a/client/src/Components/v1/IconBox/index.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Box, styled } from "@mui/material"; -import PropTypes from "prop-types"; - -/** - * IconBox - A styled box component for rendering icons with consistent sizing and styling - * - * @component - * @param {Object} [props] - Configuration options for the IconBox - * @param {number} [props.height=34] - Height of the icon box - * @param {number} [props.width=34] - Width of the icon box - * @param {number} [props.minWidth=34] - Minimum width of the icon box - * @param {number} [props.borderRadius=4] - Border radius of the icon box - * @param {number} [props.svgWidth=20] - Width of the SVG icon - * @param {number} [props.svgHeight=20] - Height of the SVG icon - * - * @example - * // Basic usage - * - * - * - * - * @example - * // Customized usage - * - * - * - * - * @returns {React.ReactElement} A styled box containing an icon - */ -const IconBox = styled(Box)( - ({ - theme, - height = 34, - width = 34, - minWidth = 34, - borderRadius = 4, - svgWidth = 20, - svgHeight = 20, - }) => ({ - height: height, - minWidth: minWidth, - width: width, - position: "relative", - border: 1, - borderStyle: "solid", - borderColor: theme.palette.primary.lowContrast, - borderRadius: borderRadius, - backgroundColor: theme.palette.tertiary.main, - "& svg": { - position: "absolute", - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - width: svgWidth, - height: svgHeight, - "& path": { - stroke: theme.palette.primary.contrastTextTertiary, - }, - }, - }) -); - -IconBox.propTypes = { - height: PropTypes.number, - width: PropTypes.number, - minWidth: PropTypes.number, - borderRadius: PropTypes.number, - svgWidth: PropTypes.number, - svgHeight: PropTypes.number, -}; - -export default IconBox; diff --git a/client/src/Components/v1/Image/index.jsx b/client/src/Components/v1/Image/index.jsx deleted file mode 100644 index 8f30f8a9e..000000000 --- a/client/src/Components/v1/Image/index.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import { Box } from "@mui/material"; -import PropTypes from "prop-types"; - -const isValidBase64Image = (data) => { - return /^[A-Za-z0-9+/=]+$/.test(data); -}; - -const Image = ({ - shouldRender = true, - src, - alt, - width = "auto", - height = "auto", - minWidth = "auto", - minHeight = "auto", - maxWidth = "auto", - maxHeight = "auto", - base64, - placeholder, - sx, -}) => { - if (shouldRender === false) { - return null; - } - - if (typeof src !== "undefined" && typeof base64 !== "undefined") { - console.warn("base64 takes precedence over src and overwrites it"); - } - - if (typeof base64 !== "undefined" && isValidBase64Image(base64)) { - src = `data:image/png;base64,${base64}`; - } - - if ( - typeof src === "undefined" && - typeof base64 === "undefined" && - typeof placeholder !== "undefined" - ) { - src = placeholder; - } - - return ( - - ); -}; - -Image.propTypes = { - shouldRender: PropTypes.bool, - src: PropTypes.string, - alt: PropTypes.string.isRequired, - width: PropTypes.string, - height: PropTypes.string, - minWidth: PropTypes.string, - minHeight: PropTypes.string, - maxWidth: PropTypes.string, - maxHeight: PropTypes.string, - base64: PropTypes.string, - sx: PropTypes.object, -}; - -export default Image; diff --git a/client/src/Components/v1/InfoBox/index.jsx b/client/src/Components/v1/InfoBox/index.jsx deleted file mode 100644 index 96e67975d..000000000 --- a/client/src/Components/v1/InfoBox/index.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import { RowContainer } from "../StandardContainer/index.jsx"; -import { Stack, Typography } from "@mui/material"; -import Image from "../Image/index.jsx"; - -import { useTheme } from "@mui/material/styles"; - -const InfoBox = ({ - img, - icon: Icon, - alt, - heading, - headingLevel = 2, - subHeading, - subHeadingLevel = "", - sx, -}) => { - const theme = useTheme(); - return ( - - {img && ( - {alt} - )} - {Icon && ( - - )} - - {heading} - - {subHeading} - - - - ); -}; - -export default InfoBox; diff --git a/client/src/Components/v1/MonitorCreateHeader/index.jsx b/client/src/Components/v1/MonitorCreateHeader/index.jsx deleted file mode 100644 index c3a979eb9..000000000 --- a/client/src/Components/v1/MonitorCreateHeader/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Stack, Button } from "@mui/material"; -import { useNavigate } from "react-router-dom"; -import PropTypes from "prop-types"; -import { useTranslation } from "react-i18next"; -import { useTheme } from "@emotion/react"; - -const CreateMonitorHeader = ({ isAdmin, label, isLoading = true, path, bulkPath }) => { - const navigate = useNavigate(); - const { t } = useTranslation(); - const theme = useTheme(); - - // Use the provided label or fall back to the translated default - - if (!isAdmin) return null; - - return ( - - - {/* {bulkPath && } */} - - ); -}; - -export default CreateMonitorHeader; - -CreateMonitorHeader.propTypes = { - isAdmin: PropTypes.bool.isRequired, - isLoading: PropTypes.bool, - path: PropTypes.string.isRequired, - label: PropTypes.string, - bulkPath: PropTypes.string, -}; diff --git a/client/src/Components/v1/MonitorCreateHeader/skeleton.jsx b/client/src/Components/v1/MonitorCreateHeader/skeleton.jsx deleted file mode 100644 index 7583f7849..000000000 --- a/client/src/Components/v1/MonitorCreateHeader/skeleton.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; - -const SkeletonLayout = () => { - return ( - - - - ); -}; - -export default SkeletonLayout; diff --git a/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx b/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx deleted file mode 100644 index 9d57edc41..000000000 --- a/client/src/Components/v1/MonitorTimeFrameHeader/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import SkeletonLayout from "./skeleton.jsx"; -import PropTypes from "prop-types"; - -const MonitorTimeFrameHeader = ({ - isLoading = false, - hasDateRange = true, - dateRange, - setDateRange, -}) => { - const theme = useTheme(); - - if (isLoading) { - return ; - } - - let timeFramePicker = null; - - if (hasDateRange) { - timeFramePicker = ( - - - - - - - ); - } - - return ( - - - Showing statistics for past{" "} - {dateRange === "recent" - ? "2 hours" - : dateRange === "day" - ? "24 hours" - : dateRange === "week" - ? "7 days" - : "30 days"} - . - - {timeFramePicker} - - ); -}; - -MonitorTimeFrameHeader.propTypes = { - isLoading: PropTypes.bool, - hasDateRange: PropTypes.bool, - dateRange: PropTypes.string, - setDateRange: PropTypes.func, -}; - -export default MonitorTimeFrameHeader; diff --git a/client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx b/client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx deleted file mode 100644 index d7c395d54..000000000 --- a/client/src/Components/v1/MonitorTimeFrameHeader/skeleton.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; - -const SkeletonLayout = () => { - return ( - - - - - ); -}; - -export default SkeletonLayout; diff --git a/client/src/Components/v1/PageStateWrapper/index.jsx b/client/src/Components/v1/PageStateWrapper/index.jsx deleted file mode 100644 index 08adbe514..000000000 --- a/client/src/Components/v1/PageStateWrapper/index.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import GenericFallback from "../GenericFallback/index.jsx"; -import Fallback from "../Fallback/index.jsx"; -import { Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; -import PropTypes from "prop-types"; -const PageStateWrapper = ({ - networkError, - isLoading, - items, - type, - fallbackLink, - fallbackChildren, - children, -}) => { - const theme = useTheme(); - const { t } = useTranslation(); - const isAdmin = useIsAdmin(); - const isEmpty = items === null || (Array.isArray(items) && items.length === 0); - const hasInitialized = items !== undefined && !(isLoading && isEmpty); - const showEmpty = !isLoading && isEmpty; - if (networkError) { - return ( - - - {t("common.toasts.networkError")} - - {t("common.toasts.checkConnection")} - - ); - } - if (!hasInitialized) { - return null; - } - if (showEmpty) { - return ( - - {fallbackChildren} - - ); - } - return <>{children}; -}; - -PageStateWrapper.propTypes = { - networkError: PropTypes.any, - isLoading: PropTypes.bool, - items: PropTypes.oneOfType([PropTypes.array, PropTypes.oneOf([null])]), - type: PropTypes.string, - fallbackLink: PropTypes.string, - fallbackChildren: PropTypes.node, - children: PropTypes.node, -}; - -export default PageStateWrapper; diff --git a/client/src/Components/v1/ProgressBars/index.css b/client/src/Components/v1/ProgressBars/index.css deleted file mode 100644 index 9b4ab2c7d..000000000 --- a/client/src/Components/v1/ProgressBars/index.css +++ /dev/null @@ -1,10 +0,0 @@ -.progress-bar-container h2.MuiTypography-root, -.progress-bar-container p.MuiTypography-root { - font-size: var(--env-var-font-size-medium); -} -.progress-bar-container p.MuiTypography-root:has(span) { - font-size: 12px; -} -.progress-bar-container p.MuiTypography-root span { - padding-left: 2px; -} diff --git a/client/src/Components/v1/ProgressBars/index.jsx b/client/src/Components/v1/ProgressBars/index.jsx deleted file mode 100644 index 86cedcbaa..000000000 --- a/client/src/Components/v1/ProgressBars/index.jsx +++ /dev/null @@ -1,180 +0,0 @@ -import { useTheme } from "@emotion/react"; -import PropTypes from "prop-types"; -import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material"; -import Icon from "../Icon"; -import "./index.css"; - -/** - * @param {Object} props - The component props. - * @param {JSX.Element} props.icon - The icon element to display (optional). - * @param {string} props.label - The label text for the progress item. - * @param {string} props.size - The size information for the progress item. - * @param {number} props.progress - The current progress value (0-100). - * @param {function} props.onClick - The function to handle click events on the remove button. - * @param {string} props.error - Error message to display if there's an error (optional). - * @returns {JSX.Element} The rendered component. - */ - -const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) => { - const theme = useTheme(); - - return ( - .MuiStack-root > svg": { - fill: theme.palette.error.contrastText, - width: "20px", - height: "20px", - }, - }, - }} - > - - {error ? ( - - ) : icon ? ( - - {icon} - - ) : ( - "" - )} - {error ? ( - - {error} - - ) : ( - - - {error ? error : label} - - - {!error && size} - - - )} - - - - - {!error ? ( - - - - - - {progress} - % - - - ) : ( - "" - )} - - ); -}; - -ProgressUpload.propTypes = { - icon: PropTypes.element, // JSX element for the icon (optional) - label: PropTypes.string, // Label text for the progress item - size: PropTypes.string.isRequired, // Size information for the progress item - progress: PropTypes.number.isRequired, // Current progress value (0-100) - onClick: PropTypes.func.isRequired, // Function to handle click events on the remove button - error: PropTypes.string, // Error message to display if there's an error (optional) -}; - -export default ProgressUpload; diff --git a/client/src/Components/v1/ProgressStepper/index.css b/client/src/Components/v1/ProgressStepper/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Components/v1/ProgressStepper/index.jsx b/client/src/Components/v1/ProgressStepper/index.jsx deleted file mode 100644 index 9fb0d15dc..000000000 --- a/client/src/Components/v1/ProgressStepper/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { Step, Stepper, StepLabel, Typography } from "@mui/material"; -import Icon from "../Icon"; -import PropTypes from "prop-types"; - -const CustomStepIcon = (props) => { - const { completed, active } = props; - return completed ? ( - - ) : ( - - ); -}; - -CustomStepIcon.propTypes = { - completed: PropTypes.bool.isRequired, - active: PropTypes.bool.isRequired, -}; - -/** - * @component - * @param {Object} props - * @param {Array} props.steps - */ - -const ProgressStepper = ({ steps }) => { - const [activeStep, setActiveStep] = React.useState(1); - return ( - - {steps.map((step, index) => { - const color = activeStep === index ? "primary" : "inherit"; - return ( - setActiveStep(index)} - > - - - {step.label} - - - - {step.content} - - - ); - })} - - ); -}; - -ProgressStepper.propTypes = { - steps: PropTypes.array.isRequired, -}; - -export default ProgressStepper; diff --git a/client/src/Components/v1/ShareComponent/index.jsx b/client/src/Components/v1/ShareComponent/index.jsx deleted file mode 100644 index 476b3672f..000000000 --- a/client/src/Components/v1/ShareComponent/index.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import html2canvas from "html2canvas"; -import Icon from "../Icon"; -import { Button } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => { - const theme = useTheme(); - const captureAndShare = async () => { - try { - // Temporarily apply styles directly to the element - const originalBackground = elementToCapture.current.style.background; - const originalPadding = elementToCapture.current.style.padding; - - elementToCapture.current.style.background = `radial-gradient(circle, ${theme.palette.gradient.color1}, ${theme.palette.gradient.color2}, ${theme.palette.gradient.color3}, ${theme.palette.gradient.color4}, ${theme.palette.gradient.color5})`; - elementToCapture.current.style.padding = `${theme.spacing(20)}`; - - // Capture the element directly - const canvas = await html2canvas(elementToCapture.current, { - useCORS: true, - scale: 2, - allowTaint: true, - backgroundColor: null, - }); - - // Restore original styles - elementToCapture.current.style.background = originalBackground; - elementToCapture.current.style.padding = originalPadding; - - const imageBlob = await new Promise((resolve) => - canvas.toBlob(resolve, "image/png") - ); - - const file = new File([imageBlob], `${fileName}.png`, { - type: "image/png", - }); - - if (navigator.share) { - await navigator.share({ - files: [file], - title: "Screenshot", - text: "Check out this screenshot!", - }); - } else { - const url = URL.createObjectURL(imageBlob); - const a = document.createElement("a"); - a.href = url; - a.download = `${fileName}.png`; - a.click(); - URL.revokeObjectURL(url); - } - } catch (error) { - console.error(error); - } - }; - return ( - - ); -}; - -export default ShareComponent; diff --git a/client/src/Components/v1/Skeletons/FullPage/index.jsx b/client/src/Components/v1/Skeletons/FullPage/index.jsx deleted file mode 100644 index 233fc3c06..000000000 --- a/client/src/Components/v1/Skeletons/FullPage/index.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; - -export const SkeletonLayout = () => { - return ( - - - - ); -}; - -export default SkeletonLayout; diff --git a/client/src/Components/v1/StandardContainer/index.jsx b/client/src/Components/v1/StandardContainer/index.jsx deleted file mode 100644 index c736b3ecc..000000000 --- a/client/src/Components/v1/StandardContainer/index.jsx +++ /dev/null @@ -1,51 +0,0 @@ -// Components -import { Stack } from "@mui/material"; - -// Utils -import { useTheme } from "@emotion/react"; - -const Container = ({ children, direction, backgroundColor, sx }) => { - const theme = useTheme(); - let bgColor = - typeof backgroundColor !== "undefined" - ? backgroundColor - : theme.palette.background.main; - return ( - - {children} - - ); -}; - -export const ColContainer = ({ children, backgroundColor, sx }) => { - return ( - - {children} - - ); -}; - -export const RowContainer = ({ children, backgroundColor, sx }) => { - return ( - - {children} - - ); -}; diff --git a/client/src/Components/v1/StatBox/index.jsx b/client/src/Components/v1/StatBox/index.jsx deleted file mode 100644 index e72f947b3..000000000 --- a/client/src/Components/v1/StatBox/index.jsx +++ /dev/null @@ -1,149 +0,0 @@ -import { Stack, Typography } from "@mui/material"; -import Image from "../Image/index.jsx"; -import { useTheme } from "@mui/material/styles"; -import PropTypes from "prop-types"; -import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js"; - -/** - * StatBox Component - * - * A reusable component that displays a statistic with a heading and subheading - * in a styled box with a gradient background. - * - * @component - * @param {Object} props - The component props - * @param {string} props.heading - The primary heading/title of the statistic - * @param {string|React.ReactNode} props.subHeading - The value or description of the statistic - * @param {boolean} [props.gradient=false] - Determines if the box should have a gradient background - * @param {string} [props.status] - The status of the statistic - * @param {Object} [props.sx] - Additional custom styling to be applied to the box - * - * @example - * return ( - * - * ) - * - * @returns {React.ReactElement} A styled box containing the statistic - */ - -const StatBox = ({ - img, - icon: Icon, - alt, - heading, - subHeading, - gradient = false, - status = "", - sx, -}) => { - const theme = useTheme(); - const { statusToTheme } = useMonitorUtils(); - const themeColor = statusToTheme[status]; - - const statusBoxStyles = gradient - ? { - background: `linear-gradient(to bottom right, ${theme.palette[themeColor].main} 30%, ${theme.palette[themeColor].lowContrast} 70%)`, - borderColor: theme.palette[themeColor].lowContrast, - } - : { - background: `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`, - borderColor: theme.palette.primary.lowContrast, - }; - - const headingStyles = gradient - ? { - color: theme.palette[themeColor].contrastText, - } - : { - color: theme.palette.primary.contrastTextSecondary, - }; - - const spanFixedStyles = { marginLeft: theme.spacing(2), fontSize: 15, fontWeight: 600 }; - const detailTextStyles = gradient - ? { - color: theme.palette[themeColor].contrastText, - "& span": { - color: theme.palette[themeColor].contrastText, - ...spanFixedStyles, - }, - } - : { - color: theme.palette.primary.contrastText, - "& span": { - color: theme.palette.primary.contrastTextTertiary, - ...spanFixedStyles, - }, - }; - - const responsiveWidths = { - default: `calc(25% - (3 * ${theme.spacing(8)} / 4))`, - md: `calc(50% - (1 * ${theme.spacing(8)} / 2))`, - }; - - return ( - - {img && ( - {alt} - )} - {Icon && ( - - )} - - {heading} - {subHeading} - - - ); -}; - -StatBox.propTypes = { - heading: PropTypes.string, - subHeading: PropTypes.node, - gradient: PropTypes.bool, - status: PropTypes.string, - sx: PropTypes.object, - icon: PropTypes.elementType, - img: PropTypes.node, - alt: PropTypes.string, -}; - -export default StatBox; diff --git a/client/src/Components/v1/StatusBoxes/index.jsx b/client/src/Components/v1/StatusBoxes/index.jsx deleted file mode 100644 index a78337ba7..000000000 --- a/client/src/Components/v1/StatusBoxes/index.jsx +++ /dev/null @@ -1,37 +0,0 @@ -// Components -import { Stack } from "@mui/material"; -import SkeletonLayout from "./skeleton.jsx"; -// Utils -import { useTheme } from "@mui/material/styles"; -import PropTypes from "prop-types"; -const StatusBoxes = ({ shouldRender = true, flexWrap = "nowrap", children }) => { - const theme = useTheme(); - if (!shouldRender) { - return ( - - ); - } - - return ( - - {children} - - ); -}; - -StatusBoxes.propTypes = { - shouldRender: PropTypes.bool, - flexWrap: PropTypes.string, - children: PropTypes.node, -}; - -export default StatusBoxes; diff --git a/client/src/Components/v1/StatusBoxes/skeleton.jsx b/client/src/Components/v1/StatusBoxes/skeleton.jsx deleted file mode 100644 index bc8b18251..000000000 --- a/client/src/Components/v1/StatusBoxes/skeleton.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Stack, Skeleton } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import PropTypes from "prop-types"; -const SkeletonLayout = ({ numBoxes, flexWrap }) => { - const theme = useTheme(); - return ( - - {Array.from({ length: numBoxes }).map((_, index) => { - const width = `${200 / numBoxes}%`; - return ( - - ); - })} - - ); -}; - -SkeletonLayout.propTypes = { - flexWrap: PropTypes.string, - numBoxes: PropTypes.number, -}; - -export default SkeletonLayout; diff --git a/client/src/Components/v1/Subheader/index.jsx b/client/src/Components/v1/Subheader/index.jsx deleted file mode 100644 index a76696567..000000000 --- a/client/src/Components/v1/Subheader/index.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import { Stack, Typography } from "@mui/material"; - -/** - - * - * @component - * @example - * - * @param {boolean} props.shouldRender - Whether to render the subheader - * @param {string} props.direction - Direction of the subheader - * @param {string} props.headerText - Header text - * @param {number} props.headerLevel - Font characteristic of the header - * @param {string} props.subHeaderText - Subheader text - * @param {number} props.subHeaderLevel - Font characteristic of the subheader - * @param {string} props.alignItems - Align items - * @param {node} props.children - Children - * @returns {JSX.Element} The rendered component - */ - -const SubHeader = ({ - shouldRender = true, - direction = "row", - headerText, - headerLevel = 1, - subHeaderText, - subHeaderLevel = 1, - alignItems = "center", - children, - ...props -}) => { - const theme = useTheme(); - - if (!shouldRender) { - return null; - } - - return ( - - - - - {headerText} - - - - {subHeaderText} - - - {children} - - ); -}; - -SubHeader.propTypes = { - shouldRender: PropTypes.bool, - direction: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - headerText: PropTypes.string, - headerLevel: PropTypes.number, - subHeaderText: PropTypes.string, - subHeaderLevel: PropTypes.number, - alignItems: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - children: PropTypes.node, -}; - -export default SubHeader; diff --git a/client/src/Components/v1/Tab/index.jsx b/client/src/Components/v1/Tab/index.jsx deleted file mode 100644 index 7609904d9..000000000 --- a/client/src/Components/v1/Tab/index.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Box, useTheme } from "@mui/material"; -import { TabList } from "@mui/lab"; -import PropTypes from "prop-types"; - -/** - * CustomTabList component - * @param {string} value - The currently selected tab's value. - * @param {function} onChange - Callback when a different tab is selected. - * @param {React.ReactNode} children - Tab components to render inside the TabList. - * @param {object} props - Additional props passed to the TabList component. - */ - -const CustomTabList = ({ value, onChange, children, ...props }) => { - const theme = useTheme(); - - return ( - - - {children} - - - ); -}; - -CustomTabList.propTypes = { - value: PropTypes.string, - onChange: PropTypes.func, - children: PropTypes.node, -}; - -export default CustomTabList; diff --git a/client/src/Components/v1/Table/TablePagination/Actions/index.jsx b/client/src/Components/v1/Table/TablePagination/Actions/index.jsx deleted file mode 100644 index 0df62878e..000000000 --- a/client/src/Components/v1/Table/TablePagination/Actions/index.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import PropTypes from "prop-types"; -import { Box, Button } from "@mui/material"; -import LeftArrow from "../../../ArrowLeft/index.jsx"; -import RightArrow from "../../../ArrowRight/index.jsx"; -import { useTheme } from "@emotion/react"; - -TablePaginationActions.propTypes = { - count: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, - rowsPerPage: PropTypes.number.isRequired, - onPageChange: PropTypes.func.isRequired, -}; - -/** - * Component for pagination actions (first, previous, next, last). - * - * @component - * @param {Object} props - * @param {number} props.count - Total number of items. - * @param {number} props.page - Current page number. - * @param {number} props.rowsPerPage - Number of rows per page. - * @param {function} props.onPageChange - Callback function to handle page change. - * - * @returns {JSX.Element} Pagination actions component. - */ - -function TablePaginationActions({ count, page, rowsPerPage, onPageChange }) { - const handleFirstPageButtonClick = (event) => { - onPageChange(event, 0); - }; - const handleBackButtonClick = (event) => { - onPageChange(event, page - 1); - }; - const handleNextButtonClick = (event) => { - onPageChange(event, page + 1); - }; - const handleLastPageButtonClick = (event) => { - onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); - }; - - const theme = useTheme(); - - return ( - - - - - - - ); -} - -export { TablePaginationActions }; diff --git a/client/src/Components/v1/Table/TablePagination/index.jsx b/client/src/Components/v1/Table/TablePagination/index.jsx deleted file mode 100644 index 77ee5ded1..000000000 --- a/client/src/Components/v1/Table/TablePagination/index.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; -import { Stack, TablePagination, Typography } from "@mui/material"; -import { TablePaginationActions } from "./Actions/index.jsx"; -import Icon from "../../Icon"; - -Pagination.propTypes = { - paginationLabel: PropTypes.string, // Label for the pagination. - itemCount: PropTypes.number, // Total number of items for pagination. - page: PropTypes.number, // Current page index. - rowsPerPage: PropTypes.number, // Number of rows displayed per page. - handleChangePage: PropTypes.func.isRequired, // Function to handle page changes. - handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page. -}; - -const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25]; - -// Determine whether pagination buttons should be shown -const shouldShowPaginationButtons = (itemCount, rowsPerPage) => { - return Math.ceil(itemCount / rowsPerPage) > 1; -}; - -// Empty action component for pagination (no-op component) -const EmptyPaginationActions = () =>
; - -/** - * Pagination component for table navigation with customized styling and behavior. - * - * @param {object} props - Component properties. - * @param {string} props.paginationLabel - Label for the pagination. - * @param {number} props.monitorCount - Total number of monitors to paginate. - * @param {number} props.page - Current page index (0-based). - * @param {number} props.rowsPerPage - Number of rows to display per page. - * @param {function} props.handleChangePage - Callback for handling page changes. - * @param {function} props.handleChangeRowsPerPage - Callback for handling changes to rows per page. - * @returns {JSX.Element} The Pagination component. - */ -function Pagination({ - paginationLabel, - itemCount = 0, - page = 0, - rowsPerPage = 5, - handleChangePage, - handleChangeRowsPerPage, -}) { - const theme = useTheme(); - - const start = page * rowsPerPage + 1; - const end = Math.min(page * rowsPerPage + rowsPerPage, itemCount); - const range = `${start} - ${end}`; - - const showPaginationButtons = shouldShowPaginationButtons(itemCount, rowsPerPage); - - return ( - - - Showing {range} of {itemCount} {paginationLabel} - - - `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` - } - slotProps={{ - select: { - MenuProps: { - keepMounted: true, - disableScrollLock: true, - PaperProps: { - className: "pagination-dropdown", - sx: { - mt: 0, - mb: theme.spacing(2), - }, - }, - transformOrigin: { vertical: "bottom", horizontal: "left" }, - anchorOrigin: { vertical: "top", horizontal: "left" }, - sx: { - mt: theme.spacing(-2), - }, - }, - inputProps: { id: "pagination-dropdown" }, - IconComponent: () => ( - - ), - sx: { - ml: theme.spacing(4), - mr: theme.spacing(12), - minWidth: theme.spacing(20), - textAlign: "left", - "&.Mui-focused > div": { - backgroundColor: theme.palette.primary.main, - }, - "& .MuiSelect-icon": { - // Add this style override - position: "absolute", - right: 0, - top: "50%", - transform: "translateY(-50%)", - }, - }, - }, - }} - sx={{ - color: theme.palette.primary.contrastTextSecondary, - "& svg path": { - stroke: theme.palette.primary.contrastTextTertiary, - strokeWidth: 1.3, - }, - "& .MuiSelect-select": { - border: 1, - borderColor: theme.palette.primary.lowContrast, - borderRadius: theme.shape.borderRadius, - }, - }} - /> - - ); -} - -export default Pagination; diff --git a/client/src/Components/v1/Table/TableUtils.js b/client/src/Components/v1/Table/TableUtils.js deleted file mode 100644 index a9f95a5b7..000000000 --- a/client/src/Components/v1/Table/TableUtils.js +++ /dev/null @@ -1,11 +0,0 @@ -export const createHeaderFactory = (getCellSx = () => {}) => { - return ({ id, content, onClick = () => {}, render = () => {} }) => { - return { - id, - content, - onClick, - getCellSx, - render, - }; - }; -}; diff --git a/client/src/Components/v1/Table/index.jsx b/client/src/Components/v1/Table/index.jsx deleted file mode 100644 index 2113a2215..000000000 --- a/client/src/Components/v1/Table/index.jsx +++ /dev/null @@ -1,180 +0,0 @@ -import { - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, -} from "@mui/material"; -import Tooltip from "@mui/material/Tooltip"; -import SkeletonLayout from "./skeleton.jsx"; -import PropTypes from "prop-types"; -import { useTheme } from "@emotion/react"; - -/** - * @typedef {Object} Header - * @property {number|string} id - The unique identifier for the header. - * @property {React.ReactNode} content - The content to display in the header cell. - * @property {Function} onClick - A function to be called when this cell is clicked, receiving the event and row data as an argument. - * @property {Object} getCellSx - Function that takes a row and returns a style object for the table cell. - * @property {Function} render - A function to render the cell content for a given row. - */ - -/** - * @typedef {Object} Config - * @property {Function} onRowClick - A function to be called when a row is clicked, receiving the row data as an argument. - * @property {Object} rowSX - Style object for the table row. - */ - -/** - * DataTable component renders a table with headers and data. - * - * @param {Object} props - The component props. - * @param {Header[]} props.headers - An array of header objects, each containing an `id`, `content`, and `render` function. - * @param {Array} props.data - An array of data objects, each representing a row. - * @returns {JSX.Element} The rendered table component. - */ - -const DataTable = ({ - shouldRender = true, - headers = [], - data = [], - config = { - emptyView: "No data", - tooltipContent: null, - onRowClick: () => {}, - }, -}) => { - const theme = useTheme(); - if (!shouldRender) { - return ; - } - - if ((headers?.length ?? 0) === 0) { - return "No data"; - } - - return ( - - - - - {headers.map((header, index) => ( - - {header.content} - - ))} - - - - {(data?.length ?? 0) === 0 ? ( - - - {config.emptyView} - - - ) : ( - data.map((row) => { - const key = row.id || row._id || Math.random(); - return ( - { - return [popper.width / 2 + 20, -popper.height / 8]; - }, - }, - }, - ], - }, - }} - > - config.onRowClick(row) : null} - > - {headers.map((header, index) => { - return ( - header.onClick(e, row) : null} - sx={header.getCellSx ? header.getCellSx(row) : {}} - > - {header.render(row)} - - ); - })} - - - ); - }) - )} - -
-
- ); -}; - -DataTable.propTypes = { - shouldRender: PropTypes.bool, - headers: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - content: PropTypes.node.isRequired, - render: PropTypes.func.isRequired, - }) - ).isRequired, - data: PropTypes.array, - config: PropTypes.shape({ - onRowClick: PropTypes.func, - rowSX: PropTypes.object, - emptyView: PropTypes.node, - }), -}; - -export default DataTable; diff --git a/client/src/Components/v1/Table/skeleton.jsx b/client/src/Components/v1/Table/skeleton.jsx deleted file mode 100644 index 062d39156..000000000 --- a/client/src/Components/v1/Table/skeleton.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Skeleton } from "@mui/material"; - -const TableSkeleton = () => { - return ( - - ); -}; - -export default TableSkeleton; diff --git a/client/src/Features/Auth/authSlice.js b/client/src/Features/Auth/authSlice.js deleted file mode 100644 index d6c63b6a9..000000000 --- a/client/src/Features/Auth/authSlice.js +++ /dev/null @@ -1,262 +0,0 @@ -import { networkService } from "../../main"; -import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; -import axios from "axios"; - -const initialState = { - isLoading: false, - authToken: "", - user: "", - success: null, - msg: null, -}; - -export const register = createAsyncThunk( - "auth/register", - async ({ user, token }, thunkApi) => { - try { - const res = await networkService.registerUser(user, token); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const login = createAsyncThunk("auth/login", async (form, thunkApi) => { - try { - const res = await networkService.loginUser(form); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } -}); - -export const update = createAsyncThunk("auth/update", async (data, thunkApi) => { - const { localData: form } = data; - try { - const fd = new FormData(); - form.firstName && fd.append("firstName", form.firstName); - form.lastName && fd.append("lastName", form.lastName); - form.password && fd.append("password", form.password); - form.newPassword && fd.append("newPassword", form.newPassword); - if (form.file && form.file !== "") { - const imageResult = await axios.get(form.file, { - responseType: "blob", - }); - fd.append("profileImage", imageResult.data); - } - form.deleteProfileImage && fd.append("deleteProfileImage", form.deleteProfileImage); - - const res = await networkService.updateUser({ - form: fd, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } -}); - -export const deleteUser = createAsyncThunk("auth/delete", async (_, thunkApi) => { - try { - const res = await networkService.deleteUser(); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } -}); - -export const forgotPassword = createAsyncThunk( - "auth/forgotPassword", - async (form, thunkApi) => { - try { - const res = await networkService.forgotPassword(form); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const setNewPassword = createAsyncThunk( - "auth/setNewPassword", - async (data, thunkApi) => { - const { token, form } = data; - try { - await networkService.validateRecoveryToken({ recoveryToken: token }); - const res = await networkService.setNewPassword({ - recoveryToken: token, - form: form, - }); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -const handleAuthFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.authToken = action.payload.data.token; - state.user = action.payload.data.user; -}; -const handleAuthRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to login or register"; -}; -const handleUpdateFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.user = action.payload.data; -}; -const handleUpdateRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to update profile data."; -}; -const handleDeleteFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; -}; -const handleDeleteRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to delete account."; -}; -const handleForgotFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; -}; -const handleForgotRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to send reset instructions."; -}; -const handleNewPasswordRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to reset password."; -}; - -const authSlice = createSlice({ - name: "auth", - initialState, - reducers: { - clearAuthState: (state) => { - state.authToken = ""; - state.user = ""; - state.isLoading = false; - state.success = true; - state.msg = "Logged out successfully"; - }, - setAuthState: (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.authToken = action.payload.data.token; - state.user = action.payload.data.user; - }, - setUser: (state, action) => { - state.user = action.payload; - }, - }, - extraReducers: (builder) => { - // Register thunk - builder - .addCase(register.pending, (state) => { - state.isLoading = true; - }) - .addCase(register.fulfilled, handleAuthFulfilled) - .addCase(register.rejected, handleAuthRejected); - - // Login thunk - builder - .addCase(login.pending, (state) => { - state.isLoading = true; - }) - .addCase(login.fulfilled, handleAuthFulfilled) - .addCase(login.rejected, handleAuthRejected); - - // Update thunk - builder - .addCase(update.pending, (state) => { - state.isLoading = true; - }) - .addCase(update.fulfilled, handleUpdateFulfilled) - .addCase(update.rejected, handleUpdateRejected); - - // Delete thunk - builder - .addCase(deleteUser.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteUser.fulfilled, handleDeleteFulfilled) - .addCase(deleteUser.rejected, handleDeleteRejected); - - // Forgot password thunk - builder - .addCase(forgotPassword.pending, (state) => { - state.isLoading = true; - }) - .addCase(forgotPassword.fulfilled, handleForgotFulfilled) - .addCase(forgotPassword.rejected, handleForgotRejected); - - // Set new password thunk - builder - .addCase(setNewPassword.pending, (state) => { - state.isLoading = true; - }) - .addCase(setNewPassword.fulfilled, handleAuthFulfilled) - .addCase(setNewPassword.rejected, handleNewPasswordRejected); - }, -}); - -export default authSlice.reducer; -export const { clearAuthState, setAuthState, setUser } = authSlice.actions; diff --git a/client/src/Features/Auth/authSlice.ts b/client/src/Features/Auth/authSlice.ts new file mode 100644 index 000000000..aa4401c79 --- /dev/null +++ b/client/src/Features/Auth/authSlice.ts @@ -0,0 +1,53 @@ +import { createSlice, type PayloadAction } from "@reduxjs/toolkit"; +import type { User } from "@/Types/User"; + +interface AuthState { + isLoading: boolean; + authToken: string; + user: User | null; + success: boolean | null; + msg: string | null; +} + +const initialState: AuthState = { + isLoading: false, + authToken: "", + user: null, + success: null, + msg: null, +}; + +const authSlice = createSlice({ + name: "auth", + initialState, + reducers: { + clearAuthState: (state) => { + state.authToken = ""; + state.user = null; + state.isLoading = false; + state.success = true; + state.msg = "Logged out successfully"; + }, + setAuthState: ( + state, + action: PayloadAction<{ + success: boolean; + msg: string; + data: { token: string; user: User }; + }> + ) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.authToken = action.payload.data.token; + state.user = action.payload.data.user; + }, + setUser: (state, action: PayloadAction) => { + state.user = action.payload; + }, + }, +}); + +export type { AuthState }; +export default authSlice.reducer; +export const { clearAuthState, setAuthState, setUser } = authSlice.actions; diff --git a/client/src/Features/UI/uiSlice.js b/client/src/Features/UI/uiSlice.js index 014940a9a..0e8ca8608 100644 --- a/client/src/Features/UI/uiSlice.js +++ b/client/src/Features/UI/uiSlice.js @@ -4,8 +4,6 @@ const initialMode = window?.matchMedia?.("(prefers-color-scheme: dark)")?.matche ? "dark" : "light"; -// Initial state for UI settings. -// Add more settings as needed (e.g., theme preferences, user settings) const initialState = { monitors: { rowsPerPage: 10, diff --git a/client/src/Hooks/UseApi.ts b/client/src/Hooks/UseApi.ts index dfd256bdd..e1faa2b29 100644 --- a/client/src/Hooks/UseApi.ts +++ b/client/src/Hooks/UseApi.ts @@ -179,3 +179,37 @@ export const useDelete = () => { return { deleteFn, loading, error }; }; + +export const useLazyGet = () => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const { toastError } = useToast(); + + const getFn = async ( + endpoint: string, + config?: AxiosRequestConfig + ): Promise | null> => { + setLoading(true); + setError(null); + + try { + const res = await get>(endpoint, { + ...config, + headers: { + ...config?.headers, + }, + }); + return res.data; + } catch (err: any) { + console.error(err); + const errMsg = err?.response?.data?.msg || err.message || "An error occurred"; + toastError(errMsg); + setError(errMsg); + return null; + } finally { + setLoading(false); + } + }; + + return { get: getFn, loading, error }; +}; diff --git a/client/src/Hooks/checkHooks.js b/client/src/Hooks/checkHooks.js deleted file mode 100644 index 2d7edd677..000000000 --- a/client/src/Hooks/checkHooks.js +++ /dev/null @@ -1,193 +0,0 @@ -import { useState, useEffect } from "react"; -import { networkService } from "../main.jsx"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; - -const useFetchChecksTeam = ({ - status, - sortOrder, - limit, - dateRange, - filter, - ack, - page, - rowsPerPage, - enabled = true, - updateTrigger, -}) => { - const [checks, setChecks] = useState(undefined); - const [checksCount, setChecksCount] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [networkError, setNetworkError] = useState(false); - - useEffect(() => { - const fetchChecks = async () => { - if (!enabled) { - return; - } - - const config = { - status, - sortOrder, - limit, - dateRange, - filter, - ack, - page, - rowsPerPage, - }; - - try { - setIsLoading(true); - const res = await networkService.getChecksByTeam(config); - setChecks(res.data.data.checks); - setChecksCount(res.data.data.checksCount); - } catch (error) { - setNetworkError(true); - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - - fetchChecks(); - }, [ - status, - sortOrder, - limit, - dateRange, - filter, - ack, - page, - rowsPerPage, - enabled, - updateTrigger, - ]); - - return [checks, checksCount, isLoading, networkError]; -}; - -const useFetchChecksByMonitor = ({ - monitorId, - type, - status, - sortOrder, - limit, - dateRange, - filter, - ack, - page, - rowsPerPage, - enabled = true, - updateTrigger, -}) => { - const [checks, setChecks] = useState(undefined); - const [checksCount, setChecksCount] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [networkError, setNetworkError] = useState(false); - - useEffect(() => { - const fetchChecks = async () => { - if (!enabled || !type) { - return; - } - - const config = { - monitorId, - type, - status, - sortOrder, - limit, - dateRange, - filter, - ack, - page, - rowsPerPage, - }; - - try { - setIsLoading(true); - const res = await networkService.getChecksByMonitor(config); - setChecks(res.data.data.checks); - setChecksCount(res.data.data.checksCount); - } catch (error) { - setNetworkError(true); - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - - fetchChecks(); - }, [ - monitorId, - type, - status, - sortOrder, - limit, - dateRange, - filter, - ack, - page, - rowsPerPage, - enabled, - updateTrigger, - ]); - - return [checks, checksCount, isLoading, networkError]; -}; - -const useFetchChecksSummaryByTeamId = ({ dateRange, updateTrigger } = {}) => { - const [summary, setSummary] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [networkError, setNetworkError] = useState(false); - - useEffect(() => { - const fetchSummary = async () => { - try { - setIsLoading(true); - - const res = await networkService.getChecksAndSummaryByTeamId({ dateRange }); - setSummary(res.data.data); - } catch (error) { - setNetworkError(true); - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - - fetchSummary(); - }, [dateRange, updateTrigger]); - - return [summary, isLoading, networkError]; -}; - -const useResolveIncident = () => { - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); - - const resolveIncident = async (checkId, setUpdateTrigger) => { - try { - setIsLoading(true); - await networkService.updateCheckStatus({ - checkId, - ack: true, - }); - setUpdateTrigger((prev) => !prev); - } catch (error) { - createToast({ body: t("checkHooks.failureResolveOne") }); - } finally { - setIsLoading(false); - } - }; - - return [resolveIncident, isLoading]; -}; - -export { - useFetchChecksByMonitor, - useFetchChecksTeam, - useFetchChecksSummaryByTeamId, - useResolveIncident, -}; diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/inviteHooks.js deleted file mode 100644 index c862eec31..000000000 --- a/client/src/Hooks/inviteHooks.js +++ /dev/null @@ -1,65 +0,0 @@ -import { useState } from "react"; -import { networkService } from "../main.jsx"; -import { useTranslation } from "react-i18next"; - -const CLIENT_HOST = import.meta.env.VITE_APP_CLIENT_HOST; - -const useGetInviteToken = () => { - const { t } = useTranslation(); - - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - const [token, setToken] = useState(undefined); - - const clearToken = () => { - setToken(undefined); - }; - const fetchToken = async (email, role) => { - const response = await networkService.requestInvitationToken({ email, role }); - const token = response?.data?.data?.token; - if (typeof token === "undefined") { - throw new Error(t("inviteNoTokenFound")); - } - return token; - }; - const getInviteToken = async ({ email, role }) => { - try { - setIsLoading(true); - const token = await fetchToken(email, role); - let inviteLink = token; - - if (typeof CLIENT_HOST !== "undefined") { - inviteLink = `${CLIENT_HOST}/register/${token}`; - } - setToken(inviteLink); - return token; - } catch (error) { - setError(error); - } finally { - setIsLoading(false); - } - }; - - const addTeamMember = async (formData, role) => { - try { - setIsLoading(true); - const token = await fetchToken(formData.email, role); - const toSubmit = { - ...formData, - inviteToken: token, - }; - delete toSubmit.confirm; - const responseRegister = await networkService.registerUser(toSubmit); - return responseRegister; - } catch (error) { - setError(error); - throw error; - } finally { - setIsLoading(false); - } - }; - - return [getInviteToken, clearToken, isLoading, error, token, addTeamMember]; -}; - -export { useGetInviteToken }; diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/logHooks.js deleted file mode 100644 index d5162de5a..000000000 --- a/client/src/Hooks/logHooks.js +++ /dev/null @@ -1,111 +0,0 @@ -import { useState, useEffect } from "react"; -import { networkService } from "../main.jsx"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; - -const useFetchLogs = () => { - const { t } = useTranslation(); - const [logs, setLogs] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - useEffect(() => { - const fetchLogs = async () => { - try { - setIsLoading(true); - const response = await networkService.getLogs(); - setLogs(response.data.data); - } catch (error) { - setError(error); - createToast({ - message: error.message, - }); - } finally { - setIsLoading(false); - } - }; - fetchLogs(); - }, [t]); - return [logs, isLoading, error]; -}; - -const useFetchQueueData = (trigger) => { - const [jobs, setJobs] = useState(undefined); - const [metrics, setMetrics] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - useEffect(() => { - const fetchJobs = async () => { - try { - setIsLoading(true); - const response = await networkService.getQueueData(); - if (response.status === 200) { - setJobs(response.data.data.jobs); - setMetrics(response.data.data.metrics); - } - } catch (error) { - setError(error); - } finally { - setIsLoading(false); - } - }; - - fetchJobs(); - }, [trigger]); - - return [jobs, metrics, isLoading, error]; -}; - -const useFlushQueue = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - - const flushQueue = async (trigger, setTrigger) => { - try { - setIsLoading(true); - await networkService.flushQueue(); - createToast({ - body: "Queue flushed", - }); - } catch (error) { - setError(error); - createToast({ - body: error.message, - }); - } finally { - setIsLoading(false); - setTrigger(!trigger); - } - }; - return [flushQueue, isLoading, error]; -}; - -const useFetchDiagnostics = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - const [diagnostics, setDiagnostics] = useState(undefined); - - const fetchDiagnostics = async () => { - try { - setIsLoading(true); - const response = await networkService.getDiagnostics(); - setDiagnostics(response.data.data); - } catch (error) { - createToast({ - body: error.message, - }); - setError(error); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchDiagnostics(); - }, []); - - return [diagnostics, fetchDiagnostics, isLoading, error]; -}; - -export { useFetchLogs, useFetchQueueData, useFlushQueue, useFetchDiagnostics }; diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js deleted file mode 100644 index 006c5b76e..000000000 --- a/client/src/Hooks/monitorHooks.js +++ /dev/null @@ -1,170 +0,0 @@ -import { useEffect, useState } from "react"; -import { networkService } from "../main.jsx"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; - -export const useFetchMonitorsByTeamId = ({ types, filter, updateTrigger }) => { - const [isLoading, setIsLoading] = useState(false); - const [monitors, setMonitors] = useState(undefined); - const [networkError, setNetworkError] = useState(false); - - useEffect(() => { - const fetchMonitors = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorsByTeamId({ - types, - filter, - }); - if (res?.data?.data) { - setMonitors(res.data.data); - } - } catch (error) { - setNetworkError(true); - createToast({ - body: error.message, - }); - } finally { - setIsLoading(false); - } - }; - fetchMonitors(); - }, [types, filter, updateTrigger]); - return [monitors, isLoading, networkError]; -}; - -export const useDeleteMonitor = () => { - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); - const deleteMonitor = async (monitorId, successCallback = () => {}) => { - try { - setIsLoading(true); - await networkService.deleteMonitorById({ monitorId }); - successCallback(); - createToast({ body: t("monitorDeleted") }); - } catch (error) { - createToast({ body: t("failedDeleteMonitor") }); - } finally { - setIsLoading(false); - } - }; - return [deleteMonitor, isLoading]; -}; - -export const usePauseMonitor = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const { t } = useTranslation(); - - const pauseMonitor = async (monitorId, successCallback = () => {}) => { - try { - setIsLoading(true); - const res = await networkService.pauseMonitorById({ monitorId }); - successCallback(); - if (res.data.data.isActive === false) { - createToast({ body: t("monitorPaused") }); - } else { - createToast({ body: t("monitorResumed") }); - } - } catch (error) { - setError(error.message); - createToast({ body: t("failedPauseMonitor") }); - } finally { - setIsLoading(false); - } - }; - - return [pauseMonitor, isLoading, error]; -}; - -export const useAddDemoMonitors = () => { - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); - const addDemoMonitors = async () => { - try { - setIsLoading(true); - await networkService.addDemoMonitors(); - createToast({ body: t("monitorHooks.successAddDemoMonitors") }); - } catch (error) { - createToast({ body: t("monitorHooks.failureAddDemoMonitors") }); - } finally { - setIsLoading(false); - } - }; - return [addDemoMonitors, isLoading]; -}; - -export const useDeleteAllMonitors = () => { - const [isLoading, setIsLoading] = useState(false); - const { t } = useTranslation(); - const deleteAllMonitors = async () => { - try { - setIsLoading(true); - await networkService.deleteAllMonitors(); - createToast({ body: t("settingsMonitorsDeleted") }); - } catch (error) { - createToast({ body: t("settingsFailedToDeleteMonitors") }); - } finally { - setIsLoading(false); - } - }; - return [deleteAllMonitors, isLoading]; -}; - -export const useDeleteMonitorStats = () => { - const { t } = useTranslation(); - const [isLoading, setIsLoading] = useState(false); - const deleteMonitorStats = async () => { - setIsLoading(true); - try { - await networkService.deleteChecksByTeamId(); - createToast({ body: t("settingsStatsCleared") }); - } catch (error) { - createToast({ body: t("settingsFailedToClearStats") }); - } finally { - setIsLoading(false); - } - }; - - return [deleteMonitorStats, isLoading]; -}; - -export const useCreateBulkMonitors = () => { - const [isLoading, setIsLoading] = useState(false); - - const createBulkMonitors = async (file, user) => { - setIsLoading(true); - - const formData = new FormData(); - formData.append("csvFile", file); - - try { - const response = await networkService.createBulkMonitors(formData); - return [true, response.data, null]; - } catch (err) { - const errorMessage = err?.response?.data?.msg || err.message; - return [false, null, errorMessage]; - } finally { - setIsLoading(false); - } - }; - - return [createBulkMonitors, isLoading]; -}; - -export const useFetchJson = () => { - const [isLoading, setIsLoading] = useState(false); - const fetchJson = async () => { - try { - setIsLoading(true); - const res = await networkService.fetchJson(); - createToast({ body: "JSON fetched successfully" }); - return res?.data?.data ?? []; - } catch (error) { - createToast({ body: "Failed to create monitor." }); - } finally { - setIsLoading(false); - } - }; - return [fetchJson, isLoading]; -}; diff --git a/client/src/Hooks/settingsHooks.js b/client/src/Hooks/settingsHooks.js deleted file mode 100644 index 4693d8daa..000000000 --- a/client/src/Hooks/settingsHooks.js +++ /dev/null @@ -1,73 +0,0 @@ -import { useState, useEffect } from "react"; -import { networkService } from "../main.jsx"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; - -const useFetchSettings = ({ setSettingsData, setIsApiKeySet, setIsEmailPasswordSet }) => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - useEffect(() => { - const fetchSettings = async () => { - setIsLoading(true); - try { - const response = await networkService.getAppSettings(); - setSettingsData(response?.data?.data); - setIsApiKeySet(response?.data?.data?.pagespeedKeySet); - setIsEmailPasswordSet(response?.data?.data?.emailPasswordSet); - } catch (error) { - createToast({ body: "Failed to fetch settings" }); - setError(error); - } finally { - setIsLoading(false); - } - }; - fetchSettings(); - }, [setSettingsData]); - - return [isLoading, error]; -}; - -const useSaveSettings = ({ - setSettingsData, - setIsApiKeySet, - setApiKeyHasBeenReset, - setIsEmailPasswordSet, - setEmailPasswordHasBeenReset, -}) => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - const { t } = useTranslation(); - - const saveSettings = async (settings) => { - setIsLoading(true); - try { - const settingsResponse = await networkService.updateAppSettings({ settings }); - // if (settings.checkTTL) { - // await networkService.updateChecksTTL({ - // ttl: settings.checkTTL, - // }); - // } - setIsApiKeySet(settingsResponse.data.data.pagespeedKeySet); - setIsEmailPasswordSet(settingsResponse.data.data.emailPasswordSet); - if (settingsResponse.data.data.pagespeedKeySet === true) { - setApiKeyHasBeenReset(false); - } - if (settingsResponse.data.data.emailPasswordSet === true) { - setEmailPasswordHasBeenReset(false); - } - setSettingsData(settingsResponse.data.data); - - createToast({ body: t("settingsSuccessSaved") }); - } catch (error) { - createToast({ body: t("settingsFailedToSave") }); - - setError(error); - } finally { - setIsLoading(false); - } - }; - - return [isLoading, error, saveSettings]; -}; - -export { useFetchSettings, useSaveSettings }; diff --git a/client/src/Hooks/useIsAdmin.js b/client/src/Hooks/useIsAdmin.js deleted file mode 100644 index 8abf78ac9..000000000 --- a/client/src/Hooks/useIsAdmin.js +++ /dev/null @@ -1,17 +0,0 @@ -import { useSelector } from "react-redux"; - -const useIsAdmin = () => { - const { user } = useSelector((state) => state.auth); - const isAdmin = - (user?.role?.includes("admin") ?? false) || - (user?.role?.includes("superadmin") ?? false); - return isAdmin; -}; - -const useIsSuperAdmin = () => { - const { user } = useSelector((state) => state.auth); - const isSuperAdmin = user?.role?.includes("superadmin"); - return isSuperAdmin; -}; - -export { useIsAdmin, useIsSuperAdmin }; diff --git a/client/src/Hooks/useIsAdmin.ts b/client/src/Hooks/useIsAdmin.ts new file mode 100644 index 000000000..3c94cca46 --- /dev/null +++ b/client/src/Hooks/useIsAdmin.ts @@ -0,0 +1,18 @@ +import type { RootState } from "@/Types/state"; +import { useSelector } from "react-redux"; + +const useIsAdmin = (): boolean => { + const { user } = useSelector((state: RootState) => state.auth); + const isAdmin = + (user?.role?.includes("admin") ?? false) || + (user?.role?.includes("superadmin") ?? false); + return isAdmin; +}; + +const useIsSuperAdmin = (): boolean => { + const { user } = useSelector((state: RootState) => state.auth); + const isSuperAdmin = user?.role?.includes("superadmin") ?? false; + return isSuperAdmin; +}; + +export { useIsAdmin, useIsSuperAdmin }; diff --git a/client/src/Hooks/useMonitorUtils.js b/client/src/Hooks/useMonitorUtils.js deleted file mode 100644 index b57c11a12..000000000 --- a/client/src/Hooks/useMonitorUtils.js +++ /dev/null @@ -1,72 +0,0 @@ -import { useCallback } from "react"; -import { useTheme } from "@mui/material"; - -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 { - ...monitor, - 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"; - }, []); - - const theme = useTheme(); - - const statusColor = { - up: theme.palette.success.lowContrast, - down: theme.palette.error.lowContrast, - paused: theme.palette.warning.lowContrast, - pending: theme.palette.warning.lowContrast, - }; - - const statusToTheme = { - up: "success", - down: "error", - paused: "warning", - pending: "secondary", - "cannot resolve": "tertiary", - }; - - const pagespeedStatusMsg = { - up: "Live (collecting data)", - down: "Inactive", - paused: "Paused", - }; - - return { - getMonitorWithPercentage, - determineState, - statusColor, - statusToTheme, - pagespeedStatusMsg, - }; -}; - -export { useMonitorUtils }; diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/useNotifications.js deleted file mode 100644 index 6cf84e45f..000000000 --- a/client/src/Hooks/useNotifications.js +++ /dev/null @@ -1,58 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { networkService } from "../main.jsx"; -import { useTranslation } from "react-i18next"; - -const useGetNotificationsByTeamId = (updateTrigger) => { - const [notifications, setNotifications] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const { t } = useTranslation(); - - const getNotifications = useCallback(async () => { - try { - setIsLoading(true); - const response = await networkService.getNotificationsByTeamId(); - setNotifications(response?.data?.data ?? []); - } catch (error) { - setError(error); - createToast({ - body: t("notifications.fetch.failed"), - }); - } finally { - setIsLoading(false); - } - }, [t]); - - useEffect(() => { - getNotifications(); - }, [getNotifications, updateTrigger]); - - return [notifications, isLoading, error]; -}; - -const useTestAllNotifications = () => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(undefined); - const { t } = useTranslation(); - const testAllNotifications = async ({ monitorId }) => { - try { - setIsLoading(true); - await networkService.testAllNotifications({ monitorId }); - createToast({ - body: t("notifications.test.success"), - }); - } catch (error) { - createToast({ - body: error.response?.data?.msg || error.message, - }); - setError(error); - } finally { - setIsLoading(false); - } - }; - - return [testAllNotifications, isLoading, error]; -}; - -export { useGetNotificationsByTeamId, useTestAllNotifications }; diff --git a/client/src/Hooks/useSendTestEmail.js b/client/src/Hooks/useSendTestEmail.js deleted file mode 100644 index 7223a489a..000000000 --- a/client/src/Hooks/useSendTestEmail.js +++ /dev/null @@ -1,60 +0,0 @@ -import { useState } from "react"; -import { networkService } from "../main.jsx"; -import { useSelector } from "react-redux"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; - -const useSendTestEmail = () => { - const [isSending, setIsSending] = useState(false); - const [error, setError] = useState(null); - const user = useSelector((state) => state.auth.user); - const { t } = useTranslation(); - - /** - * Send a test email with optional email configuration - * @param {Object} emailConfig - Optional email configuration parameters - */ - const sendTestEmail = async (emailConfig = null) => { - try { - setIsSending(true); - setError(null); - - // Send the test email with or without configuration - const response = await networkService.sendTestEmail({ - to: user.email, - emailConfig, - }); - - if (typeof response?.data?.data?.messageId !== "undefined") { - createToast({ - body: t("settingsTestEmailSuccess", "Test email sent successfully"), - }); - } else { - throw new Error( - response?.data?.error || - t("settingsTestEmailFailed", "Failed to send test email") - ); - } - } catch (error) { - createToast({ - body: t("failedToSendEmail"), - }); - setError(error); - createToast({ - body: t( - "settingsTestEmailFailedWithReason", - "Failed to send test email: {{reason}}", - { - reason: error.message || t("settingsTestEmailUnknownError", "Unknown error"), - } - ), - variant: "error", - }); - } finally { - setIsSending(false); - } - }; - return [isSending, error, sendTestEmail]; -}; - -export { useSendTestEmail }; diff --git a/client/src/Hooks/userHooks.js b/client/src/Hooks/userHooks.js deleted file mode 100644 index 3d822f979..000000000 --- a/client/src/Hooks/userHooks.js +++ /dev/null @@ -1,81 +0,0 @@ -import { useEffect, useState, useCallback } from "react"; -import { networkService } from "../main.jsx"; -import { createToast } from "../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; - -export const useGetUser = (userId) => { - const [user, setUser] = useState(undefined); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const fetchUser = useCallback(async () => { - try { - setIsLoading(true); - const response = await networkService.getUserById({ userId }); - setUser(response?.data?.data); - } catch (error) { - createToast({ - body: error.message, - }); - setError(error); - } finally { - setIsLoading(false); - } - }, [userId]); - - useEffect(() => { - if (userId) { - fetchUser(); - } - }, [userId, fetchUser]); - - return [user, isLoading, error]; -}; - -export const useEditUser = (userId) => { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const { t } = useTranslation(); - - const editUser = useCallback( - async (user) => { - try { - setIsLoading(true); - await networkService.editUser({ userId, user }); - createToast({ - body: t("editUserPage.toast.successUserUpdate"), - }); - } catch (error) { - createToast({ - body: error.message, - }); - setError(error); - } finally { - setIsLoading(false); - } - }, - [userId, t] - ); - - const changePassword = useCallback( - async (passwordForm) => { - try { - setIsLoading(true); - - await networkService.changePasswordByAdmin({ userId, passwordForm }); - createToast({ - body: t("teamPanel.changeTeamPassword.success"), - }); - } catch (error) { - createToast({ - body: error.message, - }); - setError(error); - } finally { - setIsLoading(false); - } - }, - [userId, t] - ); - - return [editUser, isLoading, error, changePassword]; -}; diff --git a/client/src/Pages/ServerUnreachable.jsx b/client/src/Pages/ServerUnreachable.jsx index bfbbfa90a..5051ee95f 100644 --- a/client/src/Pages/ServerUnreachable.jsx +++ b/client/src/Pages/ServerUnreachable.jsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { Box, Typography, Button, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import { useNavigate } from "react-router-dom"; -import { networkService } from "@/Utils/NetworkService.js"; +import { get } from "@/Utils/ApiClient"; import Alert from "@/Components/v1/Alert/index.jsx"; import { createToast } from "@/Utils/toastUtils.jsx"; import { useTranslation } from "react-i18next"; @@ -24,7 +24,7 @@ const ServerUnreachable = () => { try { // Try to connect to the backend with a simple API call // We'll use any lightweight endpoint that doesn't require authentication - await networkService.axiosInstance.get("/health", { timeout: 5000 }); + await get("/health", { timeout: 5000 }); // If successful, show toast and navigate to login page createToast({ diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx index 6f0b02137..f31373ed9 100644 --- a/client/src/Pages/Settings/SettingsEmail.jsx +++ b/client/src/Pages/Settings/SettingsEmail.jsx @@ -12,7 +12,8 @@ import { PropTypes } from "prop-types"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx"; -import { useSendTestEmail } from "@/Hooks/useSendTestEmail.js"; +import { usePost } from "@/Hooks/UseApi"; +import { useSelector } from "react-redux"; import { createToast } from "@/Utils/toastUtils.jsx"; const SettingsEmail = ({ @@ -48,7 +49,8 @@ const SettingsEmail = ({ const [password, setPassword] = useState(""); // Network - const [isSending, , sendTestEmail] = useSendTestEmail(); // Using empty placeholder for unused error variable + const { post: sendTestEmailFn, loading: isSending } = usePost(); + const user = useSelector((state) => state.auth.user); // Handlers const handlePasswordChange = (e) => { @@ -63,28 +65,12 @@ const SettingsEmail = ({ * Handle sending test email with current form values */ const handleSendTestEmail = () => { - // Collect current form values - const emailConfig = { - systemEmailHost, - systemEmailPort, - systemEmailSecure, - systemEmailPool, - systemEmailUser, - systemEmailAddress, - systemEmailPassword: password || systemEmailPassword, - systemEmailTLSServername, - systemEmailConnectionHost, - systemEmailIgnoreTLS, - systemEmailRequireTLS, - systemEmailRejectUnauthorized, - }; - // Basic validation if ( - !emailConfig.systemEmailHost || - !emailConfig.systemEmailPort || - !emailConfig.systemEmailAddress || - !emailConfig.systemEmailPassword + !systemEmailHost || + !systemEmailPort || + !systemEmailAddress || + !(password || systemEmailPassword) ) { createToast({ body: t("settingsPage.emailSettings.toastEmailRequiredFieldsError"), @@ -93,8 +79,22 @@ const SettingsEmail = ({ return; } - // Send test email with current form values - sendTestEmail(emailConfig); + // Send test email - only include optional fields if they have values + sendTestEmailFn("/settings/test-email", { + to: user.email, + systemEmailHost, + systemEmailPort, + systemEmailAddress, + systemEmailPassword: password || systemEmailPassword, + systemEmailSecure, + systemEmailPool, + systemEmailIgnoreTLS, + systemEmailRequireTLS, + systemEmailRejectUnauthorized, + ...(systemEmailUser && { systemEmailUser }), + ...(systemEmailTLSServername && { systemEmailTLSServername }), + ...(systemEmailConnectionHost && { systemEmailConnectionHost }), + }); }; if (!isAdmin) { @@ -297,6 +297,14 @@ const SettingsEmail = ({ +
+						{JSON.stringify({
+							systemEmailHost,
+							systemEmailAddress,
+							systemEmailPassword,
+						})}
+					
+ {systemEmailHost && systemEmailPort && diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index bd3513a2c..fb6185784 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -13,7 +13,7 @@ import SettingsExport from "./SettingsExport.jsx"; import Button from "@mui/material/Button"; // Utils import { settingsValidation } from "@/Validation/validation.js"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; import { useSelector, useDispatch } from "react-redux"; @@ -26,14 +26,8 @@ import { } from "@/Features/UI/uiSlice.js"; import SettingsStats from "./SettingsStats.jsx"; -import { useFetchSettings, useSaveSettings } from "@/Hooks/settingsHooks.js"; import { useIsAdmin } from "@/Hooks/useIsAdmin.js"; -import { - useAddDemoMonitors, - useDeleteAllMonitors, - useDeleteMonitorStats, - useFetchJson, -} from "@/Hooks/monitorHooks.js"; +import { useGet, usePost, useDelete, useLazyGet, usePut } from "@/Hooks/UseApi"; // Constants const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }]; @@ -58,24 +52,39 @@ const Settings = () => { const [emailPasswordHasBeenReset, setEmailPasswordHasBeenReset] = useState(false); // Network - const [isSettingsLoading, settingsError] = useFetchSettings({ - setSettingsData, - setIsApiKeySet, - setIsEmailPasswordSet, - }); + const { data: fetchedSettings, isLoading: isSettingsLoading } = useGet("/settings"); - const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors(); + useEffect(() => { + if (fetchedSettings) { + setSettingsData(fetchedSettings); + setIsApiKeySet(fetchedSettings?.pagespeedKeySet); + setIsEmailPasswordSet(fetchedSettings?.emailPasswordSet); + } + }, [fetchedSettings]); - const [isSaving, saveError, saveSettings] = useSaveSettings({ - setSettingsData, - setIsApiKeySet, - setApiKeyHasBeenReset, - setIsEmailPasswordSet, - setEmailPasswordHasBeenReset, - }); - const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors(); - const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats(); - const [fetchJson, isFetchingJson] = useFetchJson(); + const { put: saveSettingsFn, loading: isSaving } = usePut(); + + const saveSettings = async (settings) => { + const response = await saveSettingsFn("/settings", { settings }); + if (response?.data) { + const data = response.data; + setIsApiKeySet(data.pagespeedKeySet); + setIsEmailPasswordSet(data.emailPasswordSet); + if (data.pagespeedKeySet === true) { + setApiKeyHasBeenReset(false); + } + if (data.emailPasswordSet === true) { + setEmailPasswordHasBeenReset(false); + } + setSettingsData(data); + } + }; + + // New API hooks to replace monitorHooks + const { post: postDemoMonitors, loading: isAddingDemoMonitors } = usePost(); + const { deleteFn: deleteAllMonitorsFn, loading: isDeletingMonitors } = useDelete(); + const { deleteFn: deleteMonitorStatsFn, loading: isDeletingMonitorStats } = useDelete(); + const { get: fetchJson, loading: isFetchingJson } = useLazyGet(); // Setup const isAdmin = useIsAdmin(); @@ -130,22 +139,23 @@ const Settings = () => { } if (name === "deleteStats") { - await deleteMonitorStats(); + await deleteMonitorStatsFn("/checks/team"); return; } if (name === "demo") { - await addDemoMonitors(); + await postDemoMonitors("/monitors/demo", {}); return; } if (name === "deleteMonitors") { - await deleteAllMonitors(); + await deleteAllMonitorsFn("/monitors/"); return; } if (name === "export") { - const json = await fetchJson(); + const res = await fetchJson("/monitors/export/json"); + const json = res?.data ?? []; if (!json || json.length === 0) { return; } diff --git a/client/src/Pages/Uptime/BulkImport/Upload.jsx b/client/src/Pages/Uptime/BulkImport/Upload.jsx deleted file mode 100644 index 24bc0ec38..000000000 --- a/client/src/Pages/Uptime/BulkImport/Upload.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useTheme } from "@emotion/react"; -import { useState, useRef } from "react"; -import { Button, Typography } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import PropTypes from "prop-types"; - -const UploadFile = ({ onFileSelect }) => { - // Changed prop to onFileSelect - const theme = useTheme(); - const [file, setFile] = useState(); - const [error, setError] = useState(""); - const inputRef = useRef(); - const { t } = useTranslation(); - - const handleSelectFile = () => { - inputRef.current.click(); - }; - - const handleFileChange = (e) => { - setError(""); - const selectedFile = e.target.files[0]; - - // Basic file validation - if (!selectedFile) return; - - if (!selectedFile.name.toLowerCase().endsWith(".csv")) { - setError(t("bulkImport.invalidFileType")); - return; - } - - setFile(selectedFile); - onFileSelect(selectedFile); // Pass the file directly to parent - }; - - return ( -
- - - {file?.name || t("bulkImport.noFileSelected")} - - - {error} - - -
- ); -}; - -UploadFile.prototype = { - onFileSelect: PropTypes.func.isRequired, -}; - -export default UploadFile; diff --git a/client/src/Pages/Uptime/BulkImport/index.jsx b/client/src/Pages/Uptime/BulkImport/index.jsx deleted file mode 100644 index 6f2f7bcb5..000000000 --- a/client/src/Pages/Uptime/BulkImport/index.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// React, Redux, Router -import { useTheme } from "@emotion/react"; -import { useState } from "react"; -// MUI -import { Box, Stack, Typography, Button, Link } from "@mui/material"; - -//Components -import { createToast } from "../../../Utils/toastUtils.jsx"; -import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; -import UploadFile from "./Upload.jsx"; -import { useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import { Trans, useTranslation } from "react-i18next"; -import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks.js"; - -const BulkImport = () => { - const theme = useTheme(); - const { user } = useSelector((state) => state.auth); - const navigate = useNavigate(); - const { t } = useTranslation(); - const [selectedFile, setSelectedFile] = useState(null); - - const crumbs = [ - { name: t("uptime"), path: "/uptime" }, - { name: t("bulkImport.title"), path: `/uptime/bulk-import` }, - ]; - - const [createBulkMonitors, hookLoading] = useCreateBulkMonitors(); - - const handleSubmit = async () => { - if (!selectedFile) { - createToast({ body: t("bulkImport.noFileSelected") }); - return; - } - - const [success, data, error] = await createBulkMonitors(selectedFile, user); - - if (success) { - // You can use `data` here if needed - createToast({ body: t("bulkImport.uploadSuccess") }); - navigate("/uptime"); - } else { - createToast({ body: error || t("bulkImport.uploadFailed") }); - } - }; - - return ( - - - - - {t("bulkImport.title")} - - - - - {t("bulkImport.selectFileTips")} - - - - ), - sample: ( - - ), - }} - /> - - - - - setSelectedFile(file)} /> - - - - - - - - - ); -}; - -export default BulkImport; diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 4711906c7..07dc1793c 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -53,7 +53,6 @@ import CreateNewMaintenanceWindow from "@/Pages/Maintenance/create"; import ProtectedRoute from "../Components/v1/ProtectedRoute"; import RoleProtectedRoute from "../Components/v1/RoleProtectedRoute"; import withAdminCheck from "@/Components/v1/HOC/withAdminCheck"; -import BulkImport from "../Pages/Uptime/BulkImport/index.jsx"; import Logs from "../Pages/Logs"; import CreateMonitor from "@/Pages/CreateMonitor"; @@ -87,10 +86,7 @@ const Routes = () => { } /> - } - /> + { - const state = store.getState(); - if (BASE_URL !== undefined) { - baseURL = BASE_URL; - } else if (state?.settings?.apiBaseUrl ?? null) { - baseURL = state.settings.apiBaseUrl; - } else { - baseURL = FALLBACK_BASE_URL; - } - this.setBaseUrl(baseURL); - }); - this.axiosInstance.interceptors.request.use( - (config) => { - const currentLanguage = i18next.language || "en"; - - const { authToken } = store.getState().auth; - - config.headers = { - Authorization: `Bearer ${authToken}`, - "Accept-Language": currentLanguage === "gb" ? "en" : currentLanguage, - ...config.headers, - }; - - return config; - }, - (error) => { - return Promise.reject(error); - } - ); - this.axiosInstance.interceptors.response.use( - (response) => response, - (error) => { - // Handle network errors (server unreachable) - if (error.code === "ERR_NETWORK") { - // Navigate to server unreachable page - navigate("/server-unreachable"); - // Return an empty resolved promise to stop the error propagation - return Promise.reject(error); - } - - // Handle authentication errors - - if (error.response && error.response.status === 401) { - dispatch(clearAuthState()); - navigate("/login"); - } else if (error.request && !error.response) { - return Promise.reject(error); - } - return Promise.reject(error); - } - ); - } - - setBaseUrl = (url) => { - this.axiosInstance.defaults.baseURL = url; - }; - - cleanup() { - if (this.unsubscribe) { - this.unsubscribe(); - } - } - - /** - * Fetch the games associated with a monitor - * - * @async - * @returns {Promise} The response from the axios GET request. - */ - async getMonitorGames() { - return this.axiosInstance.get(`/monitors/games`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * - * ************************************ - * Create a new monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The monitor ID to be sent in the param. - * @returns {Promise} The response from the axios GET request. - */ - - async getMonitorById(config) { - return this.axiosInstance.get(`/monitors/${config.monitorId}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * - * ************************************ - * Create a new monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {Object} config.monitor - The monitor object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - */ - async createMonitor(config) { - const { monitor } = config; - - return this.axiosInstance.post(`/monitors`, monitor); - } - - /** - * ************************************ - * Get all uptime monitors for a Team - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.teamId - The ID of the team whose monitors are to be retrieved. - * @param {number} [config.limit] - The maximum number of checks to retrieve. 0 for all, -1 for none - * @param {Array} [config.types] - The types of monitors to retrieve. - * @param {number} [config.page] - The page number for pagination. - * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. - * @param {string} [config.filter] - The filter to apply to the monitors. - * @param {string} [config.field] - The field to sort by. - * @param {string} [config.order] - The order in which to sort the field. - * @returns {Promise} The response from the axios GET request. - */ - - async getMonitorsByTeamId(config) { - const { types, filter } = config ?? {}; - const params = new URLSearchParams(); - - if (types) { - types.forEach((type) => { - params.append("type", type); - }); - } - if (filter !== undefined && filter !== null && filter !== "") - params.append("filter", filter); - - return this.axiosInstance.get(`/monitors/team?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Get stats for a monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor whose statistics are to be retrieved. - * @param {string} config.sortOrder - The order in which to sort the retrieved statistics. - * @param {number} config.limit - The maximum number of statistics to retrieve. - * @param {string} config.dateRange - The date range for which to retrieve statistics. - * @param {number} config.numToDisplay - The number of checks to display. - * @param {boolean} config.normalize - Whether to normalize the retrieved statistics. - * @returns {Promise} The response from the axios GET request. - */ - async getStatsByMonitorId(config) { - const params = new URLSearchParams(); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.limit) params.append("limit", config.limit); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.numToDisplay) params.append("numToDisplay", config.numToDisplay); - if (config.normalize) params.append("normalize", config.normalize); - - return this.axiosInstance.get( - `/monitors/stats/${config.monitorId}?${params.toString()}` - ); - } - - async getHardwareDetailsByMonitorId(config) { - const params = new URLSearchParams(); - if (config.dateRange) params.append("dateRange", config.dateRange); - - return this.axiosInstance.get( - `/monitors/hardware/details/${config.monitorId}?${params.toString()}` - ); - } - async getPageSpeedDetailsByMonitorId(config) { - const params = new URLSearchParams(); - if (config.dateRange) params.append("dateRange", config.dateRange); - - return this.axiosInstance.get( - `/monitors/pagespeed/details/${config.monitorId}?${params.toString()}` - ); - } - async getUptimeDetailsById(config) { - const params = new URLSearchParams(); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.normalize) params.append("normalize", config.normalize); - - return this.axiosInstance.get( - `/monitors/uptime/details/${config.monitorId}?${params.toString()}` - ); - } - - /** - * ************************************ - * Updates a single monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor to be updated. - * @param {Object} config.updatedFields - The fields to be updated for the monitor. - * @returns {Promise} The response from the axios PUT request. - */ - async updateMonitor(config) { - const { monitorId, updatedFields } = config; - const payload = updatedFields; - return this.axiosInstance.patch(`/monitors/${monitorId}`, payload, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Deletes a single monitor by its ID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor to be deleted. - * @returns {Promise} The response from the axios DELETE request. - */ - async deleteMonitorById(config) { - return this.axiosInstance.delete(`/monitors/${config.monitorId}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Deletes all checks for all monitor by teamID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.teamId - The team ID of the monitors to be deleted. - * @returns {Promise} The response from the axios DELETE request. - */ - async deleteChecksByTeamId(config) { - return this.axiosInstance.delete(`/checks/team`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - /** - * ************************************ - * Pauses a single monitor by its ID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor to be paused. - * @returns {Promise} The response from the axios POST request. - */ - async pauseMonitorById(config) { - return this.axiosInstance.post( - `/monitors/pause/${config.monitorId}`, - {}, - { - headers: { - "Content-Type": "application/json", - }, - } - ); - } - - /** - * ************************************ - * Adds demo monitors - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @returns {Promise} The response from the axios POST request. - */ - async addDemoMonitors(config) { - return this.axiosInstance.post( - `/monitors/demo`, - {}, - { - headers: { - "Content-Type": "application/json", - }, - } - ); - } - - /** - * ************************************ - * Deletes all monitors for a team by team ID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @returns {Promise} The response from the axios DELETE request. - */ - async deleteAllMonitors(config) { - return this.axiosInstance.delete(`/monitors/`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Gets the certificate expiry for a monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor whose certificate expiry is to be retrieved. - * @returns {Promise} The response from the axios GET request. - * - */ - async getCertificateExpiry(config) { - return this.axiosInstance.get(`/monitors/certificate/${config.monitorId}`); - } - - async registerUser(user, token) { - return this.axiosInstance.post(`/auth/register`, { user, token }); - } - - /** - * ************************************ - * Logs in a user - * ************************************ - * - * @async - * @param {Object} form - The form data for the user to be logged in. - * @returns {Promise} The response from the axios POST request. - * - */ - async loginUser(form) { - return this.axiosInstance.post(`/auth/login`, form); - } - - /** - * ************************************ - * Updates a user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.userId - The ID of the user to be updated. - * @param {Object} config.form - The form data for the user to be updated. - * @returns {Promise} The response from the axios PATCH request. - * - */ - async updateUser(config) { - return this.axiosInstance.patch(`/auth/user`, config.form); - } - - /** - * ************************************ - * Deletes a user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.userId - The ID of the user to be deleted. - * - **/ - async deleteUser() { - return this.axiosInstance.delete(`/auth/user`); - } - - /** - * ************************************ - * Forgot password request - * ************************************ - * - * @async - * @param {Object} form - The form data for the password recovery request. - * @returns {Promise} The response from the axios POST request. - * - */ - async forgotPassword(form) { - return this.axiosInstance.post(`/auth/recovery/request`, form); - } - - /** - * ************************************ - * Validates a recovery token - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.recoveryToken - The recovery token to be validated. - * @returns {Promise} The response from the axios POST request. - * - */ - async validateRecoveryToken(config) { - return this.axiosInstance.post("/auth/recovery/validate", { - recoveryToken: config.recoveryToken, - }); - } - - /** - * ************************************ - * Requests password recovery - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.recoveryToken - The token for recovery request. - * @param {Object} config.form - The form data for the password recovery request. - * @returns {Promise} The response from the axios POST request. - * - */ - async setNewPassword(config) { - return this.axiosInstance.post("/auth/recovery/reset", { - ...config.form, - recoveryToken: config.recoveryToken, - }); - } - - /** - * ************************************ - * Checks if an admin user exists - * ************************************ - * - * @async - * @returns {Promise} The response from the axios GET request. - * - */ - async doesSuperAdminExist() { - return this.axiosInstance.get("/auth/users/superadmin"); - } - - /** - * ************************************ - * Get all users - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @returns {Promise} The response from the axios GET request. - * - */ - async getAllUsers(config) { - return this.axiosInstance.get("/auth/users"); - } - - /** - * ************************************ - * Requests an invitation token - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.email - The email of the user to be invited. - * @param {string} config.role - The role of the user to be invited. - * @returns {Promise} The response from the axios POST request. - * - */ - async requestInvitationToken(config) { - return this.axiosInstance.post(`/invite`, { email: config.email, role: config.role }); - } - /** - * ************************************ - * Sends an invitation token - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.email - The email of the user to be invited. - * @param {string} config.role - The role of the user to be invited. - * @returns {Promise} The response from the axios POST request. - * - */ - async sendInvitationToken(config) { - return this.axiosInstance.post(`/invite/send`, { - email: config.email, - role: config.role, - }); - } - - /** - * ************************************ - * Verifies an invitation token - * ************************************ - * - * @async - * @param {string} token - The invitation token to be verified. - * @returns {Promise} The response from the axios POST request. - * - */ - async verifyInvitationToken(token) { - return this.axiosInstance.post(`/invite/verify`, { - token, - }); - } - - /** - * ************************************ - * Get all checks for a given monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor. - * @param {string} config.sortOrder - The order in which to sort the checks. - * @param {number} config.limit - The maximum number of checks to retrieve. - * @param {string} config.dateRange - The range of dates for which to retrieve checks. - * @param {string} config.filter - The filter to apply to the checks. - * @param {boolean} config.ack - The acknowledgment status to apply to the checks. - * @param {number} config.page - The page number to retrieve in a paginated list. - * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. - * @returns {Promise} The response from the axios GET request. - * - */ - - getChecksByMonitor = async (config) => { - const params = new URLSearchParams(); - if (config.type) params.append("type", config.type); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.limit) params.append("limit", config.limit); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.filter) params.append("filter", config.filter); - if (config.ack !== undefined) params.append("ack", config.ack); - if (config.page) params.append("page", config.page); - if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - if (config.status !== undefined) params.append("status", config.status); - - return this.axiosInstance.get(`/checks/${config.monitorId}?${params.toString()}`); - }; - - /** - * ************************************ - * Get all checks for a given user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.userId - The ID of the user. - * @param {string} config.sortOrder - The order in which to sort the checks. - * @param {number} config.limit - The maximum number of checks to retrieve. - * @param {string} config.dateRange - The range of dates for which to retrieve checks. - * @param {string} config.filter - The filter to apply to the checks. - * @param {boolean} config.ack - The acknowledgment status to apply to the checks. - * @param {number} config.page - The page number to retrieve in a paginated list. - * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. - * @returns {Promise} The response from the axios GET request. - * - */ - getChecksByTeam = async (config) => { - const params = new URLSearchParams(); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.limit) params.append("limit", config.limit); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.filter) params.append("filter", config.filter); - if (config.ack !== undefined) params.append("ack", config.ack); - if (config.page) params.append("page", config.page); - if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - return this.axiosInstance.get(`/checks/team?${params.toString()}`); - }; - - /** - * ************************************ - * Get summary of checks by team ID - * ************************************ - * - * @async - * @returns {Promise} The response from the axios GET request. - */ - getChecksAndSummaryByTeamId = async ({ dateRange }) => { - return this.axiosInstance.get(`/checks/team/summary?dateRange=${dateRange}`); - }; - - /** - * ************************************ - * Update the status of a check - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.checkId - The ID of the check to update. - * @param {boolean} config.ack - The acknowledgment to update the check to. - * @returns {Promise} The response from the axios PUT request. - * - */ - async updateCheckStatus(config) { - return this.axiosInstance.patch(`/checks/check/${config.checkId}`, { - ack: config.ack, - }); - } - - /** - * ************************************ - * Update the status of all checks for a given monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.monitorId - The ID of the monitor. - * @param {boolean} config.ack - The acknowledgment to update the checks to. - * @returns {Promise} The response from the axios PUT request. - * - */ - async updateMonitorChecksStatus(config) { - return this.axiosInstance.patch(`/checks/monitor/${config.monitorId}`, { - ack: config.ack, - }); - } - - /** - * ************************************ - * Update the status of all checks for a given team - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {boolean} config.ack - The acknowledgment to update the checks to. - * @returns {Promise} The response from the axios PUT request. - * - */ - async updateAllChecksStatus(config) { - return this.axiosInstance.patch(`/checks/team/`, { - ack: config.ack, - }); - } - - /** - * ************************************ - * Get all checks for a given user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {number} config.ttl - TTL for checks - * @returns {Promise} The response from the axios GET request. - * - */ - async updateChecksTTL(config) { - return this.axiosInstance.patch( - `/checks/team/ttl`, - { ttl: config.ttl }, - { - headers: { - "Content-Type": "application/json", - }, - } - ); - } - - /** - * ************************************ - * Get app settings - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @returns {Promise} The response from the axios GET request. - * - */ - - async getAppSettings(config) { - return this.axiosInstance.get("/settings", { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * - * ************************************ - * Create a new monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {Object} config.settings - The monitor object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - */ - async updateAppSettings(config) { - return this.axiosInstance.patch(`/settings`, config.settings, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Test a notification integration - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.platform - The notification platform (slack, discord, telegram, webhook). - * @param {Object} config.payload - The payload with configuration for the notification. - * @returns {Promise} The response from the axios POST request. - */ - async testNotification(config) { - return this.axiosInstance.post("/notifications/test", config.notification, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async testAllNotifications(config) { - const { monitorId } = config; - return this.axiosInstance.post("/notifications/test/all", { - monitorId, - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Creates a maintenance window - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - * - */ - - async createMaintenanceWindow(config) { - return this.axiosInstance.post(`/maintenance-window`, config.maintenanceWindow, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Edits a maintenance window - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {Object} config.maintenanceWindowId - The maintenance window id. - * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - * - */ - - async editMaintenanceWindow(config) { - return this.axiosInstance.patch( - `/maintenance-window/${config.maintenanceWindowId}`, - config.maintenanceWindow, - { - headers: { - "Content-Type": "application/json", - }, - } - ); - } - - /** - * ************************************ - * Get maintenance window by id - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} [config.maintenanceWindowId] - The id of the maintenance window to delete. - * @returns {Promise} The response from the axios POST request. - * - */ - - async getMaintenanceWindowById(config) { - const { maintenanceWindowId } = config; - return this.axiosInstance.get(`/maintenance-window/${maintenanceWindowId}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Get maintenance windows by teamId - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} [config.active] - The status of the maintenance windows to retrieve. - * @param {number} [config.page] - The page number for pagination. - * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. - * @param {string} [config.field] - The field to sort by. - * @param {string} [config.order] - The order in which to sort the field. - * @returns {Promise} The response from the axios POST request. - * - */ - - async getMaintenanceWindowsByTeamId(config) { - const { active, page, rowsPerPage, field, order } = config; - const params = new URLSearchParams(); - - if (active) params.append("status", active); - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); - if (field) params.append("field", field); - if (order) params.append("order", order); - - return this.axiosInstance.get(`/maintenance-window/team?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - /** - * ************************************ - * Delete maintenance window by id - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} [config.maintenanceWindowId] - The id of the maintenance window to delete. - * @returns {Promise} The response from the axios POST request. - * - */ - - async deleteMaintenanceWindow(config) { - const { maintenanceWindowId } = config; - return this.axiosInstance.delete(`/maintenance-window/${maintenanceWindowId}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - /** - * ************************************ - * Fetcher github latest release version - * ************************************ - * - * @async - * @returns {Promise} The response from the axios GET request. - * - */ - async fetchGithubLatestRelease() { - return this.axiosInstance.get( - "https://api.github.com/repos/bluewave-labs/checkmate/releases/latest", - { - headers: { - Authorization: null, // No authorization header for this request - }, - } - ); - } - - async getStatusPage() { - return this.axiosInstance.get(`/status-page`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async getStatusPageByUrl(config) { - const { url, type } = config; - const params = new URLSearchParams(); - params.append("type", type); - - return this.axiosInstance.get(`/status-page/${url}?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async getStatusPagesByTeamId(config) { - return this.axiosInstance.get(`/status-page/team`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async createStatusPage(config) { - const { form, isCreate, id } = config; - - const fd = new FormData(); - fd.append("type", form.type); - form.isPublished !== undefined && fd.append("isPublished", form.isPublished); - form.companyName && fd.append("companyName", form.companyName); - form.url && fd.append("url", form.url); - form.timezone && fd.append("timezone", form.timezone); - form.color && fd.append("color", form.color); - if (form.showCharts !== undefined) { - fd.append("showCharts", String(form.showCharts)); - } - if (form.showUptimePercentage !== undefined) { - fd.append("showUptimePercentage", String(form.showUptimePercentage)); - } - if (form.showAdminLoginLink !== undefined) { - fd.append("showAdminLoginLink", String(form.showAdminLoginLink)); - } - form.monitors && - form.monitors.forEach((monitorId) => { - fd.append("monitors[]", monitorId); - }); - // Handle subMonitors, even if it's an empty array - if (form.subMonitors && form.subMonitors.length > 0) { - form.subMonitors.forEach((monitorId) => { - fd.append("subMonitors[]", monitorId); - }); - } else { - fd.append("deleteSubmonitors", true); - } - - if (form?.logo?.src && form?.logo?.src !== "") { - const imageResult = await axios.get(form.logo.src, { - responseType: "blob", - }); - fd.append("logo", imageResult.data); - // Cleanup blob - if (form.logo.src.startsWith("blob:")) { - URL.revokeObjectURL(form.logo.src); - } - } - - if (isCreate) { - return this.axiosInstance.post(`/status-page`, fd, {}); - } - - return this.axiosInstance.patch(`/status-page/${id}`, fd, {}); - } - - async deleteStatusPage(config) { - const { url } = config; - const encodedUrl = encodeURIComponent(url); - - return this.axiosInstance.delete(`/status-page/${encodedUrl}`, {}); - } - - // ************************************ - // Create bulk monitors - // ************************************ - - async createBulkMonitors(formData) { - const response = await this.axiosInstance.post(`/monitors/bulk`, formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }); - return response.data; - } - - // ************************************ - // Fetch monitors with summary by TeamID - // ************************************ - async getMonitorsWithSummaryByTeamId(config) { - const { types } = config; - const params = new URLSearchParams(); - - if (types) { - types.forEach((type) => { - params.append("type", type); - }); - } - - return this.axiosInstance.get(`/monitors/team/summary?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - // ************************************ - // Fetch monitors with checks by TeamID - // ************************************ - async getMonitorsWithChecksByTeamId(config) { - const { limit, types, page, rowsPerPage, filter, field, order } = config; - const params = new URLSearchParams(); - - if (limit) params.append("limit", limit); - if (types) { - types.forEach((type) => { - params.append("type", type); - }); - } - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); - if (filter) params.append("filter", filter); - if (field) params.append("field", field); - if (order) params.append("order", order); - - return this.axiosInstance.get(`/monitors/team/with-checks?${params.toString()}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - // ************************************ - // Send test email - // ************************************ - async sendTestEmail(config) { - // Extract recipient and email configuration - const { to, emailConfig } = config; - - // If emailConfig is provided, use the new endpoint with direct parameters - if (emailConfig) { - return this.axiosInstance.post(`/settings/test-email`, { - to, - systemEmailHost: emailConfig.systemEmailHost, - systemEmailPort: emailConfig.systemEmailPort, - systemEmailAddress: emailConfig.systemEmailAddress, - systemEmailPassword: emailConfig.systemEmailPassword, - systemEmailSecure: emailConfig.systemEmailSecure, - systemEmailPool: emailConfig.systemEmailPool, - systemEmailIgnoreTLS: emailConfig.systemEmailIgnoreTLS, - systemEmailRequireTLS: emailConfig.systemEmailRequireTLS, - systemEmailRejectUnauthorized: emailConfig.systemEmailRejectUnauthorized, - systemEmailTLSServername: emailConfig.systemEmailTLSServername, - ...(emailConfig.systemEmailUser && { - systemEmailUser: emailConfig.systemEmailUser, - }), - }); - } - - // Fallback to original behavior for backward compatibility - return this.axiosInstance.post(`/settings/test-email`, { to }); - } - - async createNotification(config) { - const { notification } = config; - return this.axiosInstance.post(`/notifications`, notification); - } - - async getNotificationsByTeamId(config) { - return this.axiosInstance.get(`/notifications/team`); - } - - async deleteNotificationById(config) { - const { id } = config; - return this.axiosInstance.delete(`/notifications/${id}`); - } - - async getNotificationById(config) { - const { id } = config; - return this.axiosInstance.get(`/notifications/${id}`); - } - - async editNotification(config) { - const { id, notification } = config; - return this.axiosInstance.patch(`/notifications/${id}`, notification); - } - - async getQueueData() { - return this.axiosInstance.get(`/queue/all-metrics`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async flushQueue() { - return this.axiosInstance.post(`/queue/flush`); - } - - async exportMonitors() { - const response = await this.axiosInstance.get("/monitors/export", { - responseType: "blob", - }); - return response; - } - - async getLogs() { - return this.axiosInstance.get(`/logs`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async getDiagnostics() { - return this.axiosInstance.get(`/diagnostic/system`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async getUserById(config) { - const userId = config?.userId; - return this.axiosInstance.get(`auth/users/${userId}`, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async editUser(config) { - const { userId, user } = config; - return this.axiosInstance.patch(`auth/users/${userId}`, user, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async changePasswordByAdmin(config) { - const { userId, passwordForm } = config; - return this.axiosInstance.patch(`auth/users/${userId}/password`, passwordForm, { - headers: { - "Content-Type": "application/json", - }, - }); - } - - async fetchJson() { - return this.axiosInstance.get("/monitors/export/json"); - } - - getIncidentsByTeam = async (config) => { - const params = new URLSearchParams(); - if (config.monitorId) params.append("monitorId", config.monitorId); - if (config.status !== undefined) params.append("status", config.status); - if (config.resolutionType) params.append("resolutionType", config.resolutionType); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.page) params.append("page", config.page); - if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - return this.axiosInstance.get(`/incidents/team?${params.toString()}`); - }; - - getIncidentById = async (incidentId) => { - return this.axiosInstance.get(`/incidents/${incidentId}`); - }; - - resolveIncidentManually = async (incidentId, options = {}) => { - const body = {}; - if (options.comment) body.comment = options.comment; - return this.axiosInstance.patch(`/incidents/${incidentId}/resolve`, body); - }; - - getIncidentSummary = async (config = {}) => { - const params = new URLSearchParams(); - if (config.limit) params.append("limit", config.limit); - const queryString = params.toString(); - return this.axiosInstance.get( - `/incidents/team/summary${queryString ? `?${queryString}` : ""}` - ); - }; -} - -export default NetworkService; - -let networkService; - -export const setNetworkService = (service) => { - networkService = service; -}; -export { networkService }; diff --git a/client/src/Utils/NetworkServiceProvider.jsx b/client/src/Utils/NetworkServiceProvider.jsx deleted file mode 100644 index 73c3a2f94..000000000 --- a/client/src/Utils/NetworkServiceProvider.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import { setNetworkService } from "./NetworkService"; -import NetworkService, { networkService } from "./NetworkService"; -import { store } from "../store"; - -const NetworkServiceProvider = ({ children }) => { - const dispatch = useDispatch(); - const navigate = useNavigate(); - if (!networkService) { - setNetworkService(new NetworkService(store, dispatch, navigate)); - } - return children; -}; - -export default NetworkServiceProvider; diff --git a/client/src/main.jsx b/client/src/main.tsx similarity index 50% rename from client/src/main.jsx rename to client/src/main.tsx index 5cfbe6f33..813a839bd 100644 --- a/client/src/main.jsx +++ b/client/src/main.tsx @@ -3,17 +3,14 @@ import App from "./App.jsx"; import "./index.css"; import { BrowserRouter as Router } from "react-router-dom"; import { Provider } from "react-redux"; -import { persistor, store } from "./store"; +import { persistor, store } from "./store.js"; import { PersistGate } from "redux-persist/integration/react"; -import NetworkServiceProvider from "./Utils/NetworkServiceProvider.jsx"; -import { networkService } from "./Utils/NetworkService"; -export { networkService }; -import I18nLoader from "./Components/v1/I18nLoader"; -import { initApiClient } from "./Utils/ApiClient"; +import I18nLoader from "./Components/v1/I18nLoader/index.jsx"; +import { initApiClient } from "./Utils/ApiClient.js"; initApiClient(store); -ReactDOM.createRoot(document.getElementById("root")).render( +ReactDOM.createRoot(document.getElementById("root")!).render( - - - +