parent
0d718bd632
commit
664d648435
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -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}>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user