Merge pull request #1708 from bluewave-labs/feat/fe/opt-in-distributed-uptime

feat: fe/opt in distributed uptime
This commit is contained in:
Alexander Holliday
2025-02-06 12:23:02 -08:00
committed by GitHub
7 changed files with 140 additions and 50 deletions

View File

@@ -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
) : (
<Navigate
to="/uptime"
replace
/>
);
};
ProtectedDistributedUptimeRoute.propTypes = {
children: PropTypes.node.isRequired,
};
export default ProtectedDistributedUptimeRoute;

View File

@@ -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: <Monitors /> },
{ name: "Pagespeed", path: "pagespeed", icon: <PageSpeed /> },
@@ -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 */
<Tooltip
key={item.path}
@@ -556,8 +563,8 @@ function Sidebar() {
</List>
</Collapse>
</React.Fragment>
)
)}
);
})}
</List>
<Divider sx={{ mt: "auto" }} />

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 = () => {
</Box>
<Stack gap={theme.spacing(20)}>
<Select
id="display-timezone"
id="display-timezones"
label="Display timezone"
value={timezone}
onChange={(e) => {
@@ -229,6 +247,27 @@ const Settings = () => {
></Select>
</Stack>
</ConfigBox>
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">Distributed uptime</Typography>
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
Enable/disable distributed uptime monitoring.
</Typography>
</Box>
<Box>
<Switch
id="enableDistributedUptime"
color="accent"
checked={distributedUptimeEnabled}
onChange={(e) => {
dispatch(setDistributedUptimeEnabled(e.target.checked));
}}
/>
{distributedUptimeEnabled === true ? "Enabled" : "Disabled"}
</Box>
</ConfigBox>
)}
{isAdmin && (
<ConfigBox>
<Box>

View File

@@ -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 = () => {
/>
<Route
path="/distributed-uptime"
element={<DistributedUptimeMonitors />}
element={
<ProtectedDistributedUptimeRoute>
<DistributedUptimeMonitors />{" "}
</ProtectedDistributedUptimeRoute>
}
/>
<Route
path="/distributed-uptime/create"
element={<CreateDistributedUptime />}
element={
<ProtectedDistributedUptimeRoute>
<CreateDistributedUptime />
</ProtectedDistributedUptimeRoute>
}
/>
<Route
path="/distributed-uptime/:monitorId"
element={<DistributedUptimeDetails />}
element={
<ProtectedDistributedUptimeRoute>
<DistributedUptimeDetails />
</ProtectedDistributedUptimeRoute>
}
/>
<Route
path="pagespeed"

View File

@@ -367,6 +367,15 @@ const baseTheme = (palette) => ({
}),
},
},
MuiSwitch: {
styleOverrides: {
root: ({ theme }) => ({
"& .MuiSwitch-track": {
backgroundColor: theme.palette.primary.contrastText,
},
}),
},
},
},
shape: {
borderRadius: 2,