chore(xo-web/VM): move boot order setting from Disks to Advanced (#4975)
See #1523
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
|
||||
> Users must be able to say: “Nice enhancement, I'm eager to test it”
|
||||
|
||||
- [VM] Move boot order setting from Disk tab to Advanced tab [#1523](https://github.com/vatesfr/xen-orchestra/issues/1523#issuecomment-563141573) (PR [#4975](https://github.com/vatesfr/xen-orchestra/pull/4975))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
> Users must be able to say: “I had this issue, happy to know it's fixed”
|
||||
|
||||
168
packages/xo-web/src/xo-app/vm/boot-order.js
Normal file
168
packages/xo-web/src/xo-app/vm/boot-order.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import _ from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import Component from 'base-component'
|
||||
import HTML5Backend from 'react-dnd-html5-backend'
|
||||
import Icon from 'icon'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { DragDropContext, DragSource, DropTarget } from 'react-dnd'
|
||||
import { Toggle } from 'form'
|
||||
import { forEach, map } from 'lodash'
|
||||
import { setVmBootOrder } from 'xo'
|
||||
|
||||
const parseBootOrder = bootOrder => {
|
||||
// FIXME missing translation
|
||||
const bootOptions = {
|
||||
c: 'Hard-Drive',
|
||||
d: 'DVD-Drive',
|
||||
n: 'Network',
|
||||
}
|
||||
const order = []
|
||||
if (bootOrder) {
|
||||
for (const id of bootOrder) {
|
||||
if (id in bootOptions) {
|
||||
order.push({ id, text: bootOptions[id], active: true })
|
||||
delete bootOptions[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
forEach(bootOptions, (text, id) => {
|
||||
order.push({ id, text, active: false })
|
||||
})
|
||||
return order
|
||||
}
|
||||
|
||||
const orderItemSource = {
|
||||
beginDrag: props => ({
|
||||
id: props.id,
|
||||
index: props.index,
|
||||
}),
|
||||
}
|
||||
|
||||
const orderItemTarget = {
|
||||
hover: (props, monitor, component) => {
|
||||
const dragIndex = monitor.getItem().index
|
||||
const hoverIndex = props.index
|
||||
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
props.move(dragIndex, hoverIndex)
|
||||
monitor.getItem().index = hoverIndex
|
||||
},
|
||||
}
|
||||
|
||||
const GRAB_STYLE = { cursor: 'grab' }
|
||||
@DropTarget('orderItem', orderItemTarget, connect => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
}))
|
||||
@DragSource('orderItem', orderItemSource, (connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}))
|
||||
class OrderItem extends Component {
|
||||
static propTypes = {
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
id: PropTypes.any.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
move: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
_toggle = checked => {
|
||||
const { item } = this.props
|
||||
item.active = checked
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item, connectDragSource, connectDropTarget } = this.props
|
||||
return connectDragSource(
|
||||
connectDropTarget(
|
||||
<li className='list-group-item'>
|
||||
<span className='mr-1' style={GRAB_STYLE}>
|
||||
<Icon icon='grab' /> <Icon icon='grab' />
|
||||
</span>
|
||||
{item.text}
|
||||
<span className='pull-right'>
|
||||
<Toggle value={item.active} onChange={this._toggle} />
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DragDropContext(HTML5Backend)
|
||||
export default class BootOrder extends Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
vm: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const { vm } = props
|
||||
const order = parseBootOrder(vm.boot && vm.boot.order)
|
||||
this.state = { order }
|
||||
}
|
||||
|
||||
_moveOrderItem = (dragIndex, hoverIndex) => {
|
||||
const order = this.state.order.slice()
|
||||
const dragItem = order.splice(dragIndex, 1)
|
||||
if (dragItem.length) {
|
||||
order.splice(hoverIndex, 0, dragItem.pop())
|
||||
this.setState({ order })
|
||||
}
|
||||
}
|
||||
|
||||
_reset = () => {
|
||||
const { vm } = this.props
|
||||
const order = parseBootOrder(vm.boot && vm.boot.order)
|
||||
this.setState({ order })
|
||||
}
|
||||
|
||||
_save = () => {
|
||||
const { vm } = this.props
|
||||
const { order: newOrder } = this.state
|
||||
let order = ''
|
||||
forEach(newOrder, item => {
|
||||
item.active && (order += item.id)
|
||||
})
|
||||
return setVmBootOrder(vm, order)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { order } = this.state
|
||||
|
||||
return (
|
||||
<form>
|
||||
<ul className='pl-0'>
|
||||
{map(order, (item, index) => (
|
||||
<OrderItem
|
||||
key={index}
|
||||
index={index}
|
||||
id={item.id}
|
||||
// FIXME missing translation
|
||||
item={item}
|
||||
move={this._moveOrderItem}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<fieldset className='form-inline'>
|
||||
<span className='pull-right'>
|
||||
<ActionButton icon='save' btnStyle='primary' handler={this._save}>
|
||||
{_('saveBootOption')}
|
||||
</ActionButton>{' '}
|
||||
<ActionButton icon='reset' handler={this._reset}>
|
||||
{_('resetBootOption')}
|
||||
</ActionButton>
|
||||
</span>
|
||||
</fieldset>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,8 @@ import {
|
||||
isAdmin,
|
||||
} from 'selectors'
|
||||
|
||||
import BootOrder from './boot-order'
|
||||
|
||||
// Button's height = react-select's height(36 px) + react-select's border-width(1 px) * 2
|
||||
// https://github.com/JedWatson/react-select/blob/916ab0e62fc7394be8e24f22251c399a68de8b1c/less/select.less#L21, L22
|
||||
const SHARE_BUTTON_STYLE = { height: '38px' }
|
||||
@@ -629,6 +631,14 @@ export default class TabAdvanced extends Component {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{vm.virtualizationMode !== 'pv' && (
|
||||
<Row>
|
||||
<Col>
|
||||
<h3>{_('vdiBootOrder')}</h3>
|
||||
<BootOrder vm={vm} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row>
|
||||
<Col>
|
||||
<h3>{_('xenSettingsLabel')}</h3>
|
||||
|
||||
@@ -3,7 +3,6 @@ import ActionButton from 'action-button'
|
||||
import Component from 'base-component'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import defined from '@xen-orchestra/defined'
|
||||
import HTML5Backend from 'react-dnd-html5-backend'
|
||||
import Icon from 'icon'
|
||||
import IsoDevice from 'iso-device'
|
||||
import MigrateVdiModalBody from 'xo/migrate-vdi-modal'
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
getCheckPermissions,
|
||||
isAdmin,
|
||||
} from 'selectors'
|
||||
import { DragDropContext, DragSource, DropTarget } from 'react-dnd'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import {
|
||||
addSubscriptions,
|
||||
@@ -65,7 +63,6 @@ import {
|
||||
isVmRunning,
|
||||
migrateVdi,
|
||||
setBootableVbd,
|
||||
setVmBootOrder,
|
||||
subscribeResourceSets,
|
||||
} from 'xo'
|
||||
|
||||
@@ -202,28 +199,6 @@ const INDIVIDUAL_ACTIONS = [
|
||||
},
|
||||
]
|
||||
|
||||
const parseBootOrder = bootOrder => {
|
||||
// FIXME missing translation
|
||||
const bootOptions = {
|
||||
c: 'Hard-Drive',
|
||||
d: 'DVD-Drive',
|
||||
n: 'Network',
|
||||
}
|
||||
const order = []
|
||||
if (bootOrder) {
|
||||
for (const id of bootOrder) {
|
||||
if (id in bootOptions) {
|
||||
order.push({ id, text: bootOptions[id], active: true })
|
||||
delete bootOptions[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
forEach(bootOptions, (text, id) => {
|
||||
order.push({ id, text, active: false })
|
||||
})
|
||||
return order
|
||||
}
|
||||
|
||||
@injectIntl
|
||||
@addSubscriptions({
|
||||
resourceSets: subscribeResourceSets,
|
||||
@@ -494,137 +469,6 @@ class AttachDisk extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const orderItemSource = {
|
||||
beginDrag: props => ({
|
||||
id: props.id,
|
||||
index: props.index,
|
||||
}),
|
||||
}
|
||||
|
||||
const orderItemTarget = {
|
||||
hover: (props, monitor, component) => {
|
||||
const dragIndex = monitor.getItem().index
|
||||
const hoverIndex = props.index
|
||||
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
props.move(dragIndex, hoverIndex)
|
||||
monitor.getItem().index = hoverIndex
|
||||
},
|
||||
}
|
||||
|
||||
@DropTarget('orderItem', orderItemTarget, connect => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
}))
|
||||
@DragSource('orderItem', orderItemSource, (connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging(),
|
||||
}))
|
||||
class OrderItem extends Component {
|
||||
static propTypes = {
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
id: PropTypes.any.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
move: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
_toggle = checked => {
|
||||
const { item } = this.props
|
||||
item.active = checked
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item, connectDragSource, connectDropTarget } = this.props
|
||||
return connectDragSource(
|
||||
connectDropTarget(
|
||||
<li className='list-group-item'>
|
||||
<Icon icon='grab' /> <Icon icon='grab' /> {item.text}
|
||||
<span className='pull-right'>
|
||||
<Toggle value={item.active} onChange={this._toggle} />
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DragDropContext(HTML5Backend)
|
||||
class BootOrder extends Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
vm: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const { vm } = props
|
||||
const order = parseBootOrder(vm.boot && vm.boot.order)
|
||||
this.state = { order }
|
||||
}
|
||||
|
||||
_moveOrderItem = (dragIndex, hoverIndex) => {
|
||||
const order = this.state.order.slice()
|
||||
const dragItem = order.splice(dragIndex, 1)
|
||||
if (dragItem.length) {
|
||||
order.splice(hoverIndex, 0, dragItem.pop())
|
||||
this.setState({ order })
|
||||
}
|
||||
}
|
||||
|
||||
_reset = () => {
|
||||
const { vm } = this.props
|
||||
const order = parseBootOrder(vm.boot && vm.boot.order)
|
||||
this.setState({ order })
|
||||
}
|
||||
|
||||
_save = () => {
|
||||
const { vm, onClose = noop } = this.props
|
||||
const { order: newOrder } = this.state
|
||||
let order = ''
|
||||
forEach(newOrder, item => {
|
||||
item.active && (order += item.id)
|
||||
})
|
||||
return setVmBootOrder(vm, order).then(onClose)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { order } = this.state
|
||||
|
||||
return (
|
||||
<form>
|
||||
<ul>
|
||||
{map(order, (item, index) => (
|
||||
<OrderItem
|
||||
key={index}
|
||||
index={index}
|
||||
id={item.id}
|
||||
// FIXME missing translation
|
||||
item={item}
|
||||
move={this._moveOrderItem}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<fieldset className='form-inline'>
|
||||
<span className='pull-right'>
|
||||
<ActionButton icon='save' btnStyle='primary' handler={this._save}>
|
||||
{_('saveBootOption')}
|
||||
</ActionButton>{' '}
|
||||
<ActionButton icon='reset' handler={this._reset}>
|
||||
{_('resetBootOption')}
|
||||
</ActionButton>
|
||||
</span>
|
||||
</fieldset>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@connectStore(() => ({
|
||||
checkPermissions: getCheckPermissions,
|
||||
isAdmin,
|
||||
@@ -635,7 +479,6 @@ export default class TabDisks extends Component {
|
||||
super(props)
|
||||
this.state = {
|
||||
attachDisk: false,
|
||||
bootOrder: false,
|
||||
newDisk: false,
|
||||
}
|
||||
}
|
||||
@@ -670,20 +513,11 @@ export default class TabDisks extends Component {
|
||||
this.setState({
|
||||
newDisk: !this.state.newDisk,
|
||||
attachDisk: false,
|
||||
bootOrder: false,
|
||||
})
|
||||
|
||||
_toggleAttachDisk = () =>
|
||||
this.setState({
|
||||
attachDisk: !this.state.attachDisk,
|
||||
bootOrder: false,
|
||||
newDisk: false,
|
||||
})
|
||||
|
||||
_toggleBootOrder = () =>
|
||||
this.setState({
|
||||
bootOrder: !this.state.bootOrder,
|
||||
attachDisk: false,
|
||||
newDisk: false,
|
||||
})
|
||||
|
||||
@@ -806,7 +640,7 @@ export default class TabDisks extends Component {
|
||||
render() {
|
||||
const { allVbds, vm } = this.props
|
||||
|
||||
const { attachDisk, bootOrder, newDisk } = this.state
|
||||
const { attachDisk, newDisk } = this.state
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@@ -826,14 +660,6 @@ export default class TabDisks extends Component {
|
||||
labelId='vdiAttachDevice'
|
||||
/>
|
||||
)}
|
||||
{vm.virtualizationMode !== 'pv' && (
|
||||
<TabButton
|
||||
btnStyle={bootOrder ? 'info' : 'primary'}
|
||||
handler={this._toggleBootOrder}
|
||||
icon='sort'
|
||||
labelId='vdiBootOrder'
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
@@ -859,12 +685,6 @@ export default class TabDisks extends Component {
|
||||
<hr />
|
||||
</div>
|
||||
)}
|
||||
{bootOrder && (
|
||||
<div>
|
||||
<BootOrder vm={vm} onClose={this._toggleBootOrder} />
|
||||
<hr />
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
|
||||
Reference in New Issue
Block a user