1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-23 22:50:09 +03:00

B OpenNebula/one#6154: Fix restricted attributes in VM dialogs (#2755)

This commit is contained in:
David 2023-09-26 10:29:50 +02:00 committed by GitHub
parent a8b1e4ca5a
commit 7b6cbf0f1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 204 additions and 31 deletions

View File

@ -20,17 +20,21 @@ import { FormWithSchema } from 'client/components/Forms'
import { SECTIONS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/backup/schema'
import { T } from 'client/constants'
import PropTypes from 'prop-types'
/**
* @param {object} props - Component props
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} IO section component
*/
const Backup = () => (
const Backup = ({ oneConfig, adminGroup }) => (
<Stack
display="grid"
gap="1em"
sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }}
>
{SECTIONS.map(({ id, ...section }) => (
{SECTIONS(oneConfig, adminGroup).map(({ id, ...section }) => (
<FormWithSchema
key={id}
cy="backups-conf"
@ -43,4 +47,9 @@ const Backup = () => (
Backup.displayName = 'Backup'
Backup.propTypes = {
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
export default Backup

View File

@ -35,9 +35,11 @@ import { T, HYPERVISORS } from 'client/constants'
/**
* @param {object} props - Component props
* @param {HYPERVISORS} props.hypervisor - VM hypervisor
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Form content component
*/
const Content = ({ hypervisor }) => {
const Content = ({ hypervisor, oneConfig, adminGroup }) => {
const {
formState: { errors },
} = useFormContext()
@ -48,28 +50,48 @@ const Content = ({ hypervisor }) => {
id: 'booting',
icon: OsIcon,
label: <Translate word={T.OSAndCpu} />,
renderContent: () => <Booting hypervisor={hypervisor} />,
renderContent: () => (
<Booting
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
),
error: !!errors?.OS,
},
{
id: 'input_output',
icon: IOIcon,
label: <Translate word={T.InputOrOutput} />,
renderContent: () => <InputOutput hypervisor={hypervisor} />,
renderContent: () => (
<InputOutput
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
),
error: ['GRAPHICS', 'INPUT'].some((id) => errors?.[id]),
},
{
id: 'context',
icon: ContextIcon,
label: <Translate word={T.Context} />,
renderContent: () => <Context hypervisor={hypervisor} />,
renderContent: () => (
<Context
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
),
error: !!errors?.CONTEXT,
},
{
id: 'backup_config',
icon: BackupIcon,
label: <Translate word={T.Backup} />,
renderContent: () => <Backup />,
renderContent: () => (
<Backup oneConfig={oneConfig} adminGroup={adminGroup} />
),
error: ['BACKUP_VOLATILE', 'FS_FREEZE', 'KEEP_LAST', 'MODE'].some(
(id) => errors?.[`BACKUP_CONFIG.${id}`]
),
@ -78,9 +100,13 @@ const Content = ({ hypervisor }) => {
[errors, hypervisor]
)
return <Tabs tabs={tabs} />
return <Tabs tabs={tabs} oneConfig={oneConfig} adminGroup={adminGroup} />
}
Content.propTypes = { hypervisor: PropTypes.string }
Content.propTypes = {
hypervisor: PropTypes.string,
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
export default Content

View File

@ -25,16 +25,34 @@ import { HYPERVISORS } from 'client/constants'
/**
* @param {object} props - Component props
* @param {HYPERVISORS} props.hypervisor - VM hypervisor
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Context section component
*/
const ContextSection = ({ hypervisor }) => (
const ContextSection = ({ hypervisor, oneConfig, adminGroup }) => (
<>
<ConfigurationSection hypervisor={hypervisor} />
<FilesSection hypervisor={hypervisor} />
<ContextVarsSection hypervisor={hypervisor} />
<ConfigurationSection
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
<FilesSection
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
<ContextVarsSection
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
</>
)
ContextSection.propTypes = { hypervisor: PropTypes.string }
ContextSection.propTypes = {
hypervisor: PropTypes.string,
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
export default ContextSection

View File

@ -38,9 +38,16 @@ const { UPDATE_CONF } = VM_ACTIONS
* @param {object|boolean} props.tabProps - Tab properties
* @param {object} [props.tabProps.actions] - Actions from tab view yaml
* @param {string} props.id - Virtual machine id
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Configuration tab
*/
const VmConfigurationTab = ({ tabProps: { actions } = {}, id }) => {
const VmConfigurationTab = ({
tabProps: { actions } = {},
id,
oneConfig,
adminGroup,
}) => {
const [updateConf] = useUpdateConfigurationMutation()
const { data: vm = {}, isFetching } = useGetVmQuery({ id })
const { TEMPLATE, BACKUPS } = vm
@ -128,7 +135,7 @@ const VmConfigurationTab = ({ tabProps: { actions } = {}, id }) => {
},
form: () =>
UpdateConfigurationForm({
stepProps: { hypervisor },
stepProps: { hypervisor, oneConfig, adminGroup },
initialValues: vm,
}),
onSubmit: handleUpdateConf,
@ -181,6 +188,8 @@ const VmConfigurationTab = ({ tabProps: { actions } = {}, id }) => {
VmConfigurationTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
VmConfigurationTab.displayName = 'VmConfigurationTab'

View File

@ -36,9 +36,11 @@ import { T, VM, VM_ACTIONS } from 'client/constants'
* @param {object} props - Props
* @param {VM} props.vm - Virtual machine
* @param {string[]} props.actions - Available actions to capacity tab
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Capacity tab
*/
const CapacityPanel = ({ vm = {}, actions }) => {
const CapacityPanel = ({ vm = {}, actions, oneConfig, adminGroup }) => {
const {
CPU,
VCPU = '-',
@ -101,7 +103,19 @@ const CapacityPanel = ({ vm = {}, actions }) => {
},
].filter(Boolean)
return <List title={<PanelHeader vm={vm} actions={actions} />} list={info} />
return (
<List
title={
<PanelHeader
vm={vm}
actions={actions}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
}
list={info}
/>
)
}
CapacityPanel.propTypes = {
@ -117,13 +131,24 @@ CapacityPanel.displayName = 'CapacityPanel'
* @param {object} props - Props
* @param {VM} props.vm - Virtual machine
* @param {string[]} props.actions - Available actions to capacity tab
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Capacity panel header
*/
const PanelHeader = ({ vm = {}, actions = [] }) => {
const PanelHeader = ({ vm = {}, actions = [], oneConfig, adminGroup }) => {
const [resizeCapacity] = useResizeMutation()
const handleResizeCapacity = async (formData) => {
const { enforce, ...restOfData } = formData
// #6154: If a restricted attribute is send to the core in resize operation, it will fail. So delete every restricted attribute for resize operation.
const restrictedAttributes = oneConfig?.VM_RESTRICTED_ATTR
Object.keys(restOfData).forEach((key) => {
if (restrictedAttributes.find((attribute) => attribute === key)) {
delete restOfData[key]
}
})
const template = jsonToXml(restOfData)
await resizeCapacity({ id: vm.ID, enforce, template })
@ -160,7 +185,11 @@ const PanelHeader = ({ vm = {}, actions = [] }) => {
title: T.ResizeCapacity,
dataCy: 'modal-resize-capacity',
},
form: () => ResizeCapacityForm({ initialValues: vm.TEMPLATE }),
form: () =>
ResizeCapacityForm({
initialValues: vm.TEMPLATE,
stepProps: { oneConfig, adminGroup, nameParentAttribute: '' },
}),
onSubmit: handleResizeCapacity,
},
]}

View File

@ -54,9 +54,11 @@ const HIDDEN_MONITORING_REG =
* @param {object} props - Props
* @param {object} props.tabProps - Tab information
* @param {string} props.id - Virtual machine id
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Information tab
*/
const VmInfoTab = ({ tabProps = {}, id }) => {
const VmInfoTab = ({ tabProps = {}, id, oneConfig, adminGroup }) => {
const {
information_panel: informationPanel,
capacity_panel: capacityPanel,
@ -157,7 +159,12 @@ const VmInfoTab = ({ tabProps = {}, id }) => {
)}
{capacityPanel?.enabled && (
<>
<Capacity actions={getActions(capacityPanel?.actions)} vm={vm} />
<Capacity
actions={getActions(capacityPanel?.actions)}
vm={vm}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
<Graphs id={id} />
</>
)}
@ -203,6 +210,8 @@ const VmInfoTab = ({ tabProps = {}, id }) => {
VmInfoTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
VmInfoTab.displayName = 'VmInfoTab'

View File

@ -50,9 +50,16 @@ const {
* @param {object} props.tabProps - Tab information
* @param {string[]} props.tabProps.actions - Actions tab
* @param {string} props.id - Virtual Machine id
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Networks tab
*/
const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => {
const VmNetworkTab = ({
tabProps: { actions } = {},
id,
oneConfig,
adminGroup,
}) => {
const { data: vm } = useGetVmQuery({ id })
const [nics, hypervisor, actionsAvailable] = useMemo(() => {
@ -72,7 +79,13 @@ const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => {
return (
<div>
{actionsAvailable?.includes?.(ATTACH_NIC) && (
<AttachAction vmId={id} currentNics={nics} hypervisor={hypervisor} />
<AttachAction
vmId={id}
currentNics={nics}
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
)}
<Stack gap="1em" py="0.8em">
@ -123,6 +136,8 @@ const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => {
VmNetworkTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
VmNetworkTab.displayName = 'VmNetworkTab'

View File

@ -58,9 +58,16 @@ const {
* @param {object} props.tabProps - Tab information
* @param {string[]} props.tabProps.actions - Actions tab
* @param {string} props.id - Virtual Machine id
* @param {object} props.oneConfig - OpenNEbula configuration
* @param {boolean} props.adminGroup - If the user is admin
* @returns {ReactElement} Storage tab
*/
const VmStorageTab = ({ tabProps: { actions } = {}, id }) => {
const VmStorageTab = ({
tabProps: { actions } = {},
id,
oneConfig,
adminGroup,
}) => {
const { data: vm = {} } = useGetVmQuery({ id })
const [disks, hypervisor, actionsAvailable] = useMemo(() => {
@ -76,7 +83,12 @@ const VmStorageTab = ({ tabProps: { actions } = {}, id }) => {
return (
<div>
{actionsAvailable?.includes?.(ATTACH_DISK) && (
<AttachAction vmId={id} hypervisor={hypervisor} />
<AttachAction
vmId={id}
hypervisor={hypervisor}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
)}
<Stack gap="1em" py="0.8em">
@ -143,6 +155,8 @@ const VmStorageTab = ({ tabProps: { actions } = {}, id }) => {
VmStorageTab.propTypes = {
tabProps: PropTypes.object,
id: PropTypes.string,
oneConfig: PropTypes.object,
adminGroup: PropTypes.bool,
}
VmStorageTab.displayName = 'VmStorageTab'

View File

@ -21,7 +21,7 @@ import { memo, useMemo } from 'react'
import { Translate } from 'client/components/HOC'
import { RESOURCE_NAMES, T } from 'client/constants'
import { useViews } from 'client/features/Auth'
import { useViews, useSystemData } from 'client/features/Auth'
import {
useGetVmQuery,
useUpdateUserTemplateMutation,
@ -79,6 +79,8 @@ const VmTabs = memo(({ id }) => {
const { USER_TEMPLATE, ID } = vm
const { adminGroup, oneConfig } = useSystemData()
const handleDismissError = async () => {
const { ERROR, SCHED_MESSAGE, ...templateWithoutError } = USER_TEMPLATE
const xml = jsonToXml({ ...templateWithoutError })
@ -92,7 +94,13 @@ const VmTabs = memo(({ id }) => {
const resource = RESOURCE_NAMES.VM
const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {}
return getAvailableInfoTabs(infoTabs, getTabComponent, id)
return getAvailableInfoTabs(
infoTabs,
getTabComponent,
id,
oneConfig,
adminGroup
)
}, [view, id])
if (isError) {

View File

@ -308,13 +308,21 @@ export const getActionsAvailable = (actions = {}, hypervisor = '') =>
* @param {object} infoTabs - Info tabs from view yaml
* @param {Function} getTabComponent - Function to get tab component
* @param {string} id - Resource id
* @param {object} oneConfig - OpenNEbula configuration
* @param {boolean} adminGroup - If the user is admin
* @returns {{
* id: string,
* name: string,
* renderContent: Function
* }[]} - List of available info tabs for the resource
*/
export const getAvailableInfoTabs = (infoTabs = {}, getTabComponent, id) =>
export const getAvailableInfoTabs = (
infoTabs = {},
getTabComponent,
id,
oneConfig,
adminGroup
) =>
Object.entries(infoTabs)
?.filter(([_, { enabled } = {}]) => !!enabled)
?.map(([tabName, tabProps]) => {
@ -324,7 +332,14 @@ export const getAvailableInfoTabs = (infoTabs = {}, getTabComponent, id) =>
TabContent && {
label: TabContent?.label ?? sentenceCase(tabName),
id: tabName,
renderContent: () => <TabContent tabProps={tabProps} id={id} />,
renderContent: () => (
<TabContent
tabProps={tabProps}
id={id}
oneConfig={oneConfig}
adminGroup={adminGroup}
/>
),
}
)
})

View File

@ -494,7 +494,28 @@ export const createForm =
(schema, fields, extraParams = {}) =>
(props = {}, initialValues) => {
const schemaCallback = typeof schema === 'function' ? schema(props) : schema
const fieldsCallback = typeof fields === 'function' ? fields(props) : fields
const disable =
props?.oneConfig &&
props?.adminGroup === false &&
typeof props?.nameParentAttribute === 'string'
const fieldsCallback = disable
? typeof fields === 'function'
? disableFields(
fields(props),
props.nameParentAttribute,
props.oneConfig,
props.adminGroup
)
: disableFields(
fields,
props.nameParentAttribute,
props.oneConfig,
props.adminGroup
)
: typeof fields === 'function'
? fields(props)
: fields
const defaultTransformInitialValue = (values) =>
schemaCallback.cast(values, { stripUnknown: true })