diff --git a/src/fireedge/src/client/components/FormControl/InformationUnitController.js b/src/fireedge/src/client/components/FormControl/InformationUnitController.js
new file mode 100644
index 0000000000..3f141cbe08
--- /dev/null
+++ b/src/fireedge/src/client/components/FormControl/InformationUnitController.js
@@ -0,0 +1,186 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2023, 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 PropTypes from 'prop-types'
+import { memo, useCallback, useEffect, useState } from 'react'
+
+import { Grid, TextField } from '@mui/material'
+import { useController, useWatch } from 'react-hook-form'
+
+import { ErrorHelper, Tooltip } from 'client/components/FormControl'
+import { Tr, labelCanBeTranslated } from 'client/components/HOC'
+import { T, UNITS } from 'client/constants'
+import { generateKey, prettyBytes } from 'client/utils'
+
+const ARRAY_UNITS = Object.values(UNITS)
+ARRAY_UNITS.splice(0, 1) // remove KB
+const DEFAULT_UNIT = ARRAY_UNITS[0]
+
+const valueInMB = (value = 0, unit = DEFAULT_UNIT) => {
+ const idxUnit = ARRAY_UNITS.indexOf(unit)
+ const numberValue = +value
+
+ return Math.round(numberValue * (idxUnit <= 0 ? 1 : 1024 ** idxUnit))
+}
+
+const InformationUnitController = memo(
+ ({
+ control,
+ cy = `input-${generateKey()}`,
+ name = '',
+ label = '',
+ tooltip,
+ watcher,
+ dependencies,
+ fieldProps = {},
+ readOnly = false,
+ onConditionChange,
+ }) => {
+ const watch = useWatch({
+ name: dependencies,
+ disabled: dependencies == null,
+ defaultValue: Array.isArray(dependencies) ? [] : undefined,
+ })
+
+ const {
+ field: { ref, value = '', onChange, ...inputProps },
+ fieldState: { error },
+ } = useController({ name, control })
+
+ useEffect(() => {
+ if (!watcher || !dependencies || !watch) return
+
+ const watcherValue = watcher(watch)
+ watcherValue !== undefined && onChange(watcherValue)
+ }, [watch, watcher, dependencies])
+
+ const [internalValue, setInternalValue] = useState(+value)
+ const [unit, setUnit] = useState(DEFAULT_UNIT)
+
+ useEffect(() => {
+ const dataUnits = prettyBytes(value, DEFAULT_UNIT, 2, true)
+ setInternalValue(dataUnits.value)
+ setUnit(dataUnits.units)
+ }, [value])
+
+ const handleChange = useCallback(
+ (internalType, valueInput) => {
+ if (internalType === 'value') {
+ setInternalValue(valueInput)
+ } else {
+ setUnit(valueInput)
+ }
+
+ const valueMB =
+ internalType === 'value'
+ ? valueInMB(valueInput, unit)
+ : valueInMB(internalValue, valueInput)
+
+ onChange(valueMB)
+ if (typeof onConditionChange === 'function') {
+ onConditionChange(valueMB)
+ }
+ },
+ [onChange, onConditionChange]
+ )
+
+ return (
+
+
+
+ handleChange('value', e.target.value)}
+ rows={3}
+ type="number"
+ label={labelCanBeTranslated(label) ? Tr(label) : label}
+ InputProps={{
+ readOnly,
+ endAdornment: tooltip && ,
+ }}
+ inputProps={{
+ 'data-cy': cy,
+ ...{
+ min: fieldProps.min,
+ max: fieldProps.max,
+ step: fieldProps.step,
+ },
+ }}
+ error={Boolean(error)}
+ helperText={
+ error ? (
+
+ ) : (
+ fieldProps.helperText
+ )
+ }
+ FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
+ {...fieldProps}
+ />
+
+
+ handleChange('unit', e.target.value)}
+ >
+ {ARRAY_UNITS.map((option, index) => (
+
+ ))}
+
+
+
+
+ )
+ },
+ (prevProps, nextProps) =>
+ prevProps.type === nextProps.type &&
+ prevProps.label === nextProps.label &&
+ prevProps.tooltip === nextProps.tooltip &&
+ prevProps.fieldProps?.value === nextProps.fieldProps?.value &&
+ prevProps.fieldProps?.helperText === nextProps.fieldProps?.helperText &&
+ prevProps.readOnly === nextProps.readOnly
+)
+
+InformationUnitController.propTypes = {
+ control: PropTypes.object,
+ cy: PropTypes.string,
+ type: PropTypes.string,
+ multiline: PropTypes.bool,
+ name: PropTypes.string.isRequired,
+ label: PropTypes.any,
+ tooltip: PropTypes.any,
+ watcher: PropTypes.func,
+ dependencies: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.arrayOf(PropTypes.string),
+ ]),
+ fieldProps: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
+ readOnly: PropTypes.bool,
+ onConditionChange: PropTypes.func,
+}
+
+InformationUnitController.displayName = 'InformationUnitController'
+
+export default InformationUnitController
diff --git a/src/fireedge/src/client/components/FormControl/index.js b/src/fireedge/src/client/components/FormControl/index.js
index 6443fa21c9..ed12418d42 100644
--- a/src/fireedge/src/client/components/FormControl/index.js
+++ b/src/fireedge/src/client/components/FormControl/index.js
@@ -16,6 +16,7 @@
import AutocompleteController from 'client/components/FormControl/AutocompleteController'
import CheckboxController from 'client/components/FormControl/CheckboxController'
import FileController from 'client/components/FormControl/FileController'
+import InformationUnitController from 'client/components/FormControl/InformationUnitController'
import PasswordController from 'client/components/FormControl/PasswordController'
import SelectController from 'client/components/FormControl/SelectController'
import SliderController from 'client/components/FormControl/SliderController'
@@ -25,30 +26,31 @@ import TextController from 'client/components/FormControl/TextController'
import TimeController from 'client/components/FormControl/TimeController'
import ToggleController from 'client/components/FormControl/ToggleController'
+import DockerfileController from 'client/components/FormControl/DockerfileController'
+import ErrorHelper from 'client/components/FormControl/ErrorHelper'
+import InputCode from 'client/components/FormControl/InputCode'
import SubmitButton, {
SubmitButtonPropTypes,
} from 'client/components/FormControl/SubmitButton'
-import InputCode from 'client/components/FormControl/InputCode'
-import DockerfileController from 'client/components/FormControl/DockerfileController'
-import ErrorHelper from 'client/components/FormControl/ErrorHelper'
import Tooltip from 'client/components/FormControl/Tooltip'
export {
AutocompleteController,
CheckboxController,
+ DockerfileController,
+ ErrorHelper,
FileController,
+ InformationUnitController,
+ InputCode,
PasswordController,
SelectController,
SliderController,
+ SubmitButton,
+ SubmitButtonPropTypes,
SwitchController,
TableController,
TextController,
TimeController,
ToggleController,
- SubmitButton,
- SubmitButtonPropTypes,
- InputCode,
- DockerfileController,
- ErrorHelper,
Tooltip,
}
diff --git a/src/fireedge/src/client/components/Forms/FormWithSchema.js b/src/fireedge/src/client/components/Forms/FormWithSchema.js
index c501a7c215..2f34426a2f 100644
--- a/src/fireedge/src/client/components/Forms/FormWithSchema.js
+++ b/src/fireedge/src/client/components/Forms/FormWithSchema.js
@@ -56,6 +56,7 @@ const INPUT_CONTROLLER = {
[INPUT_TYPES.TABLE]: FC.TableController,
[INPUT_TYPES.TOGGLE]: FC.ToggleController,
[INPUT_TYPES.DOCKERFILE]: FC.DockerfileController,
+ [INPUT_TYPES.UNITS]: FC.InformationUnitController,
}
/**
diff --git a/src/fireedge/src/client/components/Forms/Vm/ResizeCapacityForm/schema.js b/src/fireedge/src/client/components/Forms/Vm/ResizeCapacityForm/schema.js
index 26f8f16ef4..1014dbc8ad 100644
--- a/src/fireedge/src/client/components/Forms/Vm/ResizeCapacityForm/schema.js
+++ b/src/fireedge/src/client/components/Forms/Vm/ResizeCapacityForm/schema.js
@@ -28,9 +28,9 @@ const ENFORCE = {
const MEMORY = {
name: 'MEMORY',
- label: [T.MemoryWithUnit, '(MB)'],
+ label: T.Memory,
tooltip: T.MemoryConcept,
- type: INPUT_TYPES.TEXT,
+ type: INPUT_TYPES.UNITS,
htmlType: 'number',
validation: number()
.required()
diff --git a/src/fireedge/src/client/components/Forms/Vm/ResizeDiskForm/schema.js b/src/fireedge/src/client/components/Forms/Vm/ResizeDiskForm/schema.js
index efd5e908a7..9f9ddccc1f 100644
--- a/src/fireedge/src/client/components/Forms/Vm/ResizeDiskForm/schema.js
+++ b/src/fireedge/src/client/components/Forms/Vm/ResizeDiskForm/schema.js
@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { number, object } from 'yup'
+import { INPUT_TYPES, T } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
-import { T, INPUT_TYPES } from 'client/constants'
+import { number, object } from 'yup'
const SIZE = {
name: 'SIZE',
- label: [T.SizeOnUnits, 'MB'],
- type: INPUT_TYPES.TEXT,
+ label: T.Size,
+ type: INPUT_TYPES.UNITS,
htmlType: 'number',
+ grid: { md: 12 },
validation: number()
.required()
.positive()
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js
index 748ea5c257..38595ae22d 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js
@@ -47,7 +47,7 @@ const { vcenter, lxc, firecracker } = HYPERVISORS
export const MEMORY = generateCapacityInput({
name: 'MEMORY',
label: T.Memory,
- tooltip: T.MemoryConceptWithoutUnit,
+ tooltip: T.MemoryConcept,
validation: commonValidation
.integer()
.required()
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js
index 24218e0232..16a05661a5 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js
@@ -17,6 +17,7 @@ import { NumberSchema } from 'yup'
import {
HYPERVISORS,
+ INPUT_TYPES,
T,
USER_INPUT_TYPES,
VmTemplate,
@@ -36,7 +37,7 @@ const { number, numberFloat, range, rangeFloat } = USER_INPUT_TYPES
const TRANSLATES = {
MEMORY: {
name: 'MEMORY',
- label: [T.MemoryWithUnit, '(MB)'],
+ label: T.Memory,
tooltip: T.MemoryConcept,
},
CPU: { name: 'CPU', label: T.PhysicalCpuWithPercent, tooltip: T.CpuConcept },
@@ -98,9 +99,12 @@ export const FIELDS = (
// add positive number validator
isNumber && (schemaUi.validation &&= schemaUi.validation.positive())
- if (isMemory && isRange) {
- // add label format on pretty bytes
- schemaUi.fieldProps = { ...schemaUi.fieldProps, valueLabelFormat }
+ if (isMemory) {
+ schemaUi.type = INPUT_TYPES.UNITS
+ if (isRange) {
+ // add label format on pretty bytes
+ schemaUi.fieldProps = { ...schemaUi.fieldProps, valueLabelFormat }
+ }
}
if (isNumber && divisibleBy4) {
diff --git a/src/fireedge/src/client/constants/index.js b/src/fireedge/src/client/constants/index.js
index 9b68b42fa1..1d6cb79d5e 100644
--- a/src/fireedge/src/client/constants/index.js
+++ b/src/fireedge/src/client/constants/index.js
@@ -140,6 +140,7 @@ export const INPUT_TYPES = {
TABLE: 'table',
TOGGLE: 'toggle',
DOCKERFILE: 'dockerfile',
+ UNITS: 'units',
}
export const DEBUG_LEVEL = {
@@ -209,6 +210,6 @@ export * from 'client/constants/user'
export * from 'client/constants/userInput'
export * from 'client/constants/vdc'
export * from 'client/constants/vm'
-export * from 'client/constants/vmTemplate'
export * from 'client/constants/vmGroup'
+export * from 'client/constants/vmTemplate'
export * from 'client/constants/zone'
diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js
index e419cace98..4225c078ad 100644
--- a/src/fireedge/src/client/constants/translates.js
+++ b/src/fireedge/src/client/constants/translates.js
@@ -852,8 +852,7 @@ module.exports = {
MemoryModification: 'Memory modification',
AllowUsersToModifyMemory:
"Allow users to modify this template's default memory on instantiate",
- MemoryConcept: 'Amount of RAM required for the VM, in Megabytes',
- MemoryConceptWithoutUnit: 'Amount of RAM required for the VM',
+ MemoryConcept: 'Amount of RAM required for the VM',
MemoryConceptUnit: 'Choose unit of memory',
CpuConcept: `
Percentage of CPU divided by 100 required for the
diff --git a/src/fireedge/src/client/utils/helpers.js b/src/fireedge/src/client/utils/helpers.js
index 737321538a..64ca7b52af 100644
--- a/src/fireedge/src/client/utils/helpers.js
+++ b/src/fireedge/src/client/utils/helpers.js
@@ -123,13 +123,20 @@ export const downloadFile = (file) => {
* @param {'KB'|'MB'|'GB'|'TB'|'PB'|'EB'|'ZB'|'YB'} unit - The unit of value. Defaults in KB
* @param {number} fractionDigits
* - Number of digits after the decimal point. Must be in the range 0 - 20, inclusive
+ * @param {boolean} json - return a json with data
* @returns {string} Returns an string displaying sizes for humans.
*/
-export const prettyBytes = (value, unit = UNITS.KB, fractionDigits = 0) => {
+export const prettyBytes = (
+ value,
+ unit = UNITS.KB,
+ fractionDigits = 0,
+ json = false
+) => {
const units = Object.values(UNITS)
let ensuredValue = +value
- if (Math.abs(ensuredValue) === 0) return `${value} ${units[0]}`
+ if (Math.abs(ensuredValue) === 0)
+ return json ? { value, units: unit } : `${value} ${units[0]}`
let idxUnit = units.indexOf(unit)
@@ -140,7 +147,9 @@ export const prettyBytes = (value, unit = UNITS.KB, fractionDigits = 0) => {
const decimals = fractionDigits && ensuredValue % 1 !== 0 ? fractionDigits : 0
- return `${ensuredValue.toFixed(decimals)} ${units[idxUnit]}`
+ return json
+ ? { value: ensuredValue.toFixed(decimals), units: units[idxUnit] }
+ : `${ensuredValue.toFixed(decimals)} ${units[idxUnit]}`
}
/**