popojargo closed pull request #1045: Migrate couchdb setup to Redux
URL: https://github.com/apache/couchdb-fauxton/pull/1045
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git 
a/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js 
b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js
index a7873e470..f36f652fa 100644
--- 
a/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js
+++ 
b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js
@@ -120,7 +120,8 @@ export default class MainFieldsView extends React.Component 
{
               <label htmlFor="qoStable" id="qoStableLabel">Stable</label>
             </div>
             <div className="dropdown inline">
-              <label className="drop-down">Update
+              <label className="drop-down">
+                Update
                 <select className="input-small" id="qoUpdate" value={update} 
onChange={this.onUpdateChange}>
                   {this.getUpdateOptions()}
                 </select>
diff --git a/app/addons/setup/__tests__/helpers.test.js 
b/app/addons/setup/__tests__/helpers.test.js
new file mode 100644
index 000000000..30b8bdb0e
--- /dev/null
+++ b/app/addons/setup/__tests__/helpers.test.js
@@ -0,0 +1,70 @@
+// 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 {isInvalid} from '../helpers';
+
+describe('Setup - Helpers', () => {
+
+  const validData = {
+    username: 'foo',
+    password: 'bar',
+    bind_address: '0.0.0.0',
+    singlenode: false,
+    port: 5984,
+    nodeCount: 3
+  };
+
+  describe('isInvalid', () => {
+
+    it('should return an error if no username is define', () => {
+      let data = Object.assign({}, validData);
+      data.username = '';
+      expect(isInvalid(data)).toBe('Admin name is required');
+    });
+
+    it('should return an error if password not set', () => {
+      let data = Object.assign({}, validData);
+      data.password = '';
+      expect(isInvalid(data)).toBe('Admin password is required');
+    });
+
+    it('should return an error if bind address is 127.0.0.1', () => {
+      let data = Object.assign({}, validData);
+      data.bind_address = '127.0.0.1';
+      expect(isInvalid(data)).toBe('Bind address can not be 127.0.0.1');
+    });
+
+    it('should return error if port is not a number', () => {
+      let data = Object.assign({}, validData);
+      data.port = 'foo';
+      expect(isInvalid(data)).toBe('Bind port must be a number');
+    });
+
+    it('should return error if node count is not a number', () => {
+
+      let data = Object.assign({}, validData);
+      data.nodeCount = 'foo';
+      expect(isInvalid(data)).toBe('Node count must be a number');
+    });
+
+    it('should return error if node counter is lower than 1', () => {
+      let data = Object.assign({}, validData);
+      data.nodeCount = 0;
+      expect(isInvalid(data)).toBe('Node count must be >= 1');
+    });
+
+    it('should return false if valid', () => {
+      expect(isInvalid(validData)).toBe(false);
+    });
+
+  });
+});
diff --git a/app/addons/setup/__tests__/setup.test.js 
b/app/addons/setup/__tests__/setup.test.js
deleted file mode 100644
index e22ed3a64..000000000
--- a/app/addons/setup/__tests__/setup.test.js
+++ /dev/null
@@ -1,91 +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.
-import Resources from "../resources";
-import testUtils from "../../../../test/mocha/testUtils";
-var assert = testUtils.assert,
-    model;
-
-describe('Setup: verify input', () => {
-
-  beforeEach(() => {
-    model = new Resources.Model();
-  });
-
-  it('You have to set a username', () => {
-    const error = model.validate({
-      admin: {
-        user: '',
-        password: 'ente'
-      }
-    });
-
-    assert.ok(error);
-  });
-
-  it('You have to set a password', () => {
-    const error = model.validate({
-      admin: {
-        user: 'rocko',
-        password: ''
-      }
-    });
-
-    assert.ok(error);
-  });
-
-  it('Port must be a number, if defined', () => {
-    const error = model.validate({
-      admin: {
-        user: 'rocko',
-        password: 'ente'
-      },
-      port: 'port'
-    });
-
-    assert.ok(error);
-  });
-
-  it('Bind address can not be 127.0.0.1', () => {
-    const error = model.validate({
-      admin: {
-        user: 'rocko',
-        password: 'ente'
-      },
-      bind_address: '127.0.0.1'
-    });
-
-    assert.ok(error);
-  });
-
-  it('Node count must be a number', () => {
-    const error = model.validate({
-      admin: {
-        user: 'rocko',
-        password: 'ente'
-      },
-      nodeCount: 'abc'
-    });
-    assert.ok(error);
-  });
-
-  it('Node count must be >= 1', () => {
-    const error = model.validate({
-      admin: {
-        user: 'rocko',
-        password: 'ente'
-      },
-      nodeCount: 0
-    });
-    assert.ok(error);
-  });
-
-});
diff --git a/app/addons/setup/__tests__/setupComponents.test.js 
b/app/addons/setup/__tests__/setupComponents.test.js
deleted file mode 100644
index 63aede263..000000000
--- a/app/addons/setup/__tests__/setupComponents.test.js
+++ /dev/null
@@ -1,75 +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.
-import Views from "../setup";
-import Stores from "../setup.stores";
-import utils from "../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
-import sinon from "sinon";
-import { mount } from 'enzyme';
-
-const assert = utils.assert;
-
-//this was commented out. I imagine it needs to be updated
-describe.skip('Setup Components', () => {
-
-  describe('IP / Port area', () => {
-
-    it('fires callbacks on change, ip', () => {
-      const changeHandler = sinon.spy();
-      const optSettings = mount(<Views.SetupOptionalSettings 
onAlterPort={null} onAlterBindAddress={changeHandler} />);
-
-      optSettings.find('.setup-input-ip').simulate('change', {target: {value: 
'Hello, world'}});
-      assert.ok(changeHandler.calledOnce);
-    });
-
-    it('fires callbacks on change, port', () => {
-      const changeHandler = sinon.spy();
-      var optSettings = mount(
-        <Views.SetupOptionalSettings onAlterPort={changeHandler} 
onAlterBindAddress={null} />
-      );
-
-      optSettings.find('.setup-input-port').simulate('change', {target: 
{value: 'Hello, world'}});
-      assert.ok(changeHandler.calledOnce);
-    });
-
-  });
-
-  describe('SingleNodeSetup', () => {
-    beforeEach(() => {
-      sinon.stub(Stores.setupStore, 'getIsAdminParty', () => { return false; 
});
-    });
-
-    afterEach(() => {
-      utils.restore(Stores.setupStore.getIsAdminParty);
-      Stores.setupStore.reset();
-    });
-
-    it('changes the values in the store for the setup node', () => {
-      const controller = mount(
-        <Views.SetupSingleNodeController />
-      );
-
-      controller.find('.setup-setupnode-section 
.setup-input-ip').simulate('change', {target: {value: '192.168.13.42'}});
-      controller.find('.setup-setupnode-section 
.setup-input-port').simulate('change', {target: {value: '1342'}});
-      controller.find('.setup-setupnode-section 
.setup-username').simulate('change', {target: {value: 'tester'}});
-      controller.find('.setup-setupnode-section 
.setup-password').simulate('change', {target: {value: 'testerpass'}});
-
-      assert.equal(Stores.setupStore.getBindAdressForSetupNode(), 
'192.168.13.42');
-      assert.equal(Stores.setupStore.getPortForSetupNode(), '1342');
-      assert.equal(Stores.setupStore.getUsername(), 'tester');
-      assert.equal(Stores.setupStore.getPassword(), 'testerpass');
-    });
-
-  });
-
-});
diff --git a/app/addons/setup/actions.js b/app/addons/setup/actions.js
new file mode 100644
index 000000000..95dd3525e
--- /dev/null
+++ b/app/addons/setup/actions.js
@@ -0,0 +1,310 @@
+// 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 FauxtonAPI from "../../core/api";
+import {isInvalid} from "./helpers";
+import {
+  SETUP_ADD_NODE_TO_LIST,
+  SETUP_BIND_ADDRESS_ADDITIONAL_NODE,
+  SETUP_BIND_ADDRESS_FOR_SINGLE_NODE,
+  SETUP_NODE_COUNT,
+  SETUP_PORT_ADDITIONAL_NODE,
+  SETUP_PORT_FOR_SINGLE_NODE,
+  SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE,
+  SETUP_RESET_ADDITIONAL_NODE,
+  SETUP_SET_CLUSTERSTATUS,
+  SETUP_SET_PASSWORD,
+  SETUP_SET_USERNAME
+} from "./actiontypes";
+import {get, post} from '../../core/ajax';
+import Api from "../auth/api";
+
+
+/**
+ * @typedef {Object} CredentialObject
+ * @param {string} username The username
+ * @param {string} password The password
+ */
+
+
+/**
+ * Public functions
+ */
+
+const showError = (msg, error) => {
+  const errorMsg = error ? ' Error:' + error : '';
+  FauxtonAPI.addNotification({
+    msg: msg + errorMsg,
+    type: 'error',
+    fade: false,
+    clear: true
+  });
+  return;
+};
+
+
+export const getClusterStateFromCouch = () => async dispatch => {
+  const baseUrl = FauxtonAPI.urls('cluster_setup', 'server');
+  const json = await get(baseUrl);
+  dispatch({
+    type: SETUP_SET_CLUSTERSTATUS,
+    options: {
+      state: json.state
+    }
+  });
+};
+
+export const finishClusterSetup = message => async() => {
+  const baseUrl = FauxtonAPI.urls('cluster_setup', 'server');
+  const body = {action: 'finish_cluster'};
+  try {
+    const response = await post(baseUrl, body, {raw: true});
+    if (response.ok) {
+      FauxtonAPI.addNotification({
+        msg: message,
+        type: 'success',
+        fade: false,
+        clear: true
+      });
+      FauxtonAPI.navigate('#setup/finish');
+    } else {
+      const json = await response.json();
+      showError('The cluster is already finished', json.reason);
+    }
+  } catch (err) {
+    showError('There was an error. Please check your setup and try again.', 
err);
+  }
+};
+
+export const setupSingleNode = (credentials, setupNode) => async() => {
+  const baseUrl = FauxtonAPI.urls('cluster_setup', 'server');
+  const setupAttrs = {
+    action: 'enable_single_node',
+    username: credentials.username,
+    password: credentials.password,
+    bind_address: setupNode.bindAddress,
+    port: setupNode.port,
+    singlenode: true
+  };
+
+  const attrsAreInvalid = isInvalid(setupAttrs);
+
+  if (attrsAreInvalid) {
+    return showError(attrsAreInvalid);
+  }
+
+  try {
+    const response = await post(baseUrl, setupAttrs, {raw: true});
+    if (!response.ok) {
+      const json = await response.json();
+      const error = json.reason ? json.reason : json.error;
+      return showError(error);
+    }
+
+    await Api.login({name: credentials.username, password: 
credentials.password});
+    FauxtonAPI.addNotification({
+      msg: 'Single node setup successful.',
+      type: 'success',
+      fade: false,
+      clear: true
+    });
+    FauxtonAPI.navigate('#setup/finish');
+  } catch (error) {
+    showError("The cluster has not been setuped successfully.", error);
+  }
+};
+
+/**
+ * Add a node to the current cluster configuration
+ * 1. Enable cluster for the current node
+ * 2. Enable cluster for the remote node
+ * 3. Add the remote node
+ * @param isOrWasAdminParty
+ * @param credentials
+ * @param setupNode
+ * @param additionalNode
+ */
+export const addNode = (isOrWasAdminParty, credentials, setupNode, 
additionalNode) => async dispatch => {
+  const baseUrl = FauxtonAPI.urls('cluster_setup', 'server');
+  const enableSetupData = {
+    action: 'enable_cluster',
+    username: credentials.username,
+    password: credentials.password,
+    bind_address: setupNode.bindAddress,
+    port: setupNode.port,
+    node_count: setupNode.nodeCount,
+    singlenode: false
+  };
+
+  const attrsAreInvalid = isInvalid({
+    username: credentials.username,
+    password: credentials.password,
+    ...setupNode
+  });
+
+  if (attrsAreInvalid) {
+    return showError(attrsAreInvalid);
+  }
+
+  let enableNodeData = {
+    action: 'enable_cluster',
+    username: credentials.username,
+    password: credentials.password,
+    bind_address: additionalNode.bindAddress,
+    port: additionalNode.port,
+    node_count: setupNode.nodeCount,
+    remote_node: additionalNode.remoteAddress,
+    remote_current_user: credentials.username,
+    remote_current_password: credentials.password
+  };
+
+  if (isOrWasAdminParty) {
+    delete enableNodeData.remote_current_user;
+    delete enableNodeData.remote_current_password;
+  }
+
+  const additionalNodeDataIsInvalid = isInvalid(enableNodeData);
+
+  if (additionalNodeDataIsInvalid) {
+    return showError(additionalNodeDataIsInvalid);
+  }
+
+  const continueSetup = async() => {
+    const addNodeData = {
+      action: 'add_node',
+      username: credentials.username,
+      password: credentials.password,
+      host: additionalNode.remoteAddress,
+      port: additionalNode.port,
+      singlenode: false
+    };
+
+    //Enable the remote node
+    const enableRemoteNodeResponse = await post(baseUrl, enableNodeData, {raw: 
true});
+
+    if (!enableRemoteNodeResponse.ok) {
+      const json = await enableRemoteNodeResponse.json();
+      const error = json.reason ? json.reason : json.error;
+      return showError(error);
+    }
+    const addNodeResponse = await post(baseUrl, addNodeData, {raw: true});
+
+
+    if (!addNodeResponse.ok) {
+      const json = await enableRemoteNodeResponse.json();
+      const error = json.reason ? json.reason : json.error;
+      return showError(error);
+    }
+
+    dispatch({
+      type: SETUP_ADD_NODE_TO_LIST,
+      options: {
+        value: {
+          port: additionalNode.port,
+          remoteAddress: additionalNode.remoteAddress
+        }
+      }
+    });
+    FauxtonAPI.addNotification({
+      msg: 'Added node',
+      type: 'success',
+      fade: false,
+      clear: true
+    });
+  };
+  try {
+    await post(baseUrl, enableSetupData, {raw: true});
+    await Api.login({name: credentials.username, password: 
credentials.password});
+    await continueSetup();
+  } catch (err) {
+    showError('An error occured while adding the node.', err);
+  }
+};
+
+export const resetAddtionalNodeForm = () => {
+  return {
+    type: SETUP_RESET_ADDITIONAL_NODE,
+  };
+};
+
+export const alterPortAdditionalNode = value => {
+  return {
+    type: SETUP_PORT_ADDITIONAL_NODE,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const alterRemoteAddressAdditionalNode = value => {
+  return {
+    type: SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const alterBindAddressAdditionalNode = value => {
+  return {
+    type: SETUP_BIND_ADDRESS_ADDITIONAL_NODE,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const setUsername = value => {
+  return {
+    type: SETUP_SET_USERNAME,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const setPassword = value => {
+  return {
+    type: SETUP_SET_PASSWORD,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const setPortForSetupNode = value => {
+  return {
+    type: SETUP_PORT_FOR_SINGLE_NODE,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const setBindAddressForSetupNode = value => {
+  return {
+    type: SETUP_BIND_ADDRESS_FOR_SINGLE_NODE,
+    options: {
+      value: value
+    }
+  };
+};
+
+export const setNodeCount = value => {
+  return {
+    type: SETUP_NODE_COUNT,
+    options: {
+      value: value
+    }
+  };
+};
+
+
diff --git a/app/addons/setup/actiontypes.js b/app/addons/setup/actiontypes.js
new file mode 100644
index 000000000..cf94fd9ba
--- /dev/null
+++ b/app/addons/setup/actiontypes.js
@@ -0,0 +1,23 @@
+// 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.
+
+export const SETUP_SET_CLUSTERSTATUS = 'SETUP_SET_CLUSTERSTATUS';
+export const SETUP_SET_USERNAME = 'SETUP_SET_USERNAME';
+export const SETUP_SET_PASSWORD = 'SETUP_SET_PASSWORD';
+export const SETUP_BIND_ADDRESS_FOR_SINGLE_NODE = 
'SETUP_BIND_ADDRESS_FOR_SINGLE_NODE';
+export const SETUP_PORT_FOR_SINGLE_NODE = 'SETUP_PORT_FOR_SINGLE_NODE';
+export const SETUP_PORT_ADDITIONAL_NODE = 'SETUP_PORT_ADDITIONAL_NODE';
+export const SETUP_BIND_ADDRESS_ADDITIONAL_NODE = 
'SETUP_BIND_ADDRESS_ADDITIONAL_NODE';
+export const SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE = 
'SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE';
+export const SETUP_RESET_ADDITIONAL_NODE = 'SETUP_RESET_ADDITIONAL_NODE';
+export const SETUP_ADD_NODE_TO_LIST = 'SETUP_ADD_NODE_TO_LIST';
+export const SETUP_NODE_COUNT = 'SETUP_NODE_COUNT';
diff --git a/app/addons/setup/base.js b/app/addons/setup/base.js
index 789e52b2a..a457c3fa8 100644
--- a/app/addons/setup/base.js
+++ b/app/addons/setup/base.js
@@ -11,7 +11,9 @@
 // the License.
 
 import FauxtonAPI from "../../core/api";
+import app from '../../app';
 import Setup from "./route";
+import reducers from './reducers';
 import "./assets/less/setup.less";
 Setup.initialize = function () {
   FauxtonAPI.addHeaderLink({
@@ -21,4 +23,14 @@ Setup.initialize = function () {
   });
 };
 
+FauxtonAPI.addReducers({
+  setup: reducers
+});
+
+FauxtonAPI.registerUrls('cluster_setup', {
+  server: () => app.host + '/_cluster_setup',
+  app: () => '/_cluster_setup',
+  apiurl: () => window.location.origin + "/_cluster_setup"
+});
+
 export default Setup;
diff --git a/app/addons/setup/components/ConfiguredScreen.js 
b/app/addons/setup/components/ConfiguredScreen.js
new file mode 100644
index 000000000..0e7700bd3
--- /dev/null
+++ b/app/addons/setup/components/ConfiguredScreen.js
@@ -0,0 +1,46 @@
+// 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 PropTypes from 'prop-types';
+import app from "../../../app";
+
+
+export default class ClusterConfiguredScreen extends React.Component {
+  getNodeType = () => {
+    const {clusterState} = this.props;
+    if (clusterState === 'cluster_finished') {
+      return 'clustered';
+    } else if (clusterState === 'single_node_enabled') {
+      return 'single';
+    }
+    return 'unknown state';
+
+  };
+
+  render() {
+    const nodetype = this.getNodeType();
+
+    return (
+      <div className="setup-screen">
+        {app.i18n.en_US['couchdb-productname']} is configured for production 
usage as a {nodetype} node!
+        <br/>
+        <br/>
+          Do you want to <a href="#replication">replicate data</a>?
+      </div>
+    );
+  }
+}
+
+ClusterConfiguredScreen.propTypes = {
+  clusterState: PropTypes.string
+};
diff --git a/app/addons/setup/components/CurrentAdminPassword.js 
b/app/addons/setup/components/CurrentAdminPassword.js
new file mode 100644
index 000000000..2860013f2
--- /dev/null
+++ b/app/addons/setup/components/CurrentAdminPassword.js
@@ -0,0 +1,54 @@
+// 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 PropTypes from 'prop-types';
+
+
+export default class SetupCurrentAdminPassword extends React.Component {
+  render() {
+    let text = 'Specify your Admin credentials';
+
+    if (this.props.isAdminParty) {
+      text = 'Create Admin credentials.';
+    }
+
+    return (
+      <div className="setup-creds">
+        <div>
+          <p>{text}</p>
+        </div>
+        <input
+          className="setup-username"
+          onChange={this.props.onAlterUsername}
+          placeholder="Username"
+          value={this.props.username}
+          type="text"/>
+        <input
+          className="setup-password"
+          onChange={this.props.onAlterPassword}
+          placeholder="Password"
+          value={this.props.password}
+          type="password"/>
+      </div>
+    );
+  }
+}
+
+SetupCurrentAdminPassword.propTypes = {
+  onAlterUsername: PropTypes.func.isRequired,
+  onAlterPassword: PropTypes.func.isRequired,
+  username: PropTypes.string.isRequired,
+  password: PropTypes.string.isRequired,
+  isAdminParty: PropTypes.bool
+};
diff --git a/app/addons/setup/components/FirstStepController.js 
b/app/addons/setup/components/FirstStepController.js
new file mode 100644
index 000000000..279f382a8
--- /dev/null
+++ b/app/addons/setup/components/FirstStepController.js
@@ -0,0 +1,71 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy 
of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations 
under
+// the License.
+import React from 'react';
+import app from "../../../app";
+import PropTypes from 'prop-types';
+import FauxtonAPI from "../../../core/api";
+import ClusterConfiguredScreen from "./ConfiguredScreen";
+import ReactComponents from "../../components/react-components";
+
+const ConfirmButton = ReactComponents.ConfirmButton;
+
+export default class FirstStepController extends React.Component {
+
+  UNSAFE_componentWillMount() {
+    this.props.getClusterState();
+  }
+
+  render() {
+    if (this.props.clusterState === 'cluster_finished' ||
+        this.props.clusterState === 'single_node_enabled') {
+      return (<ClusterConfiguredScreen {...this.props}/>);
+    }
+
+    return (
+      <div className="setup-screen">
+        <h2>Welcome to {app.i18n.en_US['couchdb-productname']}!</h2>
+        <p>
+            This wizard should be run directly on the node, rather than 
through a load-balancer.
+        </p>
+        <p>
+            You can configure a single node, or a multi-node CouchDB 
installation.
+        </p>
+        <div>
+          <ConfirmButton
+            onClick={this.redirectToMultiNodeSetup}
+            showIcon={false}
+            text="Configure a Cluster"/>
+          <ConfirmButton
+            onClick={this.redirectToSingleNodeSetup}
+            showIcon={false}
+            id="setup-btn-no-thanks"
+            text="Configure a Single Node"/>
+        </div>
+      </div>
+    );
+  }
+
+  redirectToSingleNodeSetup = (e) => {
+    e.preventDefault();
+    FauxtonAPI.navigate('#setup/singlenode');
+  };
+
+  redirectToMultiNodeSetup = (e) => {
+    e.preventDefault();
+    FauxtonAPI.navigate('#setup/multinode');
+  };
+
+}
+
+FirstStepController.propTypes = {
+  clusterState: PropTypes.string
+};
diff --git a/app/addons/setup/components/MultipleNodeController.js 
b/app/addons/setup/components/MultipleNodeController.js
new file mode 100644
index 000000000..defbdece0
--- /dev/null
+++ b/app/addons/setup/components/MultipleNodeController.js
@@ -0,0 +1,157 @@
+// 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 PropTypes from 'prop-types';
+import ReactComponents from "../../components/react-components";
+import CurrentAdminPassword from "./CurrentAdminPassword";
+import OptionalSettings from "./OptionalSettings";
+import NodeCountSetting from "./NodeCountSetting";
+
+import {getIsAdminParty} from '../reducers';
+
+
+const ConfirmButton = ReactComponents.ConfirmButton;
+
+export default class MultipleNodesController extends React.Component {
+
+  componentDidMount() {
+    this.isAdminParty = getIsAdminParty();
+  }
+
+  getNodeList = () => {
+    return this.props.nodeList.map(function (el, i) {
+      return (
+        <div key={i} className="node-item">
+          {el.remoteAddress}:{el.port}
+        </div>
+      );
+    }, this);
+  };
+
+  _addNode = () => {
+    const {username, password, setupNode, additionalNode} = this.props;
+    this.props.addNode(this.isAdminParty, {username, password}, setupNode, 
additionalNode);
+  };
+
+  _alterPortAdditionalNode = (e) => {
+    this.props.alterPortAdditionalNode(e.target.value);
+  };
+
+  _alterBindAddressAdditionalNode = (e) => {
+    this.props.alterBindAddressAdditionalNode(e.target.value);
+  };
+
+  _alterRemoteAddressAdditionalNode = (e) => {
+    this.props.alterRemoteAddressAdditionalNode(e.target.value);
+  };
+
+  _alterUsername = (e) => {
+    this.props.alterUsername(e.target.value);
+  };
+
+  _alterPassword = (e) => {
+    this.props.alterPassword(e.target.value);
+  };
+
+  _alterBindAddressSetupNode = (e) => {
+    this.props.alterBindAddressForSetupNode(e.target.value);
+  };
+
+  _alterPortSetupNode = (e) => {
+    this.props.alterPortForSetupNode(e.target.value);
+  };
+
+  _alterNodeCount = (e) => {
+    this.props.alterNodeCount(e.target.value);
+  };
+
+  _finishClusterSetup = () => {
+    this.props.finishClusterSetup('CouchDB Cluster set up!');
+  };
+
+  render() {
+    const {setupNode, additionalNode} = this.props;
+    return (
+      <div className="setup-nodes">
+          Setup your initial base-node, afterwards add the other nodes that 
you want to add
+        <div className="setup-setupnode-section">
+          <CurrentAdminPassword
+            {...this.props}
+            onAlterUsername={this._alterUsername}
+            onAlterPassword={this._alterPassword}/>
+
+          <OptionalSettings
+            {...this.props}
+            onAlterPort={this._alterPortSetupNode}
+            onAlterBindAddress={this._alterBindAddressSetupNode}
+            ip={setupNode.bindAddress}
+            port={setupNode.port}/>
+          <NodeCountSetting
+            {...this.props}
+            onAlterNodeCount={this._alterNodeCount}
+            nodeCount={setupNode.nodeCount}/>
+        </div>
+        <hr/>
+        <div className="setup-add-nodes-section">
+          <h2>Add Nodes to the Cluster</h2>
+          <p>Remote host</p>
+          <input
+            value={additionalNode.remoteAddress}
+            onChange={this._alterRemoteAddressAdditionalNode}
+            className="input-remote-node"
+            type="text"
+            placeholder="IP Address"/>
+          <OptionalSettings
+            {...this.props}
+            onAlterPort={this._alterPortAdditionalNode}
+            onAlterBindAddress={this._alterBindAddressAdditionalNode}
+            ip={additionalNode.bindAddress} port={additionalNode.port}/>
+
+          <div className="setup-add-button">
+            <ConfirmButton
+              onClick={this._addNode}
+              showIcon={false}
+              id="setup-btn-no-thanks"
+              text="Add Node"/>
+          </div>
+        </div>
+        <div className="setup-nodelist">
+          {this.getNodeList()}
+        </div>
+
+        <div className="centered setup-finish">
+          <ConfirmButton
+            onClick={this._finishClusterSetup}
+            showIcon={false}
+            text="Configure Cluster"/>
+        </div>
+      </div>
+    );
+  }
+}
+
+MultipleNodesController.propTypes = {
+  username: PropTypes.string.isRequired,
+  password: PropTypes.string.isRequired,
+  nodeList: PropTypes.array.isRequired,
+  isAdminParty: PropTypes.bool.isRequired,
+  setupNode: PropTypes.shape({
+    bindAddress: PropTypes.string.isRequired,
+    port: PropTypes.number.isRequired,
+    nodeCount: PropTypes.number.isRequired
+  }).isRequired,
+  additionalNode: PropTypes.shape({
+    bindAddress: PropTypes.string.isRequired,
+    port: PropTypes.number.isRequired,
+    remoteAddress: PropTypes.string.isRequired
+  }).isRequired
+};
diff --git a/app/addons/setup/components/NodeCountSetting.js 
b/app/addons/setup/components/NodeCountSetting.js
new file mode 100644
index 000000000..7be1e5a32
--- /dev/null
+++ b/app/addons/setup/components/NodeCountSetting.js
@@ -0,0 +1,41 @@
+// 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 PropTypes from 'prop-types';
+
+
+export default class NodeCountSetting extends React.Component {
+
+  handleNodeCountChange = (event) => {
+    this.props.onAlterNodeCount(event);
+  };
+
+  render() {
+    return (
+      <div className="setup-node-count">
+        <p>Number of nodes to be added to the cluster (including this one)</p>
+        <input
+          className="setup-input-nodecount"
+          value={this.props.nodeCount}
+          onChange={this.handleNodeCountChange}
+          placeholder="Value of cluster n"
+          type="text"/>
+      </div>
+    );
+  }
+}
+
+NodeCountSetting.propTypes = {
+  onAlterNodeCount: PropTypes.func.isRequired,
+  nodeCount: PropTypes.number.isRequired
+};
diff --git a/app/addons/setup/components/OptionalSettings.js 
b/app/addons/setup/components/OptionalSettings.js
new file mode 100644
index 000000000..1efebb9bf
--- /dev/null
+++ b/app/addons/setup/components/OptionalSettings.js
@@ -0,0 +1,55 @@
+// 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 PropTypes from 'prop-types';
+
+export default class OptionalSettings extends React.Component {
+  handleIpChange = (event) => {
+    this.props.onAlterBindAddress(event);
+  };
+
+  handlePortChange = (event) => {
+    this.props.onAlterPort(event);
+  };
+
+  render() {
+    return (
+      <div className="setup-opt-settings">
+        <p>Bind address the node will listen on</p>
+        <input
+          className="setup-input-ip"
+          value={this.props.ip}
+          onChange={this.handleIpChange}
+          placeholder="IP Address"
+          type="text"/>
+
+        <div className="setup-port">
+          <p>Port that the node will use</p>
+          <input
+            className="setup-input-port"
+            value={this.props.port}
+            onChange={this.handlePortChange}
+            type="text"/>
+        </div>
+      </div>
+    );
+  }
+}
+
+OptionalSettings.propTypes = {
+  ip: PropTypes.string.isRequired,
+  port: PropTypes.number.isRequired,
+  onAlterBindAddress: PropTypes.func.isRequired,
+  onAlterPort: PropTypes.func.isRequired
+};
diff --git a/app/addons/setup/components/SingleNodeController.js 
b/app/addons/setup/components/SingleNodeController.js
new file mode 100644
index 000000000..29cbcfa22
--- /dev/null
+++ b/app/addons/setup/components/SingleNodeController.js
@@ -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.
+import React from 'react';
+import PropTypes from 'prop-types';
+import ReactComponents from "../../components/react-components";
+import CurrentAdminPassword from "./CurrentAdminPassword";
+import OptionalSettings from "./OptionalSettings";
+
+const ConfirmButton = ReactComponents.ConfirmButton;
+
+export default class SingleNodeController extends React.Component {
+
+  constructor() {
+    super();
+    this.finishSingleNode = this.finishSingleNode.bind(this);
+    this.onChangeUsername = this.onChangeUsername.bind(this);
+    this.onChangePassword = this.onChangePassword.bind(this);
+    this.onChangeBindAddress = this.onChangeBindAddress.bind(this);
+    this.onChangePort = this.onChangePort.bind(this);
+  }
+
+  onChangeUsername = e => this.props.alterUsername(e.target.value);
+
+  onChangePassword = e => this.props.alterPassword(e.target.value);
+
+  onChangeBindAddress = e => this.props.alterBindAddress(e.target.value);
+
+  onChangePort = e => this.props.alterPort(e.target.value);
+
+  render() {
+    return (
+      <div className="setup-nodes">
+        <div className="setup-setupnode-section">
+          <CurrentAdminPassword
+            {...this.props}
+            onAlterUsername={this.onChangeUsername}
+            onAlterPassword={this.onChangePassword}/>
+          <OptionalSettings
+            {...this.props}
+            onAlterPort={this.onChangePort}
+            onAlterBindAddress={this.onChangeBindAddress}
+            ip={this.props.bindAddress}
+            port={this.props.port}/>
+          <ConfirmButton
+            {...this.props}
+            onClick={this.finishSingleNode}
+            text="Configure Node"/>
+        </div>
+      </div>
+    );
+  }
+
+  finishSingleNode = (e) => {
+    e.preventDefault();
+    const {username, password, port, bindAddress} = this.props;
+    const credentials = {username, password};
+    const setupNode = {
+      port,
+      bindAddress,
+    };
+    this.props.setupSingleNode(credentials, setupNode);
+  };
+}
+
+SingleNodeController.propTypes = {
+  username: PropTypes.string.isRequired,
+  password: PropTypes.string.isRequired,
+  port: PropTypes.number.isRequired,
+  bindAddress: PropTypes.string.isRequired,
+  isAdminParty: PropTypes.bool.isRequired
+};
diff --git a/app/addons/setup/container/ConfiguredSceenContainer.js 
b/app/addons/setup/container/ConfiguredSceenContainer.js
new file mode 100644
index 000000000..7f61f536f
--- /dev/null
+++ b/app/addons/setup/container/ConfiguredSceenContainer.js
@@ -0,0 +1,24 @@
+// 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 {connect} from 'react-redux';
+import ConfiguredScreen from '../components/ConfiguredScreen';
+import {getClusterState} from '../reducers';
+
+const mapStateToProps = ({setup}) => {
+  return {
+    clusterState: getClusterState(setup),
+  };
+};
+
+export default connect(
+  mapStateToProps
+)(ConfiguredScreen);
diff --git a/app/addons/setup/container/FirstStepContainer.js 
b/app/addons/setup/container/FirstStepContainer.js
new file mode 100644
index 000000000..2d8a4ee85
--- /dev/null
+++ b/app/addons/setup/container/FirstStepContainer.js
@@ -0,0 +1,35 @@
+// 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 {connect} from 'react-redux';
+import FirstStepController from '../components/FirstStepController';
+import {getClusterState} from '../reducers';
+import {getClusterStateFromCouch} from '../actions';
+
+const mapStateToProps = ({setup}) => {
+  return {
+    clusterState: getClusterState(setup),
+  };
+};
+
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    getClusterState() {
+      dispatch(getClusterStateFromCouch());
+    }
+  };
+};
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(FirstStepController);
diff --git a/app/addons/setup/container/MultipleNodeContainer.js 
b/app/addons/setup/container/MultipleNodeContainer.js
new file mode 100644
index 000000000..afa05e7b1
--- /dev/null
+++ b/app/addons/setup/container/MultipleNodeContainer.js
@@ -0,0 +1,71 @@
+// 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 {connect} from 'react-redux';
+import MultipleNodeController from '../components/MultipleNodeController';
+import {getNodeList, getIsAdminParty, getAdditionalNode, getUsername, 
getPassword, getSetupNode} from '../reducers';
+import {
+  addNode,
+  alterBindAddressAdditionalNode, alterPortAdditionalNode, 
alterRemoteAddressAdditionalNode, finishClusterSetup,
+  setBindAddressForSetupNode, setNodeCount, setPassword,
+  setPortForSetupNode, setUsername
+} from "../actions";
+
+const mapStateToProps = ({setup}) => {
+  return {
+    nodeList: getNodeList(setup),
+    isAdminParty: getIsAdminParty(setup),
+    setupNode: getSetupNode(setup),
+    username: getUsername(setup),
+    password: getPassword(setup),
+    additionalNode: getAdditionalNode(setup)
+  };
+};
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    addNode(isAdminParty, credentials, setupNode, additionalNode) {
+      dispatch(addNode(isAdminParty, credentials, setupNode, additionalNode));
+    },
+    alterPortAdditionalNode(port) {
+      dispatch(alterPortAdditionalNode(port));
+    },
+    alterBindAddressAdditionalNode(bindAddress) {
+      dispatch(alterBindAddressAdditionalNode(bindAddress));
+    },
+    alterRemoteAddressAdditionalNode(remoteAddress) {
+      dispatch(alterRemoteAddressAdditionalNode(remoteAddress));
+    },
+    alterUsername(username) {
+      dispatch(setUsername(username));
+    },
+    alterPassword(password) {
+      dispatch(setPassword(password));
+    },
+    alterBindAddressForSetupNode(bindAddress) {
+      dispatch(setBindAddressForSetupNode(bindAddress));
+    },
+    alterPortForSetupNode(port) {
+      dispatch(setPortForSetupNode(port));
+    },
+    finishClusterSetup(msg) {
+      dispatch(finishClusterSetup(msg));
+    },
+    alterNodeCount(nodeCount) {
+      dispatch(setNodeCount(nodeCount));
+    }
+  };
+};
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(MultipleNodeController);
diff --git a/app/addons/setup/container/SingleNodeContainer.js 
b/app/addons/setup/container/SingleNodeContainer.js
new file mode 100644
index 000000000..b70de4102
--- /dev/null
+++ b/app/addons/setup/container/SingleNodeContainer.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.
+import {connect} from 'react-redux';
+import SingleNodeController from '../components/SingleNodeController';
+import {
+  getIsAdminParty,
+  getBindAddressForSetupNode,
+  getPortForSetupNode,
+  getNodeList,
+  getPassword,
+  getSetupNode,
+  getUsername
+} from '../reducers';
+import {setBindAddressForSetupNode, setPassword, setPortForSetupNode, 
setupSingleNode, setUsername} from "../actions";
+
+const mapStateToProps = ({setup}) => {
+  return {
+    nodeList: getNodeList(setup),
+    isAdminParty: getIsAdminParty(setup),
+    setupNode: getSetupNode(setup),
+    username: getUsername(setup),
+    password: getPassword(setup),
+    port: getPortForSetupNode(setup),
+    bindAddress: getBindAddressForSetupNode(setup)
+  };
+};
+
+const mapDispatchToProps = dispatch => {
+  return {
+    alterUsername(username) {
+      dispatch(setUsername(username));
+    },
+    alterPassword(password) {
+      dispatch(setPassword(password));
+    },
+    alterBindAddress(bindAddress) {
+      dispatch(setBindAddressForSetupNode(bindAddress));
+    },
+    alterPort(port) {
+      dispatch(setPortForSetupNode(port));
+    },
+    setupSingleNode(credentials, setupNode) {
+      dispatch(setupSingleNode(credentials, setupNode));
+    }
+  };
+};
+
+export default connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(SingleNodeController);
diff --git a/app/addons/setup/helpers.js b/app/addons/setup/helpers.js
new file mode 100644
index 000000000..27fde8f71
--- /dev/null
+++ b/app/addons/setup/helpers.js
@@ -0,0 +1,40 @@
+// 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 _ from 'lodash';
+
+export function isInvalid(attrs) {
+  if (_.isEmpty(attrs.username)) {
+    return 'Admin name is required';
+  }
+
+  if (_.isEmpty(attrs.password)) {
+    return 'Admin password is required';
+  }
+
+  if (attrs.bind_address && attrs.bind_address === '127.0.0.1' &&
+        !attrs.singlenode) {
+    return 'Bind address can not be 127.0.0.1';
+  }
+
+  if (attrs.port && _.isNaN(+attrs.port)) {
+    return 'Bind port must be a number';
+  }
+
+  if (attrs.nodeCount && _.isNaN(+attrs.nodeCount)) {
+    return 'Node count must be a number';
+  }
+
+  if (attrs.nodeCount === 0 || attrs.nodeCount && attrs.nodeCount < 1) {
+    return 'Node count must be >= 1';
+  }
+  return false;
+}
diff --git a/app/addons/setup/reducers.js b/app/addons/setup/reducers.js
new file mode 100644
index 000000000..203949d43
--- /dev/null
+++ b/app/addons/setup/reducers.js
@@ -0,0 +1,130 @@
+// 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 {
+  SETUP_SET_CLUSTERSTATUS,
+  SETUP_SET_USERNAME,
+  SETUP_SET_PASSWORD,
+  SETUP_BIND_ADDRESS_FOR_SINGLE_NODE,
+  SETUP_PORT_FOR_SINGLE_NODE,
+  SETUP_PORT_ADDITIONAL_NODE,
+  SETUP_BIND_ADDRESS_ADDITIONAL_NODE,
+  SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE,
+  SETUP_ADD_NODE_TO_LIST,
+  SETUP_RESET_ADDITIONAL_NODE,
+  SETUP_NODE_COUNT
+} from './actiontypes';
+import FauxtonAPI from "../../core/api";
+import _ from "lodash";
+
+
+const initialState = {
+  clusterState: '',
+  username: '',
+  password: '',
+  setupNode: {
+    bindAddress: '0.0.0.0',
+    port: 5984,
+    nodeCount: 3
+  },
+  nodeList: [],
+  additionalNode: {
+    bindAddress: '0.0.0.0',
+    port: 5984,
+    remoteAddress: '127.0.0.1'
+  }
+};
+
+export default function setup(state = initialState, action) {
+  const {options, type} = action;
+  switch (type) {
+    case SETUP_SET_CLUSTERSTATUS:
+      return updateState(state, 'clusterState', options.state);
+    case SETUP_SET_USERNAME:
+      return updateState(state, 'username', options.value);
+    case SETUP_SET_PASSWORD:
+      return updateState(state, 'password', options.value);
+    case SETUP_BIND_ADDRESS_FOR_SINGLE_NODE:
+      return updateState(state, 'setupNode.bindAddress', options.value);
+    case SETUP_PORT_FOR_SINGLE_NODE:
+      return updateState(state, 'setupNode.port', options.value);
+    case SETUP_PORT_ADDITIONAL_NODE:
+      return updateState(state, 'additionalNode.port', 
parseInt(options.value));
+    case SETUP_BIND_ADDRESS_ADDITIONAL_NODE:
+      return updateState(state, 'additionalNode.bindAddress', options.value);
+    case SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE:
+      return updateState(state, 'additionalNode.remoteAddress', options.value);
+    case SETUP_ADD_NODE_TO_LIST:
+      let addNodeListState = getStateCopy(state);
+      addNodeListState.nodeList.push(options.value);
+      resetAdditionalNode(addNodeListState);
+      return addNodeListState;
+    case SETUP_RESET_ADDITIONAL_NODE:
+      return resetAdditionalNode(getStateCopy(state));
+    case SETUP_NODE_COUNT:
+      const nodeCount = Math.min(options.value, 3);
+      return updateState(state, 'setupNode.nodeCount', nodeCount);
+    default:
+      return state;
+  }
+}
+
+/**
+ * Manual nested copy of the state object.
+ * @param state The current state to copy.
+ * @returns {{setupNode: {}, additionalNode: {}}}
+ */
+export const getStateCopy = (state) => {
+  return {
+    ...state,
+    setupNode: {
+      ...state.setupNode
+    },
+    additionalNode: {
+      ...state.additionalNode
+    }
+  };
+};
+
+/**
+ * Update a particular value for a state
+ * @param state The state to update
+ * @param path The property path to update
+ * @param value The value to update
+ */
+const updateState = (state, path, value) => {
+  let statecopy = getStateCopy(state);
+  return _.set(statecopy, path, value);
+};
+
+/**
+ * Reset the current additionalNode state for the initial one.
+ * @param state The state to update
+ * @returns {*}
+ */
+const resetAdditionalNode = state => {
+  state.additionalNode = Object.assign({}, initialState.additionalNode);
+  return state;
+};
+
+export const getState = state => state;
+export const getClusterState = state => state.clusterState;
+export const getNodeList = state => state.nodeList;
+export const getIsAdminParty = () => FauxtonAPI.session.isAdminParty();
+export const getUsername = state => state.username;
+export const getPassword = state => state.password;
+export const getSetupNode = state => state.setupNode;
+export const getPortForSetupNode = state => state.setupNode.port;
+export const getBindAddressForSetupNode = state => state.setupNode.bindAddress;
+export const getNodeCountForSetupNode = state => state.setupNode.nodeCount;
+export const getAdditionalNode = state => state.additionalNode;
+export const getHostForSetupNode = () => '127.0.0.1';
diff --git a/app/addons/setup/resources.js b/app/addons/setup/resources.js
deleted file mode 100644
index 3f91a3e02..000000000
--- a/app/addons/setup/resources.js
+++ /dev/null
@@ -1,60 +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.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-
-var Setup = FauxtonAPI.addon();
-
-
-Setup.Model = Backbone.Model.extend({
-
-  documentation: app.host + '/_utils/docs',
-
-  url: function (context) {
-    if (context === "apiurl") {
-      return window.location.origin + "/_cluster_setup";
-    }
-    return '/_cluster_setup';
-
-  },
-
-  validate: function (attrs) {
-    if (!attrs.username) {
-      return 'Admin name is required';
-    }
-
-    if (!attrs.password) {
-      return 'Admin password is required';
-    }
-
-    if (attrs.bind_address && attrs.bind_address === '127.0.0.1' &&
-        !attrs.singlenode) {
-      return 'Bind address can not be 127.0.0.1';
-    }
-
-    if (attrs.port && _.isNaN(+attrs.port)) {
-      return 'Bind port must be a number';
-    }
-
-    if (attrs.nodeCount && _.isNaN(+attrs.nodeCount)) {
-      return 'Node count must be a number';
-    }
-
-    if (attrs.nodeCount && attrs.nodeCount < 1) {
-      return 'Node count must be >= 1';
-    }
-  }
-
-});
-
-export default Setup;
diff --git a/app/addons/setup/route.js b/app/addons/setup/route.js
index b8845bbb9..945f3e4a2 100644
--- a/app/addons/setup/route.js
+++ b/app/addons/setup/route.js
@@ -13,13 +13,15 @@
 import React from 'react';
 import app from "../../app";
 import FauxtonAPI from "../../core/api";
-import Setup from "./resources";
-import SetupComponents from "./setup";
-import SetupActions from "./setup.actions";
 import ClusterActions from "../cluster/cluster.actions";
 import {OnePaneSimpleLayout} from '../components/layouts';
 
-var RouteObject = FauxtonAPI.RouteObject.extend({
+import ConfiguredScreenContainer from './container/ConfiguredSceenContainer';
+import FirstStepContainer from './container/FirstStepContainer';
+import SingleNodeContainer from './container/SingleNodeContainer';
+import MultipleNodeContainer from './container/MultipleNodeContainer';
+
+const SetupRouteObject = FauxtonAPI.RouteObject.extend({
   roles: ['_admin'],
   selectedHeader: 'Setup',
 
@@ -30,61 +32,60 @@ var RouteObject = FauxtonAPI.RouteObject.extend({
     'setup/multinode': 'setupMultiNode'
   },
 
-  setupInitView: function () {
-    const setup = new Setup.Model();
+  setupInitView: () => {
+    const url = FauxtonAPI.urls('cluster_setup', 'apiurl');
     ClusterActions.fetchNodes();
-    SetupActions.getClusterStateFromCouch();
     return <OnePaneSimpleLayout
-      component={<SetupComponents.SetupFirstStepController/>}
-      endpoint={setup.url('apiurl')}
-      docURL={setup.documentation}
+      component={<FirstStepContainer />}
+      endpoint={url}
+      docURL={FauxtonAPI.constants.DOC_URLS.SETUP}
       crumbs={[
-        { 'name': 'Setup ' + app.i18n.en_US['couchdb-productname'] }
+        {'name': 'Setup ' + app.i18n.en_US['couchdb-productname']}
       ]}
     />;
   },
 
-  setupSingleNode: function () {
-    const setup = new Setup.Model();
+  setupSingleNode: () => {
+    const url = FauxtonAPI.urls('cluster_setup', 'apiurl');
     ClusterActions.fetchNodes();
     return <OnePaneSimpleLayout
-      component={<SetupComponents.SetupSingleNodeController/>}
-      endpoint={setup.url('apiurl')}
-      docURL={setup.documentation}
+      component={<SingleNodeContainer />}
+      endpoint={url}
+      docURL={FauxtonAPI.constants.DOC_URLS.SETUP}
       crumbs={[
-        { 'name': 'Setup ' + app.i18n.en_US['couchdb-productname'] }
+        {'name': 'Setup ' + app.i18n.en_US['couchdb-productname']}
       ]}
     />;
   },
 
-  setupMultiNode: function () {
-    const setup = new Setup.Model();
+  setupMultiNode: () => {
+    const url = FauxtonAPI.urls('cluster_setup', 'apiurl');
     ClusterActions.fetchNodes();
     return <OnePaneSimpleLayout
-      component={<SetupComponents.SetupMultipleNodesController/>}
-      endpoint={setup.url('apiurl')}
-      docURL={setup.documentation}
+      component={<MultipleNodeContainer />}
+      endpoint={url}
+      docURL={FauxtonAPI.constants.DOC_URLS.SETUP}
       crumbs={[
-        { 'name': 'Setup ' + app.i18n.en_US['couchdb-productname'] }
+        {'name': 'Setup ' + app.i18n.en_US['couchdb-productname']}
       ]}
     />;
   },
 
-  finishView: function () {
-    const setup = new Setup.Model();
-    SetupActions.getClusterStateFromCouch();
+  finishView: () => {
+    const url = FauxtonAPI.urls('cluster_setup', 'apiurl');
     return <OnePaneSimpleLayout
-      component={<SetupComponents.ClusterConfiguredScreen/>}
-      endpoint={setup.url('apiurl')}
-      docURL={setup.documentation}
+      component={<ConfiguredScreenContainer/>}
+      endpoint={url}
+      docURL={FauxtonAPI.constants.DOC_URLS.SETUP}
       crumbs={[
-        { 'name': 'Setup ' + app.i18n.en_US['couchdb-productname'] }
+        {'name': 'Setup ' + app.i18n.en_US['couchdb-productname']}
       ]}
     />;
   }
 });
 
-
-Setup.RouteObjects = [RouteObject];
+const Setup = {
+  RouteObjects: [SetupRouteObject]
+};
 
 export default Setup;
diff --git a/app/addons/setup/setup.actions.js 
b/app/addons/setup/setup.actions.js
deleted file mode 100644
index 9d5209aec..000000000
--- a/app/addons/setup/setup.actions.js
+++ /dev/null
@@ -1,287 +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.
-import FauxtonAPI from "../../core/api";
-import SetupResources from "./resources";
-import ActionTypes from "./setup.actiontypes";
-import SetupStores from "./setup.stores";
-import Api from "../auth/api";
-var setupStore = SetupStores.setupStore;
-
-export default {
-
-  getClusterStateFromCouch: function () {
-    var setupData = new SetupResources.Model();
-
-    setupData.fetch().then(function () {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.SETUP_SET_CLUSTERSTATUS,
-        options: {
-          state: setupData.get('state')
-        }
-      });
-    });
-  },
-
-  finishClusterSetup: function (message) {
-
-    $.ajax({
-      type: 'POST',
-      url: '/_cluster_setup',
-      contentType: 'application/json',
-      dataType: 'json',
-      data: JSON.stringify({
-        action: 'finish_cluster'
-      })
-    }).success(function () {
-      FauxtonAPI.addNotification({
-        msg: message,
-        type: 'success',
-        fade: false,
-        clear: true
-      });
-      FauxtonAPI.navigate('#setup/finish');
-    }).fail(function () {
-      FauxtonAPI.addNotification({
-        msg: 'There was an error. Please check your setup and try again.',
-        type: 'error',
-        fade: false,
-        clear: true
-      });
-    });
-
-  },
-
-  setupSingleNode: function () {
-    var username = setupStore.getUsername();
-    var password = setupStore.getPassword();
-
-    var setupModel = new SetupResources.Model({
-      action: 'enable_single_node',
-      username: username,
-      password: password,
-      bind_address: setupStore.getBindAdressForSetupNode(),
-      port: setupStore.getPortForSetupNode(),
-      singlenode: true
-    });
-
-    setupModel.on('invalid', function (model, error) {
-      FauxtonAPI.addNotification({
-        msg: error,
-        type: 'error',
-        fade: false,
-        clear: true
-      });
-    });
-
-    setupModel.save()
-      .then(function () {
-        return Api.login({name: username, password});
-      })
-      .then(function () {
-        FauxtonAPI.addNotification({
-          msg: 'Single node setup successful.',
-          type: 'success',
-          fade: false,
-          clear: true
-        });
-        FauxtonAPI.navigate('#setup/finish');
-      }.bind(this));
-  },
-
-  addNode: function (isOrWasAdminParty) {
-    var username = setupStore.getUsername();
-    var password = setupStore.getPassword();
-    var portForSetupNode = setupStore.getPortForSetupNode();
-    var bindAddressForSetupNode = setupStore.getBindAdressForSetupNode();
-    var nodeCountForSetupNode = setupStore.getNodeCountForSetupNode();
-
-    var bindAddressForAdditionalNode = 
setupStore.getAdditionalNode().bindAddress;
-    var remoteAddressForAdditionalNode = 
setupStore.getAdditionalNode().remoteAddress;
-    var portForForAdditionalNode = setupStore.getAdditionalNode().port;
-
-
-    var setupNode = new SetupResources.Model({
-      action: 'enable_cluster',
-      username: username,
-      password: password,
-      bind_address: bindAddressForSetupNode,
-      port: portForSetupNode,
-      node_count: nodeCountForSetupNode,
-      singlenode: false
-    });
-
-    setupNode.on('invalid', function (model, error) {
-      FauxtonAPI.addNotification({
-        msg: error,
-        type: 'error',
-        fade: false,
-        clear: true
-      });
-    });
-
-    var additionalNodeData = {
-      action: 'enable_cluster',
-      username: username,
-      password: password,
-      bind_address: bindAddressForAdditionalNode,
-      port: portForForAdditionalNode,
-      node_count: nodeCountForSetupNode,
-      remote_node: remoteAddressForAdditionalNode,
-      remote_current_user: username,
-      remote_current_password: password
-    };
-
-    if (isOrWasAdminParty) {
-      delete additionalNodeData.remote_current_user;
-      delete additionalNodeData.remote_current_password;
-    }
-
-    var additionalNode = new SetupResources.Model(additionalNodeData);
-
-    additionalNode.on('invalid', function (model, error) {
-      FauxtonAPI.addNotification({
-        msg: error,
-        type: 'error',
-        fade: false,
-        clear: true
-      });
-    });
-    setupNode
-      .save()
-      .always(function () {
-        Api.login({name: username, password}).then(function() {
-          continueSetup();
-        });
-      });
-
-    function continueSetup () {
-      var addNodeModel = new SetupResources.Model({
-        action: 'add_node',
-        username: username,
-        password: password,
-        host: remoteAddressForAdditionalNode,
-        port: portForForAdditionalNode,
-        singlenode: false
-      });
-
-      additionalNode
-        .save()
-        .then(function () {
-          return addNodeModel.save();
-        })
-        .then(function () {
-          FauxtonAPI.dispatch({
-            type: ActionTypes.SETUP_ADD_NODE_TO_LIST,
-            options: {
-              value: {
-                port: portForForAdditionalNode,
-                remoteAddress: remoteAddressForAdditionalNode
-              }
-            }
-          });
-          FauxtonAPI.addNotification({
-            msg: 'Added node',
-            type: 'success',
-            fade: false,
-            clear: true
-          });
-        })
-        .fail(function (xhr) {
-          var responseText = JSON.parse(xhr.responseText).reason;
-          FauxtonAPI.addNotification({
-            msg: 'Adding node failed: ' + responseText,
-            type: 'error',
-            fade: false,
-            clear: true
-          });
-        });
-    }
-  },
-
-  resetAddtionalNodeForm: function () {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_RESET_ADDITIONAL_NODE,
-    });
-  },
-
-  alterPortAdditionalNode: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_PORT_ADDITIONAL_NODE,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  alterRemoteAddressAdditionalNode: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  alterBindAddressAdditionalNode: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_BIND_ADDRESS_ADDITIONAL_NODE,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  setUsername: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_SET_USERNAME,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  setPassword: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_SET_PASSWORD,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  setPortForSetupNode: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_PORT_FOR_SINGLE_NODE,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  setBindAddressForSetupNode: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_BIND_ADDRESS_FOR_SINGLE_NODE,
-      options: {
-        value: value
-      }
-    });
-  },
-
-  setNodeCount: function (value) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.SETUP_NODE_COUNT,
-      options: {
-        value: value
-      }
-    });
-  }
-};
diff --git a/app/addons/setup/setup.actiontypes.js 
b/app/addons/setup/setup.actiontypes.js
deleted file mode 100644
index 0353cc2f2..000000000
--- a/app/addons/setup/setup.actiontypes.js
+++ /dev/null
@@ -1,25 +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.
-
-export default {
-  SETUP_SET_CLUSTERSTATUS: 'SETUP_SET_CLUSTERSTATUS',
-  SETUP_SET_USERNAME: 'SETUP_SET_USERNAME',
-  SETUP_SET_PASSWORD: 'SETUP_SET_PASSWORD',
-  SETUP_BIND_ADDRESS_FOR_SINGLE_NODE: 'SETUP_BIND_ADDRESS_FOR_SINGLE_NODE',
-  SETUP_PORT_FOR_SINGLE_NODE: 'SETUP_PORT_FOR_SINGLE_NODE',
-  SETUP_PORT_ADDITIONAL_NODE: 'SETUP_PORT_ADDITIONAL_NODE',
-  SETUP_BIND_ADDRESS_ADDITIONAL_NODE: 'SETUP_BIND_ADDRESS_ADDITIONAL_NODE',
-  SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE: 'SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE',
-  SETUP_RESET_ADDITIONAL_NODE: 'SETUP_RESET_ADDITIONAL_NODE',
-  SETUP_ADD_NODE_TO_LIST: 'SETUP_ADD_NODE_TO_LIST',
-  SETUP_NODE_COUNT: 'SETUP_NODE_COUNT',
-};
diff --git a/app/addons/setup/setup.js b/app/addons/setup/setup.js
deleted file mode 100644
index fe1a59cfb..000000000
--- a/app/addons/setup/setup.js
+++ /dev/null
@@ -1,419 +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.
-
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import React from "react";
-import ReactComponents from "../components/react-components";
-import SetupActions from "./setup.actions";
-import SetupStores from "./setup.stores";
-
-var setupStore = SetupStores.setupStore;
-var ConfirmButton = ReactComponents.ConfirmButton;
-
-
-class ClusterConfiguredScreen extends React.Component {
-  getStoreState = () => {
-    return {
-      clusterState: setupStore.getClusterState()
-    };
-  };
-
-  componentDidMount() {
-    setupStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    setupStore.off('change', this.onChange);
-  }
-
-  onChange = () => {
-    this.setState(this.getStoreState());
-  };
-
-  getNodeType = () => {
-    if (this.state.clusterState === 'cluster_finished') {
-      return 'clustered';
-    } else if (this.state.clusterState === 'single_node_enabled') {
-      return 'single';
-    }
-    return 'unknown state';
-
-  };
-
-  state = this.getStoreState();
-
-  render() {
-    var nodetype = this.getNodeType();
-
-    return (
-      <div className="setup-screen">
-        {app.i18n.en_US['couchdb-productname']} is configured for production 
usage as a {nodetype} node!
-        <br />
-        <br/>
-        Do you want to <a href="#replication">replicate data</a>?
-      </div>
-    );
-  }
-}
-
-class SetupCurrentAdminPassword extends React.Component {
-  render() {
-    var text = 'Specify your Admin credentials';
-
-    if (this.props.adminParty) {
-      text = 'Create Admin credentials.';
-    }
-
-    return (
-      <div className="setup-creds">
-        <div>
-          <p>{text}</p>
-        </div>
-        <input
-          className="setup-username"
-          onChange={this.props.onAlterUsername}
-          placeholder="Username"
-          type="text" />
-        <input
-          className="setup-password"
-          onChange={this.props.onAlterPassword}
-          placeholder="Password"
-          type="password" />
-      </div>
-    );
-  }
-}
-
-class SetupNodeCountSetting extends React.Component {
-  state = {
-    nodeCountValue: this.props.nodeCountValue
-  };
-
-  handleNodeCountChange = (event) => {
-    this.props.onAlterNodeCount(event);
-    this.setState({nodeCountValue: event.target.value});
-  };
-
-  render() {
-    return (
-      <div className="setup-node-count">
-        <p>Number of nodes to be added to the cluster (including this one)</p>
-        <input
-          className="setup-input-nodecount"
-          value={this.state.nodeCountValue}
-          onChange={this.handleNodeCountChange}
-          placeholder="Value of cluster n"
-          type="text" />
-      </div>
-    );
-  }
-}
-
-class SetupOptionalSettings extends React.Component {
-  state = {
-    ipValue: this.props.ipInitialValue,
-    portValue: this.props.portValue
-  };
-
-  handleIpChange = (event) => {
-    this.props.onAlterBindAddress(event);
-    this.setState({ipValue: event.target.value});
-  };
-
-  handlePortChange = (event) => {
-    this.props.onAlterPort(event);
-    this.setState({portValue: event.target.value});
-  };
-
-  render() {
-    return (
-      <div className="setup-opt-settings">
-        <p>Bind address the node will listen on</p>
-        <input
-          className="setup-input-ip"
-          value={this.state.ipValue}
-          onChange={this.handleIpChange}
-          placeholder="IP Address"
-          type="text" />
-
-        <div className="setup-port">
-          <p>Port that the node will use</p>
-          <input
-            className="setup-input-port"
-            value={this.state.portValue}
-            onChange={this.handlePortChange}
-            defaultValue="5984"
-            type="text" />
-        </div>
-      </div>
-    );
-  }
-}
-
-class SetupMultipleNodesController extends React.Component {
-  getStoreState = () => {
-    return {
-      nodeList: setupStore.getNodeList(),
-      isAdminParty: setupStore.getIsAdminParty(),
-      remoteAddress: setupStore.getAdditionalNode().remoteAddress
-    };
-  };
-
-  componentDidMount() {
-    this.isAdminParty = setupStore.getIsAdminParty();
-    setupStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    setupStore.off('change', this.onChange);
-  }
-
-  onChange = () => {
-    this.setState(this.getStoreState());
-  };
-
-  getNodeList = () => {
-    return this.state.nodeList.map(function (el, i) {
-      return (
-        <div key={i} className="node-item">
-          {el.remoteAddress}:{el.port}
-        </div>
-      );
-    }, this);
-  };
-
-  addNode = () => {
-    SetupActions.addNode(this.isAdminParty);
-  };
-
-  alterPortAdditionalNode = (e) => {
-    SetupActions.alterPortAdditionalNode(e.target.value);
-  };
-
-  alterBindAddressAdditionalNode = (e) => {
-    SetupActions.alterBindAddressAdditionalNode(e.target.value);
-  };
-
-  alterRemoteAddressAdditionalNode = (e) => {
-    SetupActions.alterRemoteAddressAdditionalNode(e.target.value);
-  };
-
-  alterUsername = (e) => {
-    SetupActions.setUsername(e.target.value);
-  };
-
-  alterPassword = (e) => {
-    SetupActions.setPassword(e.target.value);
-  };
-
-  alterBindAddressSetupNode = (e) => {
-    SetupActions.setBindAddressForSetupNode(e.target.value);
-  };
-
-  alterPortSetupNode = (e) => {
-    SetupActions.setPortForSetupNode(e.target.value);
-  };
-
-  alterNodeCount = (e) => {
-    SetupActions.setNodeCount(e.target.value);
-  };
-
-  finishClusterSetup = () => {
-    SetupActions.finishClusterSetup('CouchDB Cluster set up!');
-  };
-
-  state = this.getStoreState();
-
-  render() {
-
-    return (
-      <div className="setup-nodes">
-        Setup your initial base-node, afterwards add the other nodes that you 
want to add
-        <div className="setup-setupnode-section">
-          <SetupCurrentAdminPassword
-            onAlterUsername={this.alterUsername}
-            onAlterPassword={this.alterPassword}
-            adminParty={this.state.isAdminParty} />
-
-          <SetupOptionalSettings
-            onAlterPort={this.alterPortSetupNode}
-            onAlterBindAddress={this.alterBindAddressSetupNode} />
-          <SetupNodeCountSetting
-            onAlterNodeCount={this.alterNodeCount} />
-        </div>
-        <hr/>
-        <div className="setup-add-nodes-section">
-          <h2>Add Nodes to the Cluster</h2>
-          <p>Remote host</p>
-          <input
-            value={this.state.remoteAddress}
-            onChange={this.alterRemoteAddressAdditionalNode}
-            className="input-remote-node"
-            type="text"
-            placeholder="IP Address" />
-
-          <SetupOptionalSettings
-            onAlterPort={this.alterPortAdditionalNode}
-            onAlterBindAddress={this.alterBindAddressAdditionalNode} />
-
-          <div className="setup-add-button">
-            <ConfirmButton
-              onClick={this.addNode}
-              showIcon={false}
-              id="setup-btn-no-thanks"
-              text="Add Node" />
-          </div>
-        </div>
-        <div className="setup-nodelist">
-          {this.getNodeList()}
-        </div>
-
-        <div className="centered setup-finish">
-          <ConfirmButton onClick={this.finishClusterSetup} showIcon={false} 
text="Configure Cluster" />
-        </div>
-      </div>
-    );
-  }
-}
-
-class SetupSingleNodeController extends React.Component {
-  getStoreState = () => {
-    return {
-      isAdminParty: setupStore.getIsAdminParty()
-    };
-  };
-
-  componentDidMount() {
-    setupStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    setupStore.off('change', this.onChange);
-  }
-
-  onChange = () => {
-    this.setState(this.getStoreState());
-  };
-
-  alterUsername = (e) => {
-    SetupActions.setUsername(e.target.value);
-  };
-
-  alterPassword = (e) => {
-    SetupActions.setPassword(e.target.value);
-  };
-
-  alterBindAddress = (e) => {
-    SetupActions.setBindAddressForSetupNode(e.target.value);
-  };
-
-  alterPort = (e) => {
-    SetupActions.setPortForSetupNode(e.target.value);
-  };
-
-  render() {
-    return (
-      <div className="setup-nodes">
-        <div className="setup-setupnode-section">
-          <SetupCurrentAdminPassword
-            onAlterUsername={this.alterUsername}
-            onAlterPassword={this.alterPassword}
-            adminParty={this.state.isAdminParty} />
-          <SetupOptionalSettings
-            onAlterPort={this.alterPort}
-            onAlterBindAddress={this.alterBindAddress} />
-          <ConfirmButton
-            onClick={this.finishSingleNode}
-            text="Configure Node" />
-        </div>
-      </div>
-    );
-  }
-
-  finishSingleNode = (e) => {
-    e.preventDefault();
-    SetupActions.setupSingleNode();
-  };
-
-  state = this.getStoreState();
-}
-
-class SetupFirstStepController extends React.Component {
-  getStoreState = () => {
-    return {
-      clusterState: setupStore.getClusterState()
-    };
-  };
-
-  componentDidMount() {
-    setupStore.on('change', this.onChange, this);
-  }
-
-  componentWillUnmount() {
-    setupStore.off('change', this.onChange);
-  }
-
-  onChange = () => {
-    this.setState(this.getStoreState());
-  };
-
-  render() {
-    if (this.state.clusterState === 'cluster_finished' ||
-        this.state.clusterState === 'single_node_enabled') {
-      return (<ClusterConfiguredScreen />);
-    }
-
-    return (
-      <div className="setup-screen">
-        <h2>Welcome to {app.i18n.en_US['couchdb-productname']}!</h2>
-        <p>
-          This wizard should be run directly on the node, rather than through 
a load-balancer.
-        </p>
-        <p>
-          You can configure a single node, or a multi-node CouchDB 
installation.
-        </p>
-        <div>
-          <ConfirmButton
-            onClick={this.redirectToMultiNodeSetup}
-            showIcon={false}
-            text="Configure a Cluster" />
-          <ConfirmButton
-            onClick={this.redirectToSingleNodeSetup}
-            showIcon={false}
-            id="setup-btn-no-thanks"
-            text="Configure a Single Node" />
-        </div>
-      </div>
-    );
-  }
-
-  redirectToSingleNodeSetup = (e) => {
-    e.preventDefault();
-    FauxtonAPI.navigate('#setup/singlenode');
-  };
-
-  redirectToMultiNodeSetup = (e) => {
-    e.preventDefault();
-    FauxtonAPI.navigate('#setup/multinode');
-  };
-
-  state = this.getStoreState();
-}
-
-export default {
-  SetupMultipleNodesController: SetupMultipleNodesController,
-  SetupFirstStepController: SetupFirstStepController,
-  ClusterConfiguredScreen: ClusterConfiguredScreen,
-  SetupSingleNodeController: SetupSingleNodeController,
-  SetupOptionalSettings: SetupOptionalSettings
-};
diff --git a/app/addons/setup/setup.stores.js b/app/addons/setup/setup.stores.js
deleted file mode 100644
index 2092ab92f..000000000
--- a/app/addons/setup/setup.stores.js
+++ /dev/null
@@ -1,198 +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.
-
-import FauxtonAPI from "../../core/api";
-import ActionTypes from "./setup.actiontypes";
-
-var SetupStore = FauxtonAPI.Store.extend({
-
-  initialize: function () {
-    this.reset();
-  },
-
-  reset: function () {
-    this._clusterState = [];
-
-    this._username = '';
-    this._password = '';
-
-    this._setupNode = {
-      bindAddress: '0.0.0.0',
-      port: 5984,
-      nodeCount: 3
-    };
-
-    this.resetAddtionalNode();
-
-    this._nodeList = [];
-  },
-
-  resetAddtionalNode: function () {
-    this._additionalNode = {
-      bindAddress: '0.0.0.0',
-      port: 5984,
-      remoteAddress: '127.0.0.1'
-    };
-  },
-
-  setClusterState: function (options) {
-    this._clusterState = options.state;
-  },
-
-  getClusterState: function () {
-    return this._clusterState;
-  },
-
-  getNodeList: function () {
-    return this._nodeList;
-  },
-
-  getIsAdminParty: function () {
-    return FauxtonAPI.session.isAdminParty();
-  },
-
-  setUsername: function (options) {
-    this._username = options.value;
-  },
-
-  setPassword: function (options) {
-    this._password = options.value;
-  },
-
-  getUsername: function () {
-    return this._username;
-  },
-
-  getPassword: function () {
-    return this._password;
-  },
-
-  setBindAdressForSetupNode: function (options) {
-    this._setupNode.bindAddress = options.value;
-  },
-
-  setPortForSetupNode: function (options) {
-    this._setupNode.port = options.value;
-  },
-
-  getPortForSetupNode: function () {
-    return this._setupNode.port;
-  },
-
-  getBindAdressForSetupNode: function () {
-    return this._setupNode.bindAddress;
-  },
-
-  setNodeCountForSetupNode: function (options) {
-    this._setupNode.nodeCount = Math.min(options.value, 3);
-  },
-
-  getNodeCountForSetupNode: function () {
-    return this._setupNode.nodeCount;
-  },
-
-  setBindAdressForAdditionalNode: function (options) {
-    this._additionalNode.bindAddress = options.value;
-  },
-
-  setPortForAdditionalNode: function (options) {
-    this._additionalNode.port = options.value;
-  },
-
-  setRemoteAddressForAdditionalNode: function (options) {
-    this._additionalNode.remoteAddress = options.value;
-  },
-
-  getAdditionalNode: function () {
-    return this._additionalNode;
-  },
-
-  addNodeToList: function (options) {
-    this._nodeList.push(options.value);
-    this.resetAddtionalNode();
-  },
-
-  getHostForSetupNode: function () {
-    return '127.0.0.1';
-  },
-
-  dispatch: function (action) {
-
-    switch (action.type) {
-      case ActionTypes.SETUP_SET_CLUSTERSTATUS:
-        this.setClusterState(action.options);
-        break;
-
-      case ActionTypes.SETUP_SET_USERNAME:
-        this.setUsername(action.options);
-        break;
-
-      case ActionTypes.SETUP_SET_PASSWORD:
-        this.setPassword(action.options);
-        break;
-
-      case ActionTypes.SETUP_BIND_ADDRESS_FOR_SINGLE_NODE:
-        this.setBindAdressForSetupNode(action.options);
-        break;
-
-      case ActionTypes.SETUP_PORT_FOR_SINGLE_NODE:
-        this.setPortForSetupNode(action.options);
-        break;
-
-      case ActionTypes.SETUP_PORT_ADDITIONAL_NODE:
-        this.setPortForAdditionalNode(action.options);
-        break;
-
-      case ActionTypes.SETUP_BIND_ADDRESS_ADDITIONAL_NODE:
-        this.setBindAdressForAdditionalNode(action.options);
-        break;
-
-      case ActionTypes.SETUP_REMOTE_ADDRESS_ADDITIONAL_NODE:
-        this.setRemoteAddressForAdditionalNode(action.options);
-        break;
-
-      case ActionTypes.SETUP_ADD_NODE_TO_LIST:
-        this.addNodeToList(action.options);
-        break;
-
-      case ActionTypes.SETUP_RESET_ADDITIONAL_NODE:
-        this.resetAddtionalNode();
-        break;
-
-      case ActionTypes.SETUP_NODE_COUNT:
-        this.setNodeCountForSetupNode(action.options);
-        break;
-
-      default:
-        return;
-    }
-
-    //This is a quick and somewhat messy fix
-    //Some of the way our components are linked together can cause a component 
to be re-rendered
-    //even after it is unmounted.
-    // This fix stops that from happening
-    setTimeout(() => {
-      this.triggerChange();
-    });
-  }
-
-});
-
-
-var setupStore = new SetupStore();
-
-setupStore.dispatchToken = 
FauxtonAPI.dispatcher.register(setupStore.dispatch.bind(setupStore));
-
-export default {
-  setupStore: setupStore,
-  SetupStore: SetupStore
-};
diff --git a/app/constants.js b/app/constants.js
index ecb64e38b..5fd260b3c 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -46,6 +46,7 @@ export default {
     VIEWS: '/_utils/docs/intro/overview.html#views',
     MANGO_INDEX: '/_utils/docs/intro/api.html#documents',
     MANGO_SEARCH: '/_utils/docs/intro/api.html#documents',
+    SETUP: '/_utils/docs/cluster/setup.html#the-cluster-setup-wizard',
     CHANGES: 
'/_utils/docs/api/database/changes.html?highlight=changes#post--db-_changes'
   }
 };


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to