mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-25 02:50:08 +03:00
B OpenNebula/one#6154: Fix error updating template (#2733)
This commit is contained in:
parent
d0c4f30c4b
commit
f242d443ba
1
src/fireedge/package-lock.json
generated
1
src/fireedge/package-lock.json
generated
@ -67,6 +67,7 @@
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-simple": "0.5.6",
|
||||
"lockfile": "1.0.4",
|
||||
"lodash": "4.17.21",
|
||||
"lodash.get": "4.4.2",
|
||||
"luxon": "2.1.1",
|
||||
"marked": "4.0.10",
|
||||
|
@ -101,6 +101,7 @@
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-simple": "0.5.6",
|
||||
"lockfile": "1.0.4",
|
||||
"lodash": "4.17.21",
|
||||
"lodash.get": "4.4.2",
|
||||
"luxon": "2.1.1",
|
||||
"marked": "4.0.10",
|
||||
|
@ -33,7 +33,7 @@ import {
|
||||
VM_ACTIONS,
|
||||
VM_ACTIONS_IN_CHARTER,
|
||||
} from 'client/constants'
|
||||
import { sentenceCase } from 'client/utils'
|
||||
import { sentenceCase, hasRestrictedAttributes } from 'client/utils'
|
||||
|
||||
/**
|
||||
* Returns a button to trigger form to create a scheduled action.
|
||||
@ -44,30 +44,36 @@ import { sentenceCase } from 'client/utils'
|
||||
* @param {function():Promise} props.onSubmit - Submit function
|
||||
* @returns {ReactElement} Button
|
||||
*/
|
||||
const CreateSchedButton = memo(({ vm, relative, onSubmit }) => (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': VM_ACTIONS.SCHED_ACTION_CREATE,
|
||||
label: T.AddAction,
|
||||
variant: 'outlined',
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
name: T.PunctualAction,
|
||||
dialogProps: {
|
||||
title: T.ScheduleAction,
|
||||
dataCy: 'modal-sched-actions',
|
||||
const CreateSchedButton = memo(
|
||||
({ vm, relative, onSubmit, oneConfig, adminGroup }) => (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': VM_ACTIONS.SCHED_ACTION_CREATE,
|
||||
label: T.AddAction,
|
||||
variant: 'outlined',
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
name: T.PunctualAction,
|
||||
dialogProps: {
|
||||
title: T.ScheduleAction,
|
||||
dataCy: 'modal-sched-actions',
|
||||
},
|
||||
form: () =>
|
||||
relative
|
||||
? CreateRelativeSchedActionForm({
|
||||
stepProps: { vm, oneConfig, adminGroup },
|
||||
})
|
||||
: CreateSchedActionForm({
|
||||
stepProps: { vm, oneConfig, adminGroup },
|
||||
}),
|
||||
onSubmit,
|
||||
},
|
||||
form: () =>
|
||||
relative
|
||||
? CreateRelativeSchedActionForm({ stepProps: vm })
|
||||
: CreateSchedActionForm({ stepProps: vm }),
|
||||
onSubmit,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
]}
|
||||
/>
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns a button to trigger form to update a scheduled action.
|
||||
@ -79,36 +85,44 @@ const CreateSchedButton = memo(({ vm, relative, onSubmit }) => (
|
||||
* @param {function():Promise} props.onSubmit - Submit function
|
||||
* @returns {ReactElement} Button
|
||||
*/
|
||||
const UpdateSchedButton = memo(({ vm, schedule, relative, onSubmit }) => {
|
||||
const { ID, ACTION } = schedule
|
||||
const titleAction = `#${ID} ${sentenceCase(ACTION)}`
|
||||
const formConfig = { stepProps: vm, initialValues: schedule }
|
||||
const UpdateSchedButton = memo(
|
||||
({ vm, schedule, relative, onSubmit, oneConfig, adminGroup }) => {
|
||||
const { ID, ACTION } = schedule
|
||||
const titleAction = `#${ID} ${sentenceCase(ACTION)}`
|
||||
const formConfig = {
|
||||
stepProps: { vm, oneConfig, adminGroup },
|
||||
initialValues: schedule,
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VM_ACTIONS.SCHED_ACTION_UPDATE}-${ID}`,
|
||||
icon: <Edit />,
|
||||
tooltip: <Translate word={T.Edit} />,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.UpdateScheduleAction} values={[titleAction]} />
|
||||
),
|
||||
dataCy: 'modal-sched-actions',
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VM_ACTIONS.SCHED_ACTION_UPDATE}-${ID}`,
|
||||
icon: <Edit />,
|
||||
tooltip: <Translate word={T.Edit} />,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.UpdateScheduleAction}
|
||||
values={[titleAction]}
|
||||
/>
|
||||
),
|
||||
dataCy: 'modal-sched-actions',
|
||||
},
|
||||
form: () =>
|
||||
relative
|
||||
? CreateRelativeSchedActionForm(formConfig)
|
||||
: CreateSchedActionForm(formConfig),
|
||||
onSubmit,
|
||||
},
|
||||
form: () =>
|
||||
relative
|
||||
? CreateRelativeSchedActionForm(formConfig)
|
||||
: CreateSchedActionForm(formConfig),
|
||||
onSubmit,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns a button to trigger modal to delete a scheduled action.
|
||||
@ -118,32 +132,47 @@ const UpdateSchedButton = memo(({ vm, schedule, relative, onSubmit }) => {
|
||||
* @param {function():Promise} props.onSubmit - Submit function
|
||||
* @returns {ReactElement} Button
|
||||
*/
|
||||
const DeleteSchedButton = memo(({ onSubmit, schedule }) => {
|
||||
const { ID, ACTION } = schedule
|
||||
const titleAction = `#${ID} ${sentenceCase(ACTION)}`
|
||||
const DeleteSchedButton = memo(
|
||||
({ onSubmit, schedule, oneConfig, adminGroup }) => {
|
||||
const { ID, ACTION } = schedule
|
||||
const titleAction = `#${ID} ${sentenceCase(ACTION)}`
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VM_ACTIONS.SCHED_ACTION_DELETE}-${ID}`,
|
||||
icon: <Trash />,
|
||||
tooltip: <Translate word={T.Delete} />,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.DeleteScheduleAction} values={[titleAction]} />
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
// Disable action if the nic has a restricted attribute on the template
|
||||
const disabledAction =
|
||||
!adminGroup &&
|
||||
hasRestrictedAttributes(
|
||||
schedule,
|
||||
'SCHED_ACTION',
|
||||
oneConfig?.VM_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VM_ACTIONS.SCHED_ACTION_DELETE}-${ID}`,
|
||||
icon: <Trash />,
|
||||
tooltip: !disabledAction ? Tr(T.Delete) : Tr(T.DetachRestricted),
|
||||
disabled: disabledAction,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.DeleteScheduleAction}
|
||||
values={[titleAction]}
|
||||
/>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit,
|
||||
},
|
||||
onSubmit,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns a button to trigger form to create a charter.
|
||||
@ -195,6 +224,8 @@ const ButtonPropTypes = {
|
||||
relative: PropTypes.bool,
|
||||
onSubmit: PropTypes.func,
|
||||
schedule: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
CreateSchedButton.propTypes = ButtonPropTypes
|
||||
|
@ -27,8 +27,11 @@ import { T, HYPERVISORS } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'advanced'
|
||||
|
||||
const Content = ({ hypervisor }) => {
|
||||
const sections = useMemo(() => SECTIONS(hypervisor), [])
|
||||
const Content = ({ hypervisor, oneConfig, adminGroup }) => {
|
||||
const sections = useMemo(
|
||||
() => SECTIONS(hypervisor, oneConfig, adminGroup),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -55,20 +58,24 @@ const Content = ({ hypervisor }) => {
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {HYPERVISORS} props.hypervisor - Hypervisor
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {Step} Advance options step
|
||||
*/
|
||||
const AdvancedOptions = ({ hypervisor } = {}) => ({
|
||||
const AdvancedOptions = ({ hypervisor, oneConfig, adminGroup } = {}) => ({
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA(hypervisor),
|
||||
resolver: SCHEMA(hypervisor, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content({ hypervisor }),
|
||||
content: () => Content({ hypervisor, oneConfig, adminGroup }),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
hypervisor: PropTypes.any,
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default AdvancedOptions
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
Section,
|
||||
getObjectSchemaFromFields,
|
||||
filterFieldsByHypervisor,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
|
||||
const { vcenter } = HYPERVISORS
|
||||
@ -47,33 +48,60 @@ const SIZE = {
|
||||
|
||||
/**
|
||||
* @param {HYPERVISORS} hypervisor - Hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = (hypervisor) => [
|
||||
const SECTIONS = (hypervisor, oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'general',
|
||||
legend: T.General,
|
||||
fields: filterFieldsByHypervisor([SIZE, ...GENERAL_FIELDS], hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor([SIZE, ...GENERAL_FIELDS], hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vcenter',
|
||||
legend: 'vCenter',
|
||||
fields: filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'throttling-bytes',
|
||||
legend: T.ThrottlingBytes,
|
||||
fields: filterFieldsByHypervisor(THROTTLING_BYTES_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(THROTTLING_BYTES_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'throttling-iops',
|
||||
legend: T.ThrottlingIOPS,
|
||||
fields: filterFieldsByHypervisor(THROTTLING_IOPS_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(THROTTLING_IOPS_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'edge-cluster',
|
||||
legend: T.EdgeCluster,
|
||||
fields: filterFieldsByHypervisor(EDGE_CLUSTER_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(EDGE_CLUSTER_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -58,7 +58,6 @@ const Steps = createSteps([ImagesTable, AdvancedOptions], {
|
||||
IMAGE_UID: UID,
|
||||
IMAGE_UNAME: UNAME,
|
||||
IMAGE_STATE: STATE,
|
||||
ORIGINAL_SIZE: SIZE,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -27,8 +27,11 @@ import { T, HYPERVISORS } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'advanced'
|
||||
|
||||
const Content = ({ hypervisor }) => {
|
||||
const sections = useMemo(() => SECTIONS(hypervisor), [])
|
||||
const Content = ({ hypervisor, oneConfig, adminGroup }) => {
|
||||
const sections = useMemo(
|
||||
() => SECTIONS(hypervisor, oneConfig, adminGroup),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -55,20 +58,24 @@ const Content = ({ hypervisor }) => {
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {HYPERVISORS} props.hypervisor - Hypervisor
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {Step} Advance options step
|
||||
*/
|
||||
const AdvancedOptions = ({ hypervisor } = {}) => ({
|
||||
const AdvancedOptions = ({ hypervisor, oneConfig, adminGroup } = {}) => ({
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA(hypervisor),
|
||||
resolver: SCHEMA(hypervisor, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content({ hypervisor }),
|
||||
content: () => Content({ hypervisor, oneConfig, adminGroup }),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
hypervisor: PropTypes.any,
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default AdvancedOptions
|
||||
|
@ -28,37 +28,65 @@ import {
|
||||
Section,
|
||||
getObjectSchemaFromFields,
|
||||
filterFieldsByHypervisor,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
|
||||
/**
|
||||
* @param {HYPERVISORS} hypervisor - Hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = (hypervisor) => [
|
||||
const SECTIONS = (hypervisor, oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'general',
|
||||
legend: T.General,
|
||||
fields: filterFieldsByHypervisor(GENERAL_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(GENERAL_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vcenter',
|
||||
legend: 'vCenter',
|
||||
fields: filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'throttling-bytes',
|
||||
legend: T.ThrottlingBytes,
|
||||
fields: filterFieldsByHypervisor(THROTTLING_BYTES_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(THROTTLING_BYTES_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'throttling-iops',
|
||||
legend: T.ThrottlingIOPS,
|
||||
fields: filterFieldsByHypervisor(THROTTLING_IOPS_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(THROTTLING_IOPS_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'edge-cluster',
|
||||
legend: T.EdgeCluster,
|
||||
fields: filterFieldsByHypervisor(EDGE_CLUSTER_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(EDGE_CLUSTER_FIELDS, hypervisor),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -26,8 +26,11 @@ import { T, HYPERVISORS } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'configuration'
|
||||
|
||||
const Content = ({ hypervisor }) => {
|
||||
const memoFields = useMemo(() => FIELDS(hypervisor), [])
|
||||
const Content = ({ hypervisor, oneConfig, adminGroup }) => {
|
||||
const memoFields = useMemo(
|
||||
() => FIELDS(hypervisor, oneConfig, adminGroup),
|
||||
[]
|
||||
)
|
||||
|
||||
return <FormWithSchema cy="attach-disk" fields={memoFields} id={STEP_ID} />
|
||||
}
|
||||
@ -37,20 +40,24 @@ const Content = ({ hypervisor }) => {
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {HYPERVISORS} props.hypervisor - Hypervisor
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {Step} Basic configuration step
|
||||
*/
|
||||
const BasicConfiguration = ({ hypervisor } = {}) => ({
|
||||
const BasicConfiguration = ({ hypervisor, oneConfig, adminGroup } = {}) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: SCHEMA(hypervisor),
|
||||
resolver: SCHEMA(hypervisor, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content({ hypervisor }),
|
||||
content: () => Content({ hypervisor, oneConfig, adminGroup }),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
hypervisor: PropTypes.any,
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default BasicConfiguration
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
getValidationFromFields,
|
||||
filterFieldsByHypervisor,
|
||||
arrayToOptions,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
@ -120,12 +121,19 @@ const FILESYSTEM = {
|
||||
|
||||
/**
|
||||
* @param {HYPERVISORS} hypervisor - hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Field[]} List of fields
|
||||
*/
|
||||
export const FIELDS = (hypervisor) =>
|
||||
filterFieldsByHypervisor(
|
||||
[SIZE, SIZEUNIT, TYPE, FORMAT, FILESYSTEM],
|
||||
hypervisor
|
||||
export const FIELDS = (hypervisor, oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
filterFieldsByHypervisor(
|
||||
[SIZE, SIZEUNIT, TYPE, FORMAT, FILESYSTEM],
|
||||
hypervisor
|
||||
),
|
||||
'DISK',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -22,15 +22,20 @@ import AdvancedOptions, {
|
||||
import { mapUserInputs, createSteps, convertToMB } from 'client/utils'
|
||||
|
||||
const Steps = createSteps([BasicConfiguration, AdvancedOptions], {
|
||||
transformInitialValue: (disk = {}, schema) => ({
|
||||
...schema.cast(
|
||||
transformInitialValue: (disk = {}, schema) => {
|
||||
const schemaCast = schema.cast(
|
||||
{
|
||||
[BASIC_ID]: disk,
|
||||
[ADVANCED_ID]: disk,
|
||||
},
|
||||
{ stripUnknown: true }
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
// #6154: Add temp id to propagate it
|
||||
schemaCast[ADVANCED_ID].TEMP_ID = disk.TEMP_ID
|
||||
|
||||
return schemaCast
|
||||
},
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const { [BASIC_ID]: configuration = {}, [ADVANCED_ID]: advanced = {} } =
|
||||
formData ?? {}
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
filterFieldsByDriver,
|
||||
getObjectSchemaFromFields,
|
||||
arrayToOptions,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
@ -40,8 +41,6 @@ import {
|
||||
transformPciToString,
|
||||
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSchema'
|
||||
|
||||
import { useDisableInputByUserAndConfig } from 'client/features/Auth'
|
||||
|
||||
const { vcenter, firecracker, lxc } = HYPERVISORS
|
||||
const PCI_TYPE_NAME = 'PCI_TYPE'
|
||||
const DEVICE_LIST = 'DEVICE_LIST'
|
||||
@ -283,7 +282,6 @@ const OVERRIDE_IPV4_FIELDS = [
|
||||
label: T.MAC,
|
||||
tooltip: T.MACConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
fieldProps: () => useDisableInputByUserAndConfig('NIC/MAC'),
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
@ -589,6 +587,8 @@ const GUEST_FIELDS = [
|
||||
* @param {VN_DRIVERS} [data.driver] - Virtual network driver
|
||||
* @param {HYPERVISORS} [data.hypervisor] - VM Hypervisor
|
||||
* @param {object} data.defaultData - VM or VM Template data
|
||||
* @param {object} data.oneConfig - Config of oned.conf
|
||||
* @param {boolean} data.adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = ({
|
||||
@ -596,6 +596,8 @@ const SECTIONS = ({
|
||||
driver,
|
||||
hypervisor = HYPERVISORS.kvm,
|
||||
defaultData,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
} = {}) => {
|
||||
const filters = { driver, hypervisor }
|
||||
|
||||
@ -606,7 +608,12 @@ const SECTIONS = ({
|
||||
{
|
||||
id: 'general',
|
||||
legend: T.General,
|
||||
fields: filterByHypAndDriver(GENERAL_FIELDS({ nics }), filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(GENERAL_FIELDS({ nics }), filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -615,27 +622,52 @@ const SECTIONS = ({
|
||||
{
|
||||
id: 'guacamole-connections',
|
||||
legend: T.GuacamoleConnections,
|
||||
fields: filterByHypAndDriver(GUACAMOLE_CONNECTIONS, filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(GUACAMOLE_CONNECTIONS, filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'override-ipv4',
|
||||
legend: T.OverrideNetworkValuesIPv4,
|
||||
fields: filterByHypAndDriver(OVERRIDE_IPV4_FIELDS, filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(OVERRIDE_IPV4_FIELDS, filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'override-ipv6',
|
||||
legend: T.OverrideNetworkValuesIPv6,
|
||||
fields: filterByHypAndDriver(OVERRIDE_IPV6_FIELDS, filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(OVERRIDE_IPV6_FIELDS, filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'hardware',
|
||||
legend: T.Hardware,
|
||||
fields: filterByHypAndDriver(HARDWARE_FIELDS(defaultData), filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(HARDWARE_FIELDS(defaultData), filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'guest',
|
||||
legend: T.GuestOptions,
|
||||
fields: filterByHypAndDriver(GUEST_FIELDS, filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(GUEST_FIELDS, filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
filterFieldsByHypervisor,
|
||||
filterFieldsByDriver,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, HYPERVISORS, VN_DRIVERS } from 'client/constants'
|
||||
|
||||
@ -112,21 +113,38 @@ const OVERRIDE_OUT_QOS_FIELDS = [
|
||||
* @param {object} data - VM or VM Template data
|
||||
* @param {VN_DRIVERS} [data.driver] - Virtual network driver
|
||||
* @param {HYPERVISORS} [data.hypervisor] - VM Hypervisor
|
||||
* @param {object} data.oneConfig - Config of oned.conf
|
||||
* @param {boolean} data.adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = ({ driver, hypervisor = HYPERVISORS.kvm } = {}) => {
|
||||
const SECTIONS = ({
|
||||
driver,
|
||||
hypervisor = HYPERVISORS.kvm,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
} = {}) => {
|
||||
const filters = { driver, hypervisor }
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'override-in-qos',
|
||||
legend: T.OverrideNetworkInboundTrafficQos,
|
||||
fields: filterByHypAndDriver(OVERRIDE_IN_QOS_FIELDS, filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(OVERRIDE_IN_QOS_FIELDS, filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'override-out-qos',
|
||||
legend: T.OverrideNetworkOutboundTrafficQos,
|
||||
fields: filterByHypAndDriver(OVERRIDE_OUT_QOS_FIELDS, filters),
|
||||
fields: disableFields(
|
||||
filterByHypAndDriver(OVERRIDE_OUT_QOS_FIELDS, filters),
|
||||
'NIC',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -60,6 +60,9 @@ const Steps = createSteps([NetworksTable, AdvancedOptions, QOSOptions], {
|
||||
{ stripUnknown: true }
|
||||
)
|
||||
|
||||
// #6154: Add temp id to propagate it
|
||||
if (rest.TEMP_ID) castedValue[ADVANCED_ID].TEMP_ID = rest.TEMP_ID
|
||||
|
||||
return {
|
||||
[NETWORK_ID]: [
|
||||
{
|
||||
|
@ -49,10 +49,15 @@ const commonTransformInitialValue = (scheduledAction, schema, typeForm) => {
|
||||
}
|
||||
}
|
||||
|
||||
return schema.cast(dataToCast, {
|
||||
const castSchema = schema.cast(dataToCast, {
|
||||
context: scheduledAction,
|
||||
stripUnknown: true,
|
||||
})
|
||||
|
||||
// #6154: Add temportal id for restricted attributes
|
||||
castSchema.TEMP_ID = scheduledAction.TEMP_ID
|
||||
|
||||
return castSchema
|
||||
}
|
||||
|
||||
const commonTransformBeforeSubmit = (formData) => {
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
} from 'client/components/Forms/Vm/CreateSchedActionForm/fields'
|
||||
import { ARGS_TYPES } from 'client/constants'
|
||||
import { getRequiredArgsByAction } from 'client/models/Scheduler'
|
||||
import { Field, getObjectSchemaFromFields } from 'client/utils'
|
||||
import { Field, getObjectSchemaFromFields, disableFields } from 'client/utils'
|
||||
|
||||
const ARG_SCHEMA = string()
|
||||
.trim()
|
||||
@ -66,16 +66,25 @@ const COMMON_SCHEMA = object({
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {object} vm - Vm resource
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.vm - Vm resource
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const VM_SCHED_FIELDS = (vm) => [
|
||||
PUNCTUAL_FIELDS.ACTION_FIELD(vm),
|
||||
...COMMON_FIELDS(vm, true),
|
||||
PUNCTUAL_FIELDS.TIME_FIELD,
|
||||
PUNCTUAL_FIELDS.END_TYPE_FIELD,
|
||||
PUNCTUAL_FIELDS.END_VALUE_FIELD,
|
||||
]
|
||||
export const VM_SCHED_FIELDS = ({ vm, oneConfig, adminGroup }) =>
|
||||
disableFields(
|
||||
[
|
||||
PUNCTUAL_FIELDS.ACTION_FIELD(vm),
|
||||
...COMMON_FIELDS(vm, true),
|
||||
PUNCTUAL_FIELDS.TIME_FIELD,
|
||||
PUNCTUAL_FIELDS.END_TYPE_FIELD,
|
||||
PUNCTUAL_FIELDS.END_VALUE_FIELD,
|
||||
],
|
||||
'SCHED_ACTION',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
|
||||
/** @type {ObjectSchema} Schema */
|
||||
export const VM_SCHED_SCHEMA = COMMON_SCHEMA.concat(
|
||||
@ -94,15 +103,21 @@ export const VM_SCHED_SCHEMA = COMMON_SCHEMA.concat(
|
||||
)
|
||||
|
||||
/** @type {Field[]} Fields for relative actions */
|
||||
export const TEMPLATE_SCHED_FIELDS = (vm) => [
|
||||
PUNCTUAL_FIELDS.ACTION_FIELD(vm),
|
||||
...COMMON_FIELDS(vm),
|
||||
PUNCTUAL_FIELDS.TIME_FIELD,
|
||||
RELATIVE_FIELDS.RELATIVE_TIME_FIELD,
|
||||
RELATIVE_FIELDS.PERIOD_FIELD,
|
||||
RELATIVE_FIELDS.END_TYPE_FIELD_WITHOUT_DATE,
|
||||
PUNCTUAL_FIELDS.END_VALUE_FIELD,
|
||||
]
|
||||
export const TEMPLATE_SCHED_FIELDS = ({ vm, oneConfig, adminGroup }) =>
|
||||
disableFields(
|
||||
[
|
||||
PUNCTUAL_FIELDS.ACTION_FIELD(vm),
|
||||
...COMMON_FIELDS(vm),
|
||||
PUNCTUAL_FIELDS.TIME_FIELD,
|
||||
RELATIVE_FIELDS.RELATIVE_TIME_FIELD,
|
||||
RELATIVE_FIELDS.PERIOD_FIELD,
|
||||
RELATIVE_FIELDS.END_TYPE_FIELD_WITHOUT_DATE,
|
||||
PUNCTUAL_FIELDS.END_VALUE_FIELD,
|
||||
],
|
||||
'SCHED_ACTION',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
|
||||
/** @type {ObjectSchema} Relative Schema */
|
||||
export const TEMPLATE_SCHED_SCHEMA = COMMON_SCHEMA.concat(
|
||||
|
@ -28,9 +28,9 @@ import {
|
||||
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/backup/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const Backup = () => (
|
||||
const Backup = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
{SECTIONS.map(({ id, ...section }) => (
|
||||
{SECTIONS(oneConfig, adminGroup).map(({ id, ...section }) => (
|
||||
<FormWithSchema
|
||||
key={id}
|
||||
id={EXTRA_ID}
|
||||
@ -44,6 +44,8 @@ const Backup = () => (
|
||||
Backup.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
Backup.displayName = 'Backup'
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
Section,
|
||||
arrayToOptions,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
@ -80,15 +81,15 @@ const MODE_FIELD = {
|
||||
}
|
||||
|
||||
/** @type {Section[]} Sections */
|
||||
export const SECTIONS = [
|
||||
export const SECTIONS = (oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'backup-configuration',
|
||||
fields: [
|
||||
BACKUP_VOLATILE_FIELD,
|
||||
FS_FREEZE_FIELD,
|
||||
KEEP_LAST_FIELD,
|
||||
MODE_FIELD,
|
||||
],
|
||||
fields: disableFields(
|
||||
[BACKUP_VOLATILE_FIELD, FS_FREEZE_FIELD, KEEP_LAST_FIELD, MODE_FIELD],
|
||||
'BACKUP_CONFIG',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -37,8 +37,11 @@ import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = 'OS'
|
||||
|
||||
const Booting = ({ hypervisor, ...props }) => {
|
||||
const sections = useMemo(() => SECTIONS(hypervisor), [hypervisor])
|
||||
const Booting = ({ hypervisor, oneConfig, adminGroup, ...props }) => {
|
||||
const sections = useMemo(
|
||||
() => SECTIONS(hypervisor, oneConfig, adminGroup),
|
||||
[hypervisor]
|
||||
)
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@ -74,6 +77,8 @@ Booting.propTypes = {
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
|
@ -27,39 +27,67 @@ import {
|
||||
Section,
|
||||
getObjectSchemaFromFields,
|
||||
filterFieldsByHypervisor,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, HYPERVISORS } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @param {HYPERVISORS} [hypervisor] - Template hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = (hypervisor) => [
|
||||
const SECTIONS = (hypervisor, oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'os-boot',
|
||||
legend: T.Boot,
|
||||
fields: filterFieldsByHypervisor(BOOT_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(BOOT_FIELDS, hypervisor),
|
||||
'OS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'os-features',
|
||||
legend: T.Features,
|
||||
fields: filterFieldsByHypervisor(FEATURES_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(FEATURES_FIELDS, hypervisor),
|
||||
'OS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'os-kernel',
|
||||
legend: T.Kernel,
|
||||
fields: filterFieldsByHypervisor(KERNEL_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(KERNEL_FIELDS, hypervisor),
|
||||
'OS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'os-ramdisk',
|
||||
legend: T.Ramdisk,
|
||||
fields: filterFieldsByHypervisor(RAMDISK_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(RAMDISK_FIELDS, hypervisor),
|
||||
'OS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'os-raw',
|
||||
legend: T.RawData,
|
||||
legendTooltip: T.RawDataConcept,
|
||||
fields: filterFieldsByHypervisor(RAW_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(RAW_FIELDS, hypervisor),
|
||||
'OS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -22,6 +22,8 @@ import { FormWithSchema, Legend } from 'client/components/Forms'
|
||||
import { SSH_PUBLIC_KEY, SCRIPT_FIELDS, OTHER_FIELDS } from './schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { disableFields } from 'client/utils'
|
||||
|
||||
const SSH_KEY_USER = '$USER[SSH_PUBLIC_KEY]'
|
||||
|
||||
/**
|
||||
@ -29,9 +31,11 @@ const SSH_KEY_USER = '$USER[SSH_PUBLIC_KEY]'
|
||||
*
|
||||
* @param {object} props - Props passed to the component
|
||||
* @param {string} [props.stepId] - ID of the step the section belongs to
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {ReactElement} - Configuration section
|
||||
*/
|
||||
const ConfigurationSection = ({ stepId }) => {
|
||||
const ConfigurationSection = ({ stepId, oneConfig, adminGroup }) => {
|
||||
const { setValue, getValues } = useFormContext()
|
||||
const SSH_PUBLIC_KEY_PATH = useMemo(
|
||||
() => [stepId, SSH_PUBLIC_KEY.name].filter(Boolean).join('.'),
|
||||
@ -66,13 +70,18 @@ const ConfigurationSection = ({ stepId }) => {
|
||||
<FormWithSchema
|
||||
id={stepId}
|
||||
cy={getCyPath('context-configuration-others')}
|
||||
fields={OTHER_FIELDS}
|
||||
fields={disableFields(OTHER_FIELDS, 'CONTEXT', oneConfig, adminGroup)}
|
||||
/>
|
||||
<section>
|
||||
<FormWithSchema
|
||||
id={stepId}
|
||||
cy={getCyPath('context-ssh-public-key')}
|
||||
fields={[SSH_PUBLIC_KEY]}
|
||||
fields={disableFields(
|
||||
[SSH_PUBLIC_KEY],
|
||||
'CONTEXT',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)}
|
||||
/>
|
||||
<Stack direction="row" gap="1em">
|
||||
<Button
|
||||
@ -95,7 +104,12 @@ const ConfigurationSection = ({ stepId }) => {
|
||||
<FormWithSchema
|
||||
id={stepId}
|
||||
cy={getCyPath('context-script')}
|
||||
fields={SCRIPT_FIELDS}
|
||||
fields={disableFields(
|
||||
SCRIPT_FIELDS,
|
||||
'CONTEXT',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)}
|
||||
rootProps={{ sx: { width: '100%', gridColumn: '1 / -1' } }}
|
||||
/>
|
||||
</Stack>
|
||||
@ -106,6 +120,8 @@ const ConfigurationSection = ({ stepId }) => {
|
||||
ConfigurationSection.propTypes = {
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ConfigurationSection
|
||||
|
@ -21,15 +21,19 @@ import { FormWithSchema } from 'client/components/Forms'
|
||||
import { FILES_FIELDS } from './schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { disableFields } from 'client/utils'
|
||||
|
||||
export const SECTION_ID = 'CONTEXT'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {string} [props.stepId] - ID of the step the section belongs to
|
||||
* @param {string} props.hypervisor - VM hypervisor
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {ReactElement} - Files section
|
||||
*/
|
||||
const FilesSection = ({ stepId, hypervisor }) => (
|
||||
const FilesSection = ({ stepId, hypervisor, oneConfig, adminGroup }) => (
|
||||
<FormWithSchema
|
||||
accordion
|
||||
legend={T.Files}
|
||||
@ -38,13 +42,24 @@ const FilesSection = ({ stepId, hypervisor }) => (
|
||||
() => [stepId, 'context-files'].filter(Boolean).join('-'),
|
||||
[stepId]
|
||||
)}
|
||||
fields={useMemo(() => FILES_FIELDS(hypervisor), [hypervisor])}
|
||||
fields={useMemo(
|
||||
() =>
|
||||
disableFields(
|
||||
FILES_FIELDS(hypervisor),
|
||||
'CONTEXT',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
[hypervisor]
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
FilesSection.propTypes = {
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default FilesSection
|
||||
|
@ -33,10 +33,10 @@ export const TAB_ID = ['CONTEXT', USER_INPUTS_ID]
|
||||
|
||||
const Context = (props) => (
|
||||
<>
|
||||
<ConfigurationSection stepId={EXTRA_ID} />
|
||||
<UserInputsSection />
|
||||
<ConfigurationSection stepId={EXTRA_ID} {...props} />
|
||||
<UserInputsSection {...props} />
|
||||
<FilesSection stepId={EXTRA_ID} {...props} />
|
||||
<ContextVarsSection stepId={EXTRA_ID} {...props} />
|
||||
<ContextVarsSection stepId={EXTRA_ID} />
|
||||
</>
|
||||
)
|
||||
|
||||
|
@ -57,6 +57,8 @@ import { USER_INPUT_SCHEMA, USER_INPUT_FIELDS } from './schema'
|
||||
import { getUserInputString } from 'client/models/Helper'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { disableFields } from 'client/utils'
|
||||
|
||||
export const SECTION_ID = 'USER_INPUTS'
|
||||
|
||||
const UserItemDraggable = styled(ListItem)(({ theme }) => ({
|
||||
@ -112,8 +114,13 @@ UserInputItem.propTypes = {
|
||||
|
||||
UserInputItem.displayName = 'UserInputItem'
|
||||
|
||||
/** @returns {JSXElementConstructor} - User Inputs section */
|
||||
const UserInputsSection = () => {
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {JSXElementConstructor} - User Inputs section
|
||||
*/
|
||||
const UserInputsSection = ({ oneConfig, adminGroup }) => {
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext()
|
||||
@ -158,7 +165,12 @@ const UserInputsSection = () => {
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-context-user-input`}
|
||||
fields={USER_INPUT_FIELDS}
|
||||
fields={disableFields(
|
||||
USER_INPUT_FIELDS,
|
||||
'USER_INPUTS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)}
|
||||
rootProps={{ sx: { m: 0 } }}
|
||||
/>
|
||||
<Button
|
||||
@ -206,4 +218,9 @@ const UserInputsSection = () => {
|
||||
)
|
||||
}
|
||||
|
||||
UserInputsSection.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default UserInputsSection
|
||||
|
@ -62,7 +62,7 @@ export const TABS = [
|
||||
Backup,
|
||||
]
|
||||
|
||||
const Content = ({ data, setFormData }) => {
|
||||
const Content = ({ data, setFormData, oneConfig, adminGroup }) => {
|
||||
const {
|
||||
watch,
|
||||
formState: { errors },
|
||||
@ -89,12 +89,21 @@ const Content = ({ data, setFormData }) => {
|
||||
name,
|
||||
label: <Translate word={name} />,
|
||||
renderContent: () => (
|
||||
<TabContent {...{ data, setFormData, hypervisor, control }} />
|
||||
<TabContent
|
||||
{...{
|
||||
data,
|
||||
setFormData,
|
||||
hypervisor,
|
||||
control,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
error: getError?.(errors[STEP_ID]),
|
||||
})
|
||||
),
|
||||
[totalErrors, view, control]
|
||||
[totalErrors, view, control, oneConfig, adminGroup]
|
||||
)
|
||||
|
||||
return <Tabs tabs={tabs} />
|
||||
@ -106,7 +115,7 @@ const Content = ({ data, setFormData }) => {
|
||||
* @param {VmTemplate} vmTemplate - VM Template
|
||||
* @returns {object} Optional configuration step
|
||||
*/
|
||||
const ExtraConfiguration = (vmTemplate) => {
|
||||
const ExtraConfiguration = ({ data: vmTemplate, oneConfig, adminGroup }) => {
|
||||
const initialHypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
|
||||
|
||||
return {
|
||||
@ -118,13 +127,15 @@ const ExtraConfiguration = (vmTemplate) => {
|
||||
return SCHEMA(hypervisor)
|
||||
},
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
content: (props) => Content({ ...props, oneConfig, adminGroup }),
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ExtraConfiguration
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
arrayToOptions,
|
||||
filterFieldsByHypervisor,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants'
|
||||
|
||||
@ -213,12 +214,28 @@ export const COMMAND = {
|
||||
|
||||
/**
|
||||
* @param {string} [hypervisor] - VM hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Field[]} List of Graphics fields
|
||||
*/
|
||||
export const GRAPHICS_FIELDS = (hypervisor) =>
|
||||
filterFieldsByHypervisor(
|
||||
[TYPE, LISTEN, PORT, KEYMAP, CUSTOM_KEYMAP, PASSWD, RANDOM_PASSWD, COMMAND],
|
||||
hypervisor
|
||||
export const GRAPHICS_FIELDS = (hypervisor, oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
filterFieldsByHypervisor(
|
||||
[
|
||||
TYPE,
|
||||
LISTEN,
|
||||
PORT,
|
||||
KEYMAP,
|
||||
CUSTOM_KEYMAP,
|
||||
PASSWD,
|
||||
RANDOM_PASSWD,
|
||||
COMMAND,
|
||||
],
|
||||
hypervisor
|
||||
),
|
||||
'GRAPHICS',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
|
||||
/** @type {ObjectSchema} Graphics schema */
|
||||
|
@ -30,7 +30,7 @@ import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = ['GRAPHICS', INPUT_ID, PCI_ID]
|
||||
|
||||
const InputOutput = ({ hypervisor }) => (
|
||||
const InputOutput = ({ hypervisor, oneConfig, adminGroup }) => (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
@ -38,12 +38,22 @@ const InputOutput = ({ hypervisor }) => (
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-io-graphics`}
|
||||
fields={GRAPHICS_FIELDS(hypervisor)}
|
||||
fields={GRAPHICS_FIELDS(hypervisor, oneConfig, adminGroup)}
|
||||
legend={T.Graphics}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
<InputsSection stepId={EXTRA_ID} hypervisor={hypervisor} />
|
||||
<PciDevicesSection stepId={EXTRA_ID} hypervisor={hypervisor} />
|
||||
<InputsSection
|
||||
stepId={EXTRA_ID}
|
||||
hypervisor={hypervisor}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
<PciDevicesSection
|
||||
stepId={EXTRA_ID}
|
||||
hypervisor={hypervisor}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
|
||||
@ -52,6 +62,8 @@ InputOutput.propTypes = {
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
InputOutput.displayName = 'InputOutput'
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
arrayToOptions,
|
||||
filterFieldsByHypervisor,
|
||||
getValidationFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
@ -72,10 +73,17 @@ const BUS = {
|
||||
|
||||
/**
|
||||
* @param {string} [hypervisor] - VM hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Field[]} List of Graphic inputs fields
|
||||
*/
|
||||
export const INPUTS_FIELDS = (hypervisor) =>
|
||||
filterFieldsByHypervisor([TYPE, BUS], hypervisor)
|
||||
export const INPUTS_FIELDS = (hypervisor, oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
filterFieldsByHypervisor([TYPE, BUS], hypervisor),
|
||||
'INPUT',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
|
||||
/** @type {ObjectSchema} Graphic input object schema */
|
||||
export const INPUT_SCHEMA = object(getValidationFromFields([TYPE, BUS]))
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, useCallback, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack, FormControl, Divider, Button, IconButton } from '@mui/material'
|
||||
import { Stack, FormControl, Divider, Button } from '@mui/material'
|
||||
import List from '@mui/material/List'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
@ -24,7 +24,7 @@ import { useFieldArray, useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { FormWithSchema, Legend } from 'client/components/Forms'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
|
||||
import {
|
||||
INPUTS_FIELDS,
|
||||
@ -33,7 +33,9 @@ import {
|
||||
busTypeIcons,
|
||||
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema'
|
||||
import { T, HYPERVISORS } from 'client/constants'
|
||||
import SubmitButton from 'client/components/FormControl/SubmitButton'
|
||||
|
||||
import { hasRestrictedAttributes } from 'client/utils'
|
||||
export const SECTION_ID = 'INPUT'
|
||||
|
||||
const InputsSection = memo(
|
||||
@ -41,10 +43,15 @@ const InputsSection = memo(
|
||||
* @param {object} props - Props
|
||||
* @param {string} [props.stepId] - ID of the step the section belongs to
|
||||
* @param {HYPERVISORS} props.hypervisor - VM hypervisor
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {ReactElement} - Inputs section
|
||||
*/
|
||||
({ stepId, hypervisor }) => {
|
||||
const fields = useMemo(() => INPUTS_FIELDS(hypervisor), [hypervisor])
|
||||
({ stepId, hypervisor, oneConfig, adminGroup }) => {
|
||||
const fields = useMemo(
|
||||
() => INPUTS_FIELDS(hypervisor, oneConfig, adminGroup),
|
||||
[hypervisor]
|
||||
)
|
||||
|
||||
const {
|
||||
fields: inputs,
|
||||
@ -112,13 +119,26 @@ const InputsSection = memo(
|
||||
const busIcon = busTypeIcons[BUS]
|
||||
const busInfo = `${BUS}`
|
||||
|
||||
// Disable action if the nic has a restricted attribute on the template
|
||||
const disabledAction =
|
||||
!adminGroup &&
|
||||
hasRestrictedAttributes(
|
||||
{ id, TYPE, BUS },
|
||||
'INPUT',
|
||||
oneConfig?.VM_RESTRICTED_ATTR
|
||||
)
|
||||
const tooltip = !disabledAction ? null : Tr(T.DetachRestricted)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={id}
|
||||
secondaryAction={
|
||||
<IconButton onClick={() => remove(index)}>
|
||||
<DeleteCircledOutline />
|
||||
</IconButton>
|
||||
<SubmitButton
|
||||
onClick={() => remove(index)}
|
||||
icon=<DeleteCircledOutline />
|
||||
disabled={disabledAction}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
}
|
||||
sx={{ '&:hover': { bgcolor: 'action.hover' } }}
|
||||
>
|
||||
@ -151,6 +171,8 @@ const InputsSection = memo(
|
||||
InputsSection.propTypes = {
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
InputsSection.displayName = 'InputsSection'
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
arrayToOptions,
|
||||
filterFieldsByHypervisor,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants'
|
||||
|
||||
@ -153,12 +154,19 @@ const TYPE_FIELD = { ...commonHiddenFieldProps('TYPE') }
|
||||
|
||||
/**
|
||||
* @param {string} [hypervisor] - VM hypervisor
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Field[]} List of Graphic inputs fields
|
||||
*/
|
||||
export const PCI_FIELDS = (hypervisor) =>
|
||||
filterFieldsByHypervisor(
|
||||
[NAME_FIELD, PROFILE_FIELD, DEVICE_FIELD, VENDOR_FIELD, CLASS_FIELD],
|
||||
hypervisor
|
||||
export const PCI_FIELDS = (hypervisor, oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
filterFieldsByHypervisor(
|
||||
[NAME_FIELD, PROFILE_FIELD, DEVICE_FIELD, VENDOR_FIELD, CLASS_FIELD],
|
||||
hypervisor
|
||||
),
|
||||
'PCI',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
|
||||
/** @type {ObjectSchema} PCI devices object schema */
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack, FormControl, Divider, Button, IconButton } from '@mui/material'
|
||||
import { Stack, FormControl, Divider, Button } from '@mui/material'
|
||||
import List from '@mui/material/List'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
@ -25,7 +25,7 @@ import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { FormWithSchema, Legend } from 'client/components/Forms'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { getPciDevices } from 'client/models/Host'
|
||||
|
||||
import {
|
||||
@ -34,16 +34,21 @@ import {
|
||||
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema'
|
||||
import { T, HYPERVISORS } from 'client/constants'
|
||||
|
||||
import { hasRestrictedAttributes } from 'client/utils'
|
||||
import SubmitButton from 'client/components/FormControl/SubmitButton'
|
||||
|
||||
export const SECTION_ID = 'PCI'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {string} [props.stepId] - ID of the step the section belongs to
|
||||
* @param {HYPERVISORS} props.hypervisor - VM hypervisor
|
||||
* @param {object} props.oneConfig - Config of oned.conf
|
||||
* @param {boolean} props.adminGroup - User is admin or not
|
||||
* @returns {ReactElement} - Inputs section
|
||||
*/
|
||||
const PciDevicesSection = ({ stepId, hypervisor }) => {
|
||||
const fields = useMemo(() => PCI_FIELDS(hypervisor))
|
||||
const PciDevicesSection = ({ stepId, hypervisor, oneConfig, adminGroup }) => {
|
||||
const fields = useMemo(() => PCI_FIELDS(hypervisor, oneConfig, adminGroup))
|
||||
|
||||
const { data: hosts = [] } = useGetHostsQuery()
|
||||
const pciDevicesAvailable = useMemo(
|
||||
@ -121,13 +126,26 @@ const PciDevicesSection = ({ stepId, hypervisor }) => {
|
||||
secondaryFields.push(`${T.Profile}: ${PROFILE}`)
|
||||
}
|
||||
|
||||
// Disable action if the nic has a restricted attribute on the template
|
||||
const disabledAction =
|
||||
!adminGroup &&
|
||||
hasRestrictedAttributes(
|
||||
{ id, DEVICE, VENDOR, CLASS, PROFILE, ...rest },
|
||||
'PCI',
|
||||
oneConfig?.VM_RESTRICTED_ATTR
|
||||
)
|
||||
const tooltip = !disabledAction ? null : Tr(T.DetachRestricted)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={id}
|
||||
secondaryAction={
|
||||
<IconButton onClick={() => remove(index)}>
|
||||
<DeleteCircledOutline />
|
||||
</IconButton>
|
||||
<SubmitButton
|
||||
onClick={() => remove(index)}
|
||||
icon=<DeleteCircledOutline />
|
||||
disabled={disabledAction}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
}
|
||||
sx={{ '&:hover': { bgcolor: 'action.hover' } }}
|
||||
>
|
||||
@ -148,6 +166,8 @@ const PciDevicesSection = ({ stepId, hypervisor }) => {
|
||||
PciDevicesSection.propTypes = {
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
PciDevicesSection.displayName = 'PciDevicesSection'
|
||||
|
@ -43,7 +43,7 @@ const mapNicNameFunction = mapNameByIndex(TAB_ID[0])
|
||||
const mapAliasNameFunction = mapNameByIndex(TAB_ID[1])
|
||||
const mapPCINameFunction = mapNameByIndex(TAB_ID[2])
|
||||
|
||||
const Networking = ({ hypervisor }) => {
|
||||
const Networking = ({ hypervisor, oneConfig, adminGroup }) => {
|
||||
const { setValue, getValues } = useFormContext()
|
||||
|
||||
const {
|
||||
@ -170,6 +170,8 @@ const Networking = ({ hypervisor }) => {
|
||||
<AttachAction
|
||||
currentNics={nics}
|
||||
hypervisor={hypervisor}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
onSubmit={handleAppend}
|
||||
/>
|
||||
<Stack
|
||||
@ -201,11 +203,15 @@ const Networking = ({ hypervisor }) => {
|
||||
<DetachAction
|
||||
nic={item}
|
||||
onSubmit={() => removeAndReorder(item)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
)}
|
||||
<AttachAction
|
||||
nic={item}
|
||||
hypervisor={hypervisor}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
currentNics={nics}
|
||||
onSubmit={(updatedNic) =>
|
||||
handleUpdate(updatedNic, id, item)
|
||||
@ -234,6 +240,8 @@ Networking.propTypes = {
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
|
@ -30,27 +30,34 @@ import {
|
||||
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import { disableFields } from 'client/utils'
|
||||
|
||||
export const TAB_ID = 'NUMA'
|
||||
|
||||
const Numa = ({ hypervisor }) => {
|
||||
const Numa = ({ hypervisor, oneConfig, adminGroup }) => {
|
||||
const enableNuma = useWatch({ name: `${EXTRA_ID}.${ENABLE_NUMA.name}` })
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-vcpu`}
|
||||
fields={[VCPU_FIELD]}
|
||||
fields={disableFields([VCPU_FIELD], 'TOPOLOGY', oneConfig, adminGroup)}
|
||||
id={GENERAL_ID}
|
||||
/>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-numa-enable`}
|
||||
fields={[ENABLE_NUMA]}
|
||||
fields={disableFields([ENABLE_NUMA], 'TOPOLOGY', oneConfig, adminGroup)}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
{enableNuma && (
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-numa`}
|
||||
fields={NUMA_FIELDS(hypervisor)}
|
||||
fields={disableFields(
|
||||
NUMA_FIELDS(hypervisor),
|
||||
'TOPOLOGY',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
)}
|
||||
@ -63,6 +70,8 @@ Numa.propTypes = {
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
|
@ -28,7 +28,7 @@ import {
|
||||
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/placement/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const Placement = () => (
|
||||
const Placement = ({ oneConfig, adminGroup }) => (
|
||||
// TODO - Host requirements: add button to select HOST in list => ID="<id>"
|
||||
// TODO - Host policy options: Packing|Stripping|Load-aware
|
||||
|
||||
@ -36,7 +36,7 @@ const Placement = () => (
|
||||
// TODO - DS policy options: Packing|Stripping
|
||||
|
||||
<>
|
||||
{SECTIONS.map(({ id, ...section }) => (
|
||||
{SECTIONS(oneConfig, adminGroup).map(({ id, ...section }) => (
|
||||
<FormWithSchema
|
||||
key={id}
|
||||
id={EXTRA_ID}
|
||||
@ -50,6 +50,8 @@ const Placement = () => (
|
||||
Placement.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
Placement.displayName = 'Placement'
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string } from 'yup'
|
||||
|
||||
import { Field, Section } from 'client/utils'
|
||||
import { Field, Section, disableFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
|
||||
/** @type {Field} Host requirement field */
|
||||
@ -55,16 +55,26 @@ const DS_RANK_FIELD = {
|
||||
}
|
||||
|
||||
/** @type {Section[]} Sections */
|
||||
const SECTIONS = [
|
||||
const SECTIONS = (oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'placement-host',
|
||||
legend: T.Host,
|
||||
fields: [HOST_REQ_FIELD, HOST_RANK_FIELD],
|
||||
fields: disableFields(
|
||||
[HOST_REQ_FIELD, HOST_RANK_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'placement-ds',
|
||||
legend: T.Datastore,
|
||||
fields: [DS_REQ_FIELD, DS_RANK_FIELD],
|
||||
fields: disableFields(
|
||||
[DS_REQ_FIELD, DS_RANK_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -32,11 +32,13 @@ import {
|
||||
import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export const TAB_ID = 'SCHED_ACTION'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('SCHED_ACTION')
|
||||
|
||||
const ScheduleAction = () => {
|
||||
const ScheduleAction = ({ oneConfig, adminGroup }) => {
|
||||
const {
|
||||
fields: scheduleActions,
|
||||
remove,
|
||||
@ -70,7 +72,12 @@ const ScheduleAction = () => {
|
||||
return (
|
||||
<>
|
||||
<Stack flexDirection="row" gap="1em">
|
||||
<CreateSchedButton relative onSubmit={handleCreateAction} />
|
||||
<CreateSchedButton
|
||||
relative
|
||||
onSubmit={handleCreateAction}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
<CharterButton relative onSubmit={handleCreateCharter} />
|
||||
</Stack>
|
||||
|
||||
@ -96,10 +103,14 @@ const ScheduleAction = () => {
|
||||
vm={{}}
|
||||
schedule={fakeValues}
|
||||
onSubmit={(newAction) => handleUpdate(newAction, index)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
<DeleteSchedButton
|
||||
schedule={fakeValues}
|
||||
onSubmit={() => handleRemove(index)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
@ -111,6 +122,13 @@ const ScheduleAction = () => {
|
||||
)
|
||||
}
|
||||
|
||||
ScheduleAction.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'sched_action',
|
||||
|
@ -42,7 +42,7 @@ export const TAB_ID = 'DISK'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('DISK')
|
||||
|
||||
const Storage = ({ hypervisor }) => {
|
||||
const Storage = ({ hypervisor, oneConfig, adminGroup }) => {
|
||||
const { getValues, setValue } = useFormContext()
|
||||
const {
|
||||
fields: disks,
|
||||
@ -76,6 +76,8 @@ const Storage = ({ hypervisor }) => {
|
||||
<div>
|
||||
<AttachAction
|
||||
hypervisor={hypervisor}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
onSubmit={(image) => append(mapNameFunction(image, disks.length))}
|
||||
/>
|
||||
<Stack
|
||||
@ -102,11 +104,15 @@ const Storage = ({ hypervisor }) => {
|
||||
<DetachAction
|
||||
disk={item}
|
||||
name={getDiskName(item)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
onSubmit={() => removeAndReorder(item?.NAME)}
|
||||
/>
|
||||
<AttachAction
|
||||
disk={item}
|
||||
hypervisor={hypervisor}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
onSubmit={(updatedDisk) => handleUpdate(updatedDisk, index)}
|
||||
/>
|
||||
</>
|
||||
@ -130,6 +136,8 @@ Storage.propTypes = {
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
|
@ -33,7 +33,7 @@ let generalFeatures
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
const Content = ({ isUpdate }) => {
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) => {
|
||||
const classes = useStyles()
|
||||
const { view, getResourceView } = useViews()
|
||||
const hypervisor = useWatch({ name: `${STEP_ID}.HYPERVISOR` })
|
||||
@ -47,7 +47,7 @@ const Content = ({ isUpdate }) => {
|
||||
generalFeatures = features
|
||||
|
||||
return (
|
||||
SECTIONS(hypervisor, isUpdate, features)
|
||||
SECTIONS(hypervisor, isUpdate, features, oneConfig, adminGroup)
|
||||
.filter(
|
||||
({ id, required }) => required || sectionsAvailable.includes(id)
|
||||
)
|
||||
@ -77,7 +77,7 @@ const Content = ({ isUpdate }) => {
|
||||
* @param {VmTemplate} vmTemplate - VM Template
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = (vmTemplate) => {
|
||||
const General = ({ data: vmTemplate, oneConfig, adminGroup }) => {
|
||||
const isUpdate = vmTemplate?.NAME
|
||||
const initialHypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
|
||||
|
||||
@ -90,12 +90,14 @@ const General = (vmTemplate) => {
|
||||
return SCHEMA(hypervisor, isUpdate, generalFeatures)
|
||||
},
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content({ isUpdate }),
|
||||
content: () => Content({ isUpdate, oneConfig, adminGroup }),
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default General
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
Section,
|
||||
filterFieldsByHypervisor,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, HYPERVISORS, VmTemplateFeatures } from 'client/constants'
|
||||
|
||||
@ -42,60 +43,112 @@ import { T, HYPERVISORS, VmTemplateFeatures } from 'client/constants'
|
||||
* @param {HYPERVISORS} [hypervisor] - Template hypervisor
|
||||
* @param {boolean} [isUpdate] - If `true`, the form is being updated
|
||||
* @param {VmTemplateFeatures} [features] - Features
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Section[]} Fields
|
||||
*/
|
||||
const SECTIONS = (hypervisor, isUpdate, features) =>
|
||||
const SECTIONS = (hypervisor, isUpdate, features, oneConfig, adminGroup) =>
|
||||
[
|
||||
{
|
||||
id: 'information',
|
||||
legend: T.Information,
|
||||
required: true,
|
||||
fields: INFORMATION_FIELDS(isUpdate),
|
||||
fields: disableFields(
|
||||
INFORMATION_FIELDS(isUpdate),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'hypervisor',
|
||||
legend: T.Hypervisor,
|
||||
required: true,
|
||||
fields: [HYPERVISOR_FIELD, VROUTER_FIELD],
|
||||
fields: disableFields(
|
||||
[HYPERVISOR_FIELD, VROUTER_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'capacity',
|
||||
legend: T.Memory,
|
||||
fields: filterFieldsByHypervisor(MEMORY_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(MEMORY_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'capacity',
|
||||
fields: filterFieldsByHypervisor(MEMORY_RESIZE_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(MEMORY_RESIZE_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
!features?.hide_cpu && {
|
||||
id: 'capacity',
|
||||
legend: T.PhysicalCpu,
|
||||
fields: filterFieldsByHypervisor(CPU_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(CPU_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'capacity',
|
||||
legend: T.VirtualCpu,
|
||||
fields: filterFieldsByHypervisor(VCPU_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(VCPU_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'showback',
|
||||
legend: T.Cost,
|
||||
fields: filterFieldsByHypervisor(SHOWBACK_FIELDS(features), hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(SHOWBACK_FIELDS(features), hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'ownership',
|
||||
legend: T.Ownership,
|
||||
fields: filterFieldsByHypervisor(OWNERSHIP_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(OWNERSHIP_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vm_group',
|
||||
legend: T.VMGroup,
|
||||
fields: filterFieldsByHypervisor(VM_GROUP_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(VM_GROUP_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vcenter',
|
||||
legend: T.vCenterDeployment,
|
||||
fields: filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
].filter(Boolean)
|
||||
|
||||
@ -103,9 +156,11 @@ const SECTIONS = (hypervisor, isUpdate, features) =>
|
||||
* @param {HYPERVISORS} [hypervisor] - Template hypervisor
|
||||
* @param {boolean} [isUpdate] - If `true`, the form is being updated
|
||||
* @param {VmTemplateFeatures} [features] - Features
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {BaseSchema} Step schema
|
||||
*/
|
||||
const SCHEMA = (hypervisor, isUpdate, features) =>
|
||||
const SCHEMA = (hypervisor, isUpdate, features, oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(
|
||||
SECTIONS(hypervisor, isUpdate, features)
|
||||
.map(({ fields }) => fields)
|
||||
|
@ -32,7 +32,7 @@ let generalFeatures
|
||||
|
||||
export const STEP_ID = 'configuration'
|
||||
|
||||
const Content = ({ vmTemplate }) => {
|
||||
const Content = ({ vmTemplate, oneConfig, adminGroup }) => {
|
||||
const classes = useStyles()
|
||||
const { view, getResourceView } = useViews()
|
||||
const { getValues, setValue } = useFormContext()
|
||||
@ -47,7 +47,7 @@ const Content = ({ vmTemplate }) => {
|
||||
|
||||
generalFeatures = features
|
||||
|
||||
return SECTIONS(vmTemplate, features).filter(
|
||||
return SECTIONS(vmTemplate, features, oneConfig, adminGroup).filter(
|
||||
({ id, required }) => required || sectionsAvailable.includes(id)
|
||||
)
|
||||
}, [view])
|
||||
@ -84,6 +84,8 @@ const Content = ({ vmTemplate }) => {
|
||||
|
||||
Content.propTypes = {
|
||||
vmTemplate: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,12 +94,12 @@ Content.propTypes = {
|
||||
* @param {VmTemplate} vmTemplate - VM Template
|
||||
* @returns {object} Basic configuration step
|
||||
*/
|
||||
const BasicConfiguration = (vmTemplate) => ({
|
||||
const BasicConfiguration = ({ data: vmTemplate, oneConfig, adminGroup }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: () => SCHEMA(vmTemplate, generalFeatures),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (props) => Content({ ...props, vmTemplate }),
|
||||
content: (props) => Content({ ...props, vmTemplate, oneConfig, adminGroup }),
|
||||
})
|
||||
|
||||
export default BasicConfiguration
|
||||
|
@ -29,45 +29,73 @@ import {
|
||||
getObjectSchemaFromFields,
|
||||
Field,
|
||||
Section,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, VmTemplate, VmTemplateFeatures } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @param {VmTemplate} [vmTemplate] - VM Template
|
||||
* @param {VmTemplateFeatures} [features] - Features
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = (vmTemplate, features) => {
|
||||
const SECTIONS = (vmTemplate, features, oneConfig, adminGroup) => {
|
||||
const hypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'information',
|
||||
legend: T.Information,
|
||||
fields: filterFieldsByHypervisor(INFORMATION_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(INFORMATION_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'capacity',
|
||||
legend: T.Capacity,
|
||||
fields: filterFieldsByHypervisor(
|
||||
CAPACITY_FIELDS(vmTemplate, features),
|
||||
hypervisor
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(
|
||||
CAPACITY_FIELDS(vmTemplate, features),
|
||||
hypervisor
|
||||
),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'ownership',
|
||||
legend: T.Ownership,
|
||||
fields: filterFieldsByHypervisor(OWNERSHIP_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(OWNERSHIP_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vm_group',
|
||||
legend: T.VMGroup,
|
||||
fields: filterFieldsByHypervisor(VM_GROUP_FIELDS, hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(VM_GROUP_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'vcenter',
|
||||
legend: T.vCenterDeployment,
|
||||
fields: filterFieldsByHypervisor([VCENTER_FOLDER_FIELD], hypervisor),
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor([VCENTER_FOLDER_FIELD], hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export const TABS = [
|
||||
},
|
||||
]
|
||||
|
||||
const Content = ({ data, setFormData, hypervisor }) => {
|
||||
const Content = ({ data, setFormData, hypervisor, oneConfig, adminGroup }) => {
|
||||
const {
|
||||
formState: { errors },
|
||||
control,
|
||||
@ -74,7 +74,16 @@ const Content = ({ data, setFormData, hypervisor }) => {
|
||||
name,
|
||||
label: <Translate word={name} />,
|
||||
renderContent: () => (
|
||||
<TabContent {...{ data, setFormData, hypervisor, control }} />
|
||||
<TabContent
|
||||
{...{
|
||||
data,
|
||||
setFormData,
|
||||
hypervisor,
|
||||
control,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
error: getError?.(errors[STEP_ID]),
|
||||
})
|
||||
@ -91,7 +100,7 @@ const Content = ({ data, setFormData, hypervisor }) => {
|
||||
* @param {VmTemplate} vmTemplate - VM Template
|
||||
* @returns {object} Optional configuration step
|
||||
*/
|
||||
const ExtraConfiguration = (vmTemplate) => {
|
||||
const ExtraConfiguration = ({ data: vmTemplate, oneConfig, adminGroup }) => {
|
||||
const hypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
|
||||
|
||||
return {
|
||||
@ -99,7 +108,8 @@ const ExtraConfiguration = (vmTemplate) => {
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (props) => Content({ ...props, hypervisor }),
|
||||
content: (props) =>
|
||||
Content({ ...props, hypervisor, oneConfig, adminGroup }),
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +117,8 @@ Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ExtraConfiguration
|
||||
|
@ -23,7 +23,7 @@ import UserInputs, {
|
||||
STEP_ID as USER_INPUTS_ID,
|
||||
} from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/UserInputs'
|
||||
import { jsonToXml, userInputsToArray } from 'client/models/Helper'
|
||||
import { createSteps, deleteObjectKeys } from 'client/utils'
|
||||
import { createSteps } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(
|
||||
(vmTemplate) => {
|
||||
@ -56,25 +56,6 @@ const Steps = createSteps(
|
||||
[EXTRA_ID]: extraTemplate = {},
|
||||
} = formData ?? {}
|
||||
|
||||
if (!adminGroup) {
|
||||
const vmRestrictedAttributes = oneConfig?.VM_RESTRICTED_ATTR ?? []
|
||||
vmRestrictedAttributes.forEach((restrictedAttr) => {
|
||||
const splitedAttr = restrictedAttr.split('/')
|
||||
|
||||
/**
|
||||
* For now, we will delete only the DISK attributes as we have to
|
||||
* investigate the core behavior related to each of them (i.e.:
|
||||
* Disk restricted attributes expect to be deleted, but NIC ones
|
||||
* must be kept unchanged).
|
||||
*
|
||||
* TODO: Review each VM_RESTRICTED_ATTR behavior to implement
|
||||
* the corresponding logic for them
|
||||
*/
|
||||
if (splitedAttr[0] !== 'DISK') return
|
||||
deleteObjectKeys(splitedAttr, extraTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
vmTemplate?.TEMPLATE?.OS &&
|
||||
extraTemplate?.OS &&
|
||||
(extraTemplate.OS = {
|
||||
|
@ -36,8 +36,19 @@ import { T } from 'client/constants'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
import { hasRestrictedAttributes } from 'client/utils'
|
||||
|
||||
const AttachAction = memo(
|
||||
({ vmId, hypervisor, nic, currentNics, onSubmit, sx }) => {
|
||||
({
|
||||
vmId,
|
||||
hypervisor,
|
||||
nic,
|
||||
currentNics,
|
||||
onSubmit,
|
||||
sx,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}) => {
|
||||
const [attachNic] = useAttachNicMutation()
|
||||
|
||||
const handleAttachNic = async (formData) => {
|
||||
@ -76,7 +87,13 @@ const AttachAction = memo(
|
||||
dialogProps: { title: T.AttachNic, dataCy: 'modal-attach-nic' },
|
||||
form: () =>
|
||||
AttachNicForm({
|
||||
stepProps: { hypervisor, nics: currentNics, defaultData: nic },
|
||||
stepProps: {
|
||||
hypervisor,
|
||||
nics: currentNics,
|
||||
defaultData: nic,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
},
|
||||
initialValues: nic,
|
||||
}),
|
||||
onSubmit: handleAttachNic,
|
||||
@ -87,42 +104,50 @@ const AttachAction = memo(
|
||||
}
|
||||
)
|
||||
|
||||
const DetachAction = memo(({ vmId, nic, onSubmit, sx }) => {
|
||||
const [detachNic] = useDetachNicMutation()
|
||||
const { NIC_ID, PARENT } = nic
|
||||
const isAlias = !!PARENT?.length
|
||||
const DetachAction = memo(
|
||||
({ vmId, nic, onSubmit, sx, oneConfig, adminGroup }) => {
|
||||
const [detachNic] = useDetachNicMutation()
|
||||
const { NIC_ID, PARENT } = nic
|
||||
const isAlias = !!PARENT?.length
|
||||
|
||||
const handleDetach = async () => {
|
||||
const handleDetachNic = onSubmit ?? detachNic
|
||||
await handleDetachNic({ id: vmId, nic: NIC_ID })
|
||||
}
|
||||
const handleDetach = async () => {
|
||||
const handleDetachNic = onSubmit ?? detachNic
|
||||
await handleDetachNic({ id: vmId, nic: NIC_ID })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `detach-nic-${NIC_ID}`,
|
||||
icon: <Trash />,
|
||||
tooltip: Tr(T.Detach),
|
||||
sx,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.DetachSomething}
|
||||
values={`${isAlias ? T.Alias : T.NIC} #${NIC_ID}`}
|
||||
/>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
// Disable action if the nic has a restricted attribute on the template
|
||||
const disabledAction =
|
||||
!adminGroup &&
|
||||
hasRestrictedAttributes(nic, 'NIC', oneConfig?.VM_RESTRICTED_ATTR)
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `detach-nic-${NIC_ID}`,
|
||||
icon: <Trash />,
|
||||
tooltip: !disabledAction ? Tr(T.Detach) : Tr(T.DetachRestricted),
|
||||
sx,
|
||||
disabled: disabledAction,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.DetachSomething}
|
||||
values={`${isAlias ? T.Alias : T.NIC} #${NIC_ID}`}
|
||||
/>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit: handleDetach,
|
||||
},
|
||||
onSubmit: handleDetach,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const UpdateAction = memo(({ vmId, nic, sx }) => {
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
@ -246,6 +271,8 @@ const ActionPropTypes = {
|
||||
securityGroupId: PropTypes.string,
|
||||
onSubmit: PropTypes.func,
|
||||
sx: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
AttachAction.propTypes = ActionPropTypes
|
||||
|
@ -46,115 +46,134 @@ import { jsonToXml } from 'client/models/Helper'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { T, VM_ACTIONS } from 'client/constants'
|
||||
|
||||
const AttachAction = memo(({ vmId, disk, hypervisor, onSubmit, sx }) => {
|
||||
const [attachDisk] = useAttachDiskMutation()
|
||||
const formConfig = { stepProps: { hypervisor }, initialValues: disk }
|
||||
import { hasRestrictedAttributes } from 'client/utils'
|
||||
|
||||
const handleAttachDisk = async (formData) => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(formData)
|
||||
const AttachAction = memo(
|
||||
({ vmId, disk, hypervisor, onSubmit, sx, oneConfig, adminGroup }) => {
|
||||
const [attachDisk] = useAttachDiskMutation()
|
||||
const formConfig = {
|
||||
stepProps: { hypervisor, oneConfig, adminGroup },
|
||||
initialValues: disk,
|
||||
}
|
||||
|
||||
const template = jsonToXml({ DISK: formData })
|
||||
await attachDisk({ id: vmId, template })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={
|
||||
disk
|
||||
? {
|
||||
'data-cy': `edit-${disk.DISK_ID}`,
|
||||
icon: <Edit />,
|
||||
tooltip: Tr(T.Edit),
|
||||
sx,
|
||||
}
|
||||
: {
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-disk',
|
||||
label: T.AttachDisk,
|
||||
variant: 'outlined',
|
||||
sx,
|
||||
}
|
||||
const handleAttachDisk = async (formData) => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(formData)
|
||||
}
|
||||
options={
|
||||
disk
|
||||
? [
|
||||
{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.EditSomething} values={[disk?.NAME]} />
|
||||
),
|
||||
},
|
||||
form: () =>
|
||||
!disk?.IMAGE && !disk?.IMAGE_ID // is volatile
|
||||
? VolatileSteps(formConfig)
|
||||
: ImageSteps(formConfig),
|
||||
onSubmit: handleAttachDisk,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
cy: 'attach-image',
|
||||
name: T.Image,
|
||||
dialogProps: {
|
||||
title: T.AttachImage,
|
||||
dataCy: 'modal-attach-image',
|
||||
},
|
||||
form: () => ImageSteps(formConfig),
|
||||
onSubmit: handleAttachDisk,
|
||||
},
|
||||
{
|
||||
cy: 'attach-volatile',
|
||||
name: T.Volatile,
|
||||
dialogProps: {
|
||||
title: T.AttachVolatile,
|
||||
dataCy: 'modal-attach-volatile',
|
||||
},
|
||||
form: () => VolatileSteps(formConfig),
|
||||
onSubmit: handleAttachDisk,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const DetachAction = memo(({ vmId, disk, name: imageName, onSubmit, sx }) => {
|
||||
const [detachDisk] = useDetachDiskMutation()
|
||||
const { DISK_ID } = disk
|
||||
const template = jsonToXml({ DISK: formData })
|
||||
await attachDisk({ id: vmId, template })
|
||||
}
|
||||
|
||||
const handleDetach = async () => {
|
||||
const handleDetachDisk = onSubmit ?? detachDisk
|
||||
await handleDetachDisk({ id: vmId, disk: DISK_ID })
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={
|
||||
disk
|
||||
? {
|
||||
'data-cy': `edit-${disk.DISK_ID}`,
|
||||
icon: <Edit />,
|
||||
tooltip: Tr(T.Edit),
|
||||
sx,
|
||||
}
|
||||
: {
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-disk',
|
||||
label: T.AttachDisk,
|
||||
variant: 'outlined',
|
||||
sx,
|
||||
}
|
||||
}
|
||||
options={
|
||||
disk
|
||||
? [
|
||||
{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate word={T.EditSomething} values={[disk?.NAME]} />
|
||||
),
|
||||
},
|
||||
form: () =>
|
||||
!disk?.IMAGE && !disk?.IMAGE_ID // is volatile
|
||||
? VolatileSteps(formConfig)
|
||||
: ImageSteps(formConfig),
|
||||
onSubmit: handleAttachDisk,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
cy: 'attach-image',
|
||||
name: T.Image,
|
||||
dialogProps: {
|
||||
title: T.AttachImage,
|
||||
dataCy: 'modal-attach-image',
|
||||
},
|
||||
form: () => ImageSteps(formConfig),
|
||||
onSubmit: handleAttachDisk,
|
||||
},
|
||||
{
|
||||
cy: 'attach-volatile',
|
||||
name: T.Volatile,
|
||||
dialogProps: {
|
||||
title: T.AttachVolatile,
|
||||
dataCy: 'modal-attach-volatile',
|
||||
},
|
||||
form: () => VolatileSteps(formConfig),
|
||||
onSubmit: handleAttachDisk,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VM_ACTIONS.DETACH_DISK}-${DISK_ID}`,
|
||||
icon: <Trash />,
|
||||
tooltip: Tr(T.Detach),
|
||||
sx,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.DetachSomething}
|
||||
values={`#${DISK_ID} - ${imageName}`}
|
||||
/>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
const DetachAction = memo(
|
||||
({ vmId, disk, name: imageName, onSubmit, sx, oneConfig, adminGroup }) => {
|
||||
const [detachDisk] = useDetachDiskMutation()
|
||||
const { DISK_ID } = disk
|
||||
|
||||
const handleDetach = async () => {
|
||||
const handleDetachDisk = onSubmit ?? detachDisk
|
||||
await handleDetachDisk({ id: vmId, disk: DISK_ID })
|
||||
}
|
||||
|
||||
// Disable action if the disk has a restricted attribute on the template
|
||||
const disabledAction =
|
||||
!adminGroup &&
|
||||
hasRestrictedAttributes(
|
||||
disk.ORIGINAL,
|
||||
'DISK',
|
||||
oneConfig?.VM_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VM_ACTIONS.DETACH_DISK}-${DISK_ID}`,
|
||||
icon: <Trash />,
|
||||
tooltip: !disabledAction ? Tr(T.Detach) : Tr(T.DetachRestricted),
|
||||
sx,
|
||||
disabled: disabledAction,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.DetachSomething}
|
||||
values={`#${DISK_ID} - ${imageName}`}
|
||||
/>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit: handleDetach,
|
||||
},
|
||||
onSubmit: handleDetach,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const SaveAsAction = memo(({ vmId, disk, snapshot, name: imageName, sx }) => {
|
||||
const [saveAsDisk] = useSaveAsDiskMutation()
|
||||
@ -382,6 +401,8 @@ const ActionPropTypes = {
|
||||
name: PropTypes.string,
|
||||
onSubmit: PropTypes.func,
|
||||
sx: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
AttachAction.propTypes = ActionPropTypes
|
||||
|
@ -102,6 +102,8 @@ module.exports = {
|
||||
Deploy: 'Deploy',
|
||||
DeployServiceTemplate: 'Deploy Service Template',
|
||||
Detach: 'Detach',
|
||||
DetachRestricted:
|
||||
'You cannot delete this resource because it has restricted attributes on this template. Please, contact with your administrator.',
|
||||
DetachSomething: 'Detach: %s',
|
||||
Disable: 'Disable',
|
||||
Dismiss: 'Dismiss',
|
||||
|
@ -35,6 +35,18 @@ import {
|
||||
import { CreateForm } from 'client/components/Forms/VmTemplate'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { jsonToXml, xmlToJson } from 'client/models/Helper'
|
||||
|
||||
import { useSystemData } from 'client/features/Auth'
|
||||
|
||||
import {
|
||||
addTempInfo,
|
||||
deleteTempInfo,
|
||||
deleteRestrictedAttributes,
|
||||
} from 'client/utils'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a VM Template.
|
||||
*
|
||||
@ -48,11 +60,24 @@ function CreateVmTemplate() {
|
||||
const [update] = useUpdateTemplateMutation()
|
||||
const [allocate] = useAllocateTemplateMutation()
|
||||
|
||||
const { data } = useGetTemplateQuery(
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
|
||||
const { data: apiTemplateDataExtended } = useGetTemplateQuery(
|
||||
{ id: templateId, extended: true },
|
||||
{ skip: templateId === undefined }
|
||||
)
|
||||
|
||||
const { data: apiTemplateData } = useGetTemplateQuery(
|
||||
{ id: templateId, extended: false },
|
||||
{ skip: templateId === undefined }
|
||||
)
|
||||
|
||||
const dataTemplateExtended = _.cloneDeep(apiTemplateDataExtended)
|
||||
const dataTemplate = _.cloneDeep(apiTemplateData)
|
||||
|
||||
// #6154: Add an unique identifier to compare on submit items that exists at the beginning of the update
|
||||
if (!adminGroup) addTempInfo(dataTemplate, dataTemplateExtended)
|
||||
|
||||
useGetVMGroupsQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
useGetHostsQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
useGetImagesQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
@ -66,19 +91,67 @@ function CreateVmTemplate() {
|
||||
history.push(PATH.TEMPLATE.VMS.LIST)
|
||||
enqueueSuccess(`VM Template created - #${newTemplateId}`)
|
||||
} else {
|
||||
await update({ id: templateId, template: xmlTemplate }).unwrap()
|
||||
/**
|
||||
* #6154: Consideration about resolving this issue:
|
||||
*
|
||||
* When the user is a non admin user, we have to delete the restricted attributes of the DISK to avoid errors.
|
||||
*
|
||||
* Core behaviour: The core will fail if in the template there is a restricted attribute that has a different value before modify it.
|
||||
* EXAMPLES:
|
||||
* - If you add a restricted attribute on the template
|
||||
* - If you remove a restricted attribute on the template
|
||||
* - If you modify the value of a restricted attribute on the template
|
||||
*
|
||||
* Core will not fail if you send a restricted attribute in the template without modify him.
|
||||
* EXAMPLES:
|
||||
* - If your template has a restricted attribute with value 1024 and you send the same attribute with 1024 value, core will not fail
|
||||
*
|
||||
* Fireedge Sunstone behaviour: The app has a different behaviour between the DISK attribute of a template and another attributes like NIC, CONTEXT, GRAPHICS,... The sequence when you're updating a template is the following:
|
||||
* 1. Get the info of the template from the core with extended value false. That returns only what the template has on the core.
|
||||
* 2. Get the info of the template from the core with extended value true. That returns the info of the template plus the info of the disk (only disk, not NIC or another attributes).
|
||||
* 3. When the template is update, DISK could have some attributes that are not in the template, so this could cause a failure.
|
||||
*
|
||||
* To resolve the issue we delete restricted attributes if there are not in template when the user is non admin . This can be done because the user can modify the restricted attributes (as part of this issue, the schemas has a read only attribute if the field is restricted)
|
||||
*
|
||||
* We delete this info onto onSubmit function becasue we need to get the original tempalte without modify. So there is need a hook that we can't do on tranformBeforeSubmit.
|
||||
* Also, the data that is an input parameter of the CreateForm is the data with extended values, so it's no possible to to that using initualValues on transformBeforeSubmit.
|
||||
*
|
||||
*/
|
||||
|
||||
// #6154: Delete restricted attributes (if there are not on the original template)
|
||||
const jsonFinal = adminGroup
|
||||
? xmlToJson(xmlTemplate)
|
||||
: deleteRestrictedAttributes(
|
||||
xmlToJson(xmlTemplate),
|
||||
dataTemplate?.TEMPLATE,
|
||||
oneConfig?.VM_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
// #6154: Delete unique identifier to compare on submit items that exists at the beginning of the update
|
||||
if (!adminGroup) {
|
||||
deleteTempInfo(jsonFinal)
|
||||
}
|
||||
|
||||
// Transform json to xml
|
||||
const xmlFinal = jsonToXml(jsonFinal)
|
||||
|
||||
await update({ id: templateId, template: xmlFinal }).unwrap()
|
||||
history.push(PATH.TEMPLATE.VMS.LIST)
|
||||
enqueueSuccess(`VM Template updated - #${templateId} ${NAME}`)
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return templateId && !data ? (
|
||||
return templateId && (!dataTemplateExtended || !dataTemplate) ? (
|
||||
<SkeletonStepsForm />
|
||||
) : (
|
||||
<CreateForm
|
||||
initialValues={data}
|
||||
stepProps={data}
|
||||
initialValues={dataTemplateExtended}
|
||||
stepProps={{
|
||||
dataTemplateExtended,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
>
|
||||
|
@ -31,6 +31,16 @@ import {
|
||||
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'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Displays the instantiation form for a VM Template.
|
||||
*
|
||||
@ -43,17 +53,56 @@ function InstantiateVmTemplate() {
|
||||
const { enqueueInfo } = useGeneralApi()
|
||||
const [instantiate] = useInstantiateTemplateMutation()
|
||||
|
||||
const { data, isError } = useGetTemplateQuery(
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
|
||||
const { data: apiTemplateDataExtended, isError } = useGetTemplateQuery(
|
||||
{ id: templateId, extended: true },
|
||||
{ skip: templateId === undefined, refetchOnMountOrArgChange: false }
|
||||
{ skip: templateId === undefined }
|
||||
)
|
||||
|
||||
const { data: apiTemplateData } = useGetTemplateQuery(
|
||||
{ id: templateId, extended: false },
|
||||
{ skip: templateId === undefined }
|
||||
)
|
||||
|
||||
const dataTemplateExtended = _.cloneDeep(apiTemplateDataExtended)
|
||||
const dataTemplate = _.cloneDeep(apiTemplateData)
|
||||
|
||||
// #6154: Add an unique identifier to compare on submit items that exists at the beginning of the update
|
||||
if (!adminGroup) addTempInfo(dataTemplate, dataTemplateExtended)
|
||||
|
||||
useGetUsersQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
useGetGroupsQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
|
||||
const onSubmit = async (templates) => {
|
||||
try {
|
||||
await Promise.all(templates.map((t) => instantiate(t).unwrap()))
|
||||
await Promise.all(
|
||||
templates.map((t) => {
|
||||
// #6154: Consideration about resolving this issue -> Read comment on src/client/containers/VmTemplates/Create.js
|
||||
|
||||
// #6154: Delete restricted attributes (if there are not on the original template)
|
||||
const jsonFinal = adminGroup
|
||||
? xmlToJson(t.template)
|
||||
: deleteRestrictedAttributes(
|
||||
xmlToJson(t?.template),
|
||||
dataTemplate?.TEMPLATE,
|
||||
oneConfig?.VM_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
// #6154: Delete unique identifier to compare on submit items that exists at the beginning of the update
|
||||
if (!adminGroup) {
|
||||
deleteTempInfo(jsonFinal)
|
||||
}
|
||||
|
||||
// Transform json to xml
|
||||
const xmlFinal = jsonToXml(jsonFinal)
|
||||
|
||||
// Modify template
|
||||
t.template = xmlFinal
|
||||
|
||||
return instantiate(t).unwrap()
|
||||
})
|
||||
)
|
||||
|
||||
history.push(PATH.INSTANCE.VMS.LIST)
|
||||
|
||||
@ -67,12 +116,16 @@ function InstantiateVmTemplate() {
|
||||
return <Redirect to={PATH.TEMPLATE.VMS.LIST} />
|
||||
}
|
||||
|
||||
return !data ? (
|
||||
return !dataTemplateExtended || !dataTemplate ? (
|
||||
<SkeletonStepsForm />
|
||||
) : (
|
||||
<InstantiateForm
|
||||
initialValues={data}
|
||||
stepProps={data}
|
||||
initialValues={dataTemplateExtended}
|
||||
stepProps={{
|
||||
dataTemplateExtended,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
>
|
||||
|
@ -26,3 +26,4 @@ export * from 'client/utils/string'
|
||||
export * from 'client/utils/number'
|
||||
export * from 'client/utils/translation'
|
||||
export * from 'client/utils/units'
|
||||
export * from 'client/utils/restrictedAttributes'
|
||||
|
330
src/fireedge/src/client/utils/restrictedAttributes.js
Normal file
330
src/fireedge/src/client/utils/restrictedAttributes.js
Normal file
@ -0,0 +1,330 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* File to define functions that do something about restricted attributes
|
||||
*/
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// Sections that are list of item
|
||||
const listSections = [
|
||||
'DISK',
|
||||
'NIC',
|
||||
'INPUT',
|
||||
'PCI',
|
||||
'SCHED_ACTION',
|
||||
'NIC_ALIAS',
|
||||
'NIC_DEFAULT',
|
||||
'USER_INPUTS',
|
||||
]
|
||||
|
||||
/**
|
||||
* Add temporal info (temp ids and original data) to each item of some sections.
|
||||
*
|
||||
* @param {object} dataTemplate - Data of the template with extended=false
|
||||
* @param {object} dataTemplateExtended - Data of the template with extended=true (basically add info about the images)
|
||||
*/
|
||||
export const addTempInfo = (dataTemplate, dataTemplateExtended) => {
|
||||
addIds(dataTemplate?.TEMPLATE?.DISK, dataTemplateExtended?.TEMPLATE?.DISK)
|
||||
// Disk is the only section that needs to add original data because the core with extended=true returns info about the images that are not on the template
|
||||
addOriginalData(
|
||||
dataTemplate?.TEMPLATE?.DISK,
|
||||
dataTemplateExtended?.TEMPLATE?.DISK
|
||||
)
|
||||
addIds(dataTemplate?.TEMPLATE?.NIC, dataTemplateExtended?.TEMPLATE?.NIC)
|
||||
addIds(dataTemplate?.TEMPLATE?.INPUT, dataTemplateExtended?.TEMPLATE?.INPUT)
|
||||
addIds(dataTemplate?.TEMPLATE?.PCI, dataTemplateExtended?.TEMPLATE?.PCI)
|
||||
addIds(
|
||||
dataTemplate?.TEMPLATE?.SCHED_ACTION,
|
||||
dataTemplateExtended?.TEMPLATE?.SCHED_ACTION
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the temporal info of each section of a template.
|
||||
*
|
||||
* @param {object} dataTemplate - Template with data
|
||||
*/
|
||||
export const deleteTempInfo = (dataTemplate) => {
|
||||
deleteTempId(dataTemplate?.DISK)
|
||||
// Disk is the only section that needs to delete original data because the core with extended=true returns info about the images that are not on the template
|
||||
deleteOriginalData(dataTemplate?.DISK)
|
||||
deleteTempId(dataTemplate?.NIC)
|
||||
deleteTempId(dataTemplate?.INPUT)
|
||||
deleteTempId(dataTemplate?.PCI)
|
||||
deleteTempId(dataTemplate?.SCHED_ACTION)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a temporal identifier to each item of a section.
|
||||
*
|
||||
* @param {object} section - Info about the template section with extended=false
|
||||
* @param {object} sectionExtended - Info about the template section with extended=true (basically add info about the images)
|
||||
*/
|
||||
const addIds = (section, sectionExtended) => {
|
||||
// Check if the section exists and it's an array or an attribute
|
||||
if (section && Array.isArray(section)) {
|
||||
// Iterate over each item
|
||||
section.forEach((item, index) => {
|
||||
// Create id
|
||||
const tempId = uuidv4()
|
||||
|
||||
// Add id to the item of the section and the item of the extended section
|
||||
item.TEMP_ID = tempId
|
||||
if (sectionExtended) {
|
||||
sectionExtended[index].TEMP_ID = tempId
|
||||
}
|
||||
})
|
||||
} else if (section) {
|
||||
// Create id
|
||||
const tempId = uuidv4()
|
||||
|
||||
// Add id to the item of the section and the item of the extended section
|
||||
if (sectionExtended) {
|
||||
sectionExtended.TEMP_ID = tempId
|
||||
}
|
||||
section.TEMP_ID = tempId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to a section extended the data of the original section.
|
||||
*
|
||||
* @param {object} section - The section like is set on the template
|
||||
* @param {object} sectionExtended - The section after use extended=true on the template
|
||||
*/
|
||||
const addOriginalData = (section, sectionExtended) => {
|
||||
// Check if the section exists and it's an array or an attribute
|
||||
if (section && Array.isArray(section)) {
|
||||
// Iterate over each item
|
||||
section.forEach((item, index) => {
|
||||
// Add id to the item of the section and the item of the extended section
|
||||
if (sectionExtended) {
|
||||
sectionExtended[index].ORIGINAL = item
|
||||
}
|
||||
})
|
||||
} else if (section) {
|
||||
// Add id to the item of the section and the item of the extended section
|
||||
if (sectionExtended) {
|
||||
sectionExtended.ORIGINAL = section
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the temporal ide of each item of the section.
|
||||
*
|
||||
* @param {object} section - Section of a template
|
||||
*/
|
||||
const deleteTempId = (section) => {
|
||||
// Check if the section exists and it's an array or an attribute
|
||||
if (section && Array.isArray(section)) {
|
||||
// Iterate and delete every temporal id
|
||||
section.forEach((item) => {
|
||||
delete item.TEMP_ID
|
||||
})
|
||||
} else if (section) {
|
||||
// Delete the temporal id if it's only one object
|
||||
delete section.TEMP_ID
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete original data in a section.
|
||||
*
|
||||
* @param {object} section - The section where to delete the original data
|
||||
*/
|
||||
const deleteOriginalData = (section) => {
|
||||
// Check if the section exists and it's an array or an attribute
|
||||
if (section && Array.isArray(section)) {
|
||||
// Iterate and delete every temporal id
|
||||
section.forEach((item) => {
|
||||
delete item.ORIGINAL
|
||||
})
|
||||
} else if (section) {
|
||||
// Delete the temporal id if it's only one object
|
||||
delete section.ORIGINAL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the restricted attributes of a template if there are not on the original template before the user makes any modification.
|
||||
*
|
||||
* @param {object} data - Data modified by the user
|
||||
* @param {object} originalData - Data before the user modifies it
|
||||
* @param {Array} restrictedAttributes - List of restricted attributes of OpenNebula
|
||||
* @returns {object}- Data without the restricted attributes that has to be removed
|
||||
*/
|
||||
export const deleteRestrictedAttributes = (
|
||||
data,
|
||||
originalData,
|
||||
restrictedAttributes
|
||||
) => {
|
||||
// If there is no restricted attributes, do nothing
|
||||
if (!restrictedAttributes) return data
|
||||
|
||||
// Create a map with the restricted attributes (using as key the parent, for example, DISK/SIZE will create DISK=["SIZE"])
|
||||
const mapRestrictedAttributes =
|
||||
mapRestrictedAttributesFunction(restrictedAttributes)
|
||||
|
||||
// Iterates over each key of the map of restricted attributes
|
||||
Object.keys(mapRestrictedAttributes).forEach((key) => {
|
||||
// Get all the restricted attributes for a key
|
||||
const value = mapRestrictedAttributes[key]
|
||||
|
||||
// 1. If the attribute is a parent template attribute (like "NAME") delete it if it's not on the original template
|
||||
// 2. If the attribute is one of the sections that could has lists (like DISK), iterate over each item and delete restricted attributes if there are not on the original template
|
||||
// 3. If the attribute is a template attribute that is not a list (like "TOPOLOGY") delete it if it's not on the original template
|
||||
if (key === 'PARENT') {
|
||||
value
|
||||
.filter((attribute) => !originalData[attribute])
|
||||
.forEach((attribute) => delete data[attribute])
|
||||
} else if (listSections.find((itemSection) => itemSection === key)) {
|
||||
deleteRestrictedAttributesOnArraySection(
|
||||
data,
|
||||
originalData,
|
||||
key,
|
||||
restrictedAttributes
|
||||
)
|
||||
} else {
|
||||
value
|
||||
.filter(
|
||||
(attribute) =>
|
||||
(originalData[key] && data[key] && !originalData[key][attribute]) ||
|
||||
(data[key] && !originalData[key])
|
||||
)
|
||||
.forEach((attribute) => delete data[key][attribute])
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a map of restricted attributes, where the key it's the left part if the attribute is splitted with "/" character and the child the righ part. For example, "DISK/SIZE" creates a key with "DISK" that has an array with one element "SIZE".
|
||||
*
|
||||
* @param {Array} restrictedAttributesArray - List of attributes
|
||||
* @returns {object} - The map with the restricted attributes
|
||||
*/
|
||||
const mapRestrictedAttributesFunction = (restrictedAttributesArray) => {
|
||||
// Creates the PARENT key
|
||||
const restrictedAttributes = { PARENT: [] }
|
||||
|
||||
// Iterate over each attribute
|
||||
restrictedAttributesArray.forEach((attribute) => {
|
||||
// Get parent and child
|
||||
const [parent, child] = attribute.split('/')
|
||||
|
||||
// Create the array if the key does not exist
|
||||
if (child && !restrictedAttributes[parent]) {
|
||||
restrictedAttributes[parent] = []
|
||||
}
|
||||
|
||||
// Add to the array
|
||||
if (child) {
|
||||
restrictedAttributes[parent].push(child)
|
||||
} else {
|
||||
restrictedAttributes.PARENT.push(parent)
|
||||
}
|
||||
})
|
||||
|
||||
return restrictedAttributes
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete restricted attributes for disks if there are not on the original data (#6154).
|
||||
*
|
||||
* @param {object} data - Data of the disks
|
||||
* @param {object} originalData - Data of the disks before the user makes any changes
|
||||
* @param {boolean} section - The section of the template
|
||||
* @param {Array} restrictedAttributes - List of restricted attributes for DISK form
|
||||
* @returns {object} - Data without disk restricted attributes that are not on the original data
|
||||
*/
|
||||
export const deleteRestrictedAttributesOnArraySection = (
|
||||
data,
|
||||
originalData,
|
||||
section,
|
||||
restrictedAttributes = []
|
||||
) => {
|
||||
// If there is no data of the section, return and exit the function
|
||||
if (!data[section]) return data
|
||||
|
||||
// Check if the section it is an element on an array and create an array of one or more elements
|
||||
const dataSection = Array.isArray(data[section])
|
||||
? data[section]
|
||||
: [data[section]]
|
||||
|
||||
// Check if the original section it is an element on an array and create an array of one or more elements
|
||||
const originalDataSection =
|
||||
originalData && originalData[section]
|
||||
? Array.isArray(originalData[section])
|
||||
? originalData[section]
|
||||
: [originalData[section]]
|
||||
: undefined
|
||||
|
||||
// Iterate over each item of the section
|
||||
data[section] = dataSection.map((item) => {
|
||||
// Find if the item it's on the original data
|
||||
const originalItemSection = originalDataSection
|
||||
? originalDataSection.find(
|
||||
(originalItem) => originalItem.TEMP_ID === item.TEMP_ID
|
||||
)
|
||||
: undefined
|
||||
|
||||
// Iterate over each key of the item to check if it's a restricted attribute and delete it if it's not on the original data or it's a new item (an user cannot add a restricted attribute because it's read only field, so if there is an restricted attribute, or this attribute it's on the original data or we have to delete it because it wasn't set by the user)
|
||||
Object.keys(item).forEach((key) => {
|
||||
// Special case on ORIGINAL_SIZE attribute. It's no needed but in older templates could be, so if exists, copy with the original value (it's no used)
|
||||
if (key === 'ORIGINAL_SIZE') {
|
||||
item[key] = originalItemSection[key]
|
||||
}
|
||||
|
||||
if (restrictedAttributes.find((attr) => attr === section + '/' + key)) {
|
||||
if (!originalItemSection) delete item[key]
|
||||
else if (originalItemSection && !originalItemSection[key])
|
||||
delete item[key]
|
||||
}
|
||||
})
|
||||
|
||||
return item
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Find if a item has restricted attributes.
|
||||
*
|
||||
* @param {object} item - The item where to find the attribute
|
||||
* @param {string} section - Section of the item
|
||||
* @param {Array} restrictedAttributes - List of restricted attributes
|
||||
* @returns {boolean} - True if any restricted attribute is found on the item
|
||||
*/
|
||||
export const hasRestrictedAttributes = (
|
||||
item,
|
||||
section,
|
||||
restrictedAttributes = []
|
||||
) => {
|
||||
// Create map with restricted attributes
|
||||
const mapRestrictedAttributes =
|
||||
mapRestrictedAttributesFunction(restrictedAttributes)
|
||||
|
||||
// Find if there is a restricted attribute in the item
|
||||
const restricteAttribute = mapRestrictedAttributes[section]?.find(
|
||||
(attribute) => item && item[attribute]
|
||||
)
|
||||
|
||||
return !!restricteAttribute
|
||||
}
|
@ -526,3 +526,46 @@ export const createForm =
|
||||
...ensuredExtraParams,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable fields that are restricted attributes in oned.conf.
|
||||
*
|
||||
* @param {Array} fields - Fields of the form
|
||||
* @param {string} nameParentAttribute - Parent name of the form
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - It he user is an admin
|
||||
* @returns {Array} - New array of fields
|
||||
*/
|
||||
export const disableFields = (
|
||||
fields = {},
|
||||
nameParentAttribute,
|
||||
oneConfig = {},
|
||||
adminGroup = true
|
||||
) => {
|
||||
// Disable fields only if it is a non admin user
|
||||
if (adminGroup) return fields
|
||||
|
||||
// Get restricted attributes
|
||||
const restrictedAttributes = oneConfig?.VM_RESTRICTED_ATTR?.filter((item) =>
|
||||
nameParentAttribute !== ''
|
||||
? item.startsWith(nameParentAttribute)
|
||||
: !item.includes('/')
|
||||
).map((item) => item.split('/')[1] ?? item)
|
||||
|
||||
// Iterate over each field and add disabled attribute if it's a restricted attribute (almost all forms has attributes with name like "ATTR" but some of them like "PARENT.ATTR")
|
||||
return fields.map((field) => {
|
||||
if (
|
||||
restrictedAttributes.some(
|
||||
(item) =>
|
||||
item === field.name || nameParentAttribute + '.' + item === field.name
|
||||
)
|
||||
) {
|
||||
field.fieldProps = {
|
||||
...field.fieldProps,
|
||||
disabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
return field
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user