parent
dc34f3478d
commit
7a2a88b7ad
@ -5,6 +5,7 @@
|
||||
- [VM migration] Display same-pool hosts first in the selector [#3262](https://github.com/vatesfr/xen-orchestra/issues/3262) (PR [#3890](https://github.com/vatesfr/xen-orchestra/pull/3890))
|
||||
- [Home/VM] Sort VM by start time [#3955](https://github.com/vatesfr/xen-orchestra/issues/3955) (PR [#3970](https://github.com/vatesfr/xen-orchestra/pull/3970))
|
||||
- [Editable fields] Unfocusing (clicking outside) submits the change instead of canceling (PR [#3980](https://github.com/vatesfr/xen-orchestra/pull/3980))
|
||||
- [Network] Dedicated page for network creation [#3895](https://github.com/vatesfr/xen-orchestra/issues/3895) (PR [#3906](https://github.com/vatesfr/xen-orchestra/pull/3906))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
|
@ -101,6 +101,7 @@ const messages = {
|
||||
newMenu: 'New',
|
||||
taskMenu: 'Tasks',
|
||||
taskPage: 'Tasks',
|
||||
newNetworkPage: 'Network',
|
||||
newVmPage: 'VM',
|
||||
newSrPage: 'Storage',
|
||||
newServerPage: 'Server',
|
||||
@ -563,6 +564,10 @@ const messages = {
|
||||
newSrNfsOptions: 'Comma delimited NFS options',
|
||||
reattachNewSrTooltip: 'Reattach SR',
|
||||
|
||||
// ------ New Newtork -----
|
||||
createNewNetworkNoPermission: 'You have no permission to create a network',
|
||||
createNewNetworkOn: 'Create a new network on {select}',
|
||||
|
||||
// ----- Acls, Users, Groups ------
|
||||
subjectName: 'Users/Groups',
|
||||
objectName: 'Object',
|
||||
@ -1690,7 +1695,6 @@ const messages = {
|
||||
|
||||
// ----- Network -----
|
||||
newNetworkCreate: 'Create network',
|
||||
newBondedNetworkCreate: 'Create bonded network',
|
||||
newNetworkInterface: 'Interface',
|
||||
newNetworkName: 'Name',
|
||||
newNetworkDescription: 'Description',
|
||||
@ -1698,13 +1702,14 @@ const messages = {
|
||||
newNetworkDefaultVlan: 'No VLAN if empty',
|
||||
newNetworkMtu: 'MTU',
|
||||
newNetworkDefaultMtu: 'Default: 1500',
|
||||
newNetworkNoNameErrorTitle: 'Name required',
|
||||
newNetworkNoNameErrorMessage: 'A name is required to create a network',
|
||||
newNetworkBondMode: 'Bond mode',
|
||||
newNetworkInfo: 'Info',
|
||||
newNetworkType: 'Type',
|
||||
deleteNetwork: 'Delete network',
|
||||
deleteNetworkConfirm: 'Are you sure you want to delete this network?',
|
||||
networkInUse: 'This network is currently in use',
|
||||
pillBonded: 'Bonded',
|
||||
bondedNetwork: 'Bonded network',
|
||||
|
||||
// ----- Add host -----
|
||||
addHostSelectHost: 'Host',
|
||||
|
@ -1,113 +0,0 @@
|
||||
import Component from 'base-component'
|
||||
import map from 'lodash/map'
|
||||
import React from 'react'
|
||||
import { createGetObject, createSelector } from 'selectors'
|
||||
import { getBondModes } from 'xo'
|
||||
import { injectIntl } from 'react-intl'
|
||||
|
||||
import _, { messages } from '../../intl'
|
||||
import { Col } from '../../grid'
|
||||
import { connectStore } from '../../utils'
|
||||
import { SelectPif } from '../../select-objects'
|
||||
import SingleLineRow from '../../single-line-row'
|
||||
|
||||
@connectStore(
|
||||
() => ({
|
||||
poolMaster: createSelector(
|
||||
createGetObject((_, props) => props.pool),
|
||||
pool => pool.master
|
||||
),
|
||||
}),
|
||||
{ withRef: true }
|
||||
)
|
||||
class CreateBondedNetworkModalBody extends Component {
|
||||
componentWillMount() {
|
||||
getBondModes().then(bondModes =>
|
||||
this.setState({ bondModes, bondMode: bondModes[0] })
|
||||
)
|
||||
}
|
||||
|
||||
_getPifPredicate = createSelector(
|
||||
() => this.props.poolMaster,
|
||||
hostId => pif => pif.$host === hostId && pif.vlan === -1
|
||||
)
|
||||
|
||||
get value() {
|
||||
const { name, description, pifs, mtu, bondMode } = this.state
|
||||
return {
|
||||
pool: this.props.pool,
|
||||
name,
|
||||
description,
|
||||
pifs: map(pifs, pif => pif.id),
|
||||
mtu,
|
||||
bondMode,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl
|
||||
return (
|
||||
<div>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkInterface')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectPif
|
||||
multi
|
||||
onChange={this.linkState('pifs')}
|
||||
predicate={this._getPifPredicate()}
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkName')}</Col>
|
||||
<Col size={6}>
|
||||
<input
|
||||
className='form-control'
|
||||
onChange={this.linkState('name')}
|
||||
type='text'
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkDescription')}</Col>
|
||||
<Col size={6}>
|
||||
<input
|
||||
className='form-control'
|
||||
onChange={this.linkState('description')}
|
||||
type='text'
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkMtu')}</Col>
|
||||
<Col size={6}>
|
||||
<input
|
||||
className='form-control'
|
||||
onChange={this.linkState('mtu')}
|
||||
placeholder={formatMessage(messages.newNetworkDefaultMtu)}
|
||||
type='text'
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkBondMode')}</Col>
|
||||
<Col size={6}>
|
||||
<select
|
||||
className='form-control'
|
||||
onChange={this.linkState('bondMode')}
|
||||
>
|
||||
{map(this.state.bondModes, mode => (
|
||||
<option value={mode}>{mode}</option>
|
||||
))}
|
||||
</select>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default injectIntl(CreateBondedNetworkModalBody, { withRef: true })
|
@ -1,84 +0,0 @@
|
||||
import React, { Component } from 'react'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { createSelector } from 'selectors'
|
||||
|
||||
import SingleLineRow from '../../single-line-row'
|
||||
import _, { messages } from '../../intl'
|
||||
import { SelectPif } from '../../select-objects'
|
||||
import { Col } from '../../grid'
|
||||
|
||||
class CreateNetworkModalBody extends Component {
|
||||
_getPifPredicate = createSelector(
|
||||
() => {
|
||||
const { container } = this.props
|
||||
return container.type === 'pool' ? container.master : container.id
|
||||
},
|
||||
hostId => pif => pif.$host === hostId && pif.vlan === -1
|
||||
)
|
||||
|
||||
get value() {
|
||||
const { refs } = this
|
||||
const { container } = this.props
|
||||
return {
|
||||
pool: container.$pool,
|
||||
name: refs.name.value,
|
||||
description: refs.description.value,
|
||||
pif: refs.pif.value && refs.pif.value.id,
|
||||
mtu: refs.mtu.value,
|
||||
vlan: refs.vlan.value,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { formatMessage } = this.props.intl
|
||||
return (
|
||||
<div>
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkInterface')}</Col>
|
||||
<Col size={6}>
|
||||
<SelectPif predicate={this._getPifPredicate()} ref='pif' />
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkName')}</Col>
|
||||
<Col size={6}>
|
||||
<input className='form-control' ref='name' type='text' />
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkDescription')}</Col>
|
||||
<Col size={6}>
|
||||
<input className='form-control' ref='description' type='text' />
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkVlan')}</Col>
|
||||
<Col size={6}>
|
||||
<input
|
||||
className='form-control'
|
||||
placeholder={formatMessage(messages.newNetworkDefaultVlan)}
|
||||
ref='vlan'
|
||||
type='text'
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
|
||||
<SingleLineRow>
|
||||
<Col size={6}>{_('newNetworkMtu')}</Col>
|
||||
<Col size={6}>
|
||||
<input
|
||||
className='form-control'
|
||||
placeholder={formatMessage(messages.newNetworkDefaultMtu)}
|
||||
ref='mtu'
|
||||
type='text'
|
||||
/>
|
||||
</Col>
|
||||
</SingleLineRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default injectIntl(CreateNetworkModalBody, { withRef: true })
|
@ -1627,39 +1627,10 @@ export const setVif = (
|
||||
export const editNetwork = (network, props) =>
|
||||
_call('network.set', { ...props, id: resolveId(network) })
|
||||
|
||||
import CreateNetworkModalBody from './create-network-modal' // eslint-disable-line import/first
|
||||
export const createNetwork = container =>
|
||||
confirm({
|
||||
icon: 'network',
|
||||
title: _('newNetworkCreate'),
|
||||
body: <CreateNetworkModalBody container={container} />,
|
||||
}).then(params => {
|
||||
if (!params.name) {
|
||||
return error(
|
||||
_('newNetworkNoNameErrorTitle'),
|
||||
_('newNetworkNoNameErrorMessage')
|
||||
)
|
||||
}
|
||||
return _call('network.create', params)
|
||||
}, noop)
|
||||
|
||||
export const getBondModes = () => _call('network.getBondModes')
|
||||
|
||||
import CreateBondedNetworkModalBody from './create-bonded-network-modal' // eslint-disable-line import/first
|
||||
export const createBondedNetwork = container =>
|
||||
confirm({
|
||||
icon: 'network',
|
||||
title: _('newBondedNetworkCreate'),
|
||||
body: <CreateBondedNetworkModalBody pool={container.$pool} />,
|
||||
}).then(params => {
|
||||
if (!params.name) {
|
||||
return error(
|
||||
_('newNetworkNoNameErrorTitle'),
|
||||
_('newNetworkNoNameErrorMessage')
|
||||
)
|
||||
}
|
||||
return _call('network.createBonded', params)
|
||||
}, noop)
|
||||
export const createNetwork = params => _call('network.create', params)
|
||||
export const createBondedNetwork = params =>
|
||||
_call('network.createBonded', params)
|
||||
|
||||
export const deleteNetwork = network =>
|
||||
confirm({
|
||||
|
@ -854,6 +854,10 @@
|
||||
@extend .fa;
|
||||
@extend .fa-database;
|
||||
}
|
||||
&-network {
|
||||
@extend .fa;
|
||||
@extend .fa-sitemap;
|
||||
}
|
||||
&-import {
|
||||
@extend .fa;
|
||||
@extend .fa-file-archive-o;
|
||||
@ -906,6 +910,13 @@
|
||||
@extend .fa-times;
|
||||
}
|
||||
}
|
||||
// New network
|
||||
&-new-network {
|
||||
&-create {
|
||||
@extend .fa;
|
||||
@extend .fa-play;
|
||||
}
|
||||
}
|
||||
// OS Icons
|
||||
&-centos {
|
||||
@extend .fa;
|
||||
|
@ -369,6 +369,11 @@ export default class Menu extends Component {
|
||||
label: 'newVmPage',
|
||||
},
|
||||
isAdmin && { to: '/new/sr', icon: 'menu-new-sr', label: 'newSrPage' },
|
||||
isPoolAdmin && {
|
||||
to: '/new/network',
|
||||
icon: 'menu-new-network',
|
||||
label: 'newNetworkPage',
|
||||
},
|
||||
isAdmin && {
|
||||
to: '/settings/servers',
|
||||
icon: 'menu-settings-servers',
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { routes } from 'utils'
|
||||
|
||||
import Network from './network'
|
||||
import Sr from './sr'
|
||||
|
||||
const New = routes('vm', {
|
||||
network: Network,
|
||||
sr: Sr,
|
||||
})(({ children }) => children)
|
||||
|
||||
|
5
packages/xo-web/src/xo-app/new/network/index.css
Normal file
5
packages/xo-web/src/xo-app/new/network/index.css
Normal file
@ -0,0 +1,5 @@
|
||||
.inlineSelect {
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
width: 20em;
|
||||
}
|
242
packages/xo-web/src/xo-app/new/network/index.js
Normal file
242
packages/xo-web/src/xo-app/new/network/index.js
Normal file
@ -0,0 +1,242 @@
|
||||
import _, { messages } from 'intl'
|
||||
import ActionButton from 'action-button'
|
||||
import decorate from 'apply-decorators'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, { Component } from 'react'
|
||||
import Wizard, { Section } from 'wizard'
|
||||
import { connectStore } from 'utils'
|
||||
import { createBondedNetwork, createNetwork, getBondModes } from 'xo'
|
||||
import { createGetObject, getIsPoolAdmin } from 'selectors'
|
||||
import { injectIntl } from 'react-intl'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { linkState } from 'reaclette-utils'
|
||||
import { map } from 'lodash'
|
||||
import { Select, Toggle } from 'form'
|
||||
import { SelectPif, SelectPool } from 'select-objects'
|
||||
|
||||
import Page from '../../page'
|
||||
import styles from './index.css'
|
||||
|
||||
const EMPTY = {
|
||||
bonded: false,
|
||||
bondMode: undefined,
|
||||
description: '',
|
||||
mtu: '',
|
||||
name: '',
|
||||
pif: undefined,
|
||||
pifs: [],
|
||||
vlan: '',
|
||||
}
|
||||
|
||||
const NewNetwork = decorate([
|
||||
connectStore(() => ({
|
||||
isPoolAdmin: getIsPoolAdmin,
|
||||
pool: createGetObject((_, props) => props.location.query.pool),
|
||||
})),
|
||||
injectIntl,
|
||||
provideState({
|
||||
initialState: () => ({ ...EMPTY, bondModes: undefined }),
|
||||
effects: {
|
||||
initialize: async () => ({ bondModes: await getBondModes() }),
|
||||
linkState,
|
||||
onChangeMode: (_, bondMode) => ({ bondMode }),
|
||||
onChangePif: (_, value) => ({ bonded }) =>
|
||||
bonded ? { pifs: value } : { pif: value },
|
||||
reset: () => EMPTY,
|
||||
toggleBonded: () => ({ bonded }) => ({
|
||||
...EMPTY,
|
||||
bonded: !bonded,
|
||||
}),
|
||||
},
|
||||
computed: {
|
||||
modeOptions: ({ bondModes }) =>
|
||||
bondModes !== undefined
|
||||
? bondModes.map(mode => ({
|
||||
label: mode,
|
||||
value: mode,
|
||||
}))
|
||||
: [],
|
||||
pifPredicate: (_, { pool }) => pif =>
|
||||
pif.vlan === -1 && pif.$host === (pool && pool.master),
|
||||
},
|
||||
}),
|
||||
injectState,
|
||||
class extends Component {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
_create = () => {
|
||||
const { pool, state } = this.props
|
||||
const {
|
||||
bonded,
|
||||
bondMode,
|
||||
description,
|
||||
mtu,
|
||||
name,
|
||||
pif,
|
||||
pifs,
|
||||
vlan,
|
||||
} = state
|
||||
return bonded
|
||||
? createBondedNetwork({
|
||||
bondMode: bondMode.value,
|
||||
description,
|
||||
mtu,
|
||||
name,
|
||||
pifs: map(pifs, 'id'),
|
||||
pool: pool.id,
|
||||
vlan,
|
||||
})
|
||||
: createNetwork({
|
||||
description,
|
||||
mtu,
|
||||
name,
|
||||
pif: pif.id,
|
||||
pool: pool.id,
|
||||
vlan,
|
||||
})
|
||||
}
|
||||
|
||||
_selectPool = pool => {
|
||||
const {
|
||||
effects,
|
||||
location: { pathname },
|
||||
} = this.props
|
||||
effects.reset()
|
||||
this.context.router.push({
|
||||
pathname,
|
||||
query: pool !== null && { pool: pool.id },
|
||||
})
|
||||
}
|
||||
|
||||
_renderHeader = () => {
|
||||
const { isPoolAdmin, pool } = this.props
|
||||
return (
|
||||
<h2>
|
||||
{isPoolAdmin
|
||||
? _('createNewNetworkOn', {
|
||||
select: (
|
||||
<span className={styles.inlineSelect}>
|
||||
<SelectPool onChange={this._selectPool} value={pool} />
|
||||
</span>
|
||||
),
|
||||
})
|
||||
: _('createNewNetworkNoPermission')}
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state, effects, intl, pool } = this.props
|
||||
const {
|
||||
bonded,
|
||||
bondMode,
|
||||
description,
|
||||
modeOptions,
|
||||
mtu,
|
||||
name,
|
||||
pif,
|
||||
pifPredicate,
|
||||
pifs,
|
||||
vlan,
|
||||
} = state
|
||||
const { formatMessage } = intl
|
||||
return (
|
||||
<Page header={this._renderHeader()}>
|
||||
{pool !== undefined && (
|
||||
<form id='networkCreation'>
|
||||
<Wizard>
|
||||
<Section icon='network' title='newNetworkType'>
|
||||
<div>
|
||||
<Toggle onChange={effects.toggleBonded} value={bonded} />{' '}
|
||||
<label>{_('bondedNetwork')}</label>
|
||||
</div>
|
||||
</Section>
|
||||
<Section icon='info' title='newNetworkInfo'>
|
||||
<div className='form-group'>
|
||||
<label>{_('newNetworkInterface')}</label>
|
||||
<SelectPif
|
||||
multi={bonded}
|
||||
onChange={effects.onChangePif}
|
||||
predicate={pifPredicate}
|
||||
required
|
||||
value={bonded ? pifs : pif}
|
||||
/>
|
||||
<label>{_('newNetworkName')}</label>
|
||||
<input
|
||||
className='form-control'
|
||||
name='name'
|
||||
onChange={effects.linkState}
|
||||
required
|
||||
type='text'
|
||||
value={name}
|
||||
/>
|
||||
<label>{_('newNetworkDescription')}</label>
|
||||
<input
|
||||
className='form-control'
|
||||
name='description'
|
||||
onChange={effects.linkState}
|
||||
type='text'
|
||||
value={description}
|
||||
/>
|
||||
<label>{_('newNetworkMtu')}</label>
|
||||
<input
|
||||
className='form-control'
|
||||
name='mtu'
|
||||
onChange={effects.linkState}
|
||||
placeholder={formatMessage(messages.newNetworkDefaultMtu)}
|
||||
type='text'
|
||||
value={mtu}
|
||||
/>
|
||||
{bonded ? (
|
||||
<div>
|
||||
<label>{_('newNetworkBondMode')}</label>
|
||||
<Select
|
||||
onChange={effects.onChangeMode}
|
||||
options={modeOptions}
|
||||
required
|
||||
value={bondMode}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<label>{_('newNetworkVlan')}</label>
|
||||
<input
|
||||
className='form-control'
|
||||
name='vlan'
|
||||
onChange={effects.linkState}
|
||||
placeholder={formatMessage(
|
||||
messages.newNetworkDefaultVlan
|
||||
)}
|
||||
type='text'
|
||||
value={vlan}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
</Wizard>
|
||||
<div className='form-group pull-right'>
|
||||
<ActionButton
|
||||
btnStyle='primary'
|
||||
className='mr-1'
|
||||
form='networkCreation'
|
||||
handler={this._create}
|
||||
icon='new-network-create'
|
||||
redirectOnSuccess={`pools/${pool.id}/network`}
|
||||
>
|
||||
{_('newNetworkCreate')}
|
||||
</ActionButton>
|
||||
<ActionButton handler={effects.reset} icon='reset'>
|
||||
{_('formReset')}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
},
|
||||
])
|
||||
export { NewNetwork as default }
|
@ -10,10 +10,10 @@ import map from 'lodash/map'
|
||||
import React, { Component } from 'react'
|
||||
import some from 'lodash/some'
|
||||
import SortedTable from 'sorted-table'
|
||||
import TabButton from 'tab-button'
|
||||
import Tooltip from 'tooltip'
|
||||
import { connectStore } from 'utils'
|
||||
import { Container, Row, Col } from 'grid'
|
||||
import { TabButtonLink } from 'tab-button'
|
||||
import { Text, Number } from 'editable'
|
||||
import { Toggle } from 'form'
|
||||
import {
|
||||
@ -24,8 +24,6 @@ import {
|
||||
} from 'selectors'
|
||||
import {
|
||||
connectPif,
|
||||
createBondedNetwork,
|
||||
createNetwork,
|
||||
deleteNetwork,
|
||||
disconnectPif,
|
||||
editNetwork,
|
||||
@ -362,19 +360,10 @@ export default class TabNetworks extends Component {
|
||||
<Container>
|
||||
<Row>
|
||||
<Col className='text-xs-right'>
|
||||
<TabButton
|
||||
btnStyle='primary'
|
||||
handler={createBondedNetwork}
|
||||
handlerParam={this.props.pool}
|
||||
icon='add'
|
||||
labelId='networkCreateBondedButton'
|
||||
/>
|
||||
<TabButton
|
||||
btnStyle='primary'
|
||||
handler={createNetwork}
|
||||
handlerParam={this.props.pool}
|
||||
<TabButtonLink
|
||||
icon='add'
|
||||
labelId='networkCreateButton'
|
||||
to={`new/network?pool=${this.props.pool.id}`}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
Loading…
Reference in New Issue
Block a user