From 2b5f7dc8def47bfed3ee5fab946d5c2551443be9 Mon Sep 17 00:00:00 2001
From: "Rajaa.BARHTAOUI"
Date: Wed, 31 Jan 2018 15:13:25 +0100
Subject: [PATCH] feat(settings/remotes): use SortedTable (#2465)
See #2416
---
src/common/intl/messages.js | 10 +-
src/common/xo/index.js | 14 +
src/xo-app/settings/remotes/index.js | 554 +++++++++++++--------------
3 files changed, 282 insertions(+), 296 deletions(-)
diff --git a/src/common/intl/messages.js b/src/common/intl/messages.js
index 91f62f841..e86f61ad6 100644
--- a/src/common/intl/messages.js
+++ b/src/common/intl/messages.js
@@ -36,6 +36,8 @@ const messages = {
filterSyntaxLinkTooltip: 'Explore the search syntax in the documentation',
filterVifsOnlyConnected: 'Connected VIFs',
filterVifsOnlyDisconnected: 'Disconnected VIFs',
+ filterRemotesOnlyConnected: 'Connected remotes',
+ filterRemotesOnlyDisconnected: 'Disconnected remotes',
// ----- Copiable component -----
copyToClipboard: 'Copy to clipboard',
@@ -359,16 +361,17 @@ const messages = {
remoteConnected: 'Connected',
remoteDisconnected: 'Disconnected',
remoteDeleteTip: 'Delete',
+ remoteDeleteSelected: 'Delete selected remotes',
remoteNamePlaceHolder: 'remote name *',
remoteMyNamePlaceHolder: 'Name *',
remoteLocalPlaceHolderPath: '/path/to/backup',
remoteNfsPlaceHolderHost: 'host *',
remoteNfsPlaceHolderPath: 'path/to/backup',
- remoteSmbPlaceHolderRemotePath: 'subfolder [path\\to\\backup]',
+ remoteSmbPlaceHolderRemotePath: 'subfolder [path\\\\to\\\\backup]',
remoteSmbPlaceHolderUsername: 'Username',
remoteSmbPlaceHolderPassword: 'Password',
remoteSmbPlaceHolderDomain: 'Domain',
- remoteSmbPlaceHolderAddressShare: '\\ *',
+ remoteSmbPlaceHolderAddressShare: '\\\\ *',
remotePlaceHolderPassword: 'password(fill to edit)',
// ------ New Storage -----
@@ -1238,6 +1241,9 @@ const messages = {
'Delete schedule{nSchedules, plural, one {} other {s}}',
deleteSchedulesModalMessage:
'Are you sure you want to delete {nSchedules, number} schedule{nSchedules, plural, one {} other {s}}?',
+ deleteRemotesModalTitle: 'Delete remote{nRemotes, plural, one {} other {s}}',
+ deleteRemotesModalMessage:
+ 'Are you sure you want to delete {nRemotes, number} remote{nRemotes, plural, one {} other {s}}?',
revertVmModalTitle: 'Revert your VM',
deleteVifsModalTitle: 'Delete VIF{nVifs, plural, one {} other {s}}',
deleteVifsModalMessage:
diff --git a/src/common/xo/index.js b/src/common/xo/index.js
index a59f3fd39..4cd9b37ac 100644
--- a/src/common/xo/index.js
+++ b/src/common/xo/index.js
@@ -1659,6 +1659,20 @@ export const deleteRemote = remote =>
subscribeRemotes.forceRefresh
)
+export const deleteRemotes = remotes =>
+ confirm({
+ title: _('deleteRemotesModalTitle', { nRemotes: remotes.length }),
+ body: _('deleteRemotesModalMessage', { nRemotes: remotes.length }),
+ }).then(
+ () =>
+ Promise.all(
+ map(remotes, remote =>
+ _call('remote.delete', { id: resolveId(remote) })
+ )
+ )::tap(subscribeRemotes.forceRefresh),
+ noop
+ )
+
export const enableRemote = remote =>
_call('remote.set', { id: resolveId(remote), enabled: true })::tap(
subscribeRemotes.forceRefresh
diff --git a/src/xo-app/settings/remotes/index.js b/src/xo-app/settings/remotes/index.js
index c67982121..f956d3b26 100644
--- a/src/xo-app/settings/remotes/index.js
+++ b/src/xo-app/settings/remotes/index.js
@@ -1,12 +1,12 @@
import _, { messages } from 'intl'
import ActionButton from 'action-button'
-import ActionRowButton from 'action-row-button'
import filter from 'lodash/filter'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import React, { Component } from 'react'
import some from 'lodash/some'
+import SortedTable from 'sorted-table'
import StateButton from 'state-button'
import Tooltip from 'tooltip'
import { addSubscriptions } from 'utils'
@@ -19,6 +19,7 @@ import { injectIntl } from 'react-intl'
import {
createRemote,
deleteRemote,
+ deleteRemotes,
disableRemote,
editRemote,
enableRemote,
@@ -31,255 +32,225 @@ const remoteTypes = {
nfs: 'remoteTypeNfs',
smb: 'remoteTypeSmb',
}
-
-class AbstractRemote extends Component {
- _changeUrlElement = (value, element) => {
- const remote = { ...this.props.remote }
- remote[element] = value
- const url = format(remote)
- return editRemote(remote, { url })
- }
-
- _showError = () => alert(_('remoteConnectionFailed'), this.props.remote.error)
-
- _changeName = name => {
- const { remote } = this.props
- return editRemote(remote, { name })
- }
-
- _test = () => {
- const { remote } = this.props
- testRemote(remote).then(answer => {
- const title = (
-
- {' '}
- {_(answer.success ? 'remoteTestSuccess' : 'remoteTestFailure', {
- name: remote.name,
- })}
-
+const _changeUrlElement = (remote, value, element) =>
+ editRemote(remote, { url: format({ ...remote, [element]: value }) })
+const _showError = remote => alert(_('remoteConnectionFailed'), remote.error)
+const COLUMN_NAME = {
+ component: @injectIntl
+ class RemoteName extends Component {
+ render () {
+ const { item: remote, intl } = this.props
+ return (
+ editRemote(remote, { name })}
+ placeholder={intl.formatMessage(messages.remoteMyNamePlaceHolder)}
+ value={remote.name}
+ />
)
- let body
- if (answer.success) {
- body = _('remoteTestSuccessMessage')
- } else {
- body = (
-
-
- - {_('remoteTestError')}
- - {answer.error}
- - {_('remoteTestStep')}
- - {answer.step}
-
-
+ }
+ },
+ name: _('remoteName'),
+ sortCriteria: 'name',
+}
+const COLUMN_STATE = {
+ itemRenderer: remote => (
+
+ ),
+ name: _('remoteState'),
+}
+
+const COLUMNS_LOCAL_REMOTE = [
+ COLUMN_NAME,
+ {
+ component: @injectIntl
+ class LocalRemotePath extends Component {
+ render () {
+ const { item: remote, intl } = this.props
+ return (
+ _changeUrlElement(remote, v, 'path')}
+ placeholder={intl.formatMessage(
+ messages.remoteLocalPlaceHolderPath
+ )}
+ value={remote.path}
+ />
)
}
- alert(title, body)
- })
- }
-
- render () {
- const { remote } = this.props
-
- return (
-
- |
-
-
- |
- {this._renderRemoteInfo(remote)} |
- {this._renderAuthInfo(remote)} |
-
- {' '}
- {remote.error && (
-
-
-
-
-
- )}
- |
-
- {remote.enabled && (
-
-
-
- )}{' '}
-
-
+ \\
+ _changeUrlElement(remote, v, 'host')}
+ placeholder={intl.formatMessage(
+ messages.remoteNfsPlaceHolderHost
+ )}
+ value={remote.host}
/>
-
- |
-
- )
- }
+ :
+ _changeUrlElement(remote, v, 'path')}
+ placeholder={intl.formatMessage(
+ messages.remoteNfsPlaceHolderPath
+ )}
+ value={remote.path}
+ />
+
+ )
+ }
+ },
+ name: _('remoteDevice'),
+ },
+ COLUMN_STATE,
+]
+const COLUMNS_SMB_REMOTE = [
+ COLUMN_NAME,
+ {
+ component: @injectIntl
+ class SmbRemoteInfo extends Component {
+ render () {
+ const { item: remote, intl } = this.props
+ return (
+
+ \\
+ _changeUrlElement(remote, v, 'host')}
+ />
+ \
+
+ _changeUrlElement(remote, v, 'path')}
+ placeholder={intl.formatMessage(
+ messages.remoteSmbPlaceHolderRemotePath
+ )}
+ value={remote.path}
+ />
+
+
+ )
+ }
+ },
+ name: _('remoteShare'),
+ },
+ COLUMN_STATE,
+ {
+ component: @injectIntl
+ class SmbRemoteAuthInfo extends Component {
+ render () {
+ const { item: remote, intl } = this.props
+ return (
+
+ _changeUrlElement(remote, v, 'username')}
+ />
+ :
+ _changeUrlElement(remote, v, 'password')}
+ placeholder={intl.formatMessage(
+ messages.remotePlaceHolderPassword
+ )}
+ />
+ @
+ _changeUrlElement(remote, v, 'domain')}
+ />
+
+ )
+ }
+ },
+ name: _('remoteAuth'),
+ },
+]
- _renderRemoteInfo () {
- throw new Error('NOT IMPLEMENTED')
- }
+const GROUPED_ACTIONS = [
+ {
+ handler: deleteRemotes,
+ icon: 'delete',
+ label: _('remoteDeleteSelected'),
+ level: 'danger',
+ },
+]
- _renderAuthInfo () {
- throw new Error('NOT IMPLEMENTED')
- }
-
- get accessible () {
- throw new Error('NOT IMPLEMENTED')
- }
-
- get unaccessible () {
- throw new Error('NOT IMPLEMENTED')
- }
-}
-
-@injectIntl
-class LocalRemote extends AbstractRemote {
- _renderRemoteInfo () {
- const { remote } = this.props
- return (
- this._changeUrlElement(v, 'path')}
- placeholder={this.props.intl.formatMessage(
- messages.remoteLocalPlaceHolderPath
- )}
- />
- )
- }
-
- _renderAuthInfo () {
- return ''
- }
-
- get accessible () {
- return 'Accessible'
- }
-
- get unaccessible () {
- return 'Unaccessible'
- }
-}
-
-@injectIntl
-class NfsRemote extends AbstractRemote {
- _renderRemoteInfo () {
- const { remote } = this.props
- return (
-
- this._changeUrlElement(v, 'host')}
- placeholder={this.props.intl.formatMessage(
- messages.remoteNfsPlaceHolderHost
- )}
- />
- :
- this._changeUrlElement(v, 'path')}
- placeholder={this.props.intl.formatMessage(
- messages.remoteNfsPlaceHolderPath
- )}
- />
-
- )
- }
-
- _renderAuthInfo () {
- return ''
- }
-
- get accessible () {
- return _('remoteMounted')
- }
-
- get unaccessible () {
- return _('remoteUnmounted')
- }
-}
-
-@injectIntl
-class SmbRemote extends AbstractRemote {
- _renderRemoteInfo () {
- const { remote } = this.props
- return (
-
- \\
- this._changeUrlElement(v, 'host')}
- />
- \
-
- this._changeUrlElement(v, 'path')}
- placeholder={this.props.intl.formatMessage(
- messages.remoteSmbPlaceHolderRemotePath
- )}
- />
-
-
- )
- }
-
- _renderAuthInfo () {
- const { remote } = this.props
- return (
-
- this._changeUrlElement(v, 'username')}
- />
- :
- this._changeUrlElement(v, 'password')}
- placeholder={this.props.intl.formatMessage(
- messages.remotePlaceHolderPassword
- )}
- />
- @
- this._changeUrlElement(v, 'domain')}
- />
-
- )
- }
-
- get accessible () {
- return 'Accessible'
- }
-
- get unaccessible () {
- return 'Unaccessible'
- }
+const INDIVIDUAL_ACTIONS = [
+ {
+ disabled: remote => !remote.enabled,
+ handler: remote =>
+ testRemote(remote).then(
+ answer =>
+ answer.success
+ ? alert(
+
+ {' '}
+ {_('remoteTestSuccess', { name: remote.name })}
+ ,
+ _('remoteTestSuccessMessage')
+ )
+ : alert(
+
+ {' '}
+ {_('remoteTestFailure', { name: remote.name })}
+ ,
+
+
+ - {_('remoteTestError')}
+ - {answer.error}
+ - {_('remoteTestStep')}
+ - {answer.step}
+
+
+ )
+ ),
+ icon: 'diagnosis',
+ label: _('remoteTestTip'),
+ level: 'primary',
+ },
+ {
+ handler: deleteRemote,
+ icon: 'delete',
+ label: _('remoteDeleteTip'),
+ level: 'danger',
+ },
+]
+const FILTERS = {
+ filterRemotesOnlyConnected: 'enabled?',
+ filterRemotesOnlyDisconnected: '!enabled?',
}
@addSubscriptions({
@@ -351,53 +322,48 @@ export default class Remotes extends Component {
return (
-
- {!isEmpty(remotes.file) && (
-
-
- {_('remoteTypeLocal')} |
- {_('remoteName')} |
- {_('remotePath')} |
- |
- {_('remoteState')} |
- {_('remoteAction')} |
-
- {map(remotes.file, (remote, key) => (
-
- ))}
-
- )}
- {!isEmpty(remotes.nfs) && (
-
-
- {_('remoteTypeNfs')} |
- {_('remoteName')} |
- {_('remoteDevice')} |
- |
- {_('remoteState')} |
- {_('remoteAction')} |
-
- {map(remotes.nfs, (remote, key) => (
-
- ))}
-
- )}
- {!isEmpty(remotes.smb) && (
-
-
- {_('remoteTypeSmb')} |
- {_('remoteName')} |
- {_('remoteShare')} |
- {_('remoteAuth')} |
- {_('remoteState')} |
- {_('remoteAction')} |
-
- {map(remotes.smb, (remote, key) => (
-
- ))}
-
- )}
-
+ {!isEmpty(remotes.file) && (
+
+
{_('remoteTypeLocal')}
+
+
+ )}
+
+ {!isEmpty(remotes.nfs) && (
+
+
{_('remoteTypeNfs')}
+
+
+ )}
+
+ {!isEmpty(remotes.smb) && (
+
+
{_('remoteTypeSmb')}
+
+
+ )}
+
{_('newRemote')}