feat(xo-web/vm/disks): use SortedTable (#2429)

See #2416
This commit is contained in:
Rajaa.BARHTAOUI 2018-03-27 11:13:05 +02:00 committed by Pierre Donias
parent 0d718bd632
commit 664d648435
4 changed files with 174 additions and 132 deletions

View File

@ -1,8 +1,7 @@
import Component from 'base-component' import Component from 'base-component'
import forEach from 'lodash/forEach'
import React from 'react' import React from 'react'
import remove from 'lodash/remove'
import { Shortcuts as ReactShortcuts } from 'react-shortcuts' import { Shortcuts as ReactShortcuts } from 'react-shortcuts'
import { forEach, remove } from 'lodash'
let enabled = true let enabled = true
const instances = [] const instances = []
@ -29,7 +28,20 @@ export default class Shortcuts extends Component {
remove(instances, this) 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 () { render () {
return enabled ? <ReactShortcuts {...this.props} /> : null return enabled ? (
<ReactShortcuts {...this.props} handler={this._handler} />
) : null
} }
} }

View File

@ -830,8 +830,8 @@ export default class SortedTable extends Component {
{shortcutsTarget !== undefined && ( {shortcutsTarget !== undefined && (
<Shortcuts <Shortcuts
handler={this._getShortcutsHandler()} handler={this._getShortcutsHandler()}
isolate
name='SortedTable' name='SortedTable'
stopPropagation
targetNodeSelector={shortcutsTarget} targetNodeSelector={shortcutsTarget}
/> />
)} )}

View File

@ -912,10 +912,10 @@ export default class Home extends Component {
return ( return (
<Page header={this._renderHeader()}> <Page header={this._renderHeader()}>
<Shortcuts <Shortcuts
name='Home'
handler={this._getShortcutsHandler()} handler={this._getShortcutsHandler()}
isolate
name='Home'
targetNodeSelector='body' targetNodeSelector='body'
stopPropagation={false}
/> />
<div> <div>
<div className={styles.itemContainer}> <div className={styles.itemContainer}>

View File

@ -1,6 +1,5 @@
import _, { messages } from 'intl' import _, { messages } from 'intl'
import ActionButton from 'action-button' import ActionButton from 'action-button'
import ActionRowButton from 'action-row-button'
import Component from 'base-component' import Component from 'base-component'
import HTML5Backend from 'react-dnd-html5-backend' import HTML5Backend from 'react-dnd-html5-backend'
import Icon from 'icon' import Icon from 'icon'
@ -10,8 +9,8 @@ import propTypes from 'prop-types-decorator'
import React from 'react' import React from 'react'
import SingleLineRow from 'single-line-row' import SingleLineRow from 'single-line-row'
import StateButton from 'state-button' import StateButton from 'state-button'
import SortedTable from 'sorted-table'
import TabButton from 'tab-button' import TabButton from 'tab-button'
import Tooltip from 'tooltip'
import { Container, Row, Col } from 'grid' import { Container, Row, Col } from 'grid'
import { import {
createSelector, createSelector,
@ -33,13 +32,15 @@ import { SizeInput, Toggle } from 'form'
import { XoSelect, Size, Text } from 'editable' import { XoSelect, Size, Text } from 'editable'
import { confirm } from 'modal' import { confirm } from 'modal'
import { error } from 'notification' import { error } from 'notification'
import { forEach, get, isEmpty, map, some } from 'lodash' import { filter, find, forEach, get, map, mapValues, some } from 'lodash'
import { import {
attachDiskToVm, attachDiskToVm,
createDisk, createDisk,
connectVbd, connectVbd,
deleteVbd, deleteVbd,
deleteVbds,
deleteVdi, deleteVdi,
deleteVdis,
disconnectVbd, disconnectVbd,
editVdi, editVdi,
isSrWritable, isSrWritable,
@ -50,6 +51,132 @@ import {
subscribeResourceSets, subscribeResourceSets,
} from 'xo' } from 'xo'
const COLUMNS_VM_PV = [
{
itemRenderer: vdi => (
<Text
value={vdi.name_label}
onChange={value => editVdi(vdi, { name_label: value })}
/>
),
name: _('vdiNameLabel'),
sortCriteria: 'name_label',
default: true,
},
{
itemRenderer: vdi => (
<Text
value={vdi.name_description}
onChange={value => editVdi(vdi, { name_description: value })}
/>
),
name: _('vdiNameDescription'),
sortCriteria: 'name_description',
},
{
itemRenderer: vdi => (
<Size
value={vdi.size || null}
onChange={size => editVdi(vdi, { size })}
/>
),
name: _('vdiSize'),
sortCriteria: 'size',
},
{
itemRenderer: (vdi, userData) => {
const sr = userData.srs[vdi.$SR]
return (
sr !== undefined && (
<XoSelect
labelProp='name_label'
onChange={sr => migrateVdi(vdi, sr)}
predicate={sr => sr.$pool === userData.vm.$pool && isSrWritable(sr)}
useLongClick
value={sr}
xoType='SR'
>
<Link to={`/srs/${sr.id}`}>{sr.name_label}</Link>
</XoSelect>
)
)
},
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 (
<Toggle
onChange={bootable => setBootableVbd(vbd, bootable)}
value={vbd.bootable}
/>
)
},
name: _('vbdBootableStatus'),
id: 'vbdBootableStatus',
},
{
itemRenderer: (vdi, userData) => {
const vbd = userData.vbdsByVdi[vdi.id]
return (
<StateButton
disabledLabel={_('vbdStatusDisconnected')}
disabledHandler={connectVbd}
disabledTooltip={_('vbdConnect')}
enabledLabel={_('vbdStatusConnected')}
enabledHandler={disconnectVbd}
enabledTooltip={_('vbdDisconnect')}
disabled={!(vbd.attached || isVmRunning(userData.vm))}
handlerParam={vbd}
state={vbd.attached}
/>
)
},
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 => { const parseBootOrder = bootOrder => {
// FIXME missing translation // FIXME missing translation
const bootOptions = { const bootOptions = {
@ -516,6 +643,21 @@ export default class TabDisks extends Component {
isAdmin || (resourceSet == null && isVmAdmin) 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 () { render () {
const { srs, vbds, vdis, vm } = this.props const { srs, vbds, vdis, vm } = this.props
@ -577,129 +719,17 @@ export default class TabDisks extends Component {
</Row> </Row>
<Row> <Row>
<Col> <Col>
{!isEmpty(vbds) ? ( <SortedTable
<table className='table'> actions={ACTIONS}
<thead className='thead-default'> collection={vdis}
<tr> columns={vm.virtualizationMode === 'pv' ? COLUMNS_VM_PV : COLUMNS}
<th>{_('vdiNameLabel')}</th> data-srs={srs}
<th>{_('vdiNameDescription')}</th> data-vbdsByVdi={this._getVbdsByVdi()}
<th>{_('vdiSize')}</th> data-vm={vm}
<th>{_('vdiSr')}</th> individualActions={this.individualActions}
{vm.virtualizationMode === 'pv' && ( shortcutsTarget='body'
<th>{_('vbdBootableStatus')}</th> stateUrlParam='s'
)} />
<th>{_('vbdStatus')}</th>
<th className='text-xs-right'>{_('vbdAction')}</th>
</tr>
</thead>
<tbody>
{map(vbds, vbd => {
const vdi = vdis[vbd.VDI]
if (vbd.is_cd_drive || !vdi) {
return
}
const sr = srs[vdi.$SR]
return (
<tr key={vbd.id}>
<td>
<Text
value={vdi.name_label}
onChange={value =>
editVdi(vdi, { name_label: value })
}
/>
</td>
<td>
<Text
value={vdi.name_description}
onChange={value =>
editVdi(vdi, { name_description: value })
}
/>
</td>
<td>
<Size
value={vdi.size || null}
onChange={size => editVdi(vdi, { size })}
/>
</td>
<td>
{' '}
{sr && (
<XoSelect
onChange={sr => migrateVdi(vdi, sr)}
xoType='SR'
predicate={sr =>
sr.$pool === vm.$pool && isSrWritable(sr)
}
labelProp='name_label'
value={sr}
useLongClick
>
<Link to={`/srs/${sr.id}`}>{sr.name_label}</Link>
</XoSelect>
)}
</td>
{vm.virtualizationMode === 'pv' && (
<td>
<Toggle
value={vbd.bootable}
onChange={bootable =>
setBootableVbd(vbd, bootable)
}
/>
</td>
)}
<td>
<StateButton
disabledLabel={_('vbdStatusDisconnected')}
disabledHandler={connectVbd}
disabledTooltip={_('vbdConnect')}
enabledLabel={_('vbdStatusConnected')}
enabledHandler={disconnectVbd}
enabledTooltip={_('vbdDisconnect')}
disabled={!(vbd.attached || isVmRunning(vm))}
handlerParam={vbd}
state={vbd.attached}
/>
</td>
<td className='text-xs-right'>
<Tooltip content={_('vdiMigrate')}>
<ActionRowButton
icon='vdi-migrate'
handler={this._migrateVdi}
handlerParam={vdi}
/>
</Tooltip>
{!vbd.attached && (
<span>
<Tooltip content={_('vdiForget')}>
<ActionRowButton
icon='vdi-forget'
handler={deleteVbd}
handlerParam={vbd}
/>
</Tooltip>
<Tooltip content={_('vdiRemove')}>
<ActionRowButton
icon='vdi-remove'
handler={deleteVdi}
handlerParam={vdi}
/>
</Tooltip>
</span>
)}
</td>
</tr>
)
})}
</tbody>
</table>
) : (
<h4 className='text-xs-center'>{_('vbdNoVbd')}</h4>
)}
</Col> </Col>
</Row> </Row>
<Row> <Row>