feat(NFS remote): allow optional port (#3131)

Fixes #2299
This commit is contained in:
badrAZ 2018-07-05 11:09:34 +02:00 committed by Julien Fontanet
parent 46100729b0
commit fb1bf6a1e7
10 changed files with 82 additions and 46 deletions

View File

@ -52,12 +52,13 @@ export default class NfsHandler extends LocalHandler {
async _mount () {
await fs.ensureDir(this._getRealPath())
const { host, path, port } = this._remote
return execa('mount', [
'-t',
'nfs',
'-o',
'vers=3',
`${this._remote.host}:${this._remote.path}`,
`${host}${port !== undefined ? ':' + port : ''}:${path}`,
this._getRealPath(),
])
}

View File

@ -7,6 +7,7 @@
- [Backup NG form] Add a link to the remotes' settings [#2711](https://github.com/vatesfr/xen-orchestra/issues/2711) [#3106](https://github.com/vatesfr/xen-orchestra/issues/3106) [#2299](https://github.com/vatesfr/xen-orchestra/issues/2299) (PR [#3128](https://github.com/vatesfr/xen-orchestra/pull/3128))
- [Backup NG logs] Make copy to clipboard and report buttons always available [#3130](https://github.com/vatesfr/xen-orchestra/issues/3130) (PR [#3133](https://github.com/vatesfr/xen-orchestra/pull/3133))
- Warning message when creating a local remote [#3105](https://github.com/vatesfr/xen-orchestra/issues/3105) (PR [3142](https://github.com/vatesfr/xen-orchestra/pull/3142))
- [Remotes] Allow optional port for NFS remote [2299](https://github.com/vatesfr/xen-orchestra/issues/2299) (PR [#3131](https://github.com/vatesfr/xen-orchestra/pull/3131))
### Bug fixes
@ -15,6 +16,8 @@
### Released packages
- xo-remote-parser 0.4.0
- @xen-orchestra/fs 0.2.0
- vhd-lib 0.3.0
- vhd-cli 0.1.0
- xo-server v5.22.0

View File

@ -3,6 +3,8 @@ import map from 'lodash/map'
import trim from 'lodash/trim'
import trimStart from 'lodash/trimStart'
const NFS_RE = /^([^:]+):(?:(\d+):)?([^:]+)$/
const sanitizePath = (...paths) =>
filter(map(paths, s => s && filter(map(s.split('/'), trim)).join('/'))).join(
'/'
@ -17,8 +19,9 @@ export const parse = string => {
object.path = `/${trimStart(rest, '/')}` // the leading slash has been forgotten on client side first implementation
} else if (type === 'nfs') {
object.type = 'nfs'
const [host, path] = rest.split(':')
const [, host, port, path] = NFS_RE.exec(rest)
object.host = host
object.port = port
object.path = `/${trimStart(path, '/')}` // takes care of a missing leading slash coming from previous version format
} else if (type === 'smb') {
object.type = 'smb'
@ -39,11 +42,19 @@ export const parse = string => {
return object
}
export const format = ({ type, host, path, username, password, domain }) => {
export const format = ({
type,
host,
path,
port,
username,
password,
domain,
}) => {
type === 'local' && (type = 'file')
let string = `${type}://`
if (type === 'nfs') {
string += `${host}:`
string += `${host}:${port !== undefined ? port + ':' : ''}`
}
if (type === 'smb') {
string += `${username}:${password}@${domain}\\\\${host}`

View File

@ -31,6 +31,16 @@ const data = deepFreeze({
object: {
type: 'nfs',
host: '192.168.100.225',
port: undefined,
path: '/media/nfs',
},
},
'nfs with port': {
string: 'nfs://192.168.100.225:20:/media/nfs',
object: {
type: 'nfs',
host: '192.168.100.225',
port: '20',
path: '/media/nfs',
},
},

View File

@ -16,6 +16,7 @@ import getEventValue from '../get-event-value'
import propTypes from '../prop-types-decorator'
import { formatSizeRaw, parseSize } from '../utils'
export Number from './number'
export Select from './select'
// ===================================================================

View File

@ -471,6 +471,7 @@ const messages = {
remoteMyNamePlaceHolder: 'Name *',
remoteLocalPlaceHolderPath: '/path/to/backup',
remoteNfsPlaceHolderHost: 'host *',
remoteNfsPlaceHolderPort: 'Port',
remoteNfsPlaceHolderPath: 'path/to/backup',
remoteSmbPlaceHolderRemotePath: 'subfolder [path\\\\to\\\\backup]',
remoteSmbPlaceHolderUsername: 'Username',

View File

@ -18,7 +18,7 @@ import { constructSmartPattern, destructSmartPattern } from 'smart-backup'
import { Container, Col, Row } from 'grid'
import { injectState, provideState } from '@julien-f/freactal'
import { SelectRemote, SelectSr, SelectVm } from 'select-objects'
import { Toggle } from 'form'
import { Number, Toggle } from 'form'
import {
cloneDeep,
flatten,
@ -42,7 +42,7 @@ import {
import Schedules from './schedules'
import SmartBackup from './smart-backup'
import { FormFeedback, FormGroup, Input, Number, Ul, Li } from './utils'
import { FormFeedback, FormGroup, Input, Ul, Li } from './utils'
// ===================================================================

View File

@ -7,8 +7,9 @@ import { Card, CardBlock } from 'card'
import { generateRandomId } from 'utils'
import { injectState, provideState } from '@julien-f/freactal'
import { isEqual } from 'lodash'
import { Number } from 'form'
import { FormFeedback, FormGroup, Number } from './utils'
import { FormFeedback, FormGroup } from './utils'
export default [
injectState,

View File

@ -1,46 +1,12 @@
import Icon from 'icon'
import PropTypes from 'prop-types'
import React from 'react'
import { injectState, provideState } from '@julien-f/freactal'
export const FormGroup = props => <div {...props} className='form-group' />
export const Input = props => <input {...props} className='form-control' />
export const Ul = props => <ul {...props} className='list-group' />
export const Li = props => <li {...props} className='list-group-item' />
export const Number = [
provideState({
effects: {
onChange: (_, { target: { value } }) => (state, props) => {
if (value === '') {
if (!props.optional) {
return
}
props.onChange(undefined)
return
}
props.onChange(+value)
},
},
}),
injectState,
({ effects, state, value, optional }) => (
<Input
type='number'
onChange={effects.onChange}
value={value === undefined ? undefined : String(value)}
min='0'
/>
),
].reduceRight((value, decorator) => decorator(value))
Number.propTypes = {
onChange: PropTypes.func.isRequired,
value: PropTypes.number.isRequired,
optional: PropTypes.bool,
}
export const FormFeedback = ({
component: Component,
error,

View File

@ -14,8 +14,9 @@ import { addSubscriptions } from 'utils'
import { alert, confirm } from 'modal'
import { error } from 'notification'
import { format, parse } from 'xo-remote-parser'
import { Password, Text } from 'editable'
import { injectIntl } from 'react-intl'
import { Number, Password, Text } from 'editable'
import { Number as InputNumber } from 'form'
import {
createRemote,
@ -34,7 +35,9 @@ const remoteTypes = {
smb: 'remoteTypeSmb',
}
const _changeUrlElement = (remote, value, element) =>
editRemote(remote, { url: format({ ...remote, [element]: value }) })
editRemote(remote, {
url: format({ ...remote, [element]: value === null ? undefined : value }),
})
const _showError = remote => alert(_('remoteConnectionFailed'), remote.error)
const COLUMN_NAME = {
component: @injectIntl
@ -122,6 +125,15 @@ const COLUMNS_NFS_REMOTE = [
value={remote.host}
/>
:
<Number
nullable
onChange={v => _changeUrlElement(remote, v, 'port')}
placeholder={intl.formatMessage(
messages.remoteNfsPlaceHolderPort
)}
value={remote.port || ''}
/>
:
<Text
onChange={v => _changeUrlElement(remote, v, 'path')}
placeholder={intl.formatMessage(
@ -278,6 +290,7 @@ export default class Remotes extends Component {
name: '',
password: '',
path: '',
port: '',
type: 'nfs',
username: '',
}
@ -294,12 +307,22 @@ export default class Remotes extends Component {
: this._createRemote()
_createRemote = async () => {
const { type, name, host, path, username, password, domain } = this.state
const {
domain,
host,
name,
password,
path,
port,
type,
username,
} = this.state
const urlParams = {
type,
host,
path,
port,
type,
}
username && (urlParams.username = username)
password && (urlParams.password = password)
@ -321,6 +344,7 @@ export default class Remotes extends Component {
name: '',
password: '',
path: '',
port: '',
type: 'nfs',
username: '',
})
@ -331,7 +355,16 @@ export default class Remotes extends Component {
render () {
const { remotes = {} } = this.props
const { type, name, host, path, username, password, domain } = this.state
const {
domain,
host,
name,
password,
path,
port,
type,
username,
} = this.state
return (
<div>
@ -440,6 +473,15 @@ export default class Remotes extends Component {
value={host}
required
/>
<br />
<InputNumber
onChange={this.linkState('port')}
optional
placeholder={this.props.intl.formatMessage(
messages.remoteNfsPlaceHolderPort
)}
value={port}
/>
</div>
<div className='input-group form-group'>
<span className='input-group-addon'>/</span>