mirror of
https://github.com/OpenNebula/one.git
synced 2025-01-10 01:17:40 +03:00
Merge branch 'master' of https://github.com/OpenNebula/one-ee
This commit is contained in:
commit
8d0a5f858b
@ -40,7 +40,7 @@ public:
|
||||
DDDEBUG = 5
|
||||
};
|
||||
|
||||
static const char error_names[];
|
||||
static const std::string error_names[];
|
||||
|
||||
Log(const MessageType _level = WARNING):log_level(_level){};
|
||||
|
||||
|
@ -47,7 +47,13 @@
|
||||
# file to log in the oned.log file
|
||||
# syslog to use the syslog facilities
|
||||
# std to use the default log stream (stderr) to use with systemd
|
||||
# debug_level: 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG
|
||||
# debug_level:
|
||||
# 0 = ERROR
|
||||
# 1 = WARNING
|
||||
# 2 = INFO
|
||||
# 3 = DEBUG
|
||||
# 4 = DDEBUG
|
||||
# 5 = DDDEBUG
|
||||
# use_vms_location: defines if store VM logs in VMS_LOCATION
|
||||
#
|
||||
#*******************************************************************************
|
||||
|
@ -576,7 +576,7 @@ class OneProvisionHelper < OpenNebulaHelper::OneHelper
|
||||
return [-1, rc.message] if OpenNebula.is_error?(rc)
|
||||
|
||||
case type
|
||||
when 'HOSTS'
|
||||
when 'HOST', 'HOSTS'
|
||||
host_operation(obj, operation, args[1])
|
||||
else
|
||||
msg = "Deleting #{type} #{obj['ID']}"
|
||||
@ -700,7 +700,7 @@ class OneProvisionHelper < OpenNebulaHelper::OneHelper
|
||||
# @returns [OpenNebula::Helper] Helper
|
||||
def helper(type)
|
||||
case type
|
||||
when 'HOSTS' then helper = OneHostHelper.new
|
||||
when 'HOST', 'HOSTS' then helper = OneHostHelper.new
|
||||
when 'DATASTORES' then helper = OneDatastoreHelper.new
|
||||
when 'NETWORKS' then helper = OneVNetHelper.new
|
||||
when 'CLUSTERS' then helper = OneClusterHelper.new
|
||||
|
@ -59,7 +59,6 @@ require 'tempfile'
|
||||
|
||||
require 'command_parser'
|
||||
require 'opennebula/oneflow_client'
|
||||
require 'models'
|
||||
require 'cli_helper'
|
||||
require 'one_helper/oneflowtemplate_helper'
|
||||
|
||||
|
@ -273,7 +273,7 @@ CommandParser::CmdParser.new(ARGV) do
|
||||
:options => [OneProvisionHelper::MODES] do
|
||||
operation = { :operation => 'delete', :message => 'deleted' }
|
||||
|
||||
helper.resources_operation(args, operation, options, 'HOSTS')
|
||||
helper.resources_operation(args, operation, options, 'HOST')
|
||||
end
|
||||
|
||||
###
|
||||
@ -288,7 +288,7 @@ CommandParser::CmdParser.new(ARGV) do
|
||||
:options => [OneProvisionHelper::MODES] do
|
||||
operation = { :operation => 'configure', :message => 'enabled' }
|
||||
|
||||
helper.resources_operation(args, operation, options, 'HOSTS')
|
||||
helper.resources_operation(args, operation, options, 'HOST')
|
||||
end
|
||||
|
||||
###
|
||||
@ -303,7 +303,7 @@ CommandParser::CmdParser.new(ARGV) do
|
||||
[:command, nil] do
|
||||
operation = { :operation => 'ssh', :message => 'enabled' }
|
||||
|
||||
helper.resources_operation(args, operation, options, 'HOSTS')
|
||||
helper.resources_operation(args, operation, options, 'HOST')
|
||||
end
|
||||
|
||||
###
|
||||
|
@ -19,25 +19,44 @@ import PropTypes from 'prop-types'
|
||||
import { Lock, User, Group, Cart } from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import Timer from 'client/components/Timer'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import { getState, getType } from 'client/models/MarketplaceApp'
|
||||
import { timeFromMilliseconds } from 'client/models/Helper'
|
||||
import {
|
||||
timeFromMilliseconds,
|
||||
getUniqueLabels,
|
||||
getColorFromString,
|
||||
} from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { T, MarketplaceApp } from 'client/constants'
|
||||
import {
|
||||
T,
|
||||
MarketplaceApp,
|
||||
MARKETPLACE_APP_ACTIONS,
|
||||
RESOURCE_NAMES,
|
||||
} from 'client/constants'
|
||||
|
||||
const MarketplaceAppCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {MarketplaceApp} props.app - Marketplace App resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ app, rootProps }) => {
|
||||
({ app, rootProps, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.VM]: vmView } = useViews()
|
||||
|
||||
const enableEditLabels =
|
||||
vmView?.actions?.[MARKETPLACE_APP_ACTIONS.EDIT_LABELS] === true &&
|
||||
!!onDeleteLabel
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
@ -48,6 +67,7 @@ const MarketplaceAppCard = memo(
|
||||
MARKETPLACE,
|
||||
ZONE_ID,
|
||||
SIZE,
|
||||
TEMPLATE: { LABELS } = {},
|
||||
} = app
|
||||
|
||||
const state = useMemo(() => getState(app), [app?.STATE])
|
||||
@ -56,6 +76,17 @@ const MarketplaceAppCard = memo(
|
||||
const time = useMemo(() => timeFromMilliseconds(+REGTIME), [REGTIME])
|
||||
const type = useMemo(() => getType(app), [app?.TYPE])
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[LABELS, enableEditLabels, onClickLabel, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`app-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
@ -67,6 +98,7 @@ const MarketplaceAppCard = memo(
|
||||
{LOCK && <Lock />}
|
||||
<span className={classes.labels}>
|
||||
<StatusChip text={type} />
|
||||
<MultipleTags tags={labels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
@ -104,6 +136,8 @@ MarketplaceAppCard.propTypes = {
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,7 @@ const NicCard = memo(
|
||||
const rulesById = Object.entries(groupBy(SECURITY_GROUPS, 'ID'))
|
||||
|
||||
return (
|
||||
<Accordion variant="outlined">
|
||||
<Accordion variant="outlined" data-cy="security-groups">
|
||||
<AccordionSummary>
|
||||
<Typography variant="body1">
|
||||
<Translate word={T.SecurityGroups} />
|
||||
|
@ -48,11 +48,12 @@ const VirtualMachineCard = memo(
|
||||
* @param {object} props - Props
|
||||
* @param {VM} props.vm - Virtual machine resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ vm, rootProps, actions, onDeleteLabel }) => {
|
||||
({ vm, rootProps, actions, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.VM]: vmView } = useViews()
|
||||
|
||||
@ -90,9 +91,10 @@ const VirtualMachineCard = memo(
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[LABELS, enableEditLabels, onDeleteLabel]
|
||||
[LABELS, enableEditLabels, onClickLabel, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -159,6 +161,7 @@ VirtualMachineCard.propTypes = {
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
actions: PropTypes.any,
|
||||
}
|
||||
|
@ -48,10 +48,11 @@ const VmTemplateCard = memo(
|
||||
* @param {object} props - Props
|
||||
* @param {VM} props.template - Virtual machine resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ template, rootProps, onDeleteLabel }) => {
|
||||
({ template, rootProps, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.VM_TEMPLATE]: templateView } = useViews()
|
||||
|
||||
@ -83,9 +84,10 @@ const VmTemplateCard = memo(
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[LABELS, enableEditLabels, onDeleteLabel]
|
||||
[LABELS, enableEditLabels, onClickLabel, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -134,6 +136,7 @@ VmTemplateCard.propTypes = {
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
|
@ -14,36 +14,49 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { memo, useState, useMemo, useEffect } from 'react'
|
||||
import { memo, useState, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Cancel as CloseIcon, NavArrowDown as CaretIcon } from 'iconoir-react'
|
||||
|
||||
import {
|
||||
Paper,
|
||||
styled,
|
||||
useMediaQuery,
|
||||
Paper,
|
||||
Popper,
|
||||
Typography,
|
||||
useTheme,
|
||||
IconButton,
|
||||
Button,
|
||||
Fade,
|
||||
Box,
|
||||
buttonClasses,
|
||||
ClickAwayListener,
|
||||
} from '@mui/material'
|
||||
|
||||
const callAll =
|
||||
(...fns) =>
|
||||
(...args) =>
|
||||
fns.forEach((fn) => fn && fn?.(...args))
|
||||
|
||||
const StyledPopper = styled(Popper)(({ theme }) => ({
|
||||
boxShadow: theme.shadows[1],
|
||||
zIndex: theme.zIndex.modal + 1,
|
||||
[theme.breakpoints.down('xs')]: { width: '100%', height: '100%' },
|
||||
}))
|
||||
|
||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
[theme.breakpoints.down('xs')]: { width: '100%', height: '100%' },
|
||||
}))
|
||||
|
||||
const HeaderPopover = memo(
|
||||
({
|
||||
id,
|
||||
icon,
|
||||
buttonLabel,
|
||||
buttonProps,
|
||||
onMouseHover,
|
||||
buttonProps: { onClick, ...buttonProps } = {},
|
||||
headerTitle,
|
||||
popperProps,
|
||||
onClickAway,
|
||||
children,
|
||||
}) => {
|
||||
const { zIndex } = useTheme()
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.only('xs'))
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
@ -54,18 +67,7 @@ const HeaderPopover = memo(
|
||||
setOpen((previousOpen) => !previousOpen)
|
||||
}
|
||||
|
||||
const handleClose = () => setOpen(false)
|
||||
|
||||
const mobileStyles = useMemo(
|
||||
() => ({
|
||||
...(isMobile && {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}),
|
||||
}),
|
||||
[isMobile]
|
||||
)
|
||||
|
||||
const handleClose = callAll(onClickAway, () => setOpen(false))
|
||||
const canBeOpen = open && Boolean(anchorEl)
|
||||
const hasId = canBeOpen ? id : undefined
|
||||
|
||||
@ -79,9 +81,7 @@ const HeaderPopover = memo(
|
||||
aria-haspopup
|
||||
aria-describedby={hasId}
|
||||
aria-expanded={open ? 'true' : 'false'}
|
||||
{...(onMouseHover
|
||||
? { onMouseEnter: handleClick, onMouseLeave: handleClose }
|
||||
: { onClick: handleClick })}
|
||||
onClick={callAll(handleClick, onClick)}
|
||||
size="small"
|
||||
endIcon={<CaretIcon />}
|
||||
startIcon={icon}
|
||||
@ -94,50 +94,38 @@ const HeaderPopover = memo(
|
||||
>
|
||||
{!isMobile && buttonLabel}
|
||||
</Button>
|
||||
<Popper
|
||||
<StyledPopper
|
||||
id={hasId}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
transition
|
||||
placement="bottom-end"
|
||||
keepMounted={false}
|
||||
style={{
|
||||
zIndex: zIndex.modal + 1,
|
||||
...mobileStyles,
|
||||
}}
|
||||
{...popperProps}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<Fade {...TransitionProps} timeout={300}>
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{ p: headerTitle ? 2 : 0, ...mobileStyles }}
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<StyledPaper variant="outlined" sx={{ p: headerTitle ? 2 : 0 }}>
|
||||
{(headerTitle || isMobile) && (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
borderBottom="1px solid"
|
||||
borderColor="divider"
|
||||
>
|
||||
{(headerTitle || isMobile) && (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
borderBottom="1px solid"
|
||||
borderColor="divider"
|
||||
>
|
||||
{headerTitle && (
|
||||
<Typography variant="body1">{headerTitle}</Typography>
|
||||
)}
|
||||
{isMobile && (
|
||||
<IconButton onClick={handleClose} size="large">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
{headerTitle && (
|
||||
<Typography variant="body1">{headerTitle}</Typography>
|
||||
)}
|
||||
{children({ handleClose: handleClose })}
|
||||
</Paper>
|
||||
</Fade>
|
||||
</ClickAwayListener>
|
||||
)}
|
||||
</Popper>
|
||||
{isMobile && (
|
||||
<IconButton onClick={handleClose} size="large">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{children({ handleClose })}
|
||||
</StyledPaper>
|
||||
</ClickAwayListener>
|
||||
</StyledPopper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -146,13 +134,13 @@ const HeaderPopover = memo(
|
||||
HeaderPopover.propTypes = {
|
||||
id: PropTypes.string,
|
||||
icon: PropTypes.node,
|
||||
buttonLabel: PropTypes.string,
|
||||
buttonLabel: PropTypes.any,
|
||||
buttonProps: PropTypes.object,
|
||||
tooltip: PropTypes.any,
|
||||
headerTitle: PropTypes.any,
|
||||
onMouseHover: PropTypes.bool,
|
||||
disablePadding: PropTypes.bool,
|
||||
popperProps: PropTypes.object,
|
||||
onClickAway: PropTypes.func,
|
||||
children: PropTypes.func,
|
||||
}
|
||||
|
||||
@ -164,8 +152,8 @@ HeaderPopover.defaultProps = {
|
||||
buttonProps: {},
|
||||
headerTitle: undefined,
|
||||
disablePadding: false,
|
||||
onMouseHover: false,
|
||||
popperProps: {},
|
||||
onClickAway: undefined,
|
||||
children: () => undefined,
|
||||
}
|
||||
|
||||
|
@ -19,45 +19,49 @@ import { styled, Typography, alpha } from '@mui/material'
|
||||
import { Copy as CopyIcon, Check as CopiedIcon, Cancel } from 'iconoir-react'
|
||||
|
||||
import { useClipboard } from 'client/hooks'
|
||||
import { SCHEMES } from 'client/constants'
|
||||
|
||||
const callAll =
|
||||
(...fns) =>
|
||||
(...args) =>
|
||||
fns.forEach((fn) => fn && fn?.(...args))
|
||||
|
||||
const Chip = styled(Typography)(
|
||||
({ theme: { palette, typography }, state = 'debug', white, icon }) => {
|
||||
({ theme: { palette }, state = 'debug', ownerState }) => {
|
||||
const { dark = state } = palette[state] ?? {}
|
||||
|
||||
const bgColor = alpha(dark, 0.2)
|
||||
const color = white ? palette.common.white : palette.text.primary
|
||||
const isWhite = ownerState.forceWhiteColor
|
||||
const bgColor = alpha(dark, palette.mode === SCHEMES.DARK ? 0.5 : 0.2)
|
||||
const color = isWhite ? palette.common.white : palette.text.primary
|
||||
const iconColor = isWhite ? palette.getContrastText(color) : dark
|
||||
|
||||
return {
|
||||
color,
|
||||
backgroundColor: bgColor,
|
||||
padding: icon ? '0.1rem 0.5rem' : '0.25rem 0.5rem',
|
||||
borderRadius: 6,
|
||||
textTransform: 'uppercase',
|
||||
fontSize: typography.overline.fontSize,
|
||||
fontWeight: 500,
|
||||
lineHeight: 'normal',
|
||||
padding: ownerState.hasIcon ? '0.1rem 0.5rem' : '0.25rem 0.5rem',
|
||||
cursor: 'default',
|
||||
...(icon && {
|
||||
userSelect: 'none',
|
||||
...(ownerState.hasIcon && {
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '0.5em',
|
||||
'& > .icon': {
|
||||
cursor: 'pointer',
|
||||
color,
|
||||
'&:hover': {
|
||||
color: white ? palette.getContrastText(color) : dark,
|
||||
},
|
||||
'&:hover': { color: iconColor },
|
||||
},
|
||||
}),
|
||||
...(ownerState.clickable && {
|
||||
WebkitTapHighlightColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
'&:hover, &:focus': {
|
||||
backgroundColor: alpha(bgColor, 0.3),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const callAll =
|
||||
(...fns) =>
|
||||
(...args) =>
|
||||
fns.forEach((fn) => fn && fn?.(...args))
|
||||
|
||||
const StatusChip = memo(
|
||||
({
|
||||
stateColor,
|
||||
@ -71,6 +75,12 @@ const StatusChip = memo(
|
||||
}) => {
|
||||
const { copy, isCopied } = useClipboard()
|
||||
|
||||
const ownerState = {
|
||||
forceWhiteColor,
|
||||
hasIcon: clipboard || onDelete ? 'true' : undefined,
|
||||
clickable: !!onClick,
|
||||
}
|
||||
|
||||
const handleCopy = useCallback(
|
||||
(evt) => {
|
||||
const textToCopy = typeof clipboard === 'string' ? clipboard : text
|
||||
@ -83,19 +93,29 @@ const StatusChip = memo(
|
||||
|
||||
const handleDelete = useCallback(
|
||||
(evt) => {
|
||||
onDelete(text)
|
||||
onDelete?.(text)
|
||||
evt.stopPropagation()
|
||||
},
|
||||
[text, onDelete]
|
||||
)
|
||||
|
||||
const handleClick = useCallback(
|
||||
(evt) => {
|
||||
onClick?.(text)
|
||||
evt.stopPropagation()
|
||||
},
|
||||
[text, onClick]
|
||||
)
|
||||
|
||||
return (
|
||||
<Chip
|
||||
component="span"
|
||||
variant="overline"
|
||||
lineHeight="normal"
|
||||
borderRadius="0.5em"
|
||||
state={stateColor}
|
||||
white={forceWhiteColor ? 'true' : undefined}
|
||||
icon={clipboard || onDelete ? 'true' : undefined}
|
||||
onClick={callAll(onClick, clipboard && handleCopy)}
|
||||
ownerState={ownerState}
|
||||
onClick={callAll(handleClick, clipboard && handleCopy)}
|
||||
data-cy={dataCy}
|
||||
{...props}
|
||||
>
|
||||
|
@ -13,78 +13,89 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { Fragment, useMemo, ReactElement } from 'react'
|
||||
import { ReactElement, Fragment, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Stack, Button } from '@mui/material'
|
||||
import { Filter } from 'iconoir-react'
|
||||
import { UseTableInstanceProps, UseFiltersState } from 'react-table'
|
||||
import { UseFiltersInstanceProps, UseFiltersState } from 'react-table'
|
||||
|
||||
import { LABEL_COLUMN_ID } from 'client/components/Tables/Enhanced/Utils/GlobalLabel'
|
||||
import HeaderPopover from 'client/components/Header/Popover'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Render all selected sorters.
|
||||
* Render all selected filters.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} [props.className] - Class name for the container
|
||||
* @param {UseTableInstanceProps} props.useTableProps - Table props
|
||||
* @returns {ReactElement} Component JSX
|
||||
*/
|
||||
const GlobalFilter = ({ className, useTableProps }) => {
|
||||
const { rows, columns, setAllFilters, state } = useTableProps
|
||||
const GlobalFilter = memo(
|
||||
(tableProps) => {
|
||||
/** @type {UseFiltersInstanceProps} */
|
||||
const { rows, columns, setAllFilters, state } = tableProps
|
||||
|
||||
/** @type {UseFiltersState} */
|
||||
const { filters } = state
|
||||
/** @type {UseFiltersState} */
|
||||
const { filters } = state
|
||||
|
||||
const columnsCanFilter = useMemo(
|
||||
() => columns.filter(({ canFilter }) => canFilter),
|
||||
[columns]
|
||||
)
|
||||
const columnsCanFilter = useMemo(
|
||||
() => columns.filter(({ canFilter }) => canFilter),
|
||||
[]
|
||||
)
|
||||
|
||||
if (columnsCanFilter.length === 0) {
|
||||
return null
|
||||
}
|
||||
if (columnsCanFilter.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack className={className} direction="row" gap="0.5em" flexWrap="wrap">
|
||||
<HeaderPopover
|
||||
id="filter-by-button"
|
||||
icon={<Filter />}
|
||||
buttonLabel={T.FilterBy}
|
||||
buttonProps={{
|
||||
'data-cy': 'filter-by-button',
|
||||
disableElevation: true,
|
||||
variant: filters?.length > 0 ? 'contained' : 'outlined',
|
||||
color: 'secondary',
|
||||
disabled: rows?.length === 0,
|
||||
}}
|
||||
popperProps={{ placement: 'bottom-end' }}
|
||||
>
|
||||
{() => (
|
||||
<Stack sx={{ width: { xs: '100%', md: 500 }, p: 2 }}>
|
||||
{columnsCanFilter.map((column, idx) => (
|
||||
<Fragment key={idx}>{column.render('Filter')}</Fragment>
|
||||
))}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => setAllFilters([])}
|
||||
sx={{ mt: 2, alignSelf: 'flex-end' }}
|
||||
>
|
||||
<Translate word={T.Clear} />
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
const filtersAreNotLabel = useMemo(
|
||||
() => filters?.filter(({ id }) => id !== LABEL_COLUMN_ID),
|
||||
[filters]
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack direction="row" gap="0.5em" flexWrap="wrap">
|
||||
<HeaderPopover
|
||||
id="filter-by-button"
|
||||
icon={<Filter />}
|
||||
headerTitle={T.FilterBy}
|
||||
buttonLabel={T.Filter}
|
||||
buttonProps={{
|
||||
'data-cy': 'filter-by-button',
|
||||
disableElevation: true,
|
||||
variant: filtersAreNotLabel.length > 0 ? 'contained' : 'outlined',
|
||||
color: 'secondary',
|
||||
disabled: rows?.length === 0,
|
||||
}}
|
||||
popperProps={{ placement: 'bottom-end' }}
|
||||
>
|
||||
{() => (
|
||||
<Stack sx={{ width: { xs: '100%', md: 500 }, p: 2 }}>
|
||||
{columnsCanFilter.map((column, idx) => (
|
||||
<Fragment key={idx}>{column.render('Filter')}</Fragment>
|
||||
))}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => setAllFilters([])}
|
||||
sx={{ mt: 2, alignSelf: 'flex-end' }}
|
||||
>
|
||||
<Translate word={T.Clear} />
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
(next, prev) =>
|
||||
next.rows === prev.rows && next.state.filters === prev.state.filters
|
||||
)
|
||||
|
||||
GlobalFilter.propTypes = {
|
||||
className: PropTypes.string,
|
||||
useTableProps: PropTypes.object.isRequired,
|
||||
preFilteredRows: PropTypes.array,
|
||||
state: PropTypes.object,
|
||||
}
|
||||
|
||||
GlobalFilter.displayName = 'GlobalFilter'
|
||||
|
||||
export default GlobalFilter
|
||||
|
@ -0,0 +1,160 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement, SyntheticEvent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import CheckIcon from 'iconoir-react/dist/Check'
|
||||
import CancelIcon from 'iconoir-react/dist/Cancel'
|
||||
import { styled, Box, InputBase, Typography } from '@mui/material'
|
||||
import Autocomplete, {
|
||||
autocompleteClasses,
|
||||
AutocompleteChangeDetails,
|
||||
AutocompleteChangeReason,
|
||||
AutocompleteCloseReason,
|
||||
} from '@mui/material/Autocomplete'
|
||||
|
||||
import { StatusCircle } from 'client/components/Status'
|
||||
import { getColorFromString } from 'client/models/Helper'
|
||||
import { Translate, Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const StyledInput = styled(InputBase)(
|
||||
({ theme: { shape, palette, transitions } }) => ({
|
||||
padding: 10,
|
||||
width: '100%',
|
||||
'& input': {
|
||||
padding: 6,
|
||||
transition: transitions.create(['border-color', 'box-shadow']),
|
||||
border: `1px solid ${palette.divider}`,
|
||||
borderRadius: shape.borderRadius / 2,
|
||||
fontSize: 14,
|
||||
'&:focus': {
|
||||
boxShadow: `0px 0px 0px 3px ${palette.secondary[palette.mode]}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
const StyledAutocompletePopper = styled('div')(({ theme }) => ({
|
||||
[`& .${autocompleteClasses.paper}`]: {
|
||||
boxShadow: 'none',
|
||||
margin: 0,
|
||||
color: 'inherit',
|
||||
fontSize: 13,
|
||||
},
|
||||
[`& .${autocompleteClasses.listbox}`]: {
|
||||
padding: 0,
|
||||
[`& .${autocompleteClasses.option}`]: {
|
||||
minHeight: 'auto',
|
||||
alignItems: 'flex-start',
|
||||
padding: 8,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
'&[aria-selected="true"]': {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
[`&.${autocompleteClasses.focused}, &.${autocompleteClasses.focused}[aria-selected="true"]`]:
|
||||
{
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
},
|
||||
},
|
||||
[`&.${autocompleteClasses.popperDisablePortal}`]: {
|
||||
position: 'relative',
|
||||
},
|
||||
}))
|
||||
|
||||
const PopperComponent = ({ disablePortal, anchorEl, open, ...other }) => (
|
||||
<StyledAutocompletePopper {...other} />
|
||||
)
|
||||
|
||||
PopperComponent.propTypes = {
|
||||
anchorEl: PropTypes.any,
|
||||
disablePortal: PropTypes.bool,
|
||||
open: PropTypes.bool,
|
||||
}
|
||||
|
||||
/**
|
||||
* AutoComplete to filter rows by label.
|
||||
*
|
||||
* @param {object} props - Component props
|
||||
* @param {string[]} props.currentValue - The current value of the filter
|
||||
* @param {function(SyntheticEvent, AutocompleteChangeReason, AutocompleteChangeDetails)} props.handleChange - Handle change event
|
||||
* @param {function(SyntheticEvent, AutocompleteCloseReason)} props.handleClose - Handle close event
|
||||
* @param {string[]} props.labels - The list of labels to filter
|
||||
* @param {string[]} props.filters - The current filters
|
||||
* @returns {ReactElement} Filter component
|
||||
*/
|
||||
const FilterByLabel = ({
|
||||
currentValue = [],
|
||||
filters = [],
|
||||
labels = [],
|
||||
handleChange,
|
||||
handleClose,
|
||||
}) => (
|
||||
<Autocomplete
|
||||
open
|
||||
multiple
|
||||
onClose={handleClose}
|
||||
value={currentValue}
|
||||
onChange={handleChange}
|
||||
disableCloseOnSelect
|
||||
PopperComponent={PopperComponent}
|
||||
renderTags={() => null}
|
||||
noOptionsText={<Translate word={T.NoLabels} />}
|
||||
renderOption={(props, option, { selected }) => (
|
||||
<Box component="li" gap="0.5em" {...props}>
|
||||
<CheckIcon style={{ visibility: selected ? 'visible' : 'hidden' }} />
|
||||
<StatusCircle color={getColorFromString(option)} size={18} />
|
||||
<Typography noWrap variant="body2" sx={{ flexGrow: 1 }}>
|
||||
{option}
|
||||
</Typography>
|
||||
<CancelIcon style={{ visibility: selected ? 'visible' : 'hidden' }} />
|
||||
</Box>
|
||||
)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
Array.isArray(value) ? value.includes(option) : value === option
|
||||
}
|
||||
options={[...labels].sort((a, b) => {
|
||||
// Display the selected labels first.
|
||||
let ai = filters.indexOf(a)
|
||||
ai = ai === -1 ? filters.length + labels.indexOf(a) : ai
|
||||
let bi = filters.indexOf(b)
|
||||
bi = bi === -1 ? filters.length + labels.indexOf(b) : bi
|
||||
|
||||
return ai - bi
|
||||
})}
|
||||
renderInput={(params) => (
|
||||
<StyledInput
|
||||
ref={params.InputProps.ref}
|
||||
inputProps={params.inputProps}
|
||||
autoFocus
|
||||
placeholder={Tr(T.FilterLabels)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
FilterByLabel.propTypes = {
|
||||
currentValue: PropTypes.array,
|
||||
filters: PropTypes.array,
|
||||
labels: PropTypes.array,
|
||||
handleChange: PropTypes.func,
|
||||
handleClose: PropTypes.func,
|
||||
}
|
||||
|
||||
FilterByLabel.displayName = 'FilterByLabel'
|
||||
|
||||
export default FilterByLabel
|
@ -0,0 +1,121 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 { ReactElement, useState, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import SettingsIcon from 'iconoir-react/dist/Settings'
|
||||
import { Stack } from '@mui/material'
|
||||
import { UseFiltersInstanceProps } from 'react-table'
|
||||
|
||||
import FilterByLabel from 'client/components/Tables/Enhanced/Utils/GlobalLabel/Filter'
|
||||
import HeaderPopover from 'client/components/Header/Popover'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const LABEL_COLUMN_ID = 'label'
|
||||
|
||||
const getLabels = (rows) =>
|
||||
rows
|
||||
?.map((row) => row.values[LABEL_COLUMN_ID]?.split(','))
|
||||
.filter(Boolean)
|
||||
.flat()
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
|
||||
/**
|
||||
* Button to filter rows by label or assign labels to selected rows.
|
||||
*
|
||||
* @returns {ReactElement} Button component
|
||||
*/
|
||||
const GlobalLabel = memo(
|
||||
(tableProps) => {
|
||||
const [pendingValue, setPendingValue] = useState([])
|
||||
|
||||
/** @type {UseFiltersInstanceProps} */
|
||||
const { setFilter, preFilteredRows, state } = tableProps
|
||||
|
||||
const labels = useMemo(
|
||||
() => [...new Set(getLabels(preFilteredRows))],
|
||||
[preFilteredRows]
|
||||
)
|
||||
|
||||
const filters = useMemo(
|
||||
() =>
|
||||
state.filters
|
||||
.filter(({ id }) => id === LABEL_COLUMN_ID)
|
||||
.map(({ value }) => value),
|
||||
[state.filters]
|
||||
)
|
||||
|
||||
if (labels.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack direction="row" gap="0.5em" flexWrap="wrap">
|
||||
<HeaderPopover
|
||||
id="filter-by-label"
|
||||
icon={<SettingsIcon />}
|
||||
headerTitle={<Translate word={T.FilterByLabel} />}
|
||||
buttonLabel={<Translate word={T.Label} />}
|
||||
buttonProps={{
|
||||
'data-cy': 'filter-by-label',
|
||||
disableElevation: true,
|
||||
variant: filters?.length > 0 ? 'contained' : 'outlined',
|
||||
color: 'secondary',
|
||||
disabled: preFilteredRows?.length === 0,
|
||||
onClick: () => setPendingValue(filters),
|
||||
}}
|
||||
popperProps={{ placement: 'bottom-end' }}
|
||||
onClickAway={() => setFilter(LABEL_COLUMN_ID, pendingValue)}
|
||||
>
|
||||
{({ handleClose }) => (
|
||||
<FilterByLabel
|
||||
currentValue={pendingValue}
|
||||
labels={labels}
|
||||
filters={filters}
|
||||
handleChange={(event, newValue, reason) => {
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
event.key === 'Backspace' &&
|
||||
reason === 'removeOption'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
setPendingValue(newValue)
|
||||
}}
|
||||
handleClose={(event, reason) => {
|
||||
reason === 'escape' && handleClose()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
(next, prev) =>
|
||||
next.preFilteredRows === prev.preFilteredRows &&
|
||||
next.state.filters === prev.state.filters
|
||||
)
|
||||
|
||||
GlobalLabel.propTypes = {
|
||||
preFilteredRows: PropTypes.array,
|
||||
state: PropTypes.object,
|
||||
}
|
||||
|
||||
GlobalLabel.displayName = 'GlobalLabel'
|
||||
|
||||
export default GlobalLabel
|
@ -13,16 +13,12 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useEffect, useMemo, ReactElement } from 'react'
|
||||
import { ReactElement, useEffect, useMemo, memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { SortDown, ArrowDown, ArrowUp } from 'iconoir-react'
|
||||
import { MenuItem, MenuList, Stack } from '@mui/material'
|
||||
import {
|
||||
UseTableInstanceProps,
|
||||
UseSortByInstanceProps,
|
||||
UseSortByState,
|
||||
} from 'react-table'
|
||||
import { UseSortByInstanceProps, UseSortByState } from 'react-table'
|
||||
|
||||
import HeaderPopover from 'client/components/Header/Popover'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
@ -31,74 +27,78 @@ import { T } from 'client/constants'
|
||||
/**
|
||||
* Render all selected sorters.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} [props.className] - Class name for the container
|
||||
* @param {UseTableInstanceProps} props.useTableProps - Table props
|
||||
* @returns {ReactElement} Component JSX
|
||||
*/
|
||||
const GlobalSort = ({ className, useTableProps }) => {
|
||||
const { headers, state } = useTableProps
|
||||
const GlobalSort = memo(
|
||||
(useTableProps) => {
|
||||
/** @type {UseSortByInstanceProps} */
|
||||
const { headers, state } = useTableProps
|
||||
|
||||
/** @type {UseSortByInstanceProps} */
|
||||
const { setSortBy } = useTableProps
|
||||
/** @type {UseSortByInstanceProps} */
|
||||
const { setSortBy } = useTableProps
|
||||
|
||||
/** @type {UseSortByState} */
|
||||
const { sortBy } = state
|
||||
/** @type {UseSortByState} */
|
||||
const { sortBy } = state
|
||||
|
||||
const sorters = useMemo(
|
||||
() =>
|
||||
headers
|
||||
.filter((header) => header.canSort && header.isVisible)
|
||||
.map((header) => {
|
||||
const sorter = sortBy.find((s) => s.id === header.id)
|
||||
const sorters = useMemo(
|
||||
() =>
|
||||
headers
|
||||
.filter((header) => header.canSort && header.isVisible)
|
||||
.map((header) => {
|
||||
const sorter = sortBy.find((s) => s.id === header.id)
|
||||
|
||||
return { ...header, ...sorter }
|
||||
}),
|
||||
[headers.length, sortBy?.[0]?.id, sortBy?.[0]?.desc]
|
||||
)
|
||||
return { ...header, ...sorter }
|
||||
}),
|
||||
[headers.length, sortBy?.[0]?.id, sortBy?.[0]?.desc]
|
||||
)
|
||||
|
||||
const handleClick = (id, name, prevDesc = true) => {
|
||||
setSortBy([{ id, desc: !prevDesc, name }])
|
||||
}
|
||||
const handleClick = (id, name, prevDesc = true) => {
|
||||
setSortBy([{ id, desc: !prevDesc, name }])
|
||||
}
|
||||
|
||||
useEffect(() => () => setSortBy([]), [])
|
||||
useEffect(() => () => setSortBy([]), [])
|
||||
|
||||
if (sorters.length === 0) {
|
||||
return null
|
||||
}
|
||||
if (sorters.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack className={className} direction="row" gap="0.5em" flexWrap="wrap">
|
||||
<HeaderPopover
|
||||
id="sort-by-button"
|
||||
icon={<SortDown />}
|
||||
buttonLabel={T.SortBy}
|
||||
buttonProps={{
|
||||
'data-cy': 'sort-by-button',
|
||||
disableElevation: true,
|
||||
variant: sortBy?.length > 0 ? 'contained' : 'outlined',
|
||||
color: 'secondary',
|
||||
}}
|
||||
popperProps={{ placement: 'bottom-end' }}
|
||||
>
|
||||
{() => (
|
||||
<MenuList>
|
||||
{sorters?.map(({ id, Header: name, desc }) => (
|
||||
<MenuItem key={id} onClick={() => handleClick(id, name, desc)}>
|
||||
{desc !== undefined && (desc ? <ArrowUp /> : <ArrowDown />)}
|
||||
<Translate word={name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Stack direction="row" gap="0.5em" flexWrap="wrap">
|
||||
<HeaderPopover
|
||||
id="sort-by-button"
|
||||
icon={<SortDown />}
|
||||
headerTitle={T.SortBy}
|
||||
buttonLabel={T.Sort}
|
||||
buttonProps={{
|
||||
'data-cy': 'sort-by-button',
|
||||
disableElevation: true,
|
||||
variant: sortBy?.length > 0 ? 'contained' : 'outlined',
|
||||
color: 'secondary',
|
||||
}}
|
||||
popperProps={{ placement: 'bottom-end' }}
|
||||
>
|
||||
{() => (
|
||||
<MenuList>
|
||||
{sorters?.map(({ id, Header: name, desc }) => (
|
||||
<MenuItem key={id} onClick={() => handleClick(id, name, desc)}>
|
||||
{desc !== undefined && (desc ? <ArrowUp /> : <ArrowDown />)}
|
||||
<Translate word={name} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
)}
|
||||
</HeaderPopover>
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
(next, prev) =>
|
||||
next.headers?.length === prev.headers?.length &&
|
||||
next.state?.sortBy === prev.state?.sortBy
|
||||
)
|
||||
|
||||
GlobalSort.propTypes = {
|
||||
className: PropTypes.string,
|
||||
useTableProps: PropTypes.object.isRequired,
|
||||
preFilteredRows: PropTypes.array,
|
||||
state: PropTypes.object,
|
||||
}
|
||||
|
||||
GlobalSort.displayName = 'GlobalSort'
|
||||
|
@ -15,6 +15,9 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
|
||||
import GlobalActions from 'client/components/Tables/Enhanced/Utils/GlobalActions'
|
||||
import GlobalLabel, {
|
||||
LABEL_COLUMN_ID,
|
||||
} from 'client/components/Tables/Enhanced/Utils/GlobalLabel'
|
||||
import GlobalFilter from 'client/components/Tables/Enhanced/Utils/GlobalFilter'
|
||||
import GlobalSearch from 'client/components/Tables/Enhanced/Utils/GlobalSearch'
|
||||
import GlobalSelectedRows from 'client/components/Tables/Enhanced/Utils/GlobalSelectedRows'
|
||||
@ -25,11 +28,15 @@ export * from 'client/components/Tables/Enhanced/Utils/GlobalActions/Action'
|
||||
export * from 'client/components/Tables/Enhanced/Utils/utils'
|
||||
|
||||
export {
|
||||
// Components
|
||||
CategoryFilter,
|
||||
GlobalActions,
|
||||
GlobalLabel,
|
||||
GlobalFilter,
|
||||
GlobalSearch,
|
||||
GlobalSelectedRows,
|
||||
GlobalSort,
|
||||
TimeFilter,
|
||||
// Constants
|
||||
LABEL_COLUMN_ID,
|
||||
}
|
||||
|
@ -33,11 +33,11 @@ export const createColumns = ({ filters = {}, columns = [] }) => {
|
||||
if (Object.keys(filters).length === 0) return columns
|
||||
|
||||
return columns.map((column) => {
|
||||
const { id = '', accessor, noFilterIds = [] } = column
|
||||
const { id = '', accessor } = column
|
||||
|
||||
// noFilterIds is a list of column ids that should not have a filter
|
||||
// it's defined in the resource columns definition
|
||||
if (noFilterIds.includes(id)) return column
|
||||
if (columns.noFilterIds?.includes(id)) return column
|
||||
|
||||
const filterById = !!filters[String(id.toLowerCase())]
|
||||
|
||||
|
@ -32,14 +32,16 @@ import {
|
||||
UseRowSelectRowProps,
|
||||
} from 'react-table'
|
||||
|
||||
import Pagination from 'client/components/Tables/Enhanced/pagination'
|
||||
import {
|
||||
GlobalActions,
|
||||
GlobalSearch,
|
||||
GlobalFilter,
|
||||
GlobalLabel,
|
||||
GlobalSort,
|
||||
GlobalSelectedRows,
|
||||
LABEL_COLUMN_ID,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
import Pagination from 'client/components/Tables/Enhanced/pagination'
|
||||
import EnhancedTableStyles from 'client/components/Tables/Enhanced/styles'
|
||||
|
||||
import { Translate } from 'client/components/HOC'
|
||||
@ -57,6 +59,7 @@ const EnhancedTable = ({
|
||||
isLoading,
|
||||
displaySelectedRows,
|
||||
disableRowSelect,
|
||||
disableGlobalLabel,
|
||||
disableGlobalSort,
|
||||
onSelectedRowsChange,
|
||||
pageSize = 10,
|
||||
@ -125,11 +128,12 @@ const EnhancedTable = ({
|
||||
page,
|
||||
gotoPage,
|
||||
pageCount,
|
||||
state: { pageIndex, selectedRowIds, ...state },
|
||||
setFilter,
|
||||
state,
|
||||
} = useTableProps
|
||||
|
||||
const gotoRowPage = async (row) => {
|
||||
const pageIdx = Math.floor(row.index / state.pageSize)
|
||||
const pageIdx = Math.floor(row.index / pageSize)
|
||||
|
||||
await gotoPage(pageIdx)
|
||||
|
||||
@ -141,11 +145,11 @@ const EnhancedTable = ({
|
||||
|
||||
useMountedLayoutEffect(() => {
|
||||
const selectedRows = preFilteredRows
|
||||
.filter((row) => !!selectedRowIds[row.id])
|
||||
.filter((row) => !!state.selectedRowIds[row.id])
|
||||
.map((row) => ({ ...row, gotoPage: () => gotoRowPage(row) }))
|
||||
|
||||
onSelectedRowsChange?.(selectedRows)
|
||||
}, [selectedRowIds])
|
||||
}, [state.selectedRowIds])
|
||||
|
||||
const handleChangePage = (newPage) => {
|
||||
gotoPage(newPage)
|
||||
@ -153,7 +157,7 @@ const EnhancedTable = ({
|
||||
const canNextPage =
|
||||
pageCount === -1 ? page.length >= pageSize : newPage < pageCount - 1
|
||||
|
||||
newPage > pageIndex && !canNextPage && fetchMore?.()
|
||||
newPage > state.pageIndex && !canNextPage && fetchMore?.()
|
||||
}
|
||||
|
||||
return (
|
||||
@ -192,8 +196,9 @@ const EnhancedTable = ({
|
||||
|
||||
{/* FILTERS */}
|
||||
<div className={styles.filters}>
|
||||
<GlobalFilter useTableProps={useTableProps} />
|
||||
{!disableGlobalSort && <GlobalSort useTableProps={useTableProps} />}
|
||||
{!disableGlobalLabel && <GlobalLabel {...useTableProps} />}
|
||||
<GlobalFilter {...useTableProps} />
|
||||
{!disableGlobalSort && <GlobalSort {...useTableProps} />}
|
||||
</div>
|
||||
|
||||
{/* SELECTED ROWS */}
|
||||
@ -240,6 +245,15 @@ const EnhancedTable = ({
|
||||
original={original}
|
||||
value={values}
|
||||
className={isSelected ? 'selected' : ''}
|
||||
onClickLabel={(label) => {
|
||||
const currentFilter =
|
||||
state.filters
|
||||
?.filter(({ id }) => id === LABEL_COLUMN_ID)
|
||||
?.map(({ value }) => value) || []
|
||||
|
||||
const nextFilter = [...new Set([...currentFilter, label])]
|
||||
setFilter(LABEL_COLUMN_ID, nextFilter)
|
||||
}}
|
||||
onClick={() => {
|
||||
typeof onRowClick === 'function' && onRowClick(original)
|
||||
|
||||
@ -277,6 +291,7 @@ EnhancedTable.propTypes = {
|
||||
}),
|
||||
refetch: PropTypes.func,
|
||||
isLoading: PropTypes.bool,
|
||||
disableGlobalLabel: PropTypes.bool,
|
||||
disableGlobalSort: PropTypes.bool,
|
||||
disableRowSelect: PropTypes.bool,
|
||||
displaySelectedRows: PropTypes.bool,
|
||||
|
@ -27,11 +27,17 @@ const COLUMNS = [
|
||||
{ Header: T.State, id: 'state', accessor: (row) => getState(row)?.name },
|
||||
{ Header: T.Type, id: 'type', accessor: getType },
|
||||
{ Header: T.Size, id: 'size', accessor: 'SIZE' },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{ Header: T.RegistrationTime, id: 'time', accessor: 'REGTIME' },
|
||||
{ Header: T.Marketplace, id: 'marketplace', accessor: 'MARKETPLACE' },
|
||||
{ Header: T.Zone, id: 'zone', accessor: 'ZONE_ID' },
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'time', 'size']
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'time', 'size', 'label']
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -13,21 +13,46 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { memo } from 'react'
|
||||
import { memo, useMemo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import api from 'client/features/OneApi/marketplaceApp'
|
||||
import api, {
|
||||
useUpdateAppMutation,
|
||||
} from 'client/features/OneApi/marketplaceApp'
|
||||
import { MarketplaceAppCard } from 'client/components/Cards'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateAppMutation()
|
||||
|
||||
const state = api.endpoints.getMarketplaceApps.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((app) => +app.ID === +original.ID),
|
||||
})
|
||||
|
||||
return <MarketplaceAppCard app={state ?? original} rootProps={props} />
|
||||
const memoApp = useMemo(() => state ?? original, [state, original])
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoApp.TEMPLATE?.LABELS?.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newUserTemplate = { ...memoApp.TEMPLATE, LABELS: newLabels }
|
||||
const templateXml = jsonToXml(newUserTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoApp.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
return (
|
||||
<MarketplaceAppCard
|
||||
app={memoApp}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
@ -36,7 +61,9 @@ Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
handleClick: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'MarketplaceAppRow'
|
||||
|
@ -23,7 +23,7 @@ import { VmTemplateCard } from 'client/components/Cards'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateTemplateMutation()
|
||||
|
||||
const state = vmTemplateApi.endpoints.getTemplates.useQueryState(
|
||||
@ -52,6 +52,7 @@ const Row = memo(
|
||||
<VmTemplateCard
|
||||
template={memoTemplate}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
@ -64,7 +65,8 @@ Row.propTypes = {
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
handleClick: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'VmTemplateRow'
|
||||
|
@ -36,6 +36,12 @@ const COLUMNS = [
|
||||
{ Header: T.Group, id: 'group', accessor: 'GNAME' },
|
||||
{ Header: T.StartTime, id: 'time', accessor: 'STIME' },
|
||||
{ Header: T.Locked, id: 'locked', accessor: 'LOCK' },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'USER_TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
{ Header: T.Type, id: 'type', accessor: getType },
|
||||
{
|
||||
Header: T.IP,
|
||||
@ -50,6 +56,6 @@ const COLUMNS = [
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'ips', 'time']
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'ips', 'time', 'label']
|
||||
|
||||
export default COLUMNS
|
||||
|
@ -26,7 +26,7 @@ const { VNC, RDP, SSH, VMRC } = VM_ACTIONS
|
||||
const CONNECTION_TYPES = [VNC, RDP, SSH, VMRC]
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, ...props }) => {
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateUserTemplateMutation()
|
||||
|
||||
const state = vmApi.endpoints.getVms.useQueryState(undefined, {
|
||||
@ -52,6 +52,7 @@ const Row = memo(
|
||||
<VirtualMachineCard
|
||||
vm={memoVm}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
actions={
|
||||
<>
|
||||
@ -75,7 +76,8 @@ Row.propTypes = {
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
handleClick: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'VirtualMachineRow'
|
||||
|
@ -18,10 +18,15 @@ module.exports = {
|
||||
Back: 'Back',
|
||||
Previous: 'Previous',
|
||||
Next: 'Next',
|
||||
Sort: 'Sort',
|
||||
SortBy: 'Sort by',
|
||||
FilterBy: 'Filter by',
|
||||
Filter: 'Filter',
|
||||
Filters: 'Filters',
|
||||
FilterBy: 'Filter by',
|
||||
FilterLabels: 'Filter labels',
|
||||
FilterByLabel: 'Filter by label',
|
||||
Label: 'Label',
|
||||
NoLabels: 'NoLabels',
|
||||
All: 'All',
|
||||
On: 'On',
|
||||
ToggleAllCurrentPageRowsSelected: 'Toggle all current page rows selected',
|
||||
|
@ -40,7 +40,7 @@ function Clusters() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<ClustersTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -40,7 +40,7 @@ function Datastores() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<DatastoresTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -40,7 +40,7 @@ function Groups() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<GroupsTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -43,7 +43,7 @@ function Hosts() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<HostsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
|
@ -40,7 +40,7 @@ function Images() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<ImagesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -43,7 +43,7 @@ function MarketplaceApps() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<MarketplaceAppsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
|
@ -40,7 +40,7 @@ function Marketplaces() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<MarketplacesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -42,7 +42,7 @@ function ServiceTemplates() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<ServiceTemplatesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -42,7 +42,7 @@ function Services() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<ServicesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -40,7 +40,7 @@ function Users() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<UsersTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -40,7 +40,7 @@ function VNetworkTemplates() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<VNetworkTemplatesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -44,7 +44,7 @@ function VirtualMachines() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<VmsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
@ -79,7 +79,9 @@ function VirtualMachines() {
|
||||
* @returns {ReactElement} VM details
|
||||
*/
|
||||
const InfoTabs = memo(({ vm, gotoPage, unselect }) => {
|
||||
const [getVm, { isFetching }] = useLazyGetVmQuery()
|
||||
const [getVm, { data: lazyData, isFetching }] = useLazyGetVmQuery()
|
||||
const id = lazyData?.ID ?? vm.ID
|
||||
const name = lazyData?.NAME ?? vm.NAME
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
@ -89,7 +91,7 @@ const InfoTabs = memo(({ vm, gotoPage, unselect }) => {
|
||||
icon={<RefreshDouble />}
|
||||
tooltip={Tr(T.Refresh)}
|
||||
isSubmitting={isFetching}
|
||||
onClick={() => getVm({ id: vm?.ID })}
|
||||
onClick={() => getVm({ id })}
|
||||
/>
|
||||
{typeof gotoPage === 'function' && (
|
||||
<SubmitButton
|
||||
@ -108,10 +110,10 @@ const InfoTabs = memo(({ vm, gotoPage, unselect }) => {
|
||||
/>
|
||||
)}
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${vm?.ID} | ${vm?.NAME}`}
|
||||
{`#${id} | ${name}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<VmTabs id={vm?.ID} />
|
||||
<VmTabs id={id} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
@ -40,7 +40,7 @@ function VirtualNetworks() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<VNetworksTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -44,7 +44,7 @@ function VmTemplates() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<VmTemplatesTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
@ -79,7 +79,9 @@ function VmTemplates() {
|
||||
* @returns {ReactElement} VM Template details
|
||||
*/
|
||||
const InfoTabs = memo(({ template, gotoPage, unselect }) => {
|
||||
const [getTemplate, { isFetching }] = useLazyGetTemplateQuery()
|
||||
const [getTemplate, { data, isFetching }] = useLazyGetTemplateQuery()
|
||||
const id = data?.ID ?? template.ID
|
||||
const name = data?.NAME ?? template.NAME
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
@ -89,7 +91,7 @@ const InfoTabs = memo(({ template, gotoPage, unselect }) => {
|
||||
icon={<RefreshDouble />}
|
||||
tooltip={Tr(T.Refresh)}
|
||||
isSubmitting={isFetching}
|
||||
onClick={() => getTemplate({ id: template?.ID })}
|
||||
onClick={() => getTemplate({ id })}
|
||||
/>
|
||||
{typeof gotoPage === 'function' && (
|
||||
<SubmitButton
|
||||
@ -108,10 +110,10 @@ const InfoTabs = memo(({ template, gotoPage, unselect }) => {
|
||||
/>
|
||||
)}
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${template?.ID || ''} | ${template?.NAME || ''}`}
|
||||
{`#${id} | ${name}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<VmTemplateTabs id={template?.ID} />
|
||||
<VmTemplateTabs id={id} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
@ -40,7 +40,7 @@ function Zones() {
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box {...(hasSelectedRows && getGridProps())}>
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<ZonesTable onSelectedRowsChange={onSelectedRowsChange} />
|
||||
|
||||
{hasSelectedRows && (
|
||||
|
@ -88,7 +88,7 @@ const imageApi = oneApi.injectEndpoints({
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {string} params.template - A string containing the template of the image on syntax XML
|
||||
* @param {string} params.id - The datastore ID
|
||||
* @param {string} params.datastore - The datastore ID
|
||||
* @param {boolean} [params.capacity] - `true` to avoid checking datastore capacity
|
||||
* @returns {number} Image id
|
||||
* @throws Fails when response isn't code 200
|
||||
|
@ -58,7 +58,7 @@ export const onedConfIncludesAction = (
|
||||
onedConf = {},
|
||||
action = 'monitor'
|
||||
) => {
|
||||
const isInZone = onedConf.FEDERATION?.ZONE_ID === marketplace.ZONE_ID
|
||||
const isInZone = (onedConf.FEDERATION?.ZONE_ID ?? '0') === marketplace.ZONE_ID
|
||||
const includesAction = onedConf.MARKET_MAD_CONF?.some(
|
||||
({ APP_ACTIONS, NAME }) =>
|
||||
APP_ACTIONS?.includes(action) &&
|
||||
|
@ -51,11 +51,11 @@ import {
|
||||
validateServerIsSecure,
|
||||
} from './utils/server'
|
||||
|
||||
const appConfig = getFireedgeConfig()
|
||||
|
||||
// set paths
|
||||
genPathResources()
|
||||
|
||||
const appConfig = getFireedgeConfig()
|
||||
|
||||
// set fireedge_key
|
||||
genFireedgeKey()
|
||||
|
||||
|
@ -206,7 +206,7 @@ const importMarket = (res = {}, next = defaultEmptyFunction, params = {}) => {
|
||||
let rtn = httpBadRequest
|
||||
const { resource, id, marketId, associated, vmname } = params
|
||||
|
||||
if (id && ['vm', 'vm-template'].includes(params.resource)) {
|
||||
if (id && ['vm', 'vm-template'].includes(resource)) {
|
||||
let message = ''
|
||||
const paramsCommand = [resource, 'import', `${id}`]
|
||||
|
||||
|
@ -25,7 +25,7 @@ const {
|
||||
const {
|
||||
MARKETAPP_EXPORT,
|
||||
MARKETAPP_DOWNLOAD,
|
||||
MARKETAPP_VMIMPORT,
|
||||
MARKETAPP_IMPORT,
|
||||
MARKETAPP_DOCKERTAGS,
|
||||
} = Actions
|
||||
|
||||
@ -39,7 +39,7 @@ module.exports = [
|
||||
action: downloadApp,
|
||||
},
|
||||
{
|
||||
...Commands[MARKETAPP_VMIMPORT],
|
||||
...Commands[MARKETAPP_IMPORT],
|
||||
action: importMarket,
|
||||
},
|
||||
{
|
||||
|
@ -25,13 +25,13 @@ const { query, resource, postBody } = fromData
|
||||
const basepath = '/marketapp'
|
||||
const MARKETAPP_EXPORT = 'marketapp.export'
|
||||
const MARKETAPP_DOWNLOAD = 'marketapp.download'
|
||||
const MARKETAPP_VMIMPORT = 'marketapp.vmimport'
|
||||
const MARKETAPP_IMPORT = 'marketapp.import'
|
||||
const MARKETAPP_DOCKERTAGS = 'marketapp.dockertags'
|
||||
|
||||
const Actions = {
|
||||
MARKETAPP_EXPORT,
|
||||
MARKETAPP_DOWNLOAD,
|
||||
MARKETAPP_VMIMPORT,
|
||||
MARKETAPP_IMPORT,
|
||||
MARKETAPP_DOCKERTAGS,
|
||||
}
|
||||
|
||||
@ -82,8 +82,8 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
[MARKETAPP_VMIMPORT]: {
|
||||
path: `${basepath}/vmimport/:vmId`,
|
||||
[MARKETAPP_IMPORT]: {
|
||||
path: `${basepath}/vmimport/:id`,
|
||||
httpMethod: POST,
|
||||
auth: true,
|
||||
params: {
|
||||
@ -91,7 +91,7 @@ module.exports = {
|
||||
from: resource,
|
||||
},
|
||||
resource: {
|
||||
from: resource,
|
||||
from: postBody,
|
||||
},
|
||||
associated: {
|
||||
from: postBody,
|
||||
|
@ -35,6 +35,7 @@ const ALLOWED_KEYS_ONED_CONF = [
|
||||
'VN_MAD_CONF',
|
||||
'IM_MAD',
|
||||
'AUTH_MAD',
|
||||
'FEDERATION',
|
||||
]
|
||||
|
||||
/**
|
||||
|
@ -14,14 +14,8 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { env } = require('process')
|
||||
const { resolve } = require('path')
|
||||
const { parse: yamlToJson } = require('yaml')
|
||||
const {
|
||||
defaultConfigFile,
|
||||
defaultWebpackMode,
|
||||
defaultSunstoneConfig,
|
||||
defaultProvisionConfig,
|
||||
protectedConfigData,
|
||||
defaultAppName,
|
||||
defaultApps,
|
||||
@ -31,22 +25,11 @@ const { existsFile, defaultError } = require('server/utils/server')
|
||||
const { messageTerminal } = require('server/utils/general')
|
||||
const { global } = require('window-or-global')
|
||||
|
||||
const defaultPath =
|
||||
env && env.NODE_ENV === defaultWebpackMode ? ['../', '../', '../'] : ['../']
|
||||
|
||||
const basePaths = [__dirname, ...defaultPath, 'etc']
|
||||
|
||||
const getConfigPathByApp = (app) =>
|
||||
({
|
||||
[defaultAppName]:
|
||||
global?.paths?.FIREEDGE_CONFIG ||
|
||||
resolve(...basePaths, defaultConfigFile),
|
||||
[defaultApps.sunstone.name]:
|
||||
global?.paths?.SUNSTONE_CONFIG ||
|
||||
resolve(...basePaths, 'sunstone', defaultSunstoneConfig),
|
||||
[defaultApps.provision.name]:
|
||||
global?.paths?.PROVISION_CONFIG ||
|
||||
resolve(...basePaths, 'provision', defaultProvisionConfig),
|
||||
[defaultAppName]: global?.paths?.FIREEDGE_CONFIG,
|
||||
[defaultApps.sunstone.name]: global?.paths?.SUNSTONE_CONFIG,
|
||||
[defaultApps.provision.name]: global?.paths?.PROVISION_CONFIG,
|
||||
}[app])
|
||||
|
||||
const getProtectedKeysByApp = (app) => protectedConfigData[app] || []
|
||||
|
@ -29,7 +29,7 @@ using namespace std;
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const char Log::error_names[] ={ 'E', 'W', 'I', 'D', 'D', 'D' };
|
||||
const string Log::error_names[] ={ "E", "W", "I", "D", "DD", "DDD" };
|
||||
|
||||
unsigned int Log::zone_id = 0;
|
||||
|
||||
|
@ -241,7 +241,7 @@ void Nebula::start(bool bootstrap_only)
|
||||
NebulaLog::log("ONE",Log::INFO,os);
|
||||
|
||||
os.str("");
|
||||
os << "Log level:" << clevel << " [0=ERROR,1=WARNING,2=INFO,3=DEBUG]";
|
||||
os << "Log level:" << clevel << " [0=ERROR,1=WARNING,2=INFO,3=DEBUG,4=DDEBUG,5=DDDEBUG]";
|
||||
|
||||
NebulaLog::log("ONE",Log::INFO,os);
|
||||
|
||||
|
@ -146,7 +146,7 @@ define(function(require) {
|
||||
CapacityCreate.calculatedRealMemory(context);
|
||||
});
|
||||
|
||||
context.on("change", "#CPU_COST", function() {
|
||||
context.on("change", "#CPU_COST", function() {
|
||||
CapacityCreate.calculatedRealCpu(context);
|
||||
});
|
||||
|
||||
@ -237,12 +237,12 @@ define(function(require) {
|
||||
cpu_input = "1";
|
||||
// [NUMA]
|
||||
$("#numa-pin-policy", formContext)
|
||||
.prop('disabled', false)
|
||||
.prop("disabled", false)
|
||||
.val("SHARED")
|
||||
.prop('disabled', true);
|
||||
.prop("disabled", true);
|
||||
$("#numa-sockets", formContext).val("1");
|
||||
$("#numa-threads", formContext)
|
||||
.prop('disabled', false)
|
||||
.prop("disabled", false)
|
||||
.prop("max", NUMA_THREADS_MAX)
|
||||
.val(NUMA_THREADS_MIN)
|
||||
.keyup(function(){
|
||||
@ -251,7 +251,7 @@ define(function(require) {
|
||||
else if (this.value < NUMA_THREADS_MIN)
|
||||
this.value = NUMA_THREADS_MIN;
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(".disabled_firecracker", formContext).prop("disabled", true);
|
||||
$(".not_firecracker", formContext).hide();
|
||||
@ -377,7 +377,7 @@ define(function(require) {
|
||||
templateJSON["VCENTER_VM_FOLDER"] = WizardFields.retrieveInput($("#vcenter_vm_folder", context));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (templateJSON["HYPERVISOR"] == "lxc"){
|
||||
templateJSON["LXC_UNPRIVILEGED"] = $("#lxc_security_unprivileged", context).val().toUpperCase();
|
||||
}
|
||||
@ -423,7 +423,7 @@ define(function(require) {
|
||||
$.extend(true, templateJSON, CapacityCreate.retrieve($("div.capacityCreate", context)));
|
||||
|
||||
if (templateJSON["MEMORY_COST"] && templateJSON["MEMORY_UNIT_COST"] && templateJSON["MEMORY_UNIT_COST"] == "GB") {
|
||||
templateJSON["MEMORY_COST"] = templateJSON["MEMORY_COST"] / 1024;
|
||||
templateJSON["MEMORY_COST"] = (templateJSON["MEMORY_COST"] / 1024).toString();
|
||||
}
|
||||
if (templateJSON["DISK_COST"]) {
|
||||
templateJSON["DISK_COST"] = (templateJSON["DISK_COST"] / 1024).toString();
|
||||
|
Loading…
Reference in New Issue
Block a user