Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 8CED8200B89 for ; Wed, 21 Sep 2016 17:18:57 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 8BA61160ABC; Wed, 21 Sep 2016 15:18:57 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 61F99160ADE for ; Wed, 21 Sep 2016 17:18:55 +0200 (CEST) Received: (qmail 86419 invoked by uid 500); 21 Sep 2016 15:18:53 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 85322 invoked by uid 99); 21 Sep 2016 15:18:53 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 21 Sep 2016 15:18:53 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 1663CE00C4; Wed, 21 Sep 2016 15:18:53 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: garren@apache.org To: commits@couchdb.apache.org Date: Wed, 21 Sep 2016 15:19:10 -0000 Message-Id: In-Reply-To: <110d4ab9069244b3896f97c915df459f@git.apache.org> References: <110d4ab9069244b3896f97c915df459f@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [19/29] fauxton commit: updated refs/heads/new-replication to b0541e1 archived-at: Wed, 21 Sep 2016 15:18:57 -0000 clean up react components and css Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/ecea635a Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/ecea635a Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/ecea635a Branch: refs/heads/new-replication Commit: ecea635ab9a13f03e579e9a8673a053b0224d032 Parents: a76ef79 Author: Garren Smith Authored: Wed Aug 17 17:14:54 2016 +0200 Committer: Garren Smith Committed: Wed Sep 14 17:22:30 2016 +0200 ---------------------------------------------------------------------- app/addons/replication/actions.js | 1 + .../replication/assets/less/replication.less | 132 ++++++- app/addons/replication/components.react.jsx | 343 +----------------- app/addons/replication/components/options.js | 92 +++++ app/addons/replication/components/source.js | 164 +++++++++ app/addons/replication/components/submit.js | 34 ++ app/addons/replication/components/target.js | 202 +++++++++++ app/addons/replication/controller.js | 349 +++++++++++++++++++ app/addons/replication/helpers.js | 7 - app/addons/replication/route.js | 7 +- 10 files changed, 967 insertions(+), 364 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js index 72e1909..5c2fde5 100644 --- a/app/addons/replication/actions.js +++ b/app/addons/replication/actions.js @@ -68,6 +68,7 @@ function replicate (params) { } function updateFormField (fieldName, value) { + console.log('ff', fieldName, value); FauxtonAPI.dispatch({ type: ActionTypes.REPLICATION_UPDATE_FORM_FIELD, options: { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/assets/less/replication.less ---------------------------------------------------------------------- diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less index 4e97de0..a0ba9d0 100644 --- a/app/addons/replication/assets/less/replication.less +++ b/app/addons/replication/assets/less/replication.less @@ -13,10 +13,124 @@ @import "../../../../../assets/less/variables.less"; @import "../../../../../assets/less/mixins.less"; -.replication-page { +div.replication-page { + padding-top: 25px !important; + display: flex; + flex-direction: column; +} + +.replication-section { + display: flex; + flex-direction: row; +} + +.replication-seperator { + margin: 6px 0 15px; +} + +.replication-input-label { + padding-right: 15px; + width: 220px; + text-align: right; + margin-top: 12px; font-size: 14px; + margin-left: 10px; + margin-right: 10px; +} - input, select { +.replication-input-select { + width: 540px; + select { + font-size: 14px; + width: 246px; + margin-bottom: 10px; + background-color: white; + border: 1px solid #cccccc; + } + .styled-select { + width: 250px; + } +} + +.replication-input-react-select { + font-size: 14px; + + .Select div.Select-control { + padding: 6px; + border: 1px solid #cccccc; + width: 246px; + + .Select-value, .Select-placeholder { + padding: 6px 10px; + } + + input { + margin-left: -6px; + } + + .Select-arrow-zone { + padding: 0; + width: 18px; + color: black; + } + } +} + +.replication-remote-connection-url { + font-size: 14px; + width: 100%; +} + +.replication-remote-connection-url-text { + font-size: 9pt; + color: #999999; + margin-bottom: 8px; +} + +.replication-new-input { + width: 248px; + font-size: 14px; +} + +.replication-doc-name { + position: relative; + width: 250px; + +} + +.replication-doc-name-icon { + cursor: pointer; + position: absolute; + right: 6px; + top: 8px; + font-size: 11px; + padding: 8px; + color: #999999; + .transition(all 0.25s linear); +} + +.replication-doc-name-icon:hover { + color: #333333; +} + +.replication-doc-name-input { + padding-right: 32px; + font-size: 14px; + width: 248px; +} + +.replication-button-row { + margin-top: 10px; + width: 540px; + margin-left: 240px; +} + +.replication-clear-link { + padding: 12px; +} + + + /*input, select { font-size: 14px; } input { @@ -59,13 +173,9 @@ font-weight: bold; font-size: 14pt; } -} +}*/ -#dashboard-content .replication-page { - padding-top: 25px; -} - -.connection-url-example { +/*.connection-url-example { font-size: 9pt; color: #999999; margin-bottom: 8px; @@ -91,10 +201,10 @@ padding-right: 32px; } } -} +}*/ -body .Select div.Select-control { +/*body .Select div.Select-control { padding: 6px; border: 1px solid #cccccc; width: 246px; @@ -109,4 +219,4 @@ body .Select div.Select-control { width: 18px; color: black; } -} +}*/ http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/replication/components.react.jsx b/app/addons/replication/components.react.jsx index b721bc0..378fbde 100644 --- a/app/addons/replication/components.react.jsx +++ b/app/addons/replication/components.react.jsx @@ -12,324 +12,15 @@ import app from '../../app'; import FauxtonAPI from '../../core/api'; import React from 'react'; -import Stores from './stores'; -import Actions from './actions'; import Constants from './constants'; import Helpers from './helpers'; import Components from '../components/react-components.react'; -import base64 from 'base-64'; -import AuthActions from '../auth/actions'; import AuthComponents from '../auth/components.react'; import ReactSelect from 'react-select'; -const store = Stores.replicationStore; -const LoadLines = Components.LoadLines; -const StyledSelect = Components.StyledSelect; -const ConfirmButton = Components.ConfirmButton; +const {LoadLines, StyledSelect, ConfirmButton} = Components; const PasswordModal = AuthComponents.PasswordModal; - -class ReplicationController extends React.Component { - constructor (props) { - super(props); - this.state = this.getStoreState(); - this.submit = this.submit.bind(this); - this.clear = this.clear.bind(this); - this.showPasswordModal = this.showPasswordModal.bind(this); - } - - getStoreState () { - return { - loading: store.isLoading(), - databases: store.getDatabases(), - authenticated: store.isAuthenticated(), - password: store.getPassword(), - - // source fields - replicationSource: store.getReplicationSource(), - sourceDatabase: store.getSourceDatabase(), - localSourceDatabaseKnown: store.isLocalSourceDatabaseKnown(), - remoteSource: store.getRemoteSource(), - - // target fields - replicationTarget: store.getReplicationTarget(), - targetDatabase: store.getTargetDatabase(), - localTargetDatabaseKnown: store.isLocalTargetDatabaseKnown(), - remoteTarget: store.getRemoteTarget(), - - // other - passwordModalVisible: store.isPasswordModalVisible(), - replicationType: store.getReplicationType(), - replicationDocName: store.getReplicationDocName() - }; - } - - componentDidMount () { - Actions.initReplicator(this.props.sourceDatabase); - store.on('change', this.onChange, this); - } - - componentWillUnmount () { - store.off('change', this.onChange); - Actions.clearReplicationForm(); - } - - onChange () { - this.setState(this.getStoreState()); - } - - clear (e) { - e.preventDefault(); - Actions.clearReplicationForm(); - } - - showPasswordModal () { - const { replicationSource, replicationTarget } = this.state; - - const hasLocalSourceOrTarget = (replicationSource === Constants.REPLICATION_SOURCE.LOCAL || - replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE || - replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE); - - // if the user is authenticated, or if NEITHER the source nor target are local, just submit. The password - // modal isn't necessary - if (!hasLocalSourceOrTarget || this.state.authenticated) { - this.submit(); - return; - } - - AuthActions.showPasswordModal(); - } - - getUsername () { - return app.session.get('userCtx').name; - } - - getAuthHeaders () { - const username = this.getUsername(); - return { - 'Authorization': 'Basic ' + base64.encode(username + ':' + this.state.password) - }; - } - - submit () { - const { replicationTarget, replicationType, replicationDocName} = this.state; - - if (!this.validate()) { - return; - } - - const params = { - source: this.getSource(), - target: this.getTarget() - }; - - if (_.contains([Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE], replicationTarget)) { - params.create_target = true; - } - if (replicationType === Constants.REPLICATION_TYPE.CONTINUOUS) { - params.continuous = true; - } - - if (replicationDocName) { - params._id = this.state.replicationDocName; - } - - // POSTing to the _replicator DB requires auth - const user = FauxtonAPI.session.user(); - const userName = _.isNull(user) ? '' : FauxtonAPI.session.user().name; - params.user_ctx = { - name: userName, - roles: ['_admin', '_reader', '_writer'] - }; - - Actions.replicate(params); - } - - getSource () { - const { replicationSource, sourceDatabase, remoteSource } = this.state; - if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) { - return { - headers: this.getAuthHeaders(), - url: window.location.origin + '/' + sourceDatabase - }; - } else { - return remoteSource; - } - } - - getTarget () { - const { replicationTarget, targetDatabase, remoteTarget, replicationSource, password } = this.state; - - let target = remoteTarget; - if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) { - target = { - headers: this.getAuthHeaders(), - url: window.location.origin + '/' + targetDatabase - }; - } else if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) { - - // check to see if we really need to send headers here or can just do the ELSE clause in all scenarioe - if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) { - target = { - headers: this.getAuthHeaders(), - url: window.location.origin + '/' + targetDatabase - }; - } else { - const port = window.location.port === '' ? '' : ':' + window.location.port; - target = window.location.protocol + '//' + this.getUsername() + ':' + password + '@' - + window.location.hostname + port + '/' + targetDatabase; - } - } - - return target; - } - - validate () { - const { replicationTarget, targetDatabase, databases } = this.state; - - if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && _.contains(databases, targetDatabase)) { - FauxtonAPI.addNotification({ - msg: 'The ' + targetDatabase + ' database already exists locally. Please enter another database name.', - type: 'error', - escape: false, - clear: true - }); - return false; - } - if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE || - replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE) { - let error = ''; - if (/\s/.test(targetDatabase)) { - error = 'The target database may not contain any spaces.'; - } else if (/^_/.test(targetDatabase)) { - error = 'The target database may not start with an underscore.'; - } - - if (error) { - FauxtonAPI.addNotification({ - msg: error, - type: 'error', - escape: false, - clear: true - }); - return false; - } - } - - return true; - } - - render () { - const { loading, replicationSource, replicationTarget, replicationType, replicationDocName, passwordModalVisible, - localSourceDatabaseKnown, databases, localTargetDatabaseKnown, sourceDatabase, remoteSource, remoteTarget, - targetDatabase } = this.state; - - if (loading) { - return ( - - ); - } - - let confirmButtonEnabled = true; - if (!replicationSource || !replicationTarget) { - confirmButtonEnabled = false; - } - if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL && !localSourceDatabaseKnown) { - confirmButtonEnabled = false; - } - if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE && !localTargetDatabaseKnown) { - confirmButtonEnabled = false; - } - - return ( -
-
-
- Replication Source: -
-
- Actions.updateFormField('replicationSource', repSource)}/> -
-
- - {replicationSource ? - Actions.updateFormField('remoteSource', val)} - /> : null} - -
- -
-
- Replication Target: -
-
- Actions.updateFormField('replicationTarget', repTarget)}/> -
-
- {replicationTarget ? - : null} - -
- -
-
- Replication Type: -
-
- Actions.updateFormField('replicationType', repType)}/> -
-
- -
-
- Replication Document: -
-
-
- Actions.updateFormField('replicationDocName', '')} /> - Actions.updateFormField('replicationDocName', e.target.value)}/> -
-
-
- -
-
-
-
- - Clear -
-
- - Replication requires authentication.

} - submitBtnLabel="Continue Replication" - onSuccess={this.submit} /> -
- ); - } -} - - class ReplicationSourceRow extends React.Component { render () { const { replicationSource, databases, sourceDatabase, remoteSource, onChange} = this.props; @@ -376,36 +67,6 @@ ReplicationSourceRow.propTypes = { }; -class ReplicationSource extends React.Component { - getOptions () { - const options = [ - { value: '', label: 'Select source' }, - { value: Constants.REPLICATION_SOURCE.LOCAL, label: 'Local database' }, - { value: Constants.REPLICATION_SOURCE.REMOTE, label: 'Remote database' } - ]; - return options.map((option) => { - return ( - - ); - }); - } - - render () { - return ( - this.props.onChange(e.target.value)} - selectId="replication-source" - selectValue={this.props.value} /> - ); - } -} -ReplicationSource.propTypes = { - value: React.PropTypes.string.isRequired, - onChange: React.PropTypes.func.isRequired -}; - - class ReplicationTarget extends React.Component { getOptions () { const options = [ @@ -537,8 +198,6 @@ ReplicationTargetRow.propTypes = { export default { - ReplicationController, - ReplicationSource, ReplicationTarget, ReplicationType, ReplicationTargetRow http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/components/options.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/options.js b/app/addons/replication/components/options.js new file mode 100644 index 0000000..02c5b98 --- /dev/null +++ b/app/addons/replication/components/options.js @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +import React from 'react'; +import Constants from '../constants'; +import Components from '../../components/react-components.react'; +import ReactSelect from 'react-select'; + +const { StyledSelect } = Components; + +const getReplicationTypeOptions = () => { + return [ + { value: Constants.REPLICATION_TYPE.ONE_TIME, label: 'One time' }, + { value: Constants.REPLICATION_TYPE.CONTINUOUS, label: 'Continuous' } + ].map(option => ); +}; + +const ReplicationType = ({value, onChange}) => { + return ( +
+
+ Replication Type: +
+
+ onChange(e.target.value)} + selectId="replication-target" + selectValue={value} /> +
+
+ ); +}; + +ReplicationType.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const ReplicationDoc = ({value, onChange}) => +
+
+ Replication Document: +
+
+ onChange('')} /> + onChange(e.target.value)}/> +
+
; + +ReplicationDoc.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +export class ReplicationOptions extends React.Component { + + render () { + const {replicationType, replicationDocName, onDocChange, onTypeChange} = this.props; + + return ( +
+ + +
+ ); + } + +} + +ReplicationOptions.propTypes = { + replicationDocName: React.PropTypes.string.isRequired, + replicationType: React.PropTypes.string.isRequired, + onDocChange: React.PropTypes.func.isRequired, + onTypeChange: React.PropTypes.func.isRequired +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/components/source.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/source.js b/app/addons/replication/components/source.js new file mode 100644 index 0000000..7336804 --- /dev/null +++ b/app/addons/replication/components/source.js @@ -0,0 +1,164 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +import React from 'react'; +import Constants from '../constants'; +import Components from '../../components/react-components.react'; +import ReactSelect from 'react-select'; + +const { StyledSelect } = Components; + +const RemoteSourceInput = ({onChange, value}) => +
+
Database URL:
+
+ onChange(e.target.value)} /> +
e.g. https://$REMOTE_USERNAME:$REMOTE_PASSWORD@$REMOTE_SERVER/$DATABASE
+
+
; + +RemoteSourceInput.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const LocalSourceInput = ({value, onChange, databases}) => { + const options = databases.map(db => ({value: db, label: db})); + return ( +
+
+ Source Name: +
+
+ +
+
+ ); +}; + +LocalSourceInput.propTypes = { + value: React.PropTypes.string.isRequired, + databases: React.PropTypes.array.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const ReplicationSourceRow = ({replicationSource, databases, sourceDatabase, remoteSource, onChangeRemote, onChangeLocal}) => { + if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) { + return ; + } + + return ; +}; + +ReplicationSourceRow.propTypes = { + replicationSource: React.PropTypes.string.isRequired, + databases: React.PropTypes.array.isRequired, + sourceDatabase: React.PropTypes.string.isRequired, + remoteSource: React.PropTypes.string.isRequired, + onChangeRemote: React.PropTypes.func.isRequired, + onChangeLocal: React.PropTypes.func.isRequired +}; + +const replicationSourceSelectOptions = () => { + return [ + { value: '', label: 'Select source' }, + { value: Constants.REPLICATION_SOURCE.LOCAL, label: 'Local database' }, + { value: Constants.REPLICATION_SOURCE.REMOTE, label: 'Remote database' } + ].map((option) => { + return ( + + ); + }); +}; + +export const ReplicationSourceSelect = ({onChange, value}) => { + + return ( +
+
+ Replication Source: +
+
+ onChange(e.target.value)} + selectId="replication-source" + selectValue={value} /> +
+
+ ); +}; + +ReplicationSourceSelect.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +export class ReplicationSource extends React.Component { + + getReplicationSourceRow () { + const { + replicationSource, + sourceDatabase, + onLocalSourceChange, + onRemoteSourceChange, + remoteSource, + databases + } = this.props; + + if (!replicationSource) { + return null; + } + + return ; + } + + render () { + const {replicationSource, onSourceSelect, sourceDatabase, remoteSource, databases} = this.props; + const Actions = {}; + return ( +
+ + {this.getReplicationSourceRow()} +
+ ); + } +} + +ReplicationSource.propTypes = { + replicationSource: React.PropTypes.string.isRequired, + sourceDatabase: React.PropTypes.string.isRequired, + remoteSource: React.PropTypes.string.isRequired, + databases: React.PropTypes.array.isRequired, + onLocalSourceChange: React.PropTypes.func.isRequired, + onRemoteSourceChange: React.PropTypes.func.isRequired +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/components/submit.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/submit.js b/app/addons/replication/components/submit.js new file mode 100644 index 0000000..521f8d7 --- /dev/null +++ b/app/addons/replication/components/submit.js @@ -0,0 +1,34 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +import React from 'react'; +import Constants from '../constants'; +import Components from '../../components/react-components.react'; + +const {ConfirmButton} = Components; + +export const ReplicationSubmit = ({onClear, disabled, onClick}) => +
+ + Clear +
; + + +ReplicationSubmit.propTypes = { + disabled: React.PropTypes.bool.isRequired, + onClick: React.PropTypes.func.isRequired, + onClear: React.PropTypes.func.isRequired +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/components/target.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/target.js b/app/addons/replication/components/target.js new file mode 100644 index 0000000..1472118 --- /dev/null +++ b/app/addons/replication/components/target.js @@ -0,0 +1,202 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +import React from 'react'; +import Constants from '../constants'; +import Components from '../../components/react-components.react'; +import ReactSelect from 'react-select'; + +const { StyledSelect } = Components; + +const replicationTargetSourceOptions = () => { + return [ + { value: '', label: 'Select target' }, + { value: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE, label: 'Existing local database' }, + { value: Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE, label: 'Existing remote database' }, + { value: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, label: 'New local database' }, + { value: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE, label: 'New remote database' } + ].map((option) => { + return ( + + ); + }); +}; + +const ReplicationTargetSelect = ({value, onChange}) => { + return ( +
+
+ Replication Target: +
+
+ onChange(e.target.value)} + selectId="replication-target" + selectValue={value} /> +
+
+ ); +}; + +ReplicationTargetSelect.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const RemoteTargetReplicationRow = ({onChange, value}) => { + return ( +
+ onChange(e.target.value)} /> +
e.g. https://$REMOTE_USERNAME:$REMOTE_PASSWORD@$REMOTE_SERVER/$DATABASE
+
+ ); +}; + +RemoteTargetReplicationRow.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const ExistingLocalTargetReplicationRow = ({onChange, value, databases}) => { + const options = databases.map(db => ({value: db, label: db})); + return ( +
+ onChange(selected.value)} + /> +
+ ); +}; + +ExistingLocalTargetReplicationRow.propTypes = { + value: React.PropTypes.string.isRequired, + databases: React.PropTypes.array.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const NewLocalTargetReplicationRow = ({onChange, value}) => + onChange(e.target.value)} + />; + +NewLocalTargetReplicationRow.propTypes = { + value: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +const ReplicationTargetRow = ({ + replicationTarget, + onLocalTargetChange, + onRemoteTargetChange, + localTarget, + remoteTarget, + databases +}) => { + if (!replicationTarget) { + return null; + } + let input; + + if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) { + targetLabel = 'New Database:'; + input = ; + } else if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) { + input = ; + } else { + input = ; + } + + let targetLabel = 'Target Name:'; + + if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE || + replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) { + targetLabel = 'New Database:'; + } + + return ( +
+
{targetLabel}
+
+ {input} +
+
+ ); +}; + +ReplicationTargetRow.propTypes = { + databases: React.PropTypes.array.isRequired, + onLocalTargetChange: React.PropTypes.func.isRequired, + onRemoteTargetChange: React.PropTypes.func.isRequired, + remoteTarget: React.PropTypes.string.isRequired, + localTarget: React.PropTypes.string.isRequired, + replicationTarget: React.PropTypes.string.isRequired +}; + +export class ReplicationTarget extends React.Component { + + render () { + const { + replicationTarget, + onLocalTargetChange, + onTargetChange, + databases, + localTarget, + onRemoteTargetChange, + remoteTarget + } = this.props; + return ( +
+ + +
+ ); + } +} + +ReplicationTarget.propTypes = { + databases: React.PropTypes.array.isRequired, + onTargetChange: React.PropTypes.func.isRequired, + onLocalTargetChange: React.PropTypes.func.isRequired, + onRemoteTargetChange: React.PropTypes.func.isRequired, + remoteTarget: React.PropTypes.string.isRequired, + localTarget: React.PropTypes.string.isRequired, + replicationTarget: React.PropTypes.string.isRequired +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/controller.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js new file mode 100644 index 0000000..92d3150 --- /dev/null +++ b/app/addons/replication/controller.js @@ -0,0 +1,349 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +import React from 'react'; +import app from '../../app'; +import FauxtonAPI from '../../core/api'; +import Stores from './stores'; +import Actions from './actions'; +import AuthActions from '../auth/actions'; +import Constants from './constants'; +import base64 from 'base-64'; +import {ReplicationSource} from './components/source'; +import {ReplicationTarget} from './components/target'; +import {ReplicationOptions} from './components/options'; +import {ReplicationSubmit} from './components/submit'; +import Components from '../components/react-components.react'; + +const {LoadLines, ConfirmButton} = Components; + +const store = Stores.replicationStore; + +export default class ReplicationController extends React.Component { + constructor (props) { + super(props); + this.state = this.getStoreState(); + this.submit = this.submit.bind(this); + this.clear = this.clear.bind(this); + this.showPasswordModal = this.showPasswordModal.bind(this); + } + + getStoreState () { + return { + loading: store.isLoading(), + databases: store.getDatabases(), + authenticated: store.isAuthenticated(), + password: store.getPassword(), + + // source fields + replicationSource: store.getReplicationSource(), + sourceDatabase: store.getSourceDatabase(), + localSourceDatabaseKnown: store.isLocalSourceDatabaseKnown(), + remoteSource: store.getRemoteSource(), + + // target fields + replicationTarget: store.getReplicationTarget(), + targetDatabase: store.getTargetDatabase(), + localTargetDatabaseKnown: store.isLocalTargetDatabaseKnown(), + remoteTarget: store.getRemoteTarget(), + + // other + passwordModalVisible: store.isPasswordModalVisible(), + replicationType: store.getReplicationType(), + replicationDocName: store.getReplicationDocName() + }; + } + + componentDidMount () { + Actions.initReplicator(this.props.sourceDatabase); + store.on('change', this.onChange, this); + } + + componentWillUnmount () { + store.off('change', this.onChange); + Actions.clearReplicationForm(); + } + + onChange () { + this.setState(this.getStoreState()); + } + + clear (e) { + e.preventDefault(); + Actions.clearReplicationForm(); + } + + showPasswordModal () { + const { replicationSource, replicationTarget } = this.state; + + const hasLocalSourceOrTarget = (replicationSource === Constants.REPLICATION_SOURCE.LOCAL || + replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE || + replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE); + + // if the user is authenticated, or if NEITHER the source nor target are local, just submit. The password + // modal isn't necessary + if (!hasLocalSourceOrTarget || this.state.authenticated) { + this.submit(); + return; + } + + AuthActions.showPasswordModal(); + } + + getUsername () { + return app.session.get('userCtx').name; + } + + getAuthHeaders () { + const username = this.getUsername(); + return { + 'Authorization': 'Basic ' + base64.encode(username + ':' + this.state.password) + }; + } + + submit () { + const { replicationTarget, replicationType, replicationDocName} = this.state; + + if (!this.validate()) { + return; + } + + const params = { + source: this.getSource(), + target: this.getTarget() + }; + + if (_.contains([Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE], replicationTarget)) { + params.create_target = true; + } + if (replicationType === Constants.REPLICATION_TYPE.CONTINUOUS) { + params.continuous = true; + } + + if (replicationDocName) { + params._id = this.state.replicationDocName; + } + + // POSTing to the _replicator DB requires auth + const user = FauxtonAPI.session.user(); + const userName = _.isNull(user) ? '' : FauxtonAPI.session.user().name; + params.user_ctx = { + name: userName, + roles: ['_admin', '_reader', '_writer'] + }; + + Actions.replicate(params); + } + + getSource () { + const { replicationSource, sourceDatabase, remoteSource } = this.state; + if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) { + return { + headers: this.getAuthHeaders(), + url: window.location.origin + '/' + sourceDatabase + }; + } else { + return remoteSource; + } + } + + getTarget () { + const { replicationTarget, targetDatabase, remoteTarget, replicationSource, password } = this.state; + + let target = remoteTarget; + if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) { + target = { + headers: this.getAuthHeaders(), + url: window.location.origin + '/' + targetDatabase + }; + } else if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) { + + // check to see if we really need to send headers here or can just do the ELSE clause in all scenarioe + if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) { + target = { + headers: this.getAuthHeaders(), + url: window.location.origin + '/' + targetDatabase + }; + } else { + const port = window.location.port === '' ? '' : ':' + window.location.port; + target = window.location.protocol + '//' + this.getUsername() + ':' + password + '@' + + window.location.hostname + port + '/' + targetDatabase; + } + } + + return target; + } + + validate () { + const { replicationTarget, targetDatabase, databases } = this.state; + + if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && _.contains(databases, targetDatabase)) { + FauxtonAPI.addNotification({ + msg: 'The ' + targetDatabase + ' database already exists locally. Please enter another database name.', + type: 'error', + escape: false, + clear: true + }); + return false; + } + if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE || + replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE) { + let error = ''; + if (/\s/.test(targetDatabase)) { + error = 'The target database may not contain any spaces.'; + } else if (/^_/.test(targetDatabase)) { + error = 'The target database may not start with an underscore.'; + } + + if (error) { + FauxtonAPI.addNotification({ + msg: error, + type: 'error', + escape: false, + clear: true + }); + return false; + } + } + + return true; + } + + confirmButtonEnabled () { + const {localSourceDatabaseKnown, replicationSource, replicationTarget, localTargetDatabaseKnown} = this.state; + + if (!replicationSource || !replicationTarget) { + return false; + } + if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL && !localSourceDatabaseKnown) { + return false; + } + if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE && !localTargetDatabaseKnown) { + return false; + } + + return true; + } + + render () { + const { + loading, replicationSource, replicationTarget, replicationType, replicationDocName, + passwordModalVisible, databases, sourceDatabase, remoteSource, remoteTarget, + targetDatabase + } = this.state; + + if (loading) { + return ( + + ); + } + + return ( +
+ +
+ +
+ + {}} + onClear={Actions.clearReplicationForm} + /> +
+ ); + } +} + + + +/* +
+
+ Replication Target: +
+
+ Actions.updateFormField('replicationTarget', repTarget)}/> +
+
+{replicationTarget ? + : null} + +
+ +
+
+ Replication Type: +
+
+ Actions.updateFormField('replicationType', repType)}/> +
+
+ +
+
+ Replication Document: +
+
+
+ Actions.updateFormField('replicationDocName', '')} /> + Actions.updateFormField('replicationDocName', e.target.value)}/> +
+
+
+ +
+
+
+
+ + Clear +
+
+ +Replication requires authentication.

} + submitBtnLabel="Continue Replication" + onSuccess={this.submit} /> +*/ http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/helpers.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/helpers.js b/app/addons/replication/helpers.js index 1ba40bc..4584faf 100644 --- a/app/addons/replication/helpers.js +++ b/app/addons/replication/helpers.js @@ -5,13 +5,6 @@ const getDatabaseLabel = db => { return matches[0]; }; -const getReactSelectOptions = list => { - return list.map(item => { - return { value: item, label: item }; - }); -}; - export default { getDatabaseLabel, - getReactSelectOptions }; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ecea635a/app/addons/replication/route.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/route.js b/app/addons/replication/route.js index a3d7d46..b25782f 100644 --- a/app/addons/replication/route.js +++ b/app/addons/replication/route.js @@ -10,10 +10,8 @@ // License for the specific language governing permissions and limitations under // the License. -import app from '../../app'; import FauxtonAPI from '../../core/api'; -import Actions from './actions'; -import Components from './components.react'; +import ReplicationController from './controller'; const ReplicationRouteObject = FauxtonAPI.RouteObject.extend({ layout: 'one_pane', @@ -29,9 +27,10 @@ const ReplicationRouteObject = FauxtonAPI.RouteObject.extend({ { name: 'Replication', link: 'replication' } ], roles: ['fx_loggedIn'], + defaultView: function (databaseName) { const sourceDatabase = databaseName || ''; - this.setComponent('#dashboard-content', Components.ReplicationController, {sourceDatabase: sourceDatabase}); + this.setComponent('#dashboard-content', ReplicationController, {sourceDatabase: sourceDatabase}); } });