You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2015/07/22 14:17:07 UTC

[1/3] fauxton commit: updated refs/heads/master to 5de1fe6

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master c754c6cac -> 5de1fe668


Admin: Fix style issues

PR: #480
PR-URL: https://github.com/apache/couchdb-fauxton/pull/480
Reviewed-By: garren smith <ga...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/3896d202
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/3896d202
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/3896d202

Branch: refs/heads/master
Commit: 3896d202d368de90b2565cf16feb6fb3898a4376
Parents: 919a7f8
Author: Robert Kowalski <ro...@apache.org>
Authored: Tue Jul 14 15:14:51 2015 +0200
Committer: Robert Kowalski <ro...@apache.org>
Committed: Wed Jul 22 14:17:08 2015 +0200

----------------------------------------------------------------------
 app/addons/auth/resources.js | 43 +++++++++++++++++----------------------
 1 file changed, 19 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/3896d202/app/addons/auth/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/resources.js b/app/addons/auth/resources.js
index c3deed7..175a314 100644
--- a/app/addons/auth/resources.js
+++ b/app/addons/auth/resources.js
@@ -55,7 +55,6 @@ function (app, FauxtonAPI, CouchdbSession) {
 
       this.messages = _.extend({},  {
           missingCredentials: 'Username or password cannot be blank.',
-          passwordsNotMatch:  'Passwords do not match.',
           loggedIn: 'You have been logged in.',
           adminCreated: 'CouchDB admin created',
           changePassword: 'Your password has been updated.',
@@ -132,10 +131,9 @@ function (app, FauxtonAPI, CouchdbSession) {
     },
 
     createAdmin: function (username, password, login) {
-      var that = this,
-          error_promise =  this.validateUser(username, password, this.messages.missingCredentials);
+      var errorPromise = this.validateUser(username, password, this.messages.missingCredentials);
 
-      if (error_promise) { return error_promise; }
+      if (errorPromise) { return errorPromise; }
 
       var admin = new Admin({
         name: username,
@@ -144,19 +142,18 @@ function (app, FauxtonAPI, CouchdbSession) {
 
       return admin.save().then(function () {
         if (login) {
-          return that.login(username, password);
-        } else {
-          return that.fetchUser({forceFetch: true});
+          return this.login(username, password);
         }
-      });
+
+        return this.fetchUser({forceFetch: true});
+
+      }.bind(this));
     },
 
     login: function (username, password) {
-      var error_promise =  this.validateUser(username, password, this.messages.missingCredentials);
-
-      if (error_promise) { return error_promise; }
+      var errorPromise = this.validateUser(username, password, this.messages.missingCredentials);
 
-      var that = this;
+      if (errorPromise) { return errorPromise; }
 
       return $.ajax({
         cache: false,
@@ -165,8 +162,8 @@ function (app, FauxtonAPI, CouchdbSession) {
         dataType: "json",
         data: {name: username, password: password}
       }).then(function () {
-        return that.fetchUser({forceFetch: true});
-      });
+        return this.fetchUser({forceFetch: true});
+      }.bind(this));
     },
 
     logout: function () {
@@ -183,23 +180,21 @@ function (app, FauxtonAPI, CouchdbSession) {
       });
     },
 
-    changePassword: function (password, password_confirm) {
-      var error_promise =  this.validatePasswords(password, password_confirm, this.messages.passwordsNotMatch);
+    changePassword: function (password, confirmedPassword) {
+      var errorMessage = 'Passwords do not match.';
+      var errorPromise = this.validatePasswords(password, confirmedPassword, errorMessage);
 
-      if (error_promise) { return error_promise; }
-
-      var  that = this,
-           info = this.get('info'),
-           userCtx = this.get('userCtx');
+      if (errorPromise) { return errorPromise; }
 
+      var userName = this.get('userCtx').name;
       var admin = new Admin({
-        name: userCtx.name,
+        name: userName,
         value: password
       });
 
       return admin.save().then(function () {
-        return that.login(userCtx.name, password);
-      });
+        return this.login(userName, password);
+      }.bind(this));
     }
   });
 


[2/3] fauxton commit: updated refs/heads/master to 5de1fe6

Posted by ro...@apache.org.
Revert "Detect if we run on a backdoor port"

This reverts commit 9ab1292d4e926ecba7c15291a04feb0342b40b21.

PR: #480
PR-URL: https://github.com/apache/couchdb-fauxton/pull/480
Reviewed-By: garren smith <ga...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/919a7f8e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/919a7f8e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/919a7f8e

Branch: refs/heads/master
Commit: 919a7f8e98c354f3646f12ce16d58e144a98635a
Parents: c754c6c
Author: Robert Kowalski <ro...@apache.org>
Authored: Tue Jun 23 13:44:49 2015 +0200
Committer: Robert Kowalski <ro...@apache.org>
Committed: Wed Jul 22 14:17:08 2015 +0200

----------------------------------------------------------------------
 Gruntfile.js                     |  2 +-
 app/addons/auth/base.js          | 12 ++---
 app/addons/auth/test/baseSpec.js | 57 ++++++---------------
 app/app.js                       | 24 ---------
 app/tests/appSpec.js             | 96 -----------------------------------
 5 files changed, 19 insertions(+), 172 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/919a7f8e/Gruntfile.js
