couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gar...@apache.org
Subject [couchdb-fauxton] branch master updated: (#864) - Update replication to work with scheduler api
Date Tue, 14 Mar 2017 14:30:24 GMT
This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git

The following commit(s) were added to refs/heads/master by this push:
       new  5f7eb98   (#864) - Update replication to work with scheduler api
5f7eb98 is described below

commit 5f7eb987c9c660c153fb4ddf964b967e0776ea50
Author: garren smith <garren.smith@gmail.com>
AuthorDate: Tue Mar 14 16:30:21 2017 +0200

    (#864) - Update replication to work with scheduler api
    
    This brings the replication section up to speed with the pending _scheduler endpoints. It allows a user to view and delete _replicate replications as well.
---
 .travis.yml                                        |   5 +-
 .../components/assets/less/styled-select.less      |   7 +
 app/addons/databases/components.react.jsx          |   2 +-
 app/addons/replication/__tests__/actions.test.js   | 182 +++++++++++
 app/addons/replication/__tests__/api.tests.js      | 145 ++++++++-
 .../storesSpec.js => __tests__/stores.tests.js}    |   0
 app/addons/replication/actions.js                  | 247 ++++++++++++---
 app/addons/replication/actiontypes.js              |  11 +-
 app/addons/replication/api.js                      | 205 +++++++++++-
 .../replication/assets/less/replication.less       |  36 ++-
 app/addons/replication/base.js                     |   9 +
 app/addons/replication/components/activity.js      | 352 +--------------------
 .../replication/components/common-activity.js      |  50 +++
 .../components/{activity.js => common-table.js}    | 325 ++++++++-----------
 app/addons/replication/components/modals.js        |  45 ++-
 app/addons/replication/components/remoteexample.js |   6 +-
 .../replication/components/replicate-activity.js   | 116 +++++++
 app/addons/replication/components/submit.js        |   1 +
 app/addons/replication/constants.js                |   1 -
 app/addons/replication/controller.js               | 124 ++++++--
 app/addons/replication/route.js                    |  22 +-
 app/addons/replication/stores.js                   | 154 +++++++--
 .../replication/tests/nightwatch/replication.js    |  12 +-
 app/core/api.js                                    |   4 +-
 assets/less/notification-center.less               |   1 +
 docker/dc.selenium.yml                             |   2 +-
 jest-setup.js                                      |   5 +
 27 files changed, 1389 insertions(+), 680 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 48d54d3..7eccf52 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,13 @@
 language: node_js
 node_js:
    - 6
-sudo: required
+sudo: false
 services:
   - docker
 git:
   depth: 1
 
 before_script:
-  - ./bin/install-docker-travis.sh
   - npm run docker:up
   - npm install
   - docker logs couchdb
@@ -19,6 +18,8 @@ before_script:
   - grunt debugDev
   - DIST=./dist/debug ./bin/fauxton &
   - sleep 30
+  - docker logs couchdb
+  - curl http://127.0.0.1:5984
 script:
   - travis_retry ./node_modules/.bin/grunt nightwatch
 after_script:
diff --git a/app/addons/components/assets/less/styled-select.less b/app/addons/components/assets/less/styled-select.less
index 1227527..45e94e7 100644
--- a/app/addons/components/assets/less/styled-select.less
+++ b/app/addons/components/assets/less/styled-select.less
@@ -31,6 +31,13 @@
   color: #333;
 }
 
+//bug in firefox for text-indent
+@-moz-document url-prefix() {
+  .styled-select  select {
+    text-indent: 0px;
+  }
+}
+
 .styled-select select:-moz-focusring {
   color: transparent;
   text-shadow: 0 0 0 #000;
diff --git a/app/addons/databases/components.react.jsx b/app/addons/databases/components.react.jsx
index cddc602..58cf053 100644
--- a/app/addons/databases/components.react.jsx
+++ b/app/addons/databases/components.react.jsx
@@ -182,7 +182,7 @@ var DatabaseRow = React.createClass({
         <td className="database-actions">
           <a className="db-actions btn fonticon-replicate set-replication-start"
             title={"Replicate " + name}
-            href={"#/replication/" + encodedId} />
+            href={"#/replication/_create/" + encodedId} />
           <a
             className="db-actions btn icon-lock set-permissions"
             title={"Set permissions for " + name} href={"#/database/" + encodedId + "/permissions"} />
diff --git a/app/addons/replication/__tests__/actions.test.js b/app/addons/replication/__tests__/actions.test.js
new file mode 100644
index 0000000..804aa6b
--- /dev/null
+++ b/app/addons/replication/__tests__/actions.test.js
@@ -0,0 +1,182 @@
+// 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 utils from '../../../../test/mocha/testUtils';
+import {replicate, getReplicationStateFrom, deleteDocs} from '../actions';
+import ActionTypes from '../actiontypes';
+import fetchMock from 'fetch-mock';
+import app from '../../../app';
+import FauxtonAPI from '../../../core/api';
+
+app.session = {
+  get () {
+    return 'test-user-name';
+  }
+};
+
+Object.defineProperty(window.location, 'origin', {
+  writable: true,
+  value: 'http://dev:8000'
+});
+
+const assert = utils.assert;
+
+describe("Replication Actions", () => {
+
+  describe('replicate', () => {
+    afterEach(fetchMock.restore);
+
+    it('creates a new database if it does not exist', (done) => {
+      fetchMock.postOnce('/_replicator', {
+        status: 404,
+        body: {
+          error: "not_found",
+          reason: "Database does not exist."
+        }
+      });
+
+      fetchMock.putOnce('/_replicator', {
+        status: 200,
+        body: {
+          ok: true
+        }
+      });
+
+      const finalPost = fetchMock.postOnce('/_replicator', {
+        status: 200,
+        body: {
+          ok: true
+        }
+      });
+
+      replicate ({
+        localSource: "animaldb",
+        localTarget: "boom123",
+        password: "testerpass",
+        remoteSource: "",
+        remoteTarget: "",
+        replicationDocName: "",
+        replicationSource: "REPLICATION_SOURCE_LOCAL",
+        replicationTarget: "REPLICATION_TARGET_NEW_LOCAL_DATABASE",
+        replicationType: "",
+        username: "tester"
+      });
+
+      //this is not pretty, and might cause some false errors. But its tricky to tell when this test has completed
+      setTimeout(() => {
+        assert.ok(finalPost.called('/_replicator'));
+        done();
+      }, 100);
+    });
+  });
+
+  describe('getReplicationStateFrom', () => {
+    const doc = {
+      "_id": "7dcea9874a8fcb13c6630a1547001559",
+      "_rev": "2-98d29cc74e77b6dc38f5fc0dcec0033c",
+      "user_ctx": {
+        "name": "tester",
+        "roles": [
+          "_admin",
+          "_reader",
+          "_writer"
+        ]
+      },
+      "source": {
+        "headers": {
+          "Authorization": "Basic dGVzdGVyOnRlc3RlcnBhc3M="
+        },
+        "url": "http://dev:8000/animaldb"
+      },
+      "target": {
+        "headers": {
+          "Authorization": "Basic dGVzdGVyOnRlc3RlcnBhc3M="
+        },
+        "url": "http://dev:8000/boom123"
+      },
+      "create_target": true,
+      "continuous": false,
+      "owner": "tester",
+      "_replication_id": "90ff5a45623aa6821a6b0c20f5d3b5e8"
+    };
+
+    const docState = {
+      "replicationDocName": "7dcea9874a8fcb13c6630a1547001559",
+      "replicationType": "REPLICATION_TYPE_ONE_TIME",
+      "replicationSource": "REPLICATION_SOURCE_LOCAL",
+      "localSource": "animaldb",
+      "replicationTarget": "REPLICATION_TARGET_EXISTING_LOCAL_DATABASE",
+      "localTarget": "boom123"
+    };
+
+    it('builds up correct state', (done) => {
+      FauxtonAPI.dispatcher.register(({type, options}) => {
+        if (ActionTypes.REPLICATION_SET_STATE_FROM_DOC === type) {
+          assert.deepEqual(docState, options);
+          setTimeout(done);
+        }
+      });
+
+      fetchMock.getOnce('/_replicator/7dcea9874a8fcb13c6630a1547001559', doc);
+      getReplicationStateFrom(doc._id);
+    });
+  });
+
+  describe('deleteDocs', () => {
+    it('sends bulk doc request', (done) => {
+      const resp = [
+        {
+          "ok": true,
+          "id": "should-fail",
+          "rev": "32-14e8495723c34271ef1391adf83defc2"
+        },
+        {
+          "ok": true,
+          "id": "my-cool-id",
+          "rev": "3-f16f14d11708952b3d787846ef6ef8a9"
+        }
+      ];
+
+      const docs = [
+        {
+          _id: "should-fail",
+          _rev: "31-cdc233eb8a98e3aa3a87cd72f6a86301",
+          raw: {
+            _id: "should-fail",
+            _rev: "31-cdc233eb8a98e3aa3a87cd72f6a86301"
+          },
+        },
+        {
+          _id: "my-cool-id",
+          _rev: "2-da6af558740409e61d563769a8044a68",
+          raw: {
+            _id: "my-cool-id",
+            _rev: "2-da6af558740409e61d563769a8044a68"
+          }
+        }
+      ];
+
+      fetchMock.getOnce('/_scheduler/job', 404);
+      fetchMock.getOnce('/_replicator/_all_docs?include_docs=true&limit=100', {rows: []});
+      fetchMock.postOnce('/_replicator/_bulk_docs', {
+        status: 200,
+        body: resp
+      });
+      deleteDocs(docs);
+
+      FauxtonAPI.dispatcher.register(({type}) => {
+        if (ActionTypes.REPLICATION_CLEAR_SELECTED_DOCS === type) {
+          setTimeout(done);
+        }
+      });
+    });
+  });
+});
diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js
index 3ebb792..54721c2 100644
--- a/app/addons/replication/__tests__/api.tests.js
+++ b/app/addons/replication/__tests__/api.tests.js
@@ -21,9 +21,12 @@ import {
   decodeFullUrl,
   getCredentialsFromUrl,
   removeCredentialsFromUrl,
-  removeSensitiveUrlInfo
+  removeSensitiveUrlInfo,
+  supportNewApi,
+  fetchReplicationDocs
 } from '../api';
 import Constants from '../constants';
+import fetchMock from 'fetch-mock';
 
 const assert = utils.assert;
 
@@ -299,6 +302,146 @@ describe('Replication API', () => {
       const url = removeCredentialsFromUrl("https://bob:m@:/rley@my-couchdb.com/db");
       assert.deepEqual(url, 'https://my-couchdb.com/db');
     });
+  });
+
+  describe('supportNewApi', () => {
+    afterEach(() => {
+      fetchMock.restore();
+    });
+
+    it('returns true for support', () => {
+      fetchMock.getOnce('/_scheduler/jobs', 200);
+      return supportNewApi(true)
+      .then(resp => {
+        assert.ok(resp);
+      });
+    });
+
+    it('returns false for no support', () => {
+      fetchMock.getOnce('/_scheduler/jobs', 404);
+      return supportNewApi(true)
+      .then(resp => {
+        assert.notOk(resp);
+      });
+    });
+
+  });
 
+  describe("fetchReplicationDocs", () => {
+    const _repDocs = {
+      "total_rows":2,
+      "offset":0,
+      "rows":[
+          {
+            "id":"_design/_replicator",
+            "key":"_design/_replicator",
+            "value":{
+              "rev":"1-1390740c4877979dbe8998382876556c"
+            },
+            "doc":{"_id":"_design/_replicator",
+            "_rev":"1-1390740c4877979dbe8998382876556c",
+            "language":"javascript",
+            "validate_doc_update":"\n    function(newDoc, oldDoc, userCtx) {\n        function reportError(error_msg) {\n            log('Error writing document `' + newDoc._id +\n                '\\' to the replicator database: ' + error_msg);\n            throw({forbidden: error_msg});\n        }\n\n        function validateEndpoint(endpoint, fieldName) {\n            if ((typeof endpoint !== 'string') &&\n                ((typeof endpoint !== 'object') || (endpoint === null))) {\n\n   [...]
+          }
+        },
+        {
+          "id":"c94d4839d1897105cb75e1251e0003ea",
+          "key":"c94d4839d1897105cb75e1251e0003ea",
+          "value":{
+            "rev":"3-4559cb522de85ce03bd0e1991025e89a"
+          },
+          "doc":{"_id":"c94d4839d1897105cb75e1251e0003ea",
+          "_rev":"3-4559cb522de85ce03bd0e1991025e89a",
+          "user_ctx":{
+            "name":"tester",
+            "roles":["_admin", "_reader", "_writer"]},
+            "source":{
+              "headers":{
+                "Authorization":"Basic dGVzdGVyOnRlc3RlcnBhc3M="
+              },
+              "url":"http://dev:5984/animaldb"},
+              "target":{
+                "headers":{
+                  "Authorization":"Basic dGVzdGVyOnRlc3RlcnBhc3M="},
+                  "url":"http://dev:5984/animaldb-clone"
+                },
+                "create_target":true,
+                "continuous":false,
+                "owner":"tester",
+                "_replication_state":"completed",
+                "_replication_state_time":"2017-02-28T12:16:28+00:00",
+                "_replication_id":"0ce2939af29317b5dbe11c15570ddfda",
+                "_replication_stats":{
+                  "revisions_checked":14,
+                  "missing_revisions_found":14,
+                  "docs_read":14,
+                  "docs_written":14,
+                  "changes_pending":null,
+                  "doc_write_failures":0,
+                  "checkpointed_source_seq":"15-g1AAAAJDeJyV0N0NgjAQAOAKRnlzBJ3AcKWl9Uk20ZbSEII4gm6im-gmugke1AQJ8aFpck3u50vuakJIVIaGrJqzKSADKrYxPqixECii123bVmWoFidMLGVsqEjYtP0voTcY9f6rzHqFKcglsz5K1imHkcJTnoJVPsqxUy4jxepEioJ7KM0cI7nih9BtkDSlkAif2zjp7qRHJwW9lLNdDkZ6S08nvQZJMsNT4b_d20k_d4oVE1aK6VT1AXTajes"
+                }
+            }
+        }
+    ]};
+
+    const _schedDocs = {
+      "offset": 0,
+      "docs": [
+        {
+          "database":"_replicator",
+          "doc_id":"c94d4839d1897105cb75e1251e0003ea",
+          "id":null,
+          "state":"completed",
+          "error_count":0,
+          "info":{
+            "revisions_checked":0,
+            "missing_revisions_found":0,
+            "docs_read":0,
+            "docs_written":0,
+            "changes_pending":null,
+            "doc_write_failures":0,
+            "checkpointed_source_seq":"56-g1AAAAGweJzLYWBgYMlgTmFQTElKzi9KdUhJMjTQy00tyixJTE_VS87JL01JzCvRy0styQEqZUpkSLL___9_VgZzIm8uUIDd1NIkNSk5LYVBAW6AKXb9aLYY47ElyQFIJtVDLeIBW2ScbGJiYGJKjBloNhnisSmPBUgyNAApoGX7QbaJg21LTDEwNE8zR_aWCVGW4VCFZNkBiGVgr3GALTNLSzQ0T0xEtgyHm7MAbEaMZw"},
+            "last_updated":"2017-03-07T14:46:17Z",
+            "start_time":"2017-03-07T14:46:16Z"
+          }
+      ],
+      "total": 1
+    };
+
+    describe('old api', () => {
+      afterEach(() => {
+        fetchMock.restore();
+      });
+
+      it("returns parsedReplicationDocs", () => {
+        fetchMock.getOnce('/_scheduler/jobs', 404);
+        fetchMock.get('/_replicator/_all_docs?include_docs=true&limit=100', _repDocs);
+        return supportNewApi(true)
+        .then(fetchReplicationDocs)
+        .then(docs => {
+          assert.deepEqual(docs.length, 1);
+          assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea");
+        });
+      });
+    });
+
+    describe('new api', () => {
+      afterEach(() => {
+        fetchMock.restore();
+      });
+
+      it("returns parsedReplicationDocs", () => {
+        fetchMock.getOnce('/_scheduler/jobs', 200);
+        fetchMock.get('/_replicator/_all_docs?include_docs=true&limit=100', _repDocs);
+        fetchMock.get('/_scheduler/docs?include_docs=true', _schedDocs);
+        return supportNewApi(true)
+        .then(fetchReplicationDocs)
+        .then(docs => {
+          assert.deepEqual(docs.length, 1);
+          assert.deepEqual(docs[0]._id, "c94d4839d1897105cb75e1251e0003ea");
+          assert.deepEqual(docs[0].stateTime, new Date('2017-03-07T14:46:17'));
+        });
+      });
+    });
   });
 });
