diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 7ea34b9ea..76f56a2ca 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -92,7 +92,7 @@ export const updateUptimeMonitor = createAsyncThunk( const res = await networkService.updateMonitor({ authToken: authToken, monitorId: monitor._id, - updatedFields: updatedFields, + monitor, }); return res.data; } catch (error) { diff --git a/Client/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx b/Client/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx new file mode 100644 index 000000000..2351fb0e0 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx @@ -0,0 +1,33 @@ +import { useState } from "react"; +import { networkService } from "../../../../main"; +import { useSelector } from "react-redux"; +import { createToast } from "../../../../Utils/toastUtils"; + +const useCreateDistributedUptimeMonitor = ({ isCreate, monitorId }) => { + const { authToken, user } = useSelector((state) => state.auth); + + const [isLoading, setIsLoading] = useState(false); + const [networkError, setNetworkError] = useState(false); + const createDistributedUptimeMonitor = async ({ form }) => { + setIsLoading(true); + try { + if (isCreate) { + await networkService.createMonitor({ authToken, monitor: form }); + } else { + await networkService.updateMonitor({ authToken, monitor: form, monitorId }); + } + + return true; + } catch (error) { + setNetworkError(true); + createToast({ body: error?.response?.data?.msg ?? error.message }); + return false; + } finally { + setIsLoading(false); + } + }; + + return [createDistributedUptimeMonitor, isLoading, networkError]; +}; + +export { useCreateDistributedUptimeMonitor }; diff --git a/Client/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx b/Client/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx new file mode 100644 index 000000000..cf7f3c377 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx @@ -0,0 +1,32 @@ +import { useEffect, useState } from "react"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; + +export const useMonitorFetch = ({ authToken, monitorId, isCreate }) => { + const [networkError, setNetworkError] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [monitor, setMonitor] = useState(undefined); + + useEffect(() => { + const fetchMonitors = async () => { + try { + if (isCreate) return; + const res = await networkService.getUptimeDetailsById({ + authToken: authToken, + monitorId: monitorId, + normalize: true, + }); + setMonitor(res?.data?.data ?? {}); + } catch (error) { + setNetworkError(true); + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchMonitors(); + }, [authToken, monitorId, isCreate]); + return [monitor, isLoading, networkError]; +}; + +export default useMonitorFetch; diff --git a/Client/src/Pages/DistributedUptime/Create/index.jsx b/Client/src/Pages/DistributedUptime/Create/index.jsx index a0efca428..0afefb207 100644 --- a/Client/src/Pages/DistributedUptime/Create/index.jsx +++ b/Client/src/Pages/DistributedUptime/Create/index.jsx @@ -13,18 +13,15 @@ import { createToast } from "../../../Utils/toastUtils"; // Utility import { useTheme } from "@emotion/react"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import { useSelector, useDispatch } from "react-redux"; +import { useSelector } from "react-redux"; import { monitorValidation } from "../../../Validation/validation"; -import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; -import { useLocation } from "react-router-dom"; +import { useParams } from "react-router-dom"; +import { useCreateDistributedUptimeMonitor } from "./Hooks/useCreateDistributedUptimeMonitor"; +import { useMonitorFetch } from "./Hooks/useMonitorFetch"; // Constants -const BREADCRUMBS = [ - { name: `distributed uptime`, path: "/distributed-uptime" }, - { name: "create", path: `/distributed-uptime/create` }, -]; const MS_PER_MINUTE = 60000; const SELECT_VALUES = [ { _id: 1, name: "1 minute" }, @@ -34,18 +31,30 @@ const SELECT_VALUES = [ { _id: 5, name: "5 minutes" }, ]; +const parseUrl = (url) => { + try { + return new URL(url); + } catch (error) { + return null; + } +}; + const CreateDistributedUptime = () => { - const location = useLocation(); - const isCreate = location.pathname.startsWith("/distributed-uptime/create"); + const { monitorId } = useParams(); + const isCreate = typeof monitorId === "undefined"; + + const BREADCRUMBS = [ + { name: `distributed uptime`, path: "/distributed-uptime" }, + { name: isCreate ? "create" : "configure", path: `` }, + ]; // Redux state const { user, authToken } = useSelector((state) => state.auth); - const isLoading = useSelector((state) => state.uptimeMonitors.isLoading); // Local state const [https, setHttps] = useState(true); const [notifications, setNotifications] = useState([]); - const [monitor, setMonitor] = useState({ + const [form, setForm] = useState({ type: "distributed_http", name: "", url: "", @@ -55,12 +64,37 @@ const CreateDistributedUptime = () => { //utils const theme = useTheme(); - const dispatch = useDispatch(); const navigate = useNavigate(); + const [createDistributedUptimeMonitor, isLoading, networkError] = + useCreateDistributedUptimeMonitor({ isCreate, monitorId }); + + const [monitor, monitorIsLoading, monitorNetworkError] = useMonitorFetch({ + authToken, + monitorId, + isCreate, + }); + + // Effect to set monitor to fetched monitor + useEffect(() => { + if (typeof monitor !== "undefined") { + const parsedUrl = parseUrl(monitor?.url); + const protocol = parsedUrl?.protocol?.replace(":", "") || ""; + setHttps(protocol === "https"); + + const newForm = { + name: monitor.name, + interval: monitor.interval / MS_PER_MINUTE, + url: parsedUrl.host, + type: monitor.type, + }; + + setForm(newForm); + } + }, [monitor]); // Handlers - const handleCreateMonitor = async (event) => { - const monitorToSubmit = { ...monitor }; + const handleCreateMonitor = async () => { + const monitorToSubmit = { ...form }; // Prepend protocol to url monitorToSubmit.url = `http${https ? "s" : ""}://` + monitorToSubmit.url; @@ -68,7 +102,6 @@ const CreateDistributedUptime = () => { const { error } = monitorValidation.validate(monitorToSubmit, { abortEarly: false, }); - if (error) { const newErrors = {}; error.details.forEach((err) => { @@ -80,16 +113,14 @@ const CreateDistributedUptime = () => { } // Append needed fields - monitorToSubmit.description = monitor.name; - monitorToSubmit.interval = monitor.interval * MS_PER_MINUTE; + monitorToSubmit.description = form.name; + monitorToSubmit.interval = form.interval * MS_PER_MINUTE; monitorToSubmit.teamId = user.teamId; monitorToSubmit.userId = user._id; monitorToSubmit.notifications = notifications; - const action = await dispatch( - createUptimeMonitor({ authToken, monitor: monitorToSubmit }) - ); - if (action.meta.requestStatus === "fulfilled") { + const success = await createDistributedUptimeMonitor({ form: monitorToSubmit }); + if (success) { createToast({ body: "Monitor created successfully!" }); navigate("/distributed-uptime"); } else { @@ -98,9 +129,10 @@ const CreateDistributedUptime = () => { }; const handleChange = (event) => { - const { name, value } = event.target; - setMonitor({ - ...monitor, + let { name, value } = event.target; + + setForm({ + ...form, [name]: value, }); const { error } = monitorValidation.validate( @@ -178,7 +210,8 @@ const CreateDistributedUptime = () => { label="URL to monitor" https={https} placeholder={"www.google.com"} - value={monitor.url} + disabled={!isCreate} + value={form.url} name="url" onChange={handleChange} error={errors["url"] ? true : false} @@ -190,7 +223,7 @@ const CreateDistributedUptime = () => { label="Display name" isOptional={true} placeholder={"Google"} - value={monitor.name} + value={form.name} name="name" onChange={handleChange} error={errors["name"] ? true : false} @@ -217,8 +250,11 @@ const CreateDistributedUptime = () => { checked={true} onChange={handleChange} /> - {monitor.type === "http" || monitor.type === "distributed_http" ? ( - + {form.type === "http" || form.type === "distributed_http" ? ( + + + + + + + ); +}; + +Controls.propTypes = { + isDeleting: PropTypes.bool, + monitorId: PropTypes.string, + isDeleteOpen: PropTypes.bool.isRequired, + setIsDeleteOpen: PropTypes.func.isRequired, +}; + +const ControlsHeader = ({ isDeleting, isDeleteOpen, setIsDeleteOpen, monitorId }) => { + const theme = useTheme(); + + return ( + + + + ); +}; + +ControlsHeader.propTypes = { + monitorId: PropTypes.string, + isDeleting: PropTypes.bool, + isDeleteOpen: PropTypes.bool.isRequired, + setIsDeleteOpen: PropTypes.func.isRequired, +}; + +export default ControlsHeader; diff --git a/Client/src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx b/Client/src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx new file mode 100644 index 000000000..ece7c42e5 --- /dev/null +++ b/Client/src/Pages/DistributedUptime/Details/Hooks/useDeleteMonitor.jsx @@ -0,0 +1,27 @@ +import { useSelector } from "react-redux"; +import { useState } from "react"; +import { networkService } from "../../../../main"; +import { createToast } from "../../../../Utils/toastUtils"; + +const useDeleteMonitor = ({ monitorId }) => { + const [isLoading, setIsLoading] = useState(false); + const { authToken } = useSelector((state) => state.auth); + const deleteMonitor = async () => { + try { + setIsLoading(true); + await networkService.deleteMonitorById({ authToken, monitorId }); + return true; + } catch (error) { + createToast({ + body: error.message, + }); + return false; + } finally { + setIsLoading(false); + } + }; + + return [deleteMonitor, isLoading]; +}; + +export { useDeleteMonitor }; diff --git a/Client/src/Pages/DistributedUptime/Details/index.jsx b/Client/src/Pages/DistributedUptime/Details/index.jsx index 9915e1811..91dd0d4f0 100644 --- a/Client/src/Pages/DistributedUptime/Details/index.jsx +++ b/Client/src/Pages/DistributedUptime/Details/index.jsx @@ -12,23 +12,31 @@ import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; import GenericFallback from "../../../Components/GenericFallback"; import MonitorCreateHeader from "../../../Components/MonitorCreateHeader"; import SkeletonLayout from "./Components/Skeleton"; +import ControlsHeader from "./Components/ControlsHeader"; +import Dialog from "../../../Components/Dialog"; //Utils import { useTheme } from "@mui/material/styles"; import { useState } from "react"; import { useParams } from "react-router-dom"; import { useIsAdmin } from "../../../Hooks/useIsAdmin"; import { useSubscribeToDetails } from "./Hooks/useSubscribeToDetails"; +import { useDeleteMonitor } from "./Hooks/useDeleteMonitor"; +import { useNavigate } from "react-router-dom"; const DistributedUptimeDetails = () => { const { monitorId } = useParams(); // Local State const [dateRange, setDateRange] = useState("day"); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); // Utils const theme = useTheme(); const isAdmin = useIsAdmin(); + const navigate = useNavigate(); const [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger] = useSubscribeToDetails({ monitorId, dateRange }); + + const [deleteMonitor, isDeleting] = useDeleteMonitor({ monitorId }); // Constants const BREADCRUMBS = [ { name: "Distributed Uptime", path: "/distributed-uptime" }, @@ -76,6 +84,12 @@ const DistributedUptimeDetails = () => { isAdmin={isAdmin} path={`/status/distributed/create/${monitorId}`} /> + { />