| // 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 FauxtonAPI from '../../../core/api'; |
| import {ReplicationSource} from './source'; |
| import {ReplicationTarget} from './target'; |
| import {ReplicationOptions} from './options'; |
| import {ReplicationSubmit} from './submit'; |
| import {ReplicationAuth} from './auth-options'; |
| import AuthAPI from '../../auth/api'; |
| import Constants from '../constants'; |
| import {ConflictModal} from './modals'; |
| import {isEmpty} from 'lodash'; |
| |
| export default class NewReplicationController extends React.Component { |
| constructor (props) { |
| super(props); |
| this.submit = this.submit.bind(this); |
| this.checkAuth = this.checkAuth.bind(this); |
| this.runReplicationChecks = this.runReplicationChecks.bind(this); |
| } |
| |
| checkAuth () { |
| this.props.hideConflictModal(); |
| const { replicationSource, replicationTarget, |
| sourceAuthType, targetAuthType, sourceAuth, targetAuth } = this.props; |
| |
| const isLocalSource = replicationSource === Constants.REPLICATION_SOURCE.LOCAL; |
| const isLocalTarget = replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE || |
| replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE; |
| |
| // Ask user to select an auth method for local source/target when one is not selected |
| // and not on admin party |
| if (!FauxtonAPI.session.isAdminParty()) { |
| if (isLocalSource && sourceAuthType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) { |
| FauxtonAPI.addNotification({ |
| msg: 'Missing credentials for local source database.', |
| type: 'error', |
| clear: true |
| }); |
| return; |
| } |
| if (isLocalTarget && targetAuthType === Constants.REPLICATION_AUTH_METHOD.NO_AUTH) { |
| FauxtonAPI.addNotification({ |
| msg: 'Missing credentials for local target database.', |
| type: 'error', |
| clear: true |
| }); |
| return; |
| } |
| } |
| |
| this.checkLocalAccountCredentials(sourceAuthType, sourceAuth, 'source', isLocalSource).then(() => { |
| this.checkLocalAccountCredentials(targetAuthType, targetAuth, 'target', isLocalTarget).then(() => { |
| this.submit(); |
| }, () => {}); |
| }, () => {}); |
| } |
| |
| checkLocalAccountCredentials(authType, auth, label, isLocal) { |
| // Skip check if it's a remote tb or not using BASIC auth |
| if (authType !== Constants.REPLICATION_AUTH_METHOD.BASIC || !isLocal) { |
| return FauxtonAPI.Promise.resolve(true); |
| } |
| |
| if (!auth.username || !auth.password) { |
| const err = `Missing ${label} credentials.`; |
| FauxtonAPI.addNotification({ |
| msg: err, |
| type: 'error', |
| clear: true |
| }); |
| return FauxtonAPI.Promise.reject(new Error(err)); |
| } |
| |
| return AuthAPI.login({ |
| name: auth.username, |
| password: auth.password |
| }).then((resp) => { |
| if (resp.error) { |
| throw (resp); |
| } |
| return true; |
| }).catch(err => { |
| FauxtonAPI.addNotification({ |
| msg: `Your username or password for ${label} database is incorrect.`, |
| type: 'error', |
| clear: true |
| }); |
| throw err; |
| }); |
| } |
| |
| checkReplicationDocID () { |
| const {showConflictModal, replicationDocName, checkReplicationDocID} = this.props; |
| checkReplicationDocID(replicationDocName).then(existingDoc => { |
| if (existingDoc) { |
| showConflictModal(); |
| return; |
| } |
| |
| this.checkAuth(); |
| }); |
| } |
| |
| runReplicationChecks () { |
| const {replicationDocName} = this.props; |
| if (!this.checkSourceTargetDatabases()) { |
| return; |
| } |
| if (replicationDocName) { |
| this.checkReplicationDocID(); |
| return; |
| } |
| |
| this.checkAuth(); |
| } |
| |
| checkSourceTargetDatabases () { |
| const { |
| remoteTarget, |
| remoteSource, |
| replicationTarget, |
| localTarget, |
| localSource, |
| databases |
| } = this.props; |
| |
| if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && _.includes(databases, localTarget)) { |
| FauxtonAPI.addNotification({ |
| msg: 'The <code>' + localTarget + '</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(localTarget)) { |
| error = 'The target database may not contain any spaces.'; |
| } else if (/^_/.test(localTarget)) { |
| 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 if remote source/target URL is valid |
| if (!isEmpty(remoteSource)) { |
| let errorMessage = ''; |
| try { |
| const url = new URL(remoteSource); |
| if (url.pathname.slice(1) === '') { |
| errorMessage = 'Invalid source database URL. Database name is missing.'; |
| } |
| } catch (err) { |
| errorMessage = 'Invalid source database URL.'; |
| } |
| if (errorMessage) { |
| FauxtonAPI.addNotification({ |
| msg: errorMessage, |
| type: 'error', |
| escape: false, |
| clear: true |
| }); |
| return false; |
| } |
| } |
| if (!isEmpty(remoteTarget)) { |
| let errorMessage = ''; |
| try { |
| const url = new URL(remoteTarget); |
| if (url.pathname.slice(1) === '') { |
| errorMessage = 'Invalid target database URL. Database name is missing.'; |
| } |
| } catch (err) { |
| errorMessage = 'Invalid target database URL.'; |
| } |
| if (errorMessage) { |
| FauxtonAPI.addNotification({ |
| msg: errorMessage, |
| 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 && !isEmpty(remoteTarget)) |
| || (localSource === localTarget && !isEmpty(localSource))) { |
| 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, |
| remoteTarget, |
| remoteSource, |
| localTarget, |
| localSource, |
| sourceAuthType, |
| sourceAuth, |
| targetAuthType, |
| targetAuth |
| } = this.props; |
| |
| let _rev; |
| if (replicationDocName) { |
| const doc = this.props.docs.find(doc => doc._id === replicationDocName); |
| if (doc) { |
| _rev = doc._rev; |
| } |
| } |
| |
| this.props.replicate({ |
| replicationTarget, |
| replicationSource, |
| replicationType, |
| replicationDocName, |
| localTarget, |
| localSource, |
| remoteTarget, |
| remoteSource, |
| _rev, |
| sourceAuthType, |
| sourceAuth, |
| targetAuthType, |
| targetAuth |
| }); |
| } |
| |
| confirmButtonEnabled () { |
| const { |
| remoteSource, |
| localSourceKnown, |
| replicationSource, |
| replicationTarget, |
| localTargetKnown, |
| localTarget, |
| submittedNoChange, |
| } = this.props; |
| |
| if (submittedNoChange) { |
| return false; |
| } |
| |
| if (!replicationSource || !replicationTarget) { |
| return false; |
| } |
| |
| if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL && !localSourceKnown) { |
| return false; |
| } |
| if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE && !localTargetKnown) { |
| return false; |
| } |
| |
| if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE && !localTarget) { |
| return false; |
| } |
| |
| if (replicationSource === Constants.REPLICATION_SOURCE.REMOTE && remoteSource === "") { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| render () { |
| const { |
| replicationSource, |
| replicationTarget, |
| replicationType, |
| replicationDocName, |
| conflictModalVisible, |
| databases, |
| localSource, |
| remoteSource, |
| remoteTarget, |
| localTarget, |
| updateFormField, |
| clearReplicationForm, |
| sourceAuthType, |
| sourceAuth, |
| targetAuthType, |
| targetAuth |
| } = this.props; |
| |
| return ( |
| <div style={ {paddingBottom: 20} }> |
| <ReplicationSource |
| replicationSource={replicationSource} |
| localSource={localSource} |
| databases={databases} |
| remoteSource={remoteSource} |
| onSourceSelect={updateFormField('replicationSource')} |
| onRemoteSourceChange={updateFormField('remoteSource')} |
| onLocalSourceChange={updateFormField('localSource')} |
| /> |
| <ReplicationAuth |
| credentials={sourceAuth} |
| authType={sourceAuthType} |
| onChangeAuthType={updateFormField('sourceAuthType')} |
| onChangeAuth={updateFormField('sourceAuth')} |
| authId={'replication-source-auth'} |
| /> |
| <hr className="replication__seperator" size="1"/> |
| <ReplicationTarget |
| replicationTarget={replicationTarget} |
| onTargetChange={updateFormField('replicationTarget')} |
| databases={databases} |
| localTarget={localTarget} |
| remoteTarget={remoteTarget} |
| onRemoteTargetChange={updateFormField('remoteTarget')} |
| onLocalTargetChange={updateFormField('localTarget')} |
| /> |
| <ReplicationAuth |
| credentials={targetAuth} |
| authType={targetAuthType} |
| onChangeAuthType={updateFormField('targetAuthType')} |
| onChangeAuth={updateFormField('targetAuth')} |
| authId={'replication-target-auth'} |
| /> |
| <hr className="replication__seperator" size="1"/> |
| <ReplicationOptions |
| replicationType={replicationType} |
| replicationDocName={replicationDocName} |
| onDocChange={updateFormField('replicationDocName')} |
| onTypeChange={updateFormField('replicationType')} |
| /> |
| <ReplicationSubmit |
| disabled={!this.confirmButtonEnabled()} |
| onClick={this.runReplicationChecks} |
| onClear={clearReplicationForm} |
| /> |
| <ConflictModal |
| visible={conflictModalVisible} |
| onClick={this.checkAuth} |
| onClose={this.props.hideConflictModal} |
| docId={replicationDocName} |
| /> |
| </div> |
| ); |
| } |
| } |