From 016743b6f3077e9129e062861767ca1d8f276a66 Mon Sep 17 00:00:00 2001 From: Christina Jenks <christina.jenks@ess.eu> Date: Fri, 4 Aug 2023 15:10:11 +0000 Subject: [PATCH] CE-1847: use common GlobalAppBar --- src/App.js | 19 +- src/api/SwaggerApi.js | 82 +-- src/api/UserProvider.js | 76 +++ src/api/UserProvider.spec.js | 4 +- src/components/IOC/IOCManage/IOCManage.js | 4 +- .../auth/AccessControl/AccessControl.js | 2 +- src/components/auth/TokenRenew/TokenRenew.js | 3 +- .../auth/TokenRenew/TokenRenew.spec.js | 3 +- src/components/common/Helper.js | 8 +- .../common/notification/Notifications.spec.js | 8 +- .../navigation/GlobalAppBar/GlobalAppBar.js | 590 ------------------ .../navigation/GlobalAppBar/index.js | 24 - .../LoginSuggester/LoginSuggester.js | 2 +- .../NavigationMenu/LoginControls.js | 114 ++++ .../NavigationMenu/NavigationMenu.js | 130 ++++ .../navigation/NavigationMenu/index.js | 4 + src/mocks/AppHarness.js | 9 +- src/style/Theme.js | 9 +- src/views/IOC/IOCDetailsView.js | 16 +- src/views/IOC/IOCListView.js | 13 +- src/views/help/HelpView.js | 10 +- src/views/home/HomeAccessControl.js | 8 +- src/views/home/HomeView.js | 8 +- src/views/host/HostDetailsView.js | 18 +- src/views/host/HostListView.js | 8 +- src/views/jobs/JobDetailsView.js | 17 +- src/views/jobs/JobListView.js | 3 +- src/views/jobs/JobLogAccessControl.js | 8 +- src/views/login/LoginView.js | 9 +- src/views/records/RecordDetailsView.js | 12 +- src/views/records/RecordListView.js | 8 +- src/views/statistics/StatisticsView.js | 9 +- 32 files changed, 451 insertions(+), 787 deletions(-) create mode 100644 src/api/UserProvider.js delete mode 100644 src/components/navigation/GlobalAppBar/GlobalAppBar.js delete mode 100644 src/components/navigation/GlobalAppBar/index.js create mode 100644 src/components/navigation/NavigationMenu/LoginControls.js create mode 100644 src/components/navigation/NavigationMenu/NavigationMenu.js create mode 100644 src/components/navigation/NavigationMenu/index.js diff --git a/src/App.js b/src/App.js index 95405938..4eaf5114 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useContext, useEffect } from "react"; import "./App.css"; import { Navigate, Route, Routes, BrowserRouter } from "react-router-dom"; import { StyledEngineProvider } from "@mui/material"; @@ -7,10 +7,11 @@ import { CssBaseline } from "@mui/material"; import { theme } from "./style/Theme"; import { IOCListView } from "./views/IOC/IOCListView"; import { HomeAccessControl } from "./views/home/HomeAccessControl"; -import { GlobalAppBar } from "./components/navigation/GlobalAppBar/GlobalAppBar"; +import NavigationMenu from "./components/navigation/NavigationMenu"; import { IOCDetailsAccessControl } from "./views/IOC/IOCDetailsAccessControl"; import { JobDetailsAccessControl } from "./views/jobs/JobDetailsAccessControl"; -import { APIProvider, UserProvider } from "./api/SwaggerApi"; +import { APIProvider } from "./api/SwaggerApi"; +import { UserProvider } from "./api/UserProvider"; import { HostListView } from "./views/host/HostListView"; import { HostDetailsAccessControl } from "./views/host/HostDetailsAccessControl"; import { StatisticsView } from "./views/statistics/StatisticsView"; @@ -19,18 +20,18 @@ import { SnackbarProvider } from "notistack"; import TokenRenew from "./components/auth/TokenRenew"; import { NotificationProvider } from "./components/common/notification/Notifications"; import NotFound from "./components/navigation/NotFound"; -import { applicationSubTitle } from "./components/common/Helper"; import { AppErrorBoundary } from "./components/navigation/ErrorBoundary/ErrorBoundary"; import { LoginView } from "./views/login/LoginView"; import { JobLogAccessControl } from "./views/jobs/JobLogAccessControl"; import { RecordListView } from "./views/records/RecordListView"; import { RecordDetailsView } from "./views/records/RecordDetailsView"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; +import { applicationTitle } from "./components/common/Helper"; // setting up the application (TAB)title function App() { - useEffect(() => { - document.title = "CE deploy & monitor" + applicationSubTitle(); - }, []); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle()), [setTitle]); return ( <AppErrorBoundary> @@ -46,7 +47,7 @@ function App() { <UserProvider> <TokenRenew /> <NotificationProvider> - <GlobalAppBar> + <NavigationMenu> <Routes> <Route path="/" @@ -117,7 +118,7 @@ function App() { exact /> </Routes> - </GlobalAppBar> + </NavigationMenu> </NotificationProvider> </UserProvider> </APIProvider> diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js index c92d1363..cf1c3b10 100644 --- a/src/api/SwaggerApi.js +++ b/src/api/SwaggerApi.js @@ -9,6 +9,7 @@ import SwaggerClient from "swagger-client"; import { createContext, useContext, useEffect, useState } from "react"; import { CircularProgress } from "@mui/material"; import { useCustomSnackbar } from "../components/common/snackbar/Snackbar"; +import { userContext } from "@ess-ics/ce-ui-common"; const createSwaggerClient = async () => { const swaggerOptions = { @@ -38,87 +39,6 @@ export function useAPI() { } export const apiContext = createContext(null); -export const userContext = createContext({ - user: null, - userRoles: [], - getUserRoles: () => {}, - login: () => {}, - logout: () => {} -}); - -export function UserProvider({ children }) { - function onLoginError(message) { - setLoginError(message); - } - - const [user, getUser, resetUser, userLoading] = useUser(); - const [loginError, setLoginError] = useState(); - const [loginResponse, loginFcn /* reset*/, , loginLoading] = - useLogin(onLoginError); - const [logoutFcn, logoutLoading] = useLogout(); - const [initialized, setInitialized] = useState(false); - - const resetLoginError = useCallback( - () => setLoginError(null), - [setLoginError] - ); - - useEffect(getUser, [loginResponse, getUser]); - - const logout = useCallback(async () => { - resetUser(); - logoutFcn(); - }, [resetUser, logoutFcn]); - - const login = useCallback( - (username, password) => { - loginFcn(username, password); - }, - [loginFcn] - ); - - const loading = Boolean(loginLoading || userLoading || logoutLoading); - - const createValue = useCallback(() => { - return { - user: user?.userInfo, - userRoles: user?.userRoles, - getUser, - login, - loginError, - resetLoginError, - logout - }; - }, [ - user?.userInfo, - user?.userRoles, - getUser, - login, - loginError, - resetLoginError, - logout - ]); - - const [value, setValue] = useState(createValue()); - - useEffect(() => { - if (!loading) { - setInitialized(true); - } - }, [loading]); - - useEffect(() => { - if (!loading) { - setValue(createValue()); - } - }, [loading, setValue, createValue]); - - return ( - initialized && ( - <userContext.Provider value={value}>{children}</userContext.Provider> - ) - ); -} export function APIProvider({ children }) { const [api] = useAPI(); diff --git a/src/api/UserProvider.js b/src/api/UserProvider.js new file mode 100644 index 00000000..1ac4a6a9 --- /dev/null +++ b/src/api/UserProvider.js @@ -0,0 +1,76 @@ +import React from "react"; +import { useCallback, useEffect, useState } from "react"; +import { useLogin, useLogout, useUser } from "./SwaggerApi"; +import { userContext } from "@ess-ics/ce-ui-common"; + +export function UserProvider({ children }) { + function onLoginError(message) { + setLoginError(message); + } + + const [user, getUser, resetUser, userLoading] = useUser(); + const [loginError, setLoginError] = useState(); + const [loginResponse, loginFcn /* reset*/, , loginLoading] = + useLogin(onLoginError); + const [logoutFcn, logoutLoading] = useLogout(); + const [initialized, setInitialized] = useState(false); + + const resetLoginError = useCallback( + () => setLoginError(null), + [setLoginError] + ); + + useEffect(getUser, [loginResponse, getUser]); + + const logout = useCallback(async () => { + resetUser(); + logoutFcn(); + }, [resetUser, logoutFcn]); + + const login = useCallback( + (username, password) => { + loginFcn(username, password); + }, + [loginFcn] + ); + + const loading = Boolean(loginLoading || userLoading || logoutLoading); + + const createValue = useCallback(() => { + return { + user: user?.userInfo, + userRoles: user?.userRoles, + login, + loginError, + resetLoginError, + logout + }; + }, [ + user?.userInfo, + user?.userRoles, + login, + loginError, + resetLoginError, + logout + ]); + + const [value, setValue] = useState(createValue()); + + useEffect(() => { + if (!loading) { + setInitialized(true); + } + }, [loading]); + + useEffect(() => { + if (!loading) { + setValue(createValue()); + } + }, [loading, setValue, createValue]); + + return ( + initialized && ( + <userContext.Provider value={value}>{children}</userContext.Provider> + ) + ); +} diff --git a/src/api/UserProvider.spec.js b/src/api/UserProvider.spec.js index b8bcfe23..7eab7552 100644 --- a/src/api/UserProvider.spec.js +++ b/src/api/UserProvider.spec.js @@ -1,5 +1,7 @@ import React, { useContext } from "react"; -import { APIProvider, userContext, UserProvider } from "./SwaggerApi"; +import { APIProvider } from "./SwaggerApi"; +import { UserProvider } from "./UserProvider"; +import { userContext } from "@ess-ics/ce-ui-common"; import { SnackbarProvider } from "notistack"; function AppHarness({ children }) { diff --git a/src/components/IOC/IOCManage/IOCManage.js b/src/components/IOC/IOCManage/IOCManage.js index 4e0e6220..5001a0e2 100644 --- a/src/components/IOC/IOCManage/IOCManage.js +++ b/src/components/IOC/IOCManage/IOCManage.js @@ -7,9 +7,9 @@ import { SimpleModal } from "../../common/SimpleModal/SimpleModal"; import { useUpdateAndDeployIoc, useCreateUndeployment, - useJobById, - userContext + useJobById } from "../../../api/SwaggerApi"; +import { userContext } from "@ess-ics/ce-ui-common"; import AlertMessages from "../AlertMessages"; import { SimpleAccordion } from "../../common/Accordion/SimpleAccordion"; import AccessControl from "../../auth/AccessControl"; diff --git a/src/components/auth/AccessControl/AccessControl.js b/src/components/auth/AccessControl/AccessControl.js index b4c81228..2f64ce61 100644 --- a/src/components/auth/AccessControl/AccessControl.js +++ b/src/components/auth/AccessControl/AccessControl.js @@ -1,5 +1,5 @@ import React, { useContext } from "react"; -import { userContext } from "../../../api/SwaggerApi"; +import { userContext } from "@ess-ics/ce-ui-common"; import AccessDenied from "../AccessDenied"; const checkPermissions = (userRoles, allowedRoles) => { diff --git a/src/components/auth/TokenRenew/TokenRenew.js b/src/components/auth/TokenRenew/TokenRenew.js index 536266fa..47b3e94e 100644 --- a/src/components/auth/TokenRenew/TokenRenew.js +++ b/src/components/auth/TokenRenew/TokenRenew.js @@ -1,5 +1,6 @@ import { useContext, useCallback } from "react"; -import { useRenewToken, userContext } from "../../../api/SwaggerApi"; +import { useRenewToken } from "../../../api/SwaggerApi"; +import { userContext } from "@ess-ics/ce-ui-common"; import { useSafePolling } from "../../../hooks/Polling"; export default function TokenRenew() { diff --git a/src/components/auth/TokenRenew/TokenRenew.spec.js b/src/components/auth/TokenRenew/TokenRenew.spec.js index ffae723c..30360a05 100644 --- a/src/components/auth/TokenRenew/TokenRenew.spec.js +++ b/src/components/auth/TokenRenew/TokenRenew.spec.js @@ -1,6 +1,7 @@ import { SnackbarProvider } from "notistack"; import React from "react"; -import { APIProvider, UserProvider } from "../../../api/SwaggerApi"; +import { APIProvider } from "../../../api/SwaggerApi"; +import { UserProvider } from "../../../api/UserProvider"; import TokenRenew from "."; function AppHarness({ children }) { diff --git a/src/components/common/Helper.js b/src/components/common/Helper.js index 360f960a..0b74973a 100644 --- a/src/components/common/Helper.js +++ b/src/components/common/Helper.js @@ -81,7 +81,7 @@ export function circularPalette(palette, index, opacity = 1.0) { return alpha(colors[((index % n) + n) % n], opacity); } -export function applicationSubTitle() { +function applicationSubTitle() { const title = `${window.ENVIRONMENT_TITLE}`; if (title && title !== "undefined") { @@ -91,6 +91,12 @@ export function applicationSubTitle() { return ""; } +export function applicationTitle(...breadcrumbs) { + return [`CE deploy & monitor ${applicationSubTitle()}`, ...breadcrumbs].join( + " / " + ); +} + export function initRequestParams(lazyParams, filter, columnSort) { let requestParams = { page: lazyParams.page, diff --git a/src/components/common/notification/Notifications.spec.js b/src/components/common/notification/Notifications.spec.js index 8db1db1b..a00a3ae0 100644 --- a/src/components/common/notification/Notifications.spec.js +++ b/src/components/common/notification/Notifications.spec.js @@ -1,10 +1,8 @@ import { SnackbarProvider } from "notistack"; import React, { useContext } from "react"; -import { - APIProvider, - userContext, - UserProvider -} from "../../../api/SwaggerApi"; +import { APIProvider } from "../../../api/SwaggerApi"; +import { UserProvider } from "../../../api/UserProvider"; +import { userContext } from "@ess-ics/ce-ui-common"; import { useEffectOnMount } from "../../../hooks/MountEffects"; import { notificationContext, NotificationProvider } from "./Notifications"; diff --git a/src/components/navigation/GlobalAppBar/GlobalAppBar.js b/src/components/navigation/GlobalAppBar/GlobalAppBar.js deleted file mode 100644 index 1799fd83..00000000 --- a/src/components/navigation/GlobalAppBar/GlobalAppBar.js +++ /dev/null @@ -1,590 +0,0 @@ -import React, { - createContext, - useCallback, - useContext, - useEffect, - useRef, - useState -} from "react"; -import { - AppBar, - Avatar, - Button, - Chip, - IconButton, - ListItem, - ListItemIcon, - ListItemText, - Toolbar, - Typography, - Menu, - MenuItem, - Tooltip, - Badge, - Popover, - Grid, - List, - Box, - Divider, - useTheme, - ListItemButton, - styled -} from "@mui/material"; -import { - LockOpen, - Home, - Assignment, - Storage, - TrendingUp, - SettingsInputComponent -} from "@mui/icons-material"; -import HelpIcon from "@mui/icons-material/Help"; -import { DeploymentStatusIcon } from "../../deployments/DeploymentIcons"; -import DeleteIcon from "@mui/icons-material/Delete"; -import NotificationsIcon from "@mui/icons-material/Notifications"; -import { MenuDrawer } from "../Menu/MenuDrawer"; -import { Link } from "react-router-dom"; -import { userContext } from "../../../api/SwaggerApi"; -import { notificationContext } from "../../common/notification/Notifications"; -import { useWindowDimensions } from "../../common/Helper"; -import { CCCEControlSymbol } from "../../../icons/CCCEControlSymbol"; -import { applicationSubTitle } from "../../common/Helper"; -import { LoginDialog } from "../../auth/Login"; -import { useRedirect } from "../../../hooks/Redirect"; - -const DivRoot = styled("div")(({ theme }) => ({ - flexGrow: 1, - paddingBottom: theme.spacing(2) -})); - -const StyledAppBar = styled(AppBar)(({ theme }) => ({ - // fixme: a hack to make the appBar stay on top of the drawer, should be between 1201 - 1299 - zIndex: 1250 -})); - -const IconButtonMenuButton = styled(IconButton)(({ theme }) => ({ - marginRight: theme.spacing(2) -})); - -const TypographyTitle = styled(Typography)(({ theme }) => ({ - flexGrow: 1 -})); - -const DividerHelpDivider = styled(Divider)(({ theme }) => ({ - marginTop: theme.spacing(5) -})); - -const ListItemIconMenuIcon = styled(ListItemIcon)(({ theme }) => ({ - marginLeft: theme.spacing(1) -})); - -const ListNotificationList = styled(List)(({ theme }) => ({ - overflow: "auto", - maxHeight: 360 -})); - -const IconButtonNotificationButton = styled(IconButton)(({ theme }) => ({ - marginRight: theme.spacing(2) -})); - -const DivContent = styled("div")(({ theme }) => ({ - marginTop: theme.spacing(1) -})); - -const ShiftContentLeft = styled("div")(({ theme }) => ({ - marginLeft: theme.drawer.widthClose -})); - -const ShiftContentRight = styled("div")(({ theme }) => ({ - marginLeft: theme.drawer.widthOpen -})); - -const InternalStyledMenu = styled(Menu)({ - "& .MuiPaper-root": { - border: "1px solid #d3d4d5" - } -}); -const StyledMenu = (props) => ( - <> - <div style={{ ...props.theme.mixins.toolbar }} /> - <InternalStyledMenu - elevation={0} - anchorOrigin={{ - vertical: "bottom", - horizontal: "center" - }} - transformOrigin={{ - vertical: "top", - horizontal: "center" - }} - {...props} - /> - </> -); - -function InfoMenuItem({ children }) { - return ( - <MenuItem - disabled - style={{ opacity: 1.0 }} - > - {children} - </MenuItem> - ); -} - -function RBACRoleInfo({ role }) { - return ( - <InfoMenuItem key={role}> - <Chip - label={role} - color="secondary" - /> - </InfoMenuItem> - ); -} - -export function LoginControls() { - const [loginFormOpen, setLoginFormOpen] = useState(false); - const { user, login, loginError, resetLoginError } = useContext(userContext); - const userRef = useRef(user); - const redirect = useRedirect(); - - const handleClose = useCallback(() => { - setLoginFormOpen(false); - }, []); - - // detect logout and redirect to login - useEffect(() => { - if (userRef.current && !user) { - redirect("/login", {}, true); - } - userRef.current = user; - }, [redirect, user]); - - useEffect(() => { - if (user) { - handleClose(); - } - }, [handleClose, user]); - - return user ? ( - <> - <NotificationMenu /> - <ProfileMenu /> - </> - ) : ( - <> - <Button - variant="outlined" - color="inherit" - onClick={() => { - resetLoginError(); - setLoginFormOpen(true); - }} - > - Login - </Button> - <LoginDialog - login={login} - open={loginFormOpen} - handleClose={handleClose} - error={loginError} - resetError={resetLoginError} - /> - </> - ); -} - -export function ButtonAppBar({ homeUrl, homeClick, title, button }) { - return ( - <DivRoot> - <StyledAppBar position="fixed"> - <Toolbar> - <IconButtonMenuButton - edge="start" - component={Link} - to={homeUrl} - onClick={homeClick} - color="inherit" - aria-label="menu" - size="large" - > - <Home /> - </IconButtonMenuButton> - <TypographyTitle - variant="h1" - noWrap - > - {title} - </TypographyTitle> - {button} - </Toolbar> - </StyledAppBar> - </DivRoot> - ); -} - -const defaultTitle = "CE deploy & monitor"; -export const GlobalAppBarContext = createContext({ - title: defaultTitle, - setButton: () => { - console.info("default GlobalAppBarContext.setButton does nothing."); - }, - setTitle: () => { - console.info("default GlobalAppBarContext.setTitle does nothing."); - } -}); - -function MenuListItem({ url, icon, text, tooltip }) { - const currentUrl = `${window.location}`; - return ( - <> - {url.includes("help") ? <DividerHelpDivider /> : <></>} - <Tooltip - title={tooltip ? text : ""} - placement="right" - arrow - > - <ListItemButton - component={Link} - to={url} - selected={currentUrl.split("?")[0].endsWith(url)} - > - <ListItemIconMenuIcon>{icon}</ListItemIconMenuIcon> - <ListItemText - primary={text} - primaryTypographyProps={{ variant: "h5" }} - /> - </ListItemButton> - </Tooltip> - </> - ); -} - -function MenuListItems({ menuItems, drawerOpen }) { - return ( - <> - {menuItems.map(({ text, url, icon }) => ( - <MenuListItem - key={text} - tooltip={!drawerOpen} - url={url} - icon={icon} - text={text} - /> - ))} - </> - ); -} - -export function UserInfo({ user }) { - return ( - <ListItem> - <ListItemIcon> - <Avatar - src={user.avatar} - variant="rounded" - /> - </ListItemIcon> - <ListItemText - primary={user.fullName} - secondary={user.email} - /> - </ListItem> - ); -} - -export function ProfileMenu() { - const theme = useTheme(); - const { user, userRoles, logout } = useContext(userContext); - const [anchorEl, setAnchorEl] = useState(null); - - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - return ( - <> - <Avatar - src={user.avatar} - variant="circular" - onClick={handleClick} - aria-controls="profile-menu" - aria-haspopup="true" - /> - <StyledMenu - id="profile-menu" - anchorEl={anchorEl} - keepMounted - open={Boolean(anchorEl)} - onClose={handleClose} - theme={theme} - > - {userRoles - .filter((role) => role.includes("DeploymentTool")) - .map((role) => ( - <RBACRoleInfo - role={role} - key={role} - /> - ))} - <MenuItem - onClick={() => { - handleClose(); - logout(); - }} - > - <ListItemIcon> - <LockOpen fontSize="small" /> - </ListItemIcon> - <ListItemText - primary="Logout" - primaryTypographyProps={{ variant: "h5" }} - /> - </MenuItem> - </StyledMenu> - </> - ); -} - -const StyledListItemButton = styled(ListItemButton)({ - "& .MuiListItemButton-root": { - margin: 0, - paddingLeft: 10 - } -}); - -const StyledListItemIcon = styled(ListItemIcon)({ - "& .MuiListItemIcon-root": { - minWidth: 0, - marginRight: 12 - } -}); - -export function NotificationMenu() { - const [anchorEl, setAnchorEl] = useState(null); - const { user } = useContext(userContext); - const { notifications, clearNotifications } = useContext(notificationContext); - - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - function haveNotification() { - return ( - <> - <Grid - item - xs={12} - > - <ListNotificationList> - {notifications.map((notification) => { - return ( - <> - {notification.link ? ( - <StyledListItemButton - component={Link} - to={notification.link} - onClick={() => { - handleClose(); - }} - > - <StyledListItemIcon> - <DeploymentStatusIcon status={notification.status} /> - </StyledListItemIcon> - <Typography>{notification.message}</Typography> - </StyledListItemButton> - ) : ( - <StyledListItemButton key={notification.message}> - <StyledListItemIcon> - <DeploymentStatusIcon status={notification.status} /> - </StyledListItemIcon> - <Typography>{notification.message}</Typography> - </StyledListItemButton> - )} - </> - ); - })} - </ListNotificationList> - </Grid> - <Grid - item - xs={1} - > - <Box - display="flex" - justifyContent="flex-end" - > - <IconButton - color="inherit" - onClick={() => { - clearNotifications(); - handleClose(); - }} - size="large" - > - <Tooltip - title="Clear all notifications" - arrow - > - <DeleteIcon fontSize="small" /> - </Tooltip> - </IconButton> - </Box> - </Grid> - </> - ); - } - - function noNotification() { - return ( - <Typography - inset="true" - style={{ margin: "4px" }} - > - You have no notifications! - </Typography> - ); - } - - return ( - <> - {user ? ( - <> - <IconButtonNotificationButton - color="inherit" - onClick={handleClick} - size="large" - > - <Badge - badgeContent={notifications.length} - color="secondary" - > - <NotificationsIcon /> - </Badge> - </IconButtonNotificationButton> - <Popover - open={Boolean(anchorEl)} - anchorEl={anchorEl} - onClose={handleClose} - anchorOrigin={{ - vertical: "bottom", - horizontal: "center" - }} - transformOrigin={{ - vertical: "top", - horizontal: "center" - }} - > - <Grid - container - spacing={0} - justifyContent="flex-end" - > - {notifications.length > 0 ? haveNotification() : noNotification()} - </Grid> - </Popover> - </> - ) : ( - <></> - )} - </> - ); -} - -export const DRAWER_OPEN = "DRAWER_OPEN"; - -const defaultButton = <LoginControls />; - -export function GlobalAppBar({ children }) { - const theme = useTheme(); - const [button, setButton] = useState(defaultButton); - const [title, setTitle] = useState(defaultTitle); - const [value] = useState({ setButton, setTitle }); - const { width } = useWindowDimensions(); - const [drawerOpen, setDrawerOpen] = useState(isDrawerOpen() ?? width > 1730); - - useEffect(() => { - if (isDrawerOpen()) { - setDrawerOpen(width > 1730); - } - }, [width, setDrawerOpen]); - - const toggleDrawer = () => { - storeDrawerOpen(!drawerOpen); - setDrawerOpen(!drawerOpen); - }; - - function isDrawerOpen() { - return JSON.parse(localStorage.getItem(DRAWER_OPEN)); - } - - function storeDrawerOpen(open) { - localStorage.setItem(DRAWER_OPEN, JSON.stringify(open)); - } - - const makeLink = (text, url, icon) => ({ text, url, icon }); - const menuItemsAll = [ - makeLink("Records", "/records", <SettingsInputComponent />), - makeLink("IOCs", "/iocs", <CCCEControlSymbol />), - makeLink("IOC hosts", "/hosts", <Storage />), - makeLink("Log", "/jobs", <Assignment />), - makeLink("Statistics", "/statistics", <TrendingUp />), - makeLink("Help", "/help", <HelpIcon />) - ]; - - return ( - <> - <ButtonAppBar - homeUrl="/home" - title={title} - button={button} - /> - <div> - <MenuDrawer - open={drawerOpen} - toggleDrawer={toggleDrawer} - > - <MenuListItems - menuItems={menuItemsAll} - drawerOpen={drawerOpen} - /> - </MenuDrawer> - </div> - <div> - <GlobalAppBarContext.Provider value={value}> - <div style={{ ...theme.mixins.toolbar }} /> - <DivContent> - {drawerOpen ? ( - <ShiftContentRight>{children}</ShiftContentRight> - ) : ( - <ShiftContentLeft>{children}</ShiftContentLeft> - )} - </DivContent> - </GlobalAppBarContext.Provider> - </div> - </> - ); -} - -export function useGlobalAppBar(initTitle, initButton = defaultButton) { - const [button, setButton] = useState(initButton); - const [title, setTitle] = useState(initTitle); - const appBar = useContext(GlobalAppBarContext); - useEffect(() => { - appBar.setTitle( - "CE deploy & monitor " + applicationSubTitle() + " / " + title - ); - appBar.setButton(button); - }, [appBar, title, button]); - return { setButton, setTitle }; -} diff --git a/src/components/navigation/GlobalAppBar/index.js b/src/components/navigation/GlobalAppBar/index.js deleted file mode 100644 index 3667500f..00000000 --- a/src/components/navigation/GlobalAppBar/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import { - GlobalAppBar, - useGlobalAppBar, - LoginControls, - ButtonAppBar, - GlobalAppBarContext, - UserInfo, - ProfileMenu, - NotificationMenu, - DRAWER_OPEN -} from "./GlobalAppBar"; - -export { - GlobalAppBar, - useGlobalAppBar, - LoginControls, - ButtonAppBar, - GlobalAppBarContext, - UserInfo, - ProfileMenu, - NotificationMenu, - DRAWER_OPEN -}; -export default GlobalAppBar; diff --git a/src/components/navigation/LoginSuggester/LoginSuggester.js b/src/components/navigation/LoginSuggester/LoginSuggester.js index 8b2348bf..6b6ae6f2 100644 --- a/src/components/navigation/LoginSuggester/LoginSuggester.js +++ b/src/components/navigation/LoginSuggester/LoginSuggester.js @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useCallback, useState } from "react"; import { CookiesProvider } from "react-cookie"; import { useCookies } from "react-cookie"; -import { userContext } from "../../../api/SwaggerApi"; +import { userContext } from "@ess-ics/ce-ui-common"; import { useEffectOnMount } from "../../../hooks/MountEffects"; // import { useRedirect } from "../../../hooks/Redirect"; import { useNavigate } from "react-router-dom"; diff --git a/src/components/navigation/NavigationMenu/LoginControls.js b/src/components/navigation/NavigationMenu/LoginControls.js new file mode 100644 index 00000000..cfdd5dbc --- /dev/null +++ b/src/components/navigation/NavigationMenu/LoginControls.js @@ -0,0 +1,114 @@ +import React, { + useRef, + useContext, + useCallback, + useEffect, + useState +} from "react"; +import { string } from "prop-types"; +import { Button, Avatar, Chip } from "@mui/material"; +import LockOpenIcon from "@mui/icons-material/LockOpen"; +import { + userContext, + LoginDialog, + IconMenuButton +} from "@ess-ics/ce-ui-common"; +import { useNavigate } from "react-router-dom"; + +const propTypes = { + user: string +}; + +export function ProfileMenu({ user }) { + const { userRoles, logout } = useContext(userContext); + + const objUserRoles = userRoles + ?.filter((role) => role.includes("DeploymentTool")) + ?.map((role) => ({ + text: ( + <Chip + label={role} + color="primary" + /> + ) + })); + + const profileMenuProps = { + /* + If editing this id, be sure to edit the check for this in + IconMenuButton.js in ce-ui-common repo as well to + match + */ + id: "user-login-profile-", + icon: ( + <Avatar + alt={user.fullName} + src={user.avatar} + sx={{ + width: (theme) => theme.spacing(3), + height: (theme) => theme.spacing(3) + }} + /> + ), + menuItems: [ + ...objUserRoles, + { + text: "Logout", + action: () => logout(), + icon: <LockOpenIcon fontSize="small" /> + } + ] + }; + + return <IconMenuButton {...profileMenuProps} />; +} + +export function LoginControls() { + const [loginFormOpen, setLoginFormOpen] = useState(false); + const { user, login, loginError, resetLoginError } = useContext(userContext); + const userRef = useRef(user); + + const navigate = useNavigate(); + const handleClose = useCallback(() => { + setLoginFormOpen(false); + }, []); + + // detect logout and redirect to login + useEffect(() => { + if (userRef.current && !user) { + navigate("/login"); + } + userRef.current = user; + }, [user, navigate]); + + useEffect(() => { + if (user) { + handleClose(); + } + }, [handleClose, user]); + + return user ? ( + <ProfileMenu user={user} /> + ) : ( + <> + <Button + color="inherit" + onClick={() => { + resetLoginError(); + setLoginFormOpen(true); + }} + > + Login + </Button> + <LoginDialog + login={login} + open={loginFormOpen} + handleClose={handleClose} + error={loginError} + resetError={resetLoginError} + /> + </> + ); +} + +ProfileMenu.propTypes = propTypes.menuListItem; diff --git a/src/components/navigation/NavigationMenu/NavigationMenu.js b/src/components/navigation/NavigationMenu/NavigationMenu.js new file mode 100644 index 00000000..02d98a7f --- /dev/null +++ b/src/components/navigation/NavigationMenu/NavigationMenu.js @@ -0,0 +1,130 @@ +import { GlobalAppBar, IconMenuButton } from "@ess-ics/ce-ui-common"; +import { + Assignment, + HelpCenter, + Home, + SettingsInputComponent, + Storage, + TrendingUp +} from "@mui/icons-material"; +import { + Box, + Divider, + IconButton, + ListItemButton, + ListItemIcon, + ListItemText, + Tooltip +} from "@mui/material"; +import React, { Fragment, useState } from "react"; +import { useNavigate } from "react-router"; +import { applicationTitle } from "../../common/Helper"; +import { LoginControls } from "./LoginControls"; +import { Link } from "react-router-dom"; +import { CCCEControlSymbol } from "../../../icons/CCCEControlSymbol"; + +function MenuListItem({ url, icon, text, tooltip }) { + const currentUrl = `${window.location}`; + + return ( + <Tooltip + title={tooltip ? text : ""} + placement="right" + arrow + > + <ListItemButton + component={Link} + to={url} + selected={currentUrl.split("?")[0].endsWith(url)} + > + <ListItemIcon>{icon}</ListItemIcon> + <ListItemText + primary={text} + primaryTypographyProps={{ variant: "button" }} + /> + </ListItemButton> + </Tooltip> + ); +} + +function MenuListItems({ menuItems, drawerOpen }) { + return ( + <Box sx={{ paddingTop: 3 }}> + {menuItems.map(({ text, url, icon }, index) => ( + <Fragment key={index}> + {typeof (text && url && icon) === "undefined" ? ( + <Divider sx={{ marginTop: 5 }} /> + ) : ( + <MenuListItem + tooltip={!drawerOpen} + url={url} + icon={icon} + text={text} + /> + )} + </Fragment> + ))} + </Box> + ); +} +const makeLink = (text, url, icon) => ({ text, url, icon }); +const menuItemsAll = [ + makeLink("Records", "/records", <SettingsInputComponent />), + makeLink("IOCs", "/iocs", <CCCEControlSymbol />), + makeLink("IOC hosts", "/hosts", <Storage />), + makeLink("Log", "/jobs", <Assignment />), + makeLink("Statistics", "/statistics", <TrendingUp />) +]; + +const NavigationMenu = ({ children }) => { + const [drawerOpen, setDrawerOpen] = useState(false); + const navigate = useNavigate(); + const goHome = () => { + navigate("/"); + }; + + const helpButtonProps = { + icon: <HelpCenter />, + menuItems: [ + { + text: "Help", + action: () => navigate("/help") + } + ] + }; + + const args = { + defaultHomeButton: ( + <IconButton + edge="start" + color="inherit" + onClick={goHome} + > + <Home /> + </IconButton> + ), + defaultTitle: applicationTitle(), + defaultActionButton: <LoginControls />, + defaultOpen: false, + widthOpen: "240px", + widthClosed: "57px", + defaultHelpButton: <IconMenuButton {...helpButtonProps} /> + }; + + return ( + <GlobalAppBar + getDrawerOpen={setDrawerOpen} + menuItems={ + <MenuListItems + drawerOpen={drawerOpen} + menuItems={menuItemsAll} + /> + } + {...args} + > + {children} + </GlobalAppBar> + ); +}; + +export default NavigationMenu; diff --git a/src/components/navigation/NavigationMenu/index.js b/src/components/navigation/NavigationMenu/index.js new file mode 100644 index 00000000..e7d2ee67 --- /dev/null +++ b/src/components/navigation/NavigationMenu/index.js @@ -0,0 +1,4 @@ +import NavigationMenu from "./NavigationMenu"; + +export { NavigationMenu }; +export default NavigationMenu; diff --git a/src/mocks/AppHarness.js b/src/mocks/AppHarness.js index 51fdee6b..d2de4995 100644 --- a/src/mocks/AppHarness.js +++ b/src/mocks/AppHarness.js @@ -3,9 +3,10 @@ import { SnackbarProvider } from "notistack"; import { Container, StyledEngineProvider } from "@mui/material"; import { ThemeProvider } from "@mui/material/styles"; import { theme } from "../style/Theme"; -import { APIProvider, UserProvider } from "../api/SwaggerApi"; +import { APIProvider } from "../api/SwaggerApi"; +import { UserProvider } from "../api/UserProvider"; import { NotificationProvider } from "../components/common/notification/Notifications"; -import { GlobalAppBar } from "../components/navigation/GlobalAppBar/GlobalAppBar"; +import NavigationMenu from "../components/navigation/NavigationMenu"; import { MemoryRouter } from "react-router-dom"; export function AppHarness({ children, initialHistory = ["/"] }) { @@ -20,9 +21,9 @@ export function AppHarness({ children, initialHistory = ["/"] }) { <APIProvider> <UserProvider> <NotificationProvider> - <GlobalAppBar> + <NavigationMenu> <Container>{children}</Container> - </GlobalAppBar> + </NavigationMenu> </NotificationProvider> </UserProvider> </APIProvider> diff --git a/src/style/Theme.js b/src/style/Theme.js index 10aff290..501c1a0e 100644 --- a/src/style/Theme.js +++ b/src/style/Theme.js @@ -1,9 +1,10 @@ import { createTheme } from "@mui/material/styles"; import { chartColors } from "./Palette"; +import { theme as ceuiTheme } from "@ess-ics/ce-ui-common"; -let theme = createTheme({}); +let theme = createTheme(ceuiTheme, {}); -theme = createTheme({ +theme = createTheme(theme, { components: { MuiWithWidth: { defaultProps: { @@ -50,10 +51,6 @@ theme = createTheme({ } } }, - drawer: { - widthOpen: 240, - widthClose: 75 - }, palette: { primary: { main: "#0099dc" diff --git a/src/views/IOC/IOCDetailsView.js b/src/views/IOC/IOCDetailsView.js index e2ac6b85..ecb584cb 100644 --- a/src/views/IOC/IOCDetailsView.js +++ b/src/views/IOC/IOCDetailsView.js @@ -1,14 +1,17 @@ import { Grid, Paper, Tab, Tabs, Typography, IconButton } from "@mui/material"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useContext, useEffect, useState } from "react"; import { useOngoingCommand, useOperationsSearch } from "../../api/SwaggerApi"; import { IOCLiveStatus } from "../../components/IOC/IOCLiveStatus"; import { IOCManage } from "../../components/IOC/IOCManage"; import { useNavigate } from "react-router-dom"; import IOCAdmin from "../../components/IOC/IOCAdmin"; import AccessControl from "../../components/auth/AccessControl"; -import { initRequestParams } from "../../components/common/Helper"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { + applicationTitle, + initRequestParams +} from "../../components/common/Helper"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { useSafePolling } from "../../hooks/Polling"; import useUrlState from "@ahooksjs/use-url-state"; import { @@ -92,10 +95,11 @@ export function IOCDetailsView({ ioc, getIOC, loading }) { [getIOC] ); - const { setTitle } = useGlobalAppBar("IOC Details"); - + const { setTitle } = useContext(GlobalAppBarContext); useEffect(() => { - ioc && setTitle("IOC Details: " + ioc.namingName); + if (ioc) { + setTitle(applicationTitle(`IOC Details: ${ioc.namingName}`)); + } }, [ioc, setTitle]); const statusTab = ( diff --git a/src/views/IOC/IOCListView.js b/src/views/IOC/IOCListView.js index fc9dcf2a..7f506a6a 100644 --- a/src/views/IOC/IOCListView.js +++ b/src/views/IOC/IOCListView.js @@ -11,10 +11,13 @@ import { import { styled } from "@mui/material/styles"; import { RootContainer } from "../../components/common/Container/RootContainer"; import React, { useState, useEffect, useContext, useCallback } from "react"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext, userContext } from "@ess-ics/ce-ui-common"; import { IOCAsyncList } from "../../components/IOC/IOCAsyncList"; -import { useIOCSearch, userContext } from "../../api/SwaggerApi"; -import { initRequestParams } from "../../components/common/Helper"; +import { useIOCSearch } from "../../api/SwaggerApi"; +import { + applicationTitle, + initRequestParams +} from "../../components/common/Helper"; import { SearchBar } from "../../components/common/SearchBar/SearchBar"; import AccessControl from "../../components/auth/AccessControl"; import useUrlState from "@ahooksjs/use-url-state"; @@ -119,8 +122,8 @@ export function IOCListView() { getIocs(requestParams); }, [getIocs, lazyParams, user, state, deploymentStatus]); - const title = "IOCs"; - useGlobalAppBar(title); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("IOCs")), [setTitle]); const setSearch = useCallback( (query) => { diff --git a/src/views/help/HelpView.js b/src/views/help/HelpView.js index 374a15e4..00cff802 100644 --- a/src/views/help/HelpView.js +++ b/src/views/help/HelpView.js @@ -1,10 +1,10 @@ -import React from "react"; +import React, { useContext, useEffect } from "react"; import { styled } from "@mui/material/styles"; import { Paper, Link, Typography } from "@mui/material"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; import "./helpfile.css"; import { RootContainer } from "../../components/common/Container/RootContainer"; -import { ExternalLink } from "@ess-ics/ce-ui-common"; +import { ExternalLink, GlobalAppBarContext } from "@ess-ics/ce-ui-common"; +import { applicationTitle } from "../../components/common/Helper"; const PREFIX = "HelpView"; @@ -24,8 +24,8 @@ const StyledRootContainer = styled(RootContainer)(({ theme }) => ({ })); export function HelpView() { - const title = "Help"; - useGlobalAppBar(title); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("Help")), [setTitle]); return ( <StyledRootContainer> diff --git a/src/views/home/HomeAccessControl.js b/src/views/home/HomeAccessControl.js index 28ca285b..3f48d052 100644 --- a/src/views/home/HomeAccessControl.js +++ b/src/views/home/HomeAccessControl.js @@ -1,11 +1,13 @@ -import React from "react"; +import React, { useContext, useEffect } from "react"; import AccessControl from "../../components/auth/AccessControl"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; import { HomeVisitorView } from "./HomeVisitorView"; import { HomeView } from "./HomeView"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; +import { applicationTitle } from "../../components/common/Helper"; export function HomeAccessControl() { - useGlobalAppBar("Home"); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("Home")), [setTitle]); return ( <AccessControl diff --git a/src/views/home/HomeView.js b/src/views/home/HomeView.js index 613c4c80..2670db51 100644 --- a/src/views/home/HomeView.js +++ b/src/views/home/HomeView.js @@ -1,7 +1,6 @@ import { Container, Grid, Paper, Button, Box, Typography } from "@mui/material"; import { styled } from "@mui/material/styles"; -import React, { useState } from "react"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import React, { useContext, useEffect, useState } from "react"; import { CreateIOC } from "../../components/IOC/CreateIOC"; import { KeyValueTable } from "../../components/common/KeyValueTable/KeyValueTable"; import { SimpleModal } from "../../components/common/SimpleModal/SimpleModal"; @@ -14,6 +13,8 @@ import { useOwnIocsWithAlarms } from "../../api/SwaggerApi"; import ReactMarkdown from "react-markdown"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; +import { applicationTitle } from "../../components/common/Helper"; const PREFIX = "HomeView"; const classes = { @@ -48,7 +49,8 @@ export function HomeView() { page: 0 }); - useGlobalAppBar("Home"); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("Home")), [setTitle]); const closeModal = () => { setIOCFormOpen(false); diff --git a/src/views/host/HostDetailsView.js b/src/views/host/HostDetailsView.js index c0e1da60..d3543cb4 100644 --- a/src/views/host/HostDetailsView.js +++ b/src/views/host/HostDetailsView.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useContext } from "react"; import { styled } from "@mui/material/styles"; import { Paper, @@ -17,9 +17,11 @@ import { IOCAsyncList } from "../../components/IOC/IOCAsyncList"; import { KeyValueTable } from "../../components/common/KeyValueTable/KeyValueTable"; import { LokiPanel } from "../../components/common/Loki/LokiPanel"; import { useNavigate } from "react-router-dom"; -import { initRequestParams } from "../../components/common/Helper"; +import { + applicationTitle, + initRequestParams +} from "../../components/common/Helper"; import AccessControl from "../../components/auth/AccessControl"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; import { useHost } from "../../api/SwaggerApi"; import AlertMessages from "../../components/IOC/AlertMessages"; import useUrlState from "@ahooksjs/use-url-state"; @@ -27,6 +29,7 @@ import { serialize, deserialize } from "../../components/common/URLState/URLState"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; const PREFIX = "HostDetailsView"; @@ -123,12 +126,11 @@ export function HostDetailsView({ id }) { navigate(-1); }; - const { setTitle } = useGlobalAppBar("Host Details"); - + const { setTitle } = useContext(GlobalAppBarContext); useEffect(() => { - host && - host.csEntryHost && - setTitle("Host Details: " + host.csEntryHost.name); + if (host && host.csEntryHost) { + setTitle(applicationTitle("Host Details: " + host.csEntryHost.name)); + } }, [host, setTitle]); return ( diff --git a/src/views/host/HostListView.js b/src/views/host/HostListView.js index 3eaafe4e..5571dd31 100644 --- a/src/views/host/HostListView.js +++ b/src/views/host/HostListView.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useContext } from "react"; import { styled } from "@mui/material/styles"; import { Container, @@ -15,8 +15,9 @@ import { RootContainer } from "../../components/common/Container/RootContainer"; import { HostList } from "../../components/host/HostList"; import { HostTable } from "../../components/host/HostTable"; import { useCSEntrySearch } from "../../api/SwaggerApi"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { + applicationTitle, initRequestParams, transformHostQuery } from "../../components/common/Helper"; @@ -43,7 +44,8 @@ const StyledRootContainer = styled(RootContainer)(({ theme }) => ({ const NoAccess = () => <></>; export function HostListView() { - useGlobalAppBar("IOC hosts"); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("IOC hosts")), [setTitle]); const [hosts, getHosts /* reset*/, , loading] = useCSEntrySearch(); const [state, setState] = useUrlState( diff --git a/src/views/jobs/JobDetailsView.js b/src/views/jobs/JobDetailsView.js index 1c746f57..9c41845f 100644 --- a/src/views/jobs/JobDetailsView.js +++ b/src/views/jobs/JobDetailsView.js @@ -1,9 +1,10 @@ -import React, { useEffect } from "react"; +import React, { useContext, useEffect } from "react"; import { IconButton, Paper } from "@mui/material"; import { JobDetails } from "../../components/Job/JobDetails"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import { useNavigate } from "react-router-dom"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; +import { applicationTitle } from "../../components/common/Helper"; export function JobDetailsView({ operation, job }) { const navigate = useNavigate(); @@ -12,11 +13,13 @@ export function JobDetailsView({ operation, job }) { navigate(-1); }; - const { setTitle } = useGlobalAppBar("Job Details"); - - useEffect(() => { - operation && setTitle("Job Details: " + operation.iocName); - }, [operation, setTitle]); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect( + () => + operation && + setTitle(applicationTitle(`Job Details: ${operation.iocName}`)), + [setTitle, operation] + ); return ( <Paper> diff --git a/src/views/jobs/JobListView.js b/src/views/jobs/JobListView.js index 3ae3b4ec..ab49c9a9 100644 --- a/src/views/jobs/JobListView.js +++ b/src/views/jobs/JobListView.js @@ -12,7 +12,8 @@ import { } from "@mui/material"; import Tabs from "@mui/material/Tabs"; import Tab from "@mui/material/Tab"; -import { useOperationsSearch, userContext } from "../../api/SwaggerApi"; +import { useOperationsSearch } from "../../api/SwaggerApi"; +import { userContext } from "@ess-ics/ce-ui-common"; import { initRequestParams } from "../../components/common/Helper"; import { useEffect } from "react"; import { SearchBar } from "../../components/common/SearchBar/SearchBar"; diff --git a/src/views/jobs/JobLogAccessControl.js b/src/views/jobs/JobLogAccessControl.js index 8ece0bd2..a6980610 100644 --- a/src/views/jobs/JobLogAccessControl.js +++ b/src/views/jobs/JobLogAccessControl.js @@ -1,11 +1,13 @@ -import React from "react"; +import React, { useContext, useEffect } from "react"; import { RootContainer } from "../../components/common/Container/RootContainer"; import AccessControl from "../../components/auth/AccessControl"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { JobListView } from "./JobListView"; +import { applicationTitle } from "../../components/common/Helper"; export function JobLogAccessControl() { - useGlobalAppBar("Log"); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("Log")), [setTitle]); return ( <RootContainer> diff --git a/src/views/login/LoginView.js b/src/views/login/LoginView.js index 602a5846..9a59f667 100644 --- a/src/views/login/LoginView.js +++ b/src/views/login/LoginView.js @@ -1,13 +1,16 @@ import { Card, CardContent, Grid } from "@mui/material"; import React, { useContext, useEffect } from "react"; import { useLocation } from "react-router-dom"; -import { userContext } from "../../api/SwaggerApi"; import { LoginForm } from "../../components/auth/Login"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext, userContext } from "@ess-ics/ce-ui-common"; import { useRedirect } from "../../hooks/Redirect"; +import { applicationTitle } from "../../components/common/Helper"; export function LoginView() { - useGlobalAppBar("Login", null); + const { setTitle } = useContext(GlobalAppBarContext); + + useEffect(() => setTitle(applicationTitle("Login")), [setTitle]); + const redirect = useRedirect(); const { user, login, loginError, resetLoginError } = useContext(userContext); diff --git a/src/views/records/RecordDetailsView.js b/src/views/records/RecordDetailsView.js index 7b64c25e..9d16cb5a 100644 --- a/src/views/records/RecordDetailsView.js +++ b/src/views/records/RecordDetailsView.js @@ -1,4 +1,4 @@ -import React, { useEffect, useCallback, useState } from "react"; +import React, { useEffect, useCallback, useState, useContext } from "react"; import { useRecord } from "../../api/SwaggerApi"; import { Paper, @@ -10,11 +10,11 @@ import { import { Link as ReactRouterLink } from "react-router-dom"; import { RootContainer } from "../../components/common/Container/RootContainer"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { SimpleAccordion } from "../../components/common/Accordion/SimpleAccordion"; import { KeyValueTable } from "../../components/common/KeyValueTable/KeyValueTable"; import { RecordBadge } from "../../components/records/RecordBadge"; -import { formatDate } from "../../components/common/Helper"; +import { applicationTitle, formatDate } from "../../components/common/Helper"; import { useParams } from "react-router-dom"; import { useNavigate } from "react-router-dom"; import { onFetchEntityError } from "../../components/common/Helper"; @@ -33,13 +33,13 @@ export function RecordDetailsView() { ); const navigate = useNavigate(); - const { setTitle } = useGlobalAppBar("Record Details"); + const { setTitle } = useContext(GlobalAppBarContext); useEffect(() => { if (record) { - setTitle("Record Details: " + record.name); + setTitle(applicationTitle(`Record Details: ${record.name}`)); } - }, [record, setTitle]); + }, [setTitle, record]); const handleClick = (event) => { navigate(-1); diff --git a/src/views/records/RecordListView.js b/src/views/records/RecordListView.js index 3d3725d6..879b8b3d 100644 --- a/src/views/records/RecordListView.js +++ b/src/views/records/RecordListView.js @@ -1,11 +1,13 @@ -import React from "react"; +import React, { useContext, useEffect } from "react"; import { Paper, Grid } from "@mui/material"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { RootContainer } from "../../components/common/Container/RootContainer"; import { RecordSearch } from "../../components/records/RecordSearch"; +import { applicationTitle } from "../../components/common/Helper"; export function RecordListView() { - useGlobalAppBar("Records"); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("Records")), [setTitle]); return ( <RootContainer> diff --git a/src/views/statistics/StatisticsView.js b/src/views/statistics/StatisticsView.js index 45cc3c2c..99bf22c4 100644 --- a/src/views/statistics/StatisticsView.js +++ b/src/views/statistics/StatisticsView.js @@ -1,7 +1,7 @@ -import React from "react"; +import React, { useContext, useEffect } from "react"; import { styled } from "@mui/material/styles"; import { Paper, Grid, useTheme, Typography } from "@mui/material"; -import { useGlobalAppBar } from "../../components/navigation/GlobalAppBar/GlobalAppBar"; +import { GlobalAppBarContext } from "@ess-ics/ce-ui-common"; import { RootContainer } from "../../components/common/Container/RootContainer"; import ActiveIOCChart from "../../components/statistics/ActiveIOCChart"; import DeploymentLineChart from "../../components/statistics/DeploymentLineChart"; @@ -12,6 +12,7 @@ import { import { HostStatistics } from "../../components/statistics/HostStatistics"; import { IOCStatistics } from "../../components/statistics/IOCStatistics"; import OperationChart from "../../components/statistics/OperationChart/OperationChart"; +import { applicationTitle } from "../../components/common/Helper"; const PREFIX = "StatisticsView"; @@ -26,8 +27,8 @@ const StyledRootContainer = styled(RootContainer)(({ theme }) => ({ })); export function StatisticsView() { - const title = "Statistics"; - useGlobalAppBar(title); + const { setTitle } = useContext(GlobalAppBarContext); + useEffect(() => setTitle(applicationTitle("Statistics")), [setTitle]); const theme = useTheme(); -- GitLab