diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..d4b59627d Binary files /dev/null and b/.DS_Store differ diff --git a/Client/package-lock.json b/Client/package-lock.json index e21e712ef..194c41ea6 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -11,10 +11,10 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@fontsource/roboto": "^5.0.13", - "@hello-pangea/dnd": "^17.0.0", - "@mui/icons-material": "6.4.3", - "@mui/lab": "6.0.0-beta.26", - "@mui/material": "6.4.3", + "@hello-pangea/dnd": "^18.0.0", + "@mui/icons-material": "6.4.4", + "@mui/lab": "6.0.0-beta.27", + "@mui/material": "6.4.4", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.26.0", "@mui/x-date-pickers": "7.26.0", @@ -450,6 +450,10 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "node_modules/@babel/runtime": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", + "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==", "license": "MIT", "peer": true, "dependencies": { @@ -1161,6 +1165,21 @@ }, "peerDependencies": { "@babel/core": "^7.0.0-0" + "node_modules/@hello-pangea/dnd": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz", + "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.26.7", + "css-box-model": "^1.2.1", + "raf-schd": "^4.0.3", + "react-redux": "^9.2.0", + "redux": "^5.0.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@babel/plugin-transform-json-strings": { @@ -1349,6 +1368,10 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "node_modules/@mui/core-downloads-tracker": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.4.tgz", + "integrity": "sha512-r+J0EditrekkTtO2CnCBCOGpNaDYwJqz8lH4rj6o/anDcskZFJodBlG8aCJkS8DL/CF/9EHS+Gz53EbmYEnQbw==", "license": "MIT", "peer": true, "dependencies": { @@ -1365,6 +1388,10 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "node_modules/@mui/icons-material": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.4.tgz", + "integrity": "sha512-uF1chGaoFmYdRUomK6f8kgJfWosk9A3HXWiVD0vQm+2mE7f25eTQ1E8RRO11LXpnUBqu8Rbv/uGlpnjT/u1Ksg==", "license": "MIT", "peer": true, "dependencies": { @@ -1383,6 +1410,20 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "@mui/material": "^6.4.4", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "6.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.27.tgz", + "integrity": "sha512-weLxPsCs2wJKgWKf46shXHE+x7qlf5VxMK3P+4HsWasMakV/uTmxsoT7PG3QCvakGQ2TdpZtQLE2umJKC0mvKQ==", "license": "MIT", "peer": true, "dependencies": { @@ -1400,10 +1441,49 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^6.4.4", + "@mui/material-pigment-css": "^6.4.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.4.tgz", + "integrity": "sha512-ISVPrIsPQsxnwvS40C4u03AuNSPigFeS2+n1qpuEZ94hDsdMi19dQM2JcC9CHEhXecSIQjP1RTyY0mPiSpSrFQ==", "license": "MIT", "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.4", + "@mui/system": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" }, "engines": { "node": ">=6.9.0" @@ -16881,6 +16961,10 @@ "version": "10.0.6", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "dependencies": { "clsx": "^2.1.0" @@ -17517,6 +17601,10 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "node_modules/prettier": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", + "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/Client/package.json b/Client/package.json index 3d9bbe755..d97fa9408 100644 --- a/Client/package.json +++ b/Client/package.json @@ -14,10 +14,10 @@ "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", "@fontsource/roboto": "^5.0.13", - "@hello-pangea/dnd": "^17.0.0", - "@mui/icons-material": "6.4.3", - "@mui/lab": "6.0.0-beta.26", - "@mui/material": "6.4.3", + "@hello-pangea/dnd": "^18.0.0", + "@mui/icons-material": "6.4.4", + "@mui/lab": "6.0.0-beta.27", + "@mui/material": "6.4.4", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.26.0", "@mui/x-date-pickers": "7.26.0", diff --git a/Client/src/Components/LanguageSelector.jsx b/Client/src/Components/LanguageSelector.jsx index 7a809065f..ca8e9207c 100644 --- a/Client/src/Components/LanguageSelector.jsx +++ b/Client/src/Components/LanguageSelector.jsx @@ -1,17 +1,16 @@ -import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Box, MenuItem, Select, Stack } from "@mui/material"; import { useTheme } from "@emotion/react"; import "flag-icons/css/flag-icons.min.css"; +import { useSelector } from "react-redux"; const LanguageSelector = () => { const { i18n } = useTranslation(); const theme = useTheme(); - const [language, setLanguage] = useState(i18n.language || "gb"); + const { language } = useSelector((state) => state.ui); const handleChange = (event) => { const newLang = event.target.value; - setLanguage(newLang); i18n.changeLanguage(newLang); }; diff --git a/Client/src/Components/Skeletons/FullPage/index.jsx b/Client/src/Components/Skeletons/FullPage/index.jsx new file mode 100644 index 000000000..233fc3c06 --- /dev/null +++ b/Client/src/Components/Skeletons/FullPage/index.jsx @@ -0,0 +1,14 @@ +import { Stack, Skeleton } from "@mui/material"; + +export const SkeletonLayout = () => { + return ( + + + + ); +}; + +export default SkeletonLayout; diff --git a/Client/src/Features/UI/uiSlice.js b/Client/src/Features/UI/uiSlice.js index 4565c6c5d..5731307ec 100644 --- a/Client/src/Features/UI/uiSlice.js +++ b/Client/src/Features/UI/uiSlice.js @@ -23,6 +23,7 @@ const initialState = { greeting: { index: 0, lastUpdate: null }, timezone: "America/Toronto", distributedUptimeEnabled: false, + language: "gb", }; const uiSlice = createSlice({ @@ -51,6 +52,9 @@ const uiSlice = createSlice({ setTimezone(state, action) { state.timezone = action.payload.timezone; }, + setLanguage(state, action) { + state.language = action.payload; + }, }, }); @@ -62,4 +66,5 @@ export const { setGreeting, setTimezone, setDistributedUptimeEnabled, + setLanguage, } = uiSlice.actions; 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}`} /> + { />