mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-22 18:50:08 +03:00
B OpenNebula/one#6229: fix sorter user inputs
Signed-off-by: Jorge Lobo <jlobo@opennebula.io>
This commit is contained in:
parent
bf30d70aaf
commit
0ef438bb8c
@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { INPUT_TYPES, T, FEDERATION_TYPE } from 'client/constants'
|
||||
import { Field, getObjectSchemaFromFields, arrayToOptions } from 'client/utils'
|
||||
import { string } from 'yup'
|
||||
import { ZonesTable } from 'client/components/Tables'
|
||||
import { FEDERATION_TYPE, INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, arrayToOptions, getObjectSchemaFromFields } from 'client/utils'
|
||||
import { string } from 'yup'
|
||||
|
||||
const ACL_TYPE_ZONE_TRANSLATIONS = {
|
||||
ALL: { value: 'ALL', text: T.All },
|
||||
@ -66,11 +66,7 @@ const ZONE = (oneConfig) => ({
|
||||
* @param {object} oneConfig - . ONE config
|
||||
* @returns {Array} - The list of fields
|
||||
*/
|
||||
const FIELDS = (oneConfig) => {
|
||||
console.log(oneConfig)
|
||||
|
||||
return [TYPE(oneConfig), ZONE(oneConfig)]
|
||||
}
|
||||
const FIELDS = (oneConfig) => [TYPE(oneConfig), ZONE(oneConfig)]
|
||||
|
||||
/**
|
||||
* Return the schema.
|
||||
@ -78,10 +74,6 @@ const FIELDS = (oneConfig) => {
|
||||
* @param {object} oneConfig - . ONE config
|
||||
* @returns {object} - The schema
|
||||
*/
|
||||
const SCHEMA = (oneConfig) => {
|
||||
console.log(oneConfig)
|
||||
const SCHEMA = (oneConfig) => getObjectSchemaFromFields(FIELDS(oneConfig))
|
||||
|
||||
return getObjectSchemaFromFields(FIELDS(oneConfig))
|
||||
}
|
||||
|
||||
export { SCHEMA, FIELDS }
|
||||
export { FIELDS, SCHEMA }
|
||||
|
@ -160,7 +160,7 @@ const FieldComponent = memo(
|
||||
const addIdToName = useCallback(
|
||||
(n) => {
|
||||
// removes character '$' and returns
|
||||
if (n.startsWith('$')) return n.slice(1)
|
||||
if (n?.startsWith('$')) return n.slice(1)
|
||||
|
||||
// concat form ID if exists
|
||||
return id ? `${id}.${n}` : n
|
||||
|
@ -13,20 +13,20 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { object, array, string, boolean, number, ref, ObjectSchema } from 'yup'
|
||||
import { ObjectSchema, array, boolean, number, object, ref, string } from 'yup'
|
||||
|
||||
import { userInputsToObject, userInputsToArray } from 'client/models/Helper'
|
||||
import {
|
||||
UserInputType,
|
||||
T,
|
||||
INPUT_TYPES,
|
||||
T,
|
||||
USER_INPUT_TYPES,
|
||||
UserInputType,
|
||||
} from 'client/constants'
|
||||
import { userInputsToArray, userInputsToObject } from 'client/models/Helper'
|
||||
import {
|
||||
Field,
|
||||
arrayToOptions,
|
||||
sentenceCase,
|
||||
getObjectSchemaFromFields,
|
||||
sentenceCase,
|
||||
} from 'client/utils'
|
||||
|
||||
const {
|
||||
@ -45,23 +45,6 @@ const { array: _array, fixed: _fixed, ...userInputTypes } = USER_INPUT_TYPES
|
||||
/** @type {UserInputType[]} User inputs types */
|
||||
const valuesOfUITypes = Object.values(userInputTypes)
|
||||
|
||||
/** @type {Field} Type field */
|
||||
const TYPE = {
|
||||
name: 'type',
|
||||
label: T.Type,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(valuesOfUITypes, {
|
||||
addEmpty: false,
|
||||
getText: (type) => sentenceCase(type),
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.oneOf(valuesOfUITypes)
|
||||
.default(() => valuesOfUITypes[0]),
|
||||
grid: { sm: 6, md: 4 },
|
||||
}
|
||||
|
||||
/** @type {Field} Name field */
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
@ -74,6 +57,54 @@ const NAME = {
|
||||
grid: { sm: 6, md: 4 },
|
||||
}
|
||||
|
||||
/** @type {Field} Type field */
|
||||
const TYPE = {
|
||||
name: 'type',
|
||||
label: T.Type,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
dependOf: NAME.name,
|
||||
values: (name) => {
|
||||
let defaultValues = valuesOfUITypes
|
||||
const sanitizedName = name?.trim()?.toLowerCase()
|
||||
switch (sanitizedName) {
|
||||
case 'memory':
|
||||
defaultValues = [
|
||||
userInputTypes.text,
|
||||
userInputTypes.text64,
|
||||
userInputTypes.number,
|
||||
userInputTypes.range,
|
||||
userInputTypes.list,
|
||||
]
|
||||
break
|
||||
case 'cpu':
|
||||
case 'vcpu':
|
||||
defaultValues = [
|
||||
userInputTypes.text,
|
||||
userInputTypes.text64,
|
||||
userInputTypes.number,
|
||||
userInputTypes.numberFloat,
|
||||
userInputTypes.range,
|
||||
userInputTypes.rangeFloat,
|
||||
userInputTypes.list,
|
||||
]
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return arrayToOptions(defaultValues, {
|
||||
addEmpty: false,
|
||||
getText: (type) => sentenceCase(type),
|
||||
})
|
||||
},
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.oneOf(valuesOfUITypes)
|
||||
.default(() => valuesOfUITypes[0]),
|
||||
grid: { sm: 6, md: 4 },
|
||||
}
|
||||
|
||||
/** @type {Field} Description field */
|
||||
const DESCRIPTION = {
|
||||
name: 'description',
|
||||
@ -194,8 +225,8 @@ const MANDATORY = {
|
||||
|
||||
/** @type {Field[]} List of User Inputs fields */
|
||||
export const USER_INPUT_FIELDS = [
|
||||
TYPE,
|
||||
NAME,
|
||||
TYPE,
|
||||
DESCRIPTION,
|
||||
DEFAULT_VALUE,
|
||||
OPTIONS,
|
||||
|
@ -27,12 +27,14 @@ import { getUserInputParams } from 'client/models/Helper'
|
||||
import { scaleVcpuByCpuFactor } from 'client/models/VirtualMachine'
|
||||
import {
|
||||
Field,
|
||||
OPTION_SORTERS,
|
||||
isDivisibleBy,
|
||||
prettyBytes,
|
||||
schemaUserInput,
|
||||
} from 'client/utils'
|
||||
|
||||
const { number, numberFloat, range, rangeFloat } = USER_INPUT_TYPES
|
||||
const { number, numberFloat, range, rangeFloat, text, text64, password } =
|
||||
USER_INPUT_TYPES
|
||||
|
||||
const TRANSLATES = {
|
||||
MEMORY: {
|
||||
@ -88,20 +90,29 @@ export const FIELDS = (
|
||||
|
||||
// set default type to number
|
||||
userInput.type ??= isCPU ? numberFloat : number
|
||||
|
||||
const ensuredOptions = divisibleBy4
|
||||
? options?.filter((value) => isDivisibleBy(+value, 4))
|
||||
: options
|
||||
|
||||
const schemaUi = schemaUserInput({ options: ensuredOptions, ...userInput })
|
||||
const schemaUserInputConfig = { options: ensuredOptions, ...userInput }
|
||||
userInput?.type === 'list' &&
|
||||
(schemaUserInputConfig.sorter = OPTION_SORTERS.numeric)
|
||||
|
||||
const schemaUi = schemaUserInput(schemaUserInputConfig)
|
||||
|
||||
const isNumber = schemaUi.validation instanceof NumberSchema
|
||||
|
||||
// add positive number validator
|
||||
isNumber && (schemaUi.validation &&= schemaUi.validation.positive())
|
||||
|
||||
if (isMemory) {
|
||||
schemaUi.type = INPUT_TYPES.UNITS
|
||||
;[text, number, numberFloat, text64, password].includes(
|
||||
userInput?.type
|
||||
) && (schemaUi.type = INPUT_TYPES.UNITS)
|
||||
if (isRange) {
|
||||
TRANSLATES[
|
||||
name
|
||||
].tooltip = `${T.MemoryConcept} ${T.MemoryConceptUserInput} `
|
||||
// add label format on pretty bytes
|
||||
schemaUi.fieldProps = { ...schemaUi.fieldProps, valueLabelFormat }
|
||||
}
|
||||
|
@ -13,20 +13,20 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo, useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { scaleVcpuByCpuFactor } from 'client/models/VirtualMachine'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import useStyles from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles'
|
||||
import {
|
||||
SCHEMA,
|
||||
SECTIONS,
|
||||
} from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/schema'
|
||||
import useStyles from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles'
|
||||
import { RESOURCE_NAMES, T, VmTemplate } from 'client/constants'
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper'
|
||||
import { T, RESOURCE_NAMES, VmTemplate } from 'client/constants'
|
||||
import { scaleVcpuByCpuFactor } from 'client/models/VirtualMachine'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
let generalFeatures
|
||||
|
||||
@ -94,7 +94,7 @@ Content.propTypes = {
|
||||
* @param {VmTemplate} vmTemplate - VM Template
|
||||
* @returns {object} Basic configuration step
|
||||
*/
|
||||
const BasicConfiguration = ({ data: vmTemplate, oneConfig, adminGroup }) => ({
|
||||
const BasicConfiguration = ({ vmTemplate, oneConfig, adminGroup }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: () => SCHEMA(vmTemplate, generalFeatures),
|
||||
|
@ -15,8 +15,8 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { BaseSchema } from 'yup'
|
||||
|
||||
import { FIELDS as INFORMATION_FIELDS } from './informationSchema'
|
||||
import { FIELDS as CAPACITY_FIELDS } from './capacitySchema'
|
||||
import { FIELDS as INFORMATION_FIELDS } from './informationSchema'
|
||||
// import { FIELDS as DISK_FIELDS, SCHEMA as DISK_SCHEMA } from './diskSchema'
|
||||
|
||||
// get schemas from VmTemplate/CreateForm
|
||||
@ -24,14 +24,14 @@ import { FIELDS as OWNERSHIP_FIELDS } from 'client/components/Forms/VmTemplate/C
|
||||
import { VCENTER_FOLDER_FIELD } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General/vcenterSchema'
|
||||
import { FIELDS as VM_GROUP_FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General/vmGroupSchema'
|
||||
|
||||
import { T, VmTemplate, VmTemplateFeatures } from 'client/constants'
|
||||
import {
|
||||
filterFieldsByHypervisor,
|
||||
getObjectSchemaFromFields,
|
||||
Field,
|
||||
Section,
|
||||
disableFields,
|
||||
filterFieldsByHypervisor,
|
||||
getObjectSchemaFromFields,
|
||||
} from 'client/utils'
|
||||
import { T, VmTemplate, VmTemplateFeatures } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @param {VmTemplate} [vmTemplate] - VM Template
|
||||
@ -118,4 +118,4 @@ const FIELDS = (vmTemplate, hideCpu) =>
|
||||
const SCHEMA = (vmTemplate, hideCpu) =>
|
||||
getObjectSchemaFromFields(FIELDS(vmTemplate, hideCpu))
|
||||
|
||||
export { SECTIONS, FIELDS, SCHEMA }
|
||||
export { FIELDS, SCHEMA, SECTIONS }
|
||||
|
@ -22,35 +22,64 @@ import ExtraConfiguration, {
|
||||
import UserInputs, {
|
||||
STEP_ID as USER_INPUTS_ID,
|
||||
} from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/UserInputs'
|
||||
import { jsonToXml, userInputsToArray } from 'client/models/Helper'
|
||||
import {
|
||||
getUserInputParams,
|
||||
jsonToXml,
|
||||
parseRangeToArray,
|
||||
userInputsToArray,
|
||||
} from 'client/models/Helper'
|
||||
import { createSteps } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
(stepProps) => {
|
||||
({ dataTemplateExtended = {}, ...rest }) => {
|
||||
const userInputs = userInputsToArray(
|
||||
stepProps?.dataTemplateExtended?.TEMPLATE?.USER_INPUTS,
|
||||
dataTemplateExtended?.TEMPLATE?.USER_INPUTS,
|
||||
{
|
||||
order: stepProps?.dataTemplateExtended?.TEMPLATE?.INPUTS_ORDER,
|
||||
order: dataTemplateExtended?.TEMPLATE?.INPUTS_ORDER,
|
||||
}
|
||||
)
|
||||
|
||||
return [
|
||||
BasicConfiguration,
|
||||
() => BasicConfiguration({ vmTemplate: dataTemplateExtended, ...rest }),
|
||||
!!userInputs.length && (() => UserInputs(userInputs)),
|
||||
ExtraConfiguration,
|
||||
].filter(Boolean)
|
||||
},
|
||||
{
|
||||
transformInitialValue: (vmTemplate, schema) => {
|
||||
const initialValue = schema.cast(
|
||||
// this delete values that are representated in USER_INPUTS
|
||||
if (vmTemplate?.TEMPLATE?.USER_INPUTS) {
|
||||
;['MEMORY', 'CPU', 'VCPU'].forEach((element) => {
|
||||
if (vmTemplate?.TEMPLATE?.USER_INPUTS?.[element]) {
|
||||
const valuesOfUserInput = getUserInputParams(
|
||||
vmTemplate.TEMPLATE.USER_INPUTS[element]
|
||||
)
|
||||
if (valuesOfUserInput?.default) {
|
||||
let options = valuesOfUserInput?.options
|
||||
valuesOfUserInput?.type === 'range' &&
|
||||
(options = parseRangeToArray(options[0], options[1]))
|
||||
|
||||
if (!options.includes(valuesOfUserInput.default)) {
|
||||
delete vmTemplate?.TEMPLATE?.USER_INPUTS?.[element]
|
||||
} else {
|
||||
vmTemplate?.TEMPLATE?.[element] &&
|
||||
delete vmTemplate?.TEMPLATE?.[element]
|
||||
}
|
||||
} else {
|
||||
vmTemplate?.TEMPLATE?.[element] &&
|
||||
delete vmTemplate?.TEMPLATE?.[element]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return schema.cast(
|
||||
{
|
||||
[BASIC_ID]: vmTemplate?.TEMPLATE,
|
||||
[EXTRA_ID]: vmTemplate?.TEMPLATE,
|
||||
},
|
||||
{ stripUnknown: true }
|
||||
)
|
||||
|
||||
return initialValue
|
||||
},
|
||||
transformBeforeSubmit: (formData, vmTemplate, _, adminGroup, oneConfig) => {
|
||||
const {
|
||||
|
@ -895,6 +895,7 @@ module.exports = {
|
||||
"Allow users to modify this template's default memory on instantiate",
|
||||
MemoryConcept: 'Amount of RAM required for the VM',
|
||||
MemoryConceptUnit: 'Choose unit of memory',
|
||||
MemoryConceptUserInput: '(This value is represented in MB)',
|
||||
CpuConcept: `
|
||||
Percentage of CPU divided by 100 required for the
|
||||
Virtual Machine. Half a processor is written 0.5`,
|
||||
|
@ -14,30 +14,30 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import { useHistory, useLocation, Redirect } from 'react-router'
|
||||
import { Redirect, useHistory, useLocation } from 'react-router'
|
||||
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import {
|
||||
useInstantiateTemplateMutation,
|
||||
useGetTemplateQuery,
|
||||
} from 'client/features/OneApi/vmTemplate'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
import { useGetGroupsQuery } from 'client/features/OneApi/group'
|
||||
import { useGetUsersQuery } from 'client/features/OneApi/user'
|
||||
import {
|
||||
useGetTemplateQuery,
|
||||
useInstantiateTemplateMutation,
|
||||
} from 'client/features/OneApi/vmTemplate'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import {
|
||||
DefaultFormStepper,
|
||||
SkeletonStepsForm,
|
||||
} from 'client/components/FormStepper'
|
||||
import { InstantiateForm } from 'client/components/Forms/VmTemplate'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import {
|
||||
addTempInfo,
|
||||
deleteTempInfo,
|
||||
deleteRestrictedAttributes,
|
||||
} from 'client/utils'
|
||||
import { useSystemData } from 'client/features/Auth'
|
||||
import { jsonToXml, xmlToJson } from 'client/models/Helper'
|
||||
import {
|
||||
addTempInfo,
|
||||
deleteRestrictedAttributes,
|
||||
deleteTempInfo,
|
||||
} from 'client/utils'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
|
@ -450,6 +450,26 @@ export const getUserInputString = (userInput) => {
|
||||
return uiString.concat(defaultValue).join(PARAMS_SEPARATOR)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform range value to array.
|
||||
*
|
||||
* @param {number} start - start number.
|
||||
* @param {number} end - end number.
|
||||
* @returns {Array} range transformed into array
|
||||
*/
|
||||
export const parseRangeToArray = (start, end) => {
|
||||
const startNumber = parseInt(start, 10)
|
||||
const endNumber = parseInt(end, 10)
|
||||
if (startNumber === endNumber) return [startNumber]
|
||||
|
||||
const ans = []
|
||||
for (let i = startNumber; i <= endNumber; i++) {
|
||||
ans.push(`${i}`)
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of user inputs defined in OpenNebula template.
|
||||
*
|
||||
@ -501,8 +521,10 @@ export const userInputsToArray = (
|
||||
|
||||
if (orderedList.length) {
|
||||
list = list.sort((a, b) => {
|
||||
const upperAName = a.name?.toUpperCase?.()
|
||||
const upperBName = b.name?.toUpperCase?.()
|
||||
const valueA = parseInt(a.name, 10)
|
||||
const valueB = parseInt(b.name, 10)
|
||||
const upperAName = isNaN(valueA) ? valueA : a.name?.toUpperCase?.()
|
||||
const upperBName = isNaN(valueB) ? valueB : b.name?.toUpperCase?.()
|
||||
|
||||
return orderedList.indexOf(upperAName) - orderedList.indexOf(upperBName)
|
||||
})
|
||||
|
@ -19,29 +19,29 @@
|
||||
import { ReactElement, SetStateAction } from 'react'
|
||||
|
||||
import {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
GridProps,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
TextFieldProps,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
CheckboxProps,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
GridProps,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
InputBaseComponentProps,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
TextFieldProps,
|
||||
} from '@mui/material'
|
||||
import { string, number, boolean, array, object, BaseSchema } from 'yup'
|
||||
import { BaseSchema, array, boolean, number, object, string } from 'yup'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import {
|
||||
UserInputObject,
|
||||
T,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
HYPERVISORS,
|
||||
INPUT_TYPES,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
T,
|
||||
USER_INPUT_TYPES,
|
||||
UserInputObject,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
VN_DRIVERS,
|
||||
INPUT_TYPES,
|
||||
USER_INPUT_TYPES,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
} from 'client/constants'
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
|
||||
@ -224,8 +224,12 @@ const getRange = (options) => options?.split?.('..').map(parseFloat)
|
||||
const getValuesFromArray = (options, separator = SEMICOLON_CHAR) =>
|
||||
options?.split(separator)
|
||||
|
||||
const getOptionsFromList = (options = []) =>
|
||||
arrayToOptions([...new Set(options)], { addEmpty: false })
|
||||
const getOptionsFromList = (options = [], sorter) => {
|
||||
const config = { addEmpty: false }
|
||||
sorter && (config.sorter = sorter)
|
||||
|
||||
return arrayToOptions([...new Set(options)], config)
|
||||
}
|
||||
|
||||
const parseUserInputValue = (value) => {
|
||||
if (value === true) {
|
||||
@ -257,6 +261,7 @@ export const schemaUserInput = ({
|
||||
max,
|
||||
options,
|
||||
default: defaultValue,
|
||||
sorter,
|
||||
}) => {
|
||||
switch (type) {
|
||||
case USER_INPUT_TYPES.fixed: {
|
||||
@ -328,7 +333,7 @@ export const schemaUserInput = ({
|
||||
.yesOrNo(),
|
||||
}
|
||||
case USER_INPUT_TYPES.list: {
|
||||
const values = getOptionsFromList(options)
|
||||
const values = getOptionsFromList(options, sorter)
|
||||
const optionValues = values.map(({ value }) => value).filter(Boolean)
|
||||
const firstOption = optionValues[0] ?? undefined
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user