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 }) => (
-
- ))}
-
- )}
-
-
- )
-}
+ 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 }) => (
+
+ ))}
+
+ )}
+
+
+ )
+ },
+ (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();