mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
F OpenNebula#6332: Restricted attributes on images and vnets (#2832)
Co-authored-by: Tino Vázquez <cvazquez@opennebula.io>
This commit is contained in:
parent
23decf1d53
commit
c9ed2fe5df
@ -29,123 +29,155 @@ import { AddRangeForm } from 'client/components/Forms/VNetwork'
|
||||
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { T, VN_ACTIONS } from 'client/constants'
|
||||
import { T, VN_ACTIONS, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
|
||||
const AddAddressRangeAction = memo(({ vnetId, onSubmit }) => {
|
||||
const [addAR] = useAddRangeToVNetMutation()
|
||||
import { hasRestrictedAttributes } from 'client/utils'
|
||||
|
||||
const handleAdd = async (formData) => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(formData)
|
||||
const AddAddressRangeAction = memo(
|
||||
({ vnetId, onSubmit, oneConfig, adminGroup }) => {
|
||||
const [addAR] = useAddRangeToVNetMutation()
|
||||
|
||||
const handleAdd = async (formData) => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(formData)
|
||||
}
|
||||
|
||||
const template = jsonToXml({ AR: formData })
|
||||
await addAR({ id: vnetId, template }).unwrap()
|
||||
}
|
||||
|
||||
const template = jsonToXml({ AR: formData })
|
||||
await addAR({ id: vnetId, template }).unwrap()
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-ar',
|
||||
startIcon: <AddIcon />,
|
||||
label: T.AddressRange,
|
||||
variant: 'outlined',
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: T.AddressRange,
|
||||
dataCy: 'modal-add-ar',
|
||||
},
|
||||
form: AddRangeForm,
|
||||
onSubmit: handleAdd,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const UpdateAddressRangeAction = memo(({ vnetId, ar, onSubmit }) => {
|
||||
const [updateAR] = useUpdateVNetRangeMutation()
|
||||
const { AR_ID } = ar
|
||||
|
||||
const handleUpdate = async (formData) => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(formData)
|
||||
const formConfig = {
|
||||
stepProps: {
|
||||
vnetId,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
restrictedAttributesType: RESTRICTED_ATTRIBUTES_TYPE.VNET,
|
||||
nameParentAttribute: 'AR',
|
||||
},
|
||||
}
|
||||
|
||||
const template = jsonToXml({ AR: formData })
|
||||
await updateAR({ id: vnetId, template })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VN_ACTIONS.UPDATE_AR}-${AR_ID}`,
|
||||
icon: <EditIcon />,
|
||||
tooltip: T.Edit,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: `${Tr(T.AddressRange)}: #${AR_ID}`,
|
||||
dataCy: 'modal-update-ar',
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-ar',
|
||||
startIcon: <AddIcon />,
|
||||
label: T.AddressRange,
|
||||
variant: 'outlined',
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: T.AddressRange,
|
||||
dataCy: 'modal-add-ar',
|
||||
},
|
||||
form: () => AddRangeForm(formConfig),
|
||||
onSubmit: handleAdd,
|
||||
},
|
||||
form: () =>
|
||||
AddRangeForm({
|
||||
initialValues: ar,
|
||||
stepProps: { isUpdate: !onSubmit && AR_ID !== undefined },
|
||||
}),
|
||||
onSubmit: handleUpdate,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const DeleteAddressRangeAction = memo(({ vnetId, ar, onSubmit }) => {
|
||||
const [removeAR] = useRemoveRangeFromVNetMutation()
|
||||
const { AR_ID } = ar
|
||||
const UpdateAddressRangeAction = memo(
|
||||
({ vnetId, ar, onSubmit, oneConfig, adminGroup }) => {
|
||||
const [updateAR] = useUpdateVNetRangeMutation()
|
||||
const { AR_ID } = ar
|
||||
|
||||
const handleRemove = async () => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(AR_ID)
|
||||
const handleUpdate = async (formData) => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(formData)
|
||||
}
|
||||
|
||||
const template = jsonToXml({ AR: formData })
|
||||
await updateAR({ id: vnetId, template })
|
||||
}
|
||||
|
||||
await removeAR({ id: vnetId, address: AR_ID })
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VN_ACTIONS.DELETE_AR}-${AR_ID}`,
|
||||
icon: <TrashIcon />,
|
||||
tooltip: T.Delete,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<>
|
||||
<Translate word={T.DeleteAddressRange} />
|
||||
{`: #${AR_ID}`}
|
||||
</>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VN_ACTIONS.UPDATE_AR}-${AR_ID}`,
|
||||
icon: <EditIcon />,
|
||||
tooltip: T.Edit,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: `${Tr(T.AddressRange)}: #${AR_ID}`,
|
||||
dataCy: 'modal-update-ar',
|
||||
},
|
||||
form: () =>
|
||||
AddRangeForm({
|
||||
initialValues: ar,
|
||||
stepProps: {
|
||||
isUpdate: !onSubmit && AR_ID !== undefined,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
restrictedAttributesType: RESTRICTED_ATTRIBUTES_TYPE.VNET,
|
||||
nameParentAttribute: 'AR',
|
||||
},
|
||||
}),
|
||||
onSubmit: handleUpdate,
|
||||
},
|
||||
onSubmit: handleRemove,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const DeleteAddressRangeAction = memo(
|
||||
({ vnetId, ar, onSubmit, oneConfig, adminGroup }) => {
|
||||
const [removeAR] = useRemoveRangeFromVNetMutation()
|
||||
const { AR_ID } = ar
|
||||
|
||||
const handleRemove = async () => {
|
||||
if (onSubmit && typeof onSubmit === 'function') {
|
||||
return await onSubmit(AR_ID)
|
||||
}
|
||||
|
||||
await removeAR({ id: vnetId, address: AR_ID })
|
||||
}
|
||||
|
||||
// Disable action if the disk has a restricted attribute on the template
|
||||
const disabledAction =
|
||||
!adminGroup &&
|
||||
hasRestrictedAttributes(ar, 'AR', oneConfig?.VNET_RESTRICTED_ATTR)
|
||||
|
||||
return (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `${VN_ACTIONS.DELETE_AR}-${AR_ID}`,
|
||||
icon: <TrashIcon />,
|
||||
tooltip: !disabledAction ? Tr(T.Detach) : Tr(T.DetachRestricted),
|
||||
disabled: disabledAction,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<>
|
||||
<Translate word={T.DeleteAddressRange} />
|
||||
{`: #${AR_ID}`}
|
||||
</>
|
||||
),
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit: handleRemove,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const ActionPropTypes = {
|
||||
vnetId: PropTypes.string,
|
||||
ar: PropTypes.object,
|
||||
onSubmit: PropTypes.func,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
AddAddressRangeAction.propTypes = ActionPropTypes
|
||||
|
@ -23,21 +23,28 @@ import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'advanced'
|
||||
|
||||
const Content = () => (
|
||||
<FormWithSchema id={STEP_ID} fields={FIELDS} cy={`${STEP_ID}`} />
|
||||
const Content = (oneConfig, adminGroup) => (
|
||||
<FormWithSchema
|
||||
id={STEP_ID}
|
||||
fields={FIELDS((oneConfig, adminGroup))}
|
||||
cy={`${STEP_ID}`}
|
||||
/>
|
||||
)
|
||||
|
||||
/**
|
||||
* Advanced options create image.
|
||||
*
|
||||
* @param {object} props - Step properties
|
||||
* @param {object} props.oneConfig - Open Nebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {object} Advanced options configuration step
|
||||
*/
|
||||
const AdvancedOptions = () => ({
|
||||
const AdvancedOptions = ({ oneConfig, adminGroup }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA,
|
||||
resolver: SCHEMA(oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
content: () => Content(oneConfig, adminGroup),
|
||||
})
|
||||
|
||||
export default AdvancedOptions
|
||||
|
@ -14,8 +14,13 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string, object, ObjectSchema } from 'yup'
|
||||
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import {
|
||||
Field,
|
||||
arrayToOptions,
|
||||
getValidationFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
import {
|
||||
IMAGE_LOCATION_TYPES,
|
||||
IMAGE_LOCATION_FIELD,
|
||||
@ -156,18 +161,23 @@ export const FS = {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [
|
||||
DEV_PREFIX,
|
||||
DEVICE,
|
||||
CUSTOM_DEV_PREFIX,
|
||||
FORMAT_FIELD,
|
||||
FS,
|
||||
CUSTOM_FORMAT,
|
||||
]
|
||||
export const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[DEV_PREFIX, DEVICE, CUSTOM_DEV_PREFIX, FORMAT_FIELD, FS, CUSTOM_FORMAT],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.IMAGE
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
||||
export const SCHEMA = (oneConfig, adminGroup) =>
|
||||
object(getValidationFromFields(FIELDS(oneConfig, adminGroup)))
|
||||
|
@ -24,21 +24,28 @@ import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
const Content = () => (
|
||||
<FormWithSchema id={STEP_ID} fields={FIELDS} cy={`${STEP_ID}`} />
|
||||
const Content = (oneConfig, adminGroup) => (
|
||||
<FormWithSchema
|
||||
id={STEP_ID}
|
||||
fields={FIELDS(oneConfig, adminGroup)}
|
||||
cy={`${STEP_ID}`}
|
||||
/>
|
||||
)
|
||||
|
||||
/**
|
||||
* General configuration about VM Template.
|
||||
*
|
||||
* @param {object} props - Step properties
|
||||
* @param {object} props.oneConfig - Open Nebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = () => ({
|
||||
const General = ({ oneConfig, adminGroup }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: SCHEMA,
|
||||
resolver: SCHEMA(oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
content: () => Content(oneConfig, adminGroup),
|
||||
})
|
||||
|
||||
export default General
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
arrayToOptions,
|
||||
getValidationFromFields,
|
||||
upperCaseFirst,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
@ -26,6 +27,7 @@ import {
|
||||
IMAGE_TYPES_STR,
|
||||
IMAGE_TYPES_FOR_IMAGES,
|
||||
UNITS,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
} from 'client/constants'
|
||||
|
||||
export const IMAGE_LOCATION_TYPES = {
|
||||
@ -193,22 +195,33 @@ export const SIZEUNIT = {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [
|
||||
NAME,
|
||||
DESCRIPTION,
|
||||
TYPE,
|
||||
PERSISTENT,
|
||||
IMAGE_LOCATION_FIELD,
|
||||
PATH_FIELD,
|
||||
UPLOAD_FIELD,
|
||||
SIZE,
|
||||
SIZEUNIT,
|
||||
]
|
||||
export const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[
|
||||
NAME,
|
||||
DESCRIPTION,
|
||||
TYPE,
|
||||
PERSISTENT,
|
||||
IMAGE_LOCATION_FIELD,
|
||||
PATH_FIELD,
|
||||
UPLOAD_FIELD,
|
||||
SIZE,
|
||||
SIZEUNIT,
|
||||
],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.IMAGE
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
||||
export const SCHEMA = (oneConfig, adminGroup) =>
|
||||
object(getValidationFromFields(FIELDS(oneConfig, adminGroup)))
|
||||
|
@ -32,9 +32,11 @@ export const CUSTOM_ATTRS_ID = 'custom-attributes'
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {boolean} [props.isUpdate] - Is `true` the form will be filter immutable attributes
|
||||
* @param {object} props.oneConfig - Open Nebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ReactElement} Form content component
|
||||
*/
|
||||
const Content = ({ isUpdate }) => {
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) => {
|
||||
const { setValue } = useFormContext()
|
||||
const customAttrs = useWatch({ name: CUSTOM_ATTRS_ID }) || {}
|
||||
|
||||
@ -47,7 +49,13 @@ const Content = ({ isUpdate }) => {
|
||||
|
||||
return (
|
||||
<Box display="grid" gap="1em">
|
||||
<FormWithSchema fields={isUpdate ? MUTABLE_FIELDS : FIELDS} />
|
||||
<FormWithSchema
|
||||
fields={
|
||||
isUpdate
|
||||
? MUTABLE_FIELDS(oneConfig, adminGroup)
|
||||
: FIELDS(oneConfig, adminGroup)
|
||||
}
|
||||
/>
|
||||
<AttributePanel
|
||||
collapse
|
||||
askToDelete={false}
|
||||
@ -63,6 +71,10 @@ const Content = ({ isUpdate }) => {
|
||||
)
|
||||
}
|
||||
|
||||
Content.propTypes = { isUpdate: PropTypes.bool }
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default Content
|
||||
|
@ -23,8 +23,9 @@ import {
|
||||
REG_V4,
|
||||
REG_V6,
|
||||
REG_MAC,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { T, INPUT_TYPES, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
|
||||
const AR_TYPES = {
|
||||
IP4: 'IP4',
|
||||
@ -183,25 +184,50 @@ const ULA_PREFIX_FIELD = {
|
||||
}
|
||||
|
||||
/** @type {Field[]} Fields */
|
||||
const FIELDS = [
|
||||
TYPE_FIELD,
|
||||
IP_FIELD,
|
||||
MAC_FIELD,
|
||||
IP6_FIELD,
|
||||
SIZE_FIELD,
|
||||
GLOBAL_PREFIX_FIELD,
|
||||
PREFIX_LENGTH_FIELD,
|
||||
ULA_PREFIX_FIELD,
|
||||
]
|
||||
const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[
|
||||
TYPE_FIELD,
|
||||
IP_FIELD,
|
||||
MAC_FIELD,
|
||||
IP6_FIELD,
|
||||
SIZE_FIELD,
|
||||
GLOBAL_PREFIX_FIELD,
|
||||
PREFIX_LENGTH_FIELD,
|
||||
ULA_PREFIX_FIELD,
|
||||
],
|
||||
'AR',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
)
|
||||
|
||||
const MUTABLE_FIELDS = [SIZE_FIELD]
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Array} - Mutable fields
|
||||
*/
|
||||
const MUTABLE_FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[SIZE_FIELD],
|
||||
'AR',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {object} stepProps - Step props
|
||||
* @param {boolean} stepProps.isUpdate - If true the form is to update the AR
|
||||
* @param {object} stepProps.oneConfig - Open Nebula configuration
|
||||
* @param {boolean} stepProps.adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {BaseSchema} Schema
|
||||
*/
|
||||
const SCHEMA = ({ isUpdate }) =>
|
||||
getObjectSchemaFromFields([...(isUpdate ? MUTABLE_FIELDS : FIELDS)])
|
||||
const SCHEMA = ({ isUpdate, oneConfig, adminGroup }) =>
|
||||
getObjectSchemaFromFields([
|
||||
...(isUpdate
|
||||
? MUTABLE_FIELDS(oneConfig, adminGroup)
|
||||
: FIELDS(oneConfig, adminGroup)),
|
||||
])
|
||||
|
||||
export { FIELDS, MUTABLE_FIELDS, SCHEMA }
|
||||
|
@ -37,7 +37,7 @@ export const TAB_ID = 'AR'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('AR')
|
||||
|
||||
const AddressesContent = () => {
|
||||
const AddressesContent = ({ oneConfig, adminGroup }) => {
|
||||
const {
|
||||
fields: addresses,
|
||||
remove,
|
||||
@ -63,7 +63,11 @@ const AddressesContent = () => {
|
||||
return (
|
||||
<>
|
||||
<Stack flexDirection="row" gap="1em">
|
||||
<AddAddressRangeAction onSubmit={handleCreateAction} />
|
||||
<AddAddressRangeAction
|
||||
onSubmit={handleCreateAction}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
@ -87,6 +91,8 @@ const AddressesContent = () => {
|
||||
vm={{}}
|
||||
ar={fakeValues}
|
||||
onSubmit={(updatedAr) => handleUpdate(updatedAr, index)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
<DeleteAddressRangeAction
|
||||
ar={fakeValues}
|
||||
@ -102,16 +108,25 @@ const AddressesContent = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const Content = ({ isUpdate }) =>
|
||||
AddressesContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) =>
|
||||
isUpdate ? (
|
||||
<Typography variant="subtitle2">
|
||||
<Translate word={T.DisabledAddressRangeInForm} />
|
||||
</Typography>
|
||||
) : (
|
||||
<AddressesContent />
|
||||
<AddressesContent oneConfig={oneConfig} adminGroup={adminGroup} />
|
||||
)
|
||||
|
||||
Content.propTypes = { isUpdate: PropTypes.bool }
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import ContextIcon from 'iconoir-react/dist/Folder'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
TabType,
|
||||
STEP_ID as EXTRA_ID,
|
||||
@ -25,20 +25,29 @@ import CustomAttributes from 'client/components/Forms/VNetwork/CreateForm/Steps/
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const ContextContent = () => (
|
||||
const ContextContent = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
<FormWithSchema id={EXTRA_ID} cy="context" fields={FIELDS} />
|
||||
<FormWithSchema
|
||||
id={EXTRA_ID}
|
||||
cy="context"
|
||||
fields={FIELDS(oneConfig, adminGroup)}
|
||||
/>
|
||||
<CustomAttributes />
|
||||
</>
|
||||
)
|
||||
|
||||
ContextContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'context',
|
||||
name: T.Context,
|
||||
icon: ContextIcon,
|
||||
Content: ContextContent,
|
||||
getError: (error) => FIELDS.some(({ name }) => error?.[name]),
|
||||
getError: (error) => FIELDS().some(({ name }) => error?.[name]),
|
||||
}
|
||||
|
||||
export default TAB
|
||||
|
@ -15,8 +15,19 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { string } from 'yup'
|
||||
|
||||
import { Field, arrayToOptions, getObjectSchemaFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES, VNET_METHODS, VNET_METHODS6 } from 'client/constants'
|
||||
import {
|
||||
Field,
|
||||
arrayToOptions,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
INPUT_TYPES,
|
||||
VNET_METHODS,
|
||||
VNET_METHODS6,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
} from 'client/constants'
|
||||
|
||||
/** @type {Field} Network address field */
|
||||
const NETWORK_ADDRESS_FIELD = {
|
||||
@ -96,15 +107,33 @@ const IP6_METHOD_FIELD = {
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
export const FIELDS = [
|
||||
NETWORK_ADDRESS_FIELD,
|
||||
NETWORK_MASK_FIELD,
|
||||
GATEWAY_FIELD,
|
||||
GATEWAY6_FIELD,
|
||||
DNS_FIELD,
|
||||
GUEST_MTU_FIELD,
|
||||
METHOD_FIELD,
|
||||
IP6_METHOD_FIELD,
|
||||
]
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Array} Fields
|
||||
*/
|
||||
export const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[
|
||||
NETWORK_ADDRESS_FIELD,
|
||||
NETWORK_MASK_FIELD,
|
||||
GATEWAY_FIELD,
|
||||
GATEWAY6_FIELD,
|
||||
DNS_FIELD,
|
||||
GUEST_MTU_FIELD,
|
||||
METHOD_FIELD,
|
||||
IP6_METHOD_FIELD,
|
||||
],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
)
|
||||
|
||||
export const SCHEMA = getObjectSchemaFromFields(FIELDS)
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {object} Schema
|
||||
*/
|
||||
export const SCHEMA = (oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(FIELDS(oneConfig, adminGroup))
|
||||
|
@ -45,7 +45,7 @@ export const STEP_ID = 'extra'
|
||||
/** @type {TabType[]} */
|
||||
export const TABS = [Addresses, Security, QoS, Context]
|
||||
|
||||
const Content = ({ isUpdate }) => {
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) => {
|
||||
const {
|
||||
watch,
|
||||
formState: { errors },
|
||||
@ -61,7 +61,14 @@ const Content = ({ isUpdate }) => {
|
||||
...section,
|
||||
name,
|
||||
label: <Translate word={name} />,
|
||||
renderContent: () => <TabContent isUpdate={isUpdate} driver={driver} />,
|
||||
renderContent: () => (
|
||||
<TabContent
|
||||
isUpdate={isUpdate}
|
||||
driver={driver}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
),
|
||||
error: getError?.(errors[STEP_ID]),
|
||||
})),
|
||||
[totalErrors, driver]
|
||||
@ -73,18 +80,19 @@ const Content = ({ isUpdate }) => {
|
||||
/**
|
||||
* Optional configuration about Virtual network.
|
||||
*
|
||||
* @param {VirtualNetwork} vnet - Virtual network
|
||||
* @param {VirtualNetwork} data - Virtual network
|
||||
* @returns {object} Optional configuration step
|
||||
*/
|
||||
const ExtraConfiguration = (vnet) => {
|
||||
const isUpdate = vnet?.NAME !== undefined
|
||||
const ExtraConfiguration = ({ data, oneConfig, adminGroup }) => {
|
||||
const isUpdate = data?.NAME !== undefined
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA(isUpdate),
|
||||
resolver: SCHEMA(isUpdate, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (formProps) => Content({ ...formProps, isUpdate }),
|
||||
content: (formProps) =>
|
||||
Content({ ...formProps, isUpdate, oneConfig, adminGroup }),
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +100,8 @@ Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ExtraConfiguration
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import QoSIcon from 'iconoir-react/dist/DataTransferBoth'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
@ -27,9 +28,9 @@ import {
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const QoSContent = () => (
|
||||
const QoSContent = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
{SECTIONS.map(({ id, ...section }) => (
|
||||
{SECTIONS(oneConfig, adminGroup).map(({ id, ...section }) => (
|
||||
<FormWithSchema
|
||||
key={id}
|
||||
id={EXTRA_ID}
|
||||
@ -40,13 +41,18 @@ const QoSContent = () => (
|
||||
</>
|
||||
)
|
||||
|
||||
QoSContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'qos',
|
||||
name: T.QoS,
|
||||
icon: QoSIcon,
|
||||
Content: QoSContent,
|
||||
getError: (error) => FIELDS.some(({ name }) => error?.[name]),
|
||||
getError: (error) => FIELDS().some(({ name }) => error?.[name]),
|
||||
}
|
||||
|
||||
export default TAB
|
||||
|
@ -15,8 +15,13 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ObjectSchema, string } from 'yup'
|
||||
|
||||
import { Field, Section, getObjectSchemaFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import {
|
||||
Field,
|
||||
Section,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
|
||||
const commonFieldProps = {
|
||||
type: INPUT_TYPES.TEXT,
|
||||
@ -73,31 +78,39 @@ const OUTBOUND_PEAK_KB_FIELD = {
|
||||
}
|
||||
|
||||
/** @type {Section[]} Sections */
|
||||
const SECTIONS = [
|
||||
const SECTIONS = (oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'qos-inbound',
|
||||
legend: T.InboundTraffic,
|
||||
fields: [
|
||||
INBOUND_AVG_BW_FIELD,
|
||||
INBOUND_PEAK_BW_FIELD,
|
||||
INBOUND_PEAK_KB_FIELD,
|
||||
],
|
||||
fields: disableFields(
|
||||
[INBOUND_AVG_BW_FIELD, INBOUND_PEAK_BW_FIELD, INBOUND_PEAK_KB_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'qos-outbound',
|
||||
legend: T.OutboundTraffic,
|
||||
fields: [
|
||||
OUTBOUND_AVG_BW_FIELD,
|
||||
OUTBOUND_PEAK_BW_FIELD,
|
||||
OUTBOUND_PEAK_KB_FIELD,
|
||||
],
|
||||
fields: disableFields(
|
||||
[OUTBOUND_AVG_BW_FIELD, OUTBOUND_PEAK_BW_FIELD, OUTBOUND_PEAK_KB_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
/** @type {Field[]} List of QoS fields */
|
||||
const FIELDS = SECTIONS.map(({ fields }) => fields).flat()
|
||||
const FIELDS = (oneConfig, adminGroup) =>
|
||||
SECTIONS(oneConfig, adminGroup)
|
||||
.map(({ fields }) => fields)
|
||||
.flat()
|
||||
|
||||
/** @type {ObjectSchema} QoS schema */
|
||||
const SCHEMA = getObjectSchemaFromFields(FIELDS)
|
||||
const SCHEMA = (oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(FIELDS(oneConfig, adminGroup))
|
||||
|
||||
export { SCHEMA, SECTIONS, FIELDS }
|
||||
|
@ -40,12 +40,14 @@ const AR_SCHEMA = object({
|
||||
|
||||
/**
|
||||
* @param {boolean} isUpdate - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ObjectSchema} Extra configuration schema
|
||||
*/
|
||||
export const SCHEMA = (isUpdate) => {
|
||||
export const SCHEMA = (isUpdate, oneConfig, adminGroup) => {
|
||||
const schema = object({ SECURITY_GROUPS: array().ensure() })
|
||||
.concat(CONTEXT_SCHEMA)
|
||||
.concat(QOS_SCHEMA)
|
||||
.concat(CONTEXT_SCHEMA(oneConfig, adminGroup))
|
||||
.concat(QOS_SCHEMA(oneConfig, adminGroup))
|
||||
|
||||
!isUpdate && schema.concat(AR_SCHEMA)
|
||||
|
||||
|
@ -25,9 +25,13 @@ import {
|
||||
} from 'client/components/Forms/VNetwork/CreateForm/Steps/ExtraConfiguration'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { isRestrictedAttributes } from 'client/utils'
|
||||
|
||||
export const TAB_ID = 'SECURITY_GROUPS'
|
||||
|
||||
const SecurityContent = () => {
|
||||
const SecurityContent = ({ oneConfig, adminGroup }) => {
|
||||
const TAB_PATH = `${EXTRA_ID}.${TAB_ID}`
|
||||
|
||||
const { setValue } = useFormContext()
|
||||
@ -43,6 +47,14 @@ const SecurityContent = () => {
|
||||
setValue(TAB_PATH, newValue)
|
||||
}
|
||||
|
||||
const readOnly =
|
||||
!adminGroup &&
|
||||
isRestrictedAttributes(
|
||||
'SECURITY_GROUPS',
|
||||
undefined,
|
||||
oneConfig?.VNET_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
return (
|
||||
<SecurityGroupsTable
|
||||
disableGlobalSort
|
||||
@ -50,10 +62,16 @@ const SecurityContent = () => {
|
||||
pageSize={5}
|
||||
initialState={{ selectedRowIds }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SecurityContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'security',
|
||||
|
@ -34,17 +34,21 @@ const DRIVER_PATH = `${STEP_ID}.VN_MAD`
|
||||
const IP_CONF_PATH = `${STEP_ID}.${IP_LINK_CONF_FIELD.name}`
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {boolean} [props.isUpdate] - Is `true` the form will be filter immutable attributes
|
||||
* @param {boolean} isUpdate - True if it is an update operation
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ReactElement} Form content component
|
||||
*/
|
||||
const Content = ({ isUpdate }) => {
|
||||
const Content = (isUpdate, oneConfig, adminGroup) => {
|
||||
const { setValue } = useFormContext()
|
||||
|
||||
const driver = useWatch({ name: DRIVER_PATH })
|
||||
const ipConf = useWatch({ name: IP_CONF_PATH }) || {}
|
||||
|
||||
const sections = useMemo(() => SECTIONS(driver, isUpdate), [driver])
|
||||
const sections = useMemo(
|
||||
() => SECTIONS(driver, isUpdate, oneConfig, adminGroup),
|
||||
[driver]
|
||||
)
|
||||
|
||||
const handleChangeAttribute = (path, newValue) => {
|
||||
const newConf = cloneObject(ipConf)
|
||||
@ -89,12 +93,12 @@ const Content = ({ isUpdate }) => {
|
||||
/**
|
||||
* General configuration about Virtual network.
|
||||
*
|
||||
* @param {VirtualNetwork} vnet - Virtual network
|
||||
* @param {VirtualNetwork} data - Virtual network
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = (vnet) => {
|
||||
const isUpdate = vnet?.NAME !== undefined
|
||||
const initialDriver = vnet?.VN_MAD
|
||||
const General = ({ data, oneConfig, adminGroup }) => {
|
||||
const isUpdate = data?.NAME !== undefined
|
||||
const initialDriver = data?.VN_MAD
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
@ -102,7 +106,7 @@ const General = (vnet) => {
|
||||
resolver: (formData) =>
|
||||
SCHEMA(formData?.[STEP_ID]?.VN_MAD ?? initialDriver, isUpdate),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content({ isUpdate }),
|
||||
content: () => Content(isUpdate, oneConfig, adminGroup),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,26 +22,38 @@ import {
|
||||
Section,
|
||||
getObjectSchemaFromFields,
|
||||
filterFieldsByHypervisor,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, VN_DRIVERS } from 'client/constants'
|
||||
import { T, VN_DRIVERS, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @param {VN_DRIVERS} driver - Virtual network driver
|
||||
* @param {boolean} [isUpdate] - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Section[]} Fields
|
||||
*/
|
||||
const SECTIONS = (driver, isUpdate) => [
|
||||
const SECTIONS = (driver, isUpdate, oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'information',
|
||||
legend: T.Information,
|
||||
fields: INFORMATION_FIELDS(isUpdate),
|
||||
fields: disableFields(
|
||||
INFORMATION_FIELDS(isUpdate),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'configuration',
|
||||
legend: T.Configuration,
|
||||
fields: filterFieldsByHypervisor(
|
||||
[DRIVER_FIELD, ...FIELDS_BY_DRIVER],
|
||||
driver
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor([DRIVER_FIELD, ...FIELDS_BY_DRIVER], driver),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
]
|
||||
@ -49,11 +61,13 @@ const SECTIONS = (driver, isUpdate) => [
|
||||
/**
|
||||
* @param {VN_DRIVERS} driver - Virtual network driver
|
||||
* @param {boolean} [isUpdate] - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {BaseSchema} Step schema
|
||||
*/
|
||||
const SCHEMA = (driver, isUpdate) =>
|
||||
const SCHEMA = (driver, isUpdate, oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(
|
||||
SECTIONS(driver, isUpdate)
|
||||
SECTIONS(driver, isUpdate, oneConfig, adminGroup)
|
||||
.map(({ schema, fields }) => schema ?? fields)
|
||||
.flat()
|
||||
).concat(object({ [IP_LINK_CONF_FIELD.name]: IP_LINK_CONF_FIELD.validation }))
|
||||
|
@ -75,8 +75,11 @@ const EnhancedTable = ({
|
||||
noDataMessage,
|
||||
messages = [],
|
||||
dataDepend,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const styles = EnhancedTableStyles()
|
||||
const styles = EnhancedTableStyles({
|
||||
readOnly: readOnly,
|
||||
})
|
||||
|
||||
const isUninitialized = useMemo(
|
||||
() => isLoading && data === undefined,
|
||||
@ -258,7 +261,7 @@ const EnhancedTable = ({
|
||||
refetch={refetch}
|
||||
isLoading={isLoading}
|
||||
singleSelect={singleSelect}
|
||||
disableRowSelect={disableRowSelect}
|
||||
disableRowSelect={disableRowSelect || readOnly}
|
||||
globalActions={globalActions}
|
||||
selectedRows={selectedRows}
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
@ -294,7 +297,7 @@ const EnhancedTable = ({
|
||||
{!disableGlobalSort && <GlobalSort {...useTableProps} />}
|
||||
</div>
|
||||
{/* SELECTED ROWS */}
|
||||
{displaySelectedRows && (
|
||||
{displaySelectedRows && !readOnly && (
|
||||
<div>
|
||||
<GlobalSelectedRows
|
||||
useTableProps={useTableProps}
|
||||
@ -369,7 +372,7 @@ const EnhancedTable = ({
|
||||
onClick={(e) => {
|
||||
typeof onRowClick === 'function' && onRowClick(original)
|
||||
|
||||
if (!disableRowSelect) {
|
||||
if (!disableRowSelect && !readOnly) {
|
||||
if (
|
||||
singleSelect ||
|
||||
(!singleSelect && !(e.ctrlKey || e.metaKey))
|
||||
@ -427,6 +430,7 @@ EnhancedTable.propTypes = {
|
||||
]),
|
||||
messages: PropTypes.array,
|
||||
dataDepend: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
||||
readOnly: PropTypes.bool,
|
||||
}
|
||||
|
||||
export * from 'client/components/Tables/Enhanced/Utils'
|
||||
|
@ -65,7 +65,12 @@ export default makeStyles(({ palette, typography, breakpoints }) => ({
|
||||
padding: '0.8em',
|
||||
cursor: 'pointer',
|
||||
color: palette.text.primary,
|
||||
backgroundColor: palette.background.paper,
|
||||
/**
|
||||
* @param {object} props - Properties of the styles
|
||||
* @returns {object} - Background color
|
||||
*/
|
||||
backgroundColor: (props) =>
|
||||
props.readOnly ? palette.action.hover : palette.background.paper,
|
||||
fontWeight: typography.fontWeightRegular,
|
||||
fontSize: '1em',
|
||||
border: `1px solid ${palette.divider}`,
|
||||
|
@ -37,9 +37,16 @@ const { ADD_AR, UPDATE_AR, DELETE_AR } = VN_ACTIONS
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string[]} props.tabProps.actions - Actions tab
|
||||
* @param {string} props.id - Virtual Network id
|
||||
* @param {object} props.oneConfig - Open Nebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ReactElement} AR tab
|
||||
*/
|
||||
const AddressTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
const AddressTab = ({
|
||||
tabProps: { actions } = {},
|
||||
id,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}) => {
|
||||
const { data: vnet } = useGetVNetworkQuery({ id })
|
||||
|
||||
/** @type {AddressRange[]} */
|
||||
@ -47,7 +54,13 @@ const AddressTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
|
||||
return (
|
||||
<Box padding={{ sm: '0.8em' }}>
|
||||
{actions[ADD_AR] === true && <AddAddressRangeAction vnetId={id} />}
|
||||
{actions[ADD_AR] === true && (
|
||||
<AddAddressRangeAction
|
||||
vnetId={id}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack gap="1em" py="0.8em">
|
||||
{addressRanges.map((ar) => (
|
||||
@ -58,10 +71,20 @@ const AddressTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
actions={
|
||||
<>
|
||||
{actions[UPDATE_AR] === true && (
|
||||
<UpdateAddressRangeAction vnetId={id} ar={ar} />
|
||||
<UpdateAddressRangeAction
|
||||
vnetId={id}
|
||||
ar={ar}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
)}
|
||||
{actions[DELETE_AR] === true && (
|
||||
<DeleteAddressRangeAction vnetId={id} ar={ar} />
|
||||
<DeleteAddressRangeAction
|
||||
vnetId={id}
|
||||
ar={ar}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
@ -75,6 +98,8 @@ const AddressTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
AddressTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
AddressTab.displayName = 'AddressTab'
|
||||
|
@ -30,6 +30,8 @@ import { SecurityGroupsTable, GlobalAction } from 'client/components/Tables'
|
||||
import { T, VN_ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { isRestrictedAttributes } from 'client/utils'
|
||||
|
||||
const { SEC_GROUP } = RESOURCE_NAMES
|
||||
const { ADD_SECGROUP } = VN_ACTIONS
|
||||
|
||||
@ -40,9 +42,16 @@ const { ADD_SECGROUP } = VN_ACTIONS
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string[]} props.tabProps.actions - Actions tab
|
||||
* @param {string} props.id - Virtual Network id
|
||||
* @param {object} props.oneConfig - OpenNebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to the oneadmin group
|
||||
* @returns {ReactElement} Security Groups tab
|
||||
*/
|
||||
const SecurityTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
const SecurityTab = ({
|
||||
tabProps: { actions } = {},
|
||||
id,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}) => {
|
||||
const { push: redirectTo } = useHistory()
|
||||
const { data: vnet } = useGetVNetworkQuery({ id })
|
||||
|
||||
@ -65,23 +74,31 @@ const SecurityTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
})
|
||||
|
||||
/** @type {GlobalAction[]} */
|
||||
const globalActions = [
|
||||
actions[ADD_SECGROUP] && {
|
||||
accessor: VN_ACTIONS.ADD_SECGROUP,
|
||||
dataCy: VN_ACTIONS.ADD_SECGROUP,
|
||||
tooltip: T.SecurityGroup,
|
||||
icon: AddIcon,
|
||||
options: [
|
||||
{
|
||||
dialogProps: { title: T.SecurityGroup },
|
||||
form: undefined,
|
||||
onSubmit: () => async (formData) => {
|
||||
console.log({ formData })
|
||||
const globalActions =
|
||||
adminGroup ||
|
||||
!isRestrictedAttributes(
|
||||
'SECURITY_GROUPS',
|
||||
undefined,
|
||||
oneConfig?.VNET_RESTRICTED_ATTR
|
||||
)
|
||||
? [
|
||||
actions[ADD_SECGROUP] && {
|
||||
accessor: VN_ACTIONS.ADD_SECGROUP,
|
||||
dataCy: VN_ACTIONS.ADD_SECGROUP,
|
||||
tooltip: T.SecurityGroup,
|
||||
icon: AddIcon,
|
||||
options: [
|
||||
{
|
||||
dialogProps: { title: T.SecurityGroup },
|
||||
form: undefined,
|
||||
onSubmit: () => async (formData) => {
|
||||
console.log({ formData })
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
].filter(Boolean)
|
||||
].filter(Boolean)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Box padding={{ sm: '0.8em', overflow: 'auto' }}>
|
||||
@ -100,6 +117,8 @@ const SecurityTab = ({ tabProps: { actions } = {}, id }) => {
|
||||
SecurityTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
SecurityTab.displayName = 'SecurityTab'
|
||||
|
@ -18,7 +18,7 @@ import PropTypes from 'prop-types'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useViews, useSystemData } from 'client/features/Auth'
|
||||
import { useGetVNetworkQuery } from 'client/features/OneApi/network'
|
||||
import { getAvailableInfoTabs } from 'client/models/Helper'
|
||||
|
||||
@ -46,11 +46,19 @@ const VNetworkTabs = memo(({ id }) => {
|
||||
id,
|
||||
})
|
||||
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
|
||||
const tabsAvailable = useMemo(() => {
|
||||
const resource = RESOURCE_NAMES.VNET
|
||||
const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {}
|
||||
|
||||
return getAvailableInfoTabs(infoTabs, getTabComponent, id)
|
||||
return getAvailableInfoTabs(
|
||||
infoTabs,
|
||||
getTabComponent,
|
||||
id,
|
||||
oneConfig,
|
||||
adminGroup
|
||||
)
|
||||
}, [view, id])
|
||||
|
||||
if (isError) {
|
||||
|
@ -15,3 +15,9 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
/** @enum {string} Base path for Open Nebula documentation */
|
||||
export const DOCS_BASE_PATH = 'https://docs.opennebula.io'
|
||||
|
||||
export const RESTRICTED_ATTRIBUTES_TYPE = {
|
||||
VM: 'VM_RESTRICTED_ATTR',
|
||||
IMAGE: 'IMAGE_RESTRICTED_ATTR',
|
||||
VNET: 'VNET_RESTRICTED_ATTR',
|
||||
}
|
||||
|
@ -31,6 +31,10 @@ import {
|
||||
import { CreateForm } from 'client/components/Forms/Image'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { useSystemData } from 'client/features/Auth'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a VM Template.
|
||||
*
|
||||
@ -41,6 +45,7 @@ function CreateImage() {
|
||||
const [allocate] = useAllocateImageMutation()
|
||||
const [upload] = useUploadImageMutation()
|
||||
const { enqueueSuccess, uploadSnackbar } = useGeneralApi()
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
useGetDatastoresQuery(undefined, { refetchOnMountOrArgChange: false })
|
||||
|
||||
const onSubmit = async ({ template, datastore, file }) => {
|
||||
@ -71,10 +76,19 @@ function CreateImage() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return (
|
||||
<CreateForm onSubmit={onSubmit} fallback={<SkeletonStepsForm />}>
|
||||
return !_.isEmpty(oneConfig) ? (
|
||||
<CreateForm
|
||||
onSubmit={onSubmit}
|
||||
stepProps={{
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</CreateForm>
|
||||
) : (
|
||||
<SkeletonStepsForm />
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,10 @@ import {
|
||||
import { CreateForm } from 'client/components/Forms/VNetwork'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { useSystemData } from 'client/features/Auth'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a Virtual Network.
|
||||
*
|
||||
@ -42,6 +46,7 @@ function CreateVirtualNetwork() {
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const [update] = useUpdateVNetMutation()
|
||||
const [allocate] = useAllocateVnetMutation()
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
|
||||
const { data } = useGetVNetworkQuery(
|
||||
{ id: vnetId, extended: true },
|
||||
@ -62,17 +67,21 @@ function CreateVirtualNetwork() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return vnetId && !data ? (
|
||||
<SkeletonStepsForm />
|
||||
) : (
|
||||
return !_.isEmpty(oneConfig) && ((vnetId && data) || !vnetId) ? (
|
||||
<CreateForm
|
||||
initialValues={data}
|
||||
stepProps={data}
|
||||
stepProps={{
|
||||
data,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</CreateForm>
|
||||
) : (
|
||||
<SkeletonStepsForm />
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -328,3 +328,28 @@ export const hasRestrictedAttributes = (
|
||||
|
||||
return !!restricteAttribute
|
||||
}
|
||||
|
||||
/**
|
||||
* Find if an attribute is a restricted attribute.
|
||||
*
|
||||
* @param {object} attribute - The attribute
|
||||
* @param {string} section - Section of the attribute
|
||||
* @param {Array} restrictedAttributes - List of restricted attributes
|
||||
* @returns {boolean} - True if it is restricted attribute
|
||||
*/
|
||||
export const isRestrictedAttributes = (
|
||||
attribute,
|
||||
section = 'PARENT',
|
||||
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(
|
||||
(restAttr) => restAttr === attribute
|
||||
)
|
||||
|
||||
return !!restricteAttribute
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import {
|
||||
VN_DRIVERS,
|
||||
INPUT_TYPES,
|
||||
USER_INPUT_TYPES,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
} from 'client/constants'
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
|
||||
@ -505,13 +506,19 @@ export const createForm =
|
||||
fields(props),
|
||||
props.nameParentAttribute,
|
||||
props.oneConfig,
|
||||
props.adminGroup
|
||||
props.adminGroup,
|
||||
props && props.restrictedAttributesType
|
||||
? props.restrictedAttributesType
|
||||
: RESTRICTED_ATTRIBUTES_TYPE.VM
|
||||
)
|
||||
: disableFields(
|
||||
fields,
|
||||
props.nameParentAttribute,
|
||||
props.oneConfig,
|
||||
props.adminGroup
|
||||
props.adminGroup,
|
||||
props && props.restrictedAttributesType
|
||||
? props.restrictedAttributesType
|
||||
: RESTRICTED_ATTRIBUTES_TYPE.VM
|
||||
)
|
||||
: typeof fields === 'function'
|
||||
? fields(props)
|
||||
@ -555,23 +562,28 @@ export const createForm =
|
||||
* @param {string} nameParentAttribute - Parent name of the form
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - It he user is an admin
|
||||
* @param {string} type - The type of restricted attributes use to filter
|
||||
* @returns {Array} - New array of fields
|
||||
*/
|
||||
export const disableFields = (
|
||||
fields = {},
|
||||
fields = [],
|
||||
nameParentAttribute,
|
||||
oneConfig = {},
|
||||
adminGroup = true
|
||||
adminGroup = true,
|
||||
type = RESTRICTED_ATTRIBUTES_TYPE.VM
|
||||
) => {
|
||||
// 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)
|
||||
const listRestrictedAttributes = oneConfig[type]
|
||||
const restrictedAttributes = listRestrictedAttributes
|
||||
.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) => {
|
||||
|
@ -37,6 +37,8 @@ const ALLOWED_KEYS_ONED_CONF = [
|
||||
'AUTH_MAD',
|
||||
'FEDERATION',
|
||||
'VM_RESTRICTED_ATTR',
|
||||
'IMAGE_RESTRICTED_ATTR',
|
||||
'VNET_RESTRICTED_ATTR',
|
||||
]
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user