added user permissions provider

master
Tudor Stanciu 2023-04-15 00:07:15 +03:00
parent b7affa7677
commit 0e025e6c68
13 changed files with 307 additions and 206 deletions

6
package-lock.json generated
View File

@ -1325,9 +1325,9 @@
} }
}, },
"@flare/tuitio-client-react": { "@flare/tuitio-client-react": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client-react/-/tuitio-client-react-1.2.5.tgz", "resolved": "https://lab.code-rove.com/public-node-registry/@flare/tuitio-client-react/-/tuitio-client-react-1.2.6.tgz",
"integrity": "sha512-Qx2EFe8WhPhP53Fpai+fhWa7QGG18vwyPvBp8CkWqf+ivHFzz+Rfa9Rrkck3gstQW7b02sYWBFwHMHLmGj8YuA==", "integrity": "sha512-T2uo9r9fQKTsbC25EaQnSifoDKrw3tgwy6fT/O1BdO4RPCUiRDr0YHiIssgPat+sB1G7iiUtV9CS8UNTJkacXw==",
"requires": { "requires": {
"@flare/js-utils": "^1.1.0", "@flare/js-utils": "^1.1.0",
"@flare/tuitio-client": "^1.2.5" "@flare/tuitio-client": "^1.2.5"

View File

@ -14,7 +14,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@flare/js-utils": "^1.1.0", "@flare/js-utils": "^1.1.0",
"@flare/tuitio-client-react": "^1.2.5", "@flare/tuitio-client-react": "^1.2.6",
"@material-ui/core": "^4.11.2", "@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/lab": "^4.0.0-alpha.61",

View File

@ -1,14 +1,12 @@
import { useToast } from "../hooks";
import { get, post } from "../utils/axios"; import { get, post } from "../utils/axios";
import { toast } from "react-toastify";
const networkRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/network`; const networkRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/network`;
const systemRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/system`; const systemRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/system`;
const powerActionsRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/resurrector`; const powerActionsRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/resurrector`;
const securityRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/security`;
const useApi = () => { const handleError = err => {
const { error } = useToast();
const handleError = err => {
let message; let message;
switch (err?.status) { switch (err?.status) {
case 500: case 500:
@ -23,15 +21,15 @@ const useApi = () => {
message = err.title; message = err.title;
} }
error(message); toast.error(message);
}; };
const defaultOptions = { const defaultOptions = {
onCompleted: () => {}, onCompleted: () => {},
onError: handleError onError: handleError
}; };
const call = async (request, options) => { const call = async (request, options) => {
const internalOptions = { ...defaultOptions, ...options }; const internalOptions = { ...defaultOptions, ...options };
const { onCompleted, onError } = internalOptions; const { onCompleted, onError } = internalOptions;
@ -41,75 +39,69 @@ const useApi = () => {
} catch (error) { } catch (error) {
onError(error); onError(error);
} }
}; };
const getSystemVersion = (options = defaultOptions) => { const getPermissions = (options = defaultOptions) => {
const promise = call(() => get(`${securityRoute}/permissions`), options);
return promise;
};
const getSystemVersion = (options = defaultOptions) => {
const releaseNotesPromise = call( const releaseNotesPromise = call(
() => get(`${systemRoute}/version`), () => get(`${systemRoute}/version`),
options options
); );
return releaseNotesPromise; return releaseNotesPromise;
}; };
const readReleaseNotes = (options = defaultOptions) => { const readReleaseNotes = (options = defaultOptions) => {
const releaseNotesPromise = call( const releaseNotesPromise = call(
() => get(`${systemRoute}/release-notes`), () => get(`${systemRoute}/release-notes`),
options options
); );
return releaseNotesPromise; return releaseNotesPromise;
}; };
const readMachines = (options = defaultOptions) => { const readMachines = (options = defaultOptions) => {
const machinesPromise = call( const machinesPromise = call(() => get(`${networkRoute}/machines`), options);
() => get(`${networkRoute}/machines`),
options
);
return machinesPromise; return machinesPromise;
}; };
const wakeMachine = (machineId, options = defaultOptions) => { const wakeMachine = (machineId, options = defaultOptions) => {
const promise = call( const promise = call(
() => post(`${powerActionsRoute}/wake`, { machineId }), () => post(`${powerActionsRoute}/wake`, { machineId }),
options options
); );
return promise; return promise;
}; };
const pingMachine = (machineId, options = defaultOptions) => { const pingMachine = (machineId, options = defaultOptions) => {
const promise = call( const promise = call(
() => post(`${powerActionsRoute}/ping`, { machineId }), () => post(`${powerActionsRoute}/ping`, { machineId }),
options options
); );
return promise; return promise;
}; };
const shutdownMachine = ( const shutdownMachine = (machineId, delay, force, options = defaultOptions) => {
machineId,
delay,
force,
options = defaultOptions
) => {
const promise = call( const promise = call(
() => post(`${powerActionsRoute}/shutdown`, { machineId, delay, force }), () => post(`${powerActionsRoute}/shutdown`, { machineId, delay, force }),
options options
); );
return promise; return promise;
}; };
const restartMachine = ( const restartMachine = (machineId, delay, force, options = defaultOptions) => {
machineId,
delay,
force,
options = defaultOptions
) => {
const promise = call( const promise = call(
() => post(`${powerActionsRoute}/restart`, { machineId, delay, force }), () => post(`${powerActionsRoute}/restart`, { machineId, delay, force }),
options options
); );
return promise; return promise;
}; };
const useApi = () => {
return { return {
getPermissions,
getSystemVersion, getSystemVersion,
readReleaseNotes, readReleaseNotes,
readMachines, readMachines,

View File

@ -1,72 +1,14 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import { UserPermissionsProvider, SensitiveInfoProvider } from "../providers";
import AppLayout from "./layout/AppLayout"; import AppLayout from "./layout/AppLayout";
import { BrowserRouter, Switch, Redirect, Route } from "react-router-dom";
import { useTuitioToken } from "@flare/tuitio-client-react";
import LoginContainer from "../features/login/components/LoginContainer";
const PrivateRoute = ({ component, ...rest }) => {
const { valid } = useTuitioToken();
return (
<Route
{...rest}
render={props =>
valid ? (
React.createElement(component, props)
) : (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
)
}
/>
);
};
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
location: PropTypes.object
};
const PublicRoute = ({ component, ...rest }) => {
const { valid } = useTuitioToken();
return (
<Route
{...rest}
render={props =>
valid ? (
<Redirect
to={{
pathname: "/"
}}
/>
) : (
React.createElement(component, props)
)
}
/>
);
};
PublicRoute.propTypes = {
component: PropTypes.func.isRequired
};
const App = () => { const App = () => {
return ( return (
<BrowserRouter basename={process.env.PUBLIC_URL || ""}> <UserPermissionsProvider>
<Switch> <SensitiveInfoProvider>
<Route exact path="/" render={() => <Redirect to="/dashboard" />} /> <AppLayout />
<PublicRoute path="/login" component={LoginContainer} /> </SensitiveInfoProvider>
<PrivateRoute path="/" component={AppLayout} /> </UserPermissionsProvider>
</Switch>
</BrowserRouter>
); );
}; };

View File

@ -0,0 +1,73 @@
import React from "react";
import PropTypes from "prop-types";
import App from "./App";
import { BrowserRouter, Switch, Redirect, Route } from "react-router-dom";
import { useTuitioToken } from "@flare/tuitio-client-react";
import LoginContainer from "../features/login/components/LoginContainer";
const PrivateRoute = ({ component, ...rest }) => {
const { valid } = useTuitioToken();
return (
<Route
{...rest}
render={props =>
valid ? (
React.createElement(component, props)
) : (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
)
}
/>
);
};
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
location: PropTypes.object
};
const PublicRoute = ({ component, ...rest }) => {
const { valid } = useTuitioToken();
return (
<Route
{...rest}
render={props =>
valid ? (
<Redirect
to={{
pathname: "/"
}}
/>
) : (
React.createElement(component, props)
)
}
/>
);
};
PublicRoute.propTypes = {
component: PropTypes.func.isRequired
};
const AppRouter = () => {
return (
<BrowserRouter basename={process.env.PUBLIC_URL || ""}>
<Switch>
<Route exact path="/" render={() => <Redirect to="/dashboard" />} />
<PublicRoute path="/login" component={LoginContainer} />
<PrivateRoute path="/" component={App} />
</Switch>
</BrowserRouter>
);
};
export default AppRouter;

View File

@ -1,17 +1,15 @@
import React from "react"; import React from "react";
import GuestAnnouncement from "./GuestAnnouncement"; import GuestAnnouncement from "./GuestAnnouncement";
import UserAnnouncement from "./UserAnnouncement"; import UserAnnouncement from "./UserAnnouncement";
import { useTuitioUserInfo } from "@flare/tuitio-client-react"; import { usePermissions } from "../../../hooks";
const AnnouncementsSection = () => { const AnnouncementsSection = () => {
const { userInfo, isGuest } = useTuitioUserInfo(); const { loading, isGuest, isUser } = usePermissions();
const loading = !userInfo;
if (loading) return ""; if (loading) return "";
return ( return (
<> <>
{isUser && <UserAnnouncement />}
{isGuest && <GuestAnnouncement />} {isGuest && <GuestAnnouncement />}
{!isGuest && <UserAnnouncement userData={userInfo} />}
</> </>
); );
}; };

View File

@ -1,22 +1,23 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { Alert, AlertTitle } from "@material-ui/lab"; import { Alert, AlertTitle } from "@material-ui/lab";
import styles from "../styles"; import styles from "../styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useTuitioUser } from "@flare/tuitio-client-react";
const useStyles = makeStyles(styles); const useStyles = makeStyles(styles);
export default function UserAnnouncement({ userData }) { export default function UserAnnouncement() {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const { userName } = useTuitioUser();
return ( return (
<div className={classes.alert}> <div className={classes.alert}>
<Alert variant="outlined" severity="info"> <Alert variant="outlined" severity="info">
<AlertTitle> <AlertTitle>
{t("Dashboard.Announcements.User.Title", { {t("Dashboard.Announcements.User.Title", {
userName: userData.firstName userName
})} })}
</AlertTitle> </AlertTitle>
{t("Dashboard.Announcements.User.Message")} {t("Dashboard.Announcements.User.Message")}
@ -24,7 +25,3 @@ export default function UserAnnouncement({ userData }) {
</div> </div>
); );
} }
UserAnnouncement.propTypes = {
userData: PropTypes.object.isRequired
};

View File

@ -5,13 +5,13 @@ import ActionButton from "./ActionButton";
import { Menu } from "@material-ui/core"; import { Menu } from "@material-ui/core";
import { MoreHoriz } from "@material-ui/icons"; import { MoreHoriz } from "@material-ui/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useTuitioUserInfo } from "@flare/tuitio-client-react"; import { usePermissions } from "../../../../hooks";
const ActionsGroup = ({ machine, actions, addLog }) => { const ActionsGroup = ({ machine, actions, addLog }) => {
const [menuAnchor, setMenuAnchor] = useState(null); const [menuAnchor, setMenuAnchor] = useState(null);
const { t } = useTranslation(); const { t } = useTranslation();
const { isSysAdmin } = useTuitioUserInfo(); const { operateMachines: canOperateMachines } = usePermissions();
const mainActions = useMemo( const mainActions = useMemo(
() => actions.filter(a => a.main === true), () => actions.filter(a => a.main === true),
@ -33,13 +33,16 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
return ( return (
<> <>
<WakeComponent machine={machine} addLog={addLog} disabled={!isSysAdmin} /> <WakeComponent
machine={machine}
addLog={addLog}
disabled={!canOperateMachines}
/>
{mainActions.map(action => ( {mainActions.map(action => (
<ActionButton <ActionButton
key={`machine-item-${machine.machineId}-${action.code}`} key={`machine-item-${machine.machineId}-${action.code}`}
action={action} action={action}
machine={machine} machine={machine}
disabled={!isSysAdmin}
/> />
))} ))}
<ActionButton <ActionButton
@ -65,7 +68,7 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
action={action} action={action}
machine={machine} machine={machine}
callback={handleMenuClose} callback={handleMenuClose}
disabled={!isSysAdmin} disabled={!canOperateMachines}
/> />
))} ))}
</Menu> </Menu>

View File

@ -1,5 +1,6 @@
import { useToast } from "./useToast"; import { useToast } from "./useToast";
import { useSensitiveInfo } from "../providers/SensitiveInfoProvider"; import { useSensitiveInfo } from "../providers/SensitiveInfoProvider";
import { usePermissions } from "../providers/UserPermissionsProvider";
import { useClipboard } from "./useClipboard"; import { useClipboard } from "./useClipboard";
export { useToast, useSensitiveInfo, useClipboard }; export { useToast, useSensitiveInfo, usePermissions, useClipboard };

View File

@ -1,11 +1,11 @@
import { toast } from "react-toastify"; import { toast } from "react-toastify";
export const useToast = () => { const info = message => toast.info(message);
const info = message => toast.info(message); const success = message => toast.success(message);
const success = message => toast.success(message); const warning = message => toast.warning(message);
const warning = message => toast.warning(message); const error = message => toast.error(message);
const error = message => toast.error(message); const dark = message => toast.dark(message);
const dark = message => toast.dark(message);
export const useToast = () => {
return { info, success, warning, error, dark }; return { info, success, warning, error, dark };
}; };

View File

@ -2,23 +2,20 @@ import React, { Suspense } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ThemeProvider from "./providers/ThemeProvider"; import ThemeProvider from "./providers/ThemeProvider";
import CssBaseline from "@material-ui/core/CssBaseline"; import CssBaseline from "@material-ui/core/CssBaseline";
import App from "./components/App"; import AppRouter from "./components/AppRouter";
import { TuitioProvider } from "@flare/tuitio-client-react"; import { TuitioProvider } from "@flare/tuitio-client-react";
import ToastProvider from "./providers/ToastProvider"; import { ToastProvider } from "./providers";
import SensitiveInfoProvider from "./providers/SensitiveInfoProvider";
import "./utils/i18n"; import "./utils/i18n";
ReactDOM.render( ReactDOM.render(
<TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}> <TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}>
<ThemeProvider> <ThemeProvider>
<CssBaseline /> <CssBaseline />
<SensitiveInfoProvider>
<Suspense fallback={<div>Loading...</div>}> <Suspense fallback={<div>Loading...</div>}>
<ToastProvider> <ToastProvider>
<App /> <AppRouter />
</ToastProvider> </ToastProvider>
</Suspense> </Suspense>
</SensitiveInfoProvider>
</ThemeProvider> </ThemeProvider>
</TuitioProvider>, </TuitioProvider>,
document.getElementById("root") document.getElementById("root")

View File

@ -0,0 +1,87 @@
import React, { useState, useEffect, useContext, useMemo } from "react";
import PropTypes from "prop-types";
import useApi from "../api";
const permissionCodes = {
VIEW_DASHBOARD: "VIEW_DASHBOARD",
MANAGE_USERS: "MANAGE_USERS",
MANAGE_SETTINGS: "MANAGE_SETTINGS",
VIEW_MACHINES: "VIEW_MACHINES",
MANAGE_MACHINES: "MANAGE_MACHINES",
OPERATE_MACHINES: "OPERATE_MACHINES",
GUEST_ACCESS: "GUEST_ACCESS"
};
const initialState = {
permissions: [],
loading: true
};
const getPermissionFlags = permissions => {
const viewDashboard =
permissions.includes(permissionCodes.VIEW_DASHBOARD) ?? false;
const manageUsers =
permissions.includes(permissionCodes.MANAGE_USERS) ?? false;
const manageSettings =
permissions.includes(permissionCodes.MANAGE_SETTINGS) ?? false;
const viewMachines =
permissions.includes(permissionCodes.VIEW_MACHINES) ?? false;
const manageMachines =
permissions.includes(permissionCodes.MANAGE_MACHINES) ?? false;
const operateMachines =
permissions.includes(permissionCodes.OPERATE_MACHINES) ?? false;
const guestAccess =
permissions.includes(permissionCodes.GUEST_ACCESS) ?? false;
const flags = {
viewDashboard,
manageUsers,
manageSettings,
viewMachines,
manageMachines,
operateMachines,
guestAccess
};
const isGuest = guestAccess === true;
const isUser = Object.values(flags).includes(true) && !flags.guestAccess;
return {
...flags,
isGuest,
isUser
};
};
const UserPermissionsContext = React.createContext(initialState);
const usePermissions = () => {
const { permissions, loading } = useContext(UserPermissionsContext);
const flags = useMemo(() => getPermissionFlags(permissions), [permissions]);
return { loading, ...flags };
};
const UserPermissionsProvider = ({ children }) => {
const [permissions, setPermissions] = useState(initialState);
const { getPermissions } = useApi();
useEffect(() => {
getPermissions({
onCompleted: data => setPermissions({ ...data, loading: false })
});
}, [getPermissions]);
return (
<UserPermissionsContext.Provider value={permissions}>
{children}
</UserPermissionsContext.Provider>
);
};
UserPermissionsProvider.propTypes = {
children: PropTypes.node.isRequired
};
export { UserPermissionsProvider, usePermissions };
export default UserPermissionsProvider;

11
src/providers/index.js Normal file
View File

@ -0,0 +1,11 @@
import ThemeProvider from "./ThemeProvider";
import ToastProvider from "./ToastProvider";
import SensitiveInfoProvider from "./SensitiveInfoProvider";
import UserPermissionsProvider from "./UserPermissionsProvider";
export {
ThemeProvider,
ToastProvider,
SensitiveInfoProvider,
UserPermissionsProvider
};