diff --git a/Client/src/Components/Inputs/Field/index.jsx b/Client/src/Components/Inputs/Field/index.jsx index 1d9034c66..6fb8ea612 100644 --- a/Client/src/Components/Inputs/Field/index.jsx +++ b/Client/src/Components/Inputs/Field/index.jsx @@ -177,7 +177,7 @@ Field.propTypes = { autoComplete: PropTypes.string, placeholder: PropTypes.string, value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, + onChange: PropTypes.func, error: PropTypes.string, disabled: PropTypes.bool, }; diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 8302aa1e5..bf7f0378e 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -68,6 +68,61 @@ export const getUptimeMonitorsByUserId = createAsyncThunk( } ); +export const updateUptimeMonitor = createAsyncThunk( + "monitors/updateMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const updatedFields = { + name: monitor.name, + description: monitor.description, + interval: monitor.interval, + }; + const res = await axiosInstance.post( + `/monitors/edit/${monitor._id}`, + updatedFields, + { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + } + ); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + +export const deleteUptimeMonitor = createAsyncThunk( + "monitors/deleteMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await axiosInstance.post( + `/monitors/delete/${monitor._id}`, + {}, + { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + } + ); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + return thunkApi.rejectWithValue(error.message); + } + } +); + const uptimeMonitorsSlice = createSlice({ name: "uptimeMonitors", initialState, @@ -137,6 +192,44 @@ const uptimeMonitorsSlice = createSlice({ state.msg = action.payload ? action.payload.msg : "Failed to create uptime monitor"; + }) + + // ***************************************************** + // update Monitor + // ***************************************************** + .addCase(updateUptimeMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateUptimeMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(updateUptimeMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to update uptime monitor"; + }) + + // ***************************************************** + // Delete Monitor + // ***************************************************** + .addCase(deleteUptimeMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteUptimeMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteUptimeMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete uptime monitor"; }); }, }); diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx index b50942e89..9a2f44d5f 100644 --- a/Client/src/Pages/Monitors/Configure/index.jsx +++ b/Client/src/Pages/Monitors/Configure/index.jsx @@ -4,8 +4,7 @@ import { useDispatch, useSelector } from "react-redux"; import { useEffect, useState } from "react"; import Button from "../../../Components/Button"; import Field from "../../../Components/Inputs/Field"; -import RadioButton from "../../../Components/RadioButton"; -import { Box, MenuItem, Stack, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import WestRoundedIcon from "@mui/icons-material/WestRounded"; import GreenCheck from "../../../assets/icons/checkbox-green.svg?react"; import RedCheck from "../../../assets/icons/checkbox-red.svg?react"; @@ -15,6 +14,25 @@ import "./index.css"; import { monitorValidation } from "../../../Validation/validation"; import Select from "../../../Components/Inputs/Select"; import { formatDurationRounded } from "../../../Utils/timeUtils"; +import { createToast } from "../../../Utils/toastUtils"; +import { + updateUptimeMonitor, + getUptimeMonitorsByUserId, + deleteUptimeMonitor, +} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; +/** + * Parses a URL string and returns a URL object. + * + * @param {string} url - The URL string to parse. + * @returns {URL} - The parsed URL object if valid, otherwise an empty string. + */ +const parseUrl = (url) => { + try { + return new URL(url); + } catch (error) { + return null; + } +}; /** * Helper function to get duration since last check @@ -39,6 +57,9 @@ const Configure = () => { const dispatch = useDispatch(); const { authToken } = useSelector((state) => state.auth); const { monitors } = useSelector((state) => state.uptimeMonitors); + const [monitor, setMonitor] = useState(); + const [duration, setDuration] = useState(0); + const [errors, setErrors] = useState({}); const { monitorId } = useParams(); const idMap = { @@ -48,26 +69,20 @@ const Configure = () => { "monitor-checks-ping": "type", }; - const [config, setConfig] = useState(); - const [monitor, setMonitor] = useState(); - const [errors, setErrors] = useState({}); useEffect(() => { - const fetchMonitor = () => { - const data = monitors.find((monitor) => monitor._id === monitorId); - setConfig(data); - setMonitor({ - name: data.name, - url: data.url.replace(/^https?:\/\//, ""), - type: data.type, - interval: data.interval / MS_PER_MINUTE, - }); - }; - fetchMonitor(); - }, [monitorId, authToken]); + const data = monitors.find((monitor) => monitor._id === monitorId); + setMonitor({ + ...data, + }); + setDuration(formatDurationRounded(data?.interval)); + }, [monitorId, authToken, monitors]); const handleChange = (event, name) => { - const { value, id } = event.target; + let { value, id } = event.target; if (!name) name = idMap[id]; + if (name === "interval") { + value = value * MS_PER_MINUTE; + } setMonitor((prev) => ({ ...prev, [name]: value, @@ -88,9 +103,28 @@ const Configure = () => { }); }; - const handleSubmit = (event) => { + const handleSubmit = async (event) => { event.preventDefault(); - // TODO + const action = await dispatch( + updateUptimeMonitor({ authToken, monitor: monitor }) + ); + if (action.meta.requestStatus === "fulfilled") { + createToast({ body: "Monitor updated successfully!" }); + dispatch(getUptimeMonitorsByUserId(authToken)); + } else { + createToast({ body: "Failed to update monitor." }); + } + }; + + const hanldeRemove = async (event) => { + event.preventDefault(); + // TODO add confirm + const action = await dispatch(deleteUptimeMonitor({ authToken, monitor })); + if (action.meta.requestStatus === "fulfilled") { + navigate("/monitors"); + } else { + createToast({ body: "Failed to delete monitor." }); + } }; const frequencies = [ @@ -101,6 +135,10 @@ const Configure = () => { { _id: 5, name: "5 minutes" }, ]; + // Parse the URL + const parsedUrl = parseUrl(monitor?.url); + const protocol = parsedUrl?.protocol?.replace(":", "") || ""; + return (