mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
da5e716bba
commit
d69115283f
@ -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 (
|
||||
<div>
|
||||
<Grid container spacing={1} width={1}>
|
||||
<Grid item style={{ flexGrow: 1 }}>
|
||||
<TextField
|
||||
{...inputProps}
|
||||
fullWidth
|
||||
inputRef={ref}
|
||||
value={internalValue}
|
||||
onChange={(e) => handleChange('value', e.target.value)}
|
||||
rows={3}
|
||||
type="number"
|
||||
label={labelCanBeTranslated(label) ? Tr(label) : label}
|
||||
InputProps={{
|
||||
readOnly,
|
||||
endAdornment: tooltip && <Tooltip title={tooltip} />,
|
||||
}}
|
||||
inputProps={{
|
||||
'data-cy': cy,
|
||||
...{
|
||||
min: fieldProps.min,
|
||||
max: fieldProps.max,
|
||||
step: fieldProps.step,
|
||||
},
|
||||
}}
|
||||
error={Boolean(error)}
|
||||
helperText={
|
||||
error ? (
|
||||
<ErrorHelper label={error?.message} />
|
||||
) : (
|
||||
fieldProps.helperText
|
||||
)
|
||||
}
|
||||
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
|
||||
{...fieldProps}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
select
|
||||
value={unit}
|
||||
InputProps={{
|
||||
readOnly,
|
||||
}}
|
||||
label={Tr(T.MemoryUnit)}
|
||||
onChange={(e) => handleChange('unit', e.target.value)}
|
||||
>
|
||||
{ARRAY_UNITS.map((option, index) => (
|
||||
<option key={`${option}-${index}`} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
(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
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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]}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user