Merge pull request #735 from bluewave-labs/feat/fe/role-restrictions

Feat/fe/role restrictions, resolves #723
This commit is contained in:
Alexander Holliday
2024-08-27 15:56:28 -07:00
committed by GitHub
6 changed files with 110 additions and 63 deletions

View File

@@ -21,6 +21,7 @@ import ProtectedRoute from "./Components/ProtectedRoute";
import Details from "./Pages/Monitors/Details";
import Maintenance from "./Pages/Maintenance";
import withAdminCheck from "./HOC/withAdminCheck";
import withAdminProp from "./HOC/withAdminProp";
import Configure from "./Pages/Monitors/Configure";
import PageSpeed from "./Pages/PageSpeed";
import CreatePageSpeed from "./Pages/PageSpeed/CreatePageSpeed";
@@ -30,6 +31,9 @@ import PageSpeedConfigure from "./Pages/PageSpeed/Configure";
function App() {
const AdminCheckedRegister = withAdminCheck(Register);
const MonitorsWithAdminProp = withAdminProp(Monitors);
const DetailsWithAdminProp = withAdminProp(Details);
return (
<>
<Routes>
@@ -37,11 +41,11 @@ function App() {
<Route
exact
path="/"
element={<ProtectedRoute Component={Monitors} />}
element={<ProtectedRoute Component={MonitorsWithAdminProp} />}
/>
<Route
path="/monitors"
element={<ProtectedRoute Component={Monitors} />}
element={<ProtectedRoute Component={MonitorsWithAdminProp} />}
/>
<Route
path="/monitors/create"
@@ -49,7 +53,7 @@ function App() {
/>
<Route
path="/monitors/:monitorId/"
element={<ProtectedRoute Component={Details} />}
element={<ProtectedRoute Component={DetailsWithAdminProp} />}
/>
<Route
path="/monitors/configure/:monitorId/"

View File

@@ -19,7 +19,7 @@ import "./index.css";
* @returns {JSX.Element} The rendered fallback UI.
*/
const Fallback = ({ title, checks, link = "/" }) => {
const Fallback = ({ title, checks, link = "/", isAdmin }) => {
const theme = useTheme();
const navigate = useNavigate();
@@ -36,7 +36,7 @@ const Fallback = ({ title, checks, link = "/" }) => {
/>
<Stack gap={theme.gap.small} maxWidth={"275px"} zIndex={1}>
<Typography component="h1" marginY={theme.gap.medium}>
A {title} is used to:
A {title} monitor is used to:
</Typography>
{checks.map((check, index) => (
<Check
@@ -46,12 +46,14 @@ const Fallback = ({ title, checks, link = "/" }) => {
/>
))}
</Stack>
<Button
level="primary"
label={`Let's create your ${title}`}
sx={{ alignSelf: "center" }}
onClick={() => navigate(link)}
/>
{isAdmin && (
<Button
level="primary"
label={`Let's create your ${title}`}
sx={{ alignSelf: "center" }}
onClick={() => navigate(link)}
/>
)}
</Stack>
);
};
@@ -60,6 +62,7 @@ Fallback.propTypes = {
title: PropTypes.string.isRequired,
checks: PropTypes.arrayOf(PropTypes.string).isRequired,
link: PropTypes.string,
isAdmin: PropTypes.bool,
};
export default Fallback;

View File

@@ -0,0 +1,20 @@
import { useSelector } from "react-redux";
const withAdminProp = (WrappedComponent) => {
const WithAdminProp = (props) => {
const { user } = useSelector((state) => state.auth);
const isAdmin =
(user?.role?.includes("admin") ?? false) ||
(user?.role?.includes("superadmin") ?? false);
return <WrappedComponent {...props} isAdmin={isAdmin} />;
};
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
WithAdminProp.displayName = `WithAdminProp(${wrappedComponentName})`;
return WithAdminProp;
};
export default withAdminProp;

View File

@@ -110,7 +110,7 @@ const SkeletonLayout = () => {
* Details page component displaying monitor details and related information.
* @component
*/
const DetailsPage = () => {
const DetailsPage = ({ isAdmin }) => {
const [monitor, setMonitor] = useState({});
const { monitorId } = useParams();
const { authToken } = useSelector((state) => state.auth);
@@ -196,29 +196,31 @@ const DetailsPage = () => {
Checking every {formatDurationRounded(monitor?.interval)}.
</Typography>
</Box>
<Button
level="tertiary"
label="Configure"
animate="rotate90"
img={
<SettingsIcon
style={{
minWidth: theme.gap.mlplus,
minHeight: theme.gap.mlplus,
}}
/>
}
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
sx={{
ml: "auto",
alignSelf: "flex-end",
backgroundColor: "#f4f4f4",
px: theme.gap.medium,
"& svg": {
mr: "6px",
},
}}
/>
{isAdmin && (
<Button
level="tertiary"
label="Configure"
animate="rotate90"
img={
<SettingsIcon
style={{
minWidth: theme.gap.mlplus,
minHeight: theme.gap.mlplus,
}}
/>
}
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
sx={{
ml: "auto",
alignSelf: "flex-end",
backgroundColor: "#f4f4f4",
px: theme.gap.medium,
"& svg": {
mr: "6px",
},
}}
/>
)}
</Stack>
<Stack
direction="row"
@@ -339,4 +341,7 @@ const DetailsPage = () => {
);
};
DetailsPage.propTypes = {
isAdmin: PropTypes.bool,
};
export default DetailsPage;

View File

@@ -30,13 +30,14 @@ import Arrow from "../../assets/icons/top-right-arrow.svg?react";
import ClockSnooze from "../../assets/icons/clock-snooze.svg?react";
import "./index.css";
const ActionsMenu = ({ monitor }) => {
const ActionsMenu = ({ monitor, isAdmin }) => {
const [anchorEl, setAnchorEl] = useState(null);
const [actions, setActions] = useState({});
const [isOpen, setIsOpen] = useState(false);
const dispatch = useDispatch();
const theme = useTheme();
const authState = useSelector((state) => state.auth);
const handleRemove = async (event) => {
event.preventDefault();
event.stopPropagation();
@@ -121,23 +122,27 @@ const ActionsMenu = ({ monitor }) => {
</MenuItem>
{/* TODO - pass monitor id to Incidents page */}
<MenuItem disabled>Incidents</MenuItem>
<MenuItem
onClick={(e) => {
e.stopPropagation();
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/monitors/configure/${actions.id}`);
}}
>
Configure
</MenuItem>
<MenuItem
onClick={(e) => {
e.stopPropagation();
openRemove(e);
}}
>
Remove
</MenuItem>
navigate(`/monitors/configure/${actions.id}`);
}}
>
Configure
</MenuItem>
)}
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
openRemove(e);
}}
>
Remove
</MenuItem>
)}
</Menu>
<Modal
aria-labelledby="modal-delete-monitor"
@@ -205,6 +210,7 @@ ActionsMenu.propTypes = {
url: PropTypes.string,
type: PropTypes.string,
}).isRequired,
isAdmin: PropTypes.bool,
};
/**
@@ -403,7 +409,7 @@ const SkeletonLayout = () => {
);
};
const Monitors = () => {
const Monitors = ({ isAdmin }) => {
const theme = useTheme();
const navigate = useNavigate();
const monitorState = useSelector((state) => state.uptimeMonitors);
@@ -483,7 +489,7 @@ const Monitors = () => {
},
{
id: idx + 4,
data: <ActionsMenu monitor={monitor} />,
data: <ActionsMenu monitor={monitor} isAdmin={isAdmin} />,
},
],
};
@@ -572,14 +578,16 @@ const Monitors = () => {
<Typography>
It looks like you dont have any monitors set up yet.
</Typography>
<Button
level="primary"
label="Create your first monitor"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ mt: theme.gap.large }}
/>
{isAdmin && (
<Button
level="primary"
label="Create your first monitor"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ mt: theme.gap.large }}
/>
)}
</Stack>
) : (
<>
@@ -622,4 +630,7 @@ const Monitors = () => {
);
};
Monitors.propTypes = {
isAdmin: PropTypes.bool,
};
export default Monitors;

View File

@@ -123,7 +123,7 @@ const SkeletonLayout = () => {
);
};
const PageSpeed = () => {
const PageSpeed = ({ isAdmin }) => {
const theme = useTheme();
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -178,10 +178,14 @@ const PageSpeed = () => {
"Give suggestions on how the page can be improved",
]}
link="/pagespeed/create"
isAdmin={isAdmin}
/>
)}
</Box>
);
};
PageSpeed.propTypes = {
isAdmin: PropTypes.bool,
};
export default PageSpeed;