----------------------------------------------------------------------
diff --git a/Gruntfile.js b/Gruntfile.js
index 570a40f..7955f1a 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -373,7 +373,7 @@ module.exports = function (grunt) {
     mochaSetup: {
       default: {
         files: {
-          src: initHelper.watchFiles(['[Ss]pec.js'], ['./app/addons/**/*[Ss]pec.js', './app/addons/**/*[Ss]pec.react.js', './app/core/**/*[Ss]pec.js', './app/**/*[Ss]pec.js'])
+          src: initHelper.watchFiles(['[Ss]pec.js'], ['./app/addons/**/*[Ss]pec.js', './app/addons/**/*[Ss]pec.react.js', './app/core/**/*[Ss]pec.js'])
         },
         template: 'test/test.config.underscore',
         config: './app/config.js'

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/919a7f8e/app/addons/auth/base.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/base.js b/app/addons/auth/base.js
index 4d0bb0c..8f4b1b3 100644
--- a/app/addons/auth/base.js
+++ b/app/addons/auth/base.js
@@ -13,13 +13,11 @@
 define([
   "app",
   "api",
-  'addons/auth/routes'
+  "addons/auth/routes"
 ],
 
 function (app, FauxtonAPI, Auth) {
 
-  var isRunningOnBackdoorPort = null;
-
   Auth.session = new Auth.Session();
   FauxtonAPI.setSession(Auth.session);
   app.session = Auth.session;
@@ -39,7 +37,6 @@ function (app, FauxtonAPI, Auth) {
       var link = {};
 
       if (session.isAdminParty()) {
-
         link = {
           id: "auth",
           title: "Admin Party!",
@@ -78,11 +75,8 @@ function (app, FauxtonAPI, Auth) {
 
     });
 
-    FauxtonAPI.isRunningOnBackdoorPort().then(function (res) {
-      isRunningOnBackdoorPort = res.runsOnBackportPort;
-      Auth.session.fetchUser().then(function () {
-        Auth.session.trigger('change');
-      });
+    Auth.session.fetchUser().then(function () {
+      Auth.session.trigger('change');
     });
 
     var auth = function (session, roles) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/919a7f8e/app/addons/auth/test/baseSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/test/baseSpec.js b/app/addons/auth/test/baseSpec.js
index d22f710..cc5e83f 100644
--- a/app/addons/auth/test/baseSpec.js
+++ b/app/addons/auth/test/baseSpec.js
@@ -21,25 +21,12 @@ define([
 
     describe("failed login", function () {
 
-      after(function () {
-        FauxtonAPI.session.off('change');
-        testUtils.restore(FauxtonAPI.session.isAdminParty);
-        testUtils.restore(FauxtonAPI.isRunningOnBackdoorPort);
-        testUtils.restore(FauxtonAPI.navigate);
-      });
-
       it("redirects with replace: true set", function () {
         var navigateSpy = sinon.spy(FauxtonAPI, 'navigate');
-
-        var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true);
-        var deferred = FauxtonAPI.Deferred();
-        sinon.stub(FauxtonAPI, 'isRunningOnBackdoorPort').returns(deferred);
+        FauxtonAPI.auth = new Auth();
+        FauxtonAPI.session.isLoggedIn = function () { return false; };
         Base.initialize();
-
-        deferred.resolve({runsOnBackportPort: false});
-
         FauxtonAPI.auth.authDeniedCb();
-
         assert.ok(navigateSpy.withArgs('/login?urlback=', {replace: true}).calledOnce);
       });
     });
@@ -48,28 +35,20 @@ define([
   describe('auth session change', function () {
 
     afterEach(function () {
-      testUtils.restore(FauxtonAPI.updateHeaderLink);
-      testUtils.restore(FauxtonAPI.session.isAdminParty);
-      testUtils.restore(FauxtonAPI.isRunningOnBackdoorPort);
-      testUtils.restore(FauxtonAPI.session.isLoggedIn);
-      testUtils.restore(FauxtonAPI.session.user);
-      FauxtonAPI.session.off('change');
+      FauxtonAPI.updateHeaderLink.restore && FauxtonAPI.updateHeaderLink.restore();
+      FauxtonAPI.session.isAdminParty.restore && FauxtonAPI.session.isAdminParty.restore();
     });
 
     it('for admin party changes title to admin party', function () {
       var spy = sinon.spy(FauxtonAPI, 'updateHeaderLink');
       var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(true);
-      var deferred = FauxtonAPI.Deferred();
-      sinon.stub(FauxtonAPI, 'isRunningOnBackdoorPort').returns(deferred);
-      Base.initialize();
-      deferred.resolve({runsOnBackportPort: true});
-
       FauxtonAPI.session.trigger('change');
 
       assert.ok(spy.calledOnce);
       var args = spy.getCall(0).args[0];
 
       assert.ok(args.title.match(/Admin Party/));
+      FauxtonAPI.session.isAdminParty.restore();
     });
 
     it('for login changes title to login', function () {
@@ -77,53 +56,47 @@ define([
       var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
       sinon.stub(FauxtonAPI.session, 'user').returns({name: 'test-user'});
       sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-      var deferred = FauxtonAPI.Deferred();
-      sinon.stub(FauxtonAPI, 'isRunningOnBackdoorPort').returns(deferred);
-      Base.initialize();
-      deferred.resolve({runsOnBackportPort: true});
-
       FauxtonAPI.session.trigger('change');
 
       assert.ok(spy.calledOnce);
       var args = spy.getCall(0).args[0];
 
       assert.equal(args.title, 'test-user');
-
+      FauxtonAPI.session.isLoggedIn.restore();
+      FauxtonAPI.session.user.restore();
+      FauxtonAPI.session.isAdminParty.restore();
     });
 
     it('for login adds logout link', function () {
+      var spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
       var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
       sinon.stub(FauxtonAPI.session, 'user').returns({name: 'test-user'});
       sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(true);
-      var deferred = FauxtonAPI.Deferred();
-      sinon.stub(FauxtonAPI, 'isRunningOnBackdoorPort').returns(deferred);
-      Base.initialize();
-      deferred.resolve({runsOnBackportPort: false});
-      var spy = sinon.spy(FauxtonAPI, 'addHeaderLink');
       FauxtonAPI.session.trigger('change');
 
       assert.ok(spy.calledOnce);
       var args = spy.getCall(0).args[0];
 
       assert.equal(args.title, 'Logout');
+      FauxtonAPI.session.isLoggedIn.restore();
+      FauxtonAPI.session.user.restore();
+      FauxtonAPI.session.isAdminParty.restore();
     });
 
     it('for logout, removes logout link', function () {
       var spy = sinon.spy(FauxtonAPI, 'removeHeaderLink');
       var stub = sinon.stub(FauxtonAPI.session, 'isAdminParty').returns(false);
       sinon.stub(FauxtonAPI.session, 'isLoggedIn').returns(false);
-      var deferred = FauxtonAPI.Deferred();
-      sinon.stub(FauxtonAPI, 'isRunningOnBackdoorPort').returns(deferred);
-      Base.initialize();
-      deferred.resolve({runsOnBackportPort: true});
-
       FauxtonAPI.session.trigger('change');
 
       assert.ok(spy.calledOnce);
       var args = spy.getCall(0).args[0];
 
       assert.equal(args.id, 'logout');
+      FauxtonAPI.session.isLoggedIn.restore();
+      FauxtonAPI.session.isAdminParty.restore();
     });
 
+
   });
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/919a7f8e/app/app.js
----------------------------------------------------------------------
diff --git a/app/app.js b/app/app.js
index a75949d..6483e5a 100644
--- a/app/app.js
+++ b/app/app.js
@@ -126,31 +126,7 @@ function (app, $, _, Backbone, Bootstrap, Helpers, Utils, FauxtonAPI, Couchdb) {
           type: 'REMOVE_NAVBAR_LINK',
           link: link
       });
-    },
-
-    isRunningOnBackdoorPort: function () {
-      if (this._backdoorDeferred) {
-        return this._backdoorDeferred;
-      }
-
-      this._backdoorDeferred = FauxtonAPI.Deferred();
-
-      $.ajax({
-        type: 'GET',
-        url: app.host + '/_cluster_setup'
-      })
-      .then(function () {
-        this._backdoorDeferred.resolve({runsOnBackportPort: false});
-      }.bind(this))
-      .fail(function (res) {
-        this._backdoorDeferred.resolve({
-          runsOnBackportPort: res.status === 400
-        });
-      }.bind(this));
-
-      return this._backdoorDeferred;
     }
-
   });
 
   return app;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/919a7f8e/app/tests/appSpec.js
----------------------------------------------------------------------
diff --git a/app/tests/appSpec.js b/app/tests/appSpec.js
deleted file mode 100644
index bf38f87..0000000
--- a/app/tests/appSpec.js
+++ /dev/null
@@ -1,96 +0,0 @@
-// 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.
-define([
-  'app',
-  'api',
-  'testUtils',
-], function (app, FauxtonAPI, testUtils) {
-  var assert = testUtils.assert;
-
-  describe('app', function () {
-
-    describe('isRunningOnBackdoorPort', function () {
-      var server, appHost;
-      beforeEach(function () {
-        appHost = app.host;
-        app.host = 'http://example.com';
-        server = sinon.fakeServer.create();
-      });
-      afterEach(function () {
-        app.host = appHost;
-        testUtils.restore(server);
-        testUtils.restore($.ajax);
-        FauxtonAPI._backdoorDeferred = null;
-      });
-
-      it('caches content', function (done) {
-        server.respondWith('GET', 'http://example.com/_cluster_setup',
-            [200, { "Content-Type": "application/json" },
-             '[{ "id": 12, "comment": "Hey there" }]']);
-
-        var spyAjax = sinon.spy($, 'ajax');
-        var promise = FauxtonAPI.isRunningOnBackdoorPort();
-        server.respond();
-
-        promise.then(function (res) {
-          var promise2 = FauxtonAPI.isRunningOnBackdoorPort();
-          promise2.then(function (res) {
-
-            assert.ok(spyAjax.calledOnce);
-            done();
-          });
-        });
-      });
-
-      it('returns false on a 401', function (done) {
-        server.respondWith('GET', 'http://example.com/_cluster_setup',
-            [401, { "Content-Type": "application/json" }, '']);
-
-        var promise = FauxtonAPI.isRunningOnBackdoorPort();
-        server.respond();
-
-        promise.then(function (res) {
-          assert.deepEqual({runsOnBackportPort: false}, res);
-          done();
-        });
-      });
-
-      it('returns false on a 200', function (done) {
-        server.respondWith('GET', 'http://example.com/_cluster_setup',
-            [200, { "Content-Type": "application/json" }, '']);
-
-        var promise = FauxtonAPI.isRunningOnBackdoorPort();
-        server.respond();
-
-        promise.then(function (res) {
-          assert.deepEqual({runsOnBackportPort: false}, res);
-          done();
-        });
-      });
-
-
-      it('returns true on a 400', function (done) {
-        server.respondWith('GET', 'http://example.com/_cluster_setup',
-            [400, {'Content-Type': 'application/json' },
-             '[{ "id": 12, "comment": "Hey there" }]']);
-
-        var promise = FauxtonAPI.isRunningOnBackdoorPort();
-        server.respond();
-
-        promise.then(function (res) {
-          assert.deepEqual({runsOnBackportPort: true}, res);
-          done();
-        });
-      });
-    });
-  });
-});


[3/3] fauxton commit: updated refs/heads/master to 5de1fe6

Posted by ro...@apache.org.
Just enable config for a "cluster-of-one"

Warn users if they run multi node setups.

Testing instructions for single node setups, given you usually run
a cluster of three nodes:

1. stop couchdb, connect fauxton to a single node, change

```
exports.couch = 'http://localhost:15984/';
```

in `tasks/helper.js`

2. get rid of `dev/lib/` in the couchdb folder, so the cluster does
not know about the other nodes any more, caution: you will lose
your current dbs.

3. boot couchdb with one node:

```
./dev/run --with-admin-party-please -n1
```
4. PROFIT!

COUCHDB-2601 COUCHDB-2599 COUCHDB-2390

PR: #480
PR-URL: https://github.com/apache/couchdb-fauxton/pull/480
Reviewed-By: garren smith <ga...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/5de1fe66
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/5de1fe66
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/5de1fe66

Branch: refs/heads/master
Commit: 5de1fe6682fc5a9dee44022498357febd73c3600
Parents: 3896d20
Author: Robert Kowalski <ro...@apache.org>
Authored: Mon Jul 13 16:31:18 2015 +0200
Committer: Robert Kowalski <ro...@apache.org>
Committed: Wed Jul 22 14:17:34 2015 +0200

----------------------------------------------------------------------
 .gitignore                                      |  1 +
 app/addons/auth/actions.js                      | 13 ++--
 app/addons/auth/resources.js                    | 20 +++--
 app/addons/auth/routes.js                       | 40 +++++++---
 .../auth/test/auth.componentsSpec.react.jsx     |  9 ++-
 app/addons/cluster/base.js                      | 25 ++++++
 app/addons/cluster/cluster.actions.js           | 51 +++++++++++++
 app/addons/cluster/cluster.actiontypes.js       | 17 +++++
 app/addons/cluster/cluster.react.jsx            | 80 ++++++++++++++++++++
 app/addons/cluster/cluster.stores.js            | 60 +++++++++++++++
 app/addons/cluster/resources.js                 | 42 ++++++++++
 app/addons/cluster/routes.js                    | 52 +++++++++++++
 app/addons/cluster/tests/clusterSpec.react.jsx  | 57 ++++++++++++++
 app/addons/cluster/tests/resourcesSpec.js       | 60 +++++++++++++++
 app/addons/config/assets/less/config.less       | 18 +++++
 app/addons/config/resources.js                  | 23 +++++-
 app/addons/config/routes.js                     | 77 +++++++++++--------
 app/addons/config/tests/configSpec.js           |  6 +-
 app/addons/config/views.js                      |  5 +-
 app/addons/cors/actions.js                      | 45 ++++++-----
 app/addons/cors/components.react.jsx            |  8 +-
 app/addons/cors/resources.js                    | 19 ++++-
 app/addons/cors/stores.js                       |  5 ++
 app/addons/cors/tests/componentsSpec.react.jsx  |  1 +
 app/addons/cors/tests/resourcesSpec.js          |  2 +-
 app/addons/documents/assets/less/sidenav.less   |  3 +-
 app/addons/fauxton/base.js                      |  1 +
 .../tests/nightwatch/highlightsidebar.js        |  6 +-
 settings.json.default                           |  1 +
 29 files changed, 654 insertions(+), 93 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index b3517fa..a5db49e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ app/load_addons.js
 app/addons/*
 !app/addons/activetasks
 !app/addons/config
+!app/addons/cluster
 !app/addons/components
 !app/addons/replication
 !app/addons/auth

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/auth/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js
index ab3f50e..e7b468b 100644
--- a/app/addons/auth/actions.js
+++ b/app/addons/auth/actions.js
@@ -11,10 +11,12 @@
 // the License.
 define([
   'api',
-  'addons/auth/actiontypes'
+  'addons/auth/actiontypes',
+  'addons/cluster/cluster.stores'
 ],
-function (FauxtonAPI, ActionTypes) {
+function (FauxtonAPI, ActionTypes, ClusterStore) {
 
+  var nodesStore = ClusterStore.nodesStore;
 
   var errorHandler = function (xhr, type, msg) {
     msg = xhr;
@@ -45,7 +47,8 @@ function (FauxtonAPI, ActionTypes) {
     },
 
     changePassword: function (password, passwordConfirm) {
-      var promise = FauxtonAPI.session.changePassword(password, passwordConfirm);
+      var nodes = nodesStore.getNodes();
+      var promise = FauxtonAPI.session.changePassword(password, passwordConfirm, nodes[0].node);
 
       promise.done(function () {
         FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.changePassword });
@@ -70,7 +73,8 @@ function (FauxtonAPI, ActionTypes) {
     },
 
     createAdmin: function (username, password, loginAfter) {
-      var promise = FauxtonAPI.session.createAdmin(username, password, loginAfter);
+      var nodes = nodesStore.getNodes();
+      var promise = FauxtonAPI.session.createAdmin(username, password, loginAfter, nodes[0].node);
 
       promise.then(function () {
         FauxtonAPI.addNotification({ msg: FauxtonAPI.session.messages.adminCreated });
@@ -84,7 +88,6 @@ function (FauxtonAPI, ActionTypes) {
       promise.fail(function (xhr, type, msg) {
         msg = xhr;
         if (arguments.length === 3) {
-          console.log("here...", xhr.responseJSON);
           msg = xhr.responseJSON.reason;
         }
         errorHandler(FauxtonAPI.session.messages.adminCreationFailedPrefix + ' ' + msg);

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/auth/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/resources.js b/app/addons/auth/resources.js
index 175a314..438cdb4 100644
--- a/app/addons/auth/resources.js
+++ b/app/addons/auth/resources.js
@@ -22,9 +22,19 @@ function (app, FauxtonAPI, CouchdbSession) {
 
 
   var Admin = Backbone.Model.extend({
+
+    initialize: function (props, options) {
+      this.node = options.node;
+    },
+
     url: function () {
-      return app.host + '/_config/admins/' + this.get("name");
+      if (!this.node) {
+        throw new Error('no node set');
+      }
+
+      return app.host + '/_node/' + this.node + '/_config/admins/' + this.get('name');
     },
+
     isNew: function () { return false; },
 
     sync: function (method, model, options) {
@@ -130,7 +140,7 @@ function (app, FauxtonAPI, CouchdbSession) {
       }
     },
 
-    createAdmin: function (username, password, login) {
+    createAdmin: function (username, password, login, node) {
       var errorPromise = this.validateUser(username, password, this.messages.missingCredentials);
 
       if (errorPromise) { return errorPromise; }
@@ -138,7 +148,7 @@ function (app, FauxtonAPI, CouchdbSession) {
       var admin = new Admin({
         name: username,
         value: password
-      });
+      }, {node: node});
 
       return admin.save().then(function () {
         if (login) {
@@ -180,7 +190,7 @@ function (app, FauxtonAPI, CouchdbSession) {
       });
     },
 
-    changePassword: function (password, confirmedPassword) {
+    changePassword: function (password, confirmedPassword, node) {
       var errorMessage = 'Passwords do not match.';
       var errorPromise = this.validatePasswords(password, confirmedPassword, errorMessage);
 
@@ -190,7 +200,7 @@ function (app, FauxtonAPI, CouchdbSession) {
       var admin = new Admin({
         name: userName,
         value: password
-      });
+      }, {node: node});
 
       return admin.save().then(function () {
         return this.login(userName, password);

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/auth/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/auth/routes.js b/app/addons/auth/routes.js
index 2632773..e7945d5 100644
--- a/app/addons/auth/routes.js
+++ b/app/addons/auth/routes.js
@@ -15,10 +15,11 @@ define([
        "api",
   'addons/auth/resources',
   'addons/auth/actions',
-  'addons/auth/components.react'
+  'addons/auth/components.react',
+  'addons/cluster/cluster.actions'
 ],
 
-function (app, FauxtonAPI, Auth, AuthActions, Components) {
+function (app, FauxtonAPI, Auth, AuthActions, Components, ClusterActions) {
 
   var AuthRouteObject = FauxtonAPI.RouteObject.extend({
     layout: 'one_pane',
@@ -27,7 +28,12 @@ function (app, FauxtonAPI, Auth, AuthActions, Components) {
       'login?*extra': 'login',
       'login': 'login',
       'logout': 'logout',
-      'createAdmin': 'createAdmin'
+      'createAdmin': 'checkNodes',
+      'createAdmin/:node': 'createAdminForNode',
+    },
+
+    checkNodes: function () {
+      ClusterActions.navigateToNodeBasedOnNodeCount('/createAdmin/');
     },
 
     login: function () {
@@ -43,13 +49,9 @@ function (app, FauxtonAPI, Auth, AuthActions, Components) {
       });
     },
 
-    changePassword: function () {
-      this.crumbs = [{name: 'Change Password', link: "#" }];
-      this.setComponent('#dashboard-content', Components.ChangePasswordForm);
-    },
-
-    createAdmin: function () {
-      this.crumbs = [{name: 'Create Admin', link:"#"}];
+    createAdminForNode: function () {
+      ClusterActions.fetchNodes();
+      this.crumbs = [{name: 'Create Admin', link: '#'}];
       this.setComponent('#dashboard-content', Components.CreateAdminForm, { loginAfter: true });
     }
   });
@@ -60,15 +62,31 @@ function (app, FauxtonAPI, Auth, AuthActions, Components) {
 
     routes: {
       'changePassword': {
+        route: 'checkNodesForPasswordChange',
+        roles: ['fx_loggedIn']
+      },
+      'changePassword/:node': {
         route: 'changePassword',
         roles: ['fx_loggedIn']
       },
       'addAdmin': {
+        route: 'checkNodesForAddAdmin',
+        roles: ['_admin']
+      },
+      'addAdmin/:node': {
         route: 'addAdmin',
         roles: ['_admin']
       }
     },
 
+    checkNodesForPasswordChange: function () {
+      ClusterActions.navigateToNodeBasedOnNodeCount('/changePassword/');
+    },
+
+    checkNodesForAddAdmin: function () {
+      ClusterActions.navigateToNodeBasedOnNodeCount('/addAdmin/');
+    },
+
     selectedHeader: function () {
       return FauxtonAPI.session.user().name;
     },
@@ -78,11 +96,13 @@ function (app, FauxtonAPI, Auth, AuthActions, Components) {
     },
 
     changePassword: function () {
+      ClusterActions.fetchNodes();
       AuthActions.selectPage('changePassword');
       this.setComponent('#dashboard-content', Components.ChangePasswordForm);
     },
 
     addAdmin: function () {
+      ClusterActions.fetchNodes();
       AuthActions.selectPage('addAdmin');
       this.setComponent('#dashboard-content', Components.CreateAdminForm, { loginAfter: false });
     },

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/auth/test/auth.componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/auth/test/auth.componentsSpec.react.jsx b/app/addons/auth/test/auth.componentsSpec.react.jsx
index cdafb83..937ea78 100644
--- a/app/addons/auth/test/auth.componentsSpec.react.jsx
+++ b/app/addons/auth/test/auth.componentsSpec.react.jsx
@@ -16,8 +16,8 @@ define([
   'addons/auth/components.react',
   'addons/auth/stores',
   'addons/auth/actions'
-], function (FauxtonAPI, React, testUtils, Components, Stores, Actions) {
-  var assert = testUtils.assert;
+], function (FauxtonAPI, React, utils, Components, Stores, Actions) {
+  var assert = utils.assert;
 
   var TestUtils = React.addons.TestUtils;
   var createAdminSidebarStore = Stores.createAdminSidebarStore;
@@ -50,6 +50,7 @@ define([
       beforeEach(function () {
         container = document.createElement('div');
         changePasswordForm = TestUtils.renderIntoDocument(<Components.ChangePasswordForm />, container);
+        utils.restore(Actions.changePassword);
       });
 
       afterEach(function () {
@@ -69,9 +70,9 @@ define([
       });
 
       it('should call action to submit form', function () {
-        var spy = sinon.spy(Actions, 'changePassword');
+        var stub = sinon.stub(Actions, 'changePassword', function () {});
         TestUtils.Simulate.submit($(changePasswordForm.getDOMNode()).find('#change-password')[0]);
-        assert.ok(spy.calledOnce);
+        assert.ok(stub.calledOnce);
       });
     });
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/base.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/base.js b/app/addons/cluster/base.js
new file mode 100644
index 0000000..7e2892b
--- /dev/null
+++ b/app/addons/cluster/base.js
@@ -0,0 +1,25 @@
+// 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.
+
+define([
+  'app',
+  'api',
+  'addons/cluster/routes'
+],
+
+function (app, FauxtonAPI, Cluster) {
+
+  Cluster.initialize = function () {};
+
+  return Cluster;
+});
+

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/cluster.actions.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/cluster.actions.js b/app/addons/cluster/cluster.actions.js
new file mode 100644
index 0000000..1da30f9
--- /dev/null
+++ b/app/addons/cluster/cluster.actions.js
@@ -0,0 +1,51 @@
+// 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.
+
+define([
+  'api',
+  'addons/cluster/resources',
+  'addons/cluster/cluster.actiontypes'
+],
+function (FauxtonAPI, ClusterResources, ActionTypes) {
+  return {
+    fetchNodes: function () {
+      var memberships = new ClusterResources.ClusterNodes();
+
+      memberships.fetch().then(function () {
+        this.updateNodes({
+          nodes: memberships.get('nodes_mapped')
+        });
+      }.bind(this));
+    },
+
+    updateNodes: function (options) {
+      FauxtonAPI.dispatch({
+        type: ActionTypes.CLUSTER_FETCH_NODES,
+        options: options
+      });
+    },
+
+    navigateToNodeBasedOnNodeCount: function (successtarget) {
+      var memberships = new ClusterResources.ClusterNodes();
+
+      memberships.fetch().then(function () {
+        var nodes = memberships.get('all_nodes');
+
+        if (nodes.length === 1) {
+          return FauxtonAPI.navigate(successtarget + nodes[0]);
+        }
+        return FauxtonAPI.navigate('/cluster/disabled', {trigger: true});
+      });
+    }
+
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/cluster.actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/cluster.actiontypes.js b/app/addons/cluster/cluster.actiontypes.js
new file mode 100644
index 0000000..8fd521e
--- /dev/null
+++ b/app/addons/cluster/cluster.actiontypes.js
@@ -0,0 +1,17 @@
+// 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.
+
+define([], function () {
+  return {
+    CLUSTER_FETCH_NODES: 'CLUSTER_FETCH_NODES'
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/cluster.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/cluster/cluster.react.jsx b/app/addons/cluster/cluster.react.jsx
new file mode 100644
index 0000000..a53f695
--- /dev/null
+++ b/app/addons/cluster/cluster.react.jsx
@@ -0,0 +1,80 @@
+// 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.
+
+define([
+  'app',
+  'api',
+  'react',
+  'addons/cluster/cluster.stores'
+
+],
+
+function (app, FauxtonAPI, React, ClusterStore) {
+
+  var nodesStore = ClusterStore.nodesStore;
+
+
+  var DisabledConfigController = React.createClass({
+
+    getStoreState: function () {
+      return {
+        nodes: nodesStore.getNodes()
+      };
+    },
+
+    getInitialState: function () {
+      return this.getStoreState();
+    },
+
+    componentDidMount: function () {
+      nodesStore.on('change', this.onChange, this);
+    },
+
+    componentWillUnmount: function () {
+      nodesStore.off('change', this.onChange);
+    },
+
+    onChange: function () {
+      this.setState(this.getStoreState());
+    },
+
+    render: function () {
+      return (
+        <div className="config-warning-cluster-wrapper">
+          <div className="config-warning-cluster-container">
+            <div>
+              <div className="config-warning-icon-container pull-left">
+                <i className="fonticon-attention-circled"></i>
+              </div>
+              It seems that you are running a cluster with {this.state.nodes.length} nodes. For CouchDB 2.0
+              we recommend using a configuration management tools like Chef, Ansible,
+              Puppet or Salt (in no particular order) to configure your nodes in a cluster.
+              <br/>
+              <br/>
+              <div className="config-warning-other-text">
+                We highly recommend against configuring nodes in your cluster using the HTTP API and
+                suggest using a configuration management tool for all configurations.
+              </div>
+            </div>
+          </div>
+        </div>
+      );
+    }
+  });
+
+  var Views = {
+    DisabledConfigController: DisabledConfigController
+  };
+
+  return Views;
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/cluster.stores.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/cluster.stores.js b/app/addons/cluster/cluster.stores.js
new file mode 100644
index 0000000..14388b3
--- /dev/null
+++ b/app/addons/cluster/cluster.stores.js
@@ -0,0 +1,60 @@
+// 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.
+
+define([
+  'api',
+  'addons/cluster/cluster.actiontypes'
+], function (FauxtonAPI, ActionTypes) {
+
+  var NodesStore = FauxtonAPI.Store.extend({
+
+    initialize: function () {
+      this.reset();
+    },
+
+    reset: function () {
+      this._nodes = [];
+    },
+
+    setNodes: function (options) {
+      this._nodes = options.nodes;
+    },
+
+    getNodes: function () {
+      return this._nodes;
+    },
+
+    dispatch: function (action) {
+
+      switch (action.type) {
+        case ActionTypes.CLUSTER_FETCH_NODES:
+          this.setNodes(action.options);
+        break;
+
+        default:
+        return;
+      }
+
+      this.triggerChange();
+    }
+
+  });
+
+
+  var nodesStore = new NodesStore();
+
+  nodesStore.dispatchToken = FauxtonAPI.dispatcher.register(nodesStore.dispatch.bind(nodesStore));
+
+  return {
+    nodesStore: nodesStore
+  };
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/resources.js b/app/addons/cluster/resources.js
new file mode 100644
index 0000000..de9b8df
--- /dev/null
+++ b/app/addons/cluster/resources.js
@@ -0,0 +1,42 @@
+// 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.
+
+define([
+  'app',
+  'api'
+],
+function (app, FauxtonAPI) {
+
+  var Cluster = FauxtonAPI.addon();
+
+  Cluster.ClusterNodes = Backbone.Model.extend({
+    url: function () {
+      return app.host + '/_membership';
+    },
+
+    parse: function (res) {
+      var list;
+
+      list = res.all_nodes.reduce(function (acc, node) {
+        var isInCluster = res.cluster_nodes.indexOf(node) !== -1;
+
+        acc.push({node: node, isInCluster: isInCluster});
+        return acc;
+      }, []);
+
+      res.nodes_mapped = list;
+      return res;
+    }
+  });
+
+  return Cluster;
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/routes.js b/app/addons/cluster/routes.js
new file mode 100644
index 0000000..0b36832
--- /dev/null
+++ b/app/addons/cluster/routes.js
@@ -0,0 +1,52 @@
+// 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.
+
+define([
+  'app',
+  'api',
+  'addons/cluster/resources',
+  'addons/cluster/cluster.react',
+  'addons/cluster/cluster.actions',
+],
+
+function (app, FauxtonAPI, Cluster, ClusterComponents, ClusterActions) {
+
+
+  var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({
+    layout: 'one_pane',
+
+    routes: {
+      'cluster/disabled': 'showDisabledFeatureScreen',
+    },
+
+    crumbs: [
+      { name: 'Config disabled', link: '_config' }
+    ],
+
+    apiUrl: function () {
+      return [this.memberships.url(), this.memberships.documentation];
+    },
+
+    initialize: function () {
+      this.memberships = new Cluster.ClusterNodes();
+    },
+
+    showDisabledFeatureScreen: function () {
+      ClusterActions.fetchNodes();
+      this.warning = this.setComponent('#dashboard-content', ClusterComponents.DisabledConfigController);
+    }
+  });
+
+  Cluster.RouteObjects = [ConfigDisabledRouteObject];
+
+  return Cluster;
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/tests/clusterSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/cluster/tests/clusterSpec.react.jsx b/app/addons/cluster/tests/clusterSpec.react.jsx
new file mode 100644
index 0000000..9a6c3b5
--- /dev/null
+++ b/app/addons/cluster/tests/clusterSpec.react.jsx
@@ -0,0 +1,57 @@
+// 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.
+define([
+  'api',
+  'addons/cluster/cluster.react',
+  'addons/cluster/cluster.actions',
+  'addons/cluster/cluster.stores',
+
+  'testUtils',
+  'react'
+], function (FauxtonAPI, ClusterComponent, ClusterActions, ClusterStores, utils, React) {
+
+  var assert = utils.assert;
+  var TestUtils = React.addons.TestUtils;
+
+  describe('Cluster Controller', function () {
+    var container, controller;
+
+    beforeEach(function () {
+
+      var nodeList = [
+        {'node': 'node1@127.0.0.1', 'isInCluster': true},
+        {'node': 'node2@127.0.0.1', 'isInCluster': true},
+        {'node': 'node3@127.0.0.1', 'isInCluster': false},
+        {'node': 'node3@127.0.0.1', 'isInCluster': false},
+        {'node': 'node3@127.0.0.1', 'isInCluster': false},
+        {'node': 'node3@127.0.0.1', 'isInCluster': false}
+      ];
+
+      ClusterActions.updateNodes({nodes: nodeList});
+
+      container = document.createElement('div');
+      controller = TestUtils.renderIntoDocument(
+        <ClusterComponent.DisabledConfigController />,
+        container
+      );
+    });
+
+    afterEach(function () {
+      ClusterStores.nodesStore.reset();
+      React.unmountComponentAtNode(container);
+    });
+
+    it('renders the amount of nodes', function () {
+      assert.ok(/6 nodes/.test($(controller.getDOMNode()).text()), 'finds 6 nodes');
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cluster/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/cluster/tests/resourcesSpec.js b/app/addons/cluster/tests/resourcesSpec.js
new file mode 100644
index 0000000..03a30c8
--- /dev/null
+++ b/app/addons/cluster/tests/resourcesSpec.js
@@ -0,0 +1,60 @@
+// 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.
+
+define([
+  'testUtils',
+  'api',
+  'addons/cluster/resources',
+], function (testUtils, FauxtonAPI, Resources) {
+  var assert = testUtils.assert;
+
+
+  describe('Membership Model', function () {
+    var data = {
+      'all_nodes': ['node1@127.0.0.1', 'node2@127.0.0.1', 'node3@127.0.0.1', 'notpartofclusternode'],
+      'cluster_nodes': ['node1@127.0.0.1', 'node2@127.0.0.1', 'node3@127.0.0.1']
+    };
+
+    it('reorders the data', function () {
+      var memberships = new Resources.ClusterNodes();
+      var res = memberships.parse(data);
+
+      assert.deepEqual([
+        {node: 'node1@127.0.0.1', isInCluster: true},
+        {node: 'node2@127.0.0.1', isInCluster: true},
+        {node: 'node3@127.0.0.1', isInCluster: true},
+        {node: 'notpartofclusternode', isInCluster: false}
+      ],
+      res.nodes_mapped);
+    });
+
+    it('keeps the exiting data', function () {
+      var memberships = new Resources.ClusterNodes();
+      var res = memberships.parse(data);
+
+      assert.deepEqual([
+        'node1@127.0.0.1',
+        'node2@127.0.0.1',
+        'node3@127.0.0.1',
+        'notpartofclusternode'
+      ],
+      res.all_nodes);
+
+      assert.deepEqual([
+        'node1@127.0.0.1',
+        'node2@127.0.0.1',
+        'node3@127.0.0.1'
+      ],
+      res.cluster_nodes);
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/config/assets/less/config.less
----------------------------------------------------------------------
diff --git a/app/addons/config/assets/less/config.less b/app/addons/config/assets/less/config.less
index d7d6db4..6363d80 100644
--- a/app/addons/config/assets/less/config.less
+++ b/app/addons/config/assets/less/config.less
@@ -111,3 +111,21 @@ table.config {
     }
   }
 }
+
+.config-warning-cluster-wrapper {
+  .config-warning-cluster-container {
+    margin-left: -50px;
+  }
+  margin: 40px auto;
+  width: 70%;
+  .fonticon-attention-circled {
+    font-size: 40px;
+  }
+  .config-warning-icon-container {
+    width: 50px;
+    margin-top: -5px;
+  }
+  .config-warning-other-text {
+    margin-left: 50px;
+  }
+}

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/config/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/config/resources.js b/app/addons/config/resources.js
index 468860c..5c3ed3f 100644
--- a/app/addons/config/resources.js
+++ b/app/addons/config/resources.js
@@ -21,12 +21,20 @@ function (app, FauxtonAPI) {
   var Config = FauxtonAPI.addon();
 
 
-  Config.Model = Backbone.Model.extend({});
   Config.OptionModel = Backbone.Model.extend({
     documentation: FauxtonAPI.constants.DOC_URLS.CONFIG,
 
+    initialize: function (_, options) {
+      this.node = options.node;
+    },
+
     url: function () {
-      return app.host + '/_config/' + this.get('section') + '/' + encodeURIComponent(this.get('name'));
+      if (!this.node) {
+        throw new Error('no node set');
+      }
+
+      return app.host + '/_node/' + this.node + '/_config/' +
+        this.get('section') + '/' + encodeURIComponent(this.get('name'));
     },
 
     isNew: function () { return false; },
@@ -49,11 +57,16 @@ function (app, FauxtonAPI) {
     }
   });
 
+  Config.Model = Backbone.Model.extend({});
   Config.Collection = Backbone.Collection.extend({
     model: Config.Model,
 
     documentation: FauxtonAPI.constants.DOC_URLS.CONFIG,
 
+    initialize: function (_, options) {
+      this.node = options.node;
+    },
+
     comparator: function (OptionModel) {
       if (OptionModel.get('section')) {
         return OptionModel.get('section');
@@ -61,7 +74,11 @@ function (app, FauxtonAPI) {
     },
 
     url: function () {
-      return window.location.origin + '/_config';
+      if (!this.node) {
+        throw new Error('no node set');
+      }
+
+      return app.host + '/_node/' + this.node + '/_config';
     },
 
     findEntryInSection: function (sectionName, entry) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/config/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js
index 77d46da..38fd32c 100644
--- a/app/addons/config/routes.js
+++ b/app/addons/config/routes.js
@@ -16,33 +16,33 @@ define([
   'addons/config/resources',
   'addons/config/views',
   'addons/cors/components.react',
-  'addons/cors/actions'
+  'addons/cors/actions',
+  'addons/cluster/cluster.actions'
 ],
 
-function (app, FauxtonAPI, Config, Views, CORSComponents, CORSActions) {
+function (app, FauxtonAPI, Config, Views, CORSComponents, CORSActions, ClusterActions) {
 
-  var ConfigRouteObject = FauxtonAPI.RouteObject.extend({
-    layout: 'with_tabs_sidebar_scroll',
 
-    initialize: function () {
-      this.configs = new Config.Collection();
+  var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({
+    layout: 'one_pane',
 
-      this.sidebar = this.setView('#sidebar-content', new Views.Tabs({
-        sidebarItems: [
-          {
-            title: 'Main config',
-            typeSelect: 'main',
-            link: '_config'
-          },
-          {
-            title: 'CORS',
-            typeSelect: 'cors',
-            link: '_config/cors'
-          }
-        ]
-      }));
+    routes: {
+      '_config': 'checkNodes',
     },
 
+    crumbs: [
+      { name: 'Config disabled', link: '_config' }
+    ],
+
+    checkNodes: function () {
+      ClusterActions.navigateToNodeBasedOnNodeCount('/_config/');
+    }
+  });
+
+
+  var ConfigPerNodeRouteObject = FauxtonAPI.RouteObject.extend({
+    layout: 'with_tabs_sidebar_scroll',
+
     roles: ['_admin'],
     selectedHeader: 'Config',
 
@@ -55,29 +55,46 @@ function (app, FauxtonAPI, Config, Views, CORSComponents, CORSActions) {
     },
 
     routes: {
-      '_config': 'config',
-      '_config/cors':'configCORS'
+      '_config/:node': 'configForNode',
+      '_config/:node/cors': 'configCorsForNode'
+    },
+
+    initialize: function (_a, _b, options) {
+      var node = options[0];
+
+      this.configs = new Config.Collection(null, {node: node});
+
+      this.sidebar = this.setView('#sidebar-content', new Views.Tabs({
+        sidebarItems: [
+          {
+            title: 'Main config',
+            typeSelect: 'main',
+            link: '_config/' + node
+          },
+          {
+            title: 'CORS',
+            typeSelect: 'cors',
+            link: '_config/' + node + '/cors'
+          }
+        ]
+      }));
     },
 
-    config: function () {
+    configForNode: function () {
       this.newSection = this.setView('#right-header', new Views.ConfigHeader({ collection: this.configs }));
       this.setView('#dashboard-content', new Views.Table({ collection: this.configs }));
       this.sidebar.setSelectedTab('main');
     },
 
-    configCORS: function () {
+    configCorsForNode: function (node) {
       this.removeView('#right-header');
       this.newSection = this.setComponent('#dashboard-content', CORSComponents.CORSController);
-      CORSActions.FetchAndEditCors();
+      CORSActions.fetchAndEditCors(node);
       this.sidebar.setSelectedTab('cors');
-    },
-
-    establish: function () {
-      return [this.configs.fetch()];
     }
   });
 
-  Config.RouteObjects = [ConfigRouteObject];
+  Config.RouteObjects = [ConfigPerNodeRouteObject, ConfigDisabledRouteObject];
 
   return Config;
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/config/tests/configSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/config/tests/configSpec.js b/app/addons/config/tests/configSpec.js
index aefce92..cd5a03c 100644
--- a/app/addons/config/tests/configSpec.js
+++ b/app/addons/config/tests/configSpec.js
@@ -30,12 +30,12 @@ define([
         options: [{
           name: "testname"
         }]
-      });
+      }, {node: 'foo'});
 
       optionModels.push(model);
     });
 
-    collection = new Resources.Collection(optionModels);
+    collection = new Resources.Collection(optionModels, {node: 'foo'}, "foo");
   });
 
   describe("Config: Add Option Tray", function () {
@@ -93,7 +93,7 @@ define([
       optionModel = new Resources.OptionModel({
         section: "foo",
         name: "bar"
-      });
+      }, {node: 'foo'});
 
       tabMenu = new Views.TableRow({
         model: optionModel,

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/config/views.js
----------------------------------------------------------------------
diff --git a/app/addons/config/views.js b/app/addons/config/views.js
index 35318e3..5f14942 100644
--- a/app/addons/config/views.js
+++ b/app/addons/config/views.js
@@ -128,7 +128,7 @@ function (app, FauxtonAPI, Config, Components) {
               name: option.name,
               value: option.value,
               index: index
-            })
+            }, {node: this.collection.node})
           }));
         }, this);
       }, this);
@@ -204,11 +204,12 @@ function (app, FauxtonAPI, Config, Components) {
     },
 
     submitForm: function () {
+
       var option = new Config.OptionModel({
         section: this.$('input[name="section"]').val(),
         name: this.$('input[name="name"]').val(),
         value: this.$('input[name="value"]').val()
-      });
+      }, {node: this.collection.node});
       option.save();
 
       var section = this.collection.find(function (section) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cors/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/cors/actions.js b/app/addons/cors/actions.js
index cce1518..1a35f78 100644
--- a/app/addons/cors/actions.js
+++ b/app/addons/cors/actions.js
@@ -15,14 +15,15 @@ define([
   'addons/cors/resources'
   ], function (FauxtonAPI, ActionTypes, Resources) {
     return {
-      FetchAndEditCors: function () {
-        var cors = new Resources.Config();
-        var httpd = new Resources.Httpd();
+      fetchAndEditCors: function (node) {
+        var cors = new Resources.Config({node: node});
+        var httpd = new Resources.Httpd({node: node});
 
         FauxtonAPI.when([cors.fetch(), httpd.fetch()]).then(function () {
           this.editCors({
             origins: cors.get('origins'),
-            isEnabled: httpd.corsEnabled()
+            isEnabled: httpd.corsEnabled(),
+            node: node
           });
         }.bind(this));
       },
@@ -68,6 +69,7 @@ define([
           originalOrigin: originalOrigin
         });
       },
+
       methodChange: function (httpMethod) {
         FauxtonAPI.dispatch({
           type: ActionTypes.CORS_METHOD_CHANGE,
@@ -75,51 +77,56 @@ define([
         });
       },
 
-      saveEnableCorsToHttpd: function (enableCors) {
+      saveEnableCorsToHttpd: function (enableCors, node) {
         var enableOption = new Resources.ConfigModel({
           section: 'httpd',
           attribute: 'enable_cors',
-          value: enableCors.toString()
+          value: enableCors.toString(),
+          node: node
         });
 
         return enableOption.save();
       },
 
-      saveCorsOrigins: function (origins) {
+      saveCorsOrigins: function (origins, node) {
         var allowOrigins = new Resources.ConfigModel({
           section: 'cors',
           attribute: 'origins',
-          value: origins
+          value: origins,
+          node: node
         });
 
         return allowOrigins.save();
       },
 
-      saveCorsCredentials: function () {
+      saveCorsCredentials: function (node) {
         var allowCredentials = new Resources.ConfigModel({
           section: 'cors',
           attribute: 'credentials',
-          value: "true"
+          value: 'true',
+          node: node
         });
 
         return allowCredentials.save();
       },
 
-      saveCorsHeaders: function () {
+      saveCorsHeaders: function (node) {
         var corsHeaders = new Resources.ConfigModel({
           section: 'cors',
           attribute: 'headers',
-          value: 'accept, authorization, content-type, origin, referer'
+          value: 'accept, authorization, content-type, origin, referer',
+          node: node
         });
 
         return corsHeaders.save();
       },
 
-      saveCorsMethods: function () {
+      saveCorsMethods: function (node) {
         var corsMethods = new Resources.ConfigModel({
           section: 'cors',
           attribute: 'methods',
-          value: 'GET, PUT, POST, HEAD, DELETE'
+          value: 'GET, PUT, POST, HEAD, DELETE',
+          node: node
         });
 
         return corsMethods.save();
@@ -135,13 +142,13 @@ define([
 
       saveCors: function (options) {
         var promises = [];
-        promises.push(this.saveEnableCorsToHttpd(options.enableCors));
+        promises.push(this.saveEnableCorsToHttpd(options.enableCors, options.node));
 
         if (options.enableCors) {
-          promises.push(this.saveCorsOrigins(this.sanitizeOrigins(options.origins)));
-          promises.push(this.saveCorsCredentials());
-          promises.push(this.saveCorsHeaders());
-          promises.push(this.saveCorsMethods());
+          promises.push(this.saveCorsOrigins(this.sanitizeOrigins(options.origins), options.node));
+          promises.push(this.saveCorsCredentials(options.node));
+          promises.push(this.saveCorsHeaders(options.node));
+          promises.push(this.saveCorsMethods(options.node));
         }
 
         FauxtonAPI.when(promises).then(function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cors/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/cors/components.react.jsx b/app/addons/cors/components.react.jsx
index 1bf330a..fb9f655 100644
--- a/app/addons/cors/components.react.jsx
+++ b/app/addons/cors/components.react.jsx
@@ -228,7 +228,8 @@ define([
         origins: corsStore.getOrigins(),
         isAllOrigins: corsStore.isAllOrigins(),
         configChanged: corsStore.hasConfigChanged(),
-        shouldSaveChange: corsStore.shouldSaveChange()
+        shouldSaveChange: corsStore.shouldSaveChange(),
+        node: corsStore.getNode()
       };
     },
 
@@ -270,10 +271,11 @@ define([
       Actions.toggleEnableCors();
     },
 
-    save: function (event) {
+    save: function () {
       Actions.saveCors({
         enableCors: this.state.corsEnabled,
-        origins: this.state.origins
+        origins: this.state.origins,
+        node: this.state.node
       });
     },
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cors/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/cors/resources.js b/app/addons/cors/resources.js
index 3f42232..a22cfc8 100644
--- a/app/addons/cors/resources.js
+++ b/app/addons/cors/resources.js
@@ -21,7 +21,11 @@ function (app, FauxtonAPI) {
 
   CORS.Config = FauxtonAPI.Model.extend({
     url: function () {
-      return app.host + '/_config/cors';
+      if (!this.get('node')) {
+        throw new Error('node not set');
+      }
+
+      return window.location.origin + '/_node/' + this.get('node') + '/_config/cors';
     },
 
     parse: function (resp) {
@@ -38,7 +42,11 @@ function (app, FauxtonAPI) {
 
   CORS.Httpd = FauxtonAPI.Model.extend({
     url: function () {
-      return app.host + '/_config/httpd';
+      if (!this.get('node')) {
+        throw new Error('node not set');
+      }
+
+      return window.location.origin + '/_node/' + this.get('node') + '/_config/httpd';
     },
 
     corsEnabled: function () {
@@ -57,7 +65,12 @@ function (app, FauxtonAPI) {
     documentation: 'cors',
 
     url: function () {
-      return app.host + '/_config/' + encodeURIComponent(this.get('section')) + '/' + encodeURIComponent(this.get('attribute'));
+      if (!this.get('node')) {
+        throw new Error('node not set');
+      }
+
+      return app.host + '/_node/' + this.get('node') + '/_config/' +
+        encodeURIComponent(this.get('section')) + '/' + encodeURIComponent(this.get('attribute'));
     },
 
     isNew: function () { return false; },

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cors/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/cors/stores.js b/app/addons/cors/stores.js
index 1200053..bd36093 100644
--- a/app/addons/cors/stores.js
+++ b/app/addons/cors/stores.js
@@ -22,6 +22,7 @@ define([
       this._origins = options.origins;
       this._configChanged = false;
       this._shouldSaveChange = false;
+      this._node = options.node;
     },
 
     shouldSaveChange: function () {
@@ -69,6 +70,10 @@ define([
       return this._origins;
     },
 
+    getNode: function () {
+      return this._node;
+    },
+
     isAllOrigins: function () {
       var origins = this.getOrigins();
       if (_.include(origins, '*')) {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cors/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/cors/tests/componentsSpec.react.jsx b/app/addons/cors/tests/componentsSpec.react.jsx
index dedbef0..b725085 100644
--- a/app/addons/cors/tests/componentsSpec.react.jsx
+++ b/app/addons/cors/tests/componentsSpec.react.jsx
@@ -33,6 +33,7 @@ define([
       beforeEach(function () {
         container = document.createElement('div');
         corsStore._origins = ['http://hello.com'];
+        corsStore._node = 'node2@127.0.0.1';
         corsStore._isEnabled = true;
         corsStore._configChanged = true;
         corsEl = TestUtils.renderIntoDocument(<Views.CORSController />, container);

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/cors/tests/resourcesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/cors/tests/resourcesSpec.js b/app/addons/cors/tests/resourcesSpec.js
index 29c30e2..175a1fc 100644
--- a/app/addons/cors/tests/resourcesSpec.js
+++ b/app/addons/cors/tests/resourcesSpec.js
@@ -20,7 +20,7 @@ define([
     var cors;
 
     beforeEach(function () {
-      cors = new CORS.Config({});
+      cors = new CORS.Config(null, {node: 'node2@127.0.0.1'});
     });
 
     it('Splits up origins into array', function () {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/documents/assets/less/sidenav.less
----------------------------------------------------------------------
diff --git a/app/addons/documents/assets/less/sidenav.less b/app/addons/documents/assets/less/sidenav.less
index 27aa406..11df376 100644
--- a/app/addons/documents/assets/less/sidenav.less
+++ b/app/addons/documents/assets/less/sidenav.less
@@ -46,8 +46,7 @@
     text-shadow: none;
     background-color: rgba(0, 0, 0, 0.05);
   }
-  .nav-list > .active > a:hover,
-  .nav-list > .active > a:focus{
+  .nav-list > .active > a:hover {
     color: white;
   }
   .nav-list > li > a:hover + div.add-dropdown .dropdown-toggle{

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/fauxton/base.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js
index c570f20..db82062 100644
--- a/app/addons/fauxton/base.js
+++ b/app/addons/fauxton/base.js
@@ -49,6 +49,7 @@ function (app, FauxtonAPI, Components, NavbarReactComponents, NavigationActions,
     }
   });
 
+
   Fauxton.initialize = function () {
     app.apiBar = new Components.ApiBar();
 

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
----------------------------------------------------------------------
diff --git a/app/addons/fauxton/tests/nightwatch/highlightsidebar.js b/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
index 445e1e5..7171887 100644
--- a/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
+++ b/app/addons/fauxton/tests/nightwatch/highlightsidebar.js
@@ -19,10 +19,10 @@ module.exports = {
       .loginToGUI()
       .url(baseUrl)
       .waitForElementPresent('#add-new-database', waitTime, false)
-      .click('a[href="#changePassword"]')
+      .click('a[href="#/replication"]')
       .pause(1000)
-      .waitForElementVisible('.auth-page', waitTime, false)
-      .assert.cssClassPresent('li[data-nav-name="' + client.globals.test_settings.fauxton_username + '"]', "active")
+      .waitForElementVisible('#replication', waitTime, false)
+      .assert.cssClassPresent('li[data-nav-name="Replication"]', 'active')
     .end();
   }
 };

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/5de1fe66/settings.json.default
----------------------------------------------------------------------
diff --git a/settings.json.default b/settings.json.default
index cdddf6c..b44c98b 100644
--- a/settings.json.default
+++ b/settings.json.default
@@ -5,6 +5,7 @@
   { "name": "databases" },
   { "name": "documents" },
   { "name": "activetasks" },
+  { "name": "cluster" },
   { "name": "config" },
   { "name": "replication" },
   { "name": "cors" },