You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2016/09/21 15:19:10 UTC
[19/29] fauxton commit: updated refs/heads/new-replication to b0541e1
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 <ga...@gmail.com>
Authored: Wed Aug 17 17:14:54 2016 +0200
Committer: Garren Smith <ga...@gmail.com>
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 <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;
- }
- }
-
- return true;
- }
-
- render () {
- const { loading, replicationSource, replicationTarget, replicationType, replicationDocName, passwordModalVisible,
- localSourceDatabaseKnown, databases, localTargetDatabaseKnown, sourceDatabase, remoteSource, remoteTarget,
- targetDatabase } = this.state;
-
- if (loading) {
- return (
- <LoadLines />
- );
- }
-
- 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 (
- <div className="replication-page">
- <div className="row">
- <div className="span3">
- Replication Source:
- </div>
- <div className="span7">
- <ReplicationSource
- value={replicationSource}
- onChange={(repSource) => Actions.updateFormField('replicationSource', repSource)}/>
- </div>
- </div>
-
- {replicationSource ?
- <ReplicationSourceRow
- replicationSource={replicationSource}
- databases={databases}
- sourceDatabase={sourceDatabase}
- remoteSource={remoteSource}
- onChange={(val) => Actions.updateFormField('remoteSource', val)}
- /> : null}
-
- <hr size="1"/>
-
- <div className="row">
- <div className="span3">
- Replication Target:
- </div>
- <div className="span7">
- <ReplicationTarget
- value={replicationTarget}
- onChange={(repTarget) => Actions.updateFormField('replicationTarget', repTarget)}/>
- </div>
- </div>
- {replicationTarget ?
- <ReplicationTargetRow
- remoteTarget={remoteTarget}
- replicationTarget={replicationTarget}
- databases={databases}
- targetDatabase={targetDatabase}
- /> : null}
-
- <hr size="1"/>
-
- <div className="row">
- <div className="span3">
- Replication Type:
- </div>
- <div className="span7">
- <ReplicationType
- value={replicationType}
- onChange={(repType) => Actions.updateFormField('replicationType', repType)}/>
- </div>
- </div>
-
- <div className="row">
- <div className="span3">
- Replication Document:
- </div>
- <div className="span7">
- <div className="custom-id-field">
- <span className="fonticon fonticon-cancel" title="Clear field"
- onClick={(e) => Actions.updateFormField('replicationDocName', '')} />
- <input type="text" placeholder="Custom, new ID (optional)" value={replicationDocName}
- onChange={(e) => Actions.updateFormField('replicationDocName', e.target.value)}/>
- </div>
- </div>
- </div>
-
- <div className="row buttons-row">
- <div className="span3">
- </div>
- <div className="span7">
- <ConfirmButton id="replicate" text="Start Replication" onClick={this.showPasswordModal} disabled={!confirmButtonEnabled}/>
- <a href="#" data-bypass="true" onClick={this.clear}>Clear</a>
- </div>
- </div>
-
- <PasswordModal
- visible={passwordModalVisible}
- modalMessage={<p>Replication requires authentication.</p>}
- submitBtnLabel="Continue Replication"
- onSuccess={this.submit} />
- </div>
- );
- }
-}
-
-
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 (
- <option value={option.value} key={option.value}>{option.label}</option>
- );
- });
- }
-
- render () {
- return (
- <StyledSelect
- selectContent={this.getOptions()}
- selectChange={(e) => 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 => <option value={option.value} key={option.value}>{option.label}</option>);
+};
+
+const ReplicationType = ({value, onChange}) => {
+ return (
+ <div className="replication-section">
+ <div className="replication-input-label">
+ Replication Type:
+ </div>
+ <div className="replication-input-select">
+ <StyledSelect
+ selectContent={getReplicationTypeOptions()}
+ selectChange={(e) => onChange(e.target.value)}
+ selectId="replication-target"
+ selectValue={value} />
+ </div>
+ </div>
+ );
+};
+
+ReplicationType.propTypes = {
+ value: React.PropTypes.string.isRequired,
+ onChange: React.PropTypes.func.isRequired
+};
+
+const ReplicationDoc = ({value, onChange}) =>
+<div className="replication-section">
+ <div className="replication-input-label">
+ Replication Document:
+ </div>
+ <div className="replication-doc-name">
+ <span className="fonticon fonticon-cancel replication-doc-name-icon" title="Clear field"
+ onClick={(e) => onChange('')} />
+ <input type="text" className="replication-doc-name-input" placeholder="Custom, new ID (optional)" value={value}
+ onChange={(e) => onChange(e.target.value)}/>
+ </div>
+</div>;
+
+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 (
+ <div>
+ <ReplicationType
+ onChange={onTypeChange}
+ value={replicationType}
+ />
+ <ReplicationDoc
+ onChange={onDocChange}
+ value={replicationDocName}
+ />
+ </div>
+ );
+ }
+
+}
+
+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}) =>
+ <div className="replication-section">
+ <div className="replication-input-label">Database URL:</div>
+ <div className="">
+ <input type="text" className="replication-remote-connection-url" placeholder="https://" value={value}
+ onChange={(e) => onChange(e.target.value)} />
+ <div className="replication-remote-connection-url-text">e.g. https://$REMOTE_USERNAME:$REMOTE_PASSWORD@$REMOTE_SERVER/$DATABASE</div>
+ </div>
+ </div>;
+
+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 (
+ <div className="replication-section">
+ <div className="replication-input-label">
+ Source Name:
+ </div>
+ <div className="replication-input-react-select">
+ <ReactSelect
+ name="source-name"
+ value={value}
+ placeholder="Database name"
+ options={options}
+ clearable={false}
+ onChange={onChange} />
+ </div>
+ </div>
+ );
+};
+
+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 <LocalSourceInput
+ value={sourceDatabase}
+ databases={databases}
+ onChange={onChangeLocal}
+ />;
+ }
+
+ return <RemoteSourceInput value={remoteSource} onChange={onChangeRemote} />;
+};
+
+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 (
+ <option value={option.value} key={option.value}>{option.label}</option>
+ );
+ });
+};
+
+export const ReplicationSourceSelect = ({onChange, value}) => {
+
+ return (
+ <div className="replication-section">
+ <div className="replication-input-label">
+ Replication Source:
+ </div>
+ <div className="replication-input-select">
+ <StyledSelect
+ selectContent={replicationSourceSelectOptions()}
+ selectChange={(e) => onChange(e.target.value)}
+ selectId="replication-source"
+ selectValue={value} />
+ </div>
+ </div>
+ );
+};
+
+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 <ReplicationSourceRow
+ replicationSource={replicationSource}
+ databases={databases}
+ sourceDatabase={sourceDatabase}
+ remoteSource={remoteSource}
+ onChangeLocal={onLocalSourceChange}
+ onChangeRemote={onRemoteSourceChange}
+ />;
+ }
+
+ render () {
+ const {replicationSource, onSourceSelect, sourceDatabase, remoteSource, databases} = this.props;
+ const Actions = {};
+ return (
+ <div>
+ <ReplicationSourceSelect
+ onChange={onSourceSelect}
+ value={replicationSource}
+ />
+ {this.getReplicationSourceRow()}
+ </div>
+ );
+ }
+}
+
+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}) =>
+<div className="replication-button-row">
+ <ConfirmButton
+ id="replicate"
+ text="Start Replication"
+ onClick={onClick}
+ disabled={disabled}
+ />
+ <a className="replication-clear-link" href="#" data-bypass="true" onClick={onClear}>Clear</a>
+</div>;
+
+
+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 (
+ <option value={option.value} key={option.value}>{option.label}</option>
+ );
+ });
+};
+
+const ReplicationTargetSelect = ({value, onChange}) => {
+ return (
+ <div className="replication-section">
+ <div className="replication-input-label">
+ Replication Target:
+ </div>
+ <div className="replication-input-select">
+ <StyledSelect
+ selectContent={replicationTargetSourceOptions()}
+ selectChange={(e) => onChange(e.target.value)}
+ selectId="replication-target"
+ selectValue={value} />
+ </div>
+ </div>
+ );
+};
+
+ReplicationTargetSelect.propTypes = {
+ value: React.PropTypes.string.isRequired,
+ onChange: React.PropTypes.func.isRequired
+};
+
+const RemoteTargetReplicationRow = ({onChange, value}) => {
+ return (
+ <div>
+ <input type="text" className="replication-remote-connection-url" placeholder="https://" value={value}
+ onChange={(e) => onChange(e.target.value)} />
+ <div className="replication-remote-connection-url-text">e.g. https://$REMOTE_USERNAME:$REMOTE_PASSWORD@$REMOTE_SERVER/$DATABASE</div>
+ </div>
+ );
+};
+
+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 (
+ <div className="replication-input-react-select">
+ <ReactSelect
+ value={value}
+ options={options}
+ placeholder="Database name"
+ clearable={false}
+ onChange={(selected) => onChange(selected.value)}
+ />
+ </div>
+ );
+};
+
+ExistingLocalTargetReplicationRow.propTypes = {
+ value: React.PropTypes.string.isRequired,
+ databases: React.PropTypes.array.isRequired,
+ onChange: React.PropTypes.func.isRequired
+};
+
+const NewLocalTargetReplicationRow = ({onChange, value}) =>
+ <input
+ type="text"
+ className="replication-new-input"
+ placeholder="Database name"
+ value={value}
+ onChange={(e) => 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 = <NewLocalTargetReplicationRow
+ value={localTarget}
+ onChange={onLocalTargetChange}
+ />;
+ } else if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
+ input = <ExistingLocalTargetReplicationRow
+ onChange={onLocalTargetChange}
+ databases={databases}
+ value={localTarget}
+ />;
+ } else {
+ input = <RemoteTargetReplicationRow
+ onChange={onRemoteTargetChange}
+ value={remoteTarget}
+ />;
+ }
+
+ let targetLabel = 'Target Name:';
+
+ if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
+ replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
+ targetLabel = 'New Database:';
+ }
+
+ return (
+ <div className="replication-section">
+ <div className="replication-input-label">{targetLabel}</div>
+ <div>
+ {input}
+ </div>
+ </div>
+ );
+};
+
+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 (
+ <div>
+ <ReplicationTargetSelect
+ value={replicationTarget}
+ onChange={onTargetChange}
+ />
+ <ReplicationTargetRow
+ remoteTarget={remoteTarget}
+ replicationTarget={replicationTarget}
+ databases={databases}
+ localTarget={localTarget}
+ onRemoteTargetChange={onRemoteTargetChange}
+ onLocalTargetChange={onLocalTargetChange}
+ />
+ </div>
+ );
+ }
+}
+
+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 <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;
+ }
+ }
+
+ 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 (
+ <LoadLines />
+ );
+ }
+
+ 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={() => {}}
+ onClear={Actions.clearReplicationForm}
+ />
+ </div>
+ );
+ }
+}
+
+
+
+/*
+<div className="row">
+ <div className="span3">
+ Replication Target:
+ </div>
+ <div className="span7">
+ <ReplicationTarget
+ value={replicationTarget}
+ onChange={(repTarget) => Actions.updateFormField('replicationTarget', repTarget)}/>
+ </div>
+</div>
+{replicationTarget ?
+ <ReplicationTargetRow
+ remoteTarget={remoteTarget}
+ replicationTarget={replicationTarget}
+ databases={databases}
+ targetDatabase={targetDatabase}
+ /> : null}
+
+<hr className="replication-seperator" className="replication-seperator" size="1"/>
+
+<div className="row">
+ <div className="span3">
+ Replication Type:
+ </div>
+ <div className="span7">
+ <ReplicationType
+ value={replicationType}
+ onChange={(repType) => Actions.updateFormField('replicationType', repType)}/>
+ </div>
+</div>
+
+<div className="row">
+ <div className="span3">
+ Replication Document:
+ </div>
+ <div className="span7">
+ <div className="custom-id-field">
+ <span className="fonticon fonticon-cancel" title="Clear field"
+ onClick={(e) => Actions.updateFormField('replicationDocName', '')} />
+ <input type="text" placeholder="Custom, new ID (optional)" value={replicationDocName}
+ onChange={(e) => Actions.updateFormField('replicationDocName', e.target.value)}/>
+ </div>
+ </div>
+</div>
+
+<div className="row buttons-row">
+ <div className="span3">
+ </div>
+ <div className="span7">
+ <ConfirmButton id="replicate" text="Start Replication" onClick={this.showPasswordModal} disabled={!this.confirmButtonEnabled()}/>
+ <a href="#" data-bypass="true" onClick={this.clear}>Clear</a>
+ </div>
+</div>
+
+<PasswordModal
+ visible={passwordModalVisible}
+ modalMessage={<p>Replication requires authentication.</p>}
+ 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});
}
});