feat(settings): remote management (#975)

This commit is contained in:
Fabrice Marsaud
2016-05-26 11:46:22 +02:00
committed by Julien Fontanet
parent e5cc5abdc9
commit 143e09b65f
6 changed files with 347 additions and 1 deletions
+19
View File
@@ -358,6 +358,25 @@ export const messages = {
defaultMessage: 'Select your backup type:'
},
// ------ New Remote -----
remoteList: {
defaultMessage: 'Remote stores for backup'
},
newRemote: {
defaultMessage: 'New File System Remote'
},
remoteTypeLocal: {
defaultMessage: 'Local'
},
remoteTypeNfs: {
defaultMessage: 'NFS'
},
remoteTypeSmb: {
defaultMessage: 'SMB'
},
remoteType: {
defaultMessage: 'Type'
},
// ----- Plugins ------
autoloadPlugin: {
+18
View File
@@ -492,3 +492,21 @@ export const purgePluginConfiguration = async id => {
throw error
}
}
// -------------------------------------------------------------------
export const createRemote = (name, url) => (
xo.call('remote.create', {name, url})
)
export const deleteRemote = id => (
xo.call('remote.delete', {id})
)
export const enableRemote = id => (
xo.call('remote.set', {id, enabled: true})
)
export const disableRemote = id => (
xo.call('remote.set', {id, enabled: false})
)
+4
View File
@@ -111,6 +111,10 @@
@extend .fa;
@extend .fa-database;
}
&-delete {
@extend .fa;
@extend .fa-trash;
}
// Backups
&-backup {
+1 -1
View File
@@ -28,8 +28,8 @@ import Vm from './vm'
dashboard: Dashboard,
home: Home,
'hosts/:id': Host,
'pools/:id': Pool,
new: New,
'pools/:id': Pool,
settings: Settings,
'srs/:id': Sr,
user: User,
+2
View File
@@ -1,10 +1,12 @@
import { routes } from 'utils'
import Plugins from './plugins'
import Remotes from './remotes'
import Servers from './servers'
const Settings = routes('servers', {
plugins: Plugins,
remotes: Remotes,
servers: Servers
})(
({ children }) => children
+303
View File
@@ -0,0 +1,303 @@
import _ from 'messages'
import ActionButton from 'action-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 { error } from 'notification'
import {
createRemote,
deleteRemote,
disableRemote,
enableRemote,
subscribeRemotes
} from 'xo'
import { format, parse } from 'xo-remote-parser'
const remoteTypes = {
local: 'remoteTypeLocal',
nfs: 'remoteTypeNfs',
smb: 'remoteTypeSmb'
}
class AbstractRemote extends Component {
_disable = id => disableRemote(id).then(subscribeRemotes.forceRefresh)
_enable = id => enableRemote(id).then(subscribeRemotes.forceRefresh)
_delete = id => deleteRemote(id).then(subscribeRemotes.forceRefresh)
render () {
const {
remote
} = this.props
return <tr>
<td></td>
<td>{remote.name}</td>
<td>{this._renderRemoteInfo(remote)}</td>
<td>{this._renderAuthInfo(remote)}</td>
<td>
{remote.enabled &&
<span>
<span className='text-success'>{this.accessible} <Icon icon='success' /></span>
{' '}
<ActionButton btnStyle='warning' handler={this._disable} handlerParam={remote.id} icon='disconnect' />
</span>
}
{!remote.enabled &&
<span>
<span className='text-muted'>{this.unaccessible}</span>
{' '}
<ActionButton btnStyle='primary' handler={this._enable} handlerParam={remote.id} icon='connect' />
</span>
}
</td>
<td><span className='text-muted'>{remote.error}</span></td>
<td><ActionButton btnStyle='danger' handler={this._delete} handlerParam={remote.id} icon='delete' /></td>
</tr>
}
_renderRemoteInfo (remote) {
throw new Error('NOT IMPLEMENTED')
}
_renderAuthInfo (remote) {
throw new Error('NOT IMPLEMENTED')
}
get accessible () {
throw new Error('NOT IMPLEMENTED')
}
get unaccessible () {
throw new Error('NOT IMPLEMENTED')
}
}
class LocalRemote extends AbstractRemote {
_renderRemoteInfo (remote) {
return remote.path
}
_renderAuthInfo (remote) {
return ''
}
get accessible () {
return 'Accessible'
}
get unaccessible () {
return 'Unaccessible'
}
}
class NfsRemote extends AbstractRemote {
_renderRemoteInfo (remote) {
return `${remote.host}:${remote.share}`
}
_renderAuthInfo (remote) {
return ''
}
get accessible () {
return 'Mounted'
}
get unaccessible () {
return 'Unmounted'
}
}
class SmbRemote extends AbstractRemote {
_renderRemoteInfo (remote) {
return <span><strong className='text-info'>\\</strong>{remote.host}{remote.path && <span><strong className='text-info'>\</strong>{remote.path}</span>}</span>
}
_renderAuthInfo (remote) {
return <span>{remote.username}@{remote.domain}</span>
}
get accessible () {
return 'Accessible'
}
get unaccessible () {
return 'Unaccessible'
}
}
export default class New extends Component {
constructor (props) {
super(props)
this.state = {
remotes: {},
type: 'local'
}
}
componentWillMount () {
this.componentWillUnmount = subscribeRemotes(rawRemotes => {
rawRemotes = map(rawRemotes, parse)
const remotes = {}
for (let type in remoteTypes) {
remotes[type] = filter(rawRemotes, r => r.type === type)
}
this.setState({
remotes
})
})
}
_handleRemoteTypeSelection = type => this.setState({type})
_createRemote = async () => {
const {
name,
host,
path,
username,
password,
domain
} = this.refs
const {
type
} = this.state
const urlParams = {
type,
host: host && host.value,
path: path && path.value
}
username && (urlParams.username = username.value)
password && (urlParams.password = password.value)
domain && (urlParams.domain = domain.value)
const url = format(urlParams)
return createRemote(name && name.value, url).then(() => {
subscribeRemotes.forceRefresh()
this.setState({type: 'local'})
path && (path.value = '')
username && (username.value = '')
password && (password.value = '')
domain && (domain.value = '')
}, err => error('Create Remote', err.message || String(err)))
}
render () {
const {
remotes,
type
} = this.state
return (
<div>
<h2>{_('remoteList')}</h2>
<table className='table table-hover'>
{!isEmpty(remotes.local) &&
<tbody>
<tr>
<th className='text-info'>Local</th>
<th>Name</th>
<th>Path</th>
<th></th>
<th>State</th>
<th>Error</th>
<th></th>
</tr>
{map(remotes.local, (remote, key) => <LocalRemote remote={remote} key={key} />)}
</tbody>
}
{!isEmpty(remotes.nfs) &&
<tbody>
<tr>
<th className='text-info'>NFS</th>
<th>Name</th>
<th>Device</th>
<th></th>
<th>State</th>
<th>Error</th>
<th></th>
</tr>
{map(remotes.nfs, (remote, key) => <NfsRemote remote={remote} key={key} />)}
</tbody>
}
{!isEmpty(remotes.smb) &&
<tbody>
<tr>
<th className='text-info'>SMB</th>
<th>Name</th>
<th>Share</th>
<th>Auth</th>
<th>State</th>
<th>Error</th>
<th></th>
</tr>
{map(remotes.smb, (remote, key) => <SmbRemote remote={remote} key={key} />)}
</tbody>
}
</table>
<h2>{_('newRemote')}</h2>
<form id='newRemoteForm'>
<div className='form-group'>
<label htmlFor='newRemoteType'>{_('remoteType')}</label>
<select
id='newRemoteType'
className='form-control'
defaultValue={type}
onChange={event => { this._handleRemoteTypeSelection(event.target.value) }}
required
>
{map(remoteTypes, (label, key) => _({key}, label, message => <option value={key}>{message}</option>))}
</select>
</div>
<div className='form-group'>
<input type='text' ref='name' className='form-control' placeholder='Name *' required />
</div>
{type === 'local' &&
<fieldset className='form-group'>
<div className='input-group'>
<span className='input-group-addon'>/</span>
<input type='text' ref='path' pattern='^(([^/]+)+(/[^/]+)*)?$' className='form-control' placeholder='path/to/backup' />
</div>
</fieldset>
}
{type === 'nfs' &&
<fieldset className='form-group'>
<div className='form-group'>
<input type='text' ref='host' className='form-control' placeholder='host *' required />
</div>
<div className='input-group'>
<span className='input-group-addon'>/</span>
<input type='text' ref='path' pattern='^(([^/]+)+(/[^/]+)*)?$' className='form-control' placeholder='path/to/backup' />
</div>
</fieldset>
}
{type === 'smb' &&
<fieldset className='form-group'>
<div className='input-group form-group'>
<span className='input-group-addon'>\\</span>
<input type='text' ref='host' pattern='^([^\\/]+)\\([^\\/]+)$' className='form-control' placeholder='<address>\<share> *' required />
<span className='input-group-addon'>\</span>
<input type='text' ref='path' pattern='^(([^\\/]+)+(\\[^\\/]+)*)?$' className='form-control' placeholder='subfolder [path\to\backup]' />
</div>
<div className='form-group'>
<input type='text' ref='username' className='form-control' placeholder='User Name' />
</div>
<div className='form-group'>
<input type='text' ref='password' className='form-control' placeholder='Password' />
</div>
<div className='form-group'>
<input type='text' ref='domain' className='form-control' placeholder='Domain' />
</div>
</fieldset>
}
<div className='form-group'>
<ActionButton type='submit' form='newRemoteForm' icon='save' btnStyle='primary' handler={this._createRemote}>
{_('savePluginConfiguration')}
</ActionButton>
</div>
</form>
</div>
)
}
}