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 (
{
/>
-
-
- Checks to perform
-
- You can always add or remove checks after adding your site.
-
-
-
-
-
-
- {errors["type"] ? (
-
- {errors["type"]}
-
- ) : (
- ""
- )}
-
-
-
{