diff --git a/app/addons/replication/tests/storesSpec.js b/app/addons/replication/__tests__/stores.tests.js
similarity index 100%
rename from app/addons/replication/tests/storesSpec.js
rename to app/addons/replication/__tests__/stores.tests.js
diff --git a/app/addons/replication/actions.js b/app/addons/replication/actions.js
index cdf5bfb..83abb24 100644
--- a/app/addons/replication/actions.js
+++ b/app/addons/replication/actions.js
@@ -9,12 +9,20 @@
 // 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 app from '../../app';
 import FauxtonAPI from '../../core/api';
 import ActionTypes from './actiontypes';
 import Helpers from './helpers';
 import Constants from './constants';
-import {createReplicationDoc, fetchReplicationDocs, decodeFullUrl} from './api';
+import {
+  supportNewApi,
+  createReplicationDoc,
+  fetchReplicateInfo,
+  fetchReplicationDocs,
+  decodeFullUrl,
+  deleteReplicatesApi,
+  createReplicatorDB
+} from './api';
+import 'whatwg-fetch';
 
 
 function initReplicator (localSource) {
@@ -26,11 +34,16 @@ function initReplicator (localSource) {
       }
     });
   }
-  $.ajax({
-    url: app.host + '/_all_dbs',
-    contentType: 'application/json',
-    dataType: 'json'
-  }).then((databases) => {
+
+  fetch('/_all_dbs', {
+    credentials: 'include',
+    headers: {
+      'Accept': 'application/json; charset=utf-8',
+      'Content-Type': 'application/json'
+    },
+  })
+  .then(resp => resp.json())
+  .then((databases) => {
     FauxtonAPI.dispatch({
       type: ActionTypes.REPLICATION_DATABASES_LOADED,
       options: {
@@ -40,16 +53,19 @@ function initReplicator (localSource) {
   });
 }
 
-function replicate (params) {
+export const replicate = (params) => {
   const replicationDoc = createReplicationDoc(params);
 
-  const promise = $.ajax({
-    url: window.location.origin + '/_replicator',
-    contentType: 'application/json',
-    type: 'POST',
-    dataType: 'json',
-    data: JSON.stringify(replicationDoc)
-  });
+  const promise = fetch('/_replicator', {
+    method: 'POST',
+    credentials: 'include',
+    headers: {
+        'Accept': 'application/json; charset=utf-8',
+        'Content-Type': 'application/json'
+    },
+    body: JSON.stringify(replicationDoc)
+  })
+  .then(res => res.json());
 
   const source = Helpers.getDatabaseLabel(replicationDoc.source);
   const target = Helpers.getDatabaseLabel(replicationDoc.target);
@@ -58,22 +74,37 @@ function replicate (params) {
     type: ActionTypes.REPLICATION_STARTING,
   });
 
-  promise.then(() => {
+  const handleError = (json) => {
     FauxtonAPI.addNotification({
-      msg: `Replication from <code>${decodeURIComponent(source)}</code> to <code>${decodeURIComponent(target)}</code> has been scheduled.`,
-      type: 'success',
-      escape: false,
+      msg: json.reason,
+      type: 'error',
       clear: true
     });
-  }, (xhr) => {
-    const errorMessage = JSON.parse(xhr.responseText);
+  };
+
+  promise.then(json => {
+    if (!json.ok) {
+      throw json;
+    }
+
     FauxtonAPI.addNotification({
-      msg: errorMessage.reason,
-      type: 'error',
+      msg: `Replication from <code>${decodeURIComponent(source)}</code> to <code>${decodeURIComponent(target)}</code> has been scheduled.`,
+      type: 'success',
+      escape: false,
       clear: true
     });
+  })
+  .catch(json => {
+    if (json.error && json.error === "not_found") {
+      return createReplicatorDB().then(() => {
+        return replicate(params);
+      })
+      .catch(handleError);
+    }
+
+    handleError(json);
   });
-}
+};
 
 function updateFormField (fieldName, value) {
   FauxtonAPI.dispatch({
@@ -89,12 +120,12 @@ function clearReplicationForm () {
   FauxtonAPI.dispatch({ type: ActionTypes.REPLICATION_CLEAR_FORM });
 }
 
-const getReplicationActivity = () => {
+const getReplicationActivity = (supportNewApi) => {
   FauxtonAPI.dispatch({
       type: ActionTypes.REPLICATION_FETCHING_STATUS,
   });
 
-  fetchReplicationDocs().then(docs => {
+  fetchReplicationDocs(supportNewApi).then(docs => {
     FauxtonAPI.dispatch({
       type: ActionTypes.REPLICATION_STATUS,
       options: docs
@@ -102,6 +133,27 @@ const getReplicationActivity = () => {
   });
 };
 
+const getReplicateActivity = () => {
+  supportNewApi()
+  .then(newApi => {
+    if (!newApi) {
+      return;
+    }
+
+    FauxtonAPI.dispatch({
+        type: ActionTypes.REPLICATION_FETCHING_REPLICATE_STATUS,
+    });
+
+    fetchReplicateInfo()
+    .then(replicateInfo => {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.REPLICATION_REPLICATE_STATUS,
+        options: replicateInfo
+      });
+    });
+  });
+};
+
 const filterDocs = (filter) => {
   FauxtonAPI.dispatch({
     type: ActionTypes.REPLICATION_FILTER_DOCS,
@@ -109,6 +161,13 @@ const filterDocs = (filter) => {
   });
 };
 
+const filterReplicate = (filter) => {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.REPLICATION_FILTER_REPLICATE,
+    options: filter
+  });
+};
+
 const selectAllDocs = () => {
   FauxtonAPI.dispatch({
     type: ActionTypes.REPLICATION_TOGGLE_ALL_DOCS
@@ -122,13 +181,32 @@ const selectDoc = (id) => {
   });
 };
 
+const selectAllReplicates = () => {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.REPLICATION_TOGGLE_ALL_REPLICATE
+  });
+};
+
+const selectReplicate = (id) => {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.REPLICATION_TOGGLE_REPLICATE,
+    options: id
+  });
+};
+
 const clearSelectedDocs = () => {
   FauxtonAPI.dispatch({
     type: ActionTypes.REPLICATION_CLEAR_SELECTED_DOCS
   });
 };
 
-const deleteDocs = (docs) => {
+const clearSelectedReplicates = () => {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.REPLICATION_CLEAR_SELECTED_REPLICATES
+  });
+};
+
+export const deleteDocs = (docs) => {
   const bulkDocs = docs.map(({raw: doc}) => {
     doc._deleted = true;
     return doc;
@@ -141,13 +219,23 @@ const deleteDocs = (docs) => {
     clear: true
   });
 
-  $.ajax({
-    url: app.host + '/_replicator/_bulk_docs',
-    contentType: 'application/json',
-    dataType: 'json',
+  fetch('/_replicator/_bulk_docs', {
+    credentials: 'include',
+    headers: {
+      'Accept': 'application/json; charset=utf-8',
+      'Content-Type': 'application/json'
+    },
     method: 'POST',
-    data: JSON.stringify({docs: bulkDocs})
-  }).then(() => {
+    body: JSON.stringify({docs: bulkDocs})
+  })
+  .then(resp => {
+    if (!resp.ok) {
+      throw resp;
+    }
+    return resp.json();
+  })
+  .then(() => {
+
     let msg = 'The selected documents have been deleted.';
     if (docs.length === 1) {
       msg = `Document <code>${docs[0]._id}</code> has been deleted`;
@@ -159,8 +247,47 @@ const deleteDocs = (docs) => {
       escape: false,
       clear: true
     });
+
     clearSelectedDocs();
     getReplicationActivity();
+  })
+  .catch(resp => {
+    resp.json()
+    .then(error => {
+      FauxtonAPI.addNotification({
+        msg: error.reason,
+        type: 'error',
+        clear: true
+      });
+    });
+
+  });
+};
+
+const deleteReplicates = (replicates) => {
+  FauxtonAPI.addNotification({
+    msg: `Deleting _replicate${replicates.length > 1 ? 's' : ''}.`,
+    type: 'success',
+    escape: false,
+    clear: true
+  });
+
+  deleteReplicatesApi(replicates)
+  .then(() => {
+    let msg = 'The selected replications have been deleted.';
+    if (replicates.length === 1) {
+      msg = `Replication <code>${replicates[0]._id}</code> has been deleted`;
+    }
+
+    clearSelectedReplicates();
+    getReplicateActivity();
+
+    FauxtonAPI.addNotification({
+      msg: msg,
+      type: 'success',
+      escape: false,
+      clear: true
+    });
   }, (xhr) => {
     const errorMessage = JSON.parse(xhr.responseText);
     FauxtonAPI.addNotification({
@@ -171,13 +298,20 @@ const deleteDocs = (docs) => {
   });
 };
 
-const getReplicationStateFrom = (id) => {
-  $.ajax({
-    url: `${app.host}/_replicator/${encodeURIComponent(id)}`,
-    contentType: 'application/json',
-    dataType: 'json',
+export const getReplicationStateFrom = (id) => {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.REPLICATION_FETCHING_FORM_STATE
+  });
+
+  fetch(`/_replicator/${encodeURIComponent(id)}`, {
+    credentials: 'include',
+    headers: {
+      'Accept': 'application/json; charset=utf-8',
+    },
     method: 'GET'
-  }).then((doc) => {
+  })
+  .then(resp => resp.json())
+  .then((doc) => {
     const stateDoc = {
       replicationDocName: doc._id,
       replicationType: doc.continuous ? Constants.REPLICATION_TYPE.CONTINUOUS : Constants.REPLICATION_TYPE.ONE_TIME,
@@ -209,10 +343,10 @@ const getReplicationStateFrom = (id) => {
       options: stateDoc
     });
 
-  }, (xhr) => {
-    const errorMessage = JSON.parse(xhr.responseText);
+  })
+  .catch(error => {
     FauxtonAPI.addNotification({
-      msg: errorMessage.reason,
+      msg: error.reason,
       type: 'error',
       clear: true
     });
@@ -238,7 +372,28 @@ const changeActivitySort = (sort) => {
   });
 };
 
+const changeTabSection = (newSection, url) => {
+  FauxtonAPI.dispatch({
+    type: ActionTypes.REPLICATION_CHANGE_TAB_SECTION,
+    options: newSection
+  });
+
+  if (url) {
+    FauxtonAPI.navigate(url, {trigger: false});
+  }
+};
+
+const checkForNewApi = () => {
+  supportNewApi().then(newApi => {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.REPLICATION_SUPPORT_NEW_API,
+      options: newApi
+    });
+  });
+};
+
 export default {
+  checkForNewApi,
   initReplicator,
   replicate,
   updateFormField,
@@ -252,5 +407,11 @@ export default {
   showConflictModal,
   hideConflictModal,
   changeActivitySort,
-  clearSelectedDocs
+  clearSelectedDocs,
+  changeTabSection,
+  getReplicateActivity,
+  filterReplicate,
+  selectReplicate,
+  selectAllReplicates,
+  deleteReplicates
 };
diff --git a/app/addons/replication/actiontypes.js b/app/addons/replication/actiontypes.js
index f2bb478..5089ea4 100644
--- a/app/addons/replication/actiontypes.js
+++ b/app/addons/replication/actiontypes.js
@@ -27,5 +27,14 @@ export default {
   REPLICATION_SHOW_CONFLICT_MODAL: 'REPLICATION_SHOW_CONFLICT_MODAL',
   REPLICATION_HIDE_CONFLICT_MODAL: 'REPLICATION_HIDE_CONFLICT_MODAL',
   REPLICATION_CHANGE_ACTIVITY_SORT: 'REPLICATION_CHANGE_ACTIVITY_SORT',
-  REPLICATION_CLEAR_SELECTED_DOCS: 'REPLICATION_CLEAR_SELECTED_DOCS'
+  REPLICATION_CLEAR_SELECTED_DOCS: 'REPLICATION_CLEAR_SELECTED_DOCS',
+  REPLICATION_CHANGE_TAB_SECTION: 'REPLICATION_CHANGE_TAB_SECTION',
+  REPLICATION_FETCHING_REPLICATE_STATUS: 'REPLICATION_FETCHING_REPLICATE_STATUS',
+  REPLICATION_REPLICATE_STATUS: 'REPLICATION_REPLICATE_STATUS',
+  REPLICATION_SUPPORT_NEW_API: 'REPLICATION_SUPPORT_NEW_API',
+  REPLICATION_FILTER_REPLICATE: 'REPLICATION_FILTER_REPLICATE',
+  REPLICATION_TOGGLE_ALL_REPLICATE: 'REPLICATION_TOGGLE_ALL_REPLICATE',
+  REPLICATION_TOGGLE_REPLICATE: 'REPLICATION_TOGGLE_REPLICATE',
+  REPLICATION_CLEAR_SELECTED_REPLICATES: 'REPLICATION_CLEAR_SELECTED_REPLICATES',
+  REPLICATION_FETCHING_FORM_STATE: 'REPLICATION_FETCHING_FORM_STATE'
 };
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index 0bda517..6f29423 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -16,6 +16,30 @@ import app from '../../app';
 import FauxtonAPI from '../../core/api';
 import base64 from 'base-64';
 import _ from 'lodash';
+import 'whatwg-fetch';
+
+let newApiPromise = null;
+export const supportNewApi = (forceCheck) => {
+  if (!newApiPromise || forceCheck) {
+    newApiPromise = new FauxtonAPI.Promise((resolve) => {
+      fetch('/_scheduler/jobs', {
+        credentials: 'include',
+        headers: {
+            'Accept': 'application/json; charset=utf-8',
+          }
+        })
+      .then(resp => {
+        if (resp.status > 202) {
+          return resolve(false);
+        }
+
+        resolve(true);
+      });
+    });
+  }
+
+  return newApiPromise;
+};
 
 export const encodeFullUrl = (fullUrl) => {
   if (!fullUrl) {return '';}
@@ -208,6 +232,9 @@ export const removeSensitiveUrlInfo = (url) => {
 
 export const getDocUrl = (doc) => {
   let url = doc;
+  if (!doc) {
+    return '';
+  }
 
   if (typeof doc === "object") {
     url = doc.url;
@@ -228,34 +255,98 @@ export const parseReplicationDocs = (rows) => {
       status: doc._replication_state,
       errorMsg: doc._replication_state_reason ? doc._replication_state_reason : '',
       statusTime: new Date(doc._replication_state_time),
-      url: `#/database/_replicator/${app.utils.getSafeIdForDoc(doc._id)}`,
+      startTime: new Date(doc._replication_start_time),
+      url: `#/database/_replicator/${encodeURIComponent(doc._id)}`,
       raw: doc
     };
   });
 };
 
+export const convertState = (state) => {
+  if (state.toLowerCase() === 'error' || state.toLowerCase() === 'crashing') {
+    return 'retrying';
+  }
+
+  return state;
+};
+
+export const combineDocsAndScheduler = (docs, schedulerDocs) => {
+  return docs.map(doc => {
+    const schedule = schedulerDocs.find(s => s.doc_id === doc._id);
+    if (!schedule) {
+      return doc;
+    }
+
+    doc.status = convertState(schedule.state);
+    if (schedule.start_time) {
+      doc.startTime = new Date(schedule.start_time);
+    }
+
+    if (schedule.last_updated) {
+      doc.stateTime = new Date(schedule.last_updated);
+    }
+
+    return doc;
+  });
+};
+
 export const fetchReplicationDocs = () => {
-  return $.ajax({
-    type: 'GET',
-    url: '/_replicator/_all_docs?include_docs=true&limit=100',
-    contentType: 'application/json; charset=utf-8',
-    dataType: 'json',
-  }).then((res) => {
-    return parseReplicationDocs(res.rows.filter(row => row.id.indexOf("_design/_replicator") === -1));
+  return supportNewApi()
+  .then(newApi => {
+    const docsPromise = fetch('/_replicator/_all_docs?include_docs=true&limit=100', {
+      credentials: 'include',
+      headers: {
+        'Accept': 'application/json; charset=utf-8',
+      }
+    })
+    .then(res => res.json())
+    .then((res) => {
+      if (res.error) {
+        return [];
+      }
+
+      return parseReplicationDocs(res.rows.filter(row => row.id.indexOf("_design/_replicator") === -1));
+    });
+
+    if (!newApi) {
+      return docsPromise;
+    }
+    const schedulerPromise = fetchSchedulerDocs();
+    return FauxtonAPI.Promise.join(docsPromise, schedulerPromise, (docs, schedulerDocs) => {
+      return combineDocsAndScheduler(docs, schedulerDocs);
+    })
+    .catch(() => {
+      return [];
+    });
+  });
+};
+
+export const fetchSchedulerDocs = () => {
+  return fetch('/_scheduler/docs?include_docs=true', {
+    credentials: 'include',
+    headers: {
+      'Accept': 'application/json; charset=utf-8',
+    }
+  })
+  .then(res => res.json())
+  .then((res) => {
+    if (res.error) {
+      return [];
+    }
+
+    return res.docs;
   });
 };
 
 export const checkReplicationDocID = (docId) => {
   const promise = FauxtonAPI.Deferred();
-  $.ajax({
-    type: 'GET',
-    url: `/_replicator/${docId}`,
-    contentType: 'application/json; charset=utf-8',
-    dataType: 'json',
-  }).then(() => {
-    promise.resolve(true);
-  }, function (xhr) {
-    if (xhr.statusText === "Object Not Found") {
+  fetch(`/_replicator/${docId}`, {
+    credentials: 'include',
+    headers: {
+      'Accept': 'application/json; charset=utf-8'
+    },
+  }).then(resp => {
+    if (resp.statusText === "Object Not Found") {
       promise.resolve(false);
       return;
     }
@@ -263,3 +354,83 @@ export const checkReplicationDocID = (docId) => {
   });
   return promise;
 };
+
+export const parseReplicateInfo = (resp) => {
+  return resp.jobs.filter(job => job.database === null).map(job => {
+    return {
+      _id: job.id,
+      source: getDocUrl(job.source.slice(0, job.source.length - 1)),
+      target: getDocUrl(job.target.slice(0, job.target.length - 1)),
+      startTime: new Date(job.start_time),
+      statusTime: new Date(job.last_updated),
+      //making an asumption here that the first element is the latest
+      status: convertState(job.history[0].type),
+      errorMsg: '',
+      selected: false,
+      continuous: /continuous/.test(job.id),
+      raw: job
+    };
+  });
+};
+
+export const fetchReplicateInfo = () => {
+  return supportNewApi()
+  .then(newApi => {
+    if (!newApi) {
+      return [];
+    }
+
+    return fetch('/_scheduler/jobs', {
+      credentials: 'include',
+      headers: {
+        'Accept': 'application/json; charset=utf-8'
+      },
+    })
+    .then(resp => resp.json())
+    .then(resp => {
+      return parseReplicateInfo(resp);
+    });
+  });
+};
+
+export const deleteReplicatesApi = (replicates) => {
+  const promises = replicates.map(replicate => {
+    const data = {
+      replication_id: replicate._id,
+      cancel: true
+    };
+
+    return fetch('/_replicate', {
+      method: 'POST',
+      credentials: 'include',
+      headers: {
+        'Accept': 'application/json; charset=utf-8',
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify(data)
+    })
+    .then(resp => resp.json());
+  });
+
+  return FauxtonAPI.Promise.all(promises);
+};
+
+export const createReplicatorDB = () => {
+  return fetch('/_replicator', {
+    method: 'PUT',
+    credentials: 'include',
+    headers: {
+        'Accept': 'application/json; charset=utf-8',
+      }
+    })
+    .then(res => {
+      if (!res.ok) {
+        throw {reason: 'Failed to create the _replicator database.'};
+      }
+
+      return res.json();
+    })
+    .then(() => {
+      return true;
+    });
+};
diff --git a/app/addons/replication/assets/less/replication.less b/app/addons/replication/assets/less/replication.less
index 8d3fe5f..828749e 100644
--- a/app/addons/replication/assets/less/replication.less
+++ b/app/addons/replication/assets/less/replication.less
@@ -44,23 +44,27 @@ div.replication__page {
   width: 540px;
   select {
     font-size: 14px;
-    width: 246px;
+    width: 400px;
     margin-bottom: 10px;
     background-color: white;
     border: 1px solid #cccccc;
   }
   .styled-select {
-    width: 250px;
+    width: 400px;
   }
 }
 
 .replication__input-react-select {
   font-size: 14px;
 
+  .Select .Select-menu-outer {
+    width: 400px;
+  }
+
   .Select div.Select-control {
     padding: 6px;
     border: 1px solid #cccccc;
-    width: 246px;
+    width: 400px;
 
     .Select-value, .Select-placeholder {
       padding: 6px 15px 6px 10px;
@@ -80,7 +84,7 @@ div.replication__page {
 
 .replication__remote-connection-url[type="text"] {
   font-size: 14px;
-  width: 100%;
+  width: 400px;
   color: #333;
 }
 
@@ -91,14 +95,14 @@ div.replication__page {
 }
 
 .replication__new-input[type="text"] {
-  width: 248px;
+  width: 400px;
   font-size: 14px;
   color: #333;
 }
 
 .replication__doc-name {
   position: relative;
-  width: 250px;
+  width: 400px;
 
 }
 
@@ -121,7 +125,7 @@ div.replication__page {
 .replication__doc-name-input[type="text"] {
   padding-right: 32px;
   font-size: 14px;
-  width: 248px;
+  width: 400px;
   color: #333;
 }
 
@@ -270,6 +274,10 @@ input.replication__bulk-select-input[type="checkbox"] {
   padding-left: 8px;
 }
 
+.replication__row-btn--no-left-pad {
+  padding-left: 0px;
+}
+
 .replication__row-btn:visited,
 .replication__row-btn:hover {
   text-decoration: none;
@@ -290,6 +298,10 @@ input.replication__filter-input[type="text"] {
   margin-bottom: 0;
 }
 
+button.replication__error-continue {
+  margin-left: 20px !important; //needed to override bootstrap
+}
+
 .replication__error-cancel,
 .replication__error-continue {
   background-color: #0082BF;
@@ -311,3 +323,13 @@ td.replication__empty-row {
 .replication__remote_icon_help:hover {
   color: #af2d24;
 }
+
+.replication__tooltip {
+  .tooltip-inner {
+    text-align: left
+  }
+}
+
+.replication__activity-caveat {
+  padding-left: 80px;
+}
diff --git a/app/addons/replication/base.js b/app/addons/replication/base.js
index d9ad42a..02efe80 100644
--- a/app/addons/replication/base.js
+++ b/app/addons/replication/base.js
@@ -13,9 +13,18 @@
 import FauxtonAPI from '../../core/api';
 import replication from './route';
 import './assets/less/replication.less';
+import Actions from './actions';
 
 replication.initialize = function () {
   FauxtonAPI.addHeaderLink({ title: 'Replication', href: '#/replication', icon: 'fonticon-replicate' });
+  FauxtonAPI.session.on('authenticated', () => {
+    if (!FauxtonAPI.session.isLoggedIn()) {
+      //don't check until user is logged in
+      return;
+    }
+
+    Actions.checkForNewApi();
+  });
 };
 
 FauxtonAPI.registerUrls('replication', {
diff --git a/app/addons/replication/components/activity.js b/app/addons/replication/components/activity.js
index e50f1b8..fb9c75a 100644
--- a/app/addons/replication/components/activity.js
+++ b/app/addons/replication/components/activity.js
@@ -10,351 +10,9 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 import React from 'react';
-import {Table} from "react-bootstrap";
-import moment from 'moment';
-import {DeleteModal, ErrorModal} from './modals';
-
-const formatUrl = (url) => {
-  const urlObj = new URL(url);
-  const encoded = encodeURIComponent(urlObj.pathname.slice(1));
-
-  if (url.indexOf(window.location.hostname) > -1) {
-    return (
-      <span>
-        {urlObj.origin + '/'}
-        <a href={`#/database/${encoded}/_all_docs`}>{urlObj.pathname.slice(1)}</a>
-      </span>
-    );
-  }
-
-  return `${urlObj.origin}${urlObj.pathname}`;
-};
-
-class RowActions extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = {
-      modalVisible: false,
-    };
-  }
-
-  showModal () {
-    this.setState({modalVisible: true});
-  }
-
-  closeModal () {
-    this.setState({modalVisible: false});
-  }
-
-  getErrorIcon () {
-    if (!this.props.error) {
-      return null;
-    }
-    return (
-      <li className="replication__row-list">
-        <a
-          data-bypass="true"
-          className="replication__row-btn replication__row-btn--warning icon-exclamation-sign"
-          onClick={this.showModal.bind(this)}
-          title="View error message">
-        </a>
-        <ErrorModal
-          onClick={this.closeModal.bind(this)}
-          onClose={this.closeModal.bind(this)}
-          errorMsg={this.props.errorMsg}
-          visible={this.state.modalVisible}
-        />
-      </li>
-    );
-  }
-
-  render () {
-    const {_id, url, deleteDocs} = this.props;
-    const errorIcon = this.getErrorIcon();
-    return (
-      <ul className="replication__row-actions-list">
-        <li className="replication__row-list">
-          <a
-            href={`#replication/id/${encodeURIComponent(_id)}`}
-            className="replication__row-btn icon-wrench"
-            title={`Edit replication`}
-            data-bypass="true"
-            >
-          </a>
-        </li>
-        <li className="replication__row-list">
-        <a
-          className="replication__row-btn fonticon-document"
-          title={`Edit replication document`}
-          href={url}
-          data-bypass="true"
-          >
-        </a>
-        </li>
-        <li className="replication__row-list">
-        <a
-          className="replication__row-btn icon-trash"
-          title={`Delete document ${_id}`}
-          onClick={() => deleteDocs(_id)}>
-        </a>
-        </li>
-        {errorIcon}
-      </ul>
-    );
-
-  }
-};
-
-RowActions.propTypes = {
-  _id: React.PropTypes.string.isRequired,
-  url: React.PropTypes.string.isRequired,
-  error: React.PropTypes.bool.isRequired,
-  errorMsg: React.PropTypes.string.isRequired,
-  deleteDocs: React.PropTypes.func.isRequired
-};
-
-const Row = ({
-  _id,
-  source,
-  target,
-  type,
-  status,
-  statusTime,
-  url,
-  selected,
-  selectDoc,
-  errorMsg,
-  deleteDocs
-}) => {
-  const momentTime = moment(statusTime);
-  const formattedTime = momentTime.isValid() ? momentTime.format("MMM Do, h:mm a") : '';
-
-  return (
-    <tr className="replication__table-row">
-      <td className="replication__table-col"><input checked={selected} type="checkbox" onChange={() => selectDoc(_id)} /> </td>
-      <td className="replication__table-col">{formatUrl(source)}</td>
-      <td className="replication__table-col">{formatUrl(target)}</td>
-      <td className="replication__table-col">{type}</td>
-      <td className={`replication__row-status replication__row-status--${status}`}>{status}</td>
-      <td className="replication__table-col">{formattedTime}</td>
-      <td className="replication__table-col">
-        <RowActions
-          deleteDocs={deleteDocs}
-          _id={_id}
-          url={url}
-          error={status === "error"}
-          errorMsg={errorMsg}
-          />
-      </td>
-    </tr>
-
-  );
-};
-
-Row.propTypes = {
-  _id: React.PropTypes.string.isRequired,
-  source: React.PropTypes.string.isRequired,
-  target: React.PropTypes.string.isRequired,
-  type: React.PropTypes.string.isRequired,
-  status: React.PropTypes.string,
-  url: React.PropTypes.string.isRequired,
-  statusTime: React.PropTypes.object.isRequired,
-  selected: React.PropTypes.bool.isRequired,
-  selectDoc: React.PropTypes.func.isRequired,
-  errorMsg: React.PropTypes.string.isRequired,
-  deleteDocs: React.PropTypes.func.isRequired
-};
-
-const BulkSelectHeader = ({isSelected, deleteDocs, someDocsSelected, onCheck}) => {
-  const trash = someDocsSelected ?
-    <button
-      onClick={() => deleteDocs()}
-      className="replication__bulk-select-trash fonticon fonticon-trash"
-      title="Delete all selected">
-    </button> : null;
-
-  return (
-    <div className="replication__bulk-select-wrapper">
-      <div className="replication__bulk-select-header">
-        <input className="replication__bulk-select-input" checked={isSelected} type="checkbox" onChange={onCheck} />
-      </div>
-    {trash}
-    </div>
-  );
-};
-
-BulkSelectHeader.propTypes = {
-  isSelected: React.PropTypes.bool.isRequired,
-  someDocsSelected: React.PropTypes.bool.isRequired,
-  onCheck: React.PropTypes.func.isRequired,
-  deleteDocs: React.PropTypes.func.isRequired
-};
-
-const EmptyRow = () =>
-  <tr>
-    <td colSpan="7" className="replication__empty-row">
-      There is no replicator-db activity or history to display.
-    </td>
-  </tr>;
-
-class ReplicationTable extends React.Component {
-  constructor (props) {
-    super(props);
-  }
-
-  sort(column, descending, docs) {
-    const sorted = docs.sort((a, b) => {
-      if (a[column] < b[column]) {
-        return -1;
-      }
-
-      if (a[column] > b[column]) {
-        return 1;
-      }
-
-      return 0;
-
-    });
-
-    if (!descending) {
-      sorted.reverse();
-    }
-
-    return sorted;
-  }
-
-  renderRows () {
-    if (this.props.docs.length === 0) {
-      return <EmptyRow />;
-    }
-
-    return this.sort(this.props.column, this.props.descending, this.props.docs).map((doc, i) => {
-      return <Row
-        key={i}
-        _id={doc._id}
-        selected={doc.selected}
-        selectDoc={this.props.selectDoc}
-        source={doc.source}
-        target={doc.target}
-        type={doc.continuous === true ? 'Continuous' : 'One time'}
-        status={doc.status}
-        statusTime={doc.statusTime}
-        url={doc.url}
-        deleteDocs={this.props.deleteDocs}
-        errorMsg={doc.errorMsg}
-        doc={doc}
-      />;
-    });
-  }
-
-  iconDirection (column) {
-    if (column === this.props.column && !this.props.descending) {
-      return 'fonticon-up-dir';
-    }
-
-    return 'fonticon-down-dir';
-  }
-
-  onSort (column) {
-    return () => {
-      this.props.changeSort({
-        descending: column === this.props.column ? !this.props.descending : true,
-        column
-      });
-    };
-  }
-
-  isSelected (header) {
-    if (header === this.props.column) {
-      return 'replication__table--selected';
-    }
-
-    return '';
-  }
-
-  render () {
-    return (
-      <Table striped>
-        <thead>
-          <tr>
-            <th className="replication__table-bulk-select">
-              <BulkSelectHeader
-                isSelected={this.props.allDocsSelected}
-                onCheck={this.props.selectAllDocs}
-                someDocsSelected={this.props.someDocsSelected}
-                deleteDocs={this.props.deleteDocs}
-                />
-            </th>
-            <th className="replication__table-header-source" onClick={this.onSort('source')}>
-              Source
-              <span className={`replication__table-header-icon ${this.iconDirection('source')} ${this.isSelected('source')}`} />
-            </th>
-            <th className="replication__table-header-target" onClick={this.onSort('target')}>
-              Target
-              <span className={`replication__table-header-icon ${this.iconDirection('target')} ${this.isSelected('target')}`} />
-            </th>
-            <th className="replication__table-header-type" onClick={this.onSort('continuous')}>
-              Type
-              <span className={`replication__table-header-icon ${this.iconDirection('continuous')} ${this.isSelected('continuous')}`} />
-            </th>
-            <th className="replication__table-header-status" onClick={this.onSort('status')}>
-              State
-              <span className={`replication__table-header-icon ${this.iconDirection('status')} ${this.isSelected('status')}`} />
-            </th>
-            <th className="replication__table-header-time" onClick={this.onSort('statusTime')}>
-              State Time
-              <span className={`replication__table-header-icon ${this.iconDirection('statusTime')} ${this.isSelected('statusTime')}`} />
-            </th>
-            <th className="replication__table-header-actions">
-              Actions
-            </th>
-          </tr>
-        </thead>
-        <tbody>
-          {this.renderRows()}
-        </tbody>
-      </Table>
-    );
-  }
-}
-
-const ReplicationFilter = ({value, onChange}) => {
-  return (
-    <div className="replication__filter">
-      <i className="replication__filter-icon fonticon-filter" />
-      <input
-        type="text"
-        placeholder="Filter replications"
-        className="replication__filter-input"
-        value={value}
-        onChange={(e) => {onChange(e.target.value);}}
-      />
-    </div>
-  );
-};
-
-ReplicationFilter.propTypes = {
-  value: React.PropTypes.string.isRequired,
-  onChange: React.PropTypes.func.isRequired
-};
-
-const ReplicationHeader = ({filter, onFilterChange}) => {
-  return (
-    <div className="replication__activity_header">
-      <div></div>
-      <ReplicationFilter value={filter} onChange={onFilterChange} />
-      <a href="#/replication/_create" className="btn save replication__activity_header-btn btn-success">
-        <i className="icon fonticon-plus-circled"></i>
-        New Replication
-      </a>
-    </div>
-  );
-};
-
-ReplicationHeader.propTypes = {
-  filter: React.PropTypes.string.isRequired,
-  onFilterChange: React.PropTypes.func.isRequired
-};
+import {DeleteModal} from './modals';
+import {ReplicationTable} from './common-table';
+import {ReplicationHeader} from './common-activity';
 
 export default class Activity extends React.Component {
   constructor (props) {
@@ -412,6 +70,9 @@ export default class Activity extends React.Component {
     const {modalVisible} = this.state;
     return (
       <div className="replication__activity">
+        <p className="replication__activity-caveat">
+          Replications must have a replication document to display in the following table.
+        </p>
         <ReplicationHeader
           filter={filter}
           onFilterChange={onFilterChange}
@@ -428,6 +89,7 @@ export default class Activity extends React.Component {
           changeSort={changeActivitySort}
         />
         <DeleteModal
+          isReplicationDB={true}
           multipleDocs={this.numDocsSelected()}
           visible={modalVisible}
           onClose={this.closeModal.bind(this)}
diff --git a/app/addons/replication/components/common-activity.js b/app/addons/replication/components/common-activity.js
new file mode 100644
index 0000000..5a75766
--- /dev/null
+++ b/app/addons/replication/components/common-activity.js
@@ -0,0 +1,50 @@
+// 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 const ReplicationFilter = ({value, onChange}) => {
+  return (
+    <div className="replication__filter">
+      <i className="replication__filter-icon fonticon-filter" />
+      <input
+        type="text"
+        placeholder="Filter replications"
+        className="replication__filter-input"
+        value={value}
+        onChange={(e) => {onChange(e.target.value);}}
+      />
+    </div>
+  );
+};
+
+ReplicationFilter.propTypes = {
+  value: React.PropTypes.string.isRequired,
+  onChange: React.PropTypes.func.isRequired
+};
+
+export const ReplicationHeader = ({filter, onFilterChange}) => {
+  return (
+    <div className="replication__activity_header">
+      <div></div>
+      <ReplicationFilter value={filter} onChange={onFilterChange} />
+      <a href="#/replication/_create" className="btn save replication__activity_header-btn btn-success">
+        <i className="icon fonticon-plus-circled"></i>
+        New Replication
+      </a>
+    </div>
+  );
+};
+
+ReplicationHeader.propTypes = {
+  filter: React.PropTypes.string.isRequired,
+  onFilterChange: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/replication/components/activity.js b/app/addons/replication/components/common-table.js
similarity index 58%
copy from app/addons/replication/components/activity.js
copy to app/addons/replication/components/common-table.js
index e50f1b8..4f287c5 100644
--- a/app/addons/replication/components/activity.js
+++ b/app/addons/replication/components/common-table.js
@@ -10,9 +10,9 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 import React from 'react';
-import {Table} from "react-bootstrap";
+import {Table, Tooltip, OverlayTrigger} from "react-bootstrap";
 import moment from 'moment';
-import {DeleteModal, ErrorModal} from './modals';
+import {ErrorModal} from './modals';
 
 const formatUrl = (url) => {
   const urlObj = new URL(url);
@@ -30,7 +30,7 @@ const formatUrl = (url) => {
   return `${urlObj.origin}${urlObj.pathname}`;
 };
 
-class RowActions extends React.Component {
+class RowStatus extends React.Component {
   constructor (props) {
     super(props);
     this.state = {
@@ -47,11 +47,13 @@ class RowActions extends React.Component {
   }
 
   getErrorIcon () {
-    if (!this.props.error) {
+    const {status} = this.props;
+    if (status !== 'error' && status !== 'retrying') {
       return null;
     }
+
     return (
-      <li className="replication__row-list">
+      <span>
         <a
           data-bypass="true"
           className="replication__row-btn replication__row-btn--warning icon-exclamation-sign"
@@ -63,51 +65,92 @@ class RowActions extends React.Component {
           onClose={this.closeModal.bind(this)}
           errorMsg={this.props.errorMsg}
           visible={this.state.modalVisible}
+          status={status}
         />
-      </li>
+      </span>
     );
   }
 
   render () {
-    const {_id, url, deleteDocs} = this.props;
-    const errorIcon = this.getErrorIcon();
+    const {statusTime, status} = this.props;
+    let momentTime = moment(statusTime);
+    let statusValue = <span>{status}</span>;
+
+    if (momentTime.isValid()) {
+      const formattedStatusTime = momentTime.format("MMM Do, h:mm a");
+      const stateTimeTooltip = <Tooltip id="">Last updated: {formattedStatusTime}</Tooltip>;
+      statusValue =
+        <OverlayTrigger placement="top" overlay={stateTimeTooltip}>
+          <span>{status}</span>
+        </OverlayTrigger>;
+    }
+
     return (
-      <ul className="replication__row-actions-list">
-        <li className="replication__row-list">
-          <a
-            href={`#replication/id/${encodeURIComponent(_id)}`}
-            className="replication__row-btn icon-wrench"
-            title={`Edit replication`}
-            data-bypass="true"
-            >
-          </a>
-        </li>
-        <li className="replication__row-list">
+      <td className={`replication__row-status replication__row-status--${status}`}>
+        {statusValue}
+        {this.getErrorIcon()}
+      </td>
+    );
+  }
+};
+
+RowStatus.propTypes = {
+  statusTime: React.PropTypes.any,
+  status: React.PropTypes.string,
+  errorMsg: React.PropTypes.string.isRequired,
+};
+
+RowStatus.defaultProps = {
+  status: ''
+};
+
+const RowActions = ({onlyDeleteAction, _id, url, deleteDocs}) => {
+  const actions = [];
+  if (!onlyDeleteAction) {
+    actions.push(
+      <li className="replication__row-list" key={1}>
         <a
-          className="replication__row-btn fonticon-document"
-          title={`Edit replication document`}
-          href={url}
+          href={`#replication/id/${encodeURIComponent(_id)}`}
+          className="replication__row-btn icon-wrench replication__row-btn--no-left-pad"
+          title={'Edit replication'}
           data-bypass="true"
           >
         </a>
-        </li>
-        <li className="replication__row-list">
+      </li>
+    );
+    actions.push(
+      <li className="replication__row-list" key={2}>
         <a
-          className="replication__row-btn icon-trash"
-          title={`Delete document ${_id}`}
-          onClick={() => deleteDocs(_id)}>
+          className="replication__row-btn fonticon-document"
+          title={'Edit replication document'}
+          href={url}
+          data-bypass="true"
+          >
         </a>
-        </li>
-        {errorIcon}
-      </ul>
+      </li>
     );
-
   }
+
+  actions.push(
+    <li className="replication__row-list" key={3}>
+      <a
+        className={`replication__row-btn icon-trash ${onlyDeleteAction ? 'replication__row-btn--no-left-pad' : ''} `}
+        title={`Delete ${onlyDeleteAction ? 'job' : 'document'} ${_id}`}
+        onClick={() => deleteDocs(_id)}>
+      </a>
+    </li>
+  );
+
+  return (
+    <ul className="replication__row-actions-list">
+      {actions}
+    </ul>
+  );
 };
 
 RowActions.propTypes = {
   _id: React.PropTypes.string.isRequired,
-  url: React.PropTypes.string.isRequired,
+  url: React.PropTypes.string,
   error: React.PropTypes.bool.isRequired,
   errorMsg: React.PropTypes.string.isRequired,
   deleteDocs: React.PropTypes.func.isRequired
@@ -118,31 +161,44 @@ const Row = ({
   source,
   target,
   type,
+  startTime,
   status,
   statusTime,
   url,
   selected,
   selectDoc,
   errorMsg,
-  deleteDocs
+  deleteDocs,
+  onlyDeleteAction,
+  showStateRow
 }) => {
-  const momentTime = moment(statusTime);
-  const formattedTime = momentTime.isValid() ? momentTime.format("MMM Do, h:mm a") : '';
+  let momentTime = moment(startTime);
+  const formattedStartTime = momentTime.isValid() ? momentTime.format("MMM Do, h:mm a") : '';
+  let stateRow = null;
+
+  if (showStateRow) {
+    stateRow = <RowStatus
+        statusTime={statusTime}
+        status={status}
+        errorMsg={errorMsg}
+      />;
+  }
 
   return (
     <tr className="replication__table-row">
       <td className="replication__table-col"><input checked={selected} type="checkbox" onChange={() => selectDoc(_id)} /> </td>
       <td className="replication__table-col">{formatUrl(source)}</td>
       <td className="replication__table-col">{formatUrl(target)}</td>
+      <td className="replication__table-col">{formattedStartTime}</td>
       <td className="replication__table-col">{type}</td>
-      <td className={`replication__row-status replication__row-status--${status}`}>{status}</td>
-      <td className="replication__table-col">{formattedTime}</td>
+      {stateRow}
       <td className="replication__table-col">
         <RowActions
+          onlyDeleteAction={onlyDeleteAction}
           deleteDocs={deleteDocs}
           _id={_id}
           url={url}
-          error={status === "error"}
+          error={status === "error" || status === 'retrying'}
           errorMsg={errorMsg}
           />
       </td>
@@ -157,12 +213,15 @@ Row.propTypes = {
   target: React.PropTypes.string.isRequired,
   type: React.PropTypes.string.isRequired,
   status: React.PropTypes.string,
-  url: React.PropTypes.string.isRequired,
+  url: React.PropTypes.string,
   statusTime: React.PropTypes.object.isRequired,
+  startTime: React.PropTypes.object,
   selected: React.PropTypes.bool.isRequired,
   selectDoc: React.PropTypes.func.isRequired,
   errorMsg: React.PropTypes.string.isRequired,
-  deleteDocs: React.PropTypes.func.isRequired
+  deleteDocs: React.PropTypes.func.isRequired,
+  onlyDeleteAction: React.PropTypes.bool.isRequired,
+  showStateRow: React.PropTypes.bool.isRequired
 };
 
 const BulkSelectHeader = ({isSelected, deleteDocs, someDocsSelected, onCheck}) => {
@@ -190,14 +249,22 @@ BulkSelectHeader.propTypes = {
   deleteDocs: React.PropTypes.func.isRequired
 };
 
-const EmptyRow = () =>
-  <tr>
-    <td colSpan="7" className="replication__empty-row">
-      There is no replicator-db activity or history to display.
-    </td>
-  </tr>;
+const EmptyRow = ({msg}) => {
+  return (
+    <tr>
+      <td colSpan="7" className="replication__empty-row">
+        {msg}
+      </td>
+    </tr>
+  );
+};
+
+EmptyRow.defaultProps = {
+  msg: "There is no replicator-db activity or history to display."
+};
+
 
-class ReplicationTable extends React.Component {
+export class ReplicationTable extends React.Component {
   constructor (props) {
     super(props);
   }
@@ -239,10 +306,13 @@ class ReplicationTable extends React.Component {
         type={doc.continuous === true ? 'Continuous' : 'One time'}
         status={doc.status}
         statusTime={doc.statusTime}
+        startTime={doc.startTime}
         url={doc.url}
         deleteDocs={this.props.deleteDocs}
         errorMsg={doc.errorMsg}
         doc={doc}
+        onlyDeleteAction={this.props.onlyDeleteAction}
+        showStateRow={this.props.showStateRow}
       />;
     });
   }
@@ -272,7 +342,21 @@ class ReplicationTable extends React.Component {
     return '';
   }
 
+  stateCol () {
+    if (this.props.showStateRow) {
+      return (
+        <th className="replication__table-header-status" onClick={this.onSort('status')}>
+          State
+          <span className={`replication__table-header-icon ${this.iconDirection('status')} ${this.isSelected('status')}`} />
+        </th>
+      );
+    }
+
+    return null;
+  }
+
   render () {
+
     return (
       <Table striped>
         <thead>
@@ -293,18 +377,15 @@ class ReplicationTable extends React.Component {
               Target
               <span className={`replication__table-header-icon ${this.iconDirection('target')} ${this.isSelected('target')}`} />
             </th>
+            <th className="replication__table-header-time" onClick={this.onSort('statusTime')}>
+              Start Time
+              <span className={`replication__table-header-icon ${this.iconDirection('statusTime')} ${this.isSelected('statusTime')}`} />
+            </th>
             <th className="replication__table-header-type" onClick={this.onSort('continuous')}>
               Type
               <span className={`replication__table-header-icon ${this.iconDirection('continuous')} ${this.isSelected('continuous')}`} />
             </th>
-            <th className="replication__table-header-status" onClick={this.onSort('status')}>
-              State
-              <span className={`replication__table-header-icon ${this.iconDirection('status')} ${this.isSelected('status')}`} />
-            </th>
-            <th className="replication__table-header-time" onClick={this.onSort('statusTime')}>
-              State Time
-              <span className={`replication__table-header-icon ${this.iconDirection('statusTime')} ${this.isSelected('statusTime')}`} />
-            </th>
+            {this.stateCol()}
             <th className="replication__table-header-actions">
               Actions
             </th>
@@ -318,135 +399,7 @@ class ReplicationTable extends React.Component {
   }
 }
 
-const ReplicationFilter = ({value, onChange}) => {
-  return (
-    <div className="replication__filter">
-      <i className="replication__filter-icon fonticon-filter" />
-      <input
-        type="text"
-        placeholder="Filter replications"
-        className="replication__filter-input"
-        value={value}
-        onChange={(e) => {onChange(e.target.value);}}
-      />
-    </div>
-  );
-};
-
-ReplicationFilter.propTypes = {
-  value: React.PropTypes.string.isRequired,
-  onChange: React.PropTypes.func.isRequired
-};
-
-const ReplicationHeader = ({filter, onFilterChange}) => {
-  return (
-    <div className="replication__activity_header">
-      <div></div>
-      <ReplicationFilter value={filter} onChange={onFilterChange} />
-      <a href="#/replication/_create" className="btn save replication__activity_header-btn btn-success">
-        <i className="icon fonticon-plus-circled"></i>
-        New Replication
-      </a>
-    </div>
-  );
-};
-
-ReplicationHeader.propTypes = {
-  filter: React.PropTypes.string.isRequired,
-  onFilterChange: React.PropTypes.func.isRequired
-};
-
-export default class Activity extends React.Component {
-  constructor (props) {
-    super(props);
-    this.state = {
-      modalVisible: false,
-      unconfirmedDeleteDocId: null
-    };
-  }
-
-  closeModal () {
-    this.setState({
-      modalVisible: false,
-      unconfirmedDeleteDocId: null
-    });
-  }
-
-  showModal (doc) {
-    this.setState({
-      modalVisible: true,
-      unconfirmedDeleteDocId: doc
-    });
-  }
-
-  confirmDeleteDocs () {
-    let docs = [];
-    if (this.state.unconfirmedDeleteDocId) {
-      const doc = this.props.docs.find(doc => doc._id === this.state.unconfirmedDeleteDocId);
-      docs.push(doc);
-    } else {
-      docs = this.props.docs.filter(doc => doc.selected);
-    }
-
-    this.props.deleteDocs(docs);
-    this.closeModal();
-  }
-
-  numDocsSelected () {
-    return this.props.docs.filter(doc => doc.selected).length;
-  }
-
-  render () {
-    const {
-      onFilterChange,
-      activitySort,
-      changeActivitySort,
-      docs,
-      filter,
-      selectAllDocs,
-      someDocsSelected,
-      allDocsSelected,
-      selectDoc
-    } = this.props;
-
-    const {modalVisible} = this.state;
-    return (
-      <div className="replication__activity">
-        <ReplicationHeader
-          filter={filter}
-          onFilterChange={onFilterChange}
-        />
-        <ReplicationTable
-          someDocsSelected={someDocsSelected}
-          allDocsSelected={allDocsSelected}
-          selectAllDocs={selectAllDocs}
-          docs={docs}
-          selectDoc={selectDoc}
-          deleteDocs={this.showModal.bind(this)}
-          descending={activitySort.descending}
-          column={activitySort.column}
-          changeSort={changeActivitySort}
-        />
-        <DeleteModal
-          multipleDocs={this.numDocsSelected()}
-          visible={modalVisible}
-          onClose={this.closeModal.bind(this)}
-          onClick={this.confirmDeleteDocs.bind(this)}
-          />
-      </div>
-    );
-  }
-}
-
-Activity.propTypes = {
-  docs: React.PropTypes.array.isRequired,
-  filter: React.PropTypes.string.isRequired,
-  selectAllDocs: React.PropTypes.func.isRequired,
-  allDocsSelected: React.PropTypes.bool.isRequired,
-  someDocsSelected: React.PropTypes.bool.isRequired,
-  selectDoc: React.PropTypes.func.isRequired,
-  onFilterChange: React.PropTypes.func.isRequired,
-  deleteDocs: React.PropTypes.func.isRequired,
-  activitySort: React.PropTypes.object.isRequired,
-  changeActivitySort: React.PropTypes.func.isRequired
+ReplicationTable.defaultProps = {
+  onlyDeleteAction: false,
+  showStateRow: true
 };
diff --git a/app/addons/replication/components/modals.js b/app/addons/replication/components/modals.js
index b51616e..9ef7c96 100644
--- a/app/addons/replication/components/modals.js
+++ b/app/addons/replication/components/modals.js
@@ -20,17 +20,22 @@ export const DeleteModal = ({
   visible,
   onClose,
   onClick,
-  multipleDocs
+  multipleDocs,
+  isReplicationDB
 }) => {
 
   if (!visible) {
     return null;
   }
 
-  let header = "You are deleting a replication document.";
+  let header = "";
+  let btnText = `Delete ${isReplicationDB ? 'Document' : 'Replication Job'}`;
+  let infoSection = `Deleting a replication ${isReplicationDB ? 'document' : 'job'} stops continuous replication 
+          and incomplete one-time replication, but does not affect replicated documents.`;
 
   if (multipleDocs > 1) {
-    header = `You are deleting ${multipleDocs} replication documents.`;
+    header = `You are deleting <strong>${multipleDocs}</strong> replication ${isReplicationDB ? 'documents' : 'jobs'}.`;
+    btnText = `Delete ${isReplicationDB ? 'Documents' : 'Replication Jobs'}`;
   }
 
   return (
@@ -39,19 +44,14 @@ export const DeleteModal = ({
         <Modal.Title>Verify Deletion</Modal.Title>
       </Modal.Header>
       <Modal.Body>
-        <p>{header}</p>
-        <p>
-          Deleting a replication document stops continuous replication
-          and incomplete one-time replication, but does not affect replicated documents.
-        </p>
-        <p>
-          Replication jobs that do not have replication documents do not appear in Replicator DB Activity.
-        </p>
+        <p dangerouslySetInnerHTML={{__html: header}}></p>
+        <p>{infoSection}</p>
       </Modal.Body>
       <Modal.Footer>
         <a className="cancel-link" onClick={onClose}>Cancel</a>
         <ConfirmButton
-          text={"Delete Document"}
+          customIcon={"icon-trash"}
+          text={btnText}
           onClick={onClick}
         />
       </Modal.Footer>
@@ -61,26 +61,40 @@ export const DeleteModal = ({
 
 DeleteModal.propTypes = {
   visible: React.PropTypes.bool.isRequired,
+  isReplicationDB: React.PropTypes.bool.isRequired,
   onClick: React.PropTypes.func.isRequired,
   onClose: React.PropTypes.func.isRequired,
   multipleDocs: React.PropTypes.number.isRequired
 };
 
-export const ErrorModal = ({visible, onClose, errorMsg}) => {
+DeleteModal.defaultProps = {
+  isReplicationDB: true
+};
+
+export const ErrorModal = ({visible, onClose, errorMsg, status}) => {
 
   if (!visible) {
     return null;
   }
 
+  let title = "Replication Error";
+  let warning = <p>The replication job will be tried at increasing intervals</p>;
+
+  if (status.toLowerCase() === 'failed') {
+    title = "Replication Error - Failed";
+    warning = null;
+  }
+
   return (
     <Modal dialogClassName="replication__error-doc-modal" show={visible} onHide={() => onClose()}>
       <Modal.Header closeButton={true}>
-        <Modal.Title>Replication Error</Modal.Title>
+        <Modal.Title>{title}</Modal.Title>
       </Modal.Header>
       <Modal.Body>
         <p>
           {errorMsg}
         </p>
+        {warning}
       </Modal.Body>
       <Modal.Footer>
       </Modal.Footer>
@@ -104,7 +118,7 @@ export const ConflictModal = ({visible, docId, onClose, onClick}) => {
   return (
     <Modal dialogClassName="replication__error-doc-modal" show={visible} onHide={() => onClose()}>
       <Modal.Header closeButton={true}>
-        <Modal.Title>Fix Document Conflict</Modal.Title>
+        <Modal.Title>Custom ID Conflict</Modal.Title>
       </Modal.Header>
       <Modal.Body>
         <p>
@@ -123,7 +137,6 @@ export const ConflictModal = ({visible, docId, onClose, onClick}) => {
           Change Document ID
         </button>
         <button onClick={onClick} className="btn replication__error-continue">
-          <i className="icon icon-eraser"></i>
           Overwrite Existing Document
         </button>
       </Modal.Footer>
diff --git a/app/addons/replication/components/remoteexample.js b/app/addons/replication/components/remoteexample.js
index 1992b32..ddde70a 100644
--- a/app/addons/replication/components/remoteexample.js
+++ b/app/addons/replication/components/remoteexample.js
@@ -13,7 +13,7 @@ import React from 'react';
 import {OverlayTrigger, Tooltip} from 'react-bootstrap';
 
 const tooltipExisting = (
-  <Tooltip id="tooltip">
+  <Tooltip id="tooltip" className="replication__tooltip">
     <p>
       If you know the credentials for the remote account, you can use that remote username and password.
     </p>
@@ -21,13 +21,13 @@ const tooltipExisting = (
       If a remote database granted permissions to your local account, you can use the local-account username and password.
     </p>
     <p>
-      If the remote database granted permissions to "everybody," you do not need to enter a username and password.
+      If the remote database granted permissions to unauthenticated connections, you do not need to enter a username or password.
     </p>
   </Tooltip>
 );
 
 const tooltipNew = (
-  <Tooltip id="tooltip">
+  <Tooltip id="tooltip" className="replication__tooltip">
     Enter the username and password of the remote account.
   </Tooltip>
 );
diff --git a/app/addons/replication/components/replicate-activity.js b/app/addons/replication/components/replicate-activity.js
new file mode 100644
index 0000000..1196793
--- /dev/null
+++ b/app/addons/replication/components/replicate-activity.js
@@ -0,0 +1,116 @@
+// 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 {DeleteModal} from './modals';
+import {ReplicationTable} from './common-table';
+import {ReplicationHeader} from './common-activity';
+
+export default class Activity extends React.Component {
+  constructor (props) {
+    super(props);
+    this.state = {
+      modalVisible: false,
+      unconfirmedDeleteDocId: null
+    };
+  }
+
+  closeModal () {
+    this.setState({
+      modalVisible: false,
+      unconfirmedDeleteDocId: null
+    });
+  }
+
+  showModal (docId) {
+    this.setState({
+      modalVisible: true,
+      unconfirmedDeleteDocId: docId
+    });
+  }
+
+  confirmDeleteDocs () {
+    let docs = [];
+    if (this.state.unconfirmedDeleteDocId) {
+      const doc = this.props.docs.find(doc => doc._id === this.state.unconfirmedDeleteDocId);
+      docs.push(doc);
+    } else {
+      docs = this.props.docs.filter(doc => doc.selected);
+    }
+
+    this.props.deleteDocs(docs);
+    this.closeModal();
+  }
+
+  numDocsSelected () {
+    return this.props.docs.filter(doc => doc.selected).length;
+  }
+
+  render () {
+    const {
+      onFilterChange,
+      activitySort,
+      changeActivitySort,
+      docs,
+      filter,
+      selectAllDocs,
+      someDocsSelected,
+      allDocsSelected,
+      selectDoc
+    } = this.props;
+
+    const {modalVisible} = this.state;
+    return (
+      <div className="replication__activity">
+        <p className="replication__activity-caveat">
+          Active _replicate jobs are displayed. Completed and failed jobs are not.
+        </p>
+        <ReplicationHeader
+          filter={filter}
+          onFilterChange={onFilterChange}
+        />
+        <ReplicationTable
+          onlyDeleteAction={true}
+          showStateRow={false}
+          someDocsSelected={someDocsSelected}
+          allDocsSelected={allDocsSelected}
+          selectAllDocs={selectAllDocs}
+          docs={docs}
+          selectDoc={selectDoc}
+          deleteDocs={this.showModal.bind(this)}
+          descending={activitySort.descending}
+          column={activitySort.column}
+          changeSort={changeActivitySort}
+        />
+        <DeleteModal
+          isReplicationDB={false}
+          multipleDocs={this.numDocsSelected()}
+          visible={modalVisible}
+          onClose={this.closeModal.bind(this)}
+          onClick={this.confirmDeleteDocs.bind(this)}
+          />
+      </div>
+    );
+  }
+}
+
+Activity.propTypes = {
+  docs: React.PropTypes.array.isRequired,
+  filter: React.PropTypes.string.isRequired,
+  selectAllDocs: React.PropTypes.func.isRequired,
+  allDocsSelected: React.PropTypes.bool.isRequired,
+  someDocsSelected: React.PropTypes.bool.isRequired,
+  selectDoc: React.PropTypes.func.isRequired,
+  onFilterChange: React.PropTypes.func.isRequired,
+  deleteDocs: React.PropTypes.func.isRequired,
+  activitySort: React.PropTypes.object.isRequired,
+  changeActivitySort: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/replication/components/submit.js b/app/addons/replication/components/submit.js
index cc921b3..f82fd92 100644
--- a/app/addons/replication/components/submit.js
+++ b/app/addons/replication/components/submit.js
@@ -17,6 +17,7 @@ const {ConfirmButton} = Components;
 export const ReplicationSubmit = ({onClear, disabled, onClick}) =>
 <div className="replication__button-row">
   <ConfirmButton
+    customIcon="fonticon-replicate"
     id="replicate"
     text="Start Replication"
     onClick={onClick}
diff --git a/app/addons/replication/constants.js b/app/addons/replication/constants.js
index 7538f20..b9e699f 100644
--- a/app/addons/replication/constants.js
+++ b/app/addons/replication/constants.js
@@ -10,7 +10,6 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-
 export default {
   REPLICATION_SOURCE: {
     LOCAL: 'REPLICATION_SOURCE_LOCAL',
diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js
index 1ac4fb2..22c0b35 100644
--- a/app/addons/replication/controller.js
+++ b/app/addons/replication/controller.js
@@ -18,6 +18,8 @@ import NewReplication from './components/newreplication';
 import Activity from './components/activity';
 import {checkReplicationDocID} from './api';
 import {OnePane, OnePaneHeader, OnePaneContent} from '../components/layouts';
+import {TabElementWrapper, TabElement} from '../components/components/tabelement';
+import ReplicateActivity from './components/replicate-activity';
 
 const {LoadLines, Polling, RefreshBtn} = Components;
 
@@ -57,23 +59,36 @@ export default class ReplicationController extends React.Component {
       submittedNoChange: store.getSubmittedNoChange(),
       statusDocs: store.getFilteredReplicationStatus(),
       statusFilter: store.getStatusFilter(),
+      replicateFilter: store.getReplicateFilter(),
       allDocsSelected: store.getAllDocsSelected(),
       someDocsSelected:  store.someDocsSelected(),
       username: store.getUsername(),
       password: store.getPassword(),
-      activitySort: store.getActivitySort()
+      activitySort: store.getActivitySort(),
+      tabSection: store.getTabSection(),
+      checkingApi: store.checkingAPI(),
+      supportNewApi: store.supportNewApi(),
+      replicateLoading: store.isReplicateInfoLoading(),
+      replicateInfo: store.getReplicateInfo(),
+      allReplicateSelected: store.getAllReplicateSelected(),
+      someReplicateSelected: store.someReplicateSelected()
     };
   }
 
   loadReplicationInfo (props, oldProps) {
     Actions.initReplicator(props.localSource);
-    Actions.getReplicationActivity();
+    this.getAllActivity();
     if (props.replicationId && props.replicationId !== oldProps.replicationId) {
       Actions.clearReplicationForm();
       Actions.getReplicationStateFrom(props.replicationId);
     }
   }
 
+  getAllActivity () {
+    Actions.getReplicationActivity();
+    Actions.getReplicateActivity();
+  }
+
   componentDidMount () {
     store.on('change', this.onChange, this);
     this.loadReplicationInfo(this.props, {});
@@ -98,10 +113,11 @@ export default class ReplicationController extends React.Component {
       passwordModalVisible, databases, localSource, remoteSource, remoteTarget,
       localTarget, statusDocs, statusFilter, loading, allDocsSelected,
       someDocsSelected, showConflictModal, localSourceKnown, localTargetKnown,
-      username, password, authenticated, activityLoading, submittedNoChange, activitySort
+      username, password, authenticated, activityLoading, submittedNoChange, activitySort, tabSection,
+      replicateInfo, replicateLoading, replicateFilter, allReplicateSelected, someReplicateSelected
     } = this.state;
 
-    if (this.props.section === 'new replication') {
+    if (tabSection === 'new replication') {
       if (loading) {
         return <LoadLines/>;
       }
@@ -141,11 +157,30 @@ export default class ReplicationController extends React.Component {
       />;
     }
 
+    if (tabSection === '_replicate') {
+      if (replicateLoading) {
+        return <LoadLines />;
+      }
+
+      return <ReplicateActivity
+        docs={replicateInfo}
+        filter={replicateFilter}
+        onFilterChange={Actions.filterReplicate}
+        selectDoc={Actions.selectReplicate}
+        selectAllDocs={Actions.selectAllReplicates}
+        allDocsSelected={allReplicateSelected}
+        someDocsSelected={someReplicateSelected}
+        deleteDocs={Actions.deleteDocs}
+        activitySort={activitySort}
+        changeActivitySort={Actions.changeActivitySort}
+        deleteDocs={Actions.deleteReplicates}
+        />;
+    }
+
     if (activityLoading) {
       return <LoadLines/>;
     }
 
-
     return <Activity
       docs={statusDocs}
       filter={statusFilter}
@@ -161,21 +196,8 @@ export default class ReplicationController extends React.Component {
            />;
   }
 
-  getCrumbs () {
-    if (this.props.section === 'new replication') {
-      return [
-        {name: 'Replication', link: 'replication'},
-        {name: 'New Replication'}
-      ];
-    }
-
-    return [
-      {name: 'Replication'}
-    ];
-  }
-
   getHeaderComponents () {
-    if (this.props.section === 'new replication') {
+    if (this.state.tabSection === 'new replication') {
       return null;
     }
 
@@ -186,25 +208,79 @@ export default class ReplicationController extends React.Component {
           max={600}
           startValue={300}
           stepSize={60}
-          onPoll={Actions.getReplicationActivity}
+          onPoll={this.getAllActivity.bind(this)}
           />
         <RefreshBtn
-          refresh={Actions.getReplicationActivity}
+          refresh={this.getAllActivity.bind(this)}
           />
       </div>
     );
   }
 
+  getTabElements () {
+    const {tabSection} = this.state;
+    const elements = [
+      <TabElement
+        key={1}
+        selected={tabSection === 'activity'}
+        text={"Replicator DB Activity"}
+        onChange={this.onTabChange.bind(this, 'activity', '#/replication')}
+      />
+    ];
+
+    if (this.state.supportNewApi) {
+      elements.push(
+        <TabElement
+          key={2}
+          selected={tabSection === '_replicate'}
+          text={"_replicate Activity"}
+          onChange={this.onTabChange.bind(this, '_replicate', '#/replication/_replicate')}
+        />
+      );
+    }
+
+    return elements;
+  }
+
+  onTabChange (section, url) {
+    Actions.changeTabSection(section, url);
+  }
+
+  getCrumbs () {
+    if (this.state.tabSection === 'new replication') {
+      return [{'name': 'Job Configuration'}];
+    }
+
+    return [];
+  }
+
+  getTabs () {
+    if (this.state.tabSection === 'new replication') {
+      return null;
+    }
+
+    return (
+      <TabElementWrapper>
+        {this.getTabElements()}
+      </TabElementWrapper>
+    );
+  }
+
   render () {
+    const { checkingAPI } = this.state;
+
+    if (checkingAPI) {
+      return <LoadLines />;
+    }
+
     return (
       <OnePane>
-        <OnePaneHeader
-          crumbs={this.getCrumbs()}
-        >
+        <OnePaneHeader crumbs={this.getCrumbs()}>
         {this.getHeaderComponents()}
         </OnePaneHeader>
         <OnePaneContent>
           <div className="template-content flex-body flex-layout flex-col">
+            {this.getTabs()}
             <div className="replication__page flex-layout flex-col">
               {this.showSection()}
             </div>
diff --git a/app/addons/replication/route.js b/app/addons/replication/route.js
index 70b66a4..95719b4 100644
--- a/app/addons/replication/route.js
+++ b/app/addons/replication/route.js
@@ -13,13 +13,15 @@
 import React from 'react';
 import FauxtonAPI from '../../core/api';
 import ReplicationController from './controller';
+import Actions from './actions';
 
 const ReplicationRouteObject = FauxtonAPI.RouteObject.extend({
   routes: {
     'replication/_create': 'defaultView',
-    'replication/:dbname': 'defaultView',
+    'replication/_create/:dbname': 'defaultView',
     'replication/id/:id': 'fromId',
-    'replication': 'activityView'
+    'replication': 'activityView',
+    'replication/_replicate': 'replicateView'
   },
   selectedHeader: 'Replication',
 
@@ -40,24 +42,30 @@ const ReplicationRouteObject = FauxtonAPI.RouteObject.extend({
 
   defaultView: function (databaseName) {
     const localSource = databaseName || '';
+    Actions.changeTabSection('new replication');
+    Actions.clearReplicationForm();
 
     return <ReplicationController
       localSource={localSource}
-      section={'new replication'}
       />;
   },
 
   fromId: function (replicationId) {
+    Actions.clearReplicationForm();
+    Actions.changeTabSection('new replication');
     return <ReplicationController
       replicationId={replicationId}
-      section={'new replication'}
     />;
   },
 
   activityView: function () {
-    return <ReplicationController
-      section={'activity'}
-    />;
+    Actions.changeTabSection('activity');
+    return <ReplicationController/>;
+  },
+
+  replicateView: function () {
+    Actions.changeTabSection('_replicate');
+    return <ReplicationController/>;
   }
 });
 
diff --git a/app/addons/replication/stores.js b/app/addons/replication/stores.js
index 715ea23..19badc7 100644
--- a/app/addons/replication/stores.js
+++ b/app/addons/replication/stores.js
@@ -16,6 +16,20 @@ import Constants from './constants';
 import AccountActionTypes from '../auth/actiontypes';
 import _ from 'lodash';
 
+// I know this could be done by just adding the _ prefix to the passed field name, I just don't much like relying
+// on the var names like that...
+const validFieldMap = {
+  remoteSource: '_remoteSource',
+  remoteTarget: '_remoteTarget',
+  localTarget: '_localTarget',
+  replicationType: '_replicationType',
+  replicationDocName: '_replicationDocName',
+  replicationSource: '_replicationSource',
+  replicationTarget: '_replicationTarget',
+  localSource: '_localSource',
+  replicationDocName: '_replicationDocName'
+};
+
 const ReplicationStore = FauxtonAPI.Store.extend({
   initialize () {
     this.reset();
@@ -46,12 +60,30 @@ const ReplicationStore = FauxtonAPI.Store.extend({
     this._statusDocs = [];
     this._statusFilteredStatusDocs = [];
     this._statusFilter = '';
+    this._replicateFilter = '';
     this._allDocsSelected = false;
+    this._allReplicateSelected = false;
     this._username = '';
     this._password = '';
     this._activityLoading = false;
+    this._tabSection = 'new replication';
+    this._supportNewApi = true;
 
     this.loadActivitySort();
+
+    this._fetchingReplicateInfo = false;
+    this._replicateInfo = [];
+
+    this._checkingAPI = true;
+    this._supportNewApi = false;
+  },
+
+  supportNewApi () {
+    return this._supportNewApi;
+  },
+
+  checkingAPI () {
+    return this._checkingAPI;
   },
 
   getActivitySort () {
@@ -59,13 +91,14 @@ const ReplicationStore = FauxtonAPI.Store.extend({
   },
 
   loadActivitySort () {
-    let sort = app.utils.localStorageGet('replication-activity-sort');
-    if (!sort) {
-      sort = {
+    const defaultSort = {
         descending: false,
         column: 'statusTime'
       };
+    let sort = app.utils.localStorageGet('replication-activity-sort');
 
+    if (!sort) {
+      sort = defaultSort;
       this.setActivitySort(sort);
     }
 
@@ -77,6 +110,23 @@ const ReplicationStore = FauxtonAPI.Store.extend({
     this._activitySort = sort;
   },
 
+  isReplicateInfoLoading () {
+    return this._fetchingReplicateInfo;
+  },
+
+  getReplicateInfo () {
+    return this._replicateInfo.filter(doc => {
+      return _.values(doc).filter(item => {
+        if (!item) {return false;}
+        return item.toString().toLowerCase().match(this._replicateFilter);
+      }).length > 0;
+    });
+  },
+
+  setReplicateInfo (info) {
+    this._replicateInfo = info;
+  },
+
   setCredentials (username, password) {
     this._username = username;
     this._password = password;
@@ -177,6 +227,29 @@ const ReplicationStore = FauxtonAPI.Store.extend({
     this._allDocsSelected = false;
   },
 
+  selectReplicate (id) {
+    const doc = this._replicateInfo.find(doc => doc._id === id);
+    if (!doc) {
+      return;
+    }
+
+    doc.selected = !doc.selected;
+    this._allReplicateSelected = false;
+  },
+
+  selectAllReplicate () {
+    this._allReplicateSelected = !this._allReplicateSelected;
+    this.getReplicateInfo().forEach(doc => doc.selected = this._allReplicateSelected);
+  },
+
+  someReplicateSelected () {
+    return this.getReplicateInfo().some(doc => doc.selected);
+  },
+
+  getAllReplicateSelected () {
+    return this._allReplicateSelected;
+  },
+
   selectAllDocs () {
     this._allDocsSelected = !this._allDocsSelected;
     this.getFilteredReplicationStatus().forEach(doc => doc.selected = this._allDocsSelected);
@@ -197,25 +270,23 @@ const ReplicationStore = FauxtonAPI.Store.extend({
   getStatusFilter () {
     return this._statusFilter;
   },
-  // to cut down on boilerplate
-  updateFormField (fieldName, value) {
 
-    // I know this could be done by just adding the _ prefix to the passed field name, I just don't much like relying
-    // on the var names like that...
-    var validFieldMap = {
-      remoteSource: '_remoteSource',
-      remoteTarget: '_remoteTarget',
-      localTarget: '_localTarget',
-      replicationType: '_replicationType',
-      replicationDocName: '_replicationDocName',
-      replicationSource: '_replicationSource',
-      replicationTarget: '_replicationTarget',
-      localSource: '_localSource'
-    };
+  setReplicateFilter (filter) {
+    this._replicateFilter = filter;
+  },
 
+  getReplicateFilter () {
+    return this._replicateFilter;
+  },
+  // to cut down on boilerplate
+  updateFormField (fieldName, value) {
     this[validFieldMap[fieldName]] = value;
   },
 
+  clearReplicationForm () {
+    _.values(validFieldMap).forEach(fieldName => this[fieldName] = '');
+  },
+
   getRemoteSource () {
     return this._remoteSource;
   },
@@ -242,6 +313,10 @@ const ReplicationStore = FauxtonAPI.Store.extend({
     });
   },
 
+  getTabSection () {
+    return this._tabSection;
+  },
+
   dispatch ({type, options}) {
     switch (type) {
 
@@ -263,13 +338,17 @@ const ReplicationStore = FauxtonAPI.Store.extend({
         this._loading = false;
       break;
 
+      case ActionTypes.REPLICATION_FETCHING_FORM_STATE:
+        this._loading = true;
+      break;
+
       case ActionTypes.REPLICATION_UPDATE_FORM_FIELD:
         this.changeAfterSubmit();
         this.updateFormField(options.fieldName, options.value);
       break;
 
       case ActionTypes.REPLICATION_CLEAR_FORM:
-        this.reset();
+        this.clearReplicationForm();
       break;
 
       case ActionTypes.REPLICATION_STARTING:
@@ -289,6 +368,10 @@ const ReplicationStore = FauxtonAPI.Store.extend({
         this.setStatusFilter(options);
       break;
 
+      case ActionTypes.REPLICATION_FILTER_REPLICATE:
+        this.setReplicateFilter(options);
+      break;
+
       case ActionTypes.REPLICATION_TOGGLE_DOC:
         this.selectDoc(options);
       break;
@@ -297,7 +380,16 @@ const ReplicationStore = FauxtonAPI.Store.extend({
         this.selectAllDocs();
       break;
 
+      case ActionTypes.REPLICATION_TOGGLE_REPLICATE:
+        this.selectReplicate(options);
+      break;
+
+      case ActionTypes.REPLICATION_TOGGLE_ALL_REPLICATE:
+        this.selectAllReplicate();
+      break;
+
       case ActionTypes.REPLICATION_SET_STATE_FROM_DOC:
+        this._loading = false;
         this.setStateFromDoc(options);
       break;
 
@@ -317,6 +409,32 @@ const ReplicationStore = FauxtonAPI.Store.extend({
         this._allDocsSelected = false;
       break;
 
+      case ActionTypes.REPLICATION_CHANGE_TAB_SECTION:
+        this._tabSection = options;
+      break;
+
+      case ActionTypes.REPLICATION_CLEAR_SELECTED_DOCS:
+        this._allDocsSelected = false;
+      break;
+
+      case ActionTypes.REPLICATION_SUPPORT_NEW_API:
+        this._checkingAPI = false;
+        this._supportNewApi = options;
+      break;
+
+      case ActionTypes.REPLICATION_FETCHING_REPLICATE_STATUS:
+        this._fetchingReplicateInfo = true;
+      break;
+
+      case ActionTypes.REPLICATION_REPLICATE_STATUS:
+        this._fetchingReplicateInfo = false;
+        this.setReplicateInfo(options);
+      break;
+
+      case ActionTypes.REPLICATION_CLEAR_SELECTED_REPLICATES:
+        this._allReplicateSelected = false;
+      break;
+
       case AccountActionTypes.AUTH_SHOW_PASSWORD_MODAL:
         this._isPasswordModalVisible = true;
       break;
diff --git a/app/addons/replication/tests/nightwatch/replication.js b/app/addons/replication/tests/nightwatch/replication.js
index ba852d5..c523c8f 100644
--- a/app/addons/replication/tests/nightwatch/replication.js
+++ b/app/addons/replication/tests/nightwatch/replication.js
@@ -46,13 +46,13 @@ module.exports = {
       .checkForDatabaseCreated(newDatabaseName1, waitTime)
       .createDocument(docName1, newDatabaseName1)
       .loginToGUI()
-      .url(baseUrl + '/#replication/_create')
+      .url(baseUrl + '/#/replication/_create')
       .waitForElementVisible('button#replicate', waitTime, true)
       .waitForElementVisible('#replication-source', waitTime, true)
 
       // select LOCAL as the source
       .clickWhenVisible('#replication-source')
-      .keys(['\uE006'])
+      .keys(['\uE015', '\uE006'])
       .waitForElementVisible('.replication__input-react-select', waitTime, true)
 
       // enter our source DB
@@ -91,13 +91,13 @@ module.exports = {
 
       // now login and fill in the replication form
       .loginToGUI()
-      .url(baseUrl + '/#replication/_create')
+      .url(baseUrl + '/#/replication/_create')
       .waitForElementVisible('button#replicate', waitTime, true)
       .waitForElementVisible('#replication-source', waitTime, true)
 
       // select the LOCAL db as the source
       .clickWhenVisible('#replication-source')
-      .keys(['\uE006'])
+      .keys(['\uE015', '\uE006'])
       .waitForElementVisible('.replication__input-react-select', waitTime, true)
       .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
 
@@ -142,13 +142,13 @@ module.exports = {
 
       // now login and fill in the replication form
       .loginToGUI()
-      .url(baseUrl + '/#replication/_create')
+      .url(baseUrl + '/#/replication/_create')
       .waitForElementVisible('button#replicate', waitTime, true)
       .waitForElementVisible('#replication-source', waitTime, true)
 
       // select the LOCAL db as the source
       .clickWhenVisible('#replication-source')
-      .keys(['\uE006'])
+      .keys(['\uE015', '\uE006'])
       .waitForElementVisible('.replication__input-react-select', waitTime, true)
       .setValue('.replication__input-react-select .Select-input input', [newDatabaseName1, client.Keys.ENTER])
 
diff --git a/app/core/api.js b/app/core/api.js
index efcb7f9..20c9f0d 100644
--- a/app/core/api.js
+++ b/app/core/api.js
@@ -20,6 +20,7 @@ import Flux from "flux";
 import $ from "jquery";
 import Backbone from "backbone";
 import _ from "lodash";
+import Promise from "bluebird";
 
 Backbone.$ = $;
 Backbone.ajax = function () {
@@ -32,7 +33,8 @@ Object.assign(FauxtonAPI, {
   utils: utils,
   Store: Store,
   Events: _.extend({}, Backbone.Events),
-  dispatcher: new Flux.Dispatcher()
+  dispatcher: new Flux.Dispatcher(),
+  Promise: Promise
 });
 
 // Pass along all constants
diff --git a/assets/less/notification-center.less b/assets/less/notification-center.less
index 07935b3..ae60641 100644
--- a/assets/less/notification-center.less
+++ b/assets/less/notification-center.less
@@ -145,6 +145,7 @@ body #dashboard #notification-center-btn {
         }
         p {
           margin-bottom: 0;
+          overflow-wrap: break-word;
         }
         div.flex-body {
           overflow: hidden;
diff --git a/docker/dc.selenium.yml b/docker/dc.selenium.yml
index 41ddd2b..5d1c385 100644
--- a/docker/dc.selenium.yml
+++ b/docker/dc.selenium.yml
@@ -11,4 +11,4 @@ services:
     image: klaemo/couchdb:2.0-dev@sha256:e9b71abaff6aeaa34ee28604c3aeb78f3a7c789ad74a7b88148e2ef78f1e3b21
     command: '--with-haproxy -a tester:testerpass'
     ports:
-      - "5984:5984"
+      - "5984:5984"
\ No newline at end of file
diff --git a/jest-setup.js b/jest-setup.js
index 5b28e77..ec63056 100644
--- a/jest-setup.js
+++ b/jest-setup.js
@@ -17,4 +17,9 @@ window.$ = window.jQuery = require('jquery');
 window._ = require('lodash');
 window.Backbone = require('backbone');
 
+Object.defineProperty(window.location, 'origin', {
+  writable: true,
+  value: 'http://dev:8000'
+});
+
 

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <commits@couchdb.apache.org>'].

Mime
View raw message