diff --git a/Client/src/App.jsx b/Client/src/App.jsx
index cad8a4ebd..12db9dab9 100644
--- a/Client/src/App.jsx
+++ b/Client/src/App.jsx
@@ -26,6 +26,7 @@ import PageSpeed from "./Pages/PageSpeed";
import CreatePageSpeed from "./Pages/PageSpeed/CreatePageSpeed";
import CreateNewMaintenanceWindow from "./Pages/Maintenance/CreateMaintenanceWindow";
import PageSpeedDetails from "./Pages/PageSpeed/Details";
+import PageSpeedConfigure from "./Pages/PageSpeed/Configure";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
@@ -103,6 +104,10 @@ function App() {
path="pagespeed/:monitorId"
element={}
/>
+ }
+ />
} />
diff --git a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx
index 05f3d8412..355b09ca6 100644
--- a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx
+++ b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx
@@ -352,7 +352,7 @@ const ProfilePanel = () => {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
- width: 400,
+ width: 500,
bgcolor: "white",
border: "solid 1px #f2f2f2",
borderRadius: `${theme.shape.borderRadius}px`,
diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
index 6524a3417..4377edb35 100644
--- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
+++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js
@@ -68,6 +68,58 @@ export const getPageSpeedByUserId = createAsyncThunk(
}
);
+export const updatePageSpeed = createAsyncThunk(
+ "pageSpeedMonitors/updatePageSpeed",
+ async (data, thunkApi) => {
+ try {
+ const { authToken, monitor } = data;
+ const updatedFields = {
+ name: monitor.name,
+ description: monitor.description,
+ interval: monitor.interval,
+ // notifications: monitor.notifications,
+ };
+ const res = await axiosInstance.put(
+ `/monitors/${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 deletePageSpeed = createAsyncThunk(
+ "pageSpeedMonitors/deletePageSpeed",
+ async (data, thunkApi) => {
+ try {
+ const { authToken, monitor } = data;
+ const res = await axiosInstance.delete(`/monitors/${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 pageSpeedMonitorSlice = createSlice({
name: "pageSpeedMonitor",
initialState,
@@ -137,6 +189,44 @@ const pageSpeedMonitorSlice = createSlice({
state.msg = action.payload
? action.payload.msg
: "Failed to create page speed monitor";
+ })
+
+ // *****************************************************
+ // Create Monitor
+ // *****************************************************
+ .addCase(updatePageSpeed.pending, (state) => {
+ state.isLoading = true;
+ })
+ .addCase(updatePageSpeed.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.success = action.payload.success;
+ state.msg = action.payload.msg;
+ })
+ .addCase(updatePageSpeed.rejected, (state, action) => {
+ state.isLoading = false;
+ state.success = false;
+ state.msg = action.payload
+ ? action.payload.msg
+ : "Failed to update page speed monitor";
+ })
+
+ // *****************************************************
+ // Delete Monitor
+ // *****************************************************
+ .addCase(deletePageSpeed.pending, (state) => {
+ state.isLoading = true;
+ })
+ .addCase(deletePageSpeed.fulfilled, (state, action) => {
+ state.isLoading = false;
+ state.success = action.payload.success;
+ state.msg = action.payload.msg;
+ })
+ .addCase(deletePageSpeed.rejected, (state, action) => {
+ state.isLoading = false;
+ state.success = false;
+ state.msg = action.payload
+ ? action.payload.msg
+ : "Failed to delete page speed monitor";
});
},
});
diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
index 4c2acd3ec..e69e6d41d 100644
--- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
+++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js
@@ -80,7 +80,7 @@ export const updateUptimeMonitor = createAsyncThunk(
notifications: monitor.notifications,
};
const res = await axiosInstance.put(
- `/monitors/edit/${monitor._id}`,
+ `/monitors/${monitor._id}`,
updatedFields,
{
headers: {
diff --git a/Client/src/Pages/Monitors/Configure/index.jsx b/Client/src/Pages/Monitors/Configure/index.jsx
index 0df88078f..08c8cbe39 100644
--- a/Client/src/Pages/Monitors/Configure/index.jsx
+++ b/Client/src/Pages/Monitors/Configure/index.jsx
@@ -460,10 +460,10 @@ const Configure = () => {
}}
>
- Really delete this monitor?
+ Do you really want to delete this monitor?
- Once deleted, this monitor cannot be retrieved back.
+ Once deleted, this monitor cannot be retrieved.
{
const { authToken } = useSelector((state) => state.auth);
@@ -53,63 +58,76 @@ const PaginationTable = ({ monitorId, dateRange }) => {
const handlePageChange = (_, newPage) => {
setPaginationController({
...paginationController,
- page: newPage,
- });
- };
-
- const handleChangeRowsPerPage = (event) => {
- setPaginationController({
- ...paginationController,
- rowsPerPage: parseInt(event.target.value, 10),
- page: 0,
+ page: newPage - 1,
});
};
+
+ let paginationComponent = <>>;
+ if (checksCount > paginationController.rowsPerPage) {
+ paginationComponent = (
+ (
+
+ )}
+ />
+ );
+ }
return (
-
-
-
-
- Status
- Date & Time
- Message
-
-
-
- {checks.map((check) => {
- const status = check.status === true ? "up" : "down";
+ <>
+
+
+
+
+ Status
+ Date & Time
+ Message
+
+
+
+ {checks.map((check) => {
+ const status = check.status === true ? "up" : "down";
- return (
-
-
-
-
-
- {new Date(check.createdAt).toLocaleString()}
-
- {check.statusCode}
-
- );
- })}
-
-
-
-
-
-
-
-
+ return (
+
+
+
+
+
+ {new Date(check.createdAt).toLocaleString()}
+
+ {check.statusCode}
+
+ );
+ })}
+
+
+
+ {paginationComponent}
+ >
);
};
diff --git a/Client/src/Pages/Monitors/index.css b/Client/src/Pages/Monitors/index.css
index 67da49c1f..7b40f7464 100644
--- a/Client/src/Pages/Monitors/index.css
+++ b/Client/src/Pages/Monitors/index.css
@@ -12,7 +12,6 @@
.monitors p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
}
-.monitors h2:has(+ p),
.monitors p.MuiTypography-root {
color: var(--env-var-color-5);
}
diff --git a/Client/src/Pages/PageSpeed/Configure/index.css b/Client/src/Pages/PageSpeed/Configure/index.css
new file mode 100644
index 000000000..577d18389
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Configure/index.css
@@ -0,0 +1,68 @@
+.configure-pagespeed form > .MuiStack-root:nth-child(2) {
+ background-color: var(--env-var-color-8);
+ border: solid 1px var(--env-var-color-6);
+ border-radius: var(--env-var-radius-1);
+ padding: var(--env-var-default-2);
+}
+.configure-pagespeed h1.MuiTypography-root {
+ font-size: var(--env-var-font-size-large-plus);
+}
+.configure-pagespeed h1.MuiTypography-root,
+.configure-pagespeed h2.MuiTypography-root {
+ color: var(--env-var-color-1);
+}
+.configure-pagespeed span.MuiTypography-root {
+ font-size: var(--env-var-font-size-small-plus);
+}
+.configure-pagespeed p.MuiTypography-root,
+.configure-pagespeed h3.MuiTypography-root {
+ color: var(--env-var-color-5);
+}
+.configure-pagespeed h1.MuiTypography-root,
+.configure-pagespeed h3.MuiTypography-root,
+.configure-pagespeed h2.MuiTypography-root {
+ font-weight: 600;
+}
+.configure-pagespeed p.MuiTypography-root,
+.configure-pagespeed h3.MuiTypography-root,
+.configure-pagespeed h3.MuiTypography-root > span.MuiTypography-root {
+ font-size: var(--env-var-font-size-medium);
+}
+.configure-pagespeed .MuiBox-root > .field + p.MuiTypography-root {
+ font-size: var(--env-var-font-size-small-plus);
+}
+.configure-pagespeed .MuiBox-root > .field + p.MuiTypography-root,
+.configure-pagespeed h3.MuiTypography-root > span.MuiTypography-root {
+ opacity: 0.6;
+}
+.configure-pagespeed .checkbox-wrapper {
+ margin-bottom: 1px;
+}
+.configure-pagespeed .MuiInputBase-root:has(> .Mui-disabled) {
+ background-color: var(--env-var-color-13);
+}
+/* to be removed */
+.configure-pagespeed .section-disabled {
+ opacity: 0.4;
+ background-color: #f1f1f2;
+ padding: 10px;
+ border-radius: 8px;
+ user-select: none;
+ pointer-events: none;
+}
+
+.configure-pagespeed button {
+ height: var(--env-var-height-2);
+}
+
+body:has(.configure-pagespeed) .select-dropdown .MuiMenuItem-root,
+.configure-pagespeed .select-wrapper .select-component > .MuiSelect-select {
+ text-transform: lowercase;
+}
+
+.configure-pagespeed .field,
+.configure-pagespeed .section-disabled,
+.configure-pagespeed .select-wrapper,
+.configure-pagespeed h3.MuiTypography-root {
+ flex: 1;
+}
diff --git a/Client/src/Pages/PageSpeed/Configure/index.jsx b/Client/src/Pages/PageSpeed/Configure/index.jsx
new file mode 100644
index 000000000..91d250dd9
--- /dev/null
+++ b/Client/src/Pages/PageSpeed/Configure/index.jsx
@@ -0,0 +1,365 @@
+import { useEffect, useState } from "react";
+import { useTheme } from "@emotion/react";
+import { Box, Modal, Skeleton, Stack, Typography } from "@mui/material";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate, useParams } from "react-router";
+import {
+ deletePageSpeed,
+ getPageSpeedByUserId,
+ updatePageSpeed,
+} from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
+import { monitorValidation } from "../../../Validation/validation";
+import { createToast } from "../../../Utils/toastUtils";
+import Button from "../../../Components/Button";
+import Field from "../../../Components/Inputs/Field";
+import Select from "../../../Components/Inputs/Select";
+import Checkbox from "../../../Components/Inputs/Checkbox";
+import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
+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";
+
+import "./index.css";
+
+/**
+ * Renders a skeleton layout.
+ *
+ * @returns {JSX.Element}
+ */
+const SkeletonLayout = () => {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const PageSpeedConfigure = () => {
+ const theme = useTheme();
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const MS_PER_MINUTE = 60000;
+ const { authToken } = useSelector((state) => state.auth);
+ const { monitors } = useSelector((state) => state.pageSpeedMonitors);
+ const { monitorId } = useParams();
+ const [monitor, setMonitor] = useState({});
+ const [errors, setErrors] = useState({});
+
+ const frequencies = [
+ { _id: 3, name: "3 minutes" },
+ { _id: 5, name: "5 minutes" },
+ { _id: 10, name: "10 minutes" },
+ { _id: 20, name: "20 minutes" },
+ { _id: 60, name: "1 hour" },
+ { _id: 1440, name: "1 day" },
+ { _id: 10080, name: "1 week" },
+ ];
+
+ useEffect(() => {
+ const data = monitors.find((monitor) => monitor._id === monitorId);
+ if (!data) {
+ console.error("Error fetching pagespeed monitor of id: " + monitorId);
+ navigate("/not-found");
+ }
+ setMonitor({
+ ...data,
+ });
+ }, [monitorId]);
+
+ const handleChange = (event, id) => {
+ let { value } = event.target;
+ if (id === "interval") {
+ value = value * MS_PER_MINUTE;
+ }
+ setMonitor((prev) => ({ ...prev, [id]: value }));
+
+ const { error } = monitorValidation.validate(
+ { [id]: value },
+ { abortEarly: false }
+ );
+
+ setErrors((prev) => {
+ const newErrors = { ...prev };
+ if (error) newErrors[id] = error.details[0].message;
+ else delete newErrors[id];
+ return newErrors;
+ });
+ };
+
+ const handleSave = async (event) => {
+ event.preventDefault();
+ const action = await dispatch(
+ updatePageSpeed({ authToken, monitor: monitor })
+ );
+ if (action.meta.requestStatus === "fulfilled") {
+ createToast({ body: "Monitor updated successfully!" });
+ dispatch(getPageSpeedByUserId(authToken));
+ } else {
+ createToast({ body: "Failed to update monitor." });
+ }
+ };
+
+ const [isOpen, setIsOpen] = useState(false);
+ const handleRemove = async (event) => {
+ event.preventDefault();
+ const action = await dispatch(deletePageSpeed({ authToken, monitor }));
+ if (action.meta.requestStatus === "fulfilled") {
+ navigate("/pagespeed");
+ } else {
+ createToast({ body: "Failed to delete monitor." });
+ }
+ };
+
+ let loading = Object.keys(monitor).length === 0;
+
+ return (
+
+ {loading ? (
+
+ ) : (
+ <>
+ }
+ onClick={() => navigate(-1)}
+ sx={{
+ backgroundColor: theme.palette.otherColors.fillGray,
+ mb: theme.gap.large,
+ px: theme.gap.ml,
+ "& svg.MuiSvgIcon-root": {
+ mr: theme.gap.small,
+ fill: theme.palette.otherColors.slateGray,
+ },
+ }}
+ />
+
+ >
+ )}
+ setIsOpen(false)}
+ disablePortal
+ >
+
+
+ Do you really want to delete this monitor?
+
+
+ Once deleted, this monitor cannot be retrieved.
+
+
+
+
+
+
+ );
+};
+
+export default PageSpeedConfigure;
diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx
index 52581f3b9..45a031da1 100644
--- a/Client/src/Pages/PageSpeed/Details/index.jsx
+++ b/Client/src/Pages/PageSpeed/Details/index.jsx
@@ -139,7 +139,7 @@ const SkeletonLayout = () => {
-
+
{
return props;
};
- const pieSize = { width: 200, height: 200 };
+ const pieSize = { width: 210, height: 200 };
const pieData = getPieData(audits);
const colorMap = getColors(performance);
@@ -321,7 +321,7 @@ const PageSpeedDetails = () => {
}}
/>
-
+ {monitor?.status ? : }
POST [/api/v1/auth/register](#post-register)
- POST [/api/v1/auth/login](#post-login)
- - POST [/api/v1/auth/user/{userId}](#post-auth-user-edit-id)
+ - PUT [/api/v1/auth/user/{userId}](#post-auth-user-edit-id)
- GET [/api/v1/auth/users](#get-all-users-id)
+ - GET [/api/v1/auth/users/admin](#get-all-admin-users-id)
+ - POST [/api/v1/auth/invite](#post-auth-invite-id)
+ - POST [/api/v1/auth/invite/verify/](#post-auth-invite-verify-id)
- POST [/api/v1/auth/recovery/request](#post-auth-recovery-request-id)
- POST [/api/v1/auth/recovery/validate](#post-auth-recovery-validate-id)
- POST [/api/v1/auth/recovery/reset](#post-auth-recovery-reset-id)
###### Monitors
- GET [/api/v1/monitors](#get-monitors)
- - GET [/api/v1/monitor/{id}](#get-monitor-id)
+ - GET [/api/v1/monitors/{monitorId}](#get-monitor-id)
- GET [/api/v1/monitors/user/{userId}?limit](#get-monitors-user-userid)
- POST [/api/v1/monitors](#post-monitors)
- - POST [/api/v1/monitors/delete/{monitorId}](#post-monitors-del-id)
- - POST [/api/v1/monitors/edit/{monitorId}](#post-monitors-edit-id)
+ - PUT [/api/v1/monitors/{monitorId}](#put-monitors-id)
+ - DELETE [/api/v1/monitors/{monitorId}](#delete-monitors-id)
+ - DELETE [/api/v1/monitors/all](#delete-all-monitors)
###### Checks
- POST [/api/v1/checks/{monitorId}](#post-checks)
- GET [/api/v1/checks/{monitorId}](#get-checks)
- - POST [/api/v1/checks/delete/{monitorId}](#delete-checks)
+ - DELETE [/api/v1/checks/{monitorId}](#delete-checks)
###### Alerts
- POST [/api/v1/alerts/{monitorId}](#create-alert)
- GET [/api/v1/alerts/user/{userId}](#get-alerts-user-id)
- GET [/api/v1/alerts/monitor/{monitorId}](#get-alerts-monitor-id)
- GET [/api/v1/alerts/{alertId}](#get-alert-alert-id)
- - POST [/api/v1/alerts/edit/{alertId}](#edit-alert)
- - POST [/api/v1/alerts/delete/{alertId}](#delete-alert)
+ - PUT [/api/v1/alerts/{alertId}](#edit-alert)
+ - DELETE [/api/v1/alerts/{alertId}](#delete-alert)
+ ###### Page Speed
+ - POST [/api/v1/pagespeed/:monitorId](#post-pagespeed)
+ - GET [/api/v1/pagespeed/:monitorId](#get-pagespeed)
+ - DELETE [/api/v1/pagespeed/:monitorId](#delete-pagespeed)
+ ###### Maintenance Window
+ - POST [/api/v1/maintenance-window/:monitorId](#post-maintenance-window)
+ - GET [/api/v1/maintenance-window/user/:userId](#get-maintenance-window-by-userid)
+ - GET [/api/v1/maintenance-window/monitor/:monitorId](#get-maintenance-window-by-monitor-id)
1. [Error Handling](#error-handling)
1. [Contributors](#contributors)
diff --git a/Server/routes/alertRoute.js b/Server/routes/alertRoute.js
index ba30fd117..434c0495a 100644
--- a/Server/routes/alertRoute.js
+++ b/Server/routes/alertRoute.js
@@ -29,14 +29,14 @@ router.get(
alertController.getAlertById
);
// Edit
-router.post(
- "/edit/:alertId",
+router.put(
+ "/:alertId",
verifyOwnership(Monitor, "monitorId"),
alertController.editAlert
);
//Delete
-router.post(
- "/delete/:alertId",
+router.delete(
+ "/:alertId",
verifyOwnership(Monitor, "monitorId"),
alertController.deleteAlert
);
diff --git a/Server/routes/checkRoute.js b/Server/routes/checkRoute.js
index bb06594ac..69aa682af 100644
--- a/Server/routes/checkRoute.js
+++ b/Server/routes/checkRoute.js
@@ -15,8 +15,8 @@ router.get(
checkController.getChecks
);
-router.post(
- "/delete/:monitorId",
+router.delete(
+ "/:monitorId",
verifyOwnership(Monitor, "monitorId"),
checkController.deleteChecks
);
diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js
index 1f50c9e70..2622181a3 100644
--- a/Server/routes/monitorRoute.js
+++ b/Server/routes/monitorRoute.js
@@ -14,10 +14,10 @@ router.delete(
monitorController.deleteMonitor
);
router.put(
- "/edit/:monitorId",
+ "/:monitorId",
verifyOwnership(Monitor, "monitorId"),
monitorController.editMonitor
);
-router.delete("/delete/all", monitorController.deleteAllMonitors);
+router.delete("/all", monitorController.deleteAllMonitors);
module.exports = router;