You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ac...@apache.org on 2018/06/25 13:14:58 UTC
[couchdb-fauxton] branch master updated: Migrate the CouchDB setup
code to Redux (#1045)
This is an automated email from the ASF dual-hosted git repository.
acote pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git
The following commit(s) were added to refs/heads/master by this push:
new d706c95 Migrate the CouchDB setup code to Redux (#1045)
d706c95 is described below
commit d706c9583ba0f731033a25b5a7d167c4b51e6c33
Author: Alexis Côté <po...@users.noreply.github.com>
AuthorDate: Mon Jun 25 09:14:55 2018 -0400
Migrate the CouchDB setup code to Redux (#1045)
---
.../components/queryoptions/MainFieldsView.js | 3 +-
app/addons/setup/__tests__/helpers.test.js | 70 ++++
app/addons/setup/__tests__/setup.test.js | 91 -----
app/addons/setup/__tests__/setupComponents.test.js | 75 ----
app/addons/setup/actions.js | 310 +++++++++++++++
app/addons/setup/actiontypes.js | 23 ++
app/addons/setup/base.js | 12 +
app/addons/setup/components/ConfiguredScreen.js | 46 +++
.../setup/components/CurrentAdminPassword.js | 54 +++
app/addons/setup/components/FirstStepController.js | 71 ++++
.../setup/components/MultipleNodeController.js | 157 ++++++++
app/addons/setup/components/NodeCountSetting.js | 41 ++
app/addons/setup/components/OptionalSettings.js | 55 +++
.../setup/components/SingleNodeController.js | 80 ++++
.../ConfiguredSceenContainer.js} | 20 +-
.../{base.js => container/FirstStepContainer.js} | 31 +-
.../setup/container/MultipleNodeContainer.js | 71 ++++
app/addons/setup/container/SingleNodeContainer.js | 60 +++
app/addons/setup/helpers.js | 40 ++
app/addons/setup/reducers.js | 130 +++++++
app/addons/setup/resources.js | 60 ---
app/addons/setup/route.js | 65 ++--
app/addons/setup/setup.actions.js | 287 --------------
app/addons/setup/setup.actiontypes.js | 25 --
app/addons/setup/setup.js | 419 ---------------------
app/addons/setup/setup.stores.js | 198 ----------
app/constants.js | 1 +
27 files changed, 1287 insertions(+), 1208 deletions(-)
diff --git a/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js
index a7873e4..f36f652 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 0000000..30b8bdb
--- /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 e22ed3a..0000000
--- 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 63aede2..0000000
--- 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 0000000..95dd352
--- /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 0000000..cf94fd9
--- /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 789e52b..a457c3f 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 0000000..0e7700b
--- /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 0000000..2860013
--- /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 0000000..279f382
--- /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 0000000..defbdec
--- /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 0000000..7be1e5a
--- /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 0000000..1efebb9
--- /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 0000000..29cbcfa
--- /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/base.js b/app/addons/setup/container/ConfiguredSceenContainer.js
similarity index 64%
copy from app/addons/setup/base.js
copy to app/addons/setup/container/ConfiguredSceenContainer.js
index 789e52b..7f61f53 100644
--- a/app/addons/setup/base.js
+++ b/app/addons/setup/container/ConfiguredSceenContainer.js
@@ -9,16 +9,16 @@
// 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';
-import FauxtonAPI from "../../core/api";
-import Setup from "./route";
-import "./assets/less/setup.less";
-Setup.initialize = function () {
- FauxtonAPI.addHeaderLink({
- title: 'Setup',
- href: "#/setup",
- icon: 'fonticon-wrench'
- });
+const mapStateToProps = ({setup}) => {
+ return {
+ clusterState: getClusterState(setup),
+ };
};
-export default Setup;
+export default connect(
+ mapStateToProps
+)(ConfiguredScreen);
diff --git a/app/addons/setup/base.js b/app/addons/setup/container/FirstStepContainer.js
similarity index 51%
copy from app/addons/setup/base.js
copy to app/addons/setup/container/FirstStepContainer.js
index 789e52b..2d8a4ee 100644
--- a/app/addons/setup/base.js
+++ b/app/addons/setup/container/FirstStepContainer.js
@@ -9,16 +9,27 @@
// 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';
-import FauxtonAPI from "../../core/api";
-import Setup from "./route";
-import "./assets/less/setup.less";
-Setup.initialize = function () {
- FauxtonAPI.addHeaderLink({
- title: 'Setup',
- href: "#/setup",
- icon: 'fonticon-wrench'
- });
+const mapStateToProps = ({setup}) => {
+ return {
+ clusterState: getClusterState(setup),
+ };
};
-export default 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 0000000..afa05e7
--- /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 0000000..b70de41
--- /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 0000000..27fde8f
--- /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 0000000..203949d
--- /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 3f91a3e..0000000
--- 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 b8845bb..945f3e4 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 9d5209a..0000000
--- 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 0353cc2..0000000
--- 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 fe1a59c..0000000
--- 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 2092ab9..0000000
--- 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 ecb64e3..5fd260b 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'
}
};