mirror of
https://github.com/OpenNebula/one.git
synced 2025-01-26 10:03:37 +03:00
F OpenNebula/one#5422: Add attach nic form (#1391)
This commit is contained in:
parent
599f79bf09
commit
3e703ec4e0
@ -44,12 +44,12 @@ const ButtonToTriggerForm = ({ buttonProps = {}, title, options = [] }) => {
|
||||
const { steps, defaultValues, resolver, fields, onSubmit: handleSubmit } = Form ?? {}
|
||||
|
||||
const handleTriggerSubmit = async formData => {
|
||||
await handleSubmit(formData)
|
||||
await handleSubmit?.(formData)
|
||||
hide()
|
||||
}
|
||||
|
||||
const openDialogForm = form => {
|
||||
show(form)
|
||||
const openDialogForm = ({ form = {}, onSubmit }) => {
|
||||
show({ ...form, onSubmit })
|
||||
handleClose()
|
||||
}
|
||||
|
||||
@ -65,8 +65,9 @@ const ButtonToTriggerForm = ({ buttonProps = {}, title, options = [] }) => {
|
||||
aria-describedby={buttonProps.cy ?? 'main-button-form'}
|
||||
disabled={!options.length}
|
||||
endIcon={isGroupButton && <NavArrowDown />}
|
||||
onClick={evt =>
|
||||
!isGroupButton ? openDialogForm(options[0].form) : handleToggle(evt)
|
||||
onClick={evt => !isGroupButton
|
||||
? openDialogForm(options[0])
|
||||
: handleToggle(evt)
|
||||
}
|
||||
{...buttonProps}
|
||||
>
|
||||
@ -86,11 +87,11 @@ const ButtonToTriggerForm = ({ buttonProps = {}, title, options = [] }) => {
|
||||
<Paper variant='outlined'>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<MenuList disablePadding>
|
||||
{options.map(({ cy, name, form = {}, onSubmit }) => (
|
||||
{options.map(({ cy, name, ...option }) => (
|
||||
<MenuItem
|
||||
key={name}
|
||||
data-cy={cy}
|
||||
onClick={() => openDialogForm({ ...form, onSubmit })}
|
||||
onClick={() => openDialogForm(option)}
|
||||
>
|
||||
<Translate word={name} />
|
||||
</MenuItem>
|
||||
|
@ -0,0 +1,46 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
FIELDS
|
||||
} from 'client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'advanced'
|
||||
|
||||
const AdvancedOptions = ({ nics = [] } = {}) => ({
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: () => SCHEMA(nics),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(
|
||||
() => (
|
||||
<FormWithSchema
|
||||
cy='attach-nic-advanced'
|
||||
id={STEP_ID}
|
||||
fields={FIELDS(nics)}
|
||||
/>
|
||||
),
|
||||
[nics?.length, nics?.[0]?.ID]
|
||||
)
|
||||
})
|
||||
|
||||
export default AdvancedOptions
|
@ -0,0 +1,77 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import * as yup from 'yup'
|
||||
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
|
||||
const RDP = {
|
||||
name: 'RDP',
|
||||
label: 'RDP connection',
|
||||
type: INPUT_TYPES.CHECKBOX,
|
||||
validation: yup
|
||||
.boolean()
|
||||
.transform(value => {
|
||||
if (typeof value === 'boolean') return value
|
||||
|
||||
return String(value).toUpperCase() === 'YES'
|
||||
})
|
||||
.default(false),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
const ALIAS = nics => ({
|
||||
name: 'PARENT',
|
||||
label: 'Attach as an alias',
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: [{ text: '', value: '' }]
|
||||
.concat(nics?.map?.(({ NAME, IP = '', NETWORK = '', NIC_ID = '' } = {}) =>
|
||||
({ text: `${NIC_ID} - ${NETWORK} ${IP}`, value: NAME })
|
||||
)),
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.default(undefined)
|
||||
})
|
||||
|
||||
const EXTERNAL = {
|
||||
name: 'EXTERNAL',
|
||||
label: 'External',
|
||||
type: INPUT_TYPES.CHECKBOX,
|
||||
tooltip: 'The NIC will be attached as an external alias of the VM',
|
||||
dependOf: ALIAS.name,
|
||||
htmlType: type => !type?.length ? INPUT_TYPES.HIDDEN : undefined,
|
||||
validation: yup
|
||||
.boolean()
|
||||
.transform(value => {
|
||||
if (typeof value === 'boolean') return value
|
||||
|
||||
return String(value).toUpperCase() === 'YES'
|
||||
})
|
||||
.default(false),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
export const FIELDS = nics => [
|
||||
RDP,
|
||||
ALIAS(nics),
|
||||
EXTERNAL
|
||||
]
|
||||
|
||||
export const SCHEMA = nics =>
|
||||
yup.object(getValidationFromFields(FIELDS(nics)))
|
@ -0,0 +1,69 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { VNetworksTable } from 'client/components/Tables'
|
||||
|
||||
import {
|
||||
SCHEMA
|
||||
} from 'client/components/Forms/Vm/AttachNicForm/Steps/NetworksTable/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'network'
|
||||
|
||||
const NetworkStep = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Network,
|
||||
resolver: () => SCHEMA,
|
||||
content: useCallback(
|
||||
({ data, setFormData }) => {
|
||||
const selectedNetwork = data?.[0]
|
||||
|
||||
const {
|
||||
handleSelect,
|
||||
handleClear
|
||||
} = useListForm({ key: STEP_ID, setList: setFormData })
|
||||
|
||||
const handleSelectedRows = rows => {
|
||||
const { original } = rows?.[0] ?? {}
|
||||
const { ID, NAME, UID, UNAME, SECURITY_GROUPS } = original ?? {}
|
||||
|
||||
const network = {
|
||||
NETWORK_ID: ID,
|
||||
NETWORK: NAME,
|
||||
NETWORK_UID: UID,
|
||||
NETWORK_UNAME: UNAME,
|
||||
SECURITY_GROUPS
|
||||
}
|
||||
|
||||
ID !== undefined ? handleSelect(network) : handleClear()
|
||||
}
|
||||
|
||||
return (
|
||||
<VNetworksTable
|
||||
singleSelect
|
||||
onlyGlobalSearch
|
||||
onlyGlobalSelectedRows
|
||||
initialState={{ selectedRowIds: { [selectedNetwork?.ID]: true } }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
/>
|
||||
)
|
||||
}, [])
|
||||
})
|
||||
|
||||
export default NetworkStep
|
@ -0,0 +1,23 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import * as yup from 'yup'
|
||||
|
||||
export const SCHEMA = yup
|
||||
.array(yup.object())
|
||||
.min(1, 'Select network')
|
||||
.max(1, 'Max. one network selected')
|
||||
.required('Network field is required')
|
||||
.default([])
|
@ -0,0 +1,38 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import * as yup from 'yup'
|
||||
|
||||
import NetworksTable from 'client/components/Forms/Vm/AttachNicForm/Steps/NetworksTable'
|
||||
import AdvancedOptions from 'client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions'
|
||||
|
||||
const Steps = stepProps => {
|
||||
const network = NetworksTable(stepProps)
|
||||
const advanced = AdvancedOptions(stepProps)
|
||||
|
||||
const steps = [network, advanced]
|
||||
|
||||
const resolver = () => yup.object({
|
||||
[network.id]: network.resolver(),
|
||||
[advanced.id]: advanced.resolver()
|
||||
})
|
||||
|
||||
const defaultValues = resolver().default()
|
||||
|
||||
return { steps, defaultValues, resolver }
|
||||
}
|
||||
|
||||
export default Steps
|
@ -0,0 +1,16 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
export { default } from 'client/components/Forms/Vm/AttachNicForm/Steps'
|
@ -14,8 +14,10 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import ResizeCapacityForm from 'client/components/Forms/Vm/ResizeCapacityForm'
|
||||
import AttachNicForm from 'client/components/Forms/Vm/AttachNicForm'
|
||||
export * from 'client/components/Forms/Vm/AttachDiskForm'
|
||||
|
||||
export {
|
||||
AttachNicForm,
|
||||
ResizeCapacityForm
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ import React, { useEffect } from 'react'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useVNetwork, useVNetworkApi } from 'client/features/One'
|
||||
|
||||
import { SkeletonTable, EnhancedTable } from 'client/components/Tables'
|
||||
import { SkeletonTable, EnhancedTable, EnhancedTableProps } from 'client/components/Tables'
|
||||
import VNetworkColumns from 'client/components/Tables/VNetworks/columns'
|
||||
import VNetworkRow from 'client/components/Tables/VNetworks/row'
|
||||
|
||||
const VNetworksTable = () => {
|
||||
const VNetworksTable = props => {
|
||||
const columns = React.useMemo(() => VNetworkColumns, [])
|
||||
|
||||
const vNetworks = useVNetwork()
|
||||
@ -45,8 +45,12 @@ const VNetworksTable = () => {
|
||||
isLoading={loading || reloading}
|
||||
getRowId={row => String(row.ID)}
|
||||
RowComponent={VNetworkRow}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VNetworksTable.propTypes = EnhancedTableProps
|
||||
VNetworksTable.displayName = 'VNetworksTable'
|
||||
|
||||
export default VNetworksTable
|
||||
|
@ -101,7 +101,7 @@ const NetworkItem = ({ nic = {}, actions }) => {
|
||||
|
||||
const hasDetails = React.useMemo(
|
||||
() => !!ALIAS.length || !!SECURITY_GROUPS?.length,
|
||||
[ALIAS.length, SECURITY_GROUPS?.length]
|
||||
[ALIAS?.length, SECURITY_GROUPS?.length]
|
||||
)
|
||||
|
||||
const handleDetach = async () => {
|
||||
|
@ -16,21 +16,23 @@
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button } from '@material-ui/core'
|
||||
|
||||
import { useDialog } from 'client/hooks'
|
||||
import { useVmApi } from 'client/features/One'
|
||||
import { TabContext } from 'client/components/Tabs/TabProvider'
|
||||
import { DialogConfirmation } from 'client/components/Dialogs'
|
||||
|
||||
import NetworkList from 'client/components/Tabs/Vm/Network/List'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import { AttachNicForm } from 'client/components/Forms/Vm'
|
||||
|
||||
import * as VirtualMachine from 'client/models/VirtualMachine'
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { mapUserInputs } from 'client/utils'
|
||||
import { T, VM_ACTIONS } from 'client/constants'
|
||||
|
||||
const VmNetworkTab = ({ tabProps = {} }) => {
|
||||
const { display, show, hide } = useDialog()
|
||||
const { data: vm } = React.useContext(TabContext)
|
||||
const { attachNic } = useVmApi()
|
||||
|
||||
const { handleRefetch, data: vm } = React.useContext(TabContext)
|
||||
const { actions = [] } = tabProps
|
||||
|
||||
const nics = VirtualMachine.getNics(vm, {
|
||||
@ -41,31 +43,33 @@ const VmNetworkTab = ({ tabProps = {} }) => {
|
||||
const hypervisor = VirtualMachine.getHypervisor(vm)
|
||||
const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor)
|
||||
|
||||
const handleAttachNic = async ({ network, advanced }) => {
|
||||
const networkSelected = network?.[0]
|
||||
const isAlias = !!advanced?.PARENT?.length
|
||||
const root = { ...networkSelected, ...mapUserInputs(advanced) }
|
||||
|
||||
const template = Helper.jsonToXml({
|
||||
[isAlias ? 'NIC_ALIAS' : 'NIC']: root
|
||||
})
|
||||
|
||||
const response = await attachNic(vm.ID, template)
|
||||
String(response) === String(vm.ID) && await handleRefetch?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{actionsAvailable?.includes?.(VM_ACTIONS.ATTACH_NIC) && (
|
||||
<Button
|
||||
data-cy='attach-nic'
|
||||
size='small'
|
||||
color='secondary'
|
||||
onClick={show}
|
||||
variant='contained'
|
||||
>
|
||||
{Tr(T.AttachNic)}
|
||||
</Button>
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{ 'data-cy': 'attach-nic' }}
|
||||
title={T.AttachNic}
|
||||
options={[{
|
||||
form: AttachNicForm({ nics }),
|
||||
onSubmit: handleAttachNic
|
||||
}]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<NetworkList actions={actionsAvailable} nics={nics} />
|
||||
|
||||
{display && (
|
||||
<DialogConfirmation
|
||||
title={T.AttachNic}
|
||||
handleAccept={hide}
|
||||
handleCancel={hide}
|
||||
>
|
||||
<p>TODO: should define in view yaml ??</p>
|
||||
</DialogConfirmation>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -46,4 +46,5 @@ export const resize = createAction('vm/resize', vmService.resize)
|
||||
export const changePermissions = createAction('vm/chmod', vmService.changePermissions)
|
||||
export const changeOwnership = createAction('vm/chown', vmService.changeOwnership)
|
||||
export const attachDisk = createAction('vm/attach/disk', vmService.attachDisk)
|
||||
export const attachNic = createAction('vm/attach/nic', vmService.attachNic)
|
||||
export const detachNic = createAction('vm/detach/nic', vmService.detachNic)
|
||||
|
@ -45,6 +45,7 @@ export const useVmApi = () => {
|
||||
changeOwnership: (id, ownership) =>
|
||||
unwrapDispatch(actions.changeOwnership({ id, ownership })),
|
||||
attachDisk: (id, template) => unwrapDispatch(actions.attachDisk({ id, template })),
|
||||
attachNic: (id, template) => unwrapDispatch(actions.attachNic({ id, template })),
|
||||
detachNic: (id, nic) => unwrapDispatch(actions.detachNic({ id, nic }))
|
||||
}
|
||||
}
|
||||
|
@ -242,6 +242,28 @@ export const vmService = ({
|
||||
return res?.data
|
||||
},
|
||||
|
||||
/**
|
||||
* Attaches a new network interface to the virtual machine.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - Virtual machine id
|
||||
* @param {string} params.template
|
||||
* - A string containing a single NIC vector attribute
|
||||
* @returns {number} Virtual machine id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
attachNic: async ({ id, template }) => {
|
||||
const name = Actions.VM_NIC_ATTACH
|
||||
const command = { name, ...Commands[name] }
|
||||
const config = requestConfig({ id, template }, command)
|
||||
|
||||
const res = await RestClient.request(config)
|
||||
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data
|
||||
|
||||
return res?.data
|
||||
},
|
||||
|
||||
/**
|
||||
* Detaches a network interface from a virtual machine.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user