diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx
index 1d4a0fa41..9c63bd1c2 100644
--- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx
+++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx
@@ -73,7 +73,14 @@ const TeamPanel = () => {
useEffect(() => {
let team = members;
if (filter !== "all")
- team = members.filter((member) => member.role.includes(filter));
+ team = members.filter((member) => {
+ if (filter === "admin") {
+ return (
+ member.role.includes("admin") || member.role.includes("superadmin")
+ );
+ }
+ return member.role.includes(filter);
+ });
const data = {
cols: [
diff --git a/Client/src/Features/Auth/authSlice.js b/Client/src/Features/Auth/authSlice.js
index fa1f0471e..f12758715 100644
--- a/Client/src/Features/Auth/authSlice.js
+++ b/Client/src/Features/Auth/authSlice.js
@@ -1,6 +1,7 @@
import { networkService } from "../../main";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { jwtDecode } from "jwt-decode";
+import axios from "axios";
const initialState = {
isLoading: false,
@@ -57,9 +58,8 @@ export const update = createAsyncThunk(
form.password && fd.append("password", form.password);
form.newPassword && fd.append("newPassword", form.newPassword);
if (form.file && form.file !== "") {
- const imageResult = await networkService.get(form.file, {
+ const imageResult = await axios.get(form.file, {
responseType: "blob",
- baseURL: "",
});
fd.append("profileImage", imageResult.data);
}
diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
index 237d9d3dc..79af537e0 100644
--- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
+++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
@@ -28,6 +28,26 @@ export const createUptimeMonitor = createAsyncThunk(
}
);
+export const getUptimeMonitorById = createAsyncThunk(
+ "monitors/getMonitorById",
+ async (data, thunkApi) => {
+ try {
+ const { authToken, monitorId } = data;
+ const res = await networkService.getMonitorByid(authToken, monitorId);
+ return res.data;
+ } catch (error) {
+ if (error.response && error.response.data) {
+ return thunkApi.rejectWithValue(error.response.data);
+ }
+ const payload = {
+ status: false,
+ msg: error.message ? error.message : "Unknown error",
+ };
+ return thunkApi.rejectWithValue(payload);
+ }
+ }
+);
+
export const getUptimeMonitorsByTeamId = createAsyncThunk(
"montiors/getMonitorsByTeamId",
async (token, thunkApi) => {
@@ -109,6 +129,26 @@ export const deleteUptimeMonitor = createAsyncThunk(
}
);
+export const pauseUptimeMonitor = createAsyncThunk(
+ "monitors/pauseMonitor",
+ async (data, thunkApi) => {
+ try {
+ const { authToken, monitorId } = data;
+ const res = await networkService.pauseMonitorById(authToken, monitorId);
+ return res.data;
+ } catch (error) {
+ if (error.response && error.response.data) {
+ return thunkApi.rejectWithValue(error.response.data);
+ }
+ const payload = {
+ status: false,
+ msg: error.message ? error.message : "Unknown error",
+ };
+ return thunkApi.rejectWithValue(payload);
+ }
+ }
+);
+
const uptimeMonitorsSlice = createSlice({
name: "uptimeMonitors",
initialState,
@@ -160,7 +200,24 @@ const uptimeMonitorsSlice = createSlice({
? action.payload.msg
: "Failed to create uptime monitor";
})
-
+ // *****************************************************
+ // Get Monitor By Id
+ // *****************************************************
+ .addCase(getUptimeMonitorById.pending, (state) => {
+ state.isLoading = true;
+ })
+ .addCase(getUptimeMonitorById.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.success = action.payload.success;
+ state.msg = action.payload.msg;
+ })
+ .addCase(getUptimeMonitorById.rejected, (state, action) => {
+ state.isLoading = false;
+ state.success = false;
+ state.msg = action.payload
+ ? action.payload.msg
+ : "Failed to pause uptime monitor";
+ })
// *****************************************************
// update Monitor
// *****************************************************
@@ -197,6 +254,24 @@ const uptimeMonitorsSlice = createSlice({
state.msg = action.payload
? action.payload.msg
: "Failed to delete uptime monitor";
+ })
+ // *****************************************************
+ // Pause Monitor
+ // *****************************************************
+ .addCase(pauseUptimeMonitor.pending, (state) => {
+ state.isLoading = true;
+ })
+ .addCase(pauseUptimeMonitor.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.success = action.payload.success;
+ state.msg = action.payload.msg;
+ })
+ .addCase(pauseUptimeMonitor.rejected, (state, action) => {
+ state.isLoading = false;
+ state.success = false;
+ state.msg = action.payload
+ ? action.payload.msg
+ : "Failed to pause uptime monitor";
});
},
});
diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx
index 6c1767446..aa80b35ce 100644
--- a/Client/src/Pages/Incidents/IncidentTable/index.jsx
+++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx
@@ -146,6 +146,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
Monitor Name
Status
Date & Time
+ Status Code
Message
@@ -166,7 +167,10 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
{new Date(check.createdAt).toLocaleString()}
- {check.statusCode}
+
+ {check.statusCode ? check.statusCode : "N/A"}
+
+ {check.message}
);
})}
diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx
index 3a546c582..9a90c5512 100644
--- a/Client/src/Pages/Monitors/Configure/index.jsx
+++ b/Client/src/Pages/Monitors/Configure/index.jsx
@@ -2,13 +2,15 @@ import { useNavigate, useParams } from "react-router";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
-import { Box, Modal, Skeleton, Stack, Typography } from "@mui/material";
+import { Box, Modal, Stack, Typography } from "@mui/material";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { logger } from "../../../Utils/Logger";
import { ConfigBox } from "../styled";
import {
updateUptimeMonitor,
+ pauseUptimeMonitor,
+ getUptimeMonitorById,
getUptimeMonitorsByTeamId,
deleteUptimeMonitor,
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
@@ -20,7 +22,8 @@ import Checkbox from "../../../Components/Inputs/Checkbox";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import PulseDot from "../../../Components/Animated/PulseDot";
import "./index.css";
-
+import SkeletonLayout from "./skeleton";
+import ButtonSpinner from "../../../Components/ButtonSpinner";
/**
* Parses a URL string and returns a URL object.
*
@@ -35,54 +38,6 @@ const parseUrl = (url) => {
}
};
-/**
- * Renders a skeleton layout.
- *
- * @returns {JSX.Element}
- */
-const SkeletonLayout = () => {
- const theme = useTheme();
-
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
/**
* Configure page displays monitor configurations and allows for editing actions.
* @component
@@ -93,7 +48,7 @@ const Configure = () => {
const theme = useTheme();
const dispatch = useDispatch();
const { user, authToken } = useSelector((state) => state.auth);
- const { monitors } = useSelector((state) => state.uptimeMonitors);
+ const { isLoading } = useSelector((state) => state.uptimeMonitors);
const [monitor, setMonitor] = useState({});
const [errors, setErrors] = useState({});
const { monitorId } = useParams();
@@ -107,15 +62,25 @@ const Configure = () => {
};
useEffect(() => {
- const data = monitors.find((monitor) => monitor._id === monitorId);
- if (!data) {
- logger.error("Error fetching monitor of id: " + monitorId);
- navigate("/not-found", { replace: true });
- }
- setMonitor({
- ...data,
- });
- }, [monitorId, authToken, monitors, navigate]);
+ const fetchMonitor = async () => {
+ try {
+ const action = await dispatch(
+ getUptimeMonitorById({ authToken, monitorId })
+ );
+
+ if (getUptimeMonitorById.fulfilled.match(action)) {
+ const monitor = action.payload.data;
+ setMonitor(monitor);
+ } else if (getUptimeMonitorById.rejected.match(action)) {
+ throw new Error(action.error.message);
+ }
+ } catch (error) {
+ logger.error("Error fetching monitor of id: " + monitorId);
+ navigate("/not-found", { replace: true });
+ }
+ };
+ fetchMonitor();
+ }, [monitorId, authToken, navigate]);
const handleChange = (event, name) => {
let { value, id } = event.target;
@@ -171,6 +136,23 @@ const Configure = () => {
}
};
+ const handlePause = async () => {
+ try {
+ const action = await dispatch(
+ pauseUptimeMonitor({ authToken, monitorId })
+ );
+ if (pauseUptimeMonitor.fulfilled.match(action)) {
+ const monitor = action.payload.data;
+ setMonitor(monitor);
+ } else if (pauseUptimeMonitor.rejected.match(action)) {
+ throw new Error(action.error.message);
+ }
+ } catch (error) {
+ logger.error("Error pausing monitor: " + monitorId);
+ createToast({ body: "Failed to pause monitor" });
+ }
+ };
+
const handleSubmit = async (event) => {
event.preventDefault();
const action = await dispatch(
@@ -207,11 +189,9 @@ const Configure = () => {
const parsedUrl = parseUrl(monitor?.url);
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
- let loading = Object.keys(monitor).length === 0;
-
return (
- {loading ? (
+ {Object.keys(monitor).length === 0 ? (
) : (
<>
@@ -268,9 +248,10 @@ const Configure = () => {
ml: "auto",
}}
>
- }
sx={{
@@ -282,8 +263,10 @@ const Configure = () => {
mr: theme.spacing(2),
},
}}
+ onClick={handlePause}
/>
-
-
} The response from the axios GET request.
+ */
+
+ async getMonitorByid(authToken, monitorId) {
+ return this.axiosInstance.get(`/monitors/${monitorId}`, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+ }
+
/**
*
* ************************************
@@ -163,6 +184,28 @@ class NetworkService {
},
});
}
+ /**
+ * ************************************
+ * Pauses a single monitor by its ID
+ * ************************************
+ *
+ * @async
+ * @param {string} authToken - The authorization token to be used in the request header.
+ * @param {string} monitorId - The ID of the monitor to be paused.
+ * @returns {Promise} The response from the axios POST request.
+ */
+ async pauseMonitorById(authToken, monitorId) {
+ return this.axiosInstance.post(
+ `/monitors/pause/${monitorId}`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ }
/**
* ************************************
@@ -226,7 +269,6 @@ class NetworkService {
return this.axiosInstance.put(`/auth/user/${userId}`, form, {
headers: {
Authorization: `Bearer ${authToken}`,
- "Content-Type": "application/json",
},
});
}
diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js
index c3fe37955..e7147b1cc 100644
--- a/Server/controllers/monitorController.js
+++ b/Server/controllers/monitorController.js
@@ -5,6 +5,7 @@ const {
createMonitorBodyValidation,
editMonitorBodyValidation,
getMonitorsByTeamIdQueryValidation,
+ pauseMonitorParamValidation,
} = require("../validation/joi");
const sslChecker = require("ssl-checker");
@@ -123,7 +124,7 @@ const getMonitorById = async (req, res, next) => {
}
try {
- const monitor = await req.db.getMonitorById(req, res);
+ const monitor = await req.db.getMonitorById(req.params.monitorId);
if (!monitor) {
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
error.status = 404;
@@ -309,7 +310,7 @@ const editMonitor = async (req, res, next) => {
// Get notifications from the request body
const notifications = req.body.notifications;
- const editedMonitor = await req.db.editMonitor(req, res);
+ const editedMonitor = await req.db.editMonitor(monitorId, req.body);
await req.db.deleteNotificationsByMonitorId(editedMonitor._id);
@@ -337,6 +338,43 @@ const editMonitor = async (req, res, next) => {
}
};
+const pauseMonitor = async (req, res, next) => {
+ try {
+ await pauseMonitorParamValidation.validateAsync(req.params);
+ } catch (error) {
+ error.status = 422;
+ error.service = SERVICE_NAME;
+ error.message =
+ error.details?.[0]?.message || error.message || "Validation Error";
+ next(error);
+ }
+
+ try {
+ const monitor = await req.db.getMonitorById(req.params.monitorId);
+ if (monitor.isActive) {
+ await req.jobQueue.deleteJob(monitor);
+ } else {
+ await req.jobQueue.addJob(monitor._id, monitor);
+ }
+ monitor.isActive = !monitor.isActive;
+ const updatedMonitor = await req.db.editMonitor(
+ req.params.monitorId,
+ monitor
+ );
+ return res.status(200).json({
+ success: true,
+ msg: updatedMonitor.isActive
+ ? successMessages.MONITOR_RESUME
+ : successMessages.MONITOR_PAUSE,
+ data: updatedMonitor,
+ });
+ } catch (error) {
+ error.service = SERVICE_NAME;
+ error.method = "pauseMonitor";
+ next(error);
+ }
+};
+
module.exports = {
getAllMonitors,
getMonitorStatsById,
@@ -347,4 +385,5 @@ module.exports = {
deleteMonitor,
deleteAllMonitors,
editMonitor,
+ pauseMonitor,
};
diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js
index 9dda77007..7a86f1891 100644
--- a/Server/db/mongo/modules/monitorModule.js
+++ b/Server/db/mongo/modules/monitorModule.js
@@ -15,7 +15,7 @@ const { NormalizeData } = require("../../../utils/dataUtils");
*/
const getAllMonitors = async (req, res) => {
try {
- const monitors = await Monitor.find({ isActive: true });
+ const monitors = await Monitor.find();
return monitors;
} catch (error) {
throw error;
@@ -431,9 +431,7 @@ const deleteMonitorsByUserId = async (userId) => {
* @returns {Promise}
* @throws {Error}
*/
-const editMonitor = async (req, res) => {
- const candidateId = req.params.monitorId;
- const candidateMonitor = req.body;
+const editMonitor = async (candidateId, candidateMonitor) => {
candidateMonitor.notifications = undefined;
try {
diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js
index d29c1889b..1a5c19f36 100644
--- a/Server/db/mongo/modules/userModule.js
+++ b/Server/db/mongo/modules/userModule.js
@@ -89,7 +89,6 @@ const getUserByEmail = async (email) => {
const updateUser = async (req, res) => {
const candidateUserId = req.params.userId;
-
try {
const candidateUser = { ...req.body };
// ******************************************
diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js
index e08c43b7d..ee1c08488 100644
--- a/Server/routes/monitorRoute.js
+++ b/Server/routes/monitorRoute.js
@@ -31,4 +31,11 @@ router.delete(
isAllowed(["superadmin"]),
monitorController.deleteAllMonitors
);
+
+router.post(
+ "/pause/:monitorId",
+ isAllowed(["admin", "superadmin"]),
+ monitorController.pauseMonitor
+);
+
module.exports = router;
diff --git a/Server/utils/messages.js b/Server/utils/messages.js
index d7de90caf..53a8f689f 100644
--- a/Server/utils/messages.js
+++ b/Server/utils/messages.js
@@ -87,6 +87,8 @@ const successMessages = {
//Job Queue
JOB_QUEUE_DELETE_JOB: "Job removed successfully",
JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!",
+ JOB_QUEUE_PAUSE_JOB: "Job paused successfully",
+ JOB_QUEUE_RESUME_JOB: "Job resumed successfully",
//Maintenance Window Controller
MAINTENANCE_WINDOW_CREATE: "Maintenance Window created successfully",
diff --git a/Server/validation/joi.js b/Server/validation/joi.js
index 714eff5cf..2a7ff173d 100644
--- a/Server/validation/joi.js
+++ b/Server/validation/joi.js
@@ -197,6 +197,10 @@ const editMonitorBodyValidation = joi.object({
notifications: joi.array().items(joi.object()),
});
+const pauseMonitorParamValidation = joi.object({
+ monitorId: joi.string().required(),
+});
+
//****************************************
// Alerts
//****************************************
@@ -350,6 +354,7 @@ module.exports = {
getMonitorsByTeamIdValidation,
getMonitorsByTeamIdQueryValidation,
editMonitorBodyValidation,
+ pauseMonitorParamValidation,
editUserParamValidation,
editUserBodyValidation,
createAlertParamValidation,