diff --git a/include/Log.h b/include/Log.h index 2c216b2fea..fbb0b06aa7 100644 --- a/include/Log.h +++ b/include/Log.h @@ -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){}; diff --git a/share/etc/oned.conf b/share/etc/oned.conf index 75cac1d618..ea2022b33f 100644 --- a/share/etc/oned.conf +++ b/share/etc/oned.conf @@ -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 # #******************************************************************************* diff --git a/src/cli/one_helper/oneprovision_helper.rb b/src/cli/one_helper/oneprovision_helper.rb index a1f9e325f4..e39705613d 100644 --- a/src/cli/one_helper/oneprovision_helper.rb +++ b/src/cli/one_helper/oneprovision_helper.rb @@ -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 diff --git a/src/cli/oneflow-template b/src/cli/oneflow-template index efc3ad164c..7ad97526f7 100755 --- a/src/cli/oneflow-template +++ b/src/cli/oneflow-template @@ -59,7 +59,6 @@ require 'tempfile' require 'command_parser' require 'opennebula/oneflow_client' -require 'models' require 'cli_helper' require 'one_helper/oneflowtemplate_helper' diff --git a/src/cli/oneprovision b/src/cli/oneprovision index a3212e238d..e7619a77c7 100755 --- a/src/cli/oneprovision +++ b/src/cli/oneprovision @@ -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 ### diff --git a/src/fireedge/src/client/components/Cards/MarketplaceAppCard.js b/src/fireedge/src/client/components/Cards/MarketplaceAppCard.js index a6e85fa5da..a357450430 100644 --- a/src/fireedge/src/client/components/Cards/MarketplaceAppCard.js +++ b/src/fireedge/src/client/components/Cards/MarketplaceAppCard.js @@ -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 (
@@ -67,6 +98,7 @@ const MarketplaceAppCard = memo( {LOCK && } +
@@ -104,6 +136,8 @@ MarketplaceAppCard.propTypes = { rootProps: PropTypes.shape({ className: PropTypes.string, }), + onClickLabel: PropTypes.func, + onDeleteLabel: PropTypes.func, actions: PropTypes.any, } diff --git a/src/fireedge/src/client/components/Cards/NicCard.js b/src/fireedge/src/client/components/Cards/NicCard.js index fdc92e040c..763a79d117 100644 --- a/src/fireedge/src/client/components/Cards/NicCard.js +++ b/src/fireedge/src/client/components/Cards/NicCard.js @@ -170,7 +170,7 @@ const NicCard = memo( const rulesById = Object.entries(groupBy(SECURITY_GROUPS, 'ID')) return ( - + diff --git a/src/fireedge/src/client/components/Cards/VirtualMachineCard.js b/src/fireedge/src/client/components/Cards/VirtualMachineCard.js index 220fca86ee..cc36dd2921 100644 --- a/src/fireedge/src/client/components/Cards/VirtualMachineCard.js +++ b/src/fireedge/src/client/components/Cards/VirtualMachineCard.js @@ -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, } diff --git a/src/fireedge/src/client/components/Cards/VmTemplateCard.js b/src/fireedge/src/client/components/Cards/VmTemplateCard.js index ba94c6ea11..c9406e0e9c 100644 --- a/src/fireedge/src/client/components/Cards/VmTemplateCard.js +++ b/src/fireedge/src/client/components/Cards/VmTemplateCard.js @@ -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, } diff --git a/src/fireedge/src/client/components/Header/Popover.js b/src/fireedge/src/client/components/Header/Popover.js index 0f88316370..a5bc72e43c 100644 --- a/src/fireedge/src/client/components/Header/Popover.js +++ b/src/fireedge/src/client/components/Header/Popover.js @@ -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={} startIcon={icon} @@ -94,50 +94,38 @@ const HeaderPopover = memo( > {!isMobile && buttonLabel} - - {({ TransitionProps }) => ( - - - + + {(headerTitle || isMobile) && ( + - {(headerTitle || isMobile) && ( - - {headerTitle && ( - {headerTitle} - )} - {isMobile && ( - - - - )} - + {headerTitle && ( + {headerTitle} )} - {children({ handleClose: handleClose })} - - - - )} - + {isMobile && ( + + + + )} + + )} + {children({ handleClose })} + + + ) } @@ -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, } diff --git a/src/fireedge/src/client/components/Status/Chip.js b/src/fireedge/src/client/components/Status/Chip.js index e2863fcdb0..00592200da 100644 --- a/src/fireedge/src/client/components/Status/Chip.js +++ b/src/fireedge/src/client/components/Status/Chip.js @@ -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 ( diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalFilter.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalFilter.js index 5ec301cd4d..6771d02c2b 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalFilter.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalFilter.js @@ -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 ( - - } - 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' }} - > - {() => ( - - {columnsCanFilter.map((column, idx) => ( - {column.render('Filter')} - ))} - - - )} - - - ) -} + const filtersAreNotLabel = useMemo( + () => filters?.filter(({ id }) => id !== LABEL_COLUMN_ID), + [filters] + ) + + return ( + + } + 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' }} + > + {() => ( + + {columnsCanFilter.map((column, idx) => ( + {column.render('Filter')} + ))} + + + )} + + + ) + }, + (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 diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalLabel/Filter.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalLabel/Filter.js new file mode 100644 index 0000000000..993ea958aa --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalLabel/Filter.js @@ -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 }) => ( + +) + +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, +}) => ( + null} + noOptionsText={} + renderOption={(props, option, { selected }) => ( + + + + + {option} + + + + )} + 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) => ( + + )} + /> +) + +FilterByLabel.propTypes = { + currentValue: PropTypes.array, + filters: PropTypes.array, + labels: PropTypes.array, + handleChange: PropTypes.func, + handleClose: PropTypes.func, +} + +FilterByLabel.displayName = 'FilterByLabel' + +export default FilterByLabel diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalLabel/index.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalLabel/index.js new file mode 100644 index 0000000000..2c911f4ffd --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalLabel/index.js @@ -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 ( + + } + headerTitle={} + buttonLabel={} + 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 }) => ( + { + if ( + event.type === 'keydown' && + event.key === 'Backspace' && + reason === 'removeOption' + ) { + return + } + + setPendingValue(newValue) + }} + handleClose={(event, reason) => { + reason === 'escape' && handleClose() + }} + /> + )} + + + ) + }, + (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 diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalSort.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalSort.js index d9559d8ee5..e05051fb94 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalSort.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalSort.js @@ -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 ( - - } - buttonLabel={T.SortBy} - buttonProps={{ - 'data-cy': 'sort-by-button', - disableElevation: true, - variant: sortBy?.length > 0 ? 'contained' : 'outlined', - color: 'secondary', - }} - popperProps={{ placement: 'bottom-end' }} - > - {() => ( - - {sorters?.map(({ id, Header: name, desc }) => ( - handleClick(id, name, desc)}> - {desc !== undefined && (desc ? : )} - - - ))} - - )} - - - ) -} + return ( + + } + 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' }} + > + {() => ( + + {sorters?.map(({ id, Header: name, desc }) => ( + handleClick(id, name, desc)}> + {desc !== undefined && (desc ? : )} + + + ))} + + )} + + + ) + }, + (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' diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js index 40f1bdfbf2..41c6efd919 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js @@ -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, } diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js index 102235da05..7f7b9eb966 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js @@ -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())] diff --git a/src/fireedge/src/client/components/Tables/Enhanced/index.js b/src/fireedge/src/client/components/Tables/Enhanced/index.js index 06637f0773..ab5748535b 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/index.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/index.js @@ -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 */}
- - {!disableGlobalSort && } + {!disableGlobalLabel && } + + {!disableGlobalSort && }
{/* 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, diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/columns.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/columns.js index be367ceea1..d9fdbcda3b 100644 --- a/src/fireedge/src/client/components/Tables/MarketplaceApps/columns.js +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/columns.js @@ -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 diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/row.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/row.js index 6788aff1ae..99c3b7f81b 100644 --- a/src/fireedge/src/client/components/Tables/MarketplaceApps/row.js +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/row.js @@ -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 + 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 ( + + ) }, (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' diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/row.js b/src/fireedge/src/client/components/Tables/VmTemplates/row.js index 36621d1717..91d45c288a 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/row.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/row.js @@ -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( ) @@ -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' diff --git a/src/fireedge/src/client/components/Tables/Vms/columns.js b/src/fireedge/src/client/components/Tables/Vms/columns.js index 57903ffbc5..bb66bdc029 100644 --- a/src/fireedge/src/client/components/Tables/Vms/columns.js +++ b/src/fireedge/src/client/components/Tables/Vms/columns.js @@ -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 diff --git a/src/fireedge/src/client/components/Tables/Vms/row.js b/src/fireedge/src/client/components/Tables/Vms/row.js index 597bd0b950..66aa03a305 100644 --- a/src/fireedge/src/client/components/Tables/Vms/row.js +++ b/src/fireedge/src/client/components/Tables/Vms/row.js @@ -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( @@ -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' diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index cc84e5f460..b77b1d1920 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -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', diff --git a/src/fireedge/src/client/containers/Clusters/index.js b/src/fireedge/src/client/containers/Clusters/index.js index e75edcabaf..b3636e6406 100644 --- a/src/fireedge/src/client/containers/Clusters/index.js +++ b/src/fireedge/src/client/containers/Clusters/index.js @@ -40,7 +40,7 @@ function Clusters() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/Datastores/index.js b/src/fireedge/src/client/containers/Datastores/index.js index 721dc1a73a..1e9a365918 100644 --- a/src/fireedge/src/client/containers/Datastores/index.js +++ b/src/fireedge/src/client/containers/Datastores/index.js @@ -40,7 +40,7 @@ function Datastores() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/Groups/index.js b/src/fireedge/src/client/containers/Groups/index.js index 2876903fb4..c8331c9849 100644 --- a/src/fireedge/src/client/containers/Groups/index.js +++ b/src/fireedge/src/client/containers/Groups/index.js @@ -40,7 +40,7 @@ function Groups() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/Hosts/index.js b/src/fireedge/src/client/containers/Hosts/index.js index ef8b5b3247..5b10b9e0e1 100644 --- a/src/fireedge/src/client/containers/Hosts/index.js +++ b/src/fireedge/src/client/containers/Hosts/index.js @@ -43,7 +43,7 @@ function Hosts() { return ( {({ getGridProps, GutterComponent }) => ( - + {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/MarketplaceApps/index.js b/src/fireedge/src/client/containers/MarketplaceApps/index.js index 83fa77af08..10a647f266 100644 --- a/src/fireedge/src/client/containers/MarketplaceApps/index.js +++ b/src/fireedge/src/client/containers/MarketplaceApps/index.js @@ -43,7 +43,7 @@ function MarketplaceApps() { return ( {({ getGridProps, GutterComponent }) => ( - + {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/ServiceTemplates/index.js b/src/fireedge/src/client/containers/ServiceTemplates/index.js index e967ea7571..c605f472a9 100644 --- a/src/fireedge/src/client/containers/ServiceTemplates/index.js +++ b/src/fireedge/src/client/containers/ServiceTemplates/index.js @@ -42,7 +42,7 @@ function ServiceTemplates() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/Services/index.js b/src/fireedge/src/client/containers/Services/index.js index c6aaa7325a..624b4100df 100644 --- a/src/fireedge/src/client/containers/Services/index.js +++ b/src/fireedge/src/client/containers/Services/index.js @@ -42,7 +42,7 @@ function Services() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/Users/index.js b/src/fireedge/src/client/containers/Users/index.js index 8e60ac2518..ddfad0652f 100644 --- a/src/fireedge/src/client/containers/Users/index.js +++ b/src/fireedge/src/client/containers/Users/index.js @@ -40,7 +40,7 @@ function Users() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/VNetworkTemplates/index.js b/src/fireedge/src/client/containers/VNetworkTemplates/index.js index 26993a40b5..f3660dab6c 100644 --- a/src/fireedge/src/client/containers/VNetworkTemplates/index.js +++ b/src/fireedge/src/client/containers/VNetworkTemplates/index.js @@ -40,7 +40,7 @@ function VNetworkTemplates() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/VirtualMachines/index.js b/src/fireedge/src/client/containers/VirtualMachines/index.js index 46d1538728..c2e1738593 100644 --- a/src/fireedge/src/client/containers/VirtualMachines/index.js +++ b/src/fireedge/src/client/containers/VirtualMachines/index.js @@ -44,7 +44,7 @@ function VirtualMachines() { return ( {({ getGridProps, GutterComponent }) => ( - + { - const [getVm, { isFetching }] = useLazyGetVmQuery() + const [getVm, { data: lazyData, isFetching }] = useLazyGetVmQuery() + const id = lazyData?.ID ?? vm.ID + const name = lazyData?.NAME ?? vm.NAME return ( @@ -89,7 +91,7 @@ const InfoTabs = memo(({ vm, gotoPage, unselect }) => { icon={} tooltip={Tr(T.Refresh)} isSubmitting={isFetching} - onClick={() => getVm({ id: vm?.ID })} + onClick={() => getVm({ id })} /> {typeof gotoPage === 'function' && ( { /> )} - {`#${vm?.ID} | ${vm?.NAME}`} + {`#${id} | ${name}`} - + ) }) diff --git a/src/fireedge/src/client/containers/VirtualNetworks/index.js b/src/fireedge/src/client/containers/VirtualNetworks/index.js index 5838d1e2de..13556a1991 100644 --- a/src/fireedge/src/client/containers/VirtualNetworks/index.js +++ b/src/fireedge/src/client/containers/VirtualNetworks/index.js @@ -40,7 +40,7 @@ function VirtualNetworks() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/containers/VmTemplates/index.js b/src/fireedge/src/client/containers/VmTemplates/index.js index db32a3ab7b..d79c24ed49 100644 --- a/src/fireedge/src/client/containers/VmTemplates/index.js +++ b/src/fireedge/src/client/containers/VmTemplates/index.js @@ -44,7 +44,7 @@ function VmTemplates() { return ( {({ getGridProps, GutterComponent }) => ( - + { - const [getTemplate, { isFetching }] = useLazyGetTemplateQuery() + const [getTemplate, { data, isFetching }] = useLazyGetTemplateQuery() + const id = data?.ID ?? template.ID + const name = data?.NAME ?? template.NAME return ( @@ -89,7 +91,7 @@ const InfoTabs = memo(({ template, gotoPage, unselect }) => { icon={} tooltip={Tr(T.Refresh)} isSubmitting={isFetching} - onClick={() => getTemplate({ id: template?.ID })} + onClick={() => getTemplate({ id })} /> {typeof gotoPage === 'function' && ( { /> )} - {`#${template?.ID || ''} | ${template?.NAME || ''}`} + {`#${id} | ${name}`} - + ) }) diff --git a/src/fireedge/src/client/containers/Zones/index.js b/src/fireedge/src/client/containers/Zones/index.js index 4ebe679033..13efdfdb39 100644 --- a/src/fireedge/src/client/containers/Zones/index.js +++ b/src/fireedge/src/client/containers/Zones/index.js @@ -40,7 +40,7 @@ function Zones() { return ( {({ getGridProps, GutterComponent }) => ( - + {hasSelectedRows && ( diff --git a/src/fireedge/src/client/features/OneApi/image.js b/src/fireedge/src/client/features/OneApi/image.js index 764a986253..c626552eed 100644 --- a/src/fireedge/src/client/features/OneApi/image.js +++ b/src/fireedge/src/client/features/OneApi/image.js @@ -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 diff --git a/src/fireedge/src/client/models/Marketplace.js b/src/fireedge/src/client/models/Marketplace.js index e458e1ab24..c04f4f9ead 100644 --- a/src/fireedge/src/client/models/Marketplace.js +++ b/src/fireedge/src/client/models/Marketplace.js @@ -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) && diff --git a/src/fireedge/src/server/index.js b/src/fireedge/src/server/index.js index 8cdda2b496..fa4c693497 100644 --- a/src/fireedge/src/server/index.js +++ b/src/fireedge/src/server/index.js @@ -51,11 +51,11 @@ import { validateServerIsSecure, } from './utils/server' -const appConfig = getFireedgeConfig() - // set paths genPathResources() +const appConfig = getFireedgeConfig() + // set fireedge_key genFireedgeKey() diff --git a/src/fireedge/src/server/routes/api/marketapp/functions.js b/src/fireedge/src/server/routes/api/marketapp/functions.js index d0c2e2263c..ef48ad2f43 100644 --- a/src/fireedge/src/server/routes/api/marketapp/functions.js +++ b/src/fireedge/src/server/routes/api/marketapp/functions.js @@ -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}`] diff --git a/src/fireedge/src/server/routes/api/marketapp/index.js b/src/fireedge/src/server/routes/api/marketapp/index.js index 1046613916..d8a509e147 100644 --- a/src/fireedge/src/server/routes/api/marketapp/index.js +++ b/src/fireedge/src/server/routes/api/marketapp/index.js @@ -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, }, { diff --git a/src/fireedge/src/server/routes/api/marketapp/routes.js b/src/fireedge/src/server/routes/api/marketapp/routes.js index 6a95c7cab8..4575a3822a 100644 --- a/src/fireedge/src/server/routes/api/marketapp/routes.js +++ b/src/fireedge/src/server/routes/api/marketapp/routes.js @@ -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, diff --git a/src/fireedge/src/server/routes/api/system/functions.js b/src/fireedge/src/server/routes/api/system/functions.js index 2177cfdf34..6efef5c446 100644 --- a/src/fireedge/src/server/routes/api/system/functions.js +++ b/src/fireedge/src/server/routes/api/system/functions.js @@ -35,6 +35,7 @@ const ALLOWED_KEYS_ONED_CONF = [ 'VN_MAD_CONF', 'IM_MAD', 'AUTH_MAD', + 'FEDERATION', ] /** diff --git a/src/fireedge/src/server/utils/yml.js b/src/fireedge/src/server/utils/yml.js index a24f49eb0e..59f5271078 100644 --- a/src/fireedge/src/server/utils/yml.js +++ b/src/fireedge/src/server/utils/yml.js @@ -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] || [] diff --git a/src/log/Log.cc b/src/log/Log.cc index ea43e3fc69..63437ad8c6 100644 --- a/src/log/Log.cc +++ b/src/log/Log.cc @@ -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; diff --git a/src/nebula/Nebula.cc b/src/nebula/Nebula.cc index 530afd7fab..a681fe2eb1 100644 --- a/src/nebula/Nebula.cc +++ b/src/nebula/Nebula.cc @@ -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); diff --git a/src/sunstone/public/app/tabs/templates-tab/form-panels/create/wizard-tabs/general.js b/src/sunstone/public/app/tabs/templates-tab/form-panels/create/wizard-tabs/general.js index 9d8bb3fdf4..6f187482a7 100644 --- a/src/sunstone/public/app/tabs/templates-tab/form-panels/create/wizard-tabs/general.js +++ b/src/sunstone/public/app/tabs/templates-tab/form-panels/create/wizard-tabs/general.js @@ -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();