This is an automated email from the ASF dual-hosted git repository. amaranhao 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 05dc83a update cluster module to redux (#1133) 05dc83a is described below commit 05dc83a0c08fefad650d0fa7da36fdff529fa74f Author: garren smith <garren.sm...@gmail.com> AuthorDate: Wed Oct 10 16:38:58 2018 +0200 update cluster module to redux (#1133) * update cluster module to redux * add error messages --- app/addons/auth/actions.js | 10 ++-- app/addons/auth/components/changepasswordform.js | 15 +++--- app/addons/auth/components/createadminform.js | 12 +++-- app/addons/auth/routes/auth.js | 2 +- app/addons/auth/routes/user.js | 2 +- app/addons/cluster/__tests__/cluster.test.js | 14 ++---- app/addons/cluster/__tests__/resources.test.js | 56 --------------------- .../cluster/{cluster.actions.js => actions.js} | 46 ++++++++++------- .../{cluster.actiontypes.js => actiontypes.js} | 0 app/addons/cluster/{resources.js => api.js} | 46 ++++++++--------- app/addons/cluster/base.js | 6 +++ app/addons/cluster/cluster.js | 40 +++++---------- app/addons/cluster/cluster.stores.js | 57 ---------------------- .../{cluster.actiontypes.js => reducers.js} | 18 ++++++- app/addons/cluster/routes.js | 23 ++++----- app/addons/config/routes.js | 2 +- app/addons/setup/route.js | 2 +- app/constants.js | 3 +- 18 files changed, 123 insertions(+), 231 deletions(-) diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js index a58cd09..1e094c9 100644 --- a/app/addons/auth/actions.js +++ b/app/addons/auth/actions.js @@ -11,7 +11,6 @@ // the License. import FauxtonAPI from "../../core/api"; import app from "../../app"; -import ClusterStore from "../cluster/cluster.stores"; import ActionTypes from './actiontypes'; import Api from './api'; @@ -19,8 +18,6 @@ const { AUTH_HIDE_PASSWORD_MODAL, } = ActionTypes; -const nodesStore = ClusterStore.nodesStore; - const errorHandler = ({ message }) => { FauxtonAPI.addNotification({ msg: message, @@ -69,11 +66,10 @@ export const login = (username, password, urlBack) => { .catch(errorHandler); }; -export const changePassword = (username, password, passwordConfirm) => () => { +export const changePassword = (username, password, passwordConfirm, nodes) => () => { if (!validatePasswords(password, passwordConfirm)) { return errorHandler({message: app.i18n.en_US['auth-passwords-not-matching']}); } - var nodes = nodesStore.getNodes(); //To change an admin's password is the same as creating an admin. So we just use the //same api function call here. Api.createAdmin({ @@ -90,8 +86,8 @@ export const changePassword = (username, password, passwordConfirm) => () => { ); }; -export const createAdmin = (username, password, loginAfter) => () => { - const node = nodesStore.getNodes()[0].node; +export const createAdmin = (username, password, loginAfter, nodes) => () => { + const node = nodes[0].node; if (!validateUser(username, password)) { return errorHandler({message: app.i18n.en_US['auth-missing-credentials']}); } diff --git a/app/addons/auth/components/changepasswordform.js b/app/addons/auth/components/changepasswordform.js index 7ca0b8f..14943d7 100644 --- a/app/addons/auth/components/changepasswordform.js +++ b/app/addons/auth/components/changepasswordform.js @@ -43,7 +43,7 @@ export class ChangePasswordForm extends React.Component { changePassword(e) { e.preventDefault(); - this.props.changePassword(this.props.username, this.state.password, this.state.passwordConfirm); + this.props.changePassword(this.props.username, this.state.password, this.state.passwordConfirm, this.props.nodes); } render() { @@ -87,11 +87,14 @@ export class ChangePasswordForm extends React.Component { } } +const mapStateToProps = ({clusters}) => { + return { + nodes: clusters.nodes, + username: FauxtonAPI.session.user().name + }; +}; + export default connect( - () => { - return { - username: FauxtonAPI.session.user().name - }; - }, + mapStateToProps, {changePassword} )(ChangePasswordForm); diff --git a/app/addons/auth/components/createadminform.js b/app/addons/auth/components/createadminform.js index f92049b..333b419 100644 --- a/app/addons/auth/components/createadminform.js +++ b/app/addons/auth/components/createadminform.js @@ -13,7 +13,6 @@ import PropTypes from 'prop-types'; import React from "react"; -import ReactDOM from "react-dom"; import { createAdmin } from "./../actions"; @@ -45,7 +44,8 @@ export class CreateAdminForm extends React.Component { this.props.createAdmin( this.state.username, this.state.password, - this.props.loginAfter + this.props.loginAfter, + this.props.nodes ); } @@ -111,8 +111,14 @@ CreateAdminForm.defaultProps = { loginAfter: false }; +const mapStateToProps = ({clusters}) => { + return { + nodes: clusters.nodes + }; +}; + export default connect( - null, + mapStateToProps, {createAdmin} )(CreateAdminForm); diff --git a/app/addons/auth/routes/auth.js b/app/addons/auth/routes/auth.js index d2ebd64..d274c08 100644 --- a/app/addons/auth/routes/auth.js +++ b/app/addons/auth/routes/auth.js @@ -12,7 +12,7 @@ import React from "react"; import FauxtonAPI from "../../../core/api"; -import ClusterActions from "../../cluster/cluster.actions"; +import ClusterActions from "../../cluster/actions"; import { AuthLayout } from "./../layout"; import app from "../../../app"; import Components from "./../components"; diff --git a/app/addons/auth/routes/user.js b/app/addons/auth/routes/user.js index 1934b44..36a8f53 100644 --- a/app/addons/auth/routes/user.js +++ b/app/addons/auth/routes/user.js @@ -12,7 +12,7 @@ import React from "react"; import FauxtonAPI from "../../../core/api"; -import ClusterActions from "../../cluster/cluster.actions"; +import ClusterActions from "../../cluster/actions"; import { AdminLayout } from "./../layout"; export default FauxtonAPI.RouteObject.extend({ diff --git a/app/addons/cluster/__tests__/cluster.test.js b/app/addons/cluster/__tests__/cluster.test.js index dd4e313..d215e99 100644 --- a/app/addons/cluster/__tests__/cluster.test.js +++ b/app/addons/cluster/__tests__/cluster.test.js @@ -9,13 +9,13 @@ // 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 ClusterComponent from "../cluster"; -import ClusterActions from "../cluster.actions"; -import ClusterStores from "../cluster.stores"; +import FauxtonAPI from "../../../core/api"; +import {DisabledConfigController} from "../cluster"; import utils from "../../../../test/mocha/testUtils"; import React from "react"; import ReactDOM from "react-dom"; import {mount} from 'enzyme'; +import sinon from 'sinon'; const assert = utils.assert; @@ -23,6 +23,7 @@ describe('Cluster Controller', () => { let controller; beforeEach(() => { + FauxtonAPI.reduxDispatch = sinon.stub(); var nodeList = [ {'node': 'node1@127.0.0.1', 'isInCluster': true}, @@ -33,16 +34,11 @@ describe('Cluster Controller', () => { {'node': 'node3@127.0.0.1', 'isInCluster': false} ]; - ClusterActions.updateNodes({nodes: nodeList}); controller = mount( - <ClusterComponent.DisabledConfigController /> + <DisabledConfigController nodes={nodeList} /> ); }); - afterEach(() => { - ClusterStores.nodesStore.reset(); - }); - it('renders the amount of nodes', () => { assert.ok(/6 nodes/.test(controller.text()), 'finds 6 nodes'); }); diff --git a/app/addons/cluster/__tests__/resources.test.js b/app/addons/cluster/__tests__/resources.test.js deleted file mode 100644 index 4dd3774..0000000 --- a/app/addons/cluster/__tests__/resources.test.js +++ /dev/null @@ -1,56 +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 testUtils from "../../../../test/mocha/testUtils"; -import Resources from "../resources"; -var assert = testUtils.assert; - - -describe('Membership Model', () => { - const data = { - 'all_nodes': ['node1@127.0.0.1', 'node2@127.0.0.1', 'node3@127.0.0.1', 'notpartofclusternode'], - 'cluster_nodes': ['node1@127.0.0.1', 'node2@127.0.0.1', 'node3@127.0.0.1'] - }; - - it('reorders the data', () => { - const memberships = new Resources.ClusterNodes(); - const res = memberships.parse(data); - - assert.deepEqual([ - {node: 'node1@127.0.0.1', isInCluster: true}, - {node: 'node2@127.0.0.1', isInCluster: true}, - {node: 'node3@127.0.0.1', isInCluster: true}, - {node: 'notpartofclusternode', isInCluster: false} - ], - res.nodes_mapped); - }); - - it('keeps the exiting data', () => { - const memberships = new Resources.ClusterNodes(); - const res = memberships.parse(data); - - assert.deepEqual([ - 'node1@127.0.0.1', - 'node2@127.0.0.1', - 'node3@127.0.0.1', - 'notpartofclusternode' - ], - res.all_nodes); - - assert.deepEqual([ - 'node1@127.0.0.1', - 'node2@127.0.0.1', - 'node3@127.0.0.1' - ], - res.cluster_nodes); - }); -}); diff --git a/app/addons/cluster/cluster.actions.js b/app/addons/cluster/actions.js similarity index 52% rename from app/addons/cluster/cluster.actions.js rename to app/addons/cluster/actions.js index b0ca2a5..60ec0c9 100644 --- a/app/addons/cluster/cluster.actions.js +++ b/app/addons/cluster/actions.js @@ -11,38 +11,48 @@ // the License. import FauxtonAPI from "../../core/api"; -import ClusterResources from "./resources"; -import ActionTypes from "./cluster.actiontypes"; +import getNodes from "./api"; +import ActionTypes from "./actiontypes"; export default { - fetchNodes: function () { - var memberships = new ClusterResources.ClusterNodes(); - - memberships.fetch().then(() => { + fetchNodes () { + getNodes().then((nodes) => { this.updateNodes({ - nodes: memberships.get('nodes_mapped') + nodes: nodes.nodes_mapped + }); + }) + .catch(err => { + FauxtonAPI.addNotification({ + type: 'error', + msg: err.message, + clear: true + }); }); - }); }, - updateNodes: function (options) { - FauxtonAPI.dispatch({ + updateNodes (options) { + FauxtonAPI.reduxDispatch({ type: ActionTypes.CLUSTER_FETCH_NODES, options: options }); }, - navigateToNodeBasedOnNodeCount: function (successtarget) { - var memberships = new ClusterResources.ClusterNodes(); + navigateToNodeBasedOnNodeCount (successtarget) { + getNodes().then((nodes) => { + const allNodes = nodes.all_nodes; - memberships.fetch().then(function () { - const nodes = memberships.get('all_nodes'); - - if (nodes.length === 1) { - return FauxtonAPI.navigate(successtarget + nodes[0]); + if (allNodes.length === 1) { + return FauxtonAPI.navigate(successtarget + allNodes[0]); } return FauxtonAPI.navigate('/cluster/disabled', {trigger: true}); - }); + }) + .catch(err => { + FauxtonAPI.addNotification({ + type: 'error', + msg: err.message, + clear: true + }); + }); } }; diff --git a/app/addons/cluster/cluster.actiontypes.js b/app/addons/cluster/actiontypes.js similarity index 100% copy from app/addons/cluster/cluster.actiontypes.js copy to app/addons/cluster/actiontypes.js diff --git a/app/addons/cluster/resources.js b/app/addons/cluster/api.js similarity index 51% rename from app/addons/cluster/resources.js rename to app/addons/cluster/api.js index 95b7ccb..a6c05a1 100644 --- a/app/addons/cluster/resources.js +++ b/app/addons/cluster/api.js @@ -10,29 +10,25 @@ // License for the specific language governing permissions and limitations under // the License. -import FauxtonAPI from "../../core/api"; import Helpers from "../../helpers"; - -var Cluster = FauxtonAPI.addon(); - -Cluster.ClusterNodes = Backbone.Model.extend({ - url: function () { - return Helpers.getServerUrl('/_membership'); - }, - - parse: function (res) { - var list; - - list = res.all_nodes.reduce(function (acc, node) { - var isInCluster = res.cluster_nodes.indexOf(node) !== -1; - - acc.push({node: node, isInCluster: isInCluster}); - return acc; - }, []); - - res.nodes_mapped = list; - return res; - } -}); - -export default Cluster; +import {get} from '../../core/ajax'; + +export default () => { + return get(Helpers.getServerUrl('/_membership')) + .then(res => { + if (!res.all_nodes) { + const details = res.reason ? res.reason : ''; + throw new Error('Failed to load list of nodes.' + details); + } + + const list = res.all_nodes.reduce(function (acc, node) { + var isInCluster = res.cluster_nodes.indexOf(node) !== -1; + + acc.push({node: node, isInCluster: isInCluster}); + return acc; + }, []); + + res.nodes_mapped = list; + return res; + }); +}; diff --git a/app/addons/cluster/base.js b/app/addons/cluster/base.js index 338334d..d61ca99 100644 --- a/app/addons/cluster/base.js +++ b/app/addons/cluster/base.js @@ -11,7 +11,13 @@ // the License. import Cluster from "./routes"; +import FauxtonAPI from "../../core/api"; +import reducers from './reducers'; Cluster.initialize = function () {}; +FauxtonAPI.addReducers({ + clusters: reducers +}); + export default Cluster; diff --git a/app/addons/cluster/cluster.js b/app/addons/cluster/cluster.js index 625e866..573e37a 100644 --- a/app/addons/cluster/cluster.js +++ b/app/addons/cluster/cluster.js @@ -11,32 +11,9 @@ // the License. import React from "react"; -import ClusterStore from "./cluster.stores"; - -var nodesStore = ClusterStore.nodesStore; - - -class DisabledConfigController extends React.Component { - getStoreState = () => { - return { - nodes: nodesStore.getNodes() - }; - }; - - componentDidMount() { - nodesStore.on('change', this.onChange, this); - } - - componentWillUnmount() { - nodesStore.off('change', this.onChange); - } - - onChange = () => { - this.setState(this.getStoreState()); - }; - - state = this.getStoreState(); +import {connect} from 'react-redux'; +export class DisabledConfigController extends React.Component { render() { return ( <div className="config-warning-cluster-wrapper"> @@ -45,7 +22,7 @@ class DisabledConfigController extends React.Component { <div className="config-warning-icon-container pull-left"> <i className="fonticon-attention-circled"></i> </div> - It seems that you are running a cluster with {this.state.nodes.length} nodes. For CouchDB 2.0 + It seems that you are running a cluster with {this.props.nodes.length} nodes. For CouchDB 2.0 we recommend using a configuration management tools like Chef, Ansible, Puppet or Salt (in no particular order) to configure your nodes in a cluster. <br/> @@ -61,8 +38,13 @@ class DisabledConfigController extends React.Component { } } -var Views = { - DisabledConfigController: DisabledConfigController +const mapStateToProps = ({clusters}) => { + return { + nodes: clusters.nodes + }; }; -export default Views; +export default connect( + mapStateToProps, + null, +)(DisabledConfigController); diff --git a/app/addons/cluster/cluster.stores.js b/app/addons/cluster/cluster.stores.js deleted file mode 100644 index 27d4557..0000000 --- a/app/addons/cluster/cluster.stores.js +++ /dev/null @@ -1,57 +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 "./cluster.actiontypes"; - -var NodesStore = FauxtonAPI.Store.extend({ - - initialize: function () { - this.reset(); - }, - - reset: function () { - this._nodes = []; - }, - - setNodes: function (options) { - this._nodes = options.nodes; - }, - - getNodes: function () { - return this._nodes; - }, - - dispatch: function (action) { - - switch (action.type) { - case ActionTypes.CLUSTER_FETCH_NODES: - this.setNodes(action.options); - break; - - default: - return; - } - - this.triggerChange(); - } - -}); - - -var nodesStore = new NodesStore(); - -nodesStore.dispatchToken = FauxtonAPI.dispatcher.register(nodesStore.dispatch.bind(nodesStore)); - -export default { - nodesStore: nodesStore -}; diff --git a/app/addons/cluster/cluster.actiontypes.js b/app/addons/cluster/reducers.js similarity index 65% rename from app/addons/cluster/cluster.actiontypes.js rename to app/addons/cluster/reducers.js index 525e7f6..8a48c7a 100644 --- a/app/addons/cluster/cluster.actiontypes.js +++ b/app/addons/cluster/reducers.js @@ -10,6 +10,20 @@ // License for the specific language governing permissions and limitations under // the License. -export default { - CLUSTER_FETCH_NODES: 'CLUSTER_FETCH_NODES' +import ActionTypes from "./actiontypes"; + +const initialState = { + nodes: [] +}; + +export default (state = initialState, {type, options}) => { + switch (type) { + case ActionTypes.CLUSTER_FETCH_NODES: + return { + ...state, + nodes: options.nodes + }; + } + + return state; }; diff --git a/app/addons/cluster/routes.js b/app/addons/cluster/routes.js index 339360f..37ed665 100644 --- a/app/addons/cluster/routes.js +++ b/app/addons/cluster/routes.js @@ -12,9 +12,9 @@ import React from 'react'; import FauxtonAPI from "../../core/api"; -import Cluster from "./resources"; -import ClusterComponents from "./cluster"; -import ClusterActions from "./cluster.actions"; +import Helpers from "../../helpers"; +import DisabledConfigController from "./cluster"; +import ClusterActions from "./actions"; import {OnePaneSimpleLayout} from '../components/layouts'; @@ -25,17 +25,12 @@ var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({ 'cluster/disabled': 'showDisabledFeatureScreen' }, - apiUrl: function () { - return [this.memberships.url('apiurl'), this.memberships.documentation]; - }, - showDisabledFeatureScreen: function () { - const memberships = new Cluster.ClusterNodes(); ClusterActions.fetchNodes(); return <OnePaneSimpleLayout - component={<ClusterComponents.DisabledConfigController/>} - endpoint={memberships.url('apiurl')} - docURL={memberships.documentation} + component={<DisabledConfigController/>} + endpoint={Helpers.getServerUrl('/_membership')} + docURL={FauxtonAPI.constants.DOC_URLS.MEMBERSHIP} crumbs={[ { name: 'Config disabled' } ]} @@ -43,6 +38,6 @@ var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({ } }); -Cluster.RouteObjects = [ConfigDisabledRouteObject]; - -export default Cluster; +export default { + RouteObjects: [ConfigDisabledRouteObject] +}; diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js index 4b924d4..4a21749 100644 --- a/app/addons/config/routes.js +++ b/app/addons/config/routes.js @@ -13,7 +13,7 @@ import React from 'react'; import FauxtonAPI from "../../core/api"; import Config from "./resources"; -import ClusterActions from "../cluster/cluster.actions"; +import ClusterActions from "../cluster/actions"; import ConfigActions from "./actions"; import Layout from './layout'; diff --git a/app/addons/setup/route.js b/app/addons/setup/route.js index 945f3e4..38752a1 100644 --- a/app/addons/setup/route.js +++ b/app/addons/setup/route.js @@ -13,7 +13,7 @@ import React from 'react'; import app from "../../app"; import FauxtonAPI from "../../core/api"; -import ClusterActions from "../cluster/cluster.actions"; +import ClusterActions from "../cluster/actions"; import {OnePaneSimpleLayout} from '../components/layouts'; import ConfiguredScreenContainer from './container/ConfiguredSceenContainer'; diff --git a/app/constants.js b/app/constants.js index 66fdc2e..7f02640 100644 --- a/app/constants.js +++ b/app/constants.js @@ -46,6 +46,7 @@ export default { MANGO_INDEX:'./docs/intro/api.html#documents', MANGO_SEARCH:'./docs/intro/api.html#documents', CHANGES:'./docs/api/database/changes.html?highlight=changes#post--db-_changes', - SETUP: './docs/cluster/setup.html#the-cluster-setup-wizard' + SETUP: './docs/cluster/setup.html#the-cluster-setup-wizard', + MEMBERSHIP: './docs/cluster/nodes.html' } };