mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-22 18:50:08 +03:00
parent
84c00d29e3
commit
83df0dffee
@ -0,0 +1,72 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
import { Box } from '@mui/material'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
|
||||
import { AttributePanel } from 'client/components/Tabs/Common'
|
||||
import { SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/schema'
|
||||
import { cleanEmpty, cloneObject, set } from 'client/utils'
|
||||
import { T, ACTIONS } from 'client/constants'
|
||||
|
||||
const ALL_ACTIONS = [
|
||||
ACTIONS.COPY_ATTRIBUTE,
|
||||
ACTIONS.ADD_ATTRIBUTE,
|
||||
ACTIONS.EDIT_ATTRIBUTE,
|
||||
ACTIONS.DELETE_ATTRIBUTE
|
||||
]
|
||||
|
||||
export const STEP_ID = 'custom-variables'
|
||||
|
||||
const Content = () => {
|
||||
const { setValue } = useFormContext()
|
||||
const customVars = useWatch({ name: STEP_ID })
|
||||
|
||||
const handleChangeAttribute = (path, newValue) => {
|
||||
const newCustomVars = cloneObject(customVars)
|
||||
|
||||
set(newCustomVars, path, newValue)
|
||||
setValue(STEP_ID, cleanEmpty(newCustomVars))
|
||||
}
|
||||
|
||||
return (
|
||||
<Box display='grid' gap='1em'>
|
||||
<AttributePanel
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={customVars}
|
||||
actions={ALL_ACTIONS}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomVariables = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.CustomVariables,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any
|
||||
}
|
||||
|
||||
export default CustomVariables
|
@ -0,0 +1,24 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, 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 { object, ObjectSchema } from 'yup'
|
||||
|
||||
import { } from 'client/utils'
|
||||
import { } from 'client/constants'
|
||||
|
||||
/** @type {ObjectSchema} Step schema */
|
||||
const SCHEMA = object()
|
||||
|
||||
export { SCHEMA }
|
@ -32,7 +32,8 @@ export const SSH_PUBLIC_KEY = {
|
||||
multiline: true,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired(),
|
||||
.notRequired()
|
||||
.ensure(),
|
||||
grid: { md: 12 },
|
||||
fieldProps: { rows: 4 }
|
||||
}
|
||||
@ -81,6 +82,7 @@ export const START_SCRIPT = {
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.ensure()
|
||||
.when(
|
||||
'$extra.CONTEXT.START_SCRIPT_BASE64',
|
||||
(scriptEncoded, schema) => scriptEncoded
|
||||
|
@ -29,7 +29,8 @@ export const FILES_DS = {
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired(),
|
||||
.notRequired()
|
||||
.ensure(),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
@ -41,7 +42,8 @@ export const INIT_SCRIPTS = {
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired(),
|
||||
.notRequired()
|
||||
.ensure(),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
|
@ -15,38 +15,46 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import General, { STEP_ID as GENERAL_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General'
|
||||
import ExtraConfiguration, { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import CustomVariables, { STEP_ID as CUSTOM_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables'
|
||||
import { jsonToXml, userInputsToArray } from 'client/models/Helper'
|
||||
import { createSteps, isBase64 } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
[General, ExtraConfiguration],
|
||||
[General, ExtraConfiguration, CustomVariables],
|
||||
{
|
||||
transformInitialValue: (vmTemplate, schema) => {
|
||||
const generalStep = schema
|
||||
.pick([GENERAL_ID])
|
||||
.cast(
|
||||
{ [GENERAL_ID]: { ...vmTemplate, ...vmTemplate?.TEMPLATE } },
|
||||
{ stripUnknown: true }
|
||||
)
|
||||
|
||||
const inputsOrder = vmTemplate?.TEMPLATE?.INPUTS_ORDER?.split(',') ?? []
|
||||
const userInputs = userInputsToArray(vmTemplate?.TEMPLATE?.USER_INPUTS)
|
||||
.sort((a, b) => inputsOrder.indexOf(a.name) - inputsOrder.indexOf(b.name))
|
||||
|
||||
const configurationStep = schema
|
||||
.pick([EXTRA_ID])
|
||||
.cast(
|
||||
{ [EXTRA_ID]: { ...vmTemplate?.TEMPLATE, USER_INPUTS: userInputs } },
|
||||
{ stripUnknown: true, context: { [EXTRA_ID]: vmTemplate.TEMPLATE } }
|
||||
)
|
||||
const knownTemplate = schema.cast(
|
||||
{
|
||||
[GENERAL_ID]: { ...vmTemplate, ...vmTemplate?.TEMPLATE },
|
||||
[EXTRA_ID]: { ...vmTemplate?.TEMPLATE, USER_INPUTS: userInputs }
|
||||
},
|
||||
{ stripUnknown: true, context: { [EXTRA_ID]: vmTemplate.TEMPLATE } }
|
||||
)
|
||||
|
||||
return { ...generalStep, ...configurationStep }
|
||||
const customVars = {}
|
||||
const knownAttributes = Object.getOwnPropertyNames({
|
||||
...knownTemplate[GENERAL_ID],
|
||||
...knownTemplate[EXTRA_ID]
|
||||
})
|
||||
|
||||
Object.entries(vmTemplate?.TEMPLATE)
|
||||
.forEach(([key, value]) => {
|
||||
if (!knownAttributes.includes(key) && value) {
|
||||
customVars[key] = value
|
||||
}
|
||||
})
|
||||
|
||||
return { ...knownTemplate, [CUSTOM_ID]: customVars }
|
||||
},
|
||||
transformBeforeSubmit: formData => {
|
||||
const {
|
||||
[GENERAL_ID]: general = {},
|
||||
[CUSTOM_ID]: customVariables = {},
|
||||
[EXTRA_ID]: {
|
||||
USER_INPUTS,
|
||||
CONTEXT: { START_SCRIPT, ENCODE_START_SCRIPT, ...restOfContext },
|
||||
...extraTemplate
|
||||
} = {}
|
||||
@ -62,16 +70,17 @@ const Steps = createSteps(
|
||||
}
|
||||
|
||||
// add user inputs to context
|
||||
Object.keys(USER_INPUTS).forEach(name => {
|
||||
const upperName = String(name).toUpperCase()
|
||||
context[upperName] = `$${upperName}`
|
||||
})
|
||||
Object.keys(extraTemplate?.USER_INPUTS ?? {})
|
||||
.forEach(name => {
|
||||
const upperName = String(name).toUpperCase()
|
||||
context[upperName] = `$${upperName}`
|
||||
})
|
||||
|
||||
return jsonToXml({
|
||||
...customVariables,
|
||||
...extraTemplate,
|
||||
...general,
|
||||
CONTEXT: context,
|
||||
USER_INPUTS: USER_INPUTS
|
||||
CONTEXT: context
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -137,10 +137,10 @@ const Attribute = memo(({
|
||||
}
|
||||
</Typography>
|
||||
<ActionWrapper {...(showActionsOnHover && { display: 'none' })}>
|
||||
{canCopy && (
|
||||
{value && canCopy && (
|
||||
<Actions.Copy name={name} value={value} />
|
||||
)}
|
||||
{canEdit && (
|
||||
{(value || numberOfParents > 0) && canEdit && (
|
||||
<Actions.Edit name={name} handleClick={handleActiveEditForm} />
|
||||
)}
|
||||
{canDelete && (
|
||||
|
@ -28,7 +28,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
|
||||
@ -57,7 +57,6 @@ const SPECIAL_ATTRIBUTES = {
|
||||
[DELETE]: false
|
||||
},
|
||||
VCENTER_PASSWORD: {
|
||||
[EDIT]: true,
|
||||
[DELETE]: false
|
||||
},
|
||||
VCENTER_USER: {
|
||||
@ -87,21 +86,24 @@ const AttributePanel = memo(({
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
handleAdd,
|
||||
actions
|
||||
actions = [],
|
||||
filtersSpecialAttributes = true
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const canUseAction = useCallback((name, action) => (
|
||||
actions?.includes?.(action) &&
|
||||
(!filtersSpecialAttributes || SPECIAL_ATTRIBUTES[name]?.[action] === undefined)
|
||||
), [actions?.length])
|
||||
|
||||
const formatAttributes = Object.entries(attributes)
|
||||
.map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
showActionsOnHover: true,
|
||||
canCopy:
|
||||
actions?.includes?.(COPY) && !SPECIAL_ATTRIBUTES[name]?.[COPY],
|
||||
canEdit:
|
||||
actions?.includes?.(EDIT) && !SPECIAL_ATTRIBUTES[name]?.[EDIT],
|
||||
canDelete:
|
||||
actions?.includes?.(DELETE) && !SPECIAL_ATTRIBUTES[name]?.[DELETE],
|
||||
canCopy: canUseAction(name, COPY),
|
||||
canEdit: canUseAction(name, EDIT),
|
||||
canDelete: canUseAction(name, DELETE),
|
||||
handleEdit,
|
||||
handleDelete
|
||||
}))
|
||||
@ -124,7 +126,8 @@ AttributePanel.propTypes = {
|
||||
handleAdd: PropTypes.func,
|
||||
handleEdit: PropTypes.func,
|
||||
handleDelete: PropTypes.func,
|
||||
title: PropTypes.string
|
||||
title: PropTypes.string,
|
||||
filtersSpecialAttributes: PropTypes.bool
|
||||
}
|
||||
|
||||
AttributePanel.displayName = 'AttributePanel'
|
||||
|
@ -36,7 +36,8 @@ const useStyles = makeStyles(theme => ({
|
||||
gap: '1em',
|
||||
'& > *': {
|
||||
flex: '1 1 50%',
|
||||
overflow: 'hidden'
|
||||
overflow: 'hidden',
|
||||
minHeight: '100%'
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.text.primary, 0.05)
|
||||
|
@ -328,7 +328,7 @@ module.exports = {
|
||||
UserTemplate: 'User Template',
|
||||
Template: 'Template',
|
||||
WhereIsRunning:
|
||||
'VM %1$s is currently running on Host %2$s and Datastore %3$s',
|
||||
'VM %1$s is currently running on Host %2$s and Datastore %3$s',
|
||||
/* VM schema - capacity */
|
||||
Capacity: 'Capacity',
|
||||
PhysicalCpu: 'Physical CPU',
|
||||
@ -357,11 +357,12 @@ module.exports = {
|
||||
ExternalConcept: 'The NIC will be attached as an external alias of the VM',
|
||||
|
||||
/* VM Template schema */
|
||||
/* VM schema - general */
|
||||
/* VM Template schema - general */
|
||||
Logo: 'Logo',
|
||||
Hypervisor: 'Hypervisor',
|
||||
TemplateName: 'Template name',
|
||||
MakeNewImagePersistent: 'Make the new images persistent',
|
||||
CustomVariables: 'Custom Variables',
|
||||
/* VM schema - ownership */
|
||||
InstantiateAsUser: 'Instantiate as different User',
|
||||
InstantiateAsGroup: 'Instantiate as different Group',
|
||||
|
@ -15,6 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import DOMPurify from 'dompurify'
|
||||
import { object, reach, ObjectSchema, BaseSchema } from 'yup'
|
||||
import { isMergeableObject } from 'client/utils/merge'
|
||||
import { HYPERVISORS } from 'client/constants'
|
||||
|
||||
/**
|
||||
@ -266,6 +267,62 @@ export const groupBy = (list, key) =>
|
||||
*/
|
||||
export const cloneObject = obj => JSON.parse(JSON.stringify(obj))
|
||||
|
||||
/**
|
||||
* Removes undefined and null values from object.
|
||||
*
|
||||
* @param {object} obj - Object value
|
||||
* @returns {object} - Cleaned object
|
||||
*/
|
||||
export const cleanEmptyObject = obj => {
|
||||
const entries = Object.entries(obj)
|
||||
.filter(([_, value]) =>
|
||||
// filter object/array values without attributes
|
||||
isMergeableObject(value)
|
||||
? Object.values(value).some(v => v != null)
|
||||
: Array.isArray(value) ? value.length > 0 : true
|
||||
)
|
||||
.map(([key, value]) => {
|
||||
let cleanedValue = value
|
||||
|
||||
if (isMergeableObject(value)) {
|
||||
cleanedValue = cleanEmptyObject(value)
|
||||
} else if (Array.isArray(value)) {
|
||||
cleanedValue = cleanEmptyArray(value)
|
||||
}
|
||||
|
||||
return [key, cleanedValue]
|
||||
})
|
||||
|
||||
return entries?.length > 0
|
||||
? entries.reduce((cleanedObject, [key, value]) => {
|
||||
// `value == null` checks against undefined and null
|
||||
return value == null ? cleanedObject : { ...cleanedObject, [key]: value }
|
||||
}, {})
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes undefined and null values from array.
|
||||
*
|
||||
* @param {Array} arr - Array value
|
||||
* @returns {object} - Cleaned object
|
||||
*/
|
||||
export const cleanEmptyArray = arr => arr
|
||||
.map(value => isMergeableObject(value) ? cleanEmpty(value) : value)
|
||||
.filter(value =>
|
||||
!(value == null) || // `value == null` checks against undefined and null
|
||||
(Array.isArray(value) && value.length > 0)
|
||||
)
|
||||
|
||||
/**
|
||||
* Removes undefined and null values from variable.
|
||||
*
|
||||
* @param {Array|object} variable - Variable
|
||||
* @returns {Array|object} - Cleaned variable
|
||||
*/
|
||||
export const cleanEmpty = variable =>
|
||||
Array.isArray(variable) ? cleanEmptyArray(variable) : cleanEmptyObject(variable)
|
||||
|
||||
/**
|
||||
* Check if value is in base64.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user