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..8b2beceb7 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/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 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/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.
.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 ? : }