Skip to content

Commit

Permalink
feat(xo-web/VM/import): ability to import XVA VM from URL (#6130)
Browse files Browse the repository at this point in the history
Follow-up of 86e390f
  • Loading branch information
MathieuRA committed Mar 29, 2022
1 parent 5468595 commit cd408c1
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
9 changes: 7 additions & 2 deletions packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
151 changes: 106 additions & 45 deletions packages/xo-web/src/xo-app/vm-import/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -22,6 +24,13 @@ import styles from './index.css'

// ===================================================================

const FILE_TYPES = [
{
label: 'XVA',
value: 'xva',
},
]

const FORMAT_TO_HANDLER = {
ova: parseOvaFile,
xva: noop,
Expand Down Expand Up @@ -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 = () => {
Expand All @@ -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: [],
Expand Down Expand Up @@ -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>
Expand All @@ -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>
)
Expand Down

0 comments on commit cd408c1

Please sign in to comment.