start info section
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/22732ae0 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/22732ae0 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/22732ae0 Branch: refs/heads/new-replication Commit: 22732ae083c84880fd3ba8a91846425e813395a0 Parents: af6c956 Author: Garren Smith <garren.sm...@gmail.com> Authored: Thu Sep 15 17:09:52 2016 +0200 Committer: Garren Smith <garren.sm...@gmail.com> Committed: Thu Sep 15 17:09:52 2016 +0200 ---------------------------------------------------------------------- app/addons/replication/actions.js | 20 +- app/addons/replication/actiontypes.js | 3 +- app/addons/replication/components/info.js | 26 ++ .../replication/components/newreplication.js | 236 +++++++++++++++++++ app/addons/replication/components/tabs.js | 49 ++++ app/addons/replication/controller.js | 234 ++++-------------- app/addons/replication/stores.js | 21 +- 7 files changed, 392 insertions(+), 197 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js index e5b5f8c..5bbece0 100644 --- a/app/addons/replication/actions.js +++ b/app/addons/replication/actions.js @@ -88,10 +88,28 @@ function clearReplicationForm () { FauxtonAPI.dispatch({ type: ActionTypes.REPLICATION_CLEAR_FORM }); } +const tabUrls = { + 'new replication': '/replication', + 'all replication': '/replication/info', + 'active replications': '/replication/info/active', + 'errors': '/replication/info/errors', + 'completed replications': '/replication/info/completed' +}; + +function changeSelectedTab (tab) { + console.log('change', tab); + FauxtonAPI.navigate(tabUrls[tab], { replace: true }); + FauxtonAPI.dispatch({ + type: ActionTypes.REPLICATION_CHANGE_TAB, + options: tab + }); +} + export default { initReplicator, replicate, updateFormField, - clearReplicationForm + clearReplicationForm, + changeSelectedTab }; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/actiontypes.js b/app/addons/replication/actiontypes.js index 880c47d..192d096 100644 --- a/app/addons/replication/actiontypes.js +++ b/app/addons/replication/actiontypes.js @@ -17,6 +17,7 @@ define([], function () { REPLICATION_DATABASES_LOADED: 'REPLICATION_DATABASES_LOADED', REPLICATION_UPDATE_FORM_FIELD: 'REPLICATION_UPDATE_FORM_FIELD', REPLICATION_CLEAR_FORM: 'REPLICATION_CLEAR_FORM', - REPLICATION_STARTING: 'REPLICATION_STARTING' + REPLICATION_STARTING: 'REPLICATION_STARTING', + REPLICATION_CHANGE_TAB: 'REPLICATION_CHANGE_TAB' }; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/components/info.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/info.js b/app/addons/replication/components/info.js new file mode 100644 index 0000000..b153759 --- /dev/null +++ b/app/addons/replication/components/info.js @@ -0,0 +1,26 @@ +// 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'; + +export default class ReplicationController extends React.Component { + constructor (props) { + super(props); + this.state = this.getStoreState(); + } + + render () { + const rows = this.getReplicationRows(this.props.docs); + <ReplicationTable> + {rows} + </ReplicationTable> + } +} http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/components/newreplication.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/newreplication.js b/app/addons/replication/components/newreplication.js new file mode 100644 index 0000000..cd2eaab --- /dev/null +++ b/app/addons/replication/components/newreplication.js @@ -0,0 +1,236 @@ +// 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 {ReplicationSource} from './source'; +import {ReplicationTarget} from './target'; +import {ReplicationOptions} from './options'; +import {ReplicationSubmit} from './submit'; +import AuthComponents from '../../auth/components.react'; +import Constants from '../constants'; + +const {PasswordModal} = AuthComponents; + +export default class ReplicationController extends React.Component { + constructor (props) { + super(props); + this.submit = this.submit.bind(this); + this.clear = this.clear.bind(this); + this.showPasswordModal = this.showPasswordModal.bind(this); + } + + clear (e) { + e.preventDefault(); + this.props.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; + } + + this.props.showPasswordModal(); + } + + validate () { + const { + remoteTarget, + remoteSource, + replicationTarget, + replicationSource, + targetDatabase, + sourceDatabase, + databases, + } = this.state; + + if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && _.contains(databases, targetDatabase)) { + FauxtonAPI.addNotification({ + msg: 'The <code>' + targetDatabase + '</code> 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; + } + } + + //check that source and target are not the same. They can trigger a false positive if they are "" + if ((remoteTarget === remoteSource && remoteTarget !== "") + || (sourceDatabase === targetDatabase && sourceDatabase !== "")) { + FauxtonAPI.addNotification({ + msg: 'Cannot replicate a database to itself', + type: 'error', + escape: false, + clear: true + }); + + return false; + } + + return true; + } + + submit () { + const { + replicationTarget, + replicationSource, + replicationType, + replicationDocName, + password, + remoteTarget, + remoteSource, + targetDatabase, + sourceDatabase + } = this.props; + + if (!this.validate()) { + return; + } + + this.props.replicate({ + replicationTarget, + replicationSource, + replicationType, + replicationDocName, + password, + localTarget: targetDatabase, + localSource: sourceDatabase, + remoteTarget, + remoteSource + }); + } + + confirmButtonEnabled () { + const { + remoteSource, + localSourceDatabaseKnown, + replicationSource, + replicationTarget, + localTargetDatabaseKnown, + targetDatabase, + submittedNoChange, + } = this.props; + + if (submittedNoChange) { + return false; + } + + 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; + } + + if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && !targetDatabase) { + return false; + } + + if (replicationSource === Constants.REPLICATION_SOURCE.REMOTE && remoteSource === "") { + return false; + } + + return true; + } + + render () { + const { + replicationSource, + replicationTarget, + replicationType, + replicationDocName, + passwordModalVisible, + databases, + sourceDatabase, + remoteSource, + remoteTarget, + targetDatabase, + updateFormField, + clearReplicationForm + } = this.props; + + return ( + <div> + <ReplicationSource + replicationSource={replicationSource} + sourceDatabase={sourceDatabase} + databases={databases} + remoteSource={remoteSource} + onSourceSelect={updateFormField('replicationSource')} + onRemoteSourceChange={updateFormField('remoteSource')} + onLocalSourceChange={updateFormField('sourceDatabase')} + /> + <hr className="replication-seperator" size="1"/> + <ReplicationTarget + replicationTarget={replicationTarget} + onTargetChange={updateFormField('replicationTarget')} + databases={databases} + localTarget={targetDatabase} + remoteTarget={remoteTarget} + onRemoteTargetChange={updateFormField('remoteTarget')} + onLocalTargetChange={updateFormField('targetDatabase')} + /> + <hr className="replication-seperator" size="1"/> + <ReplicationOptions + replicationType={replicationType} + replicationDocName={replicationDocName} + onDocChange={updateFormField('replicationDocName')} + onTypeChange={updateFormField('replicationType')} + /> + <ReplicationSubmit + disabled={!this.confirmButtonEnabled()} + onClick={this.showPasswordModal} + onClear={clearReplicationForm} + /> + <PasswordModal + visible={passwordModalVisible} + modalMessage={<p>{app.i18n.en_US['replication-password-modal-text']}</p>} + submitBtnLabel="Start Replication1213" + headerTitle={app.i18n.en_US['replication-password-modal-header']} + onSuccess={this.submit} /> + </div> + ); + } +} http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/components/tabs.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/components/tabs.js b/app/addons/replication/components/tabs.js new file mode 100644 index 0000000..58ac31e --- /dev/null +++ b/app/addons/replication/components/tabs.js @@ -0,0 +1,49 @@ +// 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 GeneralComponents from '../../components/react-components.react'; + +const { TabElementWrapper, TabElement } = GeneralComponents; + +const checked = (selectedTab, tabName) => { + return selectedTab === tabName; +}; + +export default ({selectedTab, onTabClick}) => { + return ( + <div className="monitoring__tabs-wrapper"> + <TabElementWrapper> + <TabElement + selected={checked(selectedTab, 'new replication')} + text={'New Replication'} + onChange={e => onTabClick(e.target.value.toLowerCase())} /> + <TabElement + selected={checked(selectedTab, 'all replications')} + text={'All Replications'} + onChange={e => onTabClick(e.target.value.toLowerCase())} /> + <TabElement + selected={checked(selectedTab, 'active replications')} + text={'Active Replications'} + onChange={e => onTabClick(e.target.value.toLowerCase())} /> + <TabElement + selected={checked(selectedTab, 'errors')} + text={'Errors'} + onChange={e => onTabClick(e.target.value.toLowerCase())} /> + <TabElement + selected={checked(selectedTab, 'completed replications')} + text={'Completed Replications'} + onChange={e => onTabClick(e.target.value.toLowerCase())} /> + </TabElementWrapper> + </div> + ); +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/controller.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js index d34bc46..c658229 100644 --- a/app/addons/replication/controller.js +++ b/app/addons/replication/controller.js @@ -17,14 +17,11 @@ 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'; -import AuthComponents from '../auth/components.react'; +import NewReplication from './components/newreplication'; +import ReplicationTabs from './components/tabs'; +import ReplicationInfo from './components/info'; -const {PasswordModal} = AuthComponents; const {LoadLines, ConfirmButton} = Components; const store = Stores.replicationStore; @@ -33,9 +30,6 @@ 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 () { @@ -61,7 +55,8 @@ export default class ReplicationController extends React.Component { passwordModalVisible: store.isPasswordModalVisible(), replicationType: store.getReplicationType(), replicationDocName: store.getReplicationDocName(), - submittedNoChange: store.getSubmittedNoChange() + submittedNoChange: store.getSubmittedNoChange(), + selectedTab: store.getSelectedTab() }; } @@ -79,157 +74,45 @@ export default class ReplicationController extends React.Component { 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(); - } - - submit () { - const { - replicationTarget, - replicationSource, - replicationType, - replicationDocName, - password, - remoteTarget, - remoteSource, - targetDatabase, - sourceDatabase - } = this.state; - - if (!this.validate()) { - return; - } - - Actions.replicate({ - replicationTarget, - replicationSource, - replicationType, - replicationDocName, - password, - localTarget: targetDatabase, - localSource: sourceDatabase, - remoteTarget, - remoteSource - }); - } - - validate () { + showSelectedTab () { const { - remoteTarget, - remoteSource, - replicationTarget, - replicationSource, - targetDatabase, - sourceDatabase, - databases, - } = this.state; - - if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && _.contains(databases, targetDatabase)) { - FauxtonAPI.addNotification({ - msg: 'The <code>' + targetDatabase + '</code> 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; - } - } - - console.log('rr', replicationTarget, replicationSource, sourceDatabase); - //check that source and target are not the same. They can trigger a false positive if they are "" - if ((remoteTarget === remoteSource && remoteTarget !== "") - || (sourceDatabase === targetDatabase && sourceDatabase !== "")) { - FauxtonAPI.addNotification({ - msg: 'Cannot replicate a database to itself', - type: 'error', - escape: false, - clear: true - }); - - return false; - } - - return true; - } - - confirmButtonEnabled () { - const { - remoteSource, - localSourceDatabaseKnown, - replicationSource, - replicationTarget, - localTargetDatabaseKnown, - targetDatabase, - submittedNoChange, + replicationSource, replicationTarget, replicationType, replicationDocName, + passwordModalVisible, databases, sourceDatabase, remoteSource, remoteTarget, + targetDatabase, selectedTab } = this.state; - if (submittedNoChange) { - return false; - } - - if (!replicationSource || !replicationTarget) { - return false; + if (this.state.selectedTab === 'new replication') { + const updateFormField = (field) => { + return (value) => { + Actions.updateFormField(field, value); + }; + }; + + <NewReplication + clearReplicationForm={Actions.clearReplicationForm} + replicate={Actions.replicate} + showPasswordModal={Actions.showPasswordModal} + replicationSource={replicationSource} + replicationTarget={replicationTarget} + replicationType={replicationType} + replicationDocName={replicationDocName} + passwordModalVisible={passwordModalVisible} + databases={databases} + sourceDatabase={sourceDatabase} + remoteSource={remoteSource} + remoteTarget={remoteTarget} + targetDatabase={targetDatabase} + updateFormField={updateFormField} + />; } - if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL && !localSourceDatabaseKnown) { - return false; - } - if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE && !localTargetDatabaseKnown) { - return false; - } - - if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && !targetDatabase) { - return false; - } - - if (replicationSource === Constants.REPLICATION_SOURCE.REMOTE && remoteSource === "") { - return false; - } - - return true; + return <ReplicationInfo />; } render () { const { - loading, replicationSource, replicationTarget, replicationType, replicationDocName, - passwordModalVisible, databases, sourceDatabase, remoteSource, remoteTarget, - targetDatabase + loading, + selectedTab } = this.state; if (loading) { @@ -238,45 +121,18 @@ export default class ReplicationController extends React.Component { ); } + return ( - <div className="replication-page"> - <ReplicationSource - replicationSource={replicationSource} - sourceDatabase={sourceDatabase} - databases={databases} - remoteSource={remoteSource} - onSourceSelect={Actions.updateFormField.bind(Actions, 'replicationSource')} - onRemoteSourceChange={Actions.updateFormField.bind(Actions, 'remoteSource')} - onLocalSourceChange={Actions.updateFormField.bind(Actions, 'sourceDatabase')} - /> - <hr className="replication-seperator" size="1"/> - <ReplicationTarget - replicationTarget={replicationTarget} - onTargetChange={Actions.updateFormField.bind(Actions, 'replicationTarget')} - databases={databases} - localTarget={targetDatabase} - remoteTarget={remoteTarget} - onRemoteTargetChange={Actions.updateFormField.bind(Actions, 'remoteTarget')} - onLocalTargetChange={Actions.updateFormField.bind(Actions, 'targetDatabase')} - /> - <hr className="replication-seperator" size="1"/> - <ReplicationOptions - replicationType={replicationType} - replicationDocName={replicationDocName} - onDocChange={Actions.updateFormField.bind(Actions, 'replicationDocName')} - onTypeChange={Actions.updateFormField.bind(Actions, 'replicationType')} - /> - <ReplicationSubmit - disabled={!this.confirmButtonEnabled()} - onClick={this.showPasswordModal} - onClear={Actions.clearReplicationForm} + <div className="template-content flex-body flex-layout flex-col"> + <ReplicationTabs + selectedTab={selectedTab} + onTabClick={Actions.changeSelectedTab} /> - <PasswordModal - visible={passwordModalVisible} - modalMessage={<p>{app.i18n.en_US['replication-password-modal-text']}</p>} - submitBtnLabel="Start Replication1213" - headerTitle={app.i18n.en_US['replication-password-modal-header']} - onSuccess={this.submit} /> + <div id="dashboard-content" className="flex-body"> + <div className="replication-page flex-layout flex-col"> + {this.showSelectedTab()} + </div> + </div> </div> ); } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22732ae0/app/addons/replication/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/replication/stores.js b/app/addons/replication/stores.js index 6500ec8..a2a66e8 100644 --- a/app/addons/replication/stores.js +++ b/app/addons/replication/stores.js @@ -42,6 +42,11 @@ const ReplicationStore = FauxtonAPI.Store.extend({ this._replicationType = Constants.REPLICATION_TYPE.ONE_TIME; this._replicationDocName = ''; this._submittedNoChange = false; + this._selectedTab = "new replication"; + }, + + getSelectedTab () { + return this._selectedTab; }, getSubmittedNoChange () { @@ -135,12 +140,12 @@ const ReplicationStore = FauxtonAPI.Store.extend({ return this._password; }, - dispatch: function (action) { - switch (action.type) { + dispatch: function ({type, options}) { + switch (type) { case ActionTypes.INIT_REPLICATION: this._loading = true; - this._sourceDatabase = action.options.sourceDatabase; + this._sourceDatabase = options.sourceDatabase; if (this._sourceDatabase) { this._replicationSource = Constants.REPLICATION_SOURCE.LOCAL; @@ -152,13 +157,13 @@ const ReplicationStore = FauxtonAPI.Store.extend({ break; case ActionTypes.REPLICATION_DATABASES_LOADED: - this.setDatabases(action.options.databases); + this.setDatabases(options.databases); this._loading = false; break; case ActionTypes.REPLICATION_UPDATE_FORM_FIELD: this.changeAfterSubmit(); - this.updateFormField(action.options.fieldName, action.options.value); + this.updateFormField(options.fieldName, options.value); break; case ActionTypes.REPLICATION_CLEAR_FORM: @@ -169,6 +174,10 @@ const ReplicationStore = FauxtonAPI.Store.extend({ this._submittedNoChange = true; break; + case ActionTypes.REPLICATION_CHANGE_TAB: + this._selectedTab = options; + break; + case AccountActionTypes.AUTH_SHOW_PASSWORD_MODAL: this._isPasswordModalVisible = true; break; @@ -179,7 +188,7 @@ const ReplicationStore = FauxtonAPI.Store.extend({ case AccountActionTypes.AUTH_CREDS_VALID: this._authenticated = true; - this._password = action.options.password; + this._password = options.password; break; case AccountActionTypes.AUTH_CREDS_INVALID: