feat(xo-web/VM/import): ability to import XVA VM from URL (#6130)

Follow-up of 86e390f70f
This commit is contained in:
Mathieu
2022-03-29 11:43:11 +02:00
committed by GitHub
parent 546859531b
commit cd408c1687
4 changed files with 117 additions and 47 deletions

View File

@@ -14,6 +14,7 @@
- [Rolling Pool Update] Don't update if some of the hosts are not running
- [VM form] Add link to documentation on secure boot in the Advanced tab (PR [#6146](https://github.com/vatesfr/xen-orchestra/pull/6146))
- [Install patches] Update confirmation messages for patch installation (PR [#6159](https://github.com/vatesfr/xen-orchestra/pull/6159))
- [Import VM] Ability to import a VM from a URL (PR [#6130](https://github.com/vatesfr/xen-orchestra/pull/6130))
### Bug fixes

View File

@@ -1550,8 +1550,11 @@ const messages = {
resourceSetNew: 'New',
// ---- VM import ---
fileType: 'File type:',
fromUrl: 'From URL',
importVmsList: 'Drop OVA or XVA files here to import Virtual Machines.',
noSelectedVms: 'No selected VMs.',
url: 'URL:',
vmImportToPool: 'To Pool:',
vmImportToSr: 'To SR:',
vmsToImport: 'VMs to import',

View File

@@ -1581,7 +1581,7 @@ export const fetchVmStats = (vm, granularity) => _call('vm.stats', { id: resolve
export const getVmsHaValues = () => _call('vm.getHaValues')
export const importVm = async (file, type = 'xva', data = undefined, sr) => {
export const importVm = async (file, type = 'xva', data = undefined, sr, url = undefined) => {
const { name } = file
info(_('startVmImport'), name)
@@ -1598,7 +1598,12 @@ export const importVm = async (file, type = 'xva', data = undefined, sr) => {
}
}
}
const result = await _call('vm.import', { type, data, sr: resolveId(sr) })
const result = await _call('vm.import', { type, data, sr: resolveId(sr), url })
if (url !== undefined) {
// If imported from URL, result is the ID of the created VM
success(_('vmImportSuccess'), name)
return [result]
}
formData.append('file', file)
const res = await post(result.$sendTo, formData)
const json = await res.json()

View File

@@ -10,10 +10,12 @@ import orderBy from 'lodash/orderBy'
import PropTypes from 'prop-types'
import React from 'react'
import { Container, Col, Row } from 'grid'
import { importVms, isSrWritable } from 'xo'
import { SizeInput } from 'form'
import { importVm, importVms, isSrWritable } from 'xo'
import { Select, SizeInput, Toggle } from 'form'
import { createFinder, createGetObject, createGetObjectsOfType, createSelector } from 'selectors'
import { connectStore, formatSize, mapPlus, noop } from 'utils'
import { Input } from 'debounce-input-decorator'
import { SelectNetwork, SelectPool, SelectSr } from 'select-objects'
import parseOvaFile from './ova'
@@ -22,6 +24,13 @@ import styles from './index.css'
// ===================================================================
const FILE_TYPES = [
{
label: 'XVA',
value: 'xva',
},
]
const FORMAT_TO_HANDLER = {
ova: parseOvaFile,
xva: noop,
@@ -198,7 +207,15 @@ const getRedirectionUrl = vms =>
export default class Import extends Component {
constructor(props) {
super(props)
this.state.vms = []
this.state = {
isFromUrl: false,
type: {
label: 'XVA',
value: 'xva',
},
url: '',
vms: [],
}
}
_import = () => {
@@ -217,6 +234,14 @@ export default class Import extends Component {
)
}
_importVmFromUrl = () => {
const { type, url } = this.state
const file = {
name: decodeURIComponent(url.slice(url.lastIndexOf('/') + 1)),
}
return importVm(file, type.value, undefined, this.state.sr, url)
}
_handleDrop = async files => {
this.setState({
vms: [],
@@ -270,11 +295,14 @@ export default class Import extends Component {
}
render() {
const { pool, sr, srPredicate, vms } = this.state
const { isFromUrl, pool, sr, srPredicate, type, url, vms } = this.state
return (
<Container>
<form id='import-form'>
<p>
<Toggle value={isFromUrl} onChange={this.toggleState('isFromUrl')} /> {_('fromUrl')}
</p>
<FormGrid.Row>
<FormGrid.LabelCol>{_('vmImportToPool')}</FormGrid.LabelCol>
<FormGrid.InputCol>
@@ -293,62 +321,95 @@ export default class Import extends Component {
/>
</FormGrid.InputCol>
</FormGrid.Row>
{sr && (
<div>
<Dropzone onDrop={this._handleDrop} message={_('importVmsList')} />
<hr />
<h5>{_('vmsToImport')}</h5>
{vms.length > 0 ? (
<div>
{map(vms, ({ data, error, file, type }, vmIndex) => (
<div key={file.preview} className={styles.vmContainer}>
<strong>{file.name}</strong>
<span className='pull-right'>
<strong>{`(${formatSize(file.size)})`}</strong>
</span>
{!error ? (
data && (
{sr &&
(!isFromUrl ? (
<div>
<Dropzone onDrop={this._handleDrop} message={_('importVmsList')} />
<hr />
<h5>{_('vmsToImport')}</h5>
{vms.length > 0 ? (
<div>
{map(vms, ({ data, error, file, type }, vmIndex) => (
<div key={file.preview} className={styles.vmContainer}>
<strong>{file.name}</strong>
<span className='pull-right'>
<strong>{`(${formatSize(file.size)})`}</strong>
</span>
{!error ? (
data && (
<div>
<hr />
<div className='alert alert-info' role='alert'>
<strong>{_('vmImportFileType', { type })}</strong> {_('vmImportConfigAlert')}
</div>
<VmData {...data} ref={`vm-data-${vmIndex}`} pool={pool} />
</div>
)
) : (
<div>
<hr />
<div className='alert alert-info' role='alert'>
<strong>{_('vmImportFileType', { type })}</strong> {_('vmImportConfigAlert')}
<div className='alert alert-danger' role='alert'>
<strong>{_('vmImportError')}</strong>{' '}
{(error && error.message) || _('noVmImportErrorDescription')}
</div>
<VmData {...data} ref={`vm-data-${vmIndex}`} pool={pool} />
</div>
)
) : (
<div>
<hr />
<div className='alert alert-danger' role='alert'>
<strong>{_('vmImportError')}</strong>{' '}
{(error && error.message) || _('noVmImportErrorDescription')}
</div>
</div>
)}
</div>
))}
)}
</div>
))}
</div>
) : (
<p>{_('noSelectedVms')}</p>
)}
<hr />
<div className='form-group pull-right'>
<ActionButton
btnStyle='primary'
disabled={!vms.length}
className='mr-1'
form='import-form'
handler={this._import}
icon='import'
redirectOnSuccess={getRedirectionUrl}
type='submit'
>
{_('newImport')}
</ActionButton>
<Button onClick={this._handleCleanSelectedVms}>{_('importVmsCleanList')}</Button>
</div>
) : (
<p>{_('noSelectedVms')}</p>
)}
<hr />
<div className='form-group pull-right'>
</div>
) : (
<div>
<FormGrid.Row>
<FormGrid.LabelCol>{_('url')}</FormGrid.LabelCol>
<FormGrid.InputCol>
<Input
className='form-control'
onChange={this.linkState('url')}
placeholder='https://my-company.net/vm.xva'
type='url'
/>
</FormGrid.InputCol>
</FormGrid.Row>
<FormGrid.Row>
<FormGrid.LabelCol>{_('fileType')}</FormGrid.LabelCol>
<FormGrid.InputCol>
<Select onChange={this.linkState('type')} options={FILE_TYPES} required value={type} />
</FormGrid.InputCol>
</FormGrid.Row>
<ActionButton
btnStyle='primary'
disabled={!vms.length}
className='mr-1'
className='mr-1 mt-1'
disabled={isEmpty(url)}
form='import-form'
handler={this._import}
handler={this._importVmFromUrl}
icon='import'
redirectOnSuccess={getRedirectionUrl}
type='submit'
>
{_('newImport')}
</ActionButton>
<Button onClick={this._handleCleanSelectedVms}>{_('importVmsCleanList')}</Button>
</div>
</div>
)}
))}
</form>
</Container>
)