feat(migrate-vm-modal): controlled form (#2259)
This commit is contained in:
parent
495c97b44b
commit
4d69866532
@ -1,19 +1,14 @@
|
|||||||
import Collapse from 'collapse'
|
import Collapse from 'collapse'
|
||||||
import Component from 'base-component'
|
import Component from 'base-component'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { every, forEach, map } from 'lodash'
|
import { map } from 'lodash'
|
||||||
|
|
||||||
import _ from '../../intl'
|
import _ from '../../intl'
|
||||||
import propTypes from '../../prop-types-decorator'
|
import propTypes from '../../prop-types-decorator'
|
||||||
import SingleLineRow from '../../single-line-row'
|
import SingleLineRow from '../../single-line-row'
|
||||||
import { createSelector } from '../../selectors'
|
|
||||||
import { SelectSr } from '../../select-objects'
|
|
||||||
import { isSrWritable } from 'xo'
|
|
||||||
import { Container, Col } from 'grid'
|
import { Container, Col } from 'grid'
|
||||||
|
import { isSrWritable } from 'xo'
|
||||||
// Can 2 SRs on the same pool have 2 VDIs used by the same VM
|
import { SelectSr } from '../../select-objects'
|
||||||
const areSrsCompatible = (sr1, sr2) =>
|
|
||||||
sr1.shared || sr2.shared || sr1.$container === sr2.$container
|
|
||||||
|
|
||||||
const Collapsible = ({ collapsible, children, ...props }) =>
|
const Collapsible = ({ collapsible, children, ...props }) =>
|
||||||
collapsible ? (
|
collapsible ? (
|
||||||
@ -32,96 +27,49 @@ Collapsible.propTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@propTypes({
|
@propTypes({
|
||||||
vdis: propTypes.array.isRequired,
|
mainSrPredicate: propTypes.func,
|
||||||
predicate: propTypes.func,
|
onChange: propTypes.func.isRequired,
|
||||||
|
srPredicate: propTypes.func,
|
||||||
|
value: propTypes.objectOf(
|
||||||
|
propTypes.shape({
|
||||||
|
mainSr: propTypes.object,
|
||||||
|
mapVdisSrs: propTypes.object,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
vdis: propTypes.object.isRequired,
|
||||||
})
|
})
|
||||||
export default class ChooseSrForEachVdisModal extends Component {
|
export default class ChooseSrForEachVdisModal extends Component {
|
||||||
state = {
|
_onChange = newValues => {
|
||||||
mapVdisSrs: {},
|
this.props.onChange({
|
||||||
}
|
...this.props.value,
|
||||||
|
...newValues,
|
||||||
componentWillReceiveProps (newProps) {
|
|
||||||
if (
|
|
||||||
this.props.predicate !== undefined &&
|
|
||||||
newProps.predicate !== this.props.predicate
|
|
||||||
) {
|
|
||||||
this.state = {
|
|
||||||
mainSr: undefined,
|
|
||||||
mapVdisSrs: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onChange = props => {
|
|
||||||
this.setState(props)
|
|
||||||
this.props.onChange(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
_onChangeMainSr = newSr => {
|
|
||||||
const oldSr = this.state.mainSr
|
|
||||||
|
|
||||||
if (oldSr == null || newSr == null || oldSr.$pool !== newSr.$pool) {
|
|
||||||
this.setState({
|
|
||||||
mapVdisSrs: {},
|
|
||||||
})
|
|
||||||
} else if (!newSr.shared) {
|
|
||||||
const mapVdisSrs = { ...this.state.mapVdisSrs }
|
|
||||||
forEach(mapVdisSrs, (sr, vdi) => {
|
|
||||||
if (
|
|
||||||
sr != null &&
|
|
||||||
newSr !== sr &&
|
|
||||||
sr.$container !== newSr.$container &&
|
|
||||||
!sr.shared
|
|
||||||
) {
|
|
||||||
delete mapVdisSrs[vdi]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this._onChange({ mapVdisSrs })
|
|
||||||
}
|
|
||||||
|
|
||||||
this._onChange({
|
|
||||||
mainSr: newSr,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_getSrPredicate = createSelector(
|
_onChangeMainSr = mainSr => this._onChange({ mainSr })
|
||||||
() => this.state.mainSr,
|
|
||||||
() => this.state.mapVdisSrs,
|
|
||||||
(mainSr, mapVdisSrs) => sr =>
|
|
||||||
isSrWritable(sr) &&
|
|
||||||
mainSr.$pool === sr.$pool &&
|
|
||||||
areSrsCompatible(mainSr, sr) &&
|
|
||||||
every(
|
|
||||||
mapVdisSrs,
|
|
||||||
selectedSr => selectedSr == null || areSrsCompatible(selectedSr, sr)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { props, state } = this
|
const { props } = this
|
||||||
const { vdis } = props
|
const {
|
||||||
const { mainSr, mapVdisSrs } = state
|
mainSrPredicate = isSrWritable,
|
||||||
|
srPredicate = mainSrPredicate,
|
||||||
const srPredicate = props.predicate || this._getSrPredicate()
|
value: { mainSr, mapVdisSrs },
|
||||||
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SelectSr
|
<SelectSr
|
||||||
onChange={mainSr =>
|
onChange={this._onChangeMainSr}
|
||||||
props.predicate !== undefined
|
|
||||||
? this._onChange({ mainSr })
|
|
||||||
: this._onChangeMainSr(mainSr)
|
|
||||||
}
|
|
||||||
predicate={props.predicate || isSrWritable}
|
|
||||||
placeholder={_('chooseSrForEachVdisModalMainSr')}
|
placeholder={_('chooseSrForEachVdisModalMainSr')}
|
||||||
|
predicate={mainSrPredicate}
|
||||||
value={mainSr}
|
value={mainSr}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
{vdis != null &&
|
{props.vdis != null &&
|
||||||
mainSr != null && (
|
mainSr != null && (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
collapsible={vdis.length >= 3}
|
|
||||||
buttonText={_('chooseSrForEachVdisModalSelectSr')}
|
buttonText={_('chooseSrForEachVdisModalSelectSr')}
|
||||||
|
collapsible={props.vdis.length >= 3}
|
||||||
>
|
>
|
||||||
<br />
|
<br />
|
||||||
<Container>
|
<Container>
|
||||||
@ -133,7 +81,7 @@ export default class ChooseSrForEachVdisModal extends Component {
|
|||||||
<strong>{_('chooseSrForEachVdisModalSrLabel')}</strong>
|
<strong>{_('chooseSrForEachVdisModalSrLabel')}</strong>
|
||||||
</Col>
|
</Col>
|
||||||
</SingleLineRow>
|
</SingleLineRow>
|
||||||
{map(vdis, vdi => (
|
{map(props.vdis, vdi => (
|
||||||
<SingleLineRow key={vdi.uuid}>
|
<SingleLineRow key={vdi.uuid}>
|
||||||
<Col size={6}>{vdi.name_label || vdi.name}</Col>
|
<Col size={6}>{vdi.name_label || vdi.name}</Col>
|
||||||
<Col size={6}>
|
<Col size={6}>
|
||||||
@ -143,8 +91,8 @@ export default class ChooseSrForEachVdisModal extends Component {
|
|||||||
mapVdisSrs: { ...mapVdisSrs, [vdi.uuid]: sr },
|
mapVdisSrs: { ...mapVdisSrs, [vdi.uuid]: sr },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
value={mapVdisSrs[vdi.uuid]}
|
|
||||||
predicate={srPredicate}
|
predicate={srPredicate}
|
||||||
|
value={mapVdisSrs !== undefined && mapVdisSrs[vdi.uuid]}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</SingleLineRow>
|
</SingleLineRow>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import BaseComponent from 'base-component'
|
import BaseComponent from 'base-component'
|
||||||
import every from 'lodash/every'
|
import every from 'lodash/every'
|
||||||
import forEach from 'lodash/forEach'
|
|
||||||
import find from 'lodash/find'
|
import find from 'lodash/find'
|
||||||
|
import forEach from 'lodash/forEach'
|
||||||
import map from 'lodash/map'
|
import map from 'lodash/map'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import store from 'store'
|
import store from 'store'
|
||||||
@ -11,18 +11,17 @@ import ChooseSrForEachVdisModal from '../choose-sr-for-each-vdis-modal'
|
|||||||
import invoke from '../../invoke'
|
import invoke from '../../invoke'
|
||||||
import SingleLineRow from '../../single-line-row'
|
import SingleLineRow from '../../single-line-row'
|
||||||
import { Col } from '../../grid'
|
import { Col } from '../../grid'
|
||||||
|
import { connectStore, mapPlus, resolveId, resolveIds } from '../../utils'
|
||||||
import { getDefaultNetworkForVif } from '../utils'
|
import { getDefaultNetworkForVif } from '../utils'
|
||||||
import { SelectHost, SelectNetwork } from '../../select-objects'
|
import { SelectHost, SelectNetwork } from '../../select-objects'
|
||||||
import { connectStore, mapPlus, resolveIds } from '../../utils'
|
|
||||||
import {
|
import {
|
||||||
createGetObjectsOfType,
|
createGetObjectsOfType,
|
||||||
createPicker,
|
createPicker,
|
||||||
createSelector,
|
createSelector,
|
||||||
getObject,
|
getObject,
|
||||||
} from '../../selectors'
|
} from '../../selectors'
|
||||||
import { isSrShared } from 'xo'
|
|
||||||
|
|
||||||
import { isSrWritable } from '../'
|
import { isSrShared, isSrWritable } from '../'
|
||||||
|
|
||||||
import styles from './index.css'
|
import styles from './index.css'
|
||||||
|
|
||||||
@ -68,8 +67,8 @@ export default class MigrateVmModalBody extends BaseComponent {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
mapVdisSrs: {},
|
|
||||||
mapVifsNetworks: {},
|
mapVifsNetworks: {},
|
||||||
|
targetSrs: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
this._getHostPredicate = createSelector(
|
this._getHostPredicate = createSelector(
|
||||||
@ -126,11 +125,11 @@ export default class MigrateVmModalBody extends BaseComponent {
|
|||||||
|
|
||||||
get value () {
|
get value () {
|
||||||
return {
|
return {
|
||||||
targetHost: this.state.host && this.state.host.id,
|
mapVdisSrs: resolveIds(this.state.targetSrs.mapVdisSrs),
|
||||||
sr: this.state.mainSr && this.state.mainSr.id,
|
|
||||||
mapVdisSrs: resolveIds(this.state.mapVdisSrs),
|
|
||||||
mapVifsNetworks: this.state.mapVifsNetworks,
|
mapVifsNetworks: this.state.mapVifsNetworks,
|
||||||
migrationNetwork: this.state.migrationNetworkId,
|
migrationNetwork: this.state.migrationNetworkId,
|
||||||
|
sr: resolveId(this.state.targetSrs.mainSr),
|
||||||
|
targetHost: this.state.host && this.state.host.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +173,7 @@ export default class MigrateVmModalBody extends BaseComponent {
|
|||||||
intraPool,
|
intraPool,
|
||||||
mapVifsNetworks: undefined,
|
mapVifsNetworks: undefined,
|
||||||
migrationNetwork: undefined,
|
migrationNetwork: undefined,
|
||||||
|
targetSrs: {},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -205,6 +205,7 @@ export default class MigrateVmModalBody extends BaseComponent {
|
|||||||
intraPool,
|
intraPool,
|
||||||
mapVifsNetworks: defaultNetworksForVif,
|
mapVifsNetworks: defaultNetworksForVif,
|
||||||
migrationNetworkId: defaultMigrationNetworkId,
|
migrationNetworkId: defaultMigrationNetworkId,
|
||||||
|
targetSrs: {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +220,7 @@ export default class MigrateVmModalBody extends BaseComponent {
|
|||||||
intraPool,
|
intraPool,
|
||||||
mapVifsNetworks,
|
mapVifsNetworks,
|
||||||
migrationNetworkId,
|
migrationNetworkId,
|
||||||
|
targetSrs,
|
||||||
} = this.state
|
} = this.state
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -240,8 +242,9 @@ export default class MigrateVmModalBody extends BaseComponent {
|
|||||||
<SingleLineRow>
|
<SingleLineRow>
|
||||||
<Col size={12}>
|
<Col size={12}>
|
||||||
<ChooseSrForEachVdisModal
|
<ChooseSrForEachVdisModal
|
||||||
onChange={props => this.setState(props)}
|
mainSrPredicate={this._getSrPredicate()}
|
||||||
predicate={this._getSrPredicate()}
|
onChange={this.linkState('targetSrs')}
|
||||||
|
value={targetSrs}
|
||||||
vdis={vdis}
|
vdis={vdis}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -5,7 +5,6 @@ import every from 'lodash/every'
|
|||||||
import filter from 'lodash/filter'
|
import filter from 'lodash/filter'
|
||||||
import find from 'lodash/find'
|
import find from 'lodash/find'
|
||||||
import forEach from 'lodash/forEach'
|
import forEach from 'lodash/forEach'
|
||||||
import getEventValue from 'get-event-value'
|
|
||||||
import groupBy from 'lodash/groupBy'
|
import groupBy from 'lodash/groupBy'
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import isEmpty from 'lodash/isEmpty'
|
import isEmpty from 'lodash/isEmpty'
|
||||||
@ -125,8 +124,8 @@ const openImportModal = ({ backups }) =>
|
|||||||
body: <ImportModalBody vmName={backups[0].name} backups={backups} />,
|
body: <ImportModalBody vmName={backups[0].name} backups={backups} />,
|
||||||
}).then(doImport)
|
}).then(doImport)
|
||||||
|
|
||||||
const doImport = ({ backup, mainSr, start, mapVdisSrs }) => {
|
const doImport = ({ backup, targetSrs, start }) => {
|
||||||
if (!mainSr || !backup) {
|
if (targetSrs.mainSr === undefined || backup === undefined) {
|
||||||
error(_('backupRestoreErrorTitle'), _('backupRestoreErrorMessage'))
|
error(_('backupRestoreErrorTitle'), _('backupRestoreErrorMessage'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -137,10 +136,10 @@ const doImport = ({ backup, mainSr, start, mapVdisSrs }) => {
|
|||||||
info(_('importBackupTitle'), _('importBackupMessage'))
|
info(_('importBackupTitle'), _('importBackupMessage'))
|
||||||
try {
|
try {
|
||||||
const importPromise = importMethods[backup.type]({
|
const importPromise = importMethods[backup.type]({
|
||||||
remote: backup.remoteId,
|
|
||||||
sr: mainSr,
|
|
||||||
file: backup.path,
|
file: backup.path,
|
||||||
mapVdisSrs,
|
mapVdisSrs: targetSrs.mapVdisSrs,
|
||||||
|
remote: backup.remoteId,
|
||||||
|
sr: targetSrs.mainSr,
|
||||||
}).then(id => {
|
}).then(id => {
|
||||||
return id
|
return id
|
||||||
})
|
})
|
||||||
@ -153,12 +152,8 @@ const doImport = ({ backup, mainSr, start, mapVdisSrs }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ModalBody extends Component {
|
class _ModalBody extends Component {
|
||||||
constructor () {
|
state = {
|
||||||
super()
|
targetSrs: {},
|
||||||
|
|
||||||
this.state = {
|
|
||||||
mapVdisSrs: {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get value () {
|
get value () {
|
||||||
@ -166,52 +161,52 @@ class _ModalBody extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getSrPredicate = createSelector(
|
_getSrPredicate = createSelector(
|
||||||
() => this.state.sr,
|
() => this.state.targetSrs.mainSr,
|
||||||
() => this.state.mapVdisSrs,
|
() => this.state.targetSrs.mapVdisSrs,
|
||||||
(defaultSr, mapVdisSrs) => sr =>
|
(mainSr, mapVdisSrs) => sr =>
|
||||||
sr !== defaultSr &&
|
|
||||||
isSrWritable(sr) &&
|
isSrWritable(sr) &&
|
||||||
defaultSr.$pool === sr.$pool &&
|
mainSr.$pool === sr.$pool &&
|
||||||
areSrsCompatible(defaultSr, sr) &&
|
areSrsCompatible(mainSr, sr) &&
|
||||||
every(
|
every(
|
||||||
mapVdisSrs,
|
mapVdisSrs,
|
||||||
selectedSr => selectedSr == null || areSrsCompatible(selectedSr, sr)
|
selectedSr => selectedSr == null || areSrsCompatible(selectedSr, sr)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_onChangeDefaultSr = event => {
|
_onSrsChange = props => {
|
||||||
const oldSr = this.state.sr
|
const oldMainSr = this.state.targetSrs.mainSr
|
||||||
const newSr = getEventValue(event)
|
const newMainSr = props.mainSr
|
||||||
|
|
||||||
if (oldSr == null || newSr == null || oldSr.$pool !== newSr.$pool) {
|
const targetSrs = { ...props }
|
||||||
this.setState({
|
|
||||||
mapVdisSrs: {},
|
// This code fixes the incompatibilities between the mapVdisSrs values
|
||||||
})
|
if (oldMainSr !== newMainSr) {
|
||||||
} else if (!newSr.shared) {
|
if (
|
||||||
const mapVdisSrs = { ...this.state.mapVdisSrs }
|
oldMainSr == null ||
|
||||||
forEach(mapVdisSrs, (sr, vdi) => {
|
newMainSr == null ||
|
||||||
if (
|
oldMainSr.$pool !== newMainSr.$pool
|
||||||
sr != null &&
|
) {
|
||||||
newSr !== sr &&
|
targetSrs.mapVdisSrs = {}
|
||||||
sr.$container !== newSr.$container &&
|
} else if (!newMainSr.shared) {
|
||||||
!sr.shared
|
forEach(targetSrs.mapVdisSrs, (sr, vdi) => {
|
||||||
) {
|
if (
|
||||||
delete mapVdisSrs[vdi]
|
sr != null &&
|
||||||
}
|
newMainSr !== sr &&
|
||||||
})
|
sr.$container !== newMainSr.$container &&
|
||||||
this.setState({
|
!sr.shared
|
||||||
mapVdisSrs,
|
) {
|
||||||
})
|
delete targetSrs.mapVdisSrs[vdi]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({ targetSrs })
|
||||||
sr: newSr,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { backups, intl } = this.props
|
const { props, state } = this
|
||||||
const vdis = this.state.backup && this.state.backup.vdis
|
const vdis = state.backup && state.backup.vdis
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -219,15 +214,17 @@ class _ModalBody extends Component {
|
|||||||
onChange={this.linkState('backup')}
|
onChange={this.linkState('backup')}
|
||||||
optionKey='path'
|
optionKey='path'
|
||||||
optionRenderer={backupOptionRenderer}
|
optionRenderer={backupOptionRenderer}
|
||||||
options={backups}
|
options={props.backups}
|
||||||
placeholder={intl.formatMessage(
|
placeholder={props.intl.formatMessage(
|
||||||
messages.importBackupModalSelectBackup
|
messages.importBackupModalSelectBackup
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<ChooseSrForEachVdisModal
|
<ChooseSrForEachVdisModal
|
||||||
|
onChange={this._onSrsChange}
|
||||||
|
srPredicate={this._getSrPredicate()}
|
||||||
|
value={state.targetSrs}
|
||||||
vdis={vdis}
|
vdis={vdis}
|
||||||
onChange={props => this.setState(props)}
|
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Toggle onChange={this.linkState('start')} />{' '}
|
<Toggle onChange={this.linkState('start')} />{' '}
|
||||||
|
Loading…
Reference in New Issue
Block a user