diff --git a/client/src/Components/MonitorDetailsControlHeader/index.jsx b/client/src/Components/MonitorDetailsControlHeader/index.jsx new file mode 100644 index 000000000..c4dcf1ffc --- /dev/null +++ b/client/src/Components/MonitorDetailsControlHeader/index.jsx @@ -0,0 +1,94 @@ +import Stack from "@mui/material/Stack"; +import Status from "./status"; +import Skeleton from "./skeleton"; +import Button from "@mui/material/Button"; +import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined"; +import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined"; +import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined"; + +// Utils +import PropTypes from "prop-types"; +import { useNavigate } from "react-router-dom"; +import { useTheme } from "@mui/material/styles"; +import { usePauseMonitor } from "../../Hooks/useMonitorControls"; + +/** + * MonitorDetailsControlHeader component displays the control header for monitor details. + * It includes status display, pause/resume button, and a configure button for admins. + * + * @component + * @param {Object} props - Component props + * @param {string} props.path - The base path for navigation + * @param {boolean} [props.isLoading=false] - Flag indicating if the data is loading + * @param {boolean} [props.isAdmin=false] - Flag indicating if the user is an admin + * @param {Object} props.monitor - The monitor object containing details + * @param {Function} props.triggerUpdate - Function to trigger an update + * @returns {JSX.Element} The rendered component + */ +const MonitorDetailsControlHeader = ({ + path, + isLoading = false, + isAdmin = false, + monitor, + triggerUpdate, +}) => { + const navigate = useNavigate(); + const theme = useTheme(); + const [pauseMonitor, isPausing, error] = usePauseMonitor({ + monitorId: monitor?._id, + triggerUpdate, + }); + + if (isLoading) { + return ; + } + + return ( + + + + + + + {isAdmin && ( + + )} + + + ); +}; + +MonitorDetailsControlHeader.propTypes = { + path: PropTypes.string, + isLoading: PropTypes.bool, + isAdmin: PropTypes.bool, + monitor: PropTypes.object, + triggerUpdate: PropTypes.func, +}; + +export default MonitorDetailsControlHeader; diff --git a/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx b/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx new file mode 100644 index 000000000..64dc0547f --- /dev/null +++ b/client/src/Components/MonitorDetailsControlHeader/skeleton.jsx @@ -0,0 +1,23 @@ +import { Stack, Skeleton } from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + ); +}; + +export default SkeletonLayout; diff --git a/client/src/Components/MonitorDetailsControlHeader/status.jsx b/client/src/Components/MonitorDetailsControlHeader/status.jsx new file mode 100644 index 000000000..9e01dd502 --- /dev/null +++ b/client/src/Components/MonitorDetailsControlHeader/status.jsx @@ -0,0 +1,56 @@ +// Components +import Stack from "@mui/material/Stack"; +import PulseDot from "../../Components/Animated/PulseDot"; +import Typography from "@mui/material/Typography"; +import Dot from "../../Components/Dot"; +// Utils +import { formatDurationRounded } from "../../Utils/timeUtils"; +import PropTypes from "prop-types"; +import { useTheme } from "@emotion/react"; +import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils"; + +/** + * Status component displays the status information of a monitor. + * It includes the monitor's name, URL, and check interval. + * + * @component + * @param {Object} props - Component props + * @param {Object} props.monitor - The monitor object containing details + * @param {string} props.monitor.name - The name of the monitor + * @param {string} props.monitor.url - The URL of the monitor + * @param {number} props.monitor.interval - The interval at which the monitor checks + * @returns {JSX.Element} The rendered component + */ +const Status = ({ monitor }) => { + const theme = useTheme(); + const { statusColor, determineState } = useUtils(); + + return ( + + {monitor?.name} + + + + {monitor?.url?.replace(/^https?:\/\//, "") || "..."} + + + + Checking every {formatDurationRounded(monitor?.interval)}. + + + + ); +}; + +Status.propTypes = { + monitor: PropTypes.object, +}; + +export default Status; diff --git a/client/src/Hooks/useFetchUptimeMonitorDetails.js b/client/src/Hooks/useFetchUptimeMonitorDetails.js index c8cb81e65..3c0ac7ee6 100644 --- a/client/src/Hooks/useFetchUptimeMonitorDetails.js +++ b/client/src/Hooks/useFetchUptimeMonitorDetails.js @@ -3,13 +3,12 @@ import { networkService } from "../main"; import { useNavigate } from "react-router-dom"; import { createToast } from "../Utils/toastUtils"; -export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => { +export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange, trigger }) => { const [networkError, setNetworkError] = useState(false); const [isLoading, setIsLoading] = useState(true); const [monitor, setMonitor] = useState(undefined); const [monitorStats, setMonitorStats] = useState(undefined); const navigate = useNavigate(); - useEffect(() => { const fetchMonitors = async () => { try { @@ -29,7 +28,7 @@ export const useFetchUptimeMonitorDetails = ({ monitorId, dateRange }) => { } }; fetchMonitors(); - }, [dateRange, monitorId, navigate]); + }, [dateRange, monitorId, navigate, trigger]); return [monitor, monitorStats, isLoading, networkError]; }; diff --git a/client/src/Hooks/useMonitorControls.js b/client/src/Hooks/useMonitorControls.js new file mode 100644 index 000000000..b47603b58 --- /dev/null +++ b/client/src/Hooks/useMonitorControls.js @@ -0,0 +1,28 @@ +import { useState } from "react"; +import { networkService } from "../main"; +import { createToast } from "../Utils/toastUtils"; + +const usePauseMonitor = ({ monitorId, triggerUpdate }) => { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + const pauseMonitor = async () => { + try { + setIsLoading(true); + const res = await networkService.pauseMonitorById({ monitorId }); + createToast({ + body: res.data.data.isActive + ? "Monitor resumed successfully" + : "Monitor paused successfully", + }); + triggerUpdate(); + } catch (error) { + setError(error); + } finally { + setIsLoading(false); + } + }; + + return [pauseMonitor, isLoading, error]; +}; + +export { usePauseMonitor }; diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx index 5afbc4a27..d52780eea 100644 --- a/client/src/Pages/Uptime/Configure/index.jsx +++ b/client/src/Pages/Uptime/Configure/index.jsx @@ -158,23 +158,6 @@ const Configure = () => { } }; - const handlePause = async () => { - try { - const action = await dispatch(pauseUptimeMonitor({ monitorId })); - if (pauseUptimeMonitor.fulfilled.match(action)) { - const monitor = action.payload.data; - setMonitor(monitor); - const state = action?.payload?.data.isActive === false ? "paused" : "resumed"; - createToast({ body: `Monitor ${state} successfully.` }); - } else if (pauseUptimeMonitor.rejected.match(action)) { - throw new Error(action.error.message); - } - } catch (error) { - logger.error("Error pausing monitor: " + monitorId); - createToast({ body: "Failed to pause monitor" }); - } - }; - const handleSubmit = async (event) => { event.preventDefault(); const action = await dispatch(updateUptimeMonitor({ monitor: monitor })); @@ -329,38 +312,6 @@ const Configure = () => { ml: "auto", }} > -