mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
* Fix switcher component * Fix header styles * Add popover component in header
This commit is contained in:
parent
2d31acad99
commit
e0111785ec
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { string } from 'prop-types';
|
||||
|
||||
import {
|
||||
Box,
|
||||
@ -26,12 +27,13 @@ const useStyles = makeStyles(theme => {
|
||||
},
|
||||
text: {
|
||||
...theme.typography.body1,
|
||||
paddingLeft: theme.spacing(1)
|
||||
paddingLeft: theme.spacing(1),
|
||||
overflowWrap: 'anywhere'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const ErrorHelper = ({ label = 'Error', ...rest }) => {
|
||||
const ErrorHelper = ({ label, ...rest }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
@ -44,4 +46,12 @@ const ErrorHelper = ({ label = 'Error', ...rest }) => {
|
||||
);
|
||||
};
|
||||
|
||||
ErrorHelper.propTypes = {
|
||||
label: string
|
||||
};
|
||||
|
||||
ErrorHelper.defaultProps = {
|
||||
label: 'Error'
|
||||
};
|
||||
|
||||
export default ErrorHelper;
|
||||
|
@ -14,17 +14,16 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React from 'react';
|
||||
import { func } from 'prop-types';
|
||||
|
||||
import { MenuItem, TextField } from '@material-ui/core';
|
||||
import { FilterVintage } from '@material-ui/icons';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import useOpennebula from 'client/hooks/useOpennebula';
|
||||
import { Translate } from 'client/components/HOC';
|
||||
import { Tr } from 'client/components/HOC';
|
||||
import { FILTER_POOL } from 'client/constants';
|
||||
|
||||
const GroupSelect = ({ handleChange }) => {
|
||||
const GroupSelect = props => {
|
||||
const { filterPool, authUser } = useAuth();
|
||||
const { groups } = useOpennebula();
|
||||
|
||||
@ -53,25 +52,21 @@ const GroupSelect = ({ handleChange }) => {
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
onChange={handleChange}
|
||||
defaultValue={defaultValue}
|
||||
variant="outlined"
|
||||
inputProps={{ 'data-cy': 'select-group' }}
|
||||
label={<Translate word="Select a group" />}
|
||||
label={Tr('Select a group')}
|
||||
FormHelperTextProps={{ 'data-cy': 'select-group-error' }}
|
||||
{...props}
|
||||
>
|
||||
<MenuItem value={FILTER_POOL.ALL_RESOURCES}>{`Show all`}</MenuItem>
|
||||
<MenuItem value={FILTER_POOL.ALL_RESOURCES}>{Tr('Show all')}</MenuItem>
|
||||
{orderGroups}
|
||||
</TextField>
|
||||
);
|
||||
};
|
||||
|
||||
GroupSelect.propTypes = {
|
||||
handleChange: func
|
||||
};
|
||||
GroupSelect.propTypes = {};
|
||||
|
||||
GroupSelect.defaultProps = {
|
||||
handleChange: () => undefined
|
||||
};
|
||||
GroupSelect.defaultProps = {};
|
||||
|
||||
export default GroupSelect;
|
||||
|
@ -1,35 +0,0 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
/* a copy of the License at */
|
||||
/* */
|
||||
/* http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* */
|
||||
/* Unless required by applicable law or agreed to in writing, software */
|
||||
/* distributed under the License is distributed on an "AS IS" BASIS, */
|
||||
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
|
||||
/* See the License for the specific language governing permissions and */
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Box } from '@material-ui/core';
|
||||
|
||||
import { FILTER_POOL } from 'client/constants';
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import GroupSelect from '../FormControl/GroupSelect';
|
||||
|
||||
const FilterPoolSelect = () => {
|
||||
const { setPrimaryGroup } = useAuth();
|
||||
|
||||
const handleChangeFilter = evt => {
|
||||
console.log(evt);
|
||||
// setPrimaryGroup({ group: });
|
||||
};
|
||||
|
||||
return <GroupSelect handleChange={handleChangeFilter} />;
|
||||
};
|
||||
|
||||
export default FilterPoolSelect;
|
80
src/fireedge/src/public/components/Header/Group.js
Normal file
80
src/fireedge/src/public/components/Header/Group.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from '@material-ui/core';
|
||||
import FilterIcon from '@material-ui/icons/FilterDrama';
|
||||
import SelectedIcon from '@material-ui/icons/FilterVintage';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import useOpennebula from 'client/hooks/useOpennebula';
|
||||
import Search from 'client/components/Search';
|
||||
|
||||
import { FILTER_POOL } from 'client/constants';
|
||||
import HeaderPopover from 'client/components/Header/Popover';
|
||||
import headerStyles from 'client/components/Header/styles';
|
||||
|
||||
const { ALL_RESOURCES, PRIMARY_GROUP_RESOURCES } = FILTER_POOL;
|
||||
|
||||
const Group = () => {
|
||||
const classes = headerStyles();
|
||||
const { authUser, filterPool, setPrimaryGroup } = useAuth();
|
||||
const { groups } = useOpennebula();
|
||||
|
||||
const handleChangeGroup = group => {
|
||||
group && setPrimaryGroup({ group });
|
||||
};
|
||||
|
||||
const filterSearch = ({ NAME }, search) =>
|
||||
NAME?.toLowerCase().includes(search);
|
||||
|
||||
const renderResult = ({ ID, NAME }, handleClose) => {
|
||||
const isSelected =
|
||||
(filterPool === ALL_RESOURCES && ALL_RESOURCES === ID) ||
|
||||
(filterPool === PRIMARY_GROUP_RESOURCES && authUser?.GID === ID);
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={`term-${ID}`}
|
||||
fullWidth
|
||||
className={classes.groupButton}
|
||||
onClick={() => {
|
||||
handleChangeGroup(ID);
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
{NAME}
|
||||
{isSelected && <SelectedIcon className={classes.groupSelectedIcon} />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const sortMainGroupFirst = groups
|
||||
?.concat({ ID: ALL_RESOURCES, NAME: 'Show All' })
|
||||
?.sort((a, b) => {
|
||||
if (a.ID === authUser?.GUID) {
|
||||
return -1;
|
||||
} else if (b.ID === authUser?.GUID) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<HeaderPopover
|
||||
id="group-list"
|
||||
icon={<FilterIcon />}
|
||||
IconProps={{ 'data-cy': 'header-group-button' }}
|
||||
headerTitle="Switch group"
|
||||
>
|
||||
{({ handleClose }) => (
|
||||
<Search
|
||||
list={sortMainGroupFirst}
|
||||
maxResults={5}
|
||||
filterSearch={filterSearch}
|
||||
renderResult={group => renderResult(group, handleClose)}
|
||||
/>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
);
|
||||
};
|
||||
|
||||
export default Group;
|
124
src/fireedge/src/public/components/Header/Popover.js
Normal file
124
src/fireedge/src/public/components/Header/Popover.js
Normal file
@ -0,0 +1,124 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
useMediaQuery,
|
||||
Popover,
|
||||
Typography,
|
||||
Button
|
||||
} from '@material-ui/core';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
|
||||
import { Tr } from 'client/components/HOC';
|
||||
|
||||
import headerStyles from 'client/components/Header/styles';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const typeButton = {
|
||||
button: Button,
|
||||
iconButton: IconButton
|
||||
};
|
||||
|
||||
const HeaderPopover = ({
|
||||
id,
|
||||
icon,
|
||||
buttonLabel,
|
||||
IconProps,
|
||||
headerTitle,
|
||||
disablePadding,
|
||||
children
|
||||
}) => {
|
||||
const classes = headerStyles();
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
const handleOpen = event => setAnchorEl(event.currentTarget);
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const anchorId = open ? id : undefined;
|
||||
|
||||
const ButtonComponent = React.useMemo(
|
||||
() => (buttonLabel ? typeButton.button : typeButton.iconButton),
|
||||
[buttonLabel]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonComponent
|
||||
color="inherit"
|
||||
aria-controls={anchorId}
|
||||
aria-describedby={anchorId}
|
||||
aria-haspopup="true"
|
||||
onClick={handleOpen}
|
||||
{...IconProps}
|
||||
>
|
||||
{icon}
|
||||
{buttonLabel && (
|
||||
<span className={classes.buttonLabel}>{buttonLabel}</span>
|
||||
)}
|
||||
</ButtonComponent>
|
||||
<Popover
|
||||
BackdropProps={{ invisible: !isMobile }}
|
||||
PaperProps={{
|
||||
className: clsx(classes.paper, {
|
||||
[classes.padding]: !disablePadding
|
||||
})
|
||||
}}
|
||||
id={anchorId}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
>
|
||||
{(headerTitle || isMobile) && (
|
||||
<Box className={classes.header}>
|
||||
{headerTitle && (
|
||||
<Typography className={classes.title} variant="body1">
|
||||
{Tr(headerTitle)}
|
||||
</Typography>
|
||||
)}
|
||||
{isMobile && (
|
||||
<IconButton onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{children({ handleClose })}
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
HeaderPopover.propTypes = {
|
||||
id: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
buttonLabel: PropTypes.string,
|
||||
IconProps: PropTypes.objectOf(PropTypes.object),
|
||||
headerTitle: PropTypes.string,
|
||||
disablePadding: PropTypes.bool,
|
||||
children: PropTypes.func
|
||||
};
|
||||
|
||||
HeaderPopover.defaultProps = {
|
||||
id: 'id-popover',
|
||||
icon: null,
|
||||
buttonLabel: undefined,
|
||||
IconProps: {},
|
||||
headerTitle: null,
|
||||
disablePadding: false,
|
||||
children: () => undefined
|
||||
};
|
||||
|
||||
export default HeaderPopover;
|
@ -1,114 +1,40 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
/* a copy of the License at */
|
||||
/* */
|
||||
/* http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* */
|
||||
/* Unless required by applicable law or agreed to in writing, software */
|
||||
/* distributed under the License is distributed on an "AS IS" BASIS, */
|
||||
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
|
||||
/* See the License for the specific language governing permissions and */
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { useState, useRef, Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
Popper,
|
||||
Grow,
|
||||
Paper,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
ClickAwayListener,
|
||||
Divider,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core';
|
||||
import { MenuItem, MenuList, Divider } from '@material-ui/core';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
|
||||
import { Translate } from 'client/components/HOC';
|
||||
import { PATH } from 'client/router/endpoints';
|
||||
import { SignOut } from 'client/constants';
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import FilterPoolSelect from 'client/components/Header/FilterPoolSelect';
|
||||
import { Tr } from 'client/components/HOC';
|
||||
|
||||
import { SignOut } from 'client/constants';
|
||||
import { PATH } from 'client/router/endpoints';
|
||||
import HeaderPopover from 'client/components/Header/Popover';
|
||||
|
||||
const User = React.memo(() => {
|
||||
const history = useHistory();
|
||||
const { logout, authUser, isOneAdmin } = useAuth();
|
||||
const isUpSm = useMediaQuery(theme => theme.breakpoints.up('sm'));
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const anchorRef = useRef(null);
|
||||
const { current } = anchorRef;
|
||||
|
||||
const handleToggle = () => setOpen(prevOpen => !prevOpen);
|
||||
const { logout, authUser } = useAuth();
|
||||
|
||||
const handleLogout = () => logout();
|
||||
const handleGoToSettings = () => history.push(PATH.SETTINGS);
|
||||
|
||||
const handleClose = e => {
|
||||
if (current && current.contains(e.target)) {
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
ref={anchorRef}
|
||||
color="inherit"
|
||||
aria-controls={open ? 'menu-list-grow' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleToggle}
|
||||
data-cy="header-user-button"
|
||||
>
|
||||
<AccountCircleIcon />
|
||||
{isUpSm && <span style={{ paddingLeft: 5 }}>{authUser?.NAME}</span>}
|
||||
</Button>
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === 'bottom' ? 'center top' : 'center bottom'
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList autoFocusItem={open} id="menu-list-grow">
|
||||
<MenuItem onClick={handleGoToSettings}>Settings</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={handleLogout}
|
||||
data-cy="header-logout-button"
|
||||
>
|
||||
<Translate word={SignOut} />
|
||||
</MenuItem>
|
||||
{!isOneAdmin && (
|
||||
<>
|
||||
<Divider />
|
||||
<MenuItem>
|
||||
<FilterPoolSelect />
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</Fragment>
|
||||
<HeaderPopover
|
||||
id="user-menu"
|
||||
buttonLabel={authUser?.NAME}
|
||||
icon={<AccountCircleIcon />}
|
||||
IconProps={{ 'data-cy': 'header-user-button' }}
|
||||
disablePadding
|
||||
>
|
||||
{() => (
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleGoToSettings}>{Tr('Settings')}</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogout} data-cy="header-logout-button">
|
||||
{Tr(SignOut)}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -13,72 +13,27 @@
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popper,
|
||||
Grow,
|
||||
Paper,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
ClickAwayListener
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
import { MenuItem, MenuList } from '@material-ui/core';
|
||||
import LanguageIcon from '@material-ui/icons/Language';
|
||||
|
||||
const Zone = React.memo(() => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const anchorRef = useRef(null);
|
||||
const { current } = anchorRef;
|
||||
import { Tr } from 'client/components/HOC';
|
||||
import HeaderPopover from 'client/components/Header/Popover';
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen(prevOpen => !prevOpen);
|
||||
};
|
||||
|
||||
const handleClose = e => {
|
||||
if (current && current.contains(e.target)) {
|
||||
return;
|
||||
}
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
ref={anchorRef}
|
||||
color="inherit"
|
||||
aria-controls={open ? 'menu-list-grow' : undefined}
|
||||
aria-haspopup="true"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<LanguageIcon />
|
||||
</Button>
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === 'bottom' ? 'center top' : 'center bottom'
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList autoFocusItem={open} id="menu-list-grow">
|
||||
<MenuItem onClick={handleClose}>Zone</MenuItem>
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</>
|
||||
);
|
||||
});
|
||||
const Zone = React.memo(() => (
|
||||
<HeaderPopover
|
||||
id="zone-menu"
|
||||
icon={<LanguageIcon />}
|
||||
IconProps={{ 'data-cy': 'header-zone-button' }}
|
||||
disablePadding
|
||||
>
|
||||
{({ handleClose }) => (
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleClose}>{Tr('Zone')}</MenuItem>
|
||||
</MenuList>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
));
|
||||
|
||||
export default Zone;
|
||||
|
@ -25,12 +25,15 @@ import {
|
||||
} from '@material-ui/core';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import useGeneral from 'client/hooks/useGeneral';
|
||||
import User from 'client/components/Header/User';
|
||||
import Group from 'client/components/Header/Group';
|
||||
import Zone from 'client/components/Header/Zone';
|
||||
import headerStyles from 'client/components/Header/styles';
|
||||
|
||||
const Header = ({ title }) => {
|
||||
const { isOneAdmin } = useAuth();
|
||||
const { isFixMenu, fixMenu } = useGeneral();
|
||||
const classes = headerStyles();
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'));
|
||||
@ -54,11 +57,12 @@ const Header = ({ title }) => {
|
||||
{title}
|
||||
</Typography>
|
||||
<User />
|
||||
{!isOneAdmin && <Group />}
|
||||
<Zone />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
),
|
||||
[isFixMenu, fixMenu, isUpLg]
|
||||
[isFixMenu, fixMenu, isUpLg, isOneAdmin]
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,5 +4,40 @@ export default makeStyles(theme => ({
|
||||
title: {
|
||||
flexGrow: 1,
|
||||
textTransform: 'capitalize'
|
||||
},
|
||||
/* POPOVER */
|
||||
backdrop: {
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
backgroundColor: theme.palette.action.disabledOpacity
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
},
|
||||
padding: {
|
||||
padding: theme.spacing(2)
|
||||
},
|
||||
header: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
borderBottom: '1px solid',
|
||||
borderBottomColor: theme.palette.action.disabledBackground
|
||||
},
|
||||
buttonLabel: {
|
||||
paddingLeft: theme.spacing(1),
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
/* GROUP SWITCHER */
|
||||
headerSwitcherLabel: { flexGrow: 1 },
|
||||
groupButton: { justifyContent: 'start' },
|
||||
groupSelectedIcon: {
|
||||
fontSize: '1rem',
|
||||
margin: theme.spacing(0, 2)
|
||||
}
|
||||
}));
|
||||
|
54
src/fireedge/src/public/components/Search/index.js
Normal file
54
src/fireedge/src/public/components/Search/index.js
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { TextField, Box } from '@material-ui/core';
|
||||
|
||||
const Search = ({
|
||||
list,
|
||||
maxResults,
|
||||
filterSearch,
|
||||
renderResult,
|
||||
ResultBoxProps
|
||||
}) => {
|
||||
const [search, setSearch] = useState('');
|
||||
const [result, setResult] = useState(list);
|
||||
|
||||
useEffect(() => {
|
||||
setResult(list?.filter(item => filterSearch(item, search)));
|
||||
}, [search, list]);
|
||||
|
||||
const handleChange = event => setSearch(event.target.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
type="search"
|
||||
value={search}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
placeholder="Search..."
|
||||
/>
|
||||
<Box {...ResultBoxProps}>
|
||||
{result?.slice(0, maxResults).map(renderResult)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Search.propTypes = {
|
||||
list: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
maxResults: PropTypes.number,
|
||||
filterSearch: PropTypes.func,
|
||||
renderResult: PropTypes.func,
|
||||
ResultBoxProps: PropTypes.objectOf(PropTypes.object)
|
||||
};
|
||||
|
||||
Search.defaultProps = {
|
||||
list: [],
|
||||
maxResults: undefined,
|
||||
filterSearch: (item, search) => item.toLowerCase().includes(search),
|
||||
renderResult: item => item,
|
||||
ResultBoxProps: {}
|
||||
};
|
||||
|
||||
export default Search;
|
19
src/fireedge/src/public/components/Search/styles.js
Normal file
19
src/fireedge/src/public/components/Search/styles.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
backdrop: {
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
backgroundColor: theme.palette.action.disabledOpacity
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
padding: '1rem',
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}
|
||||
},
|
||||
header: { display: 'flex', alignItems: 'center' },
|
||||
title: { flexGrow: 1 },
|
||||
button: { justifyContent: 'start' }
|
||||
}));
|
@ -26,6 +26,7 @@ module.exports = {
|
||||
Token2FA: '2FA Token',
|
||||
SignOut: 'Sign Out',
|
||||
jwtName: 'SunstoneToken',
|
||||
filterPool: 'FilterPool',
|
||||
Submit: 'Submit',
|
||||
Response: 'Response',
|
||||
by: {
|
||||
|
5
src/fireedge/src/public/containers/Application/index.js
Normal file
5
src/fireedge/src/public/containers/Application/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import Create from 'client/containers/Application/Create';
|
||||
import Deploy from 'client/containers/Application/Deploy';
|
||||
import Manage from 'client/containers/Application/Manage';
|
||||
|
||||
export { Create, Deploy, Manage };
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { func, string } from 'prop-types';
|
||||
|
||||
import { Box, Button, TextField } from '@material-ui/core';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers';
|
||||
|
@ -1,45 +1,46 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
/* a copy of the License at */
|
||||
/* */
|
||||
/* http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* */
|
||||
/* Unless required by applicable law or agreed to in writing, software */
|
||||
/* distributed under the License is distributed on an "AS IS" BASIS, */
|
||||
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
|
||||
/* See the License for the specific language governing permissions and */
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React from 'react';
|
||||
import { func } from 'prop-types';
|
||||
|
||||
import { Box, Button } from '@material-ui/core';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
|
||||
import GroupSelect from 'client/components/FormControl/GroupSelect';
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton';
|
||||
import { Tr } from 'client/components/HOC';
|
||||
import loginStyles from 'client/containers/Login/styles';
|
||||
import { Next } from 'client/constants';
|
||||
|
||||
function FormGroup({ classes, onBack, onSubmit }) {
|
||||
function FormGroup({ onBack, onSubmit }) {
|
||||
const classes = loginStyles();
|
||||
const { control, handleSubmit } = useForm();
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
className={classes?.form}
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<Controller as={<GroupSelect />} control={control} name="group" />
|
||||
<Controller as={GroupSelect} name="group" control={control} />
|
||||
<Button onClick={onBack}>Logout</Button>
|
||||
<ButtonSubmit
|
||||
data-cy="login-group-button"
|
||||
isSubmitting={false}
|
||||
label="Enter"
|
||||
label={Tr(Next)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
FormGroup.propTypes = {
|
||||
onBack: func.isRequired,
|
||||
onSubmit: func.isRequired
|
||||
};
|
||||
|
||||
FormGroup.defaultProps = {
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined
|
||||
};
|
||||
|
||||
FormGroup.propTypes = {};
|
||||
|
||||
FormGroup.defaultProps = {};
|
||||
|
@ -1,18 +1,3 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
/* a copy of the License at */
|
||||
/* */
|
||||
/* http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* */
|
||||
/* Unless required by applicable law or agreed to in writing, software */
|
||||
/* distributed under the License is distributed on an "AS IS" BASIS, */
|
||||
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
|
||||
/* See the License for the specific language governing permissions and */
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React from 'react';
|
||||
import { func, string } from 'prop-types';
|
||||
import { Box, Checkbox, TextField, FormControlLabel } from '@material-ui/core';
|
||||
|
@ -24,7 +24,6 @@ import {
|
||||
} from '@material-ui/core';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import useOpennebula from 'client/hooks/useOpennebula';
|
||||
|
||||
import FormUser from 'client/containers/Login/FormUser';
|
||||
import Form2fa from 'client/containers/Login/Form2fa';
|
||||
@ -51,7 +50,6 @@ function Login() {
|
||||
getAuthInfo,
|
||||
setPrimaryGroup
|
||||
} = useAuth();
|
||||
const { groups } = useOpennebula();
|
||||
|
||||
const handleSubmitUser = dataForm => {
|
||||
login({ ...user, ...dataForm }).then(data => {
|
||||
@ -122,11 +120,7 @@ function Login() {
|
||||
unmountOnExit
|
||||
>
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<FormGroup
|
||||
groups={groups}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitGroup}
|
||||
/>
|
||||
<FormGroup onBack={handleBack} onSubmit={handleSubmitGroup} />
|
||||
</Box>
|
||||
</Slide>
|
||||
</Box>
|
||||
|
@ -27,7 +27,6 @@ export default function useAuth() {
|
||||
filterPool,
|
||||
user: authUser
|
||||
} = useSelector(state => state?.Authenticated, shallowEqual);
|
||||
const baseURL = useSelector(state => state?.System?.baseURL, shallowEqual);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
@ -43,7 +42,7 @@ export default function useAuth() {
|
||||
dispatch(startAuth());
|
||||
|
||||
return serviceAuth
|
||||
.login(user, baseURL)
|
||||
.login(user)
|
||||
.then(data => {
|
||||
const { id, token } = data;
|
||||
dispatch(successAuth());
|
||||
@ -67,7 +66,7 @@ export default function useAuth() {
|
||||
dispatch(failureAuth({ error: err }));
|
||||
});
|
||||
},
|
||||
[dispatch, baseURL, jwtName]
|
||||
[dispatch, jwtName]
|
||||
);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
@ -79,22 +78,22 @@ export default function useAuth() {
|
||||
dispatch(startAuth());
|
||||
|
||||
return serviceAuth
|
||||
.getUser(baseURL)
|
||||
.getUser()
|
||||
.then(user => dispatch(successAuth({ user })))
|
||||
.then(serviceGroups.getGroups)
|
||||
.then(groups => dispatch(setGroups(groups)))
|
||||
.catch(err => dispatch(failureAuth({ error: err })));
|
||||
}, [dispatch, baseURL, jwtName, authUser]);
|
||||
}, [dispatch, jwtName, authUser]);
|
||||
|
||||
const setPrimaryGroup = useCallback(
|
||||
values => {
|
||||
if (values?.group === FILTER_POOL.ALL_RESOURCES) {
|
||||
({ group }) => {
|
||||
if (group === FILTER_POOL.ALL_RESOURCES) {
|
||||
dispatch(selectFilterGroup({ filterPool: FILTER_POOL.ALL_RESOURCES }));
|
||||
} else {
|
||||
dispatch(startAuth());
|
||||
|
||||
serviceUsers
|
||||
.changeGroup({ id: authUser.ID, ...values })
|
||||
.changeGroup({ id: authUser.ID, group })
|
||||
.then(() =>
|
||||
dispatch(
|
||||
selectFilterGroup({
|
||||
@ -102,10 +101,11 @@ export default function useAuth() {
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(getAuthInfo)
|
||||
.catch(err => dispatch(failureAuth({ error: err })));
|
||||
}
|
||||
},
|
||||
[dispatch, authUser, jwtName]
|
||||
[dispatch, authUser]
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -2,9 +2,8 @@ import { httpCodes } from 'server/utils/constants';
|
||||
import { jwtName, endpointsRoutes } from 'client/constants';
|
||||
import { requestData, removeStoreData } from 'client/utils';
|
||||
|
||||
export const login = (user, baseURL = '') =>
|
||||
export const login = user =>
|
||||
requestData(endpointsRoutes.login, {
|
||||
baseURL,
|
||||
data: user,
|
||||
method: 'POST',
|
||||
authenticate: false,
|
||||
@ -21,9 +20,8 @@ export const login = (user, baseURL = '') =>
|
||||
return res?.data;
|
||||
});
|
||||
|
||||
export const getUser = (baseURL = '') =>
|
||||
export const getUser = () =>
|
||||
requestData(endpointsRoutes.userInfo, {
|
||||
baseURL,
|
||||
error: err => {
|
||||
removeStoreData(jwtName);
|
||||
return err?.message;
|
||||
|
@ -37,7 +37,7 @@ export const requestParams = (data, command) => {
|
||||
const resources = getResources(mappedParams);
|
||||
const body = getDataBody(mappedParams);
|
||||
|
||||
const url = `api/${name.replace('.', '/')}`;
|
||||
const url = `/api/${name.replace('.', '/')}`;
|
||||
|
||||
return {
|
||||
url: `${url}/${resources}?${queries}`,
|
||||
|
Loading…
x
Reference in New Issue
Block a user