Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(xo-web/xo-server/host/network): display and edit the IPv6 PIF field #7218

Merged
merged 3 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

> Users must be able to say: “Nice enhancement, I'm eager to test it”

- [Host/Network/PIF] Display and ability to edit IPv6 field [#5400](https://github.com/vatesfr/xen-orchestra/issues/5400) (PR [#7218](https://github.com/vatesfr/xen-orchestra/pull/7218))

### Bug fixes

> Users must be able to say: “I had this issue, happy to know it's fixed”
Expand All @@ -27,4 +29,7 @@

<!--packages-start-->

- xo-server minor
- xo-web minor

<!--packages-end-->
37 changes: 31 additions & 6 deletions packages/xo-server/src/api/pif.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import filter from 'lodash/filter.js'
import find from 'lodash/find.js'
import { Task } from '@xen-orchestra/mixins/Tasks.mjs'

import { IPV4_CONFIG_MODES, IPV6_CONFIG_MODES } from '../xapi/index.mjs'

Expand Down Expand Up @@ -73,12 +74,34 @@ connect.resolve = {
// ===================================================================
// Reconfigure IP

export async function reconfigureIp({ pif, mode = 'DHCP', ip = '', netmask = '', gateway = '', dns = '' }) {
const xapi = this.getXapi(pif)
await xapi.call('PIF.reconfigure_ip', pif._xapiRef, mode, ip, netmask, gateway, dns)
if (pif.management) {
await xapi.call('host.management_reconfigure', pif._xapiRef)
}
export async function reconfigureIp({ pif, mode, ip = '', netmask = '', gateway = '', dns = '', ipv6, ipv6Mode }) {
const task = this.tasks.create({
julien-f marked this conversation as resolved.
Show resolved Hide resolved
name: `reconfigure ip of: ${pif.device}`,
objectId: pif.uuid,
type: 'xo:pif:reconfigureIp',
})
await task.run(async () => {
const xapi = this.getXapi(pif)

if ((ipv6 !== '' && pif.ipv6?.[0] !== ipv6) || (ipv6Mode !== undefined && ipv6Mode !== pif.ipv6Mode)) {
await Task.run(
{ properties: { name: 'reconfigure IPv6', mode: ipv6Mode, ipv6, gateway, dns, objectId: pif.uuid } },
() => xapi.call('PIF.reconfigure_ipv6', pif._xapiRef, ipv6Mode, ipv6, gateway, dns)
)
}

if (mode !== undefined && mode !== pif.mode) {
await Task.run(
{ properties: { name: 'reconfigure IPv4', mode, ip, netmask, gateway, dns, objectId: pif.uuid } },
() => xapi.call('PIF.reconfigure_ip', pif._xapiRef, mode, ip, netmask, gateway, dns)
)
}
if (pif.management) {
await Task.run({ properties: { name: 'reconfigure PIF management', objectId: pif.uuid } }, () =>
xapi.call('host.management_reconfigure', pif._xapiRef)
)
}
})
}

reconfigureIp.params = {
Expand All @@ -88,6 +111,8 @@ reconfigureIp.params = {
netmask: { type: 'string', minLength: 0, optional: true },
gateway: { type: 'string', minLength: 0, optional: true },
dns: { type: 'string', minLength: 0, optional: true },
ipv6: { type: 'string', minLength: 0, default: '' },
ipv6Mode: { enum: getIpv6ConfigurationModes(), optional: true },
}

reconfigureIp.resolve = {
Expand Down
3 changes: 3 additions & 0 deletions packages/xo-server/src/xapi-object-to-xo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -562,15 +562,18 @@ const TRANSFORMS = {
disallowUnplug: Boolean(obj.disallow_unplug),
gateway: obj.gateway,
ip: obj.IP,
ipv6: obj.IPv6,
mac: obj.MAC,
management: Boolean(obj.management), // TODO: find a better name.
carrier: Boolean(metrics && metrics.carrier),
mode: obj.ip_configuration_mode,
ipv6Mode: obj.ipv6_configuration_mode,
mtu: +obj.MTU,
netmask: obj.netmask,
// A non physical PIF is a "copy" of an existing physical PIF (same device)
// A physical PIF cannot be unplugged
physical: Boolean(obj.physical),
primaryAddressType: obj.primary_address_type,
vlan: +obj.VLAN,
speed: metrics && +metrics.speed,
$host: link(obj, 'host'),
Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1541,9 +1541,6 @@ export default {
// Original text: 'Invalid parameters'
configIpErrorTitle: undefined,

// Original text: 'IP address and netmask required'
configIpErrorMessage: undefined,

// Original text: 'Static IP address'
staticIp: undefined,

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -1596,9 +1596,6 @@ export default {
// Original text: "Invalid parameters"
configIpErrorTitle: 'Paramètres invalides',

// Original text: "IP address and netmask required"
configIpErrorMessage: 'Adresse IP et masque de réseau requis',

// Original text: "Static IP address"
staticIp: 'Adresse IP statique',

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/he.js
Original file line number Diff line number Diff line change
Expand Up @@ -1295,9 +1295,6 @@ export default {
// Original text: 'Invalid parameters'
configIpErrorTitle: undefined,

// Original text: 'IP address and netmask required'
configIpErrorMessage: undefined,

// Original text: 'Static IP address'
staticIp: undefined,

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/hu.js
Original file line number Diff line number Diff line change
Expand Up @@ -1491,9 +1491,6 @@ export default {
// Original text: "Invalid parameters"
configIpErrorTitle: 'Invalid parameters',

// Original text: "IP address and netmask required"
configIpErrorMessage: 'IP cím and netmask required',

// Original text: "Static IP address"
staticIp: 'Static IP cím',

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/it.js
Original file line number Diff line number Diff line change
Expand Up @@ -2387,9 +2387,6 @@ export default {
// Original text: 'Invalid parameters'
configIpErrorTitle: 'Parametri non validi',

// Original text: 'IP address and netmask required'
configIpErrorMessage: 'Indirizzo IP e maschera di rete richiesti',

// Original text: 'Static IP address'
staticIp: 'Indirizzo IP statico',

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/pl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1298,9 +1298,6 @@ export default {
// Original text: 'Invalid parameters'
configIpErrorTitle: undefined,

// Original text: 'IP address and netmask required'
configIpErrorMessage: undefined,

// Original text: 'Static IP address'
staticIp: undefined,

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/pt.js
Original file line number Diff line number Diff line change
Expand Up @@ -1296,9 +1296,6 @@ export default {
// Original text: 'Invalid parameters'
configIpErrorTitle: undefined,

// Original text: 'IP address and netmask required'
configIpErrorMessage: undefined,

// Original text: 'Static IP address'
staticIp: undefined,

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -1542,9 +1542,6 @@ export default {
// Original text: 'Invalid parameters'
configIpErrorTitle: undefined,

// Original text: 'IP address and netmask required'
configIpErrorMessage: undefined,

// Original text: 'Static IP address'
staticIp: undefined,

Expand Down
3 changes: 0 additions & 3 deletions packages/xo-web/src/common/intl/locales/tr.js
Original file line number Diff line number Diff line change
Expand Up @@ -1990,9 +1990,6 @@ export default {
// Original text: "Invalid parameters"
configIpErrorTitle: 'Geçersiz parametre',

// Original text: "IP address and netmask required"
configIpErrorMessage: 'IP adresi ve ağ maskesi gerekli',

// Original text: "Static IP address"
staticIp: 'Statik IP adresi',

Expand Down
4 changes: 3 additions & 1 deletion packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -1072,11 +1072,13 @@ const messages = {
defaultLockingMode: 'Default locking mode',
pifConfigureIp: 'Configure IP address',
configIpErrorTitle: 'Invalid parameters',
configIpErrorMessage: 'IP address and netmask required',
staticIp: 'Static IP address',
staticIpv6: 'Static IPv6 address',
netmask: 'Netmask',
dns: 'DNS',
gateway: 'Gateway',
ipRequired: 'An IP address is required',
netmaskRequired: 'Netmask required',
// ----- Host storage tabs -----
addSrDeviceButton: 'Add a storage',
srType: 'Type',
Expand Down
6 changes: 5 additions & 1 deletion packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2244,18 +2244,22 @@ export const deletePifs = pifs =>
body: _('deletePifsConfirm', { nPifs: pifs.length }),
}).then(() => Promise.all(map(pifs, pif => _call('pif.delete', { pif: resolveId(pif) }))), noop)

export const reconfigurePifIp = (pif, { mode, ip, netmask, gateway, dns }) =>
export const reconfigurePifIp = (pif, { mode, ip, ipv6, ipv6Mode, netmask, gateway, dns }) =>
_call('pif.reconfigureIp', {
pif: resolveId(pif),
mode,
ip,
ipv6,
ipv6Mode,
netmask,
gateway,
dns,
})

export const getIpv4ConfigModes = () => _call('pif.getIpv4ConfigurationModes')

export const getIpv6ConfigModes = () => _call('pif.getIpv6ConfigurationModes')

export const editPif = (pif, { vlan }) => _call('pif.editPif', { pif: resolveId(pif), vlan })

export const scanHostPifs = hostId => _call('host.scanPifs', { host: hostId })
Expand Down
57 changes: 44 additions & 13 deletions packages/xo-web/src/xo-app/host/tab-network.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
editNetwork,
editPif,
getIpv4ConfigModes,
getIpv6ConfigModes,
reconfigurePifIp,
scanHostPifs,
} from 'xo'
Expand All @@ -41,7 +42,10 @@ class ConfigureIpModal extends Component {

const { pif } = props
if (pif) {
this.state = pick(pif, ['ip', 'netmask', 'dns', 'gateway'])
this.state = {
...pick(pif, ['ip', 'netmask', 'dns', 'gateway']),
ipv6: pif.ipv6?.[0],
}
}
}

Expand All @@ -50,7 +54,7 @@ class ConfigureIpModal extends Component {
}

render() {
const { ip, netmask, dns, gateway } = this.state
const { ip, ipv6, netmask, dns, gateway } = this.state

return (
<div>
Expand All @@ -61,6 +65,13 @@ class ConfigureIpModal extends Component {
</Col>
</SingleLineRow>
&nbsp;
<SingleLineRow>
<Col size={6}>{_('staticIpv6')}</Col>
<Col size={6}>
<input className='form-control' onChange={this.linkState('ipv6')} value={ipv6} />
</Col>
</SingleLineRow>
&nbsp;
<SingleLineRow>
<Col size={6}>{_('netmask')}</Col>
<Col size={6}>
Expand Down Expand Up @@ -111,22 +122,27 @@ const reconfigureIp = (pif, mode) => {
title: _('pifConfigureIp'),
body: <ConfigureIpModal pif={pif} />,
}).then(params => {
if (!params.ip || !params.netmask) {
error(_('configIpErrorTitle'), _('configIpErrorMessage'))
return
if (params.ip === undefined && params.ipv6 === undefined) {
return error(_('configIpErrorTitle'), _('ipRequired'))
}
if (params.ip !== undefined && params.netmask === undefined) {
return error(_('configIpErrorTitle'), _('netmaskRequired'))
}
if (params.ipv6 !== undefined) {
params.ipv6Mode = mode
}
return reconfigurePifIp(pif, { mode, ...params })
}, noop)
}
return reconfigurePifIp(pif, { mode })
return reconfigurePifIp(pif, { [pif.primaryAddressType === 'IPv6' ? 'ipv6Mode' : 'mode']: mode })
}

class PifItemIp extends Component {
_onEditIp = () => reconfigureIp(this.props.pif, 'Static')

render() {
const { pif } = this.props
const pifIp = pif.ip
const pifIp = pif.primaryAddressType === 'IPv6' ? pif.ipv6?.[0] : pif.ip
return (
<div>
{pifIp}{' '}
Expand All @@ -143,26 +159,41 @@ class PifItemIp extends Component {
class PifItemMode extends Component {
state = { configModes: [] }

componentDidMount() {
getIpv4ConfigModes().then(configModes => this.setState({ configModes }))
async componentDidMount() {
const [ipv4ConfigModes, ipv6ConfigModes] = await Promise.all([getIpv4ConfigModes(), getIpv6ConfigModes()])
this.setState({ ipv4ConfigModes, ipv6ConfigModes })
}

_configIp = mode => mode != null && reconfigureIp(this.props.pif, mode.value)

_isIpv6 = createSelector(
() => this.props.pif.primaryAddressType,
primaryAddressType => primaryAddressType === 'IPv6'
)

_getOptions = createSelector(
() => this.state.configModes,
configModes => configModes.map(mode => ({ label: mode, value: mode }))
this._isIpv6,
() => this.state.ipv4ConfigModes,
() => this.state.ipv6ConfigModes,
(isIpv6, ipv4ConfigModes, ipv6ConfigModes) =>
(isIpv6 ? ipv6ConfigModes : ipv4ConfigModes).map(mode => ({ label: mode, value: mode }))
)

_getValue = createSelector(
this._isIpv6,
() => this.props.pif.mode,
mode => ({ label: mode, value: mode })
() => this.props.pif.ipv6Mode,
(isIpv6, mode, ipv6Mode) => {
mode = isIpv6 ? ipv6Mode : mode
return { label: mode, value: mode }
}
)

render() {
const isIpv6 = this._isIpv6()
return (
<Select onChange={this._configIp} options={this._getOptions()} value={this._getValue()}>
{this.props.pif.mode}
{this.props.pif[isIpv6 ? 'ipv6Mode' : 'mode']}
</Select>
)
}
Expand Down
Loading