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/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..b5aec4868 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));
+ }}
+ />
+ {distributedUptimeEnabled === true ? "Enabled" : "Disabled"}
+
+
+ )}
{isAdmin && (
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={
+
+
+
+ }
/>
({
}),
},
},
+ MuiSwitch: {
+ styleOverrides: {
+ root: ({ theme }) => ({
+ "& .MuiSwitch-track": {
+ backgroundColor: theme.palette.primary.contrastText,
+ },
+ }),
+ },
+ },
},
shape: {
borderRadius: 2,