From 0cf056f7258b0d4aa62709beca3f86fd70c711ab Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 12 Feb 2025 13:44:04 -0800 Subject: [PATCH 01/14] move field parsing to network util --- Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From b015f7b61a2054a878e6e9542cfbd79192d37875 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 12 Feb 2025 13:44:56 -0800 Subject: [PATCH 02/14] destructure var, build form to update monitor --- Client/src/Utils/NetworkService.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 515ae5930..0a7900e1c 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -99,9 +99,11 @@ class NetworkService { * @returns {Promise} The response from the axios POST request. */ async createMonitor(config) { - return this.axiosInstance.post(`/monitors`, config.monitor, { + const { authToken, monitor } = config; + + return this.axiosInstance.post(`/monitors`, monitor, { headers: { - Authorization: `Bearer ${config.authToken}`, + Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", }, }); @@ -284,9 +286,16 @@ class NetworkService { * @returns {Promise} The response from the axios PUT request. */ async updateMonitor(config) { - return this.axiosInstance.put(`/monitors/${config.monitorId}`, config.updatedFields, { + const { authToken, monitorId, monitor } = config; + const updatedFields = { + name: monitor.name, + description: monitor.description, + interval: monitor.interval, + notifications: monitor.notifications, + }; + return this.axiosInstance.put(`/monitors/${monitorId}`, updatedFields, { headers: { - Authorization: `Bearer ${config.authToken}`, + Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", }, }); From 89a542e804e664b687d265b2e5870f2d0e40c11d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 12 Feb 2025 13:45:38 -0800 Subject: [PATCH 03/14] Add a config route for DU --- Client/src/Routes/index.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index c36a008a7..bf88c92f0 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -107,6 +107,14 @@ const Routes = () => { } /> + + + + } + /> Date: Wed, 12 Feb 2025 13:46:09 -0800 Subject: [PATCH 04/14] simpler differentiation between creating and updating --- Client/src/Pages/StatusPage/Create/index.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Client/src/Pages/StatusPage/Create/index.jsx b/Client/src/Pages/StatusPage/Create/index.jsx index f54189fe8..22bdaa3e2 100644 --- a/Client/src/Pages/StatusPage/Create/index.jsx +++ b/Client/src/Pages/StatusPage/Create/index.jsx @@ -12,7 +12,6 @@ import { useMonitorsFetch } from "./Hooks/useMonitorsFetch"; import { useCreateStatusPage } from "./Hooks/useCreateStatusPage"; import { createToast } from "../../../Utils/toastUtils"; import { useNavigate } from "react-router-dom"; -import { useLocation } from "react-router-dom"; import { useStatusPageFetch } from "../Status/Hooks/useStatusPageFetch"; import { useParams } from "react-router-dom"; @@ -47,8 +46,7 @@ const CreateStatusPage = () => { const intervalRef = useRef(null); // Setup - const location = useLocation(); - const isCreate = location.pathname === "/status/uptime/create"; + const isCreate = typeof url === "undefined"; //Utils const theme = useTheme(); From f72dd799c4acba2c449f70458235f64ecf04bb5b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 12 Feb 2025 13:46:36 -0800 Subject: [PATCH 05/14] fix incorrect field parsing --- .../Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx index 10fffe5bd..4d4437635 100644 --- a/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx +++ b/Client/src/Pages/Uptime/Monitors/Components/UptimeDataTable/index.jsx @@ -101,7 +101,7 @@ const UptimeDataTable = ({ From dc84bd8848d688b85d4623a70c25bc33846113b9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 12 Feb 2025 13:47:21 -0800 Subject: [PATCH 06/14] fix loading condtitional, correct row renderng --- .../Monitors/Components/MonitorTable/index.jsx | 2 +- Client/src/Pages/DistributedUptime/Monitors/index.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx b/Client/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx index 203a8c2b4..ebe0fb94a 100644 --- a/Client/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx +++ b/Client/src/Pages/DistributedUptime/Monitors/Components/MonitorTable/index.jsx @@ -29,7 +29,7 @@ const MonitorTable = ({ isLoading, monitors }) => { diff --git a/Client/src/Pages/DistributedUptime/Monitors/index.jsx b/Client/src/Pages/DistributedUptime/Monitors/index.jsx index 159d265a0..2111ddca3 100644 --- a/Client/src/Pages/DistributedUptime/Monitors/index.jsx +++ b/Client/src/Pages/DistributedUptime/Monitors/index.jsx @@ -41,7 +41,7 @@ const DistributedUptimeMonitors = () => { ); } - if (typeof monitorsSummary !== "undefined" && monitorsSummary.totalMonitors === 0) { + if (typeof monitorsSummary === "undefined" || monitorsSummary.totalMonitors === 0) { return ( Date: Wed, 12 Feb 2025 13:48:01 -0800 Subject: [PATCH 07/14] implement cconfig using create component --- .../useCreateDistributedUptimeMonitor.jsx | 33 +++++++ .../Create/Hooks/useMonitorFetch.jsx | 32 ++++++ .../Pages/DistributedUptime/Create/index.jsx | 98 +++++++++++++------ 3 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 Client/src/Pages/DistributedUptime/Create/Hooks/useCreateDistributedUptimeMonitor.jsx create mode 100644 Client/src/Pages/DistributedUptime/Create/Hooks/useMonitorFetch.jsx 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..dfda12198 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}`} /> + { />