Skip to content

Commit 4f1f033

Browse files
authored
Merge pull request #1708 from bluewave-labs/feat/fe/opt-in-distributed-uptime
feat: fe/opt in distributed uptime
2 parents ce21d6e + 7b4f781 commit 4f1f033

File tree

7 files changed

+140
-50
lines changed

7 files changed

+140
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Navigate } from "react-router-dom";
2+
import { useSelector } from "react-redux";
3+
import PropTypes from "prop-types";
4+
5+
/**
6+
* @param {Object} props - The props passed to the ProtectedDistributedUptimeRoute component.
7+
* @param {React.ReactNode} props.children - The children to render if the user is authenticated.
8+
* @returns {React.ReactElement} The children wrapped in a protected route or a redirect to the login page.
9+
*/
10+
11+
const ProtectedDistributedUptimeRoute = ({ children }) => {
12+
const distributedUptimeEnabled = useSelector(
13+
(state) => state.ui.distributedUptimeEnabled
14+
);
15+
16+
return distributedUptimeEnabled === true ? (
17+
children
18+
) : (
19+
<Navigate
20+
to="/uptime"
21+
replace
22+
/>
23+
);
24+
};
25+
26+
ProtectedDistributedUptimeRoute.propTypes = {
27+
children: PropTypes.node.isRequired,
28+
};
29+
30+
export default ProtectedDistributedUptimeRoute;

Client/src/Components/Sidebar/index.jsx

+18-11
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@ import {
1515
Tooltip,
1616
Typography,
1717
} from "@mui/material";
18-
import { useLocation, useNavigate } from "react-router";
19-
import { useTheme } from "@emotion/react";
20-
import { useDispatch, useSelector } from "react-redux";
21-
import { clearAuthState } from "../../Features/Auth/authSlice";
22-
import { toggleSidebar } from "../../Features/UI/uiSlice";
23-
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
2418
import ThemeSwitch from "../ThemeSwitch";
2519
import Avatar from "../Avatar";
2620
import LockSvg from "../../assets/icons/lock.svg?react";
@@ -46,9 +40,16 @@ import Folder from "../../assets/icons/folder.svg?react";
4640
import StatusPages from "../../assets/icons/status-pages.svg?react";
4741
import ChatBubbleOutlineRoundedIcon from "@mui/icons-material/ChatBubbleOutlineRounded";
4842
import DistributedUptimeIcon from "../../assets/icons/distributed-uptime.svg?react";
49-
5043
import "./index.css";
5144

