mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-24 02:29:35 -06:00
Merge pull request #735 from bluewave-labs/feat/fe/role-restrictions
Feat/fe/role restrictions, resolves #723
This commit is contained in:
@@ -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/"
|
||||
|
||||
@@ -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;
|
||||
|
||||
20
Client/src/HOC/withAdminProp.jsx
Normal file
20
Client/src/HOC/withAdminProp.jsx
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 don’t 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user