From 27b89743c6e9f39f6ee1bfd3d4b37dec18e9f1c3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 6 Feb 2025 12:07:39 -0800 Subject: [PATCH 1/3] add toggle switch for DU monitors --- Client/src/Components/Sidebar/index.jsx | 29 +++++++----- Client/src/Features/UI/uiSlice.js | 18 +++++-- Client/src/Pages/Settings/index.css | 21 --------- Client/src/Pages/Settings/index.jsx | 63 ++++++++++++++++++++----- Client/src/Utils/Theme/globalTheme.js | 9 ++++ 5 files changed, 93 insertions(+), 47 deletions(-) delete mode 100644 Client/src/Pages/Settings/index.css diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx index ca884b096..314dfa76d 100644 --- a/Client/src/Components/Sidebar/index.jsx +++ b/Client/src/Components/Sidebar/index.jsx @@ -15,12 +15,6 @@ import { Tooltip, Typography, } from "@mui/material"; -import { useLocation, useNavigate } from "react-router"; -import { useTheme } from "@emotion/react"; -import { useDispatch, useSelector } from "react-redux"; -import { clearAuthState } from "../../Features/Auth/authSlice"; -import { toggleSidebar } from "../../Features/UI/uiSlice"; -import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; import ThemeSwitch from "../ThemeSwitch"; import Avatar from "../Avatar"; import LockSvg from "../../assets/icons/lock.svg?react"; @@ -46,9 +40,16 @@ import Folder from "../../assets/icons/folder.svg?react"; import StatusPages from "../../assets/icons/status-pages.svg?react"; import ChatBubbleOutlineRoundedIcon from "@mui/icons-material/ChatBubbleOutlineRounded"; import DistributedUptimeIcon from "../../assets/icons/distributed-uptime.svg?react"; - import "./index.css"; +// Utils +import { useLocation, useNavigate } from "react-router"; +import { useTheme } from "@emotion/react"; +import { useDispatch, useSelector } from "react-redux"; +import { clearAuthState } from "../../Features/Auth/authSlice"; +import { toggleSidebar } from "../../Features/UI/uiSlice"; +import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; + const menu = [ { name: "Uptime", path: "uptime", icon: }, { name: "Pagespeed", path: "pagespeed", icon: }, @@ -128,6 +129,9 @@ function Sidebar() { const [anchorEl, setAnchorEl] = useState(null); const [popup, setPopup] = useState(); const { user } = useSelector((state) => state.auth); + const distributedUptimeEnabled = useSelector( + (state) => state.ui.distributedUptimeEnabled + ); const accountMenuItem = menu.find((item) => item.name === "Account"); if (user.role?.includes("demo") && accountMenuItem) { @@ -322,8 +326,11 @@ function Sidebar() { /* overflow: "hidden", */ }} > - {menu.map((item) => - item.path ? ( + {menu.map((item) => { + if (item.path === "distributed-uptime" && distributedUptimeEnabled === false) { + return null; + } + return item.path ? ( /* If item has a path */ - ) - )} + ); + })} diff --git a/Client/src/Features/UI/uiSlice.js b/Client/src/Features/UI/uiSlice.js index 872691bbf..4565c6c5d 100644 --- a/Client/src/Features/UI/uiSlice.js +++ b/Client/src/Features/UI/uiSlice.js @@ -1,6 +1,8 @@ import { createSlice } from "@reduxjs/toolkit"; -const initialMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? "dark" : "light"; +const initialMode = window?.matchMedia?.("(prefers-color-scheme: dark)")?.matches + ? "dark" + : "light"; // Initial state for UI settings. // Add more settings as needed (e.g., theme preferences, user settings) @@ -20,12 +22,16 @@ const initialState = { mode: initialMode, greeting: { index: 0, lastUpdate: null }, timezone: "America/Toronto", + distributedUptimeEnabled: false, }; const uiSlice = createSlice({ name: "ui", initialState, reducers: { + setDistributedUptimeEnabled: (state, action) => { + state.distributedUptimeEnabled = action.payload; + }, setRowsPerPage: (state, action) => { const { table, value } = action.payload; if (state[table]) { @@ -49,5 +55,11 @@ const uiSlice = createSlice({ }); export default uiSlice.reducer; -export const { setRowsPerPage, toggleSidebar, setMode, setGreeting, setTimezone } = - uiSlice.actions; +export const { + setRowsPerPage, + toggleSidebar, + setMode, + setGreeting, + setTimezone, + setDistributedUptimeEnabled, +} = uiSlice.actions; diff --git a/Client/src/Pages/Settings/index.css b/Client/src/Pages/Settings/index.css deleted file mode 100644 index 52bccab5e..000000000 --- a/Client/src/Pages/Settings/index.css +++ /dev/null @@ -1,21 +0,0 @@ -.settings a.MuiTypography-root, -.settings h2.MuiTypography-root, -.settings span.MuiTypography-root, -.settings button, -.settings p.MuiTypography-root { - font-size: var(--env-var-font-size-medium); -} -.settings h1.MuiTypography-root { - font-size: var(--env-var-font-size-large); -} -.settings h1.MuiTypography-root, -.settings h2.MuiTypography-root { - font-weight: 600; -} -.settings button { - min-height: 34px; -} -.settings span.MuiTypography-root { - opacity: 0.6; - margin-right: 4px; -} diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index 3cabac713..9363694c3 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -1,10 +1,16 @@ -import { useTheme } from "@emotion/react"; -import { Box, Stack, Typography, Button } from "@mui/material"; +// Components +import { Box, Stack, Typography, Button, Switch } from "@mui/material"; import TextInput from "../../Components/Inputs/TextInput"; import Link from "../../Components/Link"; import Select from "../../Components/Inputs/Select"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { useIsAdmin } from "../../Hooks/useIsAdmin"; +import Dialog from "../../Components/Dialog"; +import ConfigBox from "../../Components/ConfigBox"; + +//Utils +import { useTheme } from "@emotion/react"; import { logger } from "../../Utils/Logger"; -import "./index.css"; import { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../Utils/toastUtils"; import { @@ -14,15 +20,17 @@ import { } from "../../Features/UptimeMonitors/uptimeMonitorsSlice"; import { update } from "../../Features/Auth/authSlice"; import PropTypes from "prop-types"; -import LoadingButton from "@mui/lab/LoadingButton"; -import { setTimezone, setMode } from "../../Features/UI/uiSlice"; +import { + setTimezone, + setMode, + setDistributedUptimeEnabled, +} from "../../Features/UI/uiSlice"; import timezones from "../../Utils/timezones.json"; import { useState, useEffect } from "react"; import { networkService } from "../../main"; import { settingsValidation } from "../../Validation/validation"; -import Dialog from "../../Components/Dialog"; -import { useIsAdmin } from "../../Hooks/useIsAdmin"; -import ConfigBox from "../../Components/ConfigBox"; + +// Constants const SECONDS_PER_DAY = 86400; const Settings = () => { @@ -32,10 +40,11 @@ const Settings = () => { const { checkTTL } = user; const { isLoading } = useSelector((state) => state.uptimeMonitors); const { isLoading: authIsLoading } = useSelector((state) => state.auth); - const { timezone } = useSelector((state) => state.ui); + const { timezone, distributedUptimeEnabled } = useSelector((state) => state.ui); const { mode } = useSelector((state) => state.ui); const [checksIsLoading, setChecksIsLoading] = useState(false); const [form, setForm] = useState({ + enableDistributedUptime: distributedUptimeEnabled, ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0, }); const [version, setVersion] = useState("unknown"); @@ -64,7 +73,16 @@ const Settings = () => { }, []); const handleChange = (event) => { - const { value, id } = event.target; + const { type, checked, value, id } = event.target; + + if (type === "checkbox") { + setForm((prev) => ({ + ...prev, + [id]: checked, + })); + return; + } + const { error } = settingsValidation.validate( { [id]: value }, { @@ -79,7 +97,6 @@ const Settings = () => { newErrors[err.path[0]] = err.message; }); setErrors(newErrors); - console.log(newErrors); logger.error("Validation errors:", error.details); } let inputValue = value; @@ -100,6 +117,7 @@ const Settings = () => { }); const updatedUser = { ...user, checkTTL: form.ttl }; const action = await dispatch(update({ authToken, localData: updatedUser })); + if (action.payload.success) { createToast({ body: "Settings saved successfully", @@ -197,7 +215,7 @@ const Settings = () => { + {isAdmin && ( + + + Distributed uptime + + Enable/disable distributed uptime monitoring. + + + + { + dispatch(setDistributedUptimeEnabled(e.target.checked)); + }} + /> + {form.enableDistributedUptime === true ? "Enabled" : "Disabled"} + + + )} {isAdmin && ( diff --git a/Client/src/Utils/Theme/globalTheme.js b/Client/src/Utils/Theme/globalTheme.js index bf5f377c2..f5ca9e1ff 100644 --- a/Client/src/Utils/Theme/globalTheme.js +++ b/Client/src/Utils/Theme/globalTheme.js @@ -367,6 +367,15 @@ const baseTheme = (palette) => ({ }), }, }, + MuiSwitch: { + styleOverrides: { + root: ({ theme }) => ({ + "& .MuiSwitch-track": { + backgroundColor: theme.palette.primary.contrastText, + }, + }), + }, + }, }, shape: { borderRadius: 2, From 4f16d8140e1ba4fdbfd5dae0300dae493d2f592a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 6 Feb 2025 12:15:43 -0800 Subject: [PATCH 2/3] fix conditional --- Client/src/Pages/Settings/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/src/Pages/Settings/index.jsx b/Client/src/Pages/Settings/index.jsx index 9363694c3..b5aec4868 100644 --- a/Client/src/Pages/Settings/index.jsx +++ b/Client/src/Pages/Settings/index.jsx @@ -264,7 +264,7 @@ const Settings = () => { dispatch(setDistributedUptimeEnabled(e.target.checked)); }} /> - {form.enableDistributedUptime === true ? "Enabled" : "Disabled"} + {distributedUptimeEnabled === true ? "Enabled" : "Disabled"} )} From 7b4f78132d84da02868821039a93d8e678a0b304 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 6 Feb 2025 12:15:59 -0800 Subject: [PATCH 3/3] protect DU routes --- .../ProtectedDistributedUptimeRoute/index.jsx | 30 +++++++++++++++++++ Client/src/Routes/index.jsx | 20 +++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 Client/src/Components/ProtectedDistributedUptimeRoute/index.jsx diff --git a/Client/src/Components/ProtectedDistributedUptimeRoute/index.jsx b/Client/src/Components/ProtectedDistributedUptimeRoute/index.jsx new file mode 100644 index 000000000..4a59859fe --- /dev/null +++ b/Client/src/Components/ProtectedDistributedUptimeRoute/index.jsx @@ -0,0 +1,30 @@ +import { Navigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import PropTypes from "prop-types"; + +/** + * @param {Object} props - The props passed to the ProtectedDistributedUptimeRoute component. + * @param {React.ReactNode} props.children - The children to render if the user is authenticated. + * @returns {React.ReactElement} The children wrapped in a protected route or a redirect to the login page. + */ + +const ProtectedDistributedUptimeRoute = ({ children }) => { + const distributedUptimeEnabled = useSelector( + (state) => state.ui.distributedUptimeEnabled + ); + + return distributedUptimeEnabled === true ? ( + children + ) : ( + + ); +}; + +ProtectedDistributedUptimeRoute.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default ProtectedDistributedUptimeRoute; diff --git a/Client/src/Routes/index.jsx b/Client/src/Routes/index.jsx index 8883a3212..3ace64ae1 100644 --- a/Client/src/Routes/index.jsx +++ b/Client/src/Routes/index.jsx @@ -48,6 +48,7 @@ import Settings from "../Pages/Settings"; import Maintenance from "../Pages/Maintenance"; import ProtectedRoute from "../Components/ProtectedRoute"; +import ProtectedDistributedUptimeRoute from "../Components/ProtectedDistributedUptimeRoute"; import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance"; import withAdminCheck from "../Components/HOC/withAdminCheck"; @@ -86,15 +87,28 @@ const Routes = () => { /> } + element={ + + {" "} + + } /> + } + element={ + + + + } /> } + element={ + + + + } />