parent
d99555a4a8
commit
53477be12d
@ -6,6 +6,7 @@
|
||||
|
||||
- Export VDI content [#2432](https://github.com/vatesfr/xen-orchestra/issues/2432) (PR [#3194](https://github.com/vatesfr/xen-orchestra/pull/3194))
|
||||
- Search syntax support wildcard (`*`) and regular expressions [#3190](https://github.com/vatesfr/xen-orchestra/issues/3190) (PRs [#3198](https://github.com/vatesfr/xen-orchestra/pull/3198) & [#3199](https://github.com/vatesfr/xen-orchestra/pull/3199))
|
||||
- Import VDI content [#2432](https://github.com/vatesfr/xen-orchestra/issues/2432) (PR [#3216](https://github.com/vatesfr/xen-orchestra/pull/3216))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
@ -8,16 +8,18 @@ import styles from './index.css'
|
||||
@propTypes({
|
||||
onDrop: propTypes.func,
|
||||
message: propTypes.node,
|
||||
multiple: propTypes.bool,
|
||||
})
|
||||
export default class Dropzone extends Component {
|
||||
render () {
|
||||
const { onDrop, message } = this.props
|
||||
const { onDrop, message, multiple } = this.props
|
||||
|
||||
return (
|
||||
<ReactDropzone
|
||||
onDrop={onDrop}
|
||||
className={styles.dropzone}
|
||||
activeClassName={styles.activeDropzone}
|
||||
className={styles.dropzone}
|
||||
multiple={multiple}
|
||||
onDrop={onDrop}
|
||||
>
|
||||
<div className={styles.dropzoneText}>{message}</div>
|
||||
</ReactDropzone>
|
||||
|
@ -918,6 +918,9 @@ const messages = {
|
||||
deleteSelectedVdis: 'Delete selected VDIs',
|
||||
deleteSelectedVdi: 'Delete selected VDI',
|
||||
exportVdi: 'Export VDI content',
|
||||
importVdi: 'Import VDI content',
|
||||
importVdiNoFile: 'No file selected',
|
||||
selectVdiMessage: 'Drop VHD file here',
|
||||
useQuotaWarning:
|
||||
'Creating this disk will use the disk space quota from the resource set {resourceSet} ({spaceLeft} left)',
|
||||
notEnoughSpaceInResourceSet:
|
||||
@ -1242,8 +1245,11 @@ const messages = {
|
||||
importVmsCleanList: 'Reset',
|
||||
vmImportSuccess: 'VM import success',
|
||||
vmImportFailed: 'VM import failed',
|
||||
vdiImportSuccess: 'VDI import success',
|
||||
vdiImportFailed: 'VDI import failed',
|
||||
setVmFailed: 'Error on setting the VM: {vm}',
|
||||
startVmImport: 'Import starting…',
|
||||
startVdiImport: 'VDI import starting…',
|
||||
startVmExport: 'Export starting…',
|
||||
startVdiExport: 'VDI export starting…',
|
||||
nCpus: 'N CPUs',
|
||||
|
21
packages/xo-web/src/common/xo/import-vdi-modal/index.js
Normal file
21
packages/xo-web/src/common/xo/import-vdi-modal/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
import _ from 'intl'
|
||||
import Component from 'base-component'
|
||||
import Dropzone from 'dropzone'
|
||||
import React from 'react'
|
||||
|
||||
export default class ImportVdiModalBody extends Component {
|
||||
get value () {
|
||||
return this.state.file
|
||||
}
|
||||
|
||||
render () {
|
||||
const { file } = this.state
|
||||
return (
|
||||
<Dropzone
|
||||
onDrop={this.linkState('file', '0')}
|
||||
message={file === undefined ? _('selectVdiMessage') : file.name}
|
||||
multiple={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
@ -1256,6 +1256,38 @@ export const importVm = (file, type = 'xva', data = undefined, sr) => {
|
||||
)
|
||||
}
|
||||
|
||||
import ImportVdiModalBody from './import-vdi-modal' // eslint-disable-line import/first
|
||||
export const importVdi = async vdi => {
|
||||
const file = await confirm({
|
||||
body: <ImportVdiModalBody />,
|
||||
icon: 'import',
|
||||
title: _('importVdi'),
|
||||
})
|
||||
|
||||
if (file === undefined) {
|
||||
error(_('importVdi'), _('importVdiNoFile'))
|
||||
return
|
||||
}
|
||||
|
||||
const { name } = file
|
||||
info(_('startVdiImport'), name)
|
||||
|
||||
return _call('disk.importContent', { id: resolveId(vdi) }).then(
|
||||
({ $sendTo }) =>
|
||||
post($sendTo, file)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw res.status
|
||||
}
|
||||
success(_('vdiImportSuccess'), name)
|
||||
return res.json().then(body => body.result)
|
||||
})
|
||||
.catch(err => {
|
||||
error(_('vdiImportFailed'), err)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const importVms = (vms, sr) =>
|
||||
Promise.all(
|
||||
map(vms, ({ file, type, data }) =>
|
||||
|
@ -16,7 +16,7 @@ import { Text } from 'editable'
|
||||
import { SizeInput, Toggle } from 'form'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { connectStore, formatSize, noop } from 'utils'
|
||||
import { concat, isEmpty, map, some } from 'lodash'
|
||||
import { concat, groupBy, isEmpty, map, mapValues, pick, some } from 'lodash'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createSelector,
|
||||
@ -31,6 +31,7 @@ import {
|
||||
disconnectVbd,
|
||||
editVdi,
|
||||
exportVdi,
|
||||
importVdi,
|
||||
isVmRunning,
|
||||
} from 'xo'
|
||||
|
||||
@ -191,6 +192,12 @@ const INDIVIDUAL_ACTIONS = [
|
||||
icon: 'export',
|
||||
label: _('exportVdi'),
|
||||
},
|
||||
{
|
||||
disabled: ({ id }, { isVdiAttached }) => isVdiAttached[id],
|
||||
handler: importVdi,
|
||||
icon: 'import',
|
||||
label: _('importVdi'),
|
||||
},
|
||||
{
|
||||
handler: vdi => copy(vdi.uuid),
|
||||
icon: 'clipboard',
|
||||
@ -276,6 +283,7 @@ class NewDisk extends Component {
|
||||
|
||||
@connectStore(() => ({
|
||||
checkPermissions: getCheckPermissions,
|
||||
vbds: createGetObjectsOfType('VBD'),
|
||||
}))
|
||||
export default class SrDisks extends Component {
|
||||
_closeNewDiskForm = () => this.setState({ newDisk: false })
|
||||
@ -293,6 +301,15 @@ export default class SrDisks extends Component {
|
||||
(check, id) => check(id, 'administrate')
|
||||
)
|
||||
|
||||
_getIsVdiAttached = createSelector(
|
||||
createSelector(
|
||||
() => this.props.vbds,
|
||||
() => map(this.props.vdis, 'id'),
|
||||
(vbds, vdis) => pick(groupBy(vbds, 'VDI'), vdis)
|
||||
),
|
||||
vbdsByVdi => mapValues(vbdsByVdi, vbds => some(vbds, 'attached'))
|
||||
)
|
||||
|
||||
render () {
|
||||
const vdis = this._getAllVdis()
|
||||
const { newDisk } = this.state
|
||||
@ -325,6 +342,7 @@ export default class SrDisks extends Component {
|
||||
<SortedTable
|
||||
collection={vdis}
|
||||
columns={COLUMNS}
|
||||
data-isVdiAttached={this._getIsVdiAttached()}
|
||||
defaultFilter='filterOnlyManaged'
|
||||
filters={FILTERS}
|
||||
groupedActions={GROUPED_ACTIONS}
|
||||
|
@ -14,6 +14,7 @@ import SortedTable from 'sorted-table'
|
||||
import TabButton from 'tab-button'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import {
|
||||
createGetObjectsOfType,
|
||||
createSelector,
|
||||
createFinder,
|
||||
getCheckPermissions,
|
||||
@ -33,7 +34,17 @@ import { SizeInput, Toggle } from 'form'
|
||||
import { XoSelect, Size, Text } from 'editable'
|
||||
import { confirm } from 'modal'
|
||||
import { error } from 'notification'
|
||||
import { filter, find, forEach, get, map, mapValues, some } from 'lodash'
|
||||
import {
|
||||
filter,
|
||||
find,
|
||||
forEach,
|
||||
get,
|
||||
groupBy,
|
||||
map,
|
||||
mapValues,
|
||||
pick,
|
||||
some,
|
||||
} from 'lodash'
|
||||
import {
|
||||
attachDiskToVm,
|
||||
createDisk,
|
||||
@ -45,6 +56,7 @@ import {
|
||||
disconnectVbd,
|
||||
editVdi,
|
||||
exportVdi,
|
||||
importVdi,
|
||||
isSrWritable,
|
||||
isVmRunning,
|
||||
migrateVdi,
|
||||
@ -585,6 +597,7 @@ class MigrateVdiModalBody extends Component {
|
||||
@connectStore(() => ({
|
||||
checkPermissions: getCheckPermissions,
|
||||
isAdmin,
|
||||
allVbds: createGetObjectsOfType('VBD'),
|
||||
}))
|
||||
export default class TabDisks extends Component {
|
||||
constructor (props) {
|
||||
@ -652,12 +665,27 @@ export default class TabDisks extends Component {
|
||||
(vdis, vbds, vm) => mapValues(vdis, vdi => find(vbds, { VDI: vdi.id }))
|
||||
)
|
||||
|
||||
_getIsVdiAttached = createSelector(
|
||||
createSelector(
|
||||
() => this.props.allVbds,
|
||||
() => Object.keys(this.props.vdis),
|
||||
(vbds, vdis) => pick(groupBy(vbds, 'VDI'), vdis)
|
||||
),
|
||||
vbdsByVdi => mapValues(vbdsByVdi, vbds => some(vbds, 'attached'))
|
||||
)
|
||||
|
||||
individualActions = [
|
||||
{
|
||||
handler: exportVdi,
|
||||
icon: 'export',
|
||||
label: _('exportVdi'),
|
||||
},
|
||||
{
|
||||
disabled: ({ id }, { isVdiAttached }) => isVdiAttached[id],
|
||||
handler: importVdi,
|
||||
icon: 'import',
|
||||
label: _('importVdi'),
|
||||
},
|
||||
{
|
||||
handler: this._migrateVdi,
|
||||
icon: 'vdi-migrate',
|
||||
@ -735,6 +763,7 @@ export default class TabDisks extends Component {
|
||||
actions={ACTIONS}
|
||||
collection={vdis}
|
||||
columns={vm.virtualizationMode === 'pv' ? COLUMNS_VM_PV : COLUMNS}
|
||||
data-isVdiAttached={this._getIsVdiAttached()}
|
||||
data-srs={srs}
|
||||
data-vbdsByVdi={this._getVbdsByVdi()}
|
||||
data-vm={vm}
|
||||
|
Loading…
Reference in New Issue
Block a user