45+
// Utils
46+
import { useLocation, useNavigate } from "react-router";
47+
import { useTheme } from "@emotion/react";
48+
import { useDispatch, useSelector } from "react-redux";
49+
import { clearAuthState } from "../../Features/Auth/authSlice";
50+
import { toggleSidebar } from "../../Features/UI/uiSlice";
51+
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
52+
5253
const menu = [
5354
{ name: "Uptime", path: "uptime", icon: <Monitors /> },
5455
{ name: "Pagespeed", path: "pagespeed", icon: <PageSpeed /> },
@@ -128,6 +129,9 @@ function Sidebar() {
128129
const [anchorEl, setAnchorEl] = useState(null);
129130
const [popup, setPopup] = useState();
130131
const { user } = useSelector((state) => state.auth);
132+
const distributedUptimeEnabled = useSelector(
133+
(state) => state.ui.distributedUptimeEnabled
134+
);
131135

132136
const accountMenuItem = menu.find((item) => item.name === "Account");
133137
if (user.role?.includes("demo") && accountMenuItem) {
@@ -322,8 +326,11 @@ function Sidebar() {
322326
/* overflow: "hidden", */
323327
}}
324328
>
325-
{menu.map((item) =>
326-
item.path ? (
329+
{menu.map((item) => {
330+
if (item.path === "distributed-uptime" && distributedUptimeEnabled === false) {
331+
return null;
332+
}
333+
return item.path ? (
327334
/* If item has a path */
328335
<Tooltip
329336
key={item.path}
@@ -556,8 +563,8 @@ function Sidebar() {
556563
</List>
557564
</Collapse>
558565
</React.Fragment>
559-
)
560-
)}
566+
);
567+
})}
561568
</List>
562569
<Divider sx={{ mt: "auto" }} />
563570

Client/src/Features/UI/uiSlice.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { createSlice } from "@reduxjs/toolkit";
22

3-
const initialMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? "dark" : "light";
3+
const initialMode = window?.matchMedia?.("(prefers-color-scheme: dark)")?.matches
4+
? "dark"
5+
: "light";
46

57
// Initial state for UI settings.
68
// Add more settings as needed (e.g., theme preferences, user settings)
@@ -20,12 +22,16 @@ const initialState = {
2022
mode: initialMode,
2123
greeting: { index: 0, lastUpdate: null },
2224
timezone: "America/Toronto",
25+
distributedUptimeEnabled: false,
2326
};
2427

2528
const uiSlice = createSlice({
2629
name: "ui",
2730
initialState,
2831
reducers: {
32+
setDistributedUptimeEnabled: (state, action) => {
33+
state.distributedUptimeEnabled = action.payload;
34+
},
2935
setRowsPerPage: (state, action) => {
3036
const { table, value } = action.payload;
3137
if (state[table]) {
@@ -49,5 +55,11 @@ const uiSlice = createSlice({
4955
});
5056

5157
export default uiSlice.reducer;
52-
export const { setRowsPerPage, toggleSidebar, setMode, setGreeting, setTimezone } =
53-
uiSlice.actions;
58+
export const {
59+
setRowsPerPage,
60+
toggleSidebar,
61+
setMode,
62+
setGreeting,
63+
setTimezone,
64+
setDistributedUptimeEnabled,
65+
} = uiSlice.actions;

Client/src/Pages/Settings/index.css

-21
This file was deleted.

Client/src/Pages/Settings/index.jsx

+51-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { useTheme } from "@emotion/react";
2-
import { Box, Stack, Typography, Button } from "@mui/material";
1+
// Components
2+
import { Box, Stack, Typography, Button, Switch } from "@mui/material";
33
import TextInput from "../../Components/Inputs/TextInput";
44
import Link from "../../Components/Link";
55
import Select from "../../Components/Inputs/Select";
6+
import LoadingButton from "@mui/lab/LoadingButton";
7+
import { useIsAdmin } from "../../Hooks/useIsAdmin";
8+
import Dialog from "../../Components/Dialog";
9+
import ConfigBox from "../../Components/ConfigBox";
10+
11+
//Utils
12+
import { useTheme } from "@emotion/react";
613
import { logger } from "../../Utils/Logger";
7-
import "./index.css";
814
import { useDispatch, useSelector } from "react-redux";
915
import { createToast } from "../../Utils/toastUtils";
1016
import {
@@ -14,15 +20,17 @@ import {
1420
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
1521
import { update } from "../../Features/Auth/authSlice";
1622
import PropTypes from "prop-types";
17-
import LoadingButton from "@mui/lab/LoadingButton";
18-
import { setTimezone, setMode } from "../../Features/UI/uiSlice";
23+
import {
24+
setTimezone,
25+
setMode,
26+
setDistributedUptimeEnabled,
27+
} from "../../Features/UI/uiSlice";
1928
import timezones from "../../Utils/timezones.json";
2029
import { useState, useEffect } from "react";
2130
import { networkService } from "../../main";
2231
import { settingsValidation } from "../../Validation/validation";
23-
import Dialog from "../../Components/Dialog";
24-
import { useIsAdmin } from "../../Hooks/useIsAdmin";
25-
import ConfigBox from "../../Components/ConfigBox";
32+
33+
// Constants
2634
const SECONDS_PER_DAY = 86400;
2735

2836
const Settings = () => {
@@ -32,10 +40,11 @@ const Settings = () => {
3240
const { checkTTL } = user;
3341
const { isLoading } = useSelector((state) => state.uptimeMonitors);
3442
const { isLoading: authIsLoading } = useSelector((state) => state.auth);
35-
const { timezone } = useSelector((state) => state.ui);
43+
const { timezone, distributedUptimeEnabled } = useSelector((state) => state.ui);
3644
const { mode } = useSelector((state) => state.ui);
3745
const [checksIsLoading, setChecksIsLoading] = useState(false);
3846
const [form, setForm] = useState({
47+
enableDistributedUptime: distributedUptimeEnabled,
3948
ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0,
4049
});
4150
const [version, setVersion] = useState("unknown");
@@ -64,7 +73,16 @@ const Settings = () => {
6473
}, []);
6574

6675
const handleChange = (event) => {
67-
const { value, id } = event.target;
76+
const { type, checked, value, id } = event.target;
77+
78+
if (type === "checkbox") {
79+
setForm((prev) => ({
80+
...prev,
81+
[id]: checked,
82+
}));
83+
return;
84+
}
85+
6886
const { error } = settingsValidation.validate(
6987
{ [id]: value },
7088
{
@@ -79,7 +97,6 @@ const Settings = () => {
7997
newErrors[err.path[0]] = err.message;
8098
});
8199
setErrors(newErrors);
82-
console.log(newErrors);
83100
logger.error("Validation errors:", error.details);
84101
}
85102
let inputValue = value;
@@ -100,6 +117,7 @@ const Settings = () => {
100117
});
101118
const updatedUser = { ...user, checkTTL: form.ttl };
102119
const action = await dispatch(update({ authToken, localData: updatedUser }));
120+
103121
if (action.payload.success) {
104122
createToast({
105123
body: "Settings saved successfully",
@@ -197,7 +215,7 @@ const Settings = () => {
197215
</Box>
198216
<Stack gap={theme.spacing(20)}>
199217
<Select
200-
id="display-timezone"
218+
id="display-timezones"
201219
label="Display timezone"
202220
value={timezone}
203221
onChange={(e) => {
@@ -229,6 +247,27 @@ const Settings = () => {
229247
></Select>
230248
</Stack>
231249
</ConfigBox>
250+
{isAdmin && (
251+
<ConfigBox>
252+
<Box>
253+
<Typography component="h1">Distributed uptime</Typography>
254+
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
255+
Enable/disable distributed uptime monitoring.
256+
</Typography>
257+
</Box>
258+
<Box>
259+
<Switch
260+
id="enableDistributedUptime"
261+
color="accent"
262+
checked={distributedUptimeEnabled}
263+
onChange={(e) => {
264+
dispatch(setDistributedUptimeEnabled(e.target.checked));
265+
}}
266+
/>
267+
{distributedUptimeEnabled === true ? "Enabled" : "Disabled"}
268+
</Box>
269+
</ConfigBox>
270+
)}
232271
{isAdmin && (
233272
<ConfigBox>
234273
<Box>

Client/src/Routes/index.jsx

+17-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import Settings from "../Pages/Settings";
4848
import Maintenance from "../Pages/Maintenance";
4949

5050
import ProtectedRoute from "../Components/ProtectedRoute";
51+
import ProtectedDistributedUptimeRoute from "../Components/ProtectedDistributedUptimeRoute";
5152
import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance";
5253
import withAdminCheck from "../Components/HOC/withAdminCheck";
5354

@@ -86,15 +87,28 @@ const Routes = () => {
8687
/>
8788
<Route
8889
path="/distributed-uptime"
89-
element={<DistributedUptimeMonitors />}
90+
element={
91+
<ProtectedDistributedUptimeRoute>
92+
<DistributedUptimeMonitors />{" "}
93+
</ProtectedDistributedUptimeRoute>
94+
}
9095
/>
96+
9197
<Route
9298
path="/distributed-uptime/create"
93-
element={<CreateDistributedUptime />}
99+
element={
100+
<ProtectedDistributedUptimeRoute>
101+
<CreateDistributedUptime />
102+
</ProtectedDistributedUptimeRoute>
103+
}
94104
/>
95105
<Route
96106
path="/distributed-uptime/:monitorId"
97-
element={<DistributedUptimeDetails />}
107+
element={
108+
<ProtectedDistributedUptimeRoute>
109+
<DistributedUptimeDetails />
110+
</ProtectedDistributedUptimeRoute>
111+
}
98112
/>
99113
<Route
100114
path="pagespeed"

Client/src/Utils/Theme/globalTheme.js

+9
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,15 @@ const baseTheme = (palette) => ({
367367
}),
368368
},
369369
},
370+
MuiSwitch: {
371+
styleOverrides: {
372+
root: ({ theme }) => ({
373+
"& .MuiSwitch-track": {
374+
backgroundColor: theme.palette.primary.contrastText,
375+
},
376+
}),
377+
},
378+
},
370379
},
371380
shape: {
372381
borderRadius: 2,

0 commit comments

Comments
 (0)