mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-31 14:18:28 -06:00
Merge pull request #1708 from bluewave-labs/feat/fe/opt-in-distributed-uptime
feat: fe/opt in distributed uptime
This commit is contained in:
@@ -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;
|
||||
@@ -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" }} />
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -367,6 +367,15 @@ const baseTheme = (palette) => ({
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiSwitch: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
"& .MuiSwitch-track": {
|
||||
backgroundColor: theme.palette.primary.contrastText,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 2,
|
||||
|
||||
Reference in New Issue
Block a user