feat(xo-web/proxy) (#4764)

See #4254
This commit is contained in:
Pierre Donias 2020-01-31 09:48:41 +01:00 committed by GitHub
parent 218bd0ffc1
commit 1ec6611410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 488 additions and 23 deletions

View File

@ -14,6 +14,7 @@
- [Home] Allow to change the number of items per page [#4535](https://github.com/vatesfr/xen-orchestra/issues/4535) (PR [#4708](https://github.com/vatesfr/xen-orchestra/pull/4708))
- [Tag] Adding a tag: ability to select from existing tags [#2810](https://github.com/vatesfr/xen-orchestra/issues/2810) (PR [#4530](https://github.com/vatesfr/xen-orchestra/pull/4530))
- [Smart backup] Ability to manually add custom tags [#2810](https://github.com/vatesfr/xen-orchestra/issues/2810) (PR [#4648](https://github.com/vatesfr/xen-orchestra/pull/4648))
- [Proxy] Ability to backup VMs via registered proxy [#4254](https://github.com/vatesfr/xen-orchestra/issues/4254) (PR [#4495](https://github.com/vatesfr/xen-orchestra/pull/4495))
### Bug fixes

View File

@ -15,6 +15,7 @@ import {
SelectIp,
SelectNetwork,
SelectPool,
SelectProxy,
SelectRemote,
SelectResourceSetIp,
SelectSr,
@ -430,6 +431,7 @@ const MAP_TYPE_SELECT = {
ip: SelectIp,
network: SelectNetwork,
pool: SelectPool,
proxy: SelectProxy,
remote: SelectRemote,
resourceSetIp: SelectResourceSetIp,
SR: SelectSr,

View File

@ -53,6 +53,11 @@ const messages = {
iscsiSessions:
'({ nSessions, number }) iSCSI session{nSessions, plural, one {} other {s}}',
requiresAdminPermissions: 'Requires admin permissions',
proxy: 'Proxy',
proxies: 'Proxies',
name: 'Name',
address: 'Address',
vm: 'VM',
// ----- Modals -----
alertOk: 'OK',
@ -287,6 +292,7 @@ const messages = {
selectPifs: 'Select PIF(s)…',
selectPools: 'Select pool(s)…',
selectRemotes: 'Select remote(s)…',
selectProxies: 'Select proxy(ies)…',
selectResourceSets: 'Select resource set(s)…',
selectResourceSetsVmTemplate: 'Select template(s)…',
selectResourceSetsSr: 'Select SR(s)…',
@ -1528,6 +1534,8 @@ const messages = {
'Are you sure you want to delete all the backups from {nMetadataBackups, number} metadata backup{nMetadataBackups, plural, one {} other {s}}?',
bulkDeleteMetadataBackupsConfirmText:
'delete {nMetadataBackups} metadata backup{nMetadataBackups, plural, one {} other {s}}',
remoteNotCompatibleWithSelectedProxy:
"The backup will not be run on this remote because it's not compatible with the selected proxy",
// ----- Restore files view -----
listRemoteBackups: 'List remote backups',
@ -2243,6 +2251,24 @@ const messages = {
xosanInstallXoaPlugin: 'Install XOA plugin first',
xosanLoadXoaPlugin: 'Load XOA plugin first',
// ----- proxies -----
forgetProxyApplianceTitle: 'Forget prox{n, plural, one {y} other {ies}}',
forgetProxyApplianceMessage:
'Are you sure you want to forget {n, number} prox{n, plural, one {y} other {ies}}?',
forgetProxies: 'Forget proxy(ies)',
destroyProxyApplianceTitle: 'Destroy prox{n, plural, one {y} other {ies}}',
destroyProxyApplianceMessage:
'Are you sure you want to destroy {n, number} prox{n, plural, one {y} other {ies}}?',
destroyProxies: 'Destroy proxy(ies)',
deployProxy: 'Deploy a proxy',
noProxiesAvailable: 'No proxies available',
checkProxyHealth: 'Test your proxy',
upgradeProxyAppliance: 'upgrade the appliance',
proxyTestSuccess: 'Test passed for {name}',
proxyTestSuccessMessage: 'The proxy appears to work correctly',
proxyLinkedRemotes: 'Click to see linked remotes',
proxyLinkedBackups: 'Click to see linked backups',
// ----- Utils -----
secondsFormat: '{seconds, plural, one {# second} other {# seconds}}',
durationFormat:

View File

@ -12,7 +12,7 @@ import Tooltip from './tooltip'
import { addSubscriptions, connectStore, formatSize } from './utils'
import { createGetObject, createSelector } from './selectors'
import { FormattedDate } from 'react-intl'
import { isSrWritable, subscribeRemotes } from './xo'
import { isSrWritable, subscribeProxies, subscribeRemotes } from './xo'
// ===================================================================
@ -372,6 +372,27 @@ Remote.defaultProps = {
// ===================================================================
export const Proxy = decorate([
addSubscriptions(({ id }) => ({
proxy: cb =>
subscribeProxies(proxies => cb(proxies.find(proxy => proxy.id === id))),
})),
({ id, proxy }) =>
proxy !== undefined ? (
<span>
<Icon icon='proxy' /> {proxy.name || proxy.address}
</span>
) : (
unknowItem(id, 'proxy')
),
])
Proxy.propTypes = {
id: PropTypes.string.isRequired,
}
// ===================================================================
export const Vgpu = connectStore(() => ({
vgpuType: createGetObject((_, props) => props.vgpu.vgpuType),
}))(({ vgpu, vgpuType }) => (
@ -399,6 +420,7 @@ const xoItemToRender = {
</span>
),
remote: ({ value: { id } }) => <Remote id={id} />,
proxy: ({ id }) => <Proxy id={id} />,
role: role => <span>{role.name}</span>,
user: user => (
<span>

View File

@ -45,6 +45,7 @@ import {
subscribeCurrentUser,
subscribeGroups,
subscribeIpPools,
subscribeProxies,
subscribeRemotes,
subscribeResourceSets,
subscribeRoles,
@ -843,6 +844,21 @@ export const SelectRemote = makeSubscriptionSelect(
// ===================================================================
export const SelectProxy = makeSubscriptionSelect(
subscriber =>
subscribeProxies(proxies => {
subscriber({
xoObjects: sortBy(proxies, 'name').map(proxy => ({
...proxy,
type: 'proxy',
})),
})
}),
{ placeholder: _('selectProxies') }
)
// ===================================================================
export const SelectResourceSet = makeSubscriptionSelect(
subscriber => {
const unsubscribeResourceSets = subscribeResourceSets(resourceSets => {

View File

@ -1,6 +1,7 @@
import asap from 'asap'
import cookies from 'cookies-js'
import fpSortBy from 'lodash/fp/sortBy'
import Icon from 'icon'
import pFinally from 'promise-toolbox/finally'
import React from 'react'
import reflect from 'promise-toolbox/reflect'
@ -306,6 +307,8 @@ export const subscribeRemotesInfo = createSubscription(() =>
_call('remote.getAllInfo')
)
export const subscribeProxies = createSubscription(() => _call('proxy.getAll'))
export const subscribeResourceSets = createSubscription(() =>
_call('resourceSet.getAll')
)
@ -2251,8 +2254,13 @@ export const getRemote = remote =>
error(_('getRemote'), err.message || String(err))
)
export const createRemote = (name, url, options) =>
_call('remote.create', { name, url, options })::tap(remote => {
export const createRemote = (name, url, options, proxy) =>
_call('remote.create', {
name,
options,
proxy: resolveId(proxy),
url,
})::tap(remote => {
testRemote(remote).catch(noop)
})
@ -2285,8 +2293,14 @@ export const disableRemote = remote =>
subscribeRemotes.forceRefresh
)
export const editRemote = (remote, { name, url, options }) =>
_call('remote.set', resolveIds({ remote, name, url, options }))::tap(() => {
export const editRemote = (remote, { name, options, proxy, url }) =>
_call('remote.set', {
id: resolveId(remote),
name,
options,
proxy: resolveId(proxy),
url,
})::tap(() => {
subscribeRemotes.forceRefresh()
testRemote(remote).catch(noop)
})
@ -3019,3 +3033,54 @@ export const subscribeTunnelState = createSubscription(() =>
)
export const getApplianceInfo = () => _call('xoa.getApplianceInfo')
// Proxy --------------------------------------------------------------------
export const deployProxyAppliance = sr =>
_call('proxy.deploy', { sr: resolveId(sr) })::tap(
subscribeProxies.forceRefresh
)
export const editProxyAppliance = (proxy, { vm, ...props }) =>
_call('proxy.update', {
id: resolveId(proxy),
vm: resolveId(vm),
...props,
})::tap(subscribeProxies.forceRefresh)
const _forgetProxyAppliance = proxy =>
_call('proxy.unregister', { id: resolveId(proxy) })
export const forgetProxyAppliances = proxies =>
confirm({
title: _('forgetProxyApplianceTitle', { n: proxies.length }),
body: _('forgetProxyApplianceMessage', { n: proxies.length }),
}).then(() =>
Promise.all(map(proxies, _forgetProxyAppliance))::tap(
subscribeProxies.forceRefresh
)
)
const _destroyProxyAppliance = proxy =>
_call('proxy.destroy', { id: resolveId(proxy) })
export const destroyProxyAppliances = proxies =>
confirm({
title: _('destroyProxyApplianceTitle', { n: proxies.length }),
body: _('destroyProxyApplianceMessage', { n: proxies.length }),
}).then(() =>
Promise.all(map(proxies, _destroyProxyAppliance))::tap(
subscribeProxies.forceRefresh
)
)
export const upgradeProxyAppliance = proxy =>
_call('proxy.upgradeAppliance', { id: resolveId(proxy) })
export const checkProxyHealth = proxy =>
_call('proxy.checkHealth', { id: resolveId(proxy) }).then(() =>
success(
<span>
<Icon icon='success' /> {_('proxyTestSuccess', { name: proxy.name })}
</span>,
_('proxyTestSuccessMessage')
)
)

View File

@ -11,6 +11,10 @@
@extend .fa;
@extend .fa-desktop;
}
&-proxy {
@extend .fa;
@extend .fa-globe;
}
&-remote {
@extend .fa;
@extend .fa-plug;
@ -157,6 +161,14 @@
@extend .fa;
@extend .fa-play;
}
&-forget {
@extend .fa;
@extend .fa-ban;
}
&-destroy {
@extend .fa;
@extend .fa-trash;
}
&-force-restart {
@extend .fa;
@extend .fa-forward;

View File

@ -15,7 +15,6 @@ import { Card, CardBlock, CardHeader } from 'card'
import { constructSmartPattern, destructSmartPattern } from 'smart-backup'
import { Container, Col, Row } from 'grid'
import { createGetObjectsOfType } from 'selectors'
import { flatten, includes, isEmpty, map, mapValues, omit, some } from 'lodash'
import { form } from 'modal'
import { generateId, linkState } from 'reaclette-utils'
import { injectIntl } from 'react-intl'
@ -23,7 +22,7 @@ import { injectState, provideState } from 'reaclette'
import { Map } from 'immutable'
import { Number } from 'form'
import { renderXoItemFromId, Remote } from 'render-xo-item'
import { SelectRemote, SelectSr, SelectVm } from 'select-objects'
import { SelectProxy, SelectRemote, SelectSr, SelectVm } from 'select-objects'
import {
addSubscriptions,
connectStore,
@ -40,6 +39,16 @@ import {
isSrWritable,
subscribeRemotes,
} from 'xo'
import {
flatten,
includes,
isEmpty,
keyBy,
map,
mapValues,
omit,
some,
} from 'lodash'
import NewSchedule from './new-schedule'
import ReportWhen from './_reportWhen'
@ -205,6 +214,7 @@ const getInitialState = ({ preSelectedVmIds, setHomeVmIdsSelection }) => {
setHomeVmIdsSelection([]) // Clear preselected vmIds
return {
_displayAdvancedSettings: undefined,
_proxyId: undefined,
_vmsPattern: undefined,
backupMode: false,
compression: undefined,
@ -227,6 +237,33 @@ const getInitialState = ({ preSelectedVmIds, setHomeVmIdsSelection }) => {
}
}
const RemoteProxyWarning = decorate([
addSubscriptions({
remotes: cb =>
subscribeRemotes(remotes => {
cb(keyBy(remotes, 'id'))
}),
}),
provideState({
computed: {
showWarning: (_, { id, proxyId, remotes = {} }) => {
const remote = remotes[id]
if (proxyId === null) {
proxyId = undefined
}
return remote !== undefined && remote.proxy !== proxyId
},
},
}),
injectState,
({ state }) =>
state.showWarning ? (
<Tooltip content={_('remoteNotCompatibleWithSelectedProxy')}>
<Icon icon='alarm' color='text-danger' />
</Tooltip>
) : null,
])
const DeleteOldBackupsFirst = ({ handler, handlerParam, value }) => (
<ActionButton
handler={handler}
@ -302,6 +339,7 @@ export default decorate([
name: state.name,
mode: state.isDelta ? 'delta' : 'full',
compression: state.compression,
proxy: state.proxyId === null ? undefined : state.proxyId,
schedules,
settings,
remotes:
@ -376,6 +414,7 @@ export default decorate([
name: state.name,
mode: state.isDelta ? 'delta' : 'full',
compression: state.compression,
proxy: state.proxyId,
settings: normalizeSettings({
offlineBackupActive: state.offlineBackupActive,
settings: settings || state.propSettings,
@ -578,6 +617,9 @@ export default decorate([
return getInitialState()
},
setCompression: (_, compression) => ({ compression }),
setProxy(_, proxy) {
this.state._proxyId = resolveId(proxy)
},
toggleDisplayAdvancedSettings: () => ({ displayAdvancedSettings }) => ({
_displayAdvancedSettings: !displayAdvancedSettings,
}),
@ -659,6 +701,7 @@ export default decorate([
formId: generateId,
inputConcurrencyId: generateId,
inputFullIntervalId: generateId,
inputProxyId: generateId,
inputTimeoutId: generateId,
// In order to keep the user preference, the offline backup is kept in the DB
@ -726,7 +769,12 @@ export default decorate([
),
selectedVmIds: state => resolveIds(state.vms),
srPredicate: ({ srs }) => sr => isSrWritable(sr) && !includes(srs, sr.id),
remotePredicate: ({ remotes }) => ({ id }) => !includes(remotes, id),
remotePredicate: ({ proxyId, remotes }) => remote => {
if (proxyId === null) {
proxyId = undefined
}
return !remotes.includes(remote.id) && remote.value.proxy === proxyId
},
propSettings: (_, { job }) =>
Map(get(() => job.settings)).map(setting =>
defined(
@ -750,6 +798,7 @@ export default decorate([
}
: setting
),
proxyId: (s, p) => defined(s._proxyId, () => p.job.proxy),
displayAdvancedSettings: (state, props) =>
defined(
state._displayAdvancedSettings,
@ -922,7 +971,11 @@ export default decorate([
<Ul>
{map(state.remotes, (id, key) => (
<Li key={id}>
<Remote id={id} />
<Remote id={id} />{' '}
<RemoteProxyWarning
id={id}
proxyId={state.proxyId}
/>
<div className='pull-right'>
<DeleteOldBackupsFirst
handler={effects.setTargetDeleteFirst}
@ -1017,6 +1070,16 @@ export default decorate([
</ActionButton>
</CardHeader>
<CardBlock>
<FormGroup>
<label htmlFor={state.inputProxyId}>
<strong>{_('proxy')}</strong>
</label>
<SelectProxy
id={state.inputProxyId}
onChange={effects.setProxy}
value={state.proxyId}
/>
</FormGroup>
<ReportWhen
onChange={effects.setReportWhen}
required

View File

@ -18,6 +18,7 @@ import { createSelector } from 'selectors'
import { get } from '@xen-orchestra/defined'
import { injectState, provideState } from 'reaclette'
import { isEmpty, map, groupBy, some } from 'lodash'
import { Proxy } from 'render-xo-item'
import {
cancelJob,
deleteBackupJobs,
@ -254,15 +255,20 @@ class JobsTable extends React.Component {
fullInterval,
offlineBackup,
offlineSnapshot,
proxyId,
reportWhen,
timeout,
} = getSettingsWithNonDefaultValue(job.mode, {
compression: job.compression,
proxyId: job.proxy,
...job.settings[''],
})
return (
<Ul>
{proxyId !== undefined && (
<Li>{_.keyValue(_('proxy'), <Proxy id={proxyId} />)}</Li>
)}
{reportWhen !== undefined && (
<Li>{_.keyValue(_('reportWhen'), reportWhen)}</Li>
)}

View File

@ -33,6 +33,7 @@ import New from './new'
import NewLegacyBackup from './backup/new-legacy-backup'
import NewVm from './new-vm'
import Pool from './pool'
import Proxies from './proxies'
import Self from './self'
import Settings from './settings'
import Sr from './sr'
@ -104,6 +105,7 @@ const BODY_STYLE = {
xosan: Xosan,
import: Import,
hub: Hub,
proxies: Proxies,
})
@connectStore(state => {
return {

View File

@ -346,6 +346,11 @@ export default class Menu extends Component {
},
],
},
isAdmin && {
to: '/proxies',
icon: 'proxy',
label: 'proxies',
},
isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
!noOperatablePools && {
to: '/tasks',

View File

@ -0,0 +1,201 @@
import _ from 'intl'
import ActionButton from 'action-button'
import addSubscriptions from 'add-subscriptions'
import decorate from 'apply-decorators'
import defined from '@xen-orchestra/defined'
import Icon from 'icon'
import NoObjects from 'no-objects'
import React from 'react'
import SortedTable from 'sorted-table'
import { adminOnly } from 'utils'
import { form } from 'modal'
import { SelectSr } from 'select-objects'
import { Text, XoSelect } from 'editable'
import { Vm } from 'render-xo-item'
import { withRouter } from 'react-router'
import {
checkProxyHealth,
deployProxyAppliance,
destroyProxyAppliances,
forgetProxyAppliances,
editProxyAppliance,
subscribeProxies,
upgradeProxyAppliance,
} from 'xo'
import Page from '../page'
const _deployProxy = () =>
form({
render: ({ onChange, value }) => (
<SelectSr onChange={onChange} value={value} required />
),
header: (
<span>
<Icon icon='proxy' /> {_('deployProxy')}
</span>
),
}).then(deployProxyAppliance)
const _editProxy = (value, { name, proxy }) =>
editProxyAppliance(proxy, { [name]: value })
const _editProxyAddress = (value, { proxy }) => {
value = value.trim()
return editProxyAppliance(proxy, {
address: value !== '' ? value : null,
})
}
const HEADER = (
<h2>
<Icon icon='proxy' /> {_('proxies')}
</h2>
)
const ACTIONS = [
{
handler: forgetProxyAppliances,
icon: 'forget',
label: _('forgetProxies'),
level: 'danger',
},
{
handler: destroyProxyAppliances,
icon: 'destroy',
label: _('destroyProxies'),
level: 'danger',
},
]
const INDIVIDUAL_ACTIONS = [
{
handler: checkProxyHealth,
icon: 'diagnosis',
label: _('checkProxyHealth'),
level: 'primary',
},
{
disabled: ({ vmUuid }) => vmUuid === undefined,
handler: upgradeProxyAppliance,
icon: 'vm',
label: _('upgradeProxyAppliance'),
level: 'primary',
},
{
handler: ({ id }, { router }) =>
router.push({
pathname: '/settings/remotes',
query: {
l: `proxy:${id}`,
nfs: `proxy:${id}`,
smb: `proxy:${id}`,
},
}),
icon: 'remote',
label: _('proxyLinkedRemotes'),
},
{
handler: ({ id }, { router }) =>
router.push({
pathname: '/backup/overview',
query: {
s: `proxy:${id}`,
},
}),
icon: 'backup',
label: _('proxyLinkedBackups'),
},
]
const COLUMNS = [
{
default: true,
itemRenderer: proxy => (
<Text
data-name='name'
data-proxy={proxy}
onChange={_editProxy}
value={proxy.name}
/>
),
name: _('name'),
sortCriteria: 'name',
},
{
itemRenderer: proxy => (
<Text
data-proxy={proxy}
onChange={_editProxyAddress}
value={defined(proxy.address, '')}
/>
),
name: _('address'),
sortCriteria: 'address',
},
{
itemRenderer: proxy => (
<XoSelect
onChange={value => _editProxy(value, { name: 'vm', proxy })}
value={proxy.vmUuid}
xoType='VM'
>
{proxy.vmUuid !== undefined ? (
<div>
<Vm id={proxy.vmUuid} />{' '}
<a
role='button'
onClick={() => _editProxy(null, { name: 'vm', proxy })}
>
<Icon icon='remove' />
</a>
</div>
) : (
_('noValue')
)}
</XoSelect>
),
name: _('vm'),
sortCriteria: 'vm',
},
]
export default decorate([
adminOnly,
withRouter,
addSubscriptions({
proxies: subscribeProxies,
}),
({ proxies, router }) => (
<Page header={HEADER} title='proxies' formatTitle>
<div>
<div className='mt-1 mb-1'>
<ActionButton
btnStyle='success'
handler={_deployProxy}
icon='proxy'
size='large'
>
{_('deployProxy')}
</ActionButton>
</div>
<NoObjects
actions={ACTIONS}
collection={proxies}
columns={COLUMNS}
component={SortedTable}
data-router={router}
emptyMessage={
<span className='text-muted'>
<Icon icon='alarm' />
&nbsp;
{_('noProxiesAvailable')}
</span>
}
individualActions={INDIVIDUAL_ACTIONS}
stateUrlParam='s'
/>
</div>
</Page>
),
])

View File

@ -19,7 +19,8 @@ import { get } from '@xen-orchestra/defined'
import { groupBy, map, isEmpty } from 'lodash'
import { injectIntl } from 'react-intl'
import { injectState, provideState } from 'reaclette'
import { Number, Password, Text } from 'editable'
import { Number, Password, Text, XoSelect } from 'editable'
import { Proxy } from 'render-xo-item'
import {
deleteRemote,
@ -119,6 +120,28 @@ const COLUMN_SPEED = {
),
}
const COLUMN_PROXY = {
itemRenderer: remote => (
<XoSelect
onChange={proxy => editRemote(remote, { proxy })}
value={remote.proxy}
xoType='proxy'
>
{remote.proxy !== undefined ? (
<div>
<Proxy id={remote.proxy} />{' '}
<a role='button' onClick={() => editRemote(remote, { proxy: null })}>
<Icon icon='remove' />
</a>
</div>
) : (
_('noValue')
)}
</XoSelect>
),
name: _('proxy'),
}
const fixRemoteUrl = remote => editRemote(remote, { url: format(remote) })
const COLUMNS_LOCAL_REMOTE = [
COLUMN_NAME,
@ -137,6 +160,7 @@ const COLUMNS_LOCAL_REMOTE = [
COLUMN_STATE,
COLUMN_DISK,
COLUMN_SPEED,
COLUMN_PROXY,
]
const COLUMNS_NFS_REMOTE = [
COLUMN_NAME,
@ -199,6 +223,7 @@ const COLUMNS_NFS_REMOTE = [
COLUMN_STATE,
COLUMN_DISK,
COLUMN_SPEED,
COLUMN_PROXY,
]
const COLUMNS_SMB_REMOTE = [
COLUMN_NAME,
@ -266,6 +291,7 @@ const COLUMNS_SMB_REMOTE = [
name: _('remoteAuth'),
},
COLUMN_SPEED,
COLUMN_PROXY,
]
const GROUPED_ACTIONS = [

View File

@ -3,7 +3,7 @@ import ActionButton from 'action-button'
import decorate from 'apply-decorators'
import Icon from 'icon'
import React from 'react'
import { addSubscriptions } from 'utils'
import { addSubscriptions, resolveId } from 'utils'
import { alert, confirm } from 'modal'
import { createRemote, editRemote, subscribeRemotes } from 'xo'
import { error } from 'notification'
@ -12,6 +12,7 @@ import { generateId, linkState } from 'reaclette-utils'
import { injectState, provideState } from 'reaclette'
import { map, some, trimStart } from 'lodash'
import { Password, Number } from 'form'
import { SelectProxy } from 'select-objects'
const remoteTypes = {
file: 'remoteTypeLocal',
@ -32,6 +33,7 @@ export default decorate([
password: undefined,
path: undefined,
port: undefined,
proxyId: undefined,
type: undefined,
username: undefined,
}),
@ -40,6 +42,9 @@ export default decorate([
setPort: (_, port) => state => ({
port: port === undefined && state.remote !== undefined ? '' : port,
}),
setProxy(_, proxy) {
this.state.proxyId = resolveId(proxy)
},
editRemote: ({ reset }) => state => {
const {
remote,
@ -50,6 +55,7 @@ export default decorate([
password = remote.password,
path = remote.path,
port = remote.port,
proxyId = remote.proxy,
type = remote.type,
username = remote.username,
} = state
@ -65,6 +71,7 @@ export default decorate([
username,
}),
options: options !== '' ? options : null,
proxy: proxyId,
}).then(reset)
},
createRemote: ({ reset }) => async (state, { remotes }) => {
@ -85,6 +92,7 @@ export default decorate([
password,
path,
port,
proxyId,
type = 'nfs',
username,
} = state
@ -107,7 +115,12 @@ export default decorate([
}
const url = format(urlParams)
return createRemote(name, url, options !== '' ? options : undefined)
return createRemote(
name,
url,
options !== '' ? options : undefined,
proxyId === null ? undefined : proxyId
)
.then(reset)
.catch(err => error('Create Remote', err.message || String(err)))
},
@ -130,6 +143,7 @@ export default decorate([
parsedPath,
path = parsedPath || '',
port = remote.port,
proxyId = remote.proxy,
type = remote.type || 'nfs',
username = remote.username || '',
} = state
@ -168,6 +182,9 @@ export default decorate([
value={name}
/>
</div>
<div className='form-group'>
<SelectProxy onChange={effects.setProxy} value={proxyId} />
</div>
{type === 'file' && (
<fieldset className='form-group'>
<div className='input-group'>

View File

@ -1750,12 +1750,13 @@ array-flatten@1.1.1:
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
array-includes@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.0.tgz#48a929ef4c6bb1fa6dc4a92c9b023a261b0ca404"
integrity sha512-ONOEQoKrvXPKk7Su92Co0YMqYO32FfqJTzkKU9u2UpIXyYZIzLSvpdg4AwvSw4mSUW0czu6inK+zby6Oj6gDjQ==
version "3.1.1"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==
dependencies:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.0"
es-abstract "^1.17.0"
is-string "^1.0.5"
array-initial@^1.0.0:
version "1.1.0"
@ -5237,7 +5238,7 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
es-abstract@^1.13.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1:
es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1:
version "1.17.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0.tgz#f42a517d0036a5591dbb2c463591dc8bb50309b1"
integrity sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==
@ -10104,9 +10105,9 @@ npm-run-path@^3.0.0:
path-key "^3.0.0"
npm-run-path@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.0.tgz#d644ec1bd0569187d2a52909971023a0a58e8438"
integrity sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ==
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
@ -12602,9 +12603,9 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
set-cookie-parser@^2.3.5:
version "2.4.3"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.3.tgz#9c917e75698a5633511c3c6a3435f334faabc240"
integrity sha512-+Eovq+TUyhqwUe+Ac9EaPlfEZOcQyy7uUPhcbEXEIsH73x/gOU56RO8wZDZW98fu3vSxhcPjuKDo1mIrmM7ixw==
version "2.4.1"
resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.1.tgz#b77d0020241628b8ce12b16ec8af492e773af31a"
integrity sha512-bUcyZhIOLvjvdO7UT5MyNd6AAci4ssZy/8Da1etJVI/2n3vgpxfJ/ntYA0bN+8qg9HZB7xR9/g+fcDbGkk/gQg==
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"