diff --git a/packages/xo-web/src/xo-app/vm/tab-disks.js b/packages/xo-web/src/xo-app/vm/tab-disks.js
index 787edbff2..b8d5fe66e 100644
--- a/packages/xo-web/src/xo-app/vm/tab-disks.js
+++ b/packages/xo-web/src/xo-app/vm/tab-disks.js
@@ -1,6 +1,5 @@
import _, { messages } from 'intl'
import ActionButton from 'action-button'
-import ActionRowButton from 'action-row-button'
import Component from 'base-component'
import HTML5Backend from 'react-dnd-html5-backend'
import Icon from 'icon'
@@ -10,8 +9,8 @@ import propTypes from 'prop-types-decorator'
import React from 'react'
import SingleLineRow from 'single-line-row'
import StateButton from 'state-button'
+import SortedTable from 'sorted-table'
import TabButton from 'tab-button'
-import Tooltip from 'tooltip'
import { Container, Row, Col } from 'grid'
import {
createSelector,
@@ -33,13 +32,15 @@ import { SizeInput, Toggle } from 'form'
import { XoSelect, Size, Text } from 'editable'
import { confirm } from 'modal'
import { error } from 'notification'
-import { forEach, get, isEmpty, map, some } from 'lodash'
+import { filter, find, forEach, get, map, mapValues, some } from 'lodash'
import {
attachDiskToVm,
createDisk,
connectVbd,
deleteVbd,
+ deleteVbds,
deleteVdi,
+ deleteVdis,
disconnectVbd,
editVdi,
isSrWritable,
@@ -50,6 +51,132 @@ import {
subscribeResourceSets,
} from 'xo'
+const COLUMNS_VM_PV = [
+ {
+ itemRenderer: vdi => (
+
editVdi(vdi, { name_label: value })}
+ />
+ ),
+ name: _('vdiNameLabel'),
+ sortCriteria: 'name_label',
+ default: true,
+ },
+ {
+ itemRenderer: vdi => (
+ editVdi(vdi, { name_description: value })}
+ />
+ ),
+ name: _('vdiNameDescription'),
+ sortCriteria: 'name_description',
+ },
+ {
+ itemRenderer: vdi => (
+ editVdi(vdi, { size })}
+ />
+ ),
+ name: _('vdiSize'),
+ sortCriteria: 'size',
+ },
+ {
+ itemRenderer: (vdi, userData) => {
+ const sr = userData.srs[vdi.$SR]
+ return (
+ sr !== undefined && (
+ migrateVdi(vdi, sr)}
+ predicate={sr => sr.$pool === userData.vm.$pool && isSrWritable(sr)}
+ useLongClick
+ value={sr}
+ xoType='SR'
+ >
+ {sr.name_label}
+
+ )
+ )
+ },
+ name: _('vdiSr'),
+ sortCriteria: (vdi, userData) => {
+ const sr = userData.srs[vdi.$SR]
+ return sr !== undefined && sr.name_label
+ },
+ },
+ {
+ itemRenderer: (vdi, userData) => {
+ const vbd = userData.vbdsByVdi[vdi.id]
+ return (
+ setBootableVbd(vbd, bootable)}
+ value={vbd.bootable}
+ />
+ )
+ },
+ name: _('vbdBootableStatus'),
+ id: 'vbdBootableStatus',
+ },
+ {
+ itemRenderer: (vdi, userData) => {
+ const vbd = userData.vbdsByVdi[vdi.id]
+ return (
+
+ )
+ },
+ name: _('vbdStatus'),
+ },
+]
+
+const COLUMNS = filter(COLUMNS_VM_PV, col => col.id !== 'vbdBootableStatus')
+
+const ACTIONS = [
+ {
+ disabled: (selectedItems, userData) =>
+ some(map(selectedItems, vdi => userData.vbdsByVdi[vdi.id]), 'attached'),
+ handler: (selectedItems, userData) =>
+ deleteVbds(map(selectedItems, vdi => userData.vbdsByVdi[vdi.id])),
+ individualDisabled: (vdi, userData) => {
+ const vbd = userData.vbdsByVdi[vdi.id]
+ return vbd !== undefined && vbd.attached
+ },
+ individualHandler: (vdi, userData) => {
+ const vbd = userData.vbdsByVdi[vdi.id]
+ return vbd !== undefined && deleteVbd(vbd)
+ },
+ icon: 'vdi-forget',
+ label: _('vdiForget'),
+ level: 'danger',
+ },
+ {
+ disabled: (selectedItems, userData) =>
+ some(map(selectedItems, vdi => userData.vbdsByVdi[vdi.id]), 'attached'),
+ handler: deleteVdis,
+ individualDisabled: (vdi, userData) => {
+ const vbd = userData.vbdsByVdi[vdi.id]
+ return vbd !== undefined && vbd.attached
+ },
+ individualHandler: deleteVdi,
+ individualLabel: _('vdiRemove'),
+ icon: 'vdi-remove',
+ label: _('deleteSelectedVdis'),
+ level: 'danger',
+ },
+]
+
const parseBootOrder = bootOrder => {
// FIXME missing translation
const bootOptions = {
@@ -516,6 +643,21 @@ export default class TabDisks extends Component {
isAdmin || (resourceSet == null && isVmAdmin)
)
+ _getVbdsByVdi = createSelector(
+ () => this.props.vdis,
+ () => this.props.vbds,
+ () => this.props.vm,
+ (vdis, vbds, vm) => mapValues(vdis, vdi => find(vbds, { VDI: vdi.id }))
+ )
+
+ individualActions = [
+ {
+ handler: this._migrateVdi,
+ icon: 'vdi-migrate',
+ label: _('vdiMigrate'),
+ },
+ ]
+
render () {
const { srs, vbds, vdis, vm } = this.props
@@ -577,129 +719,17 @@ export default class TabDisks extends Component {
- {!isEmpty(vbds) ? (
-
-
-
- {_('vdiNameLabel')} |
- {_('vdiNameDescription')} |
- {_('vdiSize')} |
- {_('vdiSr')} |
- {vm.virtualizationMode === 'pv' && (
- {_('vbdBootableStatus')} |
- )}
- {_('vbdStatus')} |
- {_('vbdAction')} |
-
-
-
- {map(vbds, vbd => {
- const vdi = vdis[vbd.VDI]
- if (vbd.is_cd_drive || !vdi) {
- return
- }
-
- const sr = srs[vdi.$SR]
-
- return (
-
-
-
- editVdi(vdi, { name_label: value })
- }
- />
- |
-
-
- editVdi(vdi, { name_description: value })
- }
- />
- |
-
- editVdi(vdi, { size })}
- />
- |
-
- {' '}
- {sr && (
- migrateVdi(vdi, sr)}
- xoType='SR'
- predicate={sr =>
- sr.$pool === vm.$pool && isSrWritable(sr)
- }
- labelProp='name_label'
- value={sr}
- useLongClick
- >
- {sr.name_label}
-
- )}
- |
- {vm.virtualizationMode === 'pv' && (
-
-
- setBootableVbd(vbd, bootable)
- }
- />
- |
- )}
-
-
- |
-
-
-
-
- {!vbd.attached && (
-
-
-
-
-
-
-
-
- )}
- |
-
- )
- })}
-
-
- ) : (
- {_('vbdNoVbd')}
- )}
+