From 5ad038a64902ed3d12f369667f4e67b9dadfcea7 Mon Sep 17 00:00:00 2001 From: Imre Toth <imre.toth@ess.eu> Date: Mon, 4 Oct 2021 15:45:28 +0200 Subject: [PATCH] ICSHWI-7866: Implementing UI changes --- src/api/SwaggerApi.js | 10 ++ src/components/IOC/IOCCreateDialog.js | 31 +++- src/components/IOC/IOCDeployDialog.js | 12 +- src/components/IOC/IOCManage.js | 9 +- src/components/common/Helper.js | 2 +- .../navigation/GlobalAppBar/GlobalAppBar.js | 153 ++++++++++-------- .../statistics/DeploymentLineChart.js | 3 +- src/views/statistics/StatisticsView.js | 4 +- 8 files changed, 135 insertions(+), 89 deletions(-) diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js index 65302f0d..a941c55a 100644 --- a/src/api/SwaggerApi.js +++ b/src/api/SwaggerApi.js @@ -504,4 +504,14 @@ export function useDeployedIOCStatistics() { const method = callAndUnpack(api.apis.Statistics.iocDeploymentHistory, unpackDeployedIOCStat) return useAsync({ fcn: method, call: false }); +} + +export function unpackAllowedGitProjects(projectList) { + return projectList.toString().split(","); +} + +export function useAllowedGitProjects() { + const api = useContext(apiContext); + const method = callAndUnpack((repoUrl) => api.apis.Git.listProjects(), unpackAllowedGitProjects) + return useAsync({ fcn: method, call: false, init: [] }); } \ No newline at end of file diff --git a/src/components/IOC/IOCCreateDialog.js b/src/components/IOC/IOCCreateDialog.js index e7e41f4f..85ae5c80 100644 --- a/src/components/IOC/IOCCreateDialog.js +++ b/src/components/IOC/IOCCreateDialog.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from "@material-ui/core"; import { Autocomplete } from "@material-ui/lab"; -import { useNamingNames } from "../../api/SwaggerApi"; +import { useNamingNames, useAllowedGitProjects } from "../../api/SwaggerApi"; import { useTypingTimer } from "../common/SearchBoxFilter/TypingTimer"; const useStyles = makeStyles((theme) => ({ @@ -15,6 +15,8 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) { const [names, getNames] = useNamingNames(); const [name, setName] = useState(); const [nameQuery, onNameKeyUp] = useTypingTimer({interval: 500}); + const [allowedGitProjects, getAllowedGitProjects] = useAllowedGitProjects(); + const [gitUrl, setGitUrl] = useState(); const handleClose = () => { setOpen(false); @@ -24,18 +26,20 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) { useEffect(() => getNames(nameQuery), [nameQuery, getNames]); + useEffect(() => { + getAllowedGitProjects(); + }, [getAllowedGitProjects]); + const onSubmit = (event) => { event.preventDefault(); - const { git: gitText } = event.currentTarget.elements; - const git = gitText.value; submitCallback({ - sourceUrl: git, + sourceUrl: gitUrl, externalNameId: name ? parseInt(name.id) : undefined }); }; return ( - <Dialog open={open} onClose={handleClose}> + <Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm"> <form onSubmit={onSubmit}> <DialogTitle id="form-dialog-title" data-testid="create-dialog-title">Create new IOC</DialogTitle> <DialogContent> @@ -50,9 +54,20 @@ export function IOCCreateDialog({ open, setOpen, submitCallback }) { onInputChange={(event, value, reason) => {event && onNameKeyUp(event.nativeEvent)}} /> - <TextField autoComplete="off" className={classes.textField} id="description" label="Description" - variant="outlined" fullWidth disabled value={name? name.description : ""} /> - <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="outlined" fullWidth required /> + <TextField autoComplete="off" className={classes.textField} id="description" label="--- IOC Description from CCDB ---" + variant="standard" fullWidth disabled value={name? name.description : ""} /> + + <Autocomplete + className={classes.textField} + autoHighlight + id="version" + options={allowedGitProjects} + defaultValue = { "" } + getOptionLabel={(option) => {return option}} + onChange={(event, value, reason) => setGitUrl(value)} + renderInput={(params) => <TextField {...params} label="Git repository" variant="outlined" fullWidth required />} + /> + </DialogContent> <DialogActions> <Button onClick={handleClose} color="primary">Cancel</Button> diff --git a/src/components/IOC/IOCDeployDialog.js b/src/components/IOC/IOCDeployDialog.js index bd31d6b9..926d6341 100644 --- a/src/components/IOC/IOCDeployDialog.js +++ b/src/components/IOC/IOCDeployDialog.js @@ -17,14 +17,13 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy const [query, onHostKeyUp] = useTypingTimer({interval: 500}); const [tagOrCommitId, getTagOrCommitId] = useTagsAndCommitIds([]); const [gitRepo, setGitRepo] = useState(init.git || ""); + const [gitVersion, setGitVersion] = useState(init.version || ""); const handleClose = () => { setOpen(false); }; console.log({...init}); - console.log(hosts); - console.log(host); useEffect(() => getHosts(`fqdn:"${query}"`), [query, getHosts, getTagOrCommitId]); @@ -49,9 +48,9 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy <form onSubmit={onSubmit}> <DialogTitle id="form-dialog-title">{ hasActiveDeployment ? "Deploy new revision" : "Deploy" }</DialogTitle> <DialogContent> - <TextField autoComplete="off" className={classes.textField} id="comment" label="Deployment comment" variant="outlined" fullWidth required/> - <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="outlined" defaultValue={init.git || ""} fullWidth - onChange={(event) => setGitRepo(event.target.value)} required/> + <TextField autoComplete="off" className={classes.textField} id="comment" label="Deployment comment" variant="outlined" fullWidth/> + <TextField autoComplete="off" className={classes.textField} id="git" label="Git repository" variant="standard" defaultValue={init.git || ""} fullWidth + onChange={(event) => setGitRepo(event.target.value)} disabled required/> <Autocomplete className={classes.textField} @@ -61,6 +60,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy defaultValue = { init.version || "" } onFocus={(event) => {getTagOrCommitId(gitRepo);}} getOptionLabel={(option) => {return option}} + onChange={(event, value, reason) => setGitVersion(value)} disabled={(!gitRepo) || (gitRepo.trim() === "")} renderInput={(params) => <TextField {...params} label="Git reference" variant="outlined" fullWidth required />} /> @@ -87,7 +87,7 @@ export function IOCDeployDialog({ open, setOpen, submitCallback, hasActiveDeploy </DialogContent> <DialogActions> <Button onClick={handleClose} color="primary">Cancel</Button> - <Button color="primary" variant="contained" type="submit">Deploy</Button> + <Button color="primary" variant="contained" type="submit" disabled={init?.version === gitVersion}>Deploy</Button> </DialogActions> </form> </Dialog> diff --git a/src/components/IOC/IOCManage.js b/src/components/IOC/IOCManage.js index 6df6c951..a5e9946e 100644 --- a/src/components/IOC/IOCManage.js +++ b/src/components/IOC/IOCManage.js @@ -8,6 +8,7 @@ import { SimpleModal } from "../../components/common/SimpleModal/SimpleModal"; import { useUpdateAndDeployIoc, useCreateUndeployment } from "../../api/SwaggerApi"; import GitRefLink from "./GitRefLink"; import IOCAlerts from "./IOCAlerts"; +import { Link } from 'react-router-dom'; export function IOCManage({ ioc, getIOC }) { const [deployDialogOpen, setDeployDialogOpen] = useState(false); @@ -38,7 +39,13 @@ export function IOCManage({ ioc, getIOC }) { // active, } = ioc; const { sourceUrl: git } = ioc.latestVersion; - const host = ioc.activeDeployment?.host.host; + let host = ioc.activeDeployment?.host.host; + + //create link if host is set + if (ioc.activeDeployment) { + host = <Typography><Link to={`/hosts/${ioc.activeDeployment.host.csEntryId}`}>{ioc.activeDeployment.host.host}</Link></Typography> + } + return { "CCDB name": <Typography><MuiLink href={`${window.CCDB_ADDRESS}?name=${ioc.latestVersion.namingName}`} target="_blank" rel="noreferrer">{ioc.latestVersion.namingName}</MuiLink></Typography>, diff --git a/src/components/common/Helper.js b/src/components/common/Helper.js index b07df13f..71bb82fb 100644 --- a/src/components/common/Helper.js +++ b/src/components/common/Helper.js @@ -118,5 +118,5 @@ export function iocHasNameDeviation(ioc) { return false; } - return ioc?.latestVersion?.namingName !== ioc?.activeDeployment?.namingName; + return ioc?.latestVersion?.namingName !== ioc?.activeDeployment?.iocName; } diff --git a/src/components/navigation/GlobalAppBar/GlobalAppBar.js b/src/components/navigation/GlobalAppBar/GlobalAppBar.js index 03d02448..50565b8c 100644 --- a/src/components/navigation/GlobalAppBar/GlobalAppBar.js +++ b/src/components/navigation/GlobalAppBar/GlobalAppBar.js @@ -55,10 +55,10 @@ const useStyles = makeStyles((theme) => ({ marginLeft: theme.spacing(1), }, typography: { - padding: theme.spacing(2), + padding: theme.spacing(2), }, notificationButton: { - marginRight: theme.spacing(2), + marginRight: theme.spacing(2), }, content: { marginTop: theme.spacing(1), @@ -75,20 +75,20 @@ const StyledMenu = withStyles({ }, })((props) => ( <> - <div style={{ ...props.theme.mixins.toolbar }} /> - <Menu - elevation={0} - getContentAnchorEl={null} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'center', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'center', - }} - {...props} - /> + <div style={{ ...props.theme.mixins.toolbar }} /> + <Menu + elevation={0} + getContentAnchorEl={null} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + {...props} + /> </> )); @@ -141,14 +141,14 @@ function MenuListItem({ selectedUrl, handleListItemClick, url, icon, text, toolt console.log(currentUrl); return ( <> - { url.includes("help") ? <Divider className={classes.helpDivider}/> : <></>} - <Tooltip title={tooltip ? text : ""} placement="right" arrow> - <ListItem button component={Link} to={url} selected={currentUrl.endsWith(url)} - onClick={(event) => handleListItemClick(event, url)}> - <ListItemIcon className={classes.menuIcon}>{icon}</ListItemIcon> - <ListItemText primary={text} primaryTypographyProps={{variant: "h5"}}/> - </ListItem> - </Tooltip> + {url.includes("help") ? <Divider className={classes.helpDivider} /> : <></>} + <Tooltip title={tooltip ? text : ""} placement="right" arrow> + <ListItem button component={Link} to={url} selected={currentUrl.endsWith(url)} + onClick={(event) => handleListItemClick(event, url)}> + <ListItemIcon className={classes.menuIcon}>{icon}</ListItemIcon> + <ListItemText primary={text} primaryTypographyProps={{ variant: "h5" }} /> + </ListItem> + </Tooltip> </> ); } @@ -157,7 +157,7 @@ function MenuListItems({ menuItems, selectedUrl, handleListItemClick, drawerOpen return ( <> {menuItems.map(({ text, url, icon }, index) => ( - <MenuListItem key={text} tooltip={!drawerOpen} selectedUrl={selectedUrl} handleListItemClick={handleListItemClick} url={url} icon={icon} text={text}/> + <MenuListItem key={text} tooltip={!drawerOpen} selectedUrl={selectedUrl} handleListItemClick={handleListItemClick} url={url} icon={icon} text={text} /> ))} </> ); @@ -191,20 +191,20 @@ export function ProfileMenu() { return ( <> <Avatar src={user.avatar} variant="circular" onClick={handleClick} aria-controls="profile-menu" - aria-haspopup="true"/> + aria-haspopup="true" /> <StyledMenu id="profile-menu" - anchorEl={anchorEl} - keepMounted - open={Boolean(anchorEl)} - onClose={handleClose} - theme={theme} - > - <StyledMenuItem onClick={() => {handleClose(); logout();}}> + anchorEl={anchorEl} + keepMounted + open={Boolean(anchorEl)} + onClose={handleClose} + theme={theme} + > + <StyledMenuItem onClick={() => { handleClose(); logout(); }}> <ListItemIcon> <LockOpen fontSize="small" /> </ListItemIcon> - <ListItemText primary="Logout" primaryTypographyProps={{variant: "h5"}} /> + <ListItemText primary="Logout" primaryTypographyProps={{ variant: "h5" }} /> </StyledMenuItem> </StyledMenu> </> @@ -214,12 +214,12 @@ export function ProfileMenu() { export function NotificationMenu() { const [anchorEl, setAnchorEl] = useState(null); const classes = useStyles(); - const {deploymentNotifications, clearAll} = useContext(notificationContext); + const { deploymentNotifications, clearAll } = useContext(notificationContext); const handleClick = (event) => { - if (deploymentNotifications().length > 0) { - setAnchorEl(event.currentTarget); - } + // if (deploymentNotifications().length > 0) { + setAnchorEl(event.currentTarget); + // } }; const handleClose = () => { @@ -229,19 +229,53 @@ export function NotificationMenu() { function statusFromNotification(text) { const lowerText = text.toLowerCase(); - if(lowerText.includes("pending")) { + if (lowerText.includes("pending")) { return "pending"; - } else if(lowerText.includes("queued")) { + } else if (lowerText.includes("queued")) { return "queued"; - } else if(lowerText.includes("running")) { + } else if (lowerText.includes("running")) { return "running"; - } else if(lowerText.includes("successful")) { + } else if (lowerText.includes("successful")) { return "successful"; - } else if(lowerText.includes("failed")) { + } else if (lowerText.includes("failed")) { return "failed"; } } + function haveNotification() { + return ( + <> + <Grid item xs={12}> + <List className={classes.notificationList}> + {deploymentNotifications().map((notification) => { + return ( + <ListItem key={notification}> + <ListItemIcon> + <DeploymentStatusIcon status={statusFromNotification(notification)} /> + </ListItemIcon> + <Typography>{notification}</Typography> + </ListItem> + ); + })} + </List> + </Grid> + <Grid item xs={2}> + <Box display="flex" justifyContent="flex-end" mx={1} mb={1}> + <IconButton color="inherit" onClick={() => { clearAll(); handleClose(); }}> + <Tooltip title="Clear all notifications" arrow> + <DeleteIcon /> + </Tooltip> + </IconButton> + </Box> + </Grid> + </> + ) + } + + function noNotification() { + return <Typography inset="true" style={{ margin: '4px' }}>You have no notifications!</Typography>; + } + return ( <> <IconButton color="inherit" onClick={handleClick} className={classes.notificationButton}> @@ -263,29 +297,8 @@ export function NotificationMenu() { }} > <Grid container spacing={0} justify="flex-end"> - <Grid item xs={12}> - <List className={classes.notificationList}> - {deploymentNotifications().map((notification) => { - return ( - <ListItem key={notification}> - <ListItemIcon> - <DeploymentStatusIcon status={statusFromNotification(notification)} /> - </ListItemIcon> - <Typography>{notification}</Typography> - </ListItem> - ); - })} - </List> - </Grid> - <Grid item xs={2}> - <Box display="flex" justifyContent="flex-end" mx={1} mb={1}> - <IconButton color="inherit" onClick={() => {clearAll(); handleClose();}}> - <Tooltip title="Clear all notifications" arrow> - <DeleteIcon /> - </Tooltip> - </IconButton> - </Box> - </Grid> + {(deploymentNotifications().length > 0) ? haveNotification() : noNotification()} + </Grid> </Popover> </> @@ -315,7 +328,7 @@ export function GlobalAppBar({ children }) { const makeLink = (text, url, icon) => ({ text, url, icon }); const menuItems = [ - makeLink("Explore IOCs", "/iocs", <CCCEControlSymbol/>), + makeLink("Explore IOCs", "/iocs", <CCCEControlSymbol />), makeLink("Explore IOC hosts", "/hosts", <Storage />), makeLink("Deployment log", "/deployments", <Assignment />), makeLink("Statistics", "/statistics", <TrendingUp />), @@ -326,13 +339,13 @@ export function GlobalAppBar({ children }) { <> <ButtonAppBar homeUrl="/home" homeClick={handleLeftMenuItemClick} title={title} button={button} profileButton={profile} notificationsButton={notifications} /> <MenuDrawer open={drawerOpen} toggleDrawer={toggleDrawer}> - <MenuListItems menuItems={menuItems} selectedUrl={selectedUrl} - handleListItemClick={handleLeftMenuItemClick} drawerOpen={drawerOpen}/> + <MenuListItems menuItems={menuItems} selectedUrl={selectedUrl} + handleListItemClick={handleLeftMenuItemClick} drawerOpen={drawerOpen} /> </MenuDrawer> <GlobalAppBarContext.Provider value={value}> <div style={{ ...theme.mixins.toolbar }} /> <div className={classes.content}> - {children} + {children} </div> </GlobalAppBarContext.Provider> </> diff --git a/src/components/statistics/DeploymentLineChart.js b/src/components/statistics/DeploymentLineChart.js index 3b8e19cc..4926d264 100644 --- a/src/components/statistics/DeploymentLineChart.js +++ b/src/components/statistics/DeploymentLineChart.js @@ -83,7 +83,8 @@ export default function DeploymentLineChart({title, chartLabel, hook}) { }, y: { ticks: { - color: '#495057' + color: '#495057', + stepSize: 1 }, grid: { color: '#ebedef' diff --git a/src/views/statistics/StatisticsView.js b/src/views/statistics/StatisticsView.js index 035e7485..143ff906 100644 --- a/src/views/statistics/StatisticsView.js +++ b/src/views/statistics/StatisticsView.js @@ -54,7 +54,7 @@ export function StatisticsView() { <Box m={2} /> <Paper> - <DeploymentLineChart title="Number of IOCs deployed by the Deployment tool" chartLabel="Deployed IOCs in time" hook={useDeployedIOCStatistics}/> + <DeploymentLineChart title="Number of successful deployments by the Deployment tool" chartLabel="Deployed IOCs in time" hook={useDeployedIOCStatistics}/> </Paper> <Box m={2} /> @@ -66,7 +66,7 @@ export function StatisticsView() { <Box m={2} /> <Paper> - <DeploymentLineChart title="IOC deployment history graph (Propmetheus)" chartLabel="Active IOCs in time" hook={useActiveIOCHistory}/> + <DeploymentLineChart title="IOC deployment history graph (Prometheus)" chartLabel="Active IOCs in time" hook={useActiveIOCHistory}/> </Paper> </RootContainer> -- GitLab