From 664d648435ce2691df873102974e8b2e86487f11 Mon Sep 17 00:00:00 2001 From: "Rajaa.BARHTAOUI" Date: Tue, 27 Mar 2018 11:13:05 +0200 Subject: [PATCH] feat(xo-web/vm/disks): use SortedTable (#2429) See #2416 --- packages/xo-web/src/common/shortcuts.js | 18 +- .../xo-web/src/common/sorted-table/index.js | 2 +- packages/xo-web/src/xo-app/home/index.js | 4 +- packages/xo-web/src/xo-app/vm/tab-disks.js | 282 ++++++++++-------- 4 files changed, 174 insertions(+), 132 deletions(-) diff --git a/packages/xo-web/src/common/shortcuts.js b/packages/xo-web/src/common/shortcuts.js index 84a23b167..9563b8cd0 100644 --- a/packages/xo-web/src/common/shortcuts.js +++ b/packages/xo-web/src/common/shortcuts.js @@ -1,8 +1,7 @@ import Component from 'base-component' -import forEach from 'lodash/forEach' import React from 'react' -import remove from 'lodash/remove' import { Shortcuts as ReactShortcuts } from 'react-shortcuts' +import { forEach, remove } from 'lodash' let enabled = true const instances = [] @@ -29,7 +28,20 @@ export default class Shortcuts extends Component { remove(instances, this) } + _handler = (command, event) => { + // When an input is focused, shortcuts are disabled by default *except* for + // non-printable keys (Esc, Enter, ...) but we want to disable them as well + // https://github.com/avocode/react-shortcuts/issues/13#issuecomment-255868423 + if (event.target.tagName === 'INPUT') { + return + } + + this.props.handler(command, event) + } + render () { - return enabled ? : null + return enabled ? ( + + ) : null } } diff --git a/packages/xo-web/src/common/sorted-table/index.js b/packages/xo-web/src/common/sorted-table/index.js index eeca8d106..1b773c456 100644 --- a/packages/xo-web/src/common/sorted-table/index.js +++ b/packages/xo-web/src/common/sorted-table/index.js @@ -830,8 +830,8 @@ export default class SortedTable extends Component { {shortcutsTarget !== undefined && ( )} diff --git a/packages/xo-web/src/xo-app/home/index.js b/packages/xo-web/src/xo-app/home/index.js index 5ae5bce7e..cbc26b764 100644 --- a/packages/xo-web/src/xo-app/home/index.js +++ b/packages/xo-web/src/xo-app/home/index.js @@ -912,10 +912,10 @@ export default class Home extends Component { return (
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) ? ( - - - - - - - - {vm.virtualizationMode === 'pv' && ( - - )} - - - - - - {map(vbds, vbd => { - const vdi = vdis[vbd.VDI] - if (vbd.is_cd_drive || !vdi) { - return - } - - const sr = srs[vdi.$SR] - - return ( - - - - - - {vm.virtualizationMode === 'pv' && ( - - )} - - - - ) - })} - -
{_('vdiNameLabel')}{_('vdiNameDescription')}{_('vdiSize')}{_('vdiSr')}{_('vbdBootableStatus')}{_('vbdStatus')}{_('vbdAction')}
- - 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} - - )} - - - setBootableVbd(vbd, bootable) - } - /> - - - - - - - {!vbd.attached && ( - - - - - - - - - )} -
- ) : ( -

{_('vbdNoVbd')}

- )} +