From f00d7275e5b977b73762c9a7b8c1e0dc53036a86 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 5 Jul 2025 18:26:34 -0400 Subject: [PATCH 01/13] [PageSpeed]: Monitor Config and Create pages consolidated into Setup Component --- client/src/Pages/PageSpeed/Setup/index.css | 9 + client/src/Pages/PageSpeed/Setup/index.jsx | 466 ++++++++++++++++++ client/src/Pages/PageSpeed/Setup/skeleton.jsx | 86 ++++ client/src/Routes/index.jsx | 5 +- 4 files changed, 564 insertions(+), 2 deletions(-) create mode 100644 client/src/Pages/PageSpeed/Setup/index.css create mode 100644 client/src/Pages/PageSpeed/Setup/index.jsx create mode 100644 client/src/Pages/PageSpeed/Setup/skeleton.jsx diff --git a/client/src/Pages/PageSpeed/Setup/index.css b/client/src/Pages/PageSpeed/Setup/index.css new file mode 100644 index 000000000..89a7c3466 --- /dev/null +++ b/client/src/Pages/PageSpeed/Setup/index.css @@ -0,0 +1,9 @@ +.configure-pagespeed button { + height: var(--env-var-height-2); +} + +.configure-pagespeed .field, +.configure-pagespeed .section-disabled, +.configure-pagespeed .select-wrapper { + flex: 1; +} diff --git a/client/src/Pages/PageSpeed/Setup/index.jsx b/client/src/Pages/PageSpeed/Setup/index.jsx new file mode 100644 index 000000000..4a653c58d --- /dev/null +++ b/client/src/Pages/PageSpeed/Setup/index.jsx @@ -0,0 +1,466 @@ +// Components +import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material"; +import ConfigBox from "../../../Components/ConfigBox"; +import Select from "../../../Components/Inputs/Select"; +import TextInput from "../../../Components/Inputs/TextInput"; +import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline"; +import Breadcrumbs from "../../../Components/Breadcrumbs"; +import PulseDot from "../../../Components/Animated/PulseDot"; +import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded"; +import SkeletonLayout from "./skeleton"; +import NotificationsConfig from "../../../Components/NotificationConfig"; +import Dialog from "../../../Components/Dialog"; +import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; +import Radio from "../../../Components/Inputs/Radio"; + + +// Utils +import { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; +import { monitorValidation } from "../../../Validation/validation"; +import { parseDomainName } from "../../../Utils/monitorUtils"; +import { useTranslation } from "react-i18next"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import { useTheme } from "@emotion/react"; +import { createToast } from "../../../Utils/toastUtils"; + +import { useParams } from "react-router"; +import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { + useCreateMonitor, + useFetchMonitorById, + useDeleteMonitor, + useUpdateMonitor, + usePauseMonitor, +} from "../../../Hooks/monitorHooks"; + +const MS_PER_MINUTE = 60000; +const SELECT_VALUES = [ + { _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" }, +]; + +const PageSpeedSetup = ({ monitor }) => { + const isConfigure = monitor; + const { t } = useTranslation(); + const theme = useTheme(); + const { monitorId } = useParams(); + const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils(); + const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId(); + const [https, setHttps] = useState(true); + const [errors, setErrors] = useState({}); + const [isOpen, setIsOpen] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [isUpdating, setIsUpdating] = useState(false); + const [isPausing, setIsPausing] = useState(false); + + // Monitor state + const [monitorData, setMonitorData] = useState({ + url: "", + name: "", + type: "pagespeed", + notifications: [], + interval: 3, + }); + + // Hooks for API actions + const [createMonitor, isCreating] = useCreateMonitor(); + const [fetchMonitor] = monitor + ? useFetchMonitorById({ monitorId, setMonitor: setMonitorData, updateTrigger }) + : [null]; + const [deleteMonitor] = useDeleteMonitor(); + const [updateMonitor] = useUpdateMonitor(); + const [pauseMonitor] = usePauseMonitor(); + + 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" }, + ]; + + // Fetch monitor if in configure mode + useEffect(() => { + if (monitor && monitorId && typeof fetchMonitor === 'function') { + setIsLoading(true); + fetchMonitor().finally(() => setIsLoading(false)); + } + }, [monitor, monitorId, updateTrigger, fetchMonitor]); + + // Handlers + + const handleChange = (event) => { + let { value, name } = event.target; + if (name === "interval") { + value = value * MS_PER_MINUTE; + } + setMonitorData((prev) => ({ + ...prev, + [name]: value + })); + const validation = monitorValidation.validate( + { [name]: value }, + { abortEarly: false } + ); + + setErrors((prev) => { + const updatedErrors = { ...prev }; + if (validation.error) { + updatedErrors[name] = validation.error.details[0].message; + } else { + delete updatedErrors[name]; + } + return updatedErrors; + }); + }; + + const handleBlur = (event) => { + const { name, value } = event.target; + if (name === "url" && monitorData.name === "") { + setMonitorData((prev) => ({ ...prev, name: parseDomainName(value) })); + } + }; + + const onSubmit = async (event) => { + event.preventDefault(); + if (isConfigure) { + setIsUpdating(true); + await updateMonitor({ monitor: monitorData, redirect: "/pagespeed" }); + setIsUpdating(false); + } else { + let form = { + url: `http${https ? "s" : ""}://` + monitorData.url, + name: monitorData.name === "" ? monitorData.url : monitorData.name, + type: monitorData.type, + interval: monitorData.interval * MS_PER_MINUTE, + description: monitorData.name === "" ? monitorData.url : monitorData.name, + notifications: monitorData.notifications, + }; + const { error } = monitorValidation.validate(form, { abortEarly: false }); + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + createToast({ body: "Please check the form for errors." }); + return; + } + setIsLoading(true); + await createMonitor({ monitor: form, redirect: "/pagespeed" }); + setIsLoading(false); + } + }; + + const handlePause = async () => { + setIsPausing(true); + await pauseMonitor({ monitorId, triggerUpdate: () => setUpdateTrigger((u) => !u) }); + setIsPausing(false); + }; + + const handleRemove = async (event) => { + event.preventDefault(); + setIsDeleting(true); + await deleteMonitor({ monitor: monitorData, redirect: "/pagespeed" }); + setIsDeleting(false); + }; + + // Loading skeleton + if (isConfigure && (isLoading || !monitorData._id)) { + return ; + } + + return ( + + + + + + {isConfigure ? monitorData.name : t("createYour") + " "} + + { !isConfigure + ? ( + + {t("pageSpeedMonitor")} + + ) + : <> + } + + {isConfigure && ( + + + + + + + + + + {monitorData.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + + + + + + + )} + + + + {t("settingsGeneralSettings")} + + + {isConfigure ? t("pageSpeedConfigureSettingsDescription") : t("distributedUptimeCreateSelectURL")} + + + + : undefined} + placeholder={isConfigure ? "random.website.com" : "google.com"} + value={monitorData.url || ""} + onChange={handleChange} + onBlur={!isConfigure ? handleBlur : undefined} + error={!!errors["url"]} + helperText={errors["url"]} + disabled={isConfigure} + /> + + + + {!isConfigure && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + + + + + + {errors["type"] ? ( + + + {errors["type"]} + + + ) : null} + + + )} + + + + {t("notificationConfig.title")} + + {t("notificationConfig.description")} + + + + + + + {t("distributedUptimeCreateAdvancedSettings")} + + + + - - - - - - - - )} - setIsOpen(false)} - confirmationButtonLabel={t("delete")} - onConfirm={handleRemove} - isLoading={isLoading || isDeleting || isUpdating || isPausing} - /> - - ); -}; - -export default PageSpeedConfigure; diff --git a/client/src/Pages/PageSpeed/Configure/skeleton.jsx b/client/src/Pages/PageSpeed/Configure/skeleton.jsx deleted file mode 100644 index 347fcaa65..000000000 --- a/client/src/Pages/PageSpeed/Configure/skeleton.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Box, Skeleton, Stack } from "@mui/material"; -import { useTheme } from "@emotion/react"; - -/** - * Renders a skeleton layout. - * - * @returns {JSX.Element} - */ -const SkeletonLayout = () => { - const theme = useTheme(); - - return ( - <> - - - - - - - - - - - - - - - - - - - - ); -}; - -export default SkeletonLayout; diff --git a/client/src/Pages/PageSpeed/Create/index.css b/client/src/Pages/PageSpeed/Create/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx deleted file mode 100644 index 5cc4a1a54..000000000 --- a/client/src/Pages/PageSpeed/Create/index.jsx +++ /dev/null @@ -1,312 +0,0 @@ -//Components -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import Button from "@mui/material/Button"; -import ButtonGroup from "@mui/material/ButtonGroup"; -import Breadcrumbs from "../../../Components/Breadcrumbs"; -import TextInput from "../../../Components/Inputs/TextInput"; -import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import ConfigBox from "../../../Components/ConfigBox"; -import Radio from "../../../Components/Inputs/Radio"; -import Select from "../../../Components/Inputs/Select"; -import NotificationsConfig from "../../../Components/NotificationConfig"; - -// Utils -import { useState } from "react"; -import { useSelector } from "react-redux"; -import { monitorValidation } from "../../../Validation/validation"; -import { parseDomainName } from "../../../Utils/monitorUtils"; -import { useTranslation } from "react-i18next"; -import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; -import { useTheme } from "@emotion/react"; -import { createToast } from "../../../Utils/toastUtils"; -import { useCreateMonitor } from "../../../Hooks/monitorHooks"; - -const MS_PER_MINUTE = 60000; - -const CRUMBS = [ - { name: "pagespeed", path: "/pagespeed" }, - { name: "create", path: `/pagespeed/create` }, -]; - -const SELECT_VALUES = [ - { _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" }, -]; - -const CreatePageSpeed = () => { - // State - const [monitor, setMonitor] = useState({ - url: "", - name: "", - type: "pagespeed", - notifications: [], - interval: 3, - }); - - const [https, setHttps] = useState(true); - const [errors, setErrors] = useState({}); - const { user } = useSelector((state) => state.auth); - const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId(); - - // Setup - const theme = useTheme(); - const [createMonitor, isCreating] = useCreateMonitor(); - - // Handlers - const onSubmit = async (event) => { - event.preventDefault(); - let form = { - url: `http${https ? "s" : ""}://` + monitor.url, - name: monitor.name === "" ? monitor.url : monitor.name, - type: monitor.type, - interval: monitor.interval * MS_PER_MINUTE, - }; - - const { error } = monitorValidation.validate(form, { - abortEarly: false, - }); - - if (error) { - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); - createToast({ body: "Please check the form for errors." }); - return; - } - - form = { - ...form, - description: form.name, - notifications: monitor.notifications, - }; - - await createMonitor({ monitor: form, redirect: "/pagespeed" }); - }; - - const handleChange = (event) => { - const { value, name } = event.target; - setMonitor({ - ...monitor, - [name]: value, - }); - - const { error } = monitorValidation.validate( - { [name]: value }, - { abortEarly: false } - ); - - setErrors((prev) => ({ - ...prev, - ...(error ? { [name]: error.details[0].message } : { [name]: undefined }), - })); - }; - - const handleBlur = (event) => { - const { name } = event.target; - if (name === "url") { - const { value } = event.target; - if (monitor.name !== "") { - return; - } - setMonitor((prev) => ({ - ...prev, - name: parseDomainName(value), - })); - } - }; - - const { t } = useTranslation(); - - return ( - - - - - - {t("createYour")}{" "} - - - {t("pageSpeedMonitor")} - - - - - - {t("settingsGeneralSettings")} - - {t("distributedUptimeCreateSelectURL")} - - - } - placeholder="google.com" - value={monitor.url} - onChange={handleChange} - onBlur={handleBlur} - error={errors["url"] ? true : false} - helperText={errors["url"]} - /> - - - - - - - {t("distributedUptimeCreateChecks")} - - - {t("distributedUptimeCreateChecksDescription")} - - - - - - - - - - - {errors["type"] ? ( - - - {errors["type"]} - - - ) : ( - "" - )} - - - - - - {t("notificationConfig.title")} - - {t("notificationConfig.description")} - - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - + + + + - - )} - - - - {t("notificationConfig.title")} - - {t("notificationConfig.description")} - - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - - - - - - - - - )} - {isConfigure && ( - setIsOpen(false)} - confirmationButtonLabel={t("delete")} - onConfirm={handleRemove} - isLoading={isLoading || isDeleting || isUpdating || isPausing} - /> - )} - - ); + > + : undefined + } + placeholder="random.website.com" + value={monitor.url || ""} + onChange={handleChange} + onBlur={!isConfigure ? handleBlur : undefined} + error={!!errors["url"]} + helperText={errors["url"]} + disabled={isConfigure} + /> + + + + {!isConfigure && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + + + + + + {errors["type"] ? ( + + + {errors["type"]} + + + ) : ( + "" + )} + + + )} + + + + {t("notificationConfig.title")} + + + {t("notificationConfig.description")} + + + + + + + + {t("distributedUptimeCreateAdvancedSettings")} + + + + { )} - {isConfigure && ( + {!isCreate && ( { onCancel={() => setIsOpen(false)} confirmationButtonLabel={t("delete")} onConfirm={handleRemove} - isLoading={isLoading || isDeleting || isUpdating || isPausing} /> )} From 840069b93519116c593d502912b19f0bb5c01f29 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Thu, 10 Jul 2025 21:53:33 -0400 Subject: [PATCH 08/13] [PageSpeed]: Implement conditional check for monitorId inside useFetchMonitorById hook --- client/src/Hooks/monitorHooks.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index d59d88c74..b3210c1b4 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -205,18 +205,22 @@ const useFetchStatsByMonitorId = ({ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { - const fetchMonitor = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorById({ monitorId: monitorId }); - setMonitor(res.data.data); - } catch (error) { - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - fetchMonitor(); + if (monitorId !== undefined) { + const fetchMonitor = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorById({ monitorId: monitorId }); + setMonitor(res.data.data); + } catch (error) { + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchMonitor(); + } else { + setIsLoading(false); + } }, [monitorId, setMonitor, updateTrigger]); return [isLoading]; }; From b7b768760825fa0b8240a8741ff2d63830c7ef98 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Thu, 10 Jul 2025 21:54:27 -0400 Subject: [PATCH 09/13] [PageSpeed]: Loading states from hooks, unused classnames, conditional calling of hook removed --- client/src/Pages/PageSpeed/Create/index.jsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/client/src/Pages/PageSpeed/Create/index.jsx b/client/src/Pages/PageSpeed/Create/index.jsx index 8941d8c9a..7c6042fad 100644 --- a/client/src/Pages/PageSpeed/Create/index.jsx +++ b/client/src/Pages/PageSpeed/Create/index.jsx @@ -84,13 +84,11 @@ const PageSpeedSetup = () => { useGetNotificationsByTeamId(); // Hooks for API actions + const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger }); const [createMonitor, isCreating] = useCreateMonitor(); - const [isLoading] = isCreate - ? [false] - : useFetchMonitorById({ monitorId, setMonitor, updateTrigger }); - const [deleteMonitor] = useDeleteMonitor(); - const [updateMonitor] = useUpdateMonitor(); - const [pauseMonitor] = usePauseMonitor(); + const [deleteMonitor, isDeleting] = useDeleteMonitor(); + const [updateMonitor, isUpdating] = useUpdateMonitor(); + const [pauseMonitor, isPausing] = usePauseMonitor(); // Handlers const onSubmit = async (event) => { @@ -186,7 +184,6 @@ const PageSpeedSetup = () => { return ( { { > - - + ) : ( + <> )} - - - + + {!isCreate && ( + + + + + + - {t("settingsGeneralSettings")} + {monitor.url?.replace(/^https?:\/\//, "") || "..."} - - {t("pageSpeedConfigureSettingsDescription")} + + {t("editing")} - - + )} + + {!isCreate && ( + + + + + )} + + + + + {t("settingsGeneralSettings")} + + + {t("pageSpeedConfigureSettingsDescription")} + + + .Mui-disabled)": { + backgroundColor: theme.palette.tertiary.main, + }, + }} + > + : undefined} + placeholder="random.website.com" + value={monitor.url || ""} + onChange={handleChange} + onBlur={isCreate ? handleBlur : undefined} + error={!!errors["url"]} + helperText={errors["url"]} + disabled={!isCreate} + /> + + + + {isCreate && ( + + + + {t("distributedUptimeCreateChecks")} + + + {t("distributedUptimeCreateChecksDescription")} + + + + + + + + + - - {isCreate && ( - + {errors["type"] ? ( - {t("distributedUptimeCreateChecks")} - - - {t("distributedUptimeCreateChecksDescription")} + {errors["type"]} - - - - - - - - - {errors["type"] ? ( - - - {errors["type"]} - - - ) : ( - "" - )} - - - )} - - - - {t("notificationConfig.title")} - - - {t("notificationConfig.description")} - - - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - - - )} + + + + + {!isCreate && ( Date: Fri, 11 Jul 2025 13:40:29 -0400 Subject: [PATCH 12/13] [PageSpeed]: Use typeof operator for clarity, and to handle undeclared and undefined variables --- client/src/Hooks/monitorHooks.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index b3210c1b4..55c348626 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -205,22 +205,22 @@ const useFetchStatsByMonitorId = ({ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { - if (monitorId !== undefined) { - const fetchMonitor = async () => { - try { - setIsLoading(true); - const res = await networkService.getMonitorById({ monitorId: monitorId }); - setMonitor(res.data.data); - } catch (error) { - createToast({ body: error.message }); - } finally { - setIsLoading(false); - } - }; - fetchMonitor(); - } else { + if (typeof monitorId === "undefined") { setIsLoading(false); - } + return + }; + const fetchMonitor = async () => { + try { + setIsLoading(true); + const res = await networkService.getMonitorById({ monitorId: monitorId }); + setMonitor(res.data.data); + } catch (error) { + createToast({ body: error.message }); + } finally { + setIsLoading(false); + } + }; + fetchMonitor(); }, [monitorId, setMonitor, updateTrigger]); return [isLoading]; }; From a7dd23141ccc022629c6cb73f39f287c126b10c9 Mon Sep 17 00:00:00 2001 From: Jesulayomy Date: Sat, 12 Jul 2025 14:26:17 -0400 Subject: [PATCH 13/13] [PageSpeed]: Formatting monitorHooks --- client/src/Hooks/monitorHooks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 55c348626..4f0e9c049 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -207,8 +207,8 @@ const useFetchMonitorById = ({ monitorId, setMonitor, updateTrigger }) => { useEffect(() => { if (typeof monitorId === "undefined") { setIsLoading(false); - return - }; + return; + } const fetchMonitor = async () => { try { setIsLoading(true);