From 802f91b12643e11c29aee887ddcd2ca81b0a4f07 Mon Sep 17 00:00:00 2001 From: rbisson <remi.bisson@inrae.fr> Date: Fri, 21 Feb 2025 11:41:49 +0100 Subject: [PATCH 1/2] [package.json] upgraded packages [Header] Added link to search [Forms] Added toasts to API calls [Sources] Added new createdAd and creator columns [Fields][Sources] Added loader to OnSave button --- package.json | 54 +-- src/components/App.js | 141 +++++- src/components/Common/Common.js | 76 ---- src/components/Common/package.json | 6 - src/components/Header/Header.js | 180 ++++---- src/components/Header/styles.js | 4 +- src/components/Layout/Layout.js | 148 ++----- src/components/Layout/styles.js | 44 +- src/components/Sidebar/Sidebar.js | 31 +- src/components/Sidebar/components/Dot.js | 52 +-- .../components/SidebarLink/SidebarLink.js | 13 +- .../Sidebar/components/SidebarLink/styles.js | 106 ++--- src/components/Sidebar/styles.js | 6 +- src/components/ToastMessage/ToastMessage.js | 17 + src/components/Widget/Widget.js | 137 +++--- src/components/Widget/styles.js | 78 ++-- src/components/Wrappers/Wrappers.js | 233 +++++----- src/index.js | 18 +- src/pages/dashboard/Dashboard.js | 97 +++-- .../dashboard/components/BigStat/BigStat.js | 184 ++++---- .../dashboard/components/BigStat/styles.js | 84 ++-- src/pages/dashboard/components/Table/Table.js | 82 ++-- src/pages/dashboard/styles.js | 4 +- src/pages/error/403.js | 16 +- src/pages/error/404.js | 14 +- src/pages/error/styles.js | 22 +- src/pages/fields/Fields.js | 100 ++--- src/pages/fields/styles.js | 178 ++++---- src/pages/groups/Groups.js | 340 +++++++-------- src/pages/groups/styles.js | 82 ++-- src/pages/home/Home.js | 62 ++- src/pages/home/styles.js | 25 -- src/pages/policies/AssignedPolicies.js | 238 +++++----- src/pages/policies/NewPolicyForm.js | 107 +++-- src/pages/policies/Policies.js | 151 +++---- src/pages/policies/PolicyAssignment.js | 108 +++-- src/pages/policies/PolicyGroupAssignment.js | 102 ++--- src/pages/policies/PolicySourceAssignment.js | 110 +++-- src/pages/policies/styles.js | 82 ++-- src/pages/requests/Requests.js | 105 ++--- src/pages/requests/styles.js | 52 +-- src/pages/roles/Roles.js | 340 +++++++-------- src/pages/roles/styles.js | 82 ++-- src/pages/sources/NewSourceForm.js | 195 +++++++++ src/pages/sources/Sources.js | 406 ++---------------- src/pages/sources/SourcesTable.js | 127 ++++++ src/pages/sources/styles.js | 178 ++++---- src/pages/users/Users.js | 119 ++--- src/pages/users/styles.js | 52 +-- src/themes/index.js | 3 +- src/utils.js | 4 +- 51 files changed, 2470 insertions(+), 2725 deletions(-) delete mode 100644 src/components/Common/Common.js delete mode 100644 src/components/Common/package.json create mode 100644 src/components/ToastMessage/ToastMessage.js delete mode 100644 src/pages/home/styles.js create mode 100644 src/pages/sources/NewSourceForm.js create mode 100644 src/pages/sources/SourcesTable.js diff --git a/package.json b/package.json index e4a846a..8d9a489 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,25 @@ "homepage": ".", "dependencies": { "@elastic/datemath": "^5.0.3", - "@elastic/eui": "^27.4.0", - "@material-ui/core": "^4.11.0", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^4.0.0-alpha.48", - "@material-ui/styles": "^4.10.0", + "@elastic/eui": "^99.3.0", + "@emotion/css": "^11.11.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", "@microlink/react-json-view": "^1.23.1", + "@mui/icons-material": "^6.4.5", + "@mui/material": "^6.4.5", + "@mui/styles": "^6.4.5", "moment": "^2.27.0", - "mui-datatables": "^3.4.0", + "mui-datatables": "^4.3.0", "oidc-client-ts": "^3.1.0", - "papaparse": "5.3.0", - "react": "^16.13.1", + "papaparse": "5.5.2", + "react": "^18.3.1", "react-apexcharts": "^1.3.3", - "react-dom": "^16.13.1", + "react-dom": "^18.3.1", "react-oidc-context": "^3.2.0", - "react-router-dom": "^5.2.0", - "react-scripts": "^3.3.0", + "react-router": "^7.2.0", + "react-scripts": "^5.0.1", + "react-toastify": "^11.0.3", "recharts": "^2.0.0-beta.1", "tinycolor2": "^1.4.1" }, @@ -46,11 +49,8 @@ ] }, "devDependencies": { - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.1.3", - "husky": "8.0.3", - "jscs": "^3.0.7", - "lint-staged": "14.0.1", + "husky": "9.1.7", + "lint-staged": "15.4.3", "prettier": "^3.2.5" }, "husky": { @@ -59,32 +59,34 @@ } }, "eslintConfig": { + "rules": { + "react/prop-types": "off" + }, "env": { "node": true, "browser": true, "es6": true }, "extends": [ - "plugin:react/recommended", - "plugin:prettier/recommended" + "eslint:recommended", + "plugin:react/recommended" + ], + "plugins": [ + "react", + "react-hooks" ], "parserOptions": { "ecmaFeatures": { "jsx": true } - }, - "plugins": [ - "react" - ] + } }, "lint-staged": { "*.+(js|jsx)": [ - "eslint --fix", - "git add" + "eslint --fix" ], "*.+(json|css|md)": [ - "prettier --write", - "git add" + "prettier --write" ] }, "prettier": { diff --git a/src/components/App.js b/src/components/App.js index 9812ec8..03cf16d 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -1,19 +1,136 @@ -import React from 'react'; -import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; -import Layout from './Layout'; +import React, { useEffect, useState } from 'react'; +import { HashRouter, Navigate, Route, Routes } from 'react-router'; import Error404 from '../pages/error/404'; import Error403 from '../pages/error/403'; +import Home from '../pages/home'; +import Dashboard from '../pages/dashboard'; +import Users from '../pages/users'; +import Roles from '../pages/roles'; +import Groups from '../pages/groups'; +import Requests from '../pages/requests'; +import Policies from '../pages/policies'; +import Sources from '../pages/sources'; +import Fields from '../pages/fields'; +import { useAuth } from 'react-oidc-context'; +import { createUser, findUserBySub } from '../services/GatekeeperService'; +import Layout from './Layout'; + +const App = () => { + const auth = useAuth(); + const [roles, setRoles] = useState([]); + const [user, setUser] = useState(); + + useEffect(() => { + const fetchUser = async (sub) => { + let user = await findUserBySub(sub); + if (!user.id) { + // User registered on Keycloak but not in the database + if (auth.user?.profile?.email) { + const result = await createUser(sub, auth.user?.profile?.email); + if (result) { + user = await findUserBySub(sub); + } else { + auth.signoutRedirect(); + } + } + } + setUser(user); + setRoles(user.roles.map((r) => r.role_id)); + }; + if (!auth || auth.isLoading) { + return; + } + if (auth.isAuthenticated) { + fetchUser(auth.user.profile.sub); + } else { + auth.signinRedirect(); + } + }, [auth]); + + const ProtectedRoute = ({ children, isAllowed, redirectPath = '/forbidden' }) => { + if (!isAllowed) { + return <Navigate to={redirectPath} />; + } + return <>{children}</>; + }; -export default function App() { return ( <HashRouter> - <Switch> - <Route exact path="/" render={() => <Redirect to="/home" />} /> - <Route path="/not-found" component={Error404} /> - <Route path="/forbidden" component={Error403} /> - <Route path="/" component={Layout} /> - <Route render={() => <Redirect to="/not-found" />} /> - </Switch> + <Routes> + <Route exact path="/" element={<Navigate to="/home" />} /> + <Route path="/not-found" element={<Error404 />} /> + <Route path="/forbidden" element={<Error403 />} /> + <Route element={<Layout user={user} roles={roles} />}> + <Route path="/home" element={<Home />} /> + <Route + path="/dashboard" + element={ + <ProtectedRoute isAllowed={roles.includes(1)}> + <Dashboard /> + </ProtectedRoute> + } + /> + <Route + path="/users" + element={ + <ProtectedRoute isAllowed={roles.includes(1)}> + <Users /> + </ProtectedRoute> + } + /> + <Route + path="/roles" + element={ + <ProtectedRoute isAllowed={roles.includes(1)}> + <Roles /> + </ProtectedRoute> + } + /> + <Route + path="/groups" + element={ + <ProtectedRoute isAllowed={roles.includes(1)}> + <Groups /> + </ProtectedRoute> + } + /> + <Route + path="/requests" + element={ + <ProtectedRoute isAllowed={roles.includes(1)}> + <Requests /> + </ProtectedRoute> + } + /> + <Route + path="/policies" + element={ + <ProtectedRoute isAllowed={roles.includes(1) || roles.includes(2)}> + <Policies /> + </ProtectedRoute> + } + /> + <Route + path="/sources" + element={ + <ProtectedRoute isAllowed={roles.includes(1) || roles.includes(2)}> + <Sources /> + </ProtectedRoute> + } + /> + <Route + path="/fields" + element={ + <ProtectedRoute isAllowed={roles.includes(1)}> + <Fields /> + </ProtectedRoute> + } + /> + </Route> + <Route render={() => <Navigate to="/not-found" />} /> + </Routes> </HashRouter> ); -} +}; + +export default App; diff --git a/src/components/Common/Common.js b/src/components/Common/Common.js deleted file mode 100644 index 13ff279..0000000 --- a/src/components/Common/Common.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import Button from '@material-ui/core/Button'; -import CloudUploadIcon from '@material-ui/icons/CloudUpload'; -import SaveIcon from '@material-ui/icons/Save'; -import Snackbar from '@material-ui/core/Snackbar'; -import MuiAlert from '@material-ui/lab/Alert'; - -function Alert(props) { - return <MuiAlert elevation={6} variant="filled" {...props} />; -} - -export const MessageBox = (message, enabled, style) => { - return ( - <> - {enabled && ( - <> - <div className={style.MessageBox}>{message}</div> - </> - )} - </> - ); -}; - -export const InputFile = (onChange, style) => { - return ( - <> - <div className={style.dashedBorder}> - <label htmlFor="outlined-button-file"> - <input - className={style.input} - id="outlined-button-file" - type="file" - multiple - onChange={onChange} - /> - <Button variant="outlined" component="span" startIcon={<CloudUploadIcon />}> - télécharger - </Button> - </label> - </div> - </> - ); -}; - -export const ActionBar = (enabled, style, onSendClick) => { - return ( - <> - {enabled && ( - <> - <div className={style.ActionBar}> - <Button - variant="contained" - color="primary" - onClick={onSendClick} - startIcon={<SaveIcon />} - > - Envoyez - </Button> - </div> - </> - )} - </> - ); -}; - -export const ShowAlert = (open, handleClose, message, severity) => { - return ( - <> - <Snackbar open={open} autoHideDuration={6000} onClose={handleClose}> - <Alert onClose={handleClose} severity={severity}> - {message} - </Alert> - </Snackbar> - </> - ); -}; diff --git a/src/components/Common/package.json b/src/components/Common/package.json deleted file mode 100644 index 2065b9d..0000000 --- a/src/components/Common/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Common", - "version": "0.0.0", - "private": true, - "main": "Common.js" -} diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 6c09d23..7416d1f 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -1,96 +1,94 @@ -import React, { useState } from 'react'; -import { AppBar, Toolbar, IconButton, Menu } from '@material-ui/core'; -import { - Menu as MenuIcon, - Person as AccountIcon, - ArrowBack as ArrowBackIcon, -} from '@material-ui/icons'; +import React, {useState} from 'react'; +import {AppBar, Box, IconButton, Link, Menu, Toolbar} from '@mui/material'; +import {Menu as MenuIcon, Person as AccountIcon} from '@mui/icons-material'; +import LaunchIcon from '@mui/icons-material/Launch'; import classNames from 'classnames'; import useStyles from './styles'; -import { Typography } from '../Wrappers/Wrappers'; -import { - useLayoutState, - useLayoutDispatch, - toggleSidebar, -} from '../../context/LayoutContext'; -import { useAuth } from 'react-oidc-context'; +import {Typography} from '../Wrappers/Wrappers'; +import {toggleSidebar, useLayoutDispatch} from '../../context/LayoutContext'; +import {useAuth} from 'react-oidc-context'; +import {EuiToolTip} from '@elastic/eui'; -export default function Header({ email }) { - const classes = useStyles(); - const layoutState = useLayoutState(); - const layoutDispatch = useLayoutDispatch(); - const auth = useAuth(); - let [profileMenu, setProfileMenu] = useState(null); +export default function Header({email}) { + const classes = useStyles(); + const layoutDispatch = useLayoutDispatch(); + const auth = useAuth(); + let [profileMenu, setProfileMenu] = useState(null); - return ( - <AppBar position="fixed" className={classes.appBar}> - <Toolbar className={classes.toolbar}> - <IconButton - color="inherit" - onClick={() => toggleSidebar(layoutDispatch)} - className={classNames( - classes.headerMenuButton, - classes.headerMenuButtonCollapse - )} - > - {layoutState.isSidebarOpened ? ( - <ArrowBackIcon - classes={{ - root: classNames(classes.headerIcon, classes.headerIconCollapse), - }} - /> - ) : ( - <MenuIcon - classes={{ - root: classNames(classes.headerIcon, classes.headerIconCollapse), - }} - /> - )} - </IconButton> - <Typography variant="h6" weight="medium" className={classes.title}> - IN-SYLVA IS administration Portal - </Typography> - <div className={classes.grow} /> - <IconButton - aria-haspopup="true" - color="inherit" - className={classes.headerMenuButton} - aria-controls="profile-menu" - onClick={(e) => setProfileMenu(e.currentTarget)} - > - <AccountIcon classes={{ root: classes.headerIcon }} /> - </IconButton> - <Menu - id="profile-menu" - open={Boolean(profileMenu)} - anchorEl={profileMenu} - onClose={() => setProfileMenu(null)} - className={classes.headerMenu} - classes={{ paper: classes.profileMenu }} - disableAutoFocusItem - > - <div className={classes.profileMenuUser}> - <div className={classes.profileMenuItem}> - <Typography variant="h4" weight="medium"> - {email} - </Typography> - </div> - <div className={classes.profileMenuItem}> - <Typography - className={classes.profileMenuLink} - color="primary" - onClick={async () => { - await auth.signoutRedirect({ - post_logout_redirect_uri: `${process.env.REACT_APP_BASE_URL}`, - }); - }} - > - Sign out - </Typography> - </div> - </div> - </Menu> - </Toolbar> - </AppBar> - ); + return ( + <AppBar position="fixed" className={classes.appBar}> + <Toolbar className={classes.toolbar} variant={'dense'}> + <IconButton + color="inherit" + onClick={() => toggleSidebar(layoutDispatch)} + className={classNames( + classes.headerMenuButton, + classes.headerMenuButtonCollapse + )} + > + <MenuIcon + classes={{ + root: classNames(classes.headerIcon, classes.headerIconCollapse), + }} + /> + </IconButton> + <Typography variant="h6" weight="medium" className={classes.title}> + IN-SYLVA IS administration Portal + </Typography> + <div className={classes.grow}/> + <EuiToolTip position={'bottom'} content={'Make a search'}> + <Link + href={process.env.REACT_APP_SEARCH_URL} + color="inherit" + rel="noopener noreferrer" + target="_blank" + > + <Box display="flex" flexDirection="row" alignItems="center"> + <p style={{marginRight: '5px'}}>Go to Search</p> + <LaunchIcon style={{fontSize: 12}}/> + </Box> + </Link> + </EuiToolTip> + <IconButton + aria-haspopup="true" + color="inherit" + className={classes.headerMenuButton} + aria-controls="profile-menu" + onClick={(e) => setProfileMenu(e.currentTarget)} + > + <AccountIcon classes={{root: classes.headerIcon}}/> + </IconButton> + <Menu + id="profile-menu" + open={Boolean(profileMenu)} + anchorEl={profileMenu} + onClose={() => setProfileMenu(null)} + className={classes.headerMenu} + classes={{paper: classes.profileMenu}} + disableAutoFocusItem + > + <div className={classes.profileMenuUser}> + <div className={classes.profileMenuItem}> + <Typography variant="h4" weight="medium"> + {email} + </Typography> + </div> + <div className={classes.profileMenuItem}> + <Typography + className={classes.profileMenuLink} + color="primary" + onClick={async () => { + await auth.signoutRedirect({ + post_logout_redirect_uri: `${process.env.REACT_APP_BASE_URL}`, + }); + }} + > + Sign out + </Typography> + </div> + </div> + </Menu> + </Toolbar> + </AppBar> + ); } diff --git a/src/components/Header/styles.js b/src/components/Header/styles.js index c56095f..ab029d2 100644 --- a/src/components/Header/styles.js +++ b/src/components/Header/styles.js @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@mui/styles'; export default makeStyles((theme) => ({ title: { @@ -31,7 +31,7 @@ export default makeStyles((theme) => ({ flexGrow: 1, }, headerMenu: { - marginTop: theme.spacing(7), + marginTop: theme.spacing(2), }, headerMenuButton: { marginLeft: theme.spacing(2), diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js index 239296a..aff32c3 100644 --- a/src/components/Layout/Layout.js +++ b/src/components/Layout/Layout.js @@ -1,126 +1,48 @@ -import React, { useState, useEffect } from 'react'; -import { Route, Switch, withRouter, Redirect } from 'react-router-dom'; +import React from 'react'; import classnames from 'classnames'; import useStyles from './styles'; import Header from '../Header'; import Sidebar from '../Sidebar'; -import Home from '../../pages/home'; -import Dashboard from '../../pages/dashboard'; -import Users from '../../pages/users'; -import Roles from '../../pages/roles/Roles'; -import Requests from '../../pages/requests'; -import Sources from '../../pages/sources/Sources'; -import Fields from '../../pages/fields'; -import Policies from '../../pages/policies'; -import Groups from '../../pages/groups/Groups'; import { useLayoutState } from '../../context/LayoutContext'; -import { useAuth } from 'react-oidc-context'; -import { createUser, findUserBySub } from '../../services/GatekeeperService'; +import { Slide, ToastContainer } from 'react-toastify'; +import { Outlet } from 'react-router'; +import { EuiPage, EuiPageBody } from '@elastic/eui'; -const ProtectedRoute = ({ isAllowed, redirectPath = '/forbidden', path, component }) => { - if (!isAllowed) { - return <Redirect to={redirectPath} />; - } - return <Route path={path} component={component} />; -}; - -function Layout(props) { +const Layout = ({ user, roles }) => { const classes = useStyles(); const layoutState = useLayoutState(); - const auth = useAuth(); - const [user, setUser] = useState(null); - const [roles, setRoles] = useState([]); - - useEffect(() => { - const fetchUser = async (sub) => { - let user = await findUserBySub(sub); - if (!user.id) { - // User registered on Keycloak but not in the database - if (auth.user?.profile?.email) { - const result = await createUser(sub, auth.user?.profile?.email); - - if (result) { - user = await findUserBySub(sub); - } else { - auth.signoutRedirect(); - } - } - } - setUser(user); - setRoles(user.roles.map((r) => r.role_id)); - }; - if (!auth || auth.isLoading) { - return; - } - if (auth.isAuthenticated) { - fetchUser(auth.user.profile.sub); - } else { - auth.signinRedirect(); - } - }, [auth]); return ( - user && - roles.length > 0 && ( - <div className={classes.root}> - <> - <Header email={user.email} usehistory={props.history} /> - <Sidebar roles={roles} /> - <div - className={classnames(classes.content, { - [classes.contentShift]: layoutState.isSidebarOpened, - })} - > - <div className={classes.fakeToolbar} /> - <Switch> - <Route path="/home" component={Home} /> - <ProtectedRoute - path="/dashboard" - isAllowed={roles.includes(1)} - component={Dashboard} - /> - <ProtectedRoute - path="/users" - isAllowed={roles.includes(1)} - component={Users} - /> - <ProtectedRoute - isAllowed={roles.includes(1)} - path="/roles" - component={Roles} - /> - <ProtectedRoute - isAllowed={roles.includes(1)} - path="/groups" - component={Groups} - /> - <ProtectedRoute - isAllowed={roles.includes(1)} - path="/requests" - component={Requests} - /> - <ProtectedRoute - isAllowed={roles.includes(1)} - path="/policies" - component={Policies} - /> - <ProtectedRoute - isAllowed={roles.includes(1) || roles.includes(2)} - path="/sources" - component={Sources} - /> - <ProtectedRoute - isAllowed={roles.includes(1) || roles.includes(2)} - path="/fields" - component={Fields} - /> - <Route render={() => <Redirect to="/not-found" />} /> - </Switch> - </div> - </> + <div className={classes.root}> + <ToastContainer + position="bottom-right" + autoClose={5000} + hideProgressBar + newestOnTop={false} + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable + pauseOnHover + theme="light" + transition={Slide} + /> + <Header email={user?.email} /> + <Sidebar roles={roles} /> + <div + className={classnames(classes.content, { + [classes.contentShift]: layoutState.isSidebarOpened, + })} + > + <div className={classes.fakeToolbar} /> + <EuiPage> + <EuiPageBody> + <Outlet /> + </EuiPageBody> + </EuiPage> </div> - ) + </div> ); -} +}; -export default withRouter(Layout); +export default Layout; diff --git a/src/components/Layout/styles.js b/src/components/Layout/styles.js index 05e123e..937eb99 100644 --- a/src/components/Layout/styles.js +++ b/src/components/Layout/styles.js @@ -1,25 +1,25 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - root: { - display: 'flex', - maxWidth: '100vw', - overflowX: 'hidden', - }, - content: { - flexGrow: 1, - padding: theme.spacing(3), - width: `calc(100vw - 240px)`, - minHeight: '100vh', - }, - contentShift: { - width: `calc(100vw - ${240 + theme.spacing(6)}px)`, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }, - fakeToolbar: { - ...theme.mixins.toolbar, - }, + root: { + display: 'flex', + maxWidth: '100vw', + overflowX: 'hidden', + }, + content: { + flexGrow: 1, + padding: theme.spacing(3), + width: `calc(100vw - 240px)`, + minHeight: '100vh', + }, + contentShift: { + width: `calc(100vw - ${240 + theme.spacing(6)}px)`, + transition: theme.transitions.create(['width', 'margin'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }, + fakeToolbar: { + ...theme.mixins.toolbar, + }, })); diff --git a/src/components/Sidebar/Sidebar.js b/src/components/Sidebar/Sidebar.js index e6a2006..ee4dcfd 100644 --- a/src/components/Sidebar/Sidebar.js +++ b/src/components/Sidebar/Sidebar.js @@ -1,22 +1,21 @@ -import React, { useState, useEffect } from 'react'; -import { Drawer, IconButton, List } from '@material-ui/core'; -import { ArrowBack as ArrowBackIcon } from '@material-ui/icons'; -import { useTheme } from '@material-ui/styles'; -import { withRouter } from 'react-router-dom'; +import React, { useEffect, useState } from 'react'; +import { Drawer, IconButton, List } from '@mui/material'; +import { ArrowBack as ArrowBackIcon } from '@mui/icons-material'; +import { useTheme } from '@mui/styles'; import classNames from 'classnames'; import useStyles from './styles'; import SidebarLink from './components/SidebarLink/SidebarLink'; import { - useLayoutState, - useLayoutDispatch, toggleSidebar, + useLayoutDispatch, + useLayoutState, } from '../../context/LayoutContext'; import { getSideBarItems } from '../../utils'; -const Sidebar = ({ location, roles }) => { +const Sidebar = ({ roles }) => { const classes = useStyles(); const theme = useTheme(); - let { isSidebarOpened } = useLayoutState(); + let { isSidebarOpened } = useLayoutState(false); const layoutDispatch = useLayoutDispatch(); let [isPermanent, setPermanent] = useState(true); const [sidebarItems, setSidebarItems] = useState([]); @@ -27,7 +26,6 @@ const Sidebar = ({ location, roles }) => { const windowWidth = window.innerWidth; const breakpointWidth = theme.breakpoints.values.md; const isSmallScreen = windowWidth < breakpointWidth; - if (isSmallScreen && isPermanent) { setPermanent(false); } else if (!isSmallScreen && !isPermanent) { @@ -44,14 +42,7 @@ const Sidebar = ({ location, roles }) => { const buildSidebarItems = () => { return sidebarItems.map((item) => { if (item.roles.some((authorizedRoleId) => roles.includes(authorizedRoleId))) { - return ( - <SidebarLink - key={item.id} - location={location} - isSidebarOpened={isSidebarOpened} - {...item} - /> - ); + return <SidebarLink key={item.id} isSidebarOpened={isSidebarOpened} {...item} />; } else { return null; } @@ -81,9 +72,9 @@ const Sidebar = ({ location, roles }) => { /> </IconButton> </div> - <List className={classes.sidebarList}>{buildSidebarItems()}</List> + <List>{buildSidebarItems()}</List> </Drawer> ); }; -export default withRouter(Sidebar); +export default Sidebar; diff --git a/src/components/Sidebar/components/Dot.js b/src/components/Sidebar/components/Dot.js index 6cb089f..bf3ac2a 100644 --- a/src/components/Sidebar/components/Dot.js +++ b/src/components/Sidebar/components/Dot.js @@ -1,34 +1,34 @@ import React from 'react'; -import { makeStyles, useTheme } from '@material-ui/styles'; +import {makeStyles, useTheme} from '@mui/styles'; import classnames from 'classnames'; const useStyles = makeStyles((theme) => ({ - dotBase: { - width: 5, - height: 5, - backgroundColor: theme.palette.text.hint, - borderRadius: '50%', - transition: theme.transitions.create('background-color'), - }, - dotLarge: { - width: 8, - height: 8, - }, + dotBase: { + width: 5, + height: 5, + backgroundColor: theme.palette.text.hint, + borderRadius: '50%', + transition: theme.transitions.create('background-color'), + }, + dotLarge: { + width: 8, + height: 8, + }, })); -export default function Dot({ size, color }) { - const classes = useStyles(); - const theme = useTheme(); +export default function Dot({size, color}) { + const classes = useStyles(); + const theme = useTheme(); - return ( - <div - className={classnames(classes.dotBase, { - [classes.dotLarge]: size === 'large', - [classes.dotSmall]: size === 'small', - })} - style={{ - backgroundColor: color && theme.palette[color] && theme.palette[color].main, - }} - /> - ); + return ( + <div + className={classnames(classes.dotBase, { + [classes.dotLarge]: size === 'large', + [classes.dotSmall]: size === 'small', + })} + style={{ + backgroundColor: color && theme.palette[color] && theme.palette[color].main, + }} + /> + ); } diff --git a/src/components/Sidebar/components/SidebarLink/SidebarLink.js b/src/components/Sidebar/components/SidebarLink/SidebarLink.js index f7363bc..be26c48 100644 --- a/src/components/Sidebar/components/SidebarLink/SidebarLink.js +++ b/src/components/Sidebar/components/SidebarLink/SidebarLink.js @@ -7,9 +7,9 @@ import { ListItemIcon, ListItemText, Typography, -} from '@material-ui/core'; -import { Inbox as InboxIcon } from '@material-ui/icons'; -import { Link } from 'react-router-dom'; +} from '@mui/material'; +import { Inbox as InboxIcon } from '@mui/icons-material'; +import { Link, useLocation } from 'react-router'; import classnames from 'classnames'; import useStyles from './styles'; import Dot from '../Dot'; @@ -19,13 +19,13 @@ export default function SidebarLink({ icon, label, children, - location, isSidebarOpened, nested, type, }) { const classes = useStyles(); let [isOpen, setIsOpen] = useState(false); + let location = useLocation(); const isLinkActive = link && (location.pathname === link || location.pathname.indexOf(link) !== -1); @@ -49,7 +49,6 @@ export default function SidebarLink({ if (!children) { return ( <ListItem - button component={link && Link} to={link} className={classes.link} @@ -59,7 +58,6 @@ export default function SidebarLink({ [classes.linkNested]: nested, }), }} - disableRipple > <ListItemIcon className={classnames(classes.linkIcon, { @@ -84,12 +82,10 @@ export default function SidebarLink({ return ( <> <ListItem - button component={link && Link} onClick={toggleCollapse} className={classes.link} to={link} - disableRipple > <ListItemIcon className={classnames(classes.linkIcon, { @@ -119,7 +115,6 @@ export default function SidebarLink({ {children.map((childrenLink) => ( <SidebarLink key={childrenLink && childrenLink.link} - location={location} isSidebarOpened={isSidebarOpened} classes={classes} nested diff --git a/src/components/Sidebar/components/SidebarLink/styles.js b/src/components/Sidebar/components/SidebarLink/styles.js index 1355dd2..21cbcb0 100644 --- a/src/components/Sidebar/components/SidebarLink/styles.js +++ b/src/components/Sidebar/components/SidebarLink/styles.js @@ -1,56 +1,56 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - link: { - textDecoration: 'none', - '&:hover, &:focus': { - backgroundColor: theme.palette.background.light, - }, - }, - linkActive: { - backgroundColor: theme.palette.background.light, - }, - linkNested: { - paddingLeft: 0, - '&:hover, &:focus': { - backgroundColor: '#FFFFFF', - }, - }, - linkIcon: { - marginRight: theme.spacing(1), - color: theme.palette.text.secondary + '99', - transition: theme.transitions.create('color'), - width: 24, - display: 'flex', - justifyContent: 'center', - }, - linkIconActive: { - color: theme.palette.primary.main, - }, - linkText: { - padding: 0, - color: theme.palette.text.secondary + 'CC', - transition: theme.transitions.create(['opacity', 'color']), - fontSize: 16, - }, - linkTextActive: { - color: theme.palette.text.primary, - }, - linkTextHidden: { - opacity: 0, - }, - nestedList: { - paddingLeft: theme.spacing(2) + 30, - }, - sectionTitle: { - marginLeft: theme.spacing(4.5), - marginTop: theme.spacing(2), - marginBottom: theme.spacing(2), - }, - divider: { - marginTop: theme.spacing(2), - marginBottom: theme.spacing(4), - height: 1, - backgroundColor: '#D8D8D880', - }, + link: { + textDecoration: 'none', + '&:hover, &:focus': { + backgroundColor: theme.palette.background.light, + }, + }, + linkActive: { + backgroundColor: theme.palette.background.light, + }, + linkNested: { + paddingLeft: 0, + '&:hover, &:focus': { + backgroundColor: '#FFFFFF', + }, + }, + linkIcon: { + marginRight: theme.spacing(1), + color: theme.palette.text.secondary + '99', + transition: theme.transitions.create('color'), + width: 24, + display: 'flex', + justifyContent: 'center', + }, + linkIconActive: { + color: theme.palette.primary.main, + }, + linkText: { + padding: 0, + color: theme.palette.text.secondary + 'CC', + transition: theme.transitions.create(['opacity', 'color']), + fontSize: 16, + }, + linkTextActive: { + color: theme.palette.text.primary, + }, + linkTextHidden: { + opacity: 0, + }, + nestedList: { + paddingLeft: theme.spacing(2) + 30, + }, + sectionTitle: { + marginLeft: theme.spacing(4.5), + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + divider: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(4), + height: 1, + backgroundColor: '#D8D8D880', + }, })); diff --git a/src/components/Sidebar/styles.js b/src/components/Sidebar/styles.js index fabdf1c..f76612d 100644 --- a/src/components/Sidebar/styles.js +++ b/src/components/Sidebar/styles.js @@ -1,6 +1,6 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@mui/styles'; -const drawerWidth = 240; +const drawerWidth = '240px'; export default makeStyles((theme) => ({ menuButton: { @@ -28,7 +28,7 @@ export default makeStyles((theme) => ({ duration: theme.transitions.duration.leavingScreen, }), overflowX: 'hidden', - width: theme.spacing(7) + 40, + width: '90px', [theme.breakpoints.down('sm')]: { width: drawerWidth, }, diff --git a/src/components/ToastMessage/ToastMessage.js b/src/components/ToastMessage/ToastMessage.js new file mode 100644 index 0000000..7a04ca9 --- /dev/null +++ b/src/components/ToastMessage/ToastMessage.js @@ -0,0 +1,17 @@ +import React from 'react'; +import {EuiFlexGroup, EuiText, EuiTitle} from '@elastic/eui'; + +const ToastMessage = ({ title, message }) => { + return ( + <EuiFlexGroup direction={"column"}> + <EuiTitle size={'xs'}> + <p>{title}</p> + </EuiTitle> + <EuiText> + <p>{message}</p> + </EuiText> + </EuiFlexGroup> + ); +}; + +export default ToastMessage; diff --git a/src/components/Widget/Widget.js b/src/components/Widget/Widget.js index 1b236d5..b96456c 100644 --- a/src/components/Widget/Widget.js +++ b/src/components/Widget/Widget.js @@ -1,76 +1,77 @@ -import React, { useState } from 'react'; -import { Paper, IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; -import { MoreVert as MoreIcon } from '@material-ui/icons'; +import React, {useState} from 'react'; +import {IconButton, Menu, MenuItem, Paper, Typography} from '@mui/material'; +import {MoreVert as MoreIcon} from '@mui/icons-material'; import classnames from 'classnames'; import useStyles from './styles'; + export default function Widget({ - children, - title, - noBodyPadding, - bodyClass, - disableWidgetMenu, - header, -}) { - const classes = useStyles(); - let [moreButtonRef, setMoreButtonRef] = useState(null); - let [isMoreMenuOpen, setMoreMenuOpen] = useState(false); + children, + title, + noBodyPadding, + bodyClass, + disableWidgetMenu, + header, + }) { + const classes = useStyles(); + let [moreButtonRef, setMoreButtonRef] = useState(null); + let [isMoreMenuOpen, setMoreMenuOpen] = useState(false); - return ( - <div className={classes.widgetWrapper}> - <Paper className={classes.paper} classes={{ root: classes.widgetRoot }}> - <div className={classes.widgetHeader}> - {header ? ( - header - ) : ( - <React.Fragment> - <Typography variant="h5" color="textSecondary"> - {title} - </Typography> - {!disableWidgetMenu && ( - <IconButton - color="primary" - classes={{ root: classes.moreButton }} - aria-owns="widget-menu" - aria-haspopup="true" - onClick={() => setMoreMenuOpen(true)} - ref={setMoreButtonRef} + return ( + <div className={classes.widgetWrapper}> + <Paper className={classes.paper} classes={{root: classes.widgetRoot}}> + <div className={classes.widgetHeader}> + {header ? ( + header + ) : ( + <React.Fragment> + <Typography variant="h5" color="textSecondary"> + {title} + </Typography> + {!disableWidgetMenu && ( + <IconButton + color="primary" + classes={{root: classes.moreButton}} + aria-owns="widget-menu" + aria-haspopup="true" + onClick={() => setMoreMenuOpen(true)} + ref={setMoreButtonRef} + > + <MoreIcon/> + </IconButton> + )} + </React.Fragment> + )} + </div> + <div + className={classnames(classes.widgetBody, { + [classes.noPadding]: noBodyPadding, + [bodyClass]: bodyClass, + })} > - <MoreIcon /> - </IconButton> - )} - </React.Fragment> - )} - </div> - <div - className={classnames(classes.widgetBody, { - [classes.noPadding]: noBodyPadding, - [bodyClass]: bodyClass, - })} - > - {children} + {children} + </div> + </Paper> + <Menu + id="widget-menu" + open={isMoreMenuOpen} + anchorEl={moreButtonRef} + onClose={() => setMoreMenuOpen(false)} + disableAutoFocusItem + > + <MenuItem> + <Typography>Edit</Typography> + </MenuItem> + <MenuItem> + <Typography>Copy</Typography> + </MenuItem> + <MenuItem> + <Typography>Delete</Typography> + </MenuItem> + <MenuItem> + <Typography>Print</Typography> + </MenuItem> + </Menu> </div> - </Paper> - <Menu - id="widget-menu" - open={isMoreMenuOpen} - anchorEl={moreButtonRef} - onClose={() => setMoreMenuOpen(false)} - disableAutoFocusItem - > - <MenuItem> - <Typography>Edit</Typography> - </MenuItem> - <MenuItem> - <Typography>Copy</Typography> - </MenuItem> - <MenuItem> - <Typography>Delete</Typography> - </MenuItem> - <MenuItem> - <Typography>Print</Typography> - </MenuItem> - </Menu> - </div> - ); + ); } diff --git a/src/components/Widget/styles.js b/src/components/Widget/styles.js index a025742..e93c2a4 100644 --- a/src/components/Widget/styles.js +++ b/src/components/Widget/styles.js @@ -1,43 +1,43 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - widgetWrapper: { - display: 'flex', - minHeight: '100%', - }, - widgetHeader: { - padding: theme.spacing(3), - paddingBottom: theme.spacing(1), - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - }, - widgetRoot: { - boxShadow: theme.customShadows.widget, - }, - widgetBody: { - paddingBottom: theme.spacing(3), - paddingRight: theme.spacing(3), - paddingLeft: theme.spacing(3), - }, - noPadding: { - padding: 0, - }, - paper: { - display: 'flex', - flexDirection: 'column', - flexGrow: 1, - overflow: 'hidden', - }, - moreButton: { - margin: -theme.spacing(1), - padding: 0, - width: 40, - height: 40, - color: theme.palette.text.hint, - '&:hover': { - backgroundColor: theme.palette.primary.main, - color: 'rgba(255, 255, 255, 0.35)', + widgetWrapper: { + display: 'flex', + minHeight: '100%', + }, + widgetHeader: { + padding: theme.spacing(3), + paddingBottom: theme.spacing(1), + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + widgetRoot: { + boxShadow: theme.customShadows.widget, + }, + widgetBody: { + paddingBottom: theme.spacing(3), + paddingRight: theme.spacing(3), + paddingLeft: theme.spacing(3), + }, + noPadding: { + padding: 0, + }, + paper: { + display: 'flex', + flexDirection: 'column', + flexGrow: 1, + overflow: 'hidden', + }, + moreButton: { + margin: -theme.spacing(1), + padding: 0, + width: 40, + height: 40, + color: theme.palette.text.hint, + '&:hover': { + backgroundColor: theme.palette.primary.main, + color: 'rgba(255, 255, 255, 0.35)', + }, }, - }, })); diff --git a/src/components/Wrappers/Wrappers.js b/src/components/Wrappers/Wrappers.js index 055af83..cd4db21 100644 --- a/src/components/Wrappers/Wrappers.js +++ b/src/components/Wrappers/Wrappers.js @@ -1,144 +1,139 @@ import React from 'react'; -import { - withStyles, - Badge as BadgeBase, - Typography as TypographyBase, - Button as ButtonBase, -} from '@material-ui/core'; -import { useTheme, makeStyles } from '@material-ui/styles'; +import {Badge as BadgeBase, Button as ButtonBase, Typography as TypographyBase, withStyles,} from '@mui/material'; +import {makeStyles, useTheme} from '@mui/styles'; import classnames from 'classnames'; const useStyles = makeStyles(() => ({ - badge: { - fontWeight: 600, - height: 16, - minWidth: 16, - }, -})); - -function Badge({ children, colorBrightness, color, ...props }) { - const classes = useStyles(); - const theme = useTheme(); - const Styled = createStyled({ badge: { - backgroundColor: getColor(color, theme, colorBrightness), + fontWeight: 600, + height: 16, + minWidth: 16, }, - }); - - return ( - <Styled> - {(styledProps) => ( - <BadgeBase - classes={{ - badge: classnames(classes.badge, styledProps.classes.badge), - }} - {...props} - > - {children} - </BadgeBase> - )} - </Styled> - ); +})); + +function Badge({children, colorBrightness, color, ...props}) { + const classes = useStyles(); + const theme = useTheme(); + const Styled = createStyled({ + badge: { + backgroundColor: getColor(color, theme, colorBrightness), + }, + }); + + return ( + <Styled> + {(styledProps) => ( + <BadgeBase + classes={{ + badge: classnames(classes.badge, styledProps.classes.badge), + }} + {...props} + > + {children} + </BadgeBase> + )} + </Styled> + ); } -function Typography({ children, weight, size, colorBrightness, color, ...props }) { - const theme = useTheme(); - - return ( - <TypographyBase - style={{ - color: getColor(color, theme, colorBrightness), - fontWeight: getFontWeight(weight), - fontSize: getFontSize(size, props.variant, theme), - }} - {...props} - > - {children} - </TypographyBase> - ); +function Typography({children, weight, size, colorBrightness, color, ...props}) { + const theme = useTheme(); + + return ( + <TypographyBase + style={{ + color: getColor(color, theme, colorBrightness), + fontWeight: getFontWeight(weight), + fontSize: getFontSize(size, props.variant, theme), + }} + {...props} + > + {children} + </TypographyBase> + ); } -function Button({ children, color, ...props }) { - const theme = useTheme(); - - const Styled = createStyled({ - button: { - backgroundColor: getColor(color, theme), - boxShadow: theme.customShadows.widget, - color: 'white', - '&:hover': { - backgroundColor: getColor(color, theme, 'light'), - boxShadow: theme.customShadows.widgetWide, - }, - }, - }); - - return ( - <Styled> - {({ classes }) => ( - <ButtonBase classes={{ root: classes.button }} {...props}> - {children} - </ButtonBase> - )} - </Styled> - ); +function Button({children, color, ...props}) { + const theme = useTheme(); + + const Styled = createStyled({ + button: { + backgroundColor: getColor(color, theme), + boxShadow: theme.customShadows.widget, + color: 'white', + '&:hover': { + backgroundColor: getColor(color, theme, 'light'), + boxShadow: theme.customShadows.widgetWide, + }, + }, + }); + + return ( + <Styled> + {({classes}) => ( + <ButtonBase classes={{root: classes.button}} {...props}> + {children} + </ButtonBase> + )} + </Styled> + ); } -export { Badge, Typography, Button }; +export {Badge, Typography, Button}; function getColor(color, theme, brightness = 'main') { - if (color && theme.palette[color] && theme.palette[color][brightness]) { - return theme.palette[color][brightness]; - } + if (color && theme.palette[color] && theme.palette[color][brightness]) { + return theme.palette[color][brightness]; + } } function getFontWeight(style) { - switch (style) { - case 'light': - return 300; - case 'medium': - return 500; - case 'bold': - return 600; - default: - return 400; - } + switch (style) { + case 'light': + return 300; + case 'medium': + return 500; + case 'bold': + return 600; + default: + return 400; + } } function getFontSize(size, variant = '', theme) { - let multiplier; - - switch (size) { - case 'sm': - multiplier = 0.8; - break; - case 'md': - multiplier = 1.5; - break; - case 'xl': - multiplier = 2; - break; - case 'xxl': - multiplier = 3; - break; - default: - multiplier = 1; - break; - } - - const defaultSize = - variant && theme.typography[variant] - ? theme.typography[variant].fontSize - : theme.typography.fontSize + 'px'; - - return `calc(${defaultSize} * ${multiplier})`; + let multiplier; + + switch (size) { + case 'sm': + multiplier = 0.8; + break; + case 'md': + multiplier = 1.5; + break; + case 'xl': + multiplier = 2; + break; + case 'xxl': + multiplier = 3; + break; + default: + multiplier = 1; + break; + } + + const defaultSize = + variant && theme.typography[variant] + ? theme.typography[variant].fontSize + : theme.typography.fontSize + 'px'; + + return `calc(${defaultSize} * ${multiplier})`; } function createStyled(styles, options) { - const Styled = function (props) { - const { children, ...other } = props; - return children(other); - }; + const Styled = function (props) { + const {children, ...other} = props; + return children(other); + }; - return withStyles(styles, options)(Styled); + return withStyles(styles, options)(Styled); } diff --git a/src/index.js b/src/index.js index 3d4a198..a11f9d8 100755 --- a/src/index.js +++ b/src/index.js @@ -1,15 +1,16 @@ import React from 'react'; -import ReactDOM from 'react-dom'; -import { ThemeProvider } from '@material-ui/styles'; -import { CssBaseline } from '@material-ui/core'; +import { CssBaseline } from '@mui/material'; import Themes from './themes'; -import '@elastic/eui/dist/eui_theme_light.css'; import App from './components/App'; import { LayoutProvider } from './context/LayoutContext'; import { AuthProvider } from 'react-oidc-context'; import { userManager } from './services/GatekeeperService'; +import { createRoot } from 'react-dom/client'; +import { EuiProvider } from '@elastic/eui'; +import { ThemeProvider } from '@mui/material/styles'; -ReactDOM.render( +const root = createRoot(document.getElementById('root')); +root.render( <AuthProvider userManager={userManager} onSigninCallback={() => { @@ -23,9 +24,10 @@ ReactDOM.render( <LayoutProvider> <ThemeProvider theme={Themes.default}> <CssBaseline /> - <App /> + <EuiProvider> + <App /> + </EuiProvider> </ThemeProvider> </LayoutProvider> - </AuthProvider>, - document.getElementById('root') + </AuthProvider> ); diff --git a/src/pages/dashboard/Dashboard.js b/src/pages/dashboard/Dashboard.js index 5807df6..dd4e972 100644 --- a/src/pages/dashboard/Dashboard.js +++ b/src/pages/dashboard/Dashboard.js @@ -1,24 +1,24 @@ import React from 'react'; -import { Grid, LinearProgress } from '@material-ui/core'; -import { useTheme } from '@material-ui/styles'; +import { Grid, LinearProgress } from '@mui/material'; +import { useTheme } from '@mui/styles'; import keycloakLogo from '../../images/Keycloak_Logo.svg'; import mongoLogo from '../../images/mongo_logo.svg'; import { - ResponsiveContainer, + Area, AreaChart, - LineChart, + Cell, Line, - Area, - PieChart, + LineChart, Pie, - Cell, + PieChart, + ResponsiveContainer, } from 'recharts'; import { EuiButton, EuiCard, - EuiIcon, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiSpacer, } from '@elastic/eui'; import useStyles from './styles'; @@ -68,7 +68,7 @@ urlMaps.set( : 'http://localhost:9000/#/init/admin' ); -export default function Dashboard() { +const Dashboard = () => { const classes = useStyles(); const theme = useTheme(); const { @@ -80,7 +80,7 @@ export default function Dashboard() { } = process.env; return ( - <> + <EuiFlexGroup direction={'column'}> <EuiFlexGroup gutterSize="l"> <EuiFlexItem> <EuiCard @@ -178,7 +178,6 @@ export default function Dashboard() { /> </EuiFlexItem> </EuiFlexGroup> - <Grid container spacing={4}> <Grid item lg={3} md={4} sm={6} xs={12}> <Widget @@ -191,26 +190,26 @@ export default function Dashboard() { <Typography size="xl" weight="medium"> 12, 678 </Typography> - <LineChart - width={55} - height={30} - data={[ - { value: 10 }, - { value: 15 }, - { value: 10 }, - { value: 17 }, - { value: 18 }, - ]} - margin={{ left: theme.spacing(2) }} - > - <Line - type="natural" - dataKey="value" - stroke={theme.palette.success.main} - strokeWidth={2} - dot={false} - /> - </LineChart> + <LineChart + width={55} + height={30} + data={[ + { value: 10 }, + { value: 15 }, + { value: 10 }, + { value: 17 }, + { value: 18 }, + ]} + margin={{ left: theme.spacing(2) }} + > + <Line + type="natural" + dataKey="value" + stroke={theme.palette.success.main} + strokeWidth={2} + dot={false} + /> + </LineChart> </div> <Grid container @@ -384,7 +383,7 @@ export default function Dashboard() { <Widget title="Number of users" upperTitle className={classes.card}> <Grid container spacing={2}> <Grid item xs={6}> - <ResponsiveContainer width="100%" height={144}> + <ResponsiveContainer width="99%" height={144}> <PieChart margin={{ left: theme.spacing(2) }}> <Pie data={PieChartData} @@ -403,24 +402,28 @@ export default function Dashboard() { </ResponsiveContainer> </Grid> <Grid item xs={6}> - <div className={classes.pieChartLegendWrapper}> - {PieChartData.map(({ name, value, color }, index) => ( - <div key={color} className={classes.legendItemContainer}> - <Dot color={color} /> - <Typography style={{ whiteSpace: 'nowrap' }}> - {name} - </Typography> - <Typography color="text" colorBrightness="secondary"> - {value} - </Typography> - </div> - ))} - </div> + <ResponsiveContainer width="100%" height={144}> + <div className={classes.pieChartLegendWrapper}> + {PieChartData.map(({ name, value, color }, index) => ( + <div key={index} className={classes.legendItemContainer}> + <Dot color={color} /> + <Typography style={{ whiteSpace: 'nowrap' }}> + {name} + </Typography> + <Typography color="text" colorBrightness="secondary"> + {value} + </Typography> + </div> + ))} + </div> + </ResponsiveContainer> </Grid> </Grid> </Widget> </Grid> </Grid> - </> + </EuiFlexGroup> ); -} +}; + +export default Dashboard; diff --git a/src/pages/dashboard/components/BigStat/BigStat.js b/src/pages/dashboard/components/BigStat/BigStat.js index 824e1bf..60ee310 100644 --- a/src/pages/dashboard/components/BigStat/BigStat.js +++ b/src/pages/dashboard/components/BigStat/BigStat.js @@ -1,100 +1,100 @@ -import React, { useState } from 'react'; -import { Grid, Select, MenuItem, Input } from '@material-ui/core'; -import { ArrowForward as ArrowForwardIcon } from '@material-ui/icons'; -import { useTheme } from '@material-ui/styles'; -import { BarChart, Bar } from 'recharts'; +import React, {useState} from 'react'; +import {Grid, Input, MenuItem, Select} from '@mui/material'; +import {ArrowForward as ArrowForwardIcon} from '@mui/icons-material'; +import {useTheme} from '@mui/styles'; +import {Bar, BarChart} from 'recharts'; import classnames from 'classnames'; import useStyles from './styles'; import Widget from '../../../../components/Widget'; -import { Typography } from '../../../../components/Wrappers'; -import { getRandomData } from '../../../../utils'; +import {Typography} from '../../../../components/Wrappers'; +import {getRandomData} from '../../../../utils'; export default function BigStat(props) { - let { product, total, color, registrations, bounce } = props; - const classes = useStyles(); - const theme = useTheme(); - let [value, setValue] = useState('daily'); + let {product, total, color, registrations, bounce} = props; + const classes = useStyles(); + const theme = useTheme(); + let [value, setValue] = useState('daily'); - return ( - <Widget - header={ - <div className={classes.title}> - <Typography variant="h5">{product}</Typography> + return ( + <Widget + header={ + <div className={classes.title}> + <Typography variant="h5">{product}</Typography> - <Select - value={value} - onChange={(e) => setValue(e.target.value)} - input={<Input disableUnderline classes={{ input: classes.selectInput }} />} - className={classes.select} - > - <MenuItem value="daily">Daily</MenuItem> - <MenuItem value="weekly">Weekly</MenuItem> - <MenuItem value="monthly">Monthly</MenuItem> - </Select> - </div> - } - upperTitle - > - <div className={classes.totalValueContainer}> - <div className={classes.totalValue}> - <Typography size="xxl" color="text" colorBrightness="secondary"> - {total[value]} - </Typography> - <Typography color={total.percent.profit ? 'success' : 'secondary'}> - {total.percent.profit ? '+' : '-'} - {total.percent.value}% - </Typography> - </div> - <BarChart width={150} height={70} data={getRandomData(7)}> - <Bar - dataKey="value" - fill={theme.palette[color].main} - radius={10} - barSize={10} - /> - </BarChart> - </div> - <div className={classes.bottomStatsContainer}> - <div className={classnames(classes.statCell, classes.borderRight)}> - <Grid container alignItems="center"> - <Typography variant="h6">{registrations[value].value}</Typography> - <ArrowForwardIcon - className={classnames(classes.profitArrow, { - [!registrations[value].profit]: classes.profitArrowDanger, - })} - /> - </Grid> - <Typography size="sm" color="text" colorBrightness="secondary"> - Registrations - </Typography> - </div> - <div className={classes.statCell}> - <Grid container alignItems="center"> - <Typography variant="h6">{bounce[value].value}%</Typography> - <ArrowForwardIcon - className={classnames(classes.profitArrow, { - [!registrations[value].profit]: classes.profitArrowDanger, - })} - /> - </Grid> - <Typography size="sm" color="text" colorBrightness="secondary"> - Bounce Rate - </Typography> - </div> - <div className={classnames(classes.statCell, classes.borderRight)}> - <Grid container alignItems="center"> - <Typography variant="h6">{registrations[value].value * 10}</Typography> - <ArrowForwardIcon - className={classnames(classes.profitArrow, { - [classes.profitArrowDanger]: !registrations[value].profit, - })} - /> - </Grid> - <Typography size="sm" color="text" colorBrightness="secondary"> - Views - </Typography> - </div> - </div> - </Widget> - ); + <Select + value={value} + onChange={(e) => setValue(e.target.value)} + input={<Input disableUnderline classes={{input: classes.selectInput}}/>} + className={classes.select} + > + <MenuItem value="daily">Daily</MenuItem> + <MenuItem value="weekly">Weekly</MenuItem> + <MenuItem value="monthly">Monthly</MenuItem> + </Select> + </div> + } + upperTitle + > + <div className={classes.totalValueContainer}> + <div className={classes.totalValue}> + <Typography size="xxl" color="text" colorBrightness="secondary"> + {total[value]} + </Typography> + <Typography color={total.percent.profit ? 'success' : 'secondary'}> + {total.percent.profit ? '+' : '-'} + {total.percent.value}% + </Typography> + </div> + <BarChart width={150} height={70} data={getRandomData(7)}> + <Bar + dataKey="value" + fill={theme.palette[color].main} + radius={10} + barSize={10} + /> + </BarChart> + </div> + <div className={classes.bottomStatsContainer}> + <div className={classnames(classes.statCell, classes.borderRight)}> + <Grid container alignItems="center"> + <Typography variant="h6">{registrations[value].value}</Typography> + <ArrowForwardIcon + className={classnames(classes.profitArrow, { + [!registrations[value].profit]: classes.profitArrowDanger, + })} + /> + </Grid> + <Typography size="sm" color="text" colorBrightness="secondary"> + Registrations + </Typography> + </div> + <div className={classes.statCell}> + <Grid container alignItems="center"> + <Typography variant="h6">{bounce[value].value}%</Typography> + <ArrowForwardIcon + className={classnames(classes.profitArrow, { + [!registrations[value].profit]: classes.profitArrowDanger, + })} + /> + </Grid> + <Typography size="sm" color="text" colorBrightness="secondary"> + Bounce Rate + </Typography> + </div> + <div className={classnames(classes.statCell, classes.borderRight)}> + <Grid container alignItems="center"> + <Typography variant="h6">{registrations[value].value * 10}</Typography> + <ArrowForwardIcon + className={classnames(classes.profitArrow, { + [classes.profitArrowDanger]: !registrations[value].profit, + })} + /> + </Grid> + <Typography size="sm" color="text" colorBrightness="secondary"> + Views + </Typography> + </div> + </div> + </Widget> + ); } diff --git a/src/pages/dashboard/components/BigStat/styles.js b/src/pages/dashboard/components/BigStat/styles.js index 535ac67..428900a 100644 --- a/src/pages/dashboard/components/BigStat/styles.js +++ b/src/pages/dashboard/components/BigStat/styles.js @@ -1,45 +1,45 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - title: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - width: '100%', - marginBottom: theme.spacing(1), - }, - bottomStatsContainer: { - display: 'flex', - justifyContent: 'space-between', - margin: theme.spacing(1) * -2, - marginTop: theme.spacing(1), - }, - statCell: { - padding: theme.spacing(2), - }, - totalValueContainer: { - display: 'flex', - alignItems: 'flex-end', - justifyContent: 'space-between', - }, - totalValue: { - display: 'flex', - alignItems: 'baseline', - }, - profitArrow: { - transform: 'rotate(-45deg)', - fill: theme.palette.success.main, - }, - profitArrowDanger: { - transform: 'rotate(45deg)', - fill: theme.palette.secondary.main, - }, - selectInput: { - padding: 10, - paddingRight: 25, - '&:focus': { - backgroundColor: 'white', - }, - }, + title: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + marginBottom: theme.spacing(1), + }, + bottomStatsContainer: { + display: 'flex', + justifyContent: 'space-between', + margin: theme.spacing(1) * -2, + marginTop: theme.spacing(1), + }, + statCell: { + padding: theme.spacing(2), + }, + totalValueContainer: { + display: 'flex', + alignItems: 'flex-end', + justifyContent: 'space-between', + }, + totalValue: { + display: 'flex', + alignItems: 'baseline', + }, + profitArrow: { + transform: 'rotate(-45deg)', + fill: theme.palette.success.main, + }, + profitArrowDanger: { + transform: 'rotate(45deg)', + fill: theme.palette.secondary.main, + }, + selectInput: { + padding: 10, + paddingRight: 25, + '&:focus': { + backgroundColor: 'white', + }, + }, })); diff --git a/src/pages/dashboard/components/Table/Table.js b/src/pages/dashboard/components/Table/Table.js index b724153..9b2015f 100644 --- a/src/pages/dashboard/components/Table/Table.js +++ b/src/pages/dashboard/components/Table/Table.js @@ -1,48 +1,48 @@ import React from 'react'; -import { Table, TableRow, TableHead, TableBody, TableCell } from '@material-ui/core'; -import { Button } from '../../../../components/Wrappers'; +import {Table, TableBody, TableCell, TableHead, TableRow} from '@mui/material'; +import {Button} from '../../../../components/Wrappers'; const states = { - sent: 'success', - pending: 'warning', - declined: 'secondary', + sent: 'success', + pending: 'warning', + declined: 'secondary', }; -export default function TableComponent({ data }) { - const keys = Object.keys(data[0]).map((i) => i.toUpperCase()); - keys.shift(); // delete "id" key +export default function TableComponent({data}) { + const keys = Object.keys(data[0]).map((i) => i.toUpperCase()); + keys.shift(); // delete "id" key - return ( - <Table className="mb-0"> - <TableHead> - <TableRow> - {keys.map((key) => ( - <TableCell key={key}>{key}</TableCell> - ))} - </TableRow> - </TableHead> - <TableBody> - {data.map(({ id, name, email, product, price, date, city, status }) => ( - <TableRow key={id}> - <TableCell className="pl-3 fw-normal">{name}</TableCell> - <TableCell>{email}</TableCell> - <TableCell>{product}</TableCell> - <TableCell>{price}</TableCell> - <TableCell>{date}</TableCell> - <TableCell>{city}</TableCell> - <TableCell> - <Button - color={states[status.toLowerCase()]} - size="small" - className="px-2" - variant="contained" - > - {status} - </Button> - </TableCell> - </TableRow> - ))} - </TableBody> - </Table> - ); + return ( + <Table className="mb-0"> + <TableHead> + <TableRow> + {keys.map((key) => ( + <TableCell key={key}>{key}</TableCell> + ))} + </TableRow> + </TableHead> + <TableBody> + {data.map(({id, name, email, product, price, date, city, status}) => ( + <TableRow key={id}> + <TableCell className="pl-3 fw-normal">{name}</TableCell> + <TableCell>{email}</TableCell> + <TableCell>{product}</TableCell> + <TableCell>{price}</TableCell> + <TableCell>{date}</TableCell> + <TableCell>{city}</TableCell> + <TableCell> + <Button + color={states[status.toLowerCase()]} + size="small" + className="px-2" + variant="contained" + > + {status} + </Button> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); } diff --git a/src/pages/dashboard/styles.js b/src/pages/dashboard/styles.js index 11e091c..dcd0bc6 100644 --- a/src/pages/dashboard/styles.js +++ b/src/pages/dashboard/styles.js @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@mui/styles'; export default makeStyles((theme) => ({ card: { @@ -67,7 +67,7 @@ export default makeStyles((theme) => ({ maxWidth: '100%', }, serverOverviewElementText: { - minWidth: 145, + minWidth: '145px', paddingRight: theme.spacing(2), }, serverOverviewElementChartWrapper: { diff --git a/src/pages/error/403.js b/src/pages/error/403.js index 1cace73..bdae4c9 100644 --- a/src/pages/error/403.js +++ b/src/pages/error/403.js @@ -1,25 +1,23 @@ import React from 'react'; -import { Grid, Paper, Typography, Button } from '@material-ui/core'; +import { Button, Grid, Paper, Typography } from '@mui/material'; import classnames from 'classnames'; import useStyles from './styles'; import logo from './logo.png'; -export default function Error403() { +const Error403 = () => { const classes = useStyles(); return ( <Grid container className={classes.container}> <div className={classes.logotype}> <img className={classes.logotypeIcon} src={logo} alt="logo" /> - <Typography variant="h3" color="white" className={classes.logotypeText}> - In-Sylva Project - </Typography> </div> <Paper classes={{ root: classes.paperRoot }}> <Typography variant="h1" color="primary" className={classnames(classes.textRow, classes.errorCode)} + gutterBottom={false} > 403 </Typography> @@ -27,7 +25,9 @@ export default function Error403() { Forbidden </Typography> <Typography variant="h5" color="primary" className={classes.textRow}> - Oops. Looks like you don't have the right permissions to access this page + <p> + Oops. Looks like you don't have the right permissions to access this page + </p> </Typography> <Button href="#/home" @@ -41,4 +41,6 @@ export default function Error403() { </Paper> </Grid> ); -} +}; + +export default Error403; diff --git a/src/pages/error/404.js b/src/pages/error/404.js index ccf5ebe..584e731 100644 --- a/src/pages/error/404.js +++ b/src/pages/error/404.js @@ -1,18 +1,16 @@ import React from 'react'; -import { Grid, Paper, Typography, Button } from '@material-ui/core'; +import { Button, Grid, Paper, Typography } from '@mui/material'; import classnames from 'classnames'; import useStyles from './styles'; import logo from './logo.png'; -export default function Error404() { +const Error404 = () => { const classes = useStyles(); + return ( <Grid container className={classes.container}> <div className={classes.logotype}> <img className={classes.logotypeIcon} src={logo} alt="logo" /> - <Typography variant="h3" color="white" className={classes.logotypeText}> - In-Sylva Project - </Typography> </div> <Paper classes={{ root: classes.paperRoot }}> <Typography @@ -26,7 +24,7 @@ export default function Error404() { Not Found </Typography> <Typography variant="h5" color="primary" className={classes.textRow}> - Oops. Looks like the page you're looking for no longer exists + Oops. Looks like the page you're looking for no longer exists </Typography> <Button href="#/home" @@ -40,4 +38,6 @@ export default function Error404() { </Paper> </Grid> ); -} +}; + +export default Error404; diff --git a/src/pages/error/styles.js b/src/pages/error/styles.js index 7546f5d..b945236 100644 --- a/src/pages/error/styles.js +++ b/src/pages/error/styles.js @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/styles'; +import { makeStyles } from '@mui/styles'; export default makeStyles((theme) => ({ container: { @@ -16,37 +16,29 @@ export default makeStyles((theme) => ({ logotype: { display: 'flex', alignItems: 'center', - marginBottom: theme.spacing(12), + marginBottom: theme.spacing(8), [theme.breakpoints.down('sm')]: { display: 'none', }, }, - logotypeText: { - fontWeight: 500, - color: 'white', - marginLeft: theme.spacing(2), - }, logotypeIcon: { - width: 70, - marginRight: theme.spacing(2), + width: 200, }, paperRoot: { boxShadow: theme.customShadows.widgetDark, display: 'flex', flexDirection: 'column', alignItems: 'center', - paddingTop: theme.spacing(8), - paddingBottom: theme.spacing(8), - paddingLeft: theme.spacing(6), - paddingRight: theme.spacing(6), + padding: theme.spacing(4), maxWidth: 404, + maxHeight: '50%' }, textRow: { - marginBottom: theme.spacing(10), + marginBottom: theme.spacing(4), textAlign: 'center', }, errorCode: { - fontSize: 148, + fontSize: 130, fontWeight: 600, }, safetyText: { diff --git a/src/pages/fields/Fields.js b/src/pages/fields/Fields.js index d6d52d0..bb1ec01 100644 --- a/src/pages/fields/Fields.js +++ b/src/pages/fields/Fields.js @@ -1,26 +1,20 @@ -import React, { useState, useCallback, useEffect, memo } from 'react'; +import React, { memo, useCallback, useEffect, useState } from 'react'; import { + EuiButton, + EuiFilePicker, EuiForm, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, - EuiTabbedContent, EuiFormRow, - EuiText, - EuiFilePicker, - EuiButton, EuiInMemoryTable, + EuiPageHeader, + EuiPageSection, EuiSpacer, + EuiTabbedContent, + EuiText, + EuiTitle, } from '@elastic/eui'; -import { ShowAlert } from '../../components/Common'; import Papa from 'papaparse'; -import { - getPublicFields, - deleteAllStdFields, - createStdField, -} from '../../services/GatekeeperService'; +import { createStdField, deleteAllStdFields, getPublicFields } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; const renderFiles = (files) => { if (files && files.length > 0) { @@ -38,7 +32,12 @@ const renderFiles = (files) => { } }; -const NewFieldsForm = memo(({ files, onFilePickerChange, onSaveField }) => { +const NewFieldsForm = memo(function NewFieldsForm({ + files, + onFilePickerChange, + onSaveField, + isLoading + }) { return ( <> <EuiSpacer /> @@ -61,7 +60,7 @@ const NewFieldsForm = memo(({ files, onFilePickerChange, onSaveField }) => { </EuiText> </EuiFormRow> <EuiFormRow> - <EuiButton fill onClick={onSaveField} disabled={!files}> + <EuiButton fill onClick={onSaveField} disabled={!files} isLoading={isLoading}> Load </EuiButton> </EuiFormRow> @@ -70,7 +69,7 @@ const NewFieldsForm = memo(({ files, onFilePickerChange, onSaveField }) => { ); }); -const StdFields = memo(({ stdFields, stdFieldColumns }) => { +const StdFields = memo(function StdFields({ stdFields, stdFieldColumns }) { return ( <> <EuiSpacer /> @@ -91,12 +90,10 @@ const StdFields = memo(({ stdFields, stdFieldColumns }) => { const Fields = () => { const [selectedTabNumber, setSelectedTabNumber] = useState(0); - const [open, setOpen] = useState(false); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); const [files, setFiles] = useState(); const [loadedFields, setLoadedFields] = useState([]); const [stdFields, setStdFields] = useState([]); + const [isLoading, setIsLoading] = useState(false); const loadStdFields = useCallback(async () => { const fields = await getPublicFields(); @@ -151,6 +148,7 @@ const Fields = () => { }; const onSaveField = async () => { + setIsLoading(true); if (loadedFields) { await deleteAllStdFields(); try { @@ -184,17 +182,17 @@ const Fields = () => { obligation_or_condition, values, list_url, - default_display_fields + default_display_fields, ); } // Reload fields after processing await loadStdFields(); + setIsLoading(false); } catch (error) { console.error('Unexpected error during field saving:', error); - setOpen(true); - setAlertMessage('An unexpected error occurred.'); - setSeverity('error'); + toast.error('An unexpected error occurred.'); } + toast.success('Fields created.'); } }; @@ -264,6 +262,7 @@ const Fields = () => { files={files} onFilePickerChange={onFilePickerChange} onSaveField={onSaveField} + isLoading={isLoading} /> ), }, @@ -274,38 +273,25 @@ const Fields = () => { }, ]; - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; - return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Field management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiForm> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - loadStdFields(); - }} - /> - </EuiForm> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Field management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <EuiForm> + <EuiTabbedContent + tabs={tabContents} + selectedTab={tabContents[selectedTabNumber]} + onTabClick={(tab) => { + setSelectedTabNumber(tabContents.indexOf(tab)); + loadStdFields(); + }} + /> + </EuiForm> + </EuiPageSection> ); }; diff --git a/src/pages/fields/styles.js b/src/pages/fields/styles.js index 9e578a3..2b26ab1 100644 --- a/src/pages/fields/styles.js +++ b/src/pages/fields/styles.js @@ -1,96 +1,96 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - iconsContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + iconsContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, - body: { - backgroundColor: '#f2ede8', - margin: 0, - padding: 0, - fontSize: '16px', - fontFamily: 'sans-serif', - display: 'flex', - height: '100vh', - }, - FileUploadForm: { - backgroundColor: '#fff', - padding: '2rem', - display: 'grid', - gridGap: '1.6rem', - }, - ActionBar: { - textAlign: 'right', - }, + body: { + backgroundColor: '#f2ede8', + margin: 0, + padding: 0, + fontSize: '16px', + fontFamily: 'sans-serif', + display: 'flex', + height: '100vh', + }, + FileUploadForm: { + backgroundColor: '#fff', + padding: '2rem', + display: 'grid', + gridGap: '1.6rem', + }, + ActionBar: { + textAlign: 'right', + }, - ActionBarButton: { - padding: '.4rem', - }, + ActionBarButton: { + padding: '.4rem', + }, - MessageBox: { - padding: '1.6rem', - backgroundColor: '#faf4e9', - borderTop: '3px solid #ffa600', - }, - input: { - display: 'none', - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, + MessageBox: { + padding: '1.6rem', + backgroundColor: '#faf4e9', + borderTop: '3px solid #ffa600', + }, + input: { + display: 'none', + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, - appBar: { - position: 'relative', - }, - layout: { - width: 'auto', - marginLeft: theme.spacing(2), - marginRight: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { - width: 600, - marginLeft: 'auto', - marginRight: 'auto', - }, - }, - paper: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - padding: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { - marginTop: theme.spacing(6), - marginBottom: theme.spacing(6), - padding: theme.spacing(3), - }, - }, - stepper: { - padding: theme.spacing(3, 0, 5), - }, - buttons: { - display: 'flex', - justifyContent: 'flex-end', - }, - button: { - marginTop: theme.spacing(3), - marginLeft: theme.spacing(1), - }, + appBar: { + position: 'relative', + }, + layout: { + width: 'auto', + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { + width: 600, + marginLeft: 'auto', + marginRight: 'auto', + }, + }, + paper: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + padding: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { + marginTop: theme.spacing(6), + marginBottom: theme.spacing(6), + padding: theme.spacing(3), + }, + }, + stepper: { + padding: theme.spacing(3, 0, 5), + }, + buttons: { + display: 'flex', + justifyContent: 'flex-end', + }, + button: { + marginTop: theme.spacing(3), + marginLeft: theme.spacing(1), + }, })); diff --git a/src/pages/groups/Groups.js b/src/pages/groups/Groups.js index 8e4441d..92bc713 100644 --- a/src/pages/groups/Groups.js +++ b/src/pages/groups/Groups.js @@ -1,157 +1,149 @@ -import React, { useState, Fragment, useEffect, memo, useCallback } from 'react'; -import { ShowAlert } from '../../components/Common'; +import React, { Fragment, memo, useCallback, useEffect, useState } from 'react'; import { - EuiForm, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, - EuiTabbedContent, - EuiFormRow, + EuiBasicTable, + EuiButton, + EuiComboBox, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiSpacer, - EuiButton, - EuiBasicTable, + EuiForm, + EuiFormRow, + EuiPageHeader, + EuiPageSection, EuiSelectable, - EuiComboBox, + EuiSpacer, + EuiTabbedContent, + EuiTitle, } from '@elastic/eui'; import { + addUserToGroup, createGroup, + deleteGroup, getGroups, getUsers, - deleteGroup, - addUserToGroup, removeUserFromGroup, } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; +import ToastMessage from "../../components/ToastMessage/ToastMessage"; -const NewGroupForm = memo( - ({ - groupNameValue, - setGroupNameValue, - groupDescriptionValue, - setGroupDescriptionValue, - onSaveGroup, - groups, - groupColumns, - }) => { - return ( - <> - <br /> - <EuiForm component="form"> - <EuiFormRow label="Name"> - <EuiFieldText - value={groupNameValue} - onChange={(e) => setGroupNameValue(e.target.value)} - /> - </EuiFormRow> - <EuiFormRow label="Description"> - <EuiFieldText - id="descriptionValue" - value={groupDescriptionValue} - onChange={(e) => setGroupDescriptionValue(e.target.value)} - /> - </EuiFormRow> - <EuiSpacer /> - { - <EuiButton fill onClick={onSaveGroup}> - Save - </EuiButton> - } - <EuiSpacer /> - <EuiFormRow fullWidth label=""> - <EuiBasicTable items={groups} columns={groupColumns} /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); - -const GroupAssignment = memo( - ({ groups, setGroups, users, setUsers, onGroupAssignment }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Groups" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={groups} - onChange={(newOptions) => setGroups(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Users" fullWidth> - <EuiSelectable - searchable - options={users} - onChange={(newOptions) => setUsers(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - { - <EuiButton type="submit" onClick={onGroupAssignment} fill> - Save - </EuiButton> - } - </EuiForm> - </> - ); - } -); +const NewGroupForm = memo(function NewGroupForm({ + groupNameValue, + setGroupNameValue, + groupDescriptionValue, + setGroupDescriptionValue, + onSaveGroup, + groups, + groupColumns, +}) { + return ( + <EuiForm component="form"> + <EuiFormRow label="Name"> + <EuiFieldText + value={groupNameValue} + onChange={(e) => setGroupNameValue(e.target.value)} + /> + </EuiFormRow> + <EuiFormRow label="Description"> + <EuiFieldText + id="descriptionValue" + value={groupDescriptionValue} + onChange={(e) => setGroupDescriptionValue(e.target.value)} + /> + </EuiFormRow> + <EuiSpacer /> + { + <EuiButton fill onClick={onSaveGroup}> + Save + </EuiButton> + } + <EuiSpacer /> + <EuiFormRow fullWidth label=""> + <EuiBasicTable items={groups} columns={groupColumns} /> + </EuiFormRow> + </EuiForm> + ); +}); -const AssignedGroups = memo( - ({ - groups, - selectedGroup, - onSelectedGroup, - groupedUsers, - assignedGroupedUserColumns, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Select specific group"> - <EuiComboBox - placeholder="Select a group" +const GroupAssignment = memo(function GroupAssignment({ + groups, + setGroups, + users, + setUsers, + onGroupAssignment, +}) { + return ( + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Groups" fullWidth> + <EuiSelectable + searchable singleSelection={true} options={groups} - selectedOptions={selectedGroup} - onChange={(e) => { - onSelectedGroup(e); - }} - /> + onChange={(newOptions) => setGroups(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> </EuiFormRow> - <EuiFormRow label="Assigned policies" fullWidth> - <EuiBasicTable items={groupedUsers} columns={assignedGroupedUserColumns} /> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Users" fullWidth> + <EuiSelectable + searchable + options={users} + onChange={(newOptions) => setUsers(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> </EuiFormRow> - </EuiForm> - </> - ); - } -); + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + { + <EuiButton type="submit" onClick={onGroupAssignment} fill> + Save + </EuiButton> + } + </EuiForm> + ); +}); + +const AssignedGroups = memo(function AssignedGroups({ + groups, + selectedGroup, + onSelectedGroup, + groupedUsers, + assignedGroupedUserColumns, +}) { + return ( + <EuiForm component="form"> + <EuiFormRow label="Select specific group"> + <EuiComboBox + placeholder="Select a group" + singleSelection={true} + options={groups} + selectedOptions={selectedGroup} + onChange={(e) => { + onSelectedGroup(e); + }} + /> + </EuiFormRow> + <EuiFormRow label="Assigned policies" fullWidth> + <EuiBasicTable items={groupedUsers} columns={assignedGroupedUserColumns} /> + </EuiFormRow> + </EuiForm> + ); +}); const Groups = () => { const [selectedTabNumber, setSelectedTabNumber] = useState(0); @@ -159,10 +151,6 @@ const Groups = () => { const [groupDescriptionValue, setGroupDescriptionValue] = useState(''); const [groups, setGroups] = useState([]); - const [open, setOpen] = useState(false); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); - const [users, setUsers] = useState([]); const [tblGroups, setTblGroups] = useState([]); const [selectedGroup, setSelectedGroup] = useState([]); @@ -209,14 +197,11 @@ const Groups = () => { if (group) { const result = await deleteGroup(group.id); if (result) { - setAlertMessage('Group has been deleted.'); - setSeverity('success'); + toast.success('Group has been deleted.'); setRefresh(true); } else { - setAlertMessage(`Error: ${result.error}`); - setSeverity('error'); + toast.error(`Error: ${result.error}`); } - setOpen(true); } }; @@ -224,14 +209,11 @@ const Groups = () => { if (groupNameValue && groupDescriptionValue) { const result = await createGroup(groupNameValue, groupDescriptionValue); if (result?.id) { - setAlertMessage('Group has been created.'); - setSeverity('success'); + toast.success('Group has been created.'); setRefresh(true); } else { - setAlertMessage(`Error: ${result.error}`); - setSeverity('error'); + toast.error(<ToastMessage title={'Group was not created'} message={result.message}/>); } - setOpen(true); } }; @@ -247,7 +229,13 @@ const Groups = () => { for (const user of checkedUsers) { for (const group of checkedGroups) { - const response = await addUserToGroup(user.sub, group.value); + const result = await addUserToGroup(user.sub, group.value); + if (result?.error) { + toast.error(<ToastMessage title={'User could not be assigned to group'} message={result.message}/>); + setRefresh(true); + } else { + toast.success('User has been assigned to group.'); + } } setRefresh(true); } @@ -324,6 +312,7 @@ const Groups = () => { name: 'New group', content: ( <> + <EuiSpacer /> <NewGroupForm groupNameValue={groupNameValue} setGroupNameValue={setGroupNameValue} @@ -341,6 +330,7 @@ const Groups = () => { name: 'Group assignment', content: ( <> + <EuiSpacer /> <GroupAssignment groups={tblGroups} setGroups={setTblGroups} @@ -356,6 +346,7 @@ const Groups = () => { name: 'Assigned groups', content: ( <> + <EuiSpacer /> <AssignedGroups groups={tblGroups} selectedGroup={selectedGroup} @@ -368,39 +359,22 @@ const Groups = () => { }, ]; - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; - return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Groups management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiForm> - <EuiForm> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - }} - /> - </EuiForm> - </EuiForm> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Groups management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <EuiTabbedContent + tabs={tabContents} + selectedTab={tabContents[selectedTabNumber]} + onTabClick={(tab) => { + setSelectedTabNumber(tabContents.indexOf(tab)); + }} + /> + </EuiPageSection> ); }; diff --git a/src/pages/groups/styles.js b/src/pages/groups/styles.js index 4842f10..98e4e92 100644 --- a/src/pages/groups/styles.js +++ b/src/pages/groups/styles.js @@ -1,44 +1,44 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - roleContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, - formControl: { - margin: theme.spacing(1), - minWidth: 120, - maxWidth: 300, - }, - chips: { - display: 'flex', - flexWrap: 'wrap', - }, - chip: { - margin: 2, - }, - noLabel: { - marginTop: theme.spacing(3), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + roleContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + maxWidth: 300, + }, + chips: { + display: 'flex', + flexWrap: 'wrap', + }, + chip: { + margin: 2, + }, + noLabel: { + marginTop: theme.spacing(3), + }, })); diff --git a/src/pages/home/Home.js b/src/pages/home/Home.js index 76e97bc..8d4729b 100644 --- a/src/pages/home/Home.js +++ b/src/pages/home/Home.js @@ -1,48 +1,34 @@ import React from 'react'; import { - EuiButton, - EuiIcon, EuiFlexGroup, EuiFlexItem, + EuiIcon, + EuiPageSection, EuiText, - EuiSpacer, } from '@elastic/eui'; import logo from '../../images/logo.png'; -import useStyles from './styles'; - -export default function Home() { - const classes = useStyles(); - const { REACT_APP_VALIDATOR_URL } = process.env; +const Home = () => { return ( - <EuiFlexGroup - direction="column" - justifyContent="center" - alignItems="center" - style={{ minHeight: 'calc(90vh)', textAlign: 'center' }} - > - {/* Logo at the top */} - <EuiFlexItem grow={false}> - <EuiIcon type={logo} size="original" /> - </EuiFlexItem> - - {/* Main content */} - <EuiFlexItem> - <div className={classes.heroContainer}> - <EuiText> - <h1>Welcome to the Portal</h1> - <p> - This is the central hub for managing users, groups, sources, policies, - fields, and relationships between them. - </p> - </EuiText> - <EuiSpacer size="l" /> - <EuiButton color="primary" size="m" href={`${REACT_APP_VALIDATOR_URL}`} target="_blank"> - <EuiIcon type="checkInCircleFilled" size="l" /> - Validate your JSON sources - </EuiButton> - </div> - </EuiFlexItem> - </EuiFlexGroup> + <EuiPageSection color={'plain'}> + <EuiFlexGroup direction="column" justifyContent="center" alignItems="center"> + {/* Logo at the top */} + <EuiFlexItem grow={false}> + <EuiIcon type={logo} size="original" /> + </EuiFlexItem> + {/* Main content */} + <EuiFlexItem> + <EuiText textAlign={"center"}> + <h1>Welcome to the Portal</h1> + <p> + This backoffice allows you to manage users, groups, sources, policies, + fields, and relationships between them. + </p> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPageSection> ); -} +}; + +export default Home; diff --git a/src/pages/home/styles.js b/src/pages/home/styles.js deleted file mode 100644 index 05e123e..0000000 --- a/src/pages/home/styles.js +++ /dev/null @@ -1,25 +0,0 @@ -import { makeStyles } from '@material-ui/styles'; - -export default makeStyles((theme) => ({ - root: { - display: 'flex', - maxWidth: '100vw', - overflowX: 'hidden', - }, - content: { - flexGrow: 1, - padding: theme.spacing(3), - width: `calc(100vw - 240px)`, - minHeight: '100vh', - }, - contentShift: { - width: `calc(100vw - ${240 + theme.spacing(6)}px)`, - transition: theme.transitions.create(['width', 'margin'], { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.enteringScreen, - }), - }, - fakeToolbar: { - ...theme.mixins.toolbar, - }, -})); diff --git a/src/pages/policies/AssignedPolicies.js b/src/pages/policies/AssignedPolicies.js index 53ca15c..0cf5ebe 100644 --- a/src/pages/policies/AssignedPolicies.js +++ b/src/pages/policies/AssignedPolicies.js @@ -1,131 +1,129 @@ import React, { memo } from 'react'; import { EuiBasicTable, EuiComboBox, EuiForm, EuiFormRow } from '@elastic/eui'; -const AssignedPolicies = memo( - ({ - policies, - selectedPolicy, - onSelectedPolicy, - assignedPolicySources, - assignedPolicyFields, - assignedPolicyGroups, - onDeletePolicySource, - onDeletePolicyField, - onDeletePolicyGroup, - }) => { - const assignedPolicyColumns = [ - { - field: 'field_name', - name: 'Field name', - sortable: true, +const AssignedPolicies = memo(function AssignedPolicies({ + policies, + selectedPolicy, + onSelectedPolicy, + assignedPolicySources, + assignedPolicyFields, + assignedPolicyGroups, + onDeletePolicySource, + onDeletePolicyField, + onDeletePolicyGroup, +}) { + const assignedPolicyColumns = [ + { + field: 'field_name', + name: 'Field name', + sortable: true, + }, + { + field: 'definition_and_comment', + name: 'Definition and comment', + truncateText: true, + mobileOptions: { + show: false, }, - { - field: 'definition_and_comment', - name: 'Definition and comment', - truncateText: true, - mobileOptions: { - show: false, - }, - }, - { - field: 'field_type', - name: 'Field type', - }, - { - name: 'Actions', - actions: [ - { - name: 'Delete', - description: 'Delete this policies-field', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: async (e) => { - await onDeletePolicyField(e); - }, + }, + { + field: 'field_type', + name: 'Field type', + }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policies-field', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: async (e) => { + await onDeletePolicyField(e); }, - ], - }, - ]; + }, + ], + }, + ]; - const assignedPolicyGroupColumns = [ - { field: 'group_name', name: 'Group name' }, - { field: 'group_description', name: 'Group description' }, - { - name: 'Actions', - actions: [ - { - name: 'Delete', - description: 'Delete this policies-group', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: async (e) => { - await onDeletePolicyGroup(e); - }, + const assignedPolicyGroupColumns = [ + { field: 'group_name', name: 'Group name' }, + { field: 'group_description', name: 'Group description' }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policies-group', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: async (e) => { + await onDeletePolicyGroup(e); }, - ], - }, - ]; + }, + ], + }, + ]; - const assignedPolicySourceColumns = [ - { - field: 'source_name', - name: 'Source name', - }, - { - field: 'source_description', - name: 'Source description', - }, - { - name: 'Actions', - actions: [ - { - name: 'Delete', - description: 'Delete this policy-source', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: async (e) => { - await onDeletePolicySource(e); - }, + const assignedPolicySourceColumns = [ + { + field: 'source_name', + name: 'Source name', + }, + { + field: 'source_description', + name: 'Source description', + }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policy-source', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: async (e) => { + await onDeletePolicySource(e); }, - ], - }, - ]; - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Select a specific policy"> - <EuiComboBox - placeholder="Select a policy" - singleSelection={true} - options={policies} - selectedOptions={selectedPolicy} - onChange={(e) => { - onSelectedPolicy(e); - }} - /> - </EuiFormRow> - <EuiFormRow label="Assigned sources" fullWidth> - <EuiBasicTable - items={assignedPolicySources} - columns={assignedPolicySourceColumns} - /> - </EuiFormRow> - <EuiFormRow label="Assigned standard fields" fullWidth> - <EuiBasicTable items={assignedPolicyFields} columns={assignedPolicyColumns} /> - </EuiFormRow> - <EuiFormRow label="Assigned groups" fullWidth> - <EuiBasicTable - items={assignedPolicyGroups} - columns={assignedPolicyGroupColumns} - /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); + }, + ], + }, + ]; + return ( + <> + <EuiForm component="form"> + <EuiFormRow label="Select a specific policy"> + <EuiComboBox + placeholder="Select a policy" + singleSelection={true} + options={policies} + selectedOptions={selectedPolicy} + onChange={(e) => { + onSelectedPolicy(e); + }} + /> + </EuiFormRow> + <EuiFormRow label="Assigned sources" fullWidth> + <EuiBasicTable + items={assignedPolicySources} + columns={assignedPolicySourceColumns} + /> + </EuiFormRow> + <EuiFormRow label="Assigned standard fields" fullWidth> + <EuiBasicTable items={assignedPolicyFields} columns={assignedPolicyColumns} /> + </EuiFormRow> + <EuiFormRow label="Assigned groups" fullWidth> + <EuiBasicTable + items={assignedPolicyGroups} + columns={assignedPolicyGroupColumns} + /> + </EuiFormRow> + </EuiForm> + </> + ); +}); export default AssignedPolicies; diff --git a/src/pages/policies/NewPolicyForm.js b/src/pages/policies/NewPolicyForm.js index 6350ba1..b389023 100644 --- a/src/pages/policies/NewPolicyForm.js +++ b/src/pages/policies/NewPolicyForm.js @@ -1,59 +1,56 @@ import React, { memo } from 'react'; -import { - EuiForm, - EuiFormRow, - EuiFieldText, - EuiButton, - EuiSpacer, - EuiBasicTable, -} from '@elastic/eui'; +import { EuiBasicTable, EuiButton, EuiFieldText, EuiForm, EuiFormRow, EuiSpacer } from '@elastic/eui'; -const NewPolicyForm = memo( - ({ policyName, setPolicyName, onSaveNewPolicy, onDeletePolicy, policies }) => { - const policyColumns = [ - { - field: 'policyname', - name: 'Policy name', - }, - { - field: 'sourcename', - name: 'Source name', - }, - { - name: 'Actions', - actions: [ - { - name: 'Delete', - description: 'Delete this policy', - icon: 'trash', - type: 'icon', - color: 'danger', - onClick: onDeletePolicy, - }, - ], - }, - ]; - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Policy name"> - <EuiFieldText - value={policyName} - onChange={(e) => setPolicyName(e.target.value)} - /> - </EuiFormRow> - <EuiSpacer /> - <EuiButton type="submit" onClick={onSaveNewPolicy} fill> - Save - </EuiButton> - <EuiSpacer /> - <EuiFormRow label="" fullWidth> - <EuiBasicTable items={policies} rowheader="Name" columns={policyColumns} /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); +const NewPolicyForm = memo(function NewPolicyForm({ + policyName, + setPolicyName, + onSaveNewPolicy, + onDeletePolicy, + policies, + }) { + const policyColumns = [ + { + field: 'policyname', + name: 'Policy name', + }, + { + field: 'sourcename', + name: 'Source name', + }, + { + name: 'Actions', + actions: [ + { + name: 'Delete', + description: 'Delete this policy', + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: onDeletePolicy, + }, + ], + }, + ]; + return ( + <> + <EuiForm component="form"> + <EuiFormRow label="Policy name"> + <EuiFieldText + value={policyName} + onChange={(e) => setPolicyName(e.target.value)} + /> + </EuiFormRow> + <EuiSpacer /> + <EuiButton type="submit" onClick={onSaveNewPolicy} fill disabled={!policyName}> + Save + </EuiButton> + <EuiSpacer /> + <EuiFormRow label="" fullWidth> + <EuiBasicTable items={policies} rowheader="Name" columns={policyColumns} /> + </EuiFormRow> + </EuiForm> + </> + ); +}); export default NewPolicyForm; diff --git a/src/pages/policies/Policies.js b/src/pages/policies/Policies.js index e53c0d5..1c1ce47 100644 --- a/src/pages/policies/Policies.js +++ b/src/pages/policies/Policies.js @@ -1,35 +1,34 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { - EuiForm, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, + EuiPageHeader, + EuiPageSection, + EuiSpacer, EuiTabbedContent, + EuiTitle, } from '@elastic/eui'; import PolicyAssignment from './PolicyAssignment'; import PolicyGroupAssignment from './PolicyGroupAssignment'; import PolicySourceAssignment from './PolicySourceAssignment'; import NewPolicyForm from './NewPolicyForm'; import AssignedPolicies from './AssignedPolicies'; -import { ShowAlert } from '../../components/Common'; import { + addFieldToPolicy, + addPolicyToGroup, + addSourceToPolicy, + createPolicy, + deletePolicy, findUserBySub, - getSources, + getGroups, getPolicies, + getSources, getStdFields, - getGroups, - createPolicy, - deletePolicy, - addSourceToPolicy, - addFieldToPolicy, - addPolicyToGroup, - removeSourceFromPolicy, + getUser, removeFieldFromPolicy, removePolicyFromGroup, - getUser, + removeSourceFromPolicy, } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; +import ToastMessage from "../../components/ToastMessage/ToastMessage"; const Policies = () => { const [selectedTabNumber, setSelectedTabNumber] = useState(0); @@ -37,10 +36,6 @@ const Policies = () => { const [policyName, setPolicyName] = useState(''); const [selectedSource, setSelectedSource] = useState(); - const [open, setOpen] = useState(false); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); - const [optPolicies, setOptPolicies] = useState([]); const [selectedPolicy, setSelectedPolicy] = useState(); const [optStdFields, setOptStdFields] = useState([]); @@ -144,15 +139,12 @@ const Policies = () => { if (policyName) { const result = await createPolicy(policyName); if (result.id) { - setAlertMessage('Policy created.'); - setSeverity('success'); + toast.success('Policy created.'); setPolicyName(''); loadPolicies(); } else { - setAlertMessage('Policy not created.'); - setSeverity('error'); + toast.error('Policy not created.'); } - setOpen(true); } }; @@ -160,14 +152,11 @@ const Policies = () => { if (!e.id) return; const response = await deletePolicy(e.id); if (response && response.success) { - setAlertMessage('Policy deleted.'); - setSeverity('success'); + toast.success('Policy deleted.'); loadPolicies(); } else { - setAlertMessage('Policy not deleted.'); - setSeverity('error'); + toast.error('Policy not deleted.'); } - setOpen(true); }; const onPolicySourceAssignment = async (e) => { @@ -183,7 +172,12 @@ const Policies = () => { if (checkedPolicies && checkedSources) { for (const policy of checkedPolicies) { for (const source of checkedSources) { - await addSourceToPolicy(source.value, policy.value); + const response = await addSourceToPolicy(source.value, policy.value); + if (response.error) { + toast.error(<ToastMessage title={'Source not assigned to policy'} message={response.message}/>); + } else { + toast.success('Source assigned to policy.'); + } } } } @@ -203,10 +197,14 @@ const Policies = () => { }); for (const policy of checkedPolicies) { for (const stdField of checkedStdFields) { - await addFieldToPolicy(stdField.value, policy.value); + const response = await addFieldToPolicy(stdField.value, policy.value); + if (response.error) { + toast.error(<ToastMessage title={'Field not assigned to policy'} message={response.message}/>); + } else { + toast.success('Field assigned to policy.'); + } } } - setOptPolicies(optPolicies.map((e) => ({ ...e, checked: null }))); setOptStdFields(optStdFields.map((e) => ({ ...e, checked: null }))); loadPolicies(); @@ -217,18 +215,20 @@ const Policies = () => { const checkedPolicies = optGroupPolicies.filter((e) => { return e.checked === 'on'; }); - const checkedGroups = optGroups.filter((e) => { return e.checked === 'on'; }); - if (checkedPolicies && checkedGroups) { for (const group of checkedGroups) { for (const policy of checkedPolicies) { - await addPolicyToGroup(policy.value, group.value); + const response = await addPolicyToGroup(policy.value, group.value); + if (response.error) { + toast.error(<ToastMessage title={'Group not assigned to policy'} message={response.message}/>); + } else { + toast.success('Group assigned to policy.'); + } } } - setOptGroupPolicies(optGroupPolicies.map((e) => ({ ...e, checked: null }))); setOPtGroups(optGroups.map((e) => ({ ...e, checked: null }))); loadPolicies(); @@ -285,15 +285,12 @@ const Policies = () => { if (e.id && selectedPolicy.length > 0) { const result = await removeSourceFromPolicy(e.id, selectedPolicy[0].value); if (result && result.success) { - setAlertMessage('Policies-Source deleted.'); - setSeverity('success'); + toast.success('Policies-Source deleted.'); onSelectedPolicy(selectedPolicy); loadPolicies(); } else { - setAlertMessage('Policies-Source not deleted.'); - setSeverity('error'); + toast.error('Policies-Source not deleted.'); } - setOpen(true); } }; @@ -301,15 +298,12 @@ const Policies = () => { if (e.id && selectedPolicy.length > 0) { const result = await removeFieldFromPolicy(e.id, selectedPolicy[0].value); if (result && result.success) { - setAlertMessage('Policies-Field deleted.'); - setSeverity('success'); + toast.success('Policies-Field deleted.'); onSelectedPolicy(selectedPolicy); loadPolicies(); } else { - setAlertMessage('Policies-Field not deleted.'); - setSeverity('error'); + toast.error('Policies-Field not deleted.'); } - setOpen(true); } }; @@ -317,23 +311,13 @@ const Policies = () => { if (e.id && selectedPolicy.length > 0) { const result = await removePolicyFromGroup(selectedPolicy[0].value, e.id); if (result && result.success) { - setAlertMessage('Policies-Group deleted.'); - setSeverity('success'); + toast.success('Policies-Group deleted.'); onSelectedPolicy(selectedPolicy); loadPolicies(); } else { - setAlertMessage('Policies-Group not deleted.'); - setSeverity('error'); + toast.error('Policies-Group not deleted.'); } - setOpen(true); - } - }; - - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; } - setOpen(false); }; const tabContents = [ @@ -342,7 +326,7 @@ const Policies = () => { name: 'New policy', content: ( <> - <br /> + <EuiSpacer /> <NewPolicyForm policyName={policyName} setPolicyName={setPolicyName} @@ -363,7 +347,7 @@ const Policies = () => { name: 'Policy-Source assignment', content: ( <> - <br /> + <EuiSpacer /> <PolicySourceAssignment optPolicies={optSourcePolicies} setOptPolicies={setOptSourcePolicies} @@ -379,7 +363,7 @@ const Policies = () => { name: 'Policy-Field assignment', content: ( <> - <br /> + <EuiSpacer /> <PolicyAssignment optPolicies={optPolicies} setOptPolicies={setOptPolicies} @@ -395,7 +379,7 @@ const Policies = () => { name: 'Policy-Group assignment', content: ( <> - <br /> + <EuiSpacer /> <PolicyGroupAssignment optPolicies={optGroupPolicies} setOptPolicies={setOptGroupPolicies} @@ -411,6 +395,7 @@ const Policies = () => { name: 'Assigned policies', content: ( <> + <EuiSpacer /> <br /> <AssignedPolicies policies={optPolicies} @@ -429,29 +414,21 @@ const Policies = () => { ]; return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Policies management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiForm> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - }} - /> - </EuiForm> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Policies management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <EuiTabbedContent + tabs={tabContents} + selectedTab={tabContents[selectedTabNumber]} + onTabClick={(tab) => { + setSelectedTabNumber(tabContents.indexOf(tab)); + }} + /> + </EuiPageSection> ); }; diff --git a/src/pages/policies/PolicyAssignment.js b/src/pages/policies/PolicyAssignment.js index 44cfbc0..742b4bc 100644 --- a/src/pages/policies/PolicyAssignment.js +++ b/src/pages/policies/PolicyAssignment.js @@ -9,60 +9,58 @@ import { } from '@elastic/eui'; import React, { Fragment, memo } from 'react'; -const PolicyAssignment = memo( - ({ - optPolicies, - setOptPolicies, - optStdFields, - setOptStdFields, - onPolicyAssignment, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Policies" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optPolicies} - onChange={(newOptions) => setOptPolicies(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Standard fields" fullWidth> - <EuiSelectable - searchable - options={optStdFields} - onChange={(newOptions) => setOptStdFields(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiButton type="submit" onClick={onPolicyAssignment} fill> - Save - </EuiButton> - </EuiForm> - </> - ); - } -); +const PolicyAssignment = memo(function PolicyAssignment({ + optPolicies, + setOptPolicies, + optStdFields, + setOptStdFields, + onPolicyAssignment, +}) { + return ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Policies" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={optPolicies} + onChange={(newOptions) => setOptPolicies(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Standard fields" fullWidth> + <EuiSelectable + searchable + options={optStdFields} + onChange={(newOptions) => setOptStdFields(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiButton type="submit" onClick={onPolicyAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ); +}); export default PolicyAssignment; diff --git a/src/pages/policies/PolicyGroupAssignment.js b/src/pages/policies/PolicyGroupAssignment.js index 7233a2b..15c6315 100644 --- a/src/pages/policies/PolicyGroupAssignment.js +++ b/src/pages/policies/PolicyGroupAssignment.js @@ -9,54 +9,58 @@ import { } from '@elastic/eui'; import React, { Fragment, memo } from 'react'; -const PolicyGroupAssignment = memo( - ({ optPolicies, setOptPolicies, optGroups, setOPtGroups, onPolicyGroupAssignment }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Policies" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optPolicies} - onChange={(newOptions) => setOptPolicies(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Groups" fullWidth> - <EuiSelectable - searchable - options={optGroups} - onChange={(newOptions) => setOPtGroups(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiButton type="submit" onClick={onPolicyGroupAssignment} fill> - Save - </EuiButton> - </EuiForm> - </> - ); - } -); +const PolicyGroupAssignment = memo(function PolicyGroupAssignment({ + optPolicies, + setOptPolicies, + optGroups, + setOPtGroups, + onPolicyGroupAssignment, +}) { + return ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Policies" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={optPolicies} + onChange={(newOptions) => setOptPolicies(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Groups" fullWidth> + <EuiSelectable + searchable + options={optGroups} + onChange={(newOptions) => setOPtGroups(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiButton type="submit" onClick={onPolicyGroupAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ); +}); export default PolicyGroupAssignment; diff --git a/src/pages/policies/PolicySourceAssignment.js b/src/pages/policies/PolicySourceAssignment.js index 4db353b..b0a4870 100644 --- a/src/pages/policies/PolicySourceAssignment.js +++ b/src/pages/policies/PolicySourceAssignment.js @@ -9,61 +9,59 @@ import { } from '@elastic/eui'; import React, { Fragment, memo } from 'react'; -const PolicySourceAssignment = memo( - ({ - optPolicies, - setOptPolicies, - optSources, - setOptSources, - onPolicySourceAssignment, - }) => { - return ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Policies" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={optPolicies} - onChange={(newOptions) => setOptPolicies(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Sources" fullWidth> - <EuiSelectable - searchable - singleSelection={false} - options={optSources} - onChange={(newOptions) => setOptSources(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> - <EuiButton type="submit" onClick={onPolicySourceAssignment} fill> - Save - </EuiButton> - </EuiForm> - </> - ); - } -); +const PolicySourceAssignment = memo(function PolicySourceAssignment({ + optPolicies, + setOptPolicies, + optSources, + setOptSources, + onPolicySourceAssignment, +}) { + return ( + <> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Policies" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={optPolicies} + onChange={(newOptions) => setOptPolicies(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Sources" fullWidth> + <EuiSelectable + searchable + singleSelection={false} + options={optSources} + onChange={(newOptions) => setOptSources(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> + <EuiButton type="submit" onClick={onPolicySourceAssignment} fill> + Save + </EuiButton> + </EuiForm> + </> + ); +}); export default PolicySourceAssignment; diff --git a/src/pages/policies/styles.js b/src/pages/policies/styles.js index 4842f10..98e4e92 100644 --- a/src/pages/policies/styles.js +++ b/src/pages/policies/styles.js @@ -1,44 +1,44 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - roleContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, - formControl: { - margin: theme.spacing(1), - minWidth: 120, - maxWidth: 300, - }, - chips: { - display: 'flex', - flexWrap: 'wrap', - }, - chip: { - margin: 2, - }, - noLabel: { - marginTop: theme.spacing(3), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + roleContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + maxWidth: 300, + }, + chips: { + display: 'flex', + flexWrap: 'wrap', + }, + chip: { + margin: 2, + }, + noLabel: { + marginTop: theme.spacing(3), + }, })); diff --git a/src/pages/requests/Requests.js b/src/pages/requests/Requests.js index 5d2387b..81524d8 100644 --- a/src/pages/requests/Requests.js +++ b/src/pages/requests/Requests.js @@ -1,45 +1,34 @@ -import React, { useState, useCallback, useEffect, memo } from 'react'; -import { ShowAlert } from '../../components/Common/Common'; +import React, { memo, useCallback, useEffect, useState } from 'react'; import { + EuiBasicTable, EuiForm, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, - EuiTabbedContent, EuiFormRow, - EuiBasicTable, + EuiPageHeader, + EuiPageSection, + EuiSpacer, + EuiTabbedContent, + EuiTitle, } from '@elastic/eui'; import { - getRequests, - getPendingRequests, deleteUserRequest, + getPendingRequests, + getRequests, updateUserRequest, } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; -const RequestList = memo(({ requests, requestColumns }) => { +const RequestList = memo(function RequestList({ requests, requestColumns }) { return ( - <> - <EuiForm component="form"> - <EuiFormRow fullWidth> - <EuiBasicTable - itemId="id" - isSelectable={true} - items={requests} - columns={requestColumns} - /> - </EuiFormRow> - </EuiForm> - </> + <EuiForm component="form"> + <EuiFormRow fullWidth> + <EuiBasicTable itemId="id" items={requests} columns={requestColumns} /> + </EuiFormRow> + </EuiForm> ); }); const Requests = () => { const [selectedTabNumber, setSelectedTabNumber] = useState(0); - const [open, setOpen] = useState(false); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); const [requests, setRequests] = useState([]); const [pendingRequests, setPendingRequests] = useState([]); @@ -66,15 +55,12 @@ const Requests = () => { if (request) { const result = await deleteUserRequest(request.id); if (result) { - setAlertMessage('Request has been deleted.'); - setSeverity('success'); + toast.success('Request has been deleted.'); await loadRequests(); await loadPendingRequests(); } else { - setAlertMessage(`Error: ${result.error}`); - setSeverity('error'); + toast.error(`Error: ${result.error}`); } - setOpen(true); } }; @@ -82,15 +68,12 @@ const Requests = () => { if (request) { const result = await updateUserRequest(request.id, true); if (result) { - setAlertMessage('Request has been processed.'); - setSeverity('success'); + toast.success('Request has been processed.'); await loadRequests(); await loadPendingRequests(); } else { - setAlertMessage(`Error: ${result.error}`); - setSeverity('error'); + toast.error(`Error: ${result.error}`); } - setOpen(true); } }; @@ -142,7 +125,7 @@ const Requests = () => { name: 'Pending requests', content: ( <> - <br /> + <EuiSpacer /> <RequestList requests={pendingRequests} requestColumns={pendingRequestColumns} @@ -155,43 +138,29 @@ const Requests = () => { name: 'All requests', content: ( <> - <br /> + <EuiSpacer /> <RequestList requests={requests} requestColumns={requestColumns} /> </> ), }, ]; - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Requests management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiForm> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - }} - /> - </EuiForm> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Requests management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <EuiTabbedContent + tabs={tabContents} + selectedTab={tabContents[selectedTabNumber]} + onTabClick={(tab) => { + setSelectedTabNumber(tabContents.indexOf(tab)); + }} + /> + </EuiPageSection> ); }; diff --git a/src/pages/requests/styles.js b/src/pages/requests/styles.js index 366d14e..4949f06 100644 --- a/src/pages/requests/styles.js +++ b/src/pages/requests/styles.js @@ -1,29 +1,29 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - iconsContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + iconsContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, })); diff --git a/src/pages/roles/Roles.js b/src/pages/roles/Roles.js index 4f59de7..9642403 100644 --- a/src/pages/roles/Roles.js +++ b/src/pages/roles/Roles.js @@ -1,69 +1,59 @@ -import React, { useState, Fragment, useCallback, useEffect } from 'react'; -import { ShowAlert } from '../../components/Common'; +import React, { Fragment, useCallback, useEffect, useState } from 'react'; import { - EuiForm, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, - EuiTabbedContent, - EuiFormRow, + EuiBasicTable, + EuiButton, + EuiComboBox, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiSpacer, - EuiButton, - EuiBasicTable, + EuiForm, + EuiFormRow, + EuiPageHeader, + EuiPageSection, EuiSelectable, - EuiComboBox, + EuiSpacer, + EuiTabbedContent, + EuiTitle, } from '@elastic/eui'; -import { - getRoles, - getUsers, - createRole, - addUserToRole, - deleteRole, -} from '../../services/GatekeeperService'; +import { addUserToRole, createRole, deleteRole, getRoles, getUsers } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; const newRoleForm = ({ - roleNameValue, - setRoleNameValue, - roleDescriptionValue, - setRoleDescriptionValue, - roles, - onSaveRole, - roleColumns, -}) => { + roleNameValue, + setRoleNameValue, + roleDescriptionValue, + setRoleDescriptionValue, + roles, + onSaveRole, + roleColumns, + }) => { return ( roles && roles.length > 0 && ( - <> - <EuiForm component="form"> - <EuiFormRow label="Name"> - <EuiFieldText - value={roleNameValue || ''} - onChange={(e) => setRoleNameValue(e.target.value)} - /> - </EuiFormRow> - <EuiFormRow label="Description"> - <EuiFieldText - value={roleDescriptionValue || ''} - onChange={(e) => setRoleDescriptionValue(e.target.value)} - /> - </EuiFormRow> - <EuiSpacer /> - { - <EuiButton fill onClick={onSaveRole}> - Save - </EuiButton> - } - <EuiSpacer /> - <EuiFormRow label="" fullWidth> - <EuiBasicTable items={roles} rowheader="Name" columns={roleColumns} /> - </EuiFormRow> - </EuiForm> - </> + <EuiForm component="form"> + <EuiFormRow label="Name"> + <EuiFieldText + value={roleNameValue || ''} + onChange={(e) => setRoleNameValue(e.target.value)} + /> + </EuiFormRow> + <EuiFormRow label="Description"> + <EuiFieldText + value={roleDescriptionValue || ''} + onChange={(e) => setRoleDescriptionValue(e.target.value)} + /> + </EuiFormRow> + <EuiSpacer /> + { + <EuiButton fill onClick={onSaveRole}> + Save + </EuiButton> + } + <EuiSpacer /> + <EuiFormRow label="" fullWidth> + <EuiBasicTable items={roles} rowheader="Name" columns={roleColumns} /> + </EuiFormRow> + </EuiForm> ) ); }; @@ -74,83 +64,80 @@ const roleAssignment = (roles, setRoles, users, setUsers, onRoleAssignment) => { users && roles.length > 0 && users.length > 0 && ( - <> - <EuiForm component="form"> - <EuiFlexGroup component="span"> - <EuiFlexItem component="span"> - <EuiFormRow label="Roles" fullWidth> - <EuiSelectable - searchable - singleSelection={true} - options={roles} - onChange={(newOptions) => setRoles(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem component="span"> - <EuiFormRow label="Users" fullWidth> - <EuiSelectable - searchable - options={users} - onChange={(newOptions) => setUsers(newOptions)} - > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer /> + <EuiForm component="form"> + <EuiFlexGroup component="span"> + <EuiFlexItem component="span"> + <EuiFormRow label="Roles" fullWidth> + <EuiSelectable + searchable + singleSelection={true} + options={roles} + onChange={(newOptions) => setRoles(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem component="span"> + <EuiFormRow label="Users" fullWidth> + <EuiSelectable + searchable + options={users} + onChange={(newOptions) => setUsers(newOptions)} + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer /> - <EuiButton type="submit" onClick={onRoleAssignment} fill> - Save - </EuiButton> - </EuiForm> - </> + <EuiButton type="submit" onClick={onRoleAssignment} fill> + Save + </EuiButton> + </EuiForm> ) ); }; + const assignedRoles = ({ - roles, - selectedRole, - onSelectedRole, - assignedRoleToUsers, - assignedRolesColumns, -}) => { + roles, + selectedRole, + onSelectedRole, + assignedRoleToUsers, + assignedRolesColumns, + }) => { return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Select specific roles"> - <EuiComboBox - placeholder="Select a role" - singleSelection={true} - options={roles} - selectedOptions={selectedRole} - onChange={(e) => { - onSelectedRole(e); - }} - /> - </EuiFormRow> - <EuiFormRow label="Assigned roles" fullWidth> - <EuiBasicTable - items={assignedRoleToUsers} - rowheader="" - columns={assignedRolesColumns} - /> - </EuiFormRow> - </EuiForm> - </> + <EuiForm component="form"> + <EuiFormRow label="Select specific roles"> + <EuiComboBox + placeholder="Select a role" + singleSelection={true} + options={roles} + selectedOptions={selectedRole} + onChange={(e) => { + onSelectedRole(e); + }} + /> + </EuiFormRow> + <EuiFormRow label="Assigned roles" fullWidth> + <EuiBasicTable + items={assignedRoleToUsers} + rowheader="" + columns={assignedRolesColumns} + /> + </EuiFormRow> + </EuiForm> ); }; @@ -161,14 +148,10 @@ const Roles = () => { const [roles, setRoles] = useState([]); const [users, setUsers] = useState([]); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); - const [open, setOpen] = useState(false); - const [selectedRole, setSelectedRole] = useState(); const [assignedRoleToUsers, setAssignedRoleToUsers] = useState([]); - const loads = useCallback(async () => { + const fetchRolesAndUsers = useCallback(async () => { const roles = await getRoles(); const users = await getUsers(); @@ -195,27 +178,24 @@ const Roles = () => { useEffect(() => { setSelectedRole([]); setAssignedRoleToUsers([]); - loads(); - }, [loads, selectedTabNumber]); + fetchRolesAndUsers(); + }, [fetchRolesAndUsers, selectedTabNumber]); useEffect(() => { - loads(); - }, [loads]); + fetchRolesAndUsers(); + }, [fetchRolesAndUsers]); const onSaveRole = async (e) => { e.preventDefault(); const result = await createRole(roleNameValue, roleDescriptionValue); - if (result) { - setAlertMessage('Role has been created.'); - setSeverity('success'); + if (!result.error) { + toast.success('Role has been created.'); + setRoleNameValue(''); + setRoleDescriptionValue(''); } else { - setAlertMessage('Role has not been created.'); - setSeverity('error'); + toast.error('Role has not been created.'); } - setOpen(true); - setRoleNameValue(''); - setRoleDescriptionValue(''); - await loads(); + await fetchRolesAndUsers(); }; const onRoleAssignment = async (e) => { @@ -230,10 +210,15 @@ const Roles = () => { for (const user of checkedUsers) { for (const role of checkedRoles) { - await addUserToRole(user.sub, role.value); + const result = await addUserToRole(user.sub, role.value); + if (result.error) { + toast.error('Could not assign role to user.'); + } else { + toast.success('Role assigned to user.'); + } } } - await loads(); + await fetchRolesAndUsers(); setRoles(roles.map((role) => ({ ...role, checked: null }))); setUsers(users.map((user) => ({ ...user, checked: null }))); }; @@ -241,15 +226,12 @@ const Roles = () => { const removeUserFromRole = async (e) => { const result = await removeUserFromRole(e.sub, e.roleId); if (result) { - setAlertMessage('User has been removed from role.'); - setSeverity('success'); + toast.success('User has been removed from role.'); } else { - setAlertMessage('User has not been removed from role.'); - setSeverity('error'); + toast.error('User has not been removed from role.'); } - setOpen(true); onSelectedRole([]); - await loads(); + await fetchRolesAndUsers(); }; const onSelectedRole = async (e) => { @@ -277,14 +259,11 @@ const Roles = () => { const onDeleteRole = async (e) => { const result = await deleteRole(e.value); if (result) { - setAlertMessage('Role has been deleted.'); - setSeverity('success'); + toast.success('Role has been deleted.'); } else { - setAlertMessage('Role has not been deleted.'); - setSeverity('error'); + toast.error('Role has not been deleted.'); } - setOpen(true); - await loads(); + await fetchRolesAndUsers(); }; const roleActions = [ @@ -321,13 +300,6 @@ const Roles = () => { { name: 'Actions', actions: assignedRoleActions }, ]; - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; - const tabContents = [ { id: 'tab1', @@ -376,29 +348,21 @@ const Roles = () => { ]; return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Roles management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiForm> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - }} - /> - </EuiForm> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Roles management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <EuiTabbedContent + tabs={tabContents} + selectedTab={tabContents[selectedTabNumber]} + onTabClick={(tab) => { + setSelectedTabNumber(tabContents.indexOf(tab)); + }} + /> + </EuiPageSection> ); }; diff --git a/src/pages/roles/styles.js b/src/pages/roles/styles.js index 4842f10..98e4e92 100644 --- a/src/pages/roles/styles.js +++ b/src/pages/roles/styles.js @@ -1,44 +1,44 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - roleContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, - formControl: { - margin: theme.spacing(1), - minWidth: 120, - maxWidth: 300, - }, - chips: { - display: 'flex', - flexWrap: 'wrap', - }, - chip: { - margin: 2, - }, - noLabel: { - marginTop: theme.spacing(3), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + roleContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, + formControl: { + margin: theme.spacing(1), + minWidth: 120, + maxWidth: 300, + }, + chips: { + display: 'flex', + flexWrap: 'wrap', + }, + chip: { + margin: 2, + }, + noLabel: { + marginTop: theme.spacing(3), + }, })); diff --git a/src/pages/sources/NewSourceForm.js b/src/pages/sources/NewSourceForm.js new file mode 100644 index 0000000..62dafbc --- /dev/null +++ b/src/pages/sources/NewSourceForm.js @@ -0,0 +1,195 @@ +import React, { memo, useState } from 'react'; +import { + EuiButton, + EuiCallOut, + EuiFieldText, + EuiFilePicker, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiLink, + EuiPanel, + EuiText, +} from '@elastic/eui'; +import ReactJson from '@microlink/react-json-view'; +import { createSource } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; +import ToastMessage from "../../components/ToastMessage/ToastMessage"; + +const NewSourceForm = memo(function NewSourceForm({ loadSources }) { + const [files, setFiles] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [loadedData, setLoadedData] = useState([]); + const [nameValue, setNameValue] = useState(); + const [descriptionValue, setDescriptionValue] = useState(); + + const onFilePickerChange = async (files) => { + if (files.length > 0) { + setFiles(files); + await handleSelectedFile(files); + } else { + setFiles([]); + setLoadedData([]); + } + }; + + const handleSelectedFile = async (files) => { + for (const file of files) { + const reader = new FileReader(); + await reader.readAsText(file); + reader.onload = () => handleData(reader.result); + } + }; + + const handleData = (file) => { + const { metadataRecords } = JSON.parse(file); + if (metadataRecords) { + setLoadedData(metadataRecords); + } + }; + + const renderFiles = (files) => { + if (files.length > 0) { + return ( + <ul> + {Object.keys(files).map((item, i) => ( + <li key={i}> + <strong>{files[item].name}</strong> ({files[item].size} bytes) + </li> + ))} + </ul> + ); + } else { + return <p>Upload files to continue.</p>; + } + }; + + const ValidatorWarning = () => { + return ( + <EuiCallOut title={'Verify your source'} color="warning"> + {/*TODO add iconType warning after upgrading EUI packages*/} + <EuiText> + <p> + We strongly advise you to test and correct your source with our validator + before uploading. + </p> + </EuiText> + </EuiCallOut> + ); + }; + + const ValidatorAction = () => { + const { REACT_APP_VALIDATOR_URL } = process.env; + + return ( + <EuiButton color="warning" size="m"> + <EuiLink + href={REACT_APP_VALIDATOR_URL} + target="_blank" + rel="noopener noreferrer" + color={'warning'} + external + > + {/*TODO replace icon type with editorChecklist after upgrading EUI packages*/} + <EuiIcon type="folderCheck" size="l" /> + Verify my source + </EuiLink> + </EuiButton> + ); + }; + + const onSaveSource = async (e) => { + setIsLoading(true); + e.preventDefault(); + if (nameValue && descriptionValue && loadedData.length !== 0) { + const result = await createSource(loadedData, nameValue, descriptionValue); + if (result?.id) { + toast.success('Source created.'); + setNameValue(''); + setDescriptionValue(''); + setFiles([]); + setLoadedData([]); + await loadSources(); + } else { + toast.error(<ToastMessage title={'Source not created.'} message={result.message} />); + } + setIsLoading(false); + } + }; + + return ( + <EuiFlexGroup> + <EuiFlexItem> + <EuiFlexGrid columns={2}> + <EuiFlexItem> + <ValidatorWarning /> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup alignItems={'center'}> + <ValidatorAction /> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup> + <EuiPanel hasShadow={false} grow={false}> + <EuiFormRow label="Source or file name"> + <EuiFieldText + id="nameValue" + value={nameValue || ''} + onChange={(e) => setNameValue(e.target.value)} + /> + </EuiFormRow> + <EuiFormRow label="Source description"> + <EuiFieldText + id="descriptionValue" + value={descriptionValue || ''} + onChange={(e) => setDescriptionValue(e.target.value)} + /> + </EuiFormRow> + <EuiFormRow label="Source files"> + <EuiFilePicker + id="filePicker1" + multiple + initialPromptText="Select or drag and drop multiple source files" + onChange={(files) => { + onFilePickerChange(files); + }} + display={'large'} + /> + </EuiFormRow> + <EuiFormRow label="Files attached"> + <EuiText>{renderFiles(files)}</EuiText> + </EuiFormRow> + <EuiButton + fill + onClick={onSaveSource} + isLoading={isLoading} + disabled={!nameValue || !descriptionValue || loadedData.length === 0} + > + Save + </EuiButton> + </EuiPanel> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + {loadedData && loadedData.length !== 0 && ( + <EuiPanel hasShadow={false}> + <ReactJson + style={{ maxHeight: '45vh', overflow: 'auto' }} + name="Loaded data" + collapsed={true} + iconStyle={'triangle'} + src={loadedData} + /> + </EuiPanel> + )} + </EuiFlexItem> + </EuiFlexGrid> + </EuiFlexItem> + </EuiFlexGroup> + ); +}); + +export default NewSourceForm; diff --git a/src/pages/sources/Sources.js b/src/pages/sources/Sources.js index 10957cd..f6c68aa 100644 --- a/src/pages/sources/Sources.js +++ b/src/pages/sources/Sources.js @@ -1,317 +1,35 @@ -import React, { useState, useCallback, useEffect, memo, useRef } from 'react'; -import { - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, - EuiTabbedContent, - EuiFormRow, - EuiFieldText, - EuiText, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiButton, - EuiFilePicker, - EuiBasicTable, - EuiHealth, - EuiConfirmModal, - EuiOverlayMask, -} from '@elastic/eui'; - -import { ShowAlert } from '../../components/Common'; -import ReactJson from '@microlink/react-json-view'; -import { - getUser, - deleteSource, - createSource, - findUserBySub, - getSources, -} from '../../services/GatekeeperService'; - -const NewSourceForm = memo( - ({ - nameValue, - setNameValue, - descriptionValue, - setDescriptionValue, - onSaveSource, - onFilePickerChange, - files, - renderFiles, - metaUrfms, - }) => { - return ( - <> - <EuiSpacer /> - <EuiFlexGroup> - <EuiFlexItem grow={3}> - <EuiFormRow label="Source or file name"> - <EuiFieldText - id="nameValue" - value={nameValue || ''} - onChange={(e) => setNameValue(e.target.value)} - /> - </EuiFormRow> - <EuiFormRow label="Source description"> - <EuiFieldText - id="descriptionValue" - value={descriptionValue || ''} - onChange={(e) => setDescriptionValue(e.target.value)} - /> - </EuiFormRow> - <EuiSpacer /> - <EuiFormRow label="Source files"> - <EuiFilePicker - id="filePicker1" - multiple - initialPromptText="Select or drag and drop multiple source files" - onChange={(files) => { - onFilePickerChange(files); - }} - display={'large'} - /> - </EuiFormRow> - <EuiFormRow label=""> - <EuiText> - <h3>Files attached</h3> - {renderFiles(files)} - </EuiText> - </EuiFormRow> - <EuiFormRow label=""> - { - <EuiButton - fill - onClick={onSaveSource} - disabled={!nameValue || !descriptionValue || !metaUrfms} - > - Save - </EuiButton> - } - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem grow={3}> - <ReactJson - name="Metadata records" - collapsed={true} - iconStyle={'triangle'} - src={metaUrfms} - /> - </EuiFlexItem> - </EuiFlexGroup> - </> - ); - } -); - -const SourcesForm = memo( - ({ - metaUrfmColumns, - sources, - onTableChange, - tableref, - pagination, - sorting, - }) => { - return ( - <> - <EuiSpacer /> - <EuiBasicTable - itemId="id" - items={sources} - columns={metaUrfmColumns} - onChange={onTableChange} - ref={tableref} - pagination={pagination} - sorting={sorting} - /> - </> - ); - } -); - -const renderFiles = (files) => { - if (files.length > 0) { - return ( - <ul> - {Object.keys(files).map((item, i) => ( - <li key={i}> - <strong>{files[item].name}</strong> ({files[item].size} bytes) - </li> - ))} - </ul> - ); - } else { - return <p>Upload files to continue.</p>; - } -}; +import React, { useCallback, useEffect, useState } from 'react'; +import { EuiPageHeader, EuiPageSection, EuiSpacer, EuiTabbedContent, EuiTitle } from '@elastic/eui'; +import { findUserBySub, getSources, getUser } from '../../services/GatekeeperService'; +import NewSourceForm from './NewSourceForm'; +import SourcesTable from './SourcesTable'; +import { toast } from 'react-toastify'; +import ToastMessage from '../../components/ToastMessage/ToastMessage'; const Sources = () => { - const [metaUrfms, setMetaUrfms] = useState([]); const [selectedTabNumber, setSelectedTabNumber] = useState(0); - const [files, setFiles] = useState([]); - const [nameValue, setNameValue] = useState(); - const [descriptionValue, setDescriptionValue] = useState(); - const [open, setOpen] = useState(false); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); const [sources, setSources] = useState([]); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(5); - const [sortDirection, setSortDirection] = useState('asc'); - const [sortField, setSortField] = useState('name'); - const [source, setSource] = useState({}); - const [isModalVisible, setIsModalVisible] = useState(false); - const tableref = useRef(); - - const onTableChange = ({ page = {}, sort = {} }) => { - const { index: pageIndex, size: pageSize } = page; - const { field: sortField, direction: sortDirection } = sort; - setPageIndex(pageIndex); - setPageSize(pageSize); - setSortField(sortField); - setSortDirection(sortDirection); - }; - - const pagination = { - pageIndex: pageIndex, - pageSize: pageSize, - totalItemCount: typeof sources === ([] || null) ? 0 : sources.length, - pageSizeOptions: [3, 5, 8], - }; - - const sorting = { - sort: { - field: sortField, - direction: sortDirection, - }, - }; - - const closeModal = () => setIsModalVisible(false); - const showModal = () => setIsModalVisible(true); - - // delete source from db, elasticsearch and mongodb - const onDeleteSource = async (source) => { - setSource(source); - showModal(); - }; - - const onDeleteSourceConfirm = async () => { - closeModal(); - if (source.id) { - await deleteSource(source.id); - setAlertMessage('Source deleted.'); - setSeverity('success'); - setOpen(true); - await loadSources(); - } else { - setOpen(true); - setAlertMessage('An error occurred while deleting this source.'); - setSeverity('error'); - } - }; - - const metaUrfmActions = [ - { - name: 'Delete', - description: 'Delete this source', - icon: 'trash', - type: 'icon', - color: 'danger', - enable: () => false, - onClick: onDeleteSource, - }, - ]; - const metaUrfmColumns = [ - { field: 'name', name: 'Source or file name' }, - { field: 'index_id', name: 'Elasticsearch index' }, - { field: 'mng_id', name: 'MongoDb index' }, - { field: 'description', name: 'Source description' }, - { - field: 'is_send', - name: 'Status', - dataType: 'boolean', - render: (value) => { - const color = value ? 'success' : 'danger'; - const label = value ? 'sent' : 'unsent'; - return <EuiHealth color={color}>{label}</EuiHealth>; - }, - }, - { name: 'Actions', actions: metaUrfmActions }, - ]; - - const onFilePickerChange = async (files) => { - if (files.length > 0) { - setFiles(files); - await handleSelectedFile(files); - } else { - setFiles([]); - setMetaUrfms([]); - } - }; - - const handleSelectedFile = async (files) => { - for (const file of files) { - const reader = new FileReader(); - await reader.readAsText(file); - reader.onload = () => handleData(reader.result); - } - }; - - const handleData = (file) => { - const { metadataRecords } = JSON.parse(file); - if (metadataRecords) { - setMetaUrfms(metadataRecords); - } - }; - - const onSaveSource = async (e) => { - e.preventDefault(); - if (nameValue && descriptionValue && metaUrfms) { - const result = await createSource(metaUrfms, nameValue, descriptionValue); - - if (result?.id) { - setOpen(true); - setAlertMessage('Source created.'); - setSeverity('success'); - setNameValue(''); - setDescriptionValue(''); - setFiles([]); - setMetaUrfms([]); - - await loadSources(); - } else { - setOpen(true); - setAlertMessage('Unexpected error while creating source.'); - setSeverity('error'); - } - } - }; const loadSources = useCallback(async () => { - const userInfo = getUser(); - const sub = userInfo.profile?.sub; - const user = await findUserBySub(sub); - const role = user.roles[0].role_id; - let result; - if (role === 1) { - result = await getSources(); - } else { - result = await getSources(); - } + let result = await getSources(); if (result) { - result = result.map((source) => { + const newSources = result.map((source) => { return { id: source.id, name: source.name, description: source.description, - index_id: source.source_indices[0].index_id, - mng_id: source.source_indices[0].mng_id, - is_send: source.source_indices[0].is_send, + index_id: source.source_indices[0]?.index_id, + mng_id: source.source_indices[0]?.mng_id, + is_send: source.source_indices[0]?.is_send, + createdAt: source.createdAt, + creator: source.providers[0]?.user?.email, }; }); - setSources(result); + setSources(newSources); + } else { + toast.error( + <ToastMessage title={'Error loading sources'} message={result.error} />, + ); } }, [getSources, getUser, findUserBySub]); @@ -325,30 +43,14 @@ const Sources = () => { return () => (isSubscribed = false); }, [loadSources]); - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; - const tabContents = [ { id: 'tab1', name: 'New source', content: ( <> - <NewSourceForm - nameValue={nameValue} - setNameValue={setNameValue} - descriptionValue={descriptionValue} - setDescriptionValue={setDescriptionValue} - onFilePickerChange={onFilePickerChange} - files={files} - renderFiles={renderFiles} - onSaveSource={onSaveSource} - metaUrfms={metaUrfms} - /> + <EuiSpacer /> + <NewSourceForm loadSources={loadSources} /> </> ), }, @@ -357,61 +59,29 @@ const Sources = () => { name: 'Sources', content: ( <> - <SourcesForm - metaUrfmColumns={metaUrfmColumns} - sources={typeof sources === undefined ? [] : sources} - onTableChange={onTableChange} - tableref={tableref} - pagination={pagination} - sorting={sorting} - /> + <EuiSpacer /> + <SourcesTable sources={sources} loadSources={loadSources} /> </> ), }, ]; - let Warningmodal; - if (isModalVisible) { - Warningmodal = ( - <EuiOverlayMask> - <EuiConfirmModal - title="Warning" - onCancel={closeModal} - onConfirm={onDeleteSourceConfirm} - cancelButtonText="Cancel" - confirmButtonText="Confirm" - buttonColor="danger" - defaultFocusedButton="confirm" - > - <p>Are you sure you want to delete this file?</p> - </EuiConfirmModal> - </EuiOverlayMask> - ); - } - return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Source management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiTabbedContent - tabs={tabContents} - selectedTab={tabContents[selectedTabNumber]} - onTabClick={(tab) => { - setSelectedTabNumber(tabContents.indexOf(tab)); - }} - /> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - {Warningmodal} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Source management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <EuiTabbedContent + tabs={tabContents} + selectedTab={tabContents[selectedTabNumber]} + onTabClick={(tab) => { + setSelectedTabNumber(tabContents.indexOf(tab)); + }} + /> + </EuiPageSection> ); }; diff --git a/src/pages/sources/SourcesTable.js b/src/pages/sources/SourcesTable.js new file mode 100644 index 0000000..ea62c35 --- /dev/null +++ b/src/pages/sources/SourcesTable.js @@ -0,0 +1,127 @@ +import React, { memo, useRef, useState } from 'react'; +import { EuiBasicTable, EuiConfirmModal, EuiHealth, EuiOverlayMask } from '@elastic/eui'; +import { deleteSource } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; + +const SourcesTable = memo(function SourcesTable({ sources, loadSources }) { + const [isModalVisible, setIsModalVisible] = useState(false); + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(15); + const [sortDirection, setSortDirection] = useState('asc'); + const [sortField, setSortField] = useState('name'); + const [deleteSourceID, setDeleteSourceID] = useState(); + const tableRef = useRef({}); + + const closeDeleteConfirmationModal = () => setIsModalVisible(false); + const showDeleteConfirmationModal = () => setIsModalVisible(true); + + // Opens confirmation modal + const onDeleteSource = async (source) => { + setDeleteSourceID(source.id); + showDeleteConfirmationModal(); + }; + + // Delete source from db, elasticsearch and mongodb + const onDeleteSourceConfirm = async () => { + closeDeleteConfirmationModal(); + if (deleteSourceID) { + await deleteSource(deleteSourceID); + toast.success('Source deleted.'); + await loadSources(); + } else { + toast.success('The source was not deleted.'); + } + }; + + const onTableChange = ({ page = {}, sort = {} }) => { + const { index: pageIndex, size: pageSize } = page; + const { field: sortField, direction: sortDirection } = sort; + setPageIndex(pageIndex); + setPageSize(pageSize); + setSortField(sortField); + setSortDirection(sortDirection); + }; + + const pagination = { + pageIndex: pageIndex, + pageSize: pageSize, + totalItemCount: typeof sources === ([] || null) ? 0 : sources.length, + pageSizeOptions: [5, 15, 30], + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + const actions = [ + { + name: 'Delete', + description: 'Delete this source', + icon: 'trash', + type: 'icon', + color: 'danger', + enable: () => false, + onClick: onDeleteSource, + }, + ]; + + const columns = [ + { field: 'name', name: 'Name' }, + { field: 'index_id', name: 'Elasticsearch index' }, + { field: 'mng_id', name: 'MongoDb index' }, + { field: 'description', name: 'Description' }, + { + field: 'is_send', + name: 'Status', + dataType: 'boolean', + render: (value) => { + const color = value ? 'success' : 'danger'; + const label = value ? 'sent' : 'unsent'; + return <EuiHealth color={color}>{label}</EuiHealth>; + }, + }, + { field: 'createdAt', name: 'Created at', dataType: 'date' }, + { field: 'creator', name: 'Creator' }, + { name: 'Actions', actions }, + ]; + + const deleteConfirmationModal = () => { + if (isModalVisible) { + return ( + <EuiOverlayMask> + <EuiConfirmModal + title="Warning" + onCancel={closeDeleteConfirmationModal} + onConfirm={onDeleteSourceConfirm} + cancelButtonText="Cancel" + confirmButtonText="Confirm" + buttonColor="danger" + defaultFocusedButton="confirm" + > + <p>Are you sure you want to delete this file?</p> + </EuiConfirmModal> + </EuiOverlayMask> + ); + } + }; + + return ( + <> + <EuiBasicTable + itemId="id" + items={sources} + columns={columns} + onChange={onTableChange} + ref={tableRef} + pagination={pagination} + sorting={sorting} + /> + {deleteConfirmationModal()} + </> + ); +}); + +export default SourcesTable; diff --git a/src/pages/sources/styles.js b/src/pages/sources/styles.js index 9e578a3..2b26ab1 100644 --- a/src/pages/sources/styles.js +++ b/src/pages/sources/styles.js @@ -1,96 +1,96 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - iconsContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + iconsContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, - body: { - backgroundColor: '#f2ede8', - margin: 0, - padding: 0, - fontSize: '16px', - fontFamily: 'sans-serif', - display: 'flex', - height: '100vh', - }, - FileUploadForm: { - backgroundColor: '#fff', - padding: '2rem', - display: 'grid', - gridGap: '1.6rem', - }, - ActionBar: { - textAlign: 'right', - }, + body: { + backgroundColor: '#f2ede8', + margin: 0, + padding: 0, + fontSize: '16px', + fontFamily: 'sans-serif', + display: 'flex', + height: '100vh', + }, + FileUploadForm: { + backgroundColor: '#fff', + padding: '2rem', + display: 'grid', + gridGap: '1.6rem', + }, + ActionBar: { + textAlign: 'right', + }, - ActionBarButton: { - padding: '.4rem', - }, + ActionBarButton: { + padding: '.4rem', + }, - MessageBox: { - padding: '1.6rem', - backgroundColor: '#faf4e9', - borderTop: '3px solid #ffa600', - }, - input: { - display: 'none', - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, + MessageBox: { + padding: '1.6rem', + backgroundColor: '#faf4e9', + borderTop: '3px solid #ffa600', + }, + input: { + display: 'none', + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, - appBar: { - position: 'relative', - }, - layout: { - width: 'auto', - marginLeft: theme.spacing(2), - marginRight: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { - width: 600, - marginLeft: 'auto', - marginRight: 'auto', - }, - }, - paper: { - marginTop: theme.spacing(3), - marginBottom: theme.spacing(3), - padding: theme.spacing(2), - [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { - marginTop: theme.spacing(6), - marginBottom: theme.spacing(6), - padding: theme.spacing(3), - }, - }, - stepper: { - padding: theme.spacing(3, 0, 5), - }, - buttons: { - display: 'flex', - justifyContent: 'flex-end', - }, - button: { - marginTop: theme.spacing(3), - marginLeft: theme.spacing(1), - }, + appBar: { + position: 'relative', + }, + layout: { + width: 'auto', + marginLeft: theme.spacing(2), + marginRight: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(2) * 2)]: { + width: 600, + marginLeft: 'auto', + marginRight: 'auto', + }, + }, + paper: { + marginTop: theme.spacing(3), + marginBottom: theme.spacing(3), + padding: theme.spacing(2), + [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: { + marginTop: theme.spacing(6), + marginBottom: theme.spacing(6), + padding: theme.spacing(3), + }, + }, + stepper: { + padding: theme.spacing(3, 0, 5), + }, + buttons: { + display: 'flex', + justifyContent: 'flex-end', + }, + button: { + marginTop: theme.spacing(3), + marginLeft: theme.spacing(1), + }, })); diff --git a/src/pages/users/Users.js b/src/pages/users/Users.js index 4910000..b1e3f43 100644 --- a/src/pages/users/Users.js +++ b/src/pages/users/Users.js @@ -1,45 +1,36 @@ -import React, { useState, useEffect, useCallback, memo, useRef } from 'react'; -import { ShowAlert } from '../../components/Common'; +import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; import { - EuiForm, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, - EuiPageContentBody, - EuiFormRow, EuiBasicTable, + EuiPageHeader, + EuiPageSection, + EuiSpacer, + EuiTitle, } from '@elastic/eui'; -import { getUsers, deleteUser } from '../../services/GatekeeperService'; - -const UserList = memo( - ({ users, userColumns, onTableChange, tableRef, pagination, sorting }) => { - return ( - <> - <EuiForm component="form"> - <EuiFormRow label="Users" fullWidth> - <EuiBasicTable - itemId="id" - isSelectable={true} - items={users} - columns={userColumns} - onChange={onTableChange} - ref={tableRef} - pagination={pagination} - sorting={sorting} - /> - </EuiFormRow> - </EuiForm> - </> - ); - } -); +import { deleteUser, getUsers } from '../../services/GatekeeperService'; +import { toast } from 'react-toastify'; + +const UserList = memo(function UserList({ + users, + userColumns, + onTableChange, + tableRef, + pagination, + sorting, +}) { + return ( + <EuiBasicTable + itemId="id" + items={users} + columns={userColumns} + onChange={onTableChange} + ref={tableRef} + pagination={pagination} + sorting={sorting} + /> + ); +}); const Users = () => { - const [open, setOpen] = useState(false); - const [alertMessage, setAlertMessage] = useState(''); - const [severity, setSeverity] = useState('info'); - const [users, setUsers] = useState([]); const [sortDirection, setSortDirection] = useState('asc'); const [sortField, setSortField] = useState('username'); @@ -78,13 +69,9 @@ const Users = () => { if (id) { const response = await deleteUser(id); if (!response) { - setAlertMessage('User has not been deleted.'); - setSeverity('error'); - setOpen(true); + toast.error('User has not been deleted.'); } else { - setAlertMessage('User has been deleted.'); - setSeverity('success'); - setOpen(true); + toast.success('User has been deleted.'); await loadUsers(); } } @@ -137,37 +124,23 @@ const Users = () => { setSortDirection(sortDirection); }; - const handleClose = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setOpen(false); - }; return ( - <> - <EuiPageContent> - <EuiPageContentHeader> - <EuiPageContentHeaderSection> - <EuiTitle> - <h6>Users management</h6> - </EuiTitle> - </EuiPageContentHeaderSection> - </EuiPageContentHeader> - <EuiPageContentBody> - <EuiForm> - <UserList - users={users} - userColumns={userColumns} - onTableChange={onTableChange} - tableRef={tableRef} - pagination={pagination} - sorting={sorting} - /> - </EuiForm> - </EuiPageContentBody> - </EuiPageContent> - {ShowAlert(open, handleClose, alertMessage, severity)} - </> + <EuiPageSection color={'plain'}> + <EuiPageHeader> + <EuiTitle> + <h6>Users management</h6> + </EuiTitle> + </EuiPageHeader> + <EuiSpacer /> + <UserList + users={users} + userColumns={userColumns} + onTableChange={onTableChange} + tableRef={tableRef} + pagination={pagination} + sorting={sorting} + /> + </EuiPageSection> ); }; diff --git a/src/pages/users/styles.js b/src/pages/users/styles.js index 366d14e..4949f06 100644 --- a/src/pages/users/styles.js +++ b/src/pages/users/styles.js @@ -1,29 +1,29 @@ -import { makeStyles } from '@material-ui/styles'; +import {makeStyles} from '@mui/styles'; export default makeStyles((theme) => ({ - titleBold: { - fontWeight: 600, - }, - tab: { - color: theme.palette.primary.light + 'CC', - }, - iconsContainer: { - boxShadow: theme.customShadows.widget, - overflow: 'hidden', - paddingBottom: theme.spacing(2), - }, - dashedBorder: { - border: '1px dashed', - borderColor: theme.palette.primary.main, - padding: theme.spacing(2), - paddingTop: theme.spacing(4), - paddingBottom: theme.spacing(4), - marginTop: theme.spacing(1), - }, - text: { - marginBottom: theme.spacing(2), - }, - root: { - padding: theme.spacing(3, 2), - }, + titleBold: { + fontWeight: 600, + }, + tab: { + color: theme.palette.primary.light + 'CC', + }, + iconsContainer: { + boxShadow: theme.customShadows.widget, + overflow: 'hidden', + paddingBottom: theme.spacing(2), + }, + dashedBorder: { + border: '1px dashed', + borderColor: theme.palette.primary.main, + padding: theme.spacing(2), + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), + marginTop: theme.spacing(1), + }, + text: { + marginBottom: theme.spacing(2), + }, + root: { + padding: theme.spacing(3, 2), + }, })); diff --git a/src/themes/index.js b/src/themes/index.js index 9869367..fafa05f 100644 --- a/src/themes/index.js +++ b/src/themes/index.js @@ -1,6 +1,5 @@ import defaultTheme from './default'; - -import { createTheme } from '@material-ui/core'; +import { createTheme } from '@mui/material'; const overrides = { typography: { diff --git a/src/utils.js b/src/utils.js index 6b39314..3dd5e3b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -74,7 +74,7 @@ export const getSideBarItems = () => { id: 6, label: 'Policies', link: '/policies', - roles: [1], + roles: [1, 2], icon: <EuiIcon type="lockOpen" size="l" />, }, { @@ -88,7 +88,7 @@ export const getSideBarItems = () => { id: 8, label: 'Fields', link: '/fields', - roles: [1, 2], + roles: [1], icon: <EuiIcon type="visTable" size="l" />, }, ]; -- GitLab From 7b76cca0af012f743df1d1867b82a069d2530af4 Mon Sep 17 00:00:00 2001 From: rbisson <remi.bisson@inrae.fr> Date: Fri, 21 Feb 2025 11:46:38 +0100 Subject: [PATCH 2/2] v1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d9a489..050ecca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "in-sylva.portal", - "version": "1.1.1-alpha", + "version": "1.2.0", "private": true, "homepage": ".", "dependencies": { -- GitLab