You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ro...@apache.org on 2016/09/13 12:31:09 UTC
[1/2] fauxton commit: updated refs/heads/master to 4f633e5
Repository: couchdb-fauxton
Updated Branches:
refs/heads/master e51775b8a -> 4f633e508
react: port config section to react
PR: #750
PR-URL: https://github.com/apache/couchdb-fauxton/pull/750
Reviewed-By: Robert Kowalski <ro...@kowalski.gd>
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/ee1a92b0
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/ee1a92b0
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/ee1a92b0
Branch: refs/heads/master
Commit: ee1a92b02e68d92dd86e5979d5d8f2d100fb24e7
Parents: e51775b
Author: samk <sa...@gmail.com>
Authored: Sun Jul 24 21:55:11 2016 +1000
Committer: Robert Kowalski <ro...@apache.org>
Committed: Tue Sep 13 14:22:41 2016 +0200
----------------------------------------------------------------------
app/addons/config/actions.js | 121 ++++++
app/addons/config/actiontypes.js | 27 ++
app/addons/config/assets/less/config.less | 107 ++++-
app/addons/config/components.react.jsx | 398 +++++++++++++++++++
app/addons/config/resources.js | 67 +---
app/addons/config/routes.js | 16 +-
app/addons/config/stores.js | 149 +++++++
.../config/templates/add_config_option.html | 26 --
app/addons/config/templates/dashboard.html | 24 --
app/addons/config/templates/header.html | 15 -
app/addons/config/templates/item.html | 40 --
app/addons/config/tests/actionsSpec.js | 193 +++++++++
.../config/tests/componentsSpec.react.jsx | 306 ++++++++++++++
app/addons/config/tests/configSpec.js | 162 --------
app/addons/config/tests/storesSpec.js | 94 +++++
app/addons/config/views.js | 229 -----------
devserver.js | 4 +-
tasks/helper.js | 2 +-
webpack.config.dev.js | 4 +-
19 files changed, 1411 insertions(+), 573 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/actions.js
----------------------------------------------------------------------
diff --git a/app/addons/config/actions.js b/app/addons/config/actions.js
new file mode 100644
index 0000000..00cfc23
--- /dev/null
+++ b/app/addons/config/actions.js
@@ -0,0 +1,121 @@
+// 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 ActionTypes from './actiontypes';
+import FauxtonAPI from '../../core/api';
+import Resources from './resources';
+
+export default {
+ fetchAndEditConfig (node) {
+ FauxtonAPI.dispatch({ type: ActionTypes.LOADING_CONFIG });
+
+ var configModel = new Resources.ConfigModel({ node });
+
+ configModel.fetch().then(() => this.editSections({ sections: configModel.get('sections'), node }));
+ },
+
+ editSections (options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.EDIT_CONFIG, options });
+ },
+
+ editOption (options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.EDIT_OPTION, options });
+ },
+
+ cancelEdit (options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.CANCEL_EDIT, options });
+ },
+
+ saveOption (node, options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.SAVING_OPTION, options });
+
+ var modelAttrs = options;
+ modelAttrs.node = node;
+ var optionModel = new Resources.OptionModel(modelAttrs);
+
+ optionModel.save()
+ .then(() => this.optionSaveSuccess(options))
+ .fail(xhr => this.optionSaveFailure(options, JSON.parse(xhr.responseText).reason));
+ },
+
+ optionSaveSuccess (options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.OPTION_SAVE_SUCCESS, options });
+ FauxtonAPI.addNotification({
+ msg: `Option ${options.optionName} saved`,
+ type: 'success'
+ });
+ },
+
+ optionSaveFailure (options, error) {
+ FauxtonAPI.dispatch({ type: ActionTypes.OPTION_SAVE_FAILURE, options });
+ FauxtonAPI.addNotification({
+ msg: `Option save failed: ${error}`,
+ type: 'error'
+ });
+ },
+
+ addOption (node, options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.ADDING_OPTION });
+
+ var modelAttrs = options;
+ modelAttrs.node = node;
+ var optionModel = new Resources.OptionModel(modelAttrs);
+
+ optionModel.save()
+ .then(() => this.optionAddSuccess(options))
+ .fail(xhr => this.optionAddFailure(options, JSON.parse(xhr.responseText).reason));
+ },
+
+ optionAddSuccess (options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.OPTION_ADD_SUCCESS, options });
+ FauxtonAPI.addNotification({
+ msg: `Option ${options.optionName} added`,
+ type: 'success'
+ });
+ },
+
+ optionAddFailure (options, error) {
+ FauxtonAPI.dispatch({ type: ActionTypes.OPTION_ADD_FAILURE, options });
+ FauxtonAPI.addNotification({
+ msg: `Option add failed: ${error}`,
+ type: 'error'
+ });
+ },
+
+ deleteOption (node, options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.DELETING_OPTION, options });
+
+ var modelAttrs = options;
+ modelAttrs.node = node;
+ var optionModel = new Resources.OptionModel(modelAttrs);
+
+ optionModel.destroy()
+ .then(() => this.optionDeleteSuccess(options))
+ .fail((xhr) => this.optionDeleteFailure(options, JSON.parse(xhr.responseText).reason));
+ },
+
+ optionDeleteSuccess (options) {
+ FauxtonAPI.dispatch({ type: ActionTypes.OPTION_DELETE_SUCCESS, options });
+ FauxtonAPI.addNotification({
+ msg: `Option ${options.optionName} deleted`,
+ type: 'success'
+ });
+ },
+
+ optionDeleteFailure (options, error) {
+ FauxtonAPI.dispatch({ type: ActionTypes.OPTION_DELETE_FAILURE, options });
+ FauxtonAPI.addNotification({
+ msg: `Option delete failed: ${error}`,
+ type: 'error'
+ });
+ }
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/config/actiontypes.js b/app/addons/config/actiontypes.js
new file mode 100644
index 0000000..9391ac3
--- /dev/null
+++ b/app/addons/config/actiontypes.js
@@ -0,0 +1,27 @@
+// 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 {
+ EDIT_CONFIG: 'EDIT_CONFIG',
+ LOADING_CONFIG: 'LOADING_CONFIG',
+ EDIT_OPTION: 'EDIT_OPTION',
+ CANCEL_EDIT: 'CANCEL_EDIT',
+ SAVING_OPTION: 'SAVING_OPTION',
+ OPTION_SAVE_SUCCESS: 'OPTION_SAVE_SUCCESS',
+ OPTION_SAVE_FAILURE: 'OPTION_SAVE_FAILURE',
+ DELETING_OPTION: 'DELETING_OPTION',
+ OPTION_DELETE_SUCCESS: 'OPTION_DELETE_SUCCESS',
+ OPTION_DELETE_FAILURE: 'OPTION_DELETE_FAILURE',
+ ADDING_OPTION: 'ADDING_OPTION',
+ OPTION_ADD_SUCCESS: 'OPTION_ADD_SUCCESS',
+ OPTION_ADD_FAILURE: 'OPTION_ADD_FAILURE'
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/assets/less/config.less
----------------------------------------------------------------------
diff --git a/app/addons/config/assets/less/config.less b/app/addons/config/assets/less/config.less
index cd57171..f74b2ec 100644
--- a/app/addons/config/assets/less/config.less
+++ b/app/addons/config/assets/less/config.less
@@ -9,31 +9,55 @@
// 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 "../../../../../assets/less/variables.less";
@import "../../../../../assets/less/bootstrap/mixins.less";
-
-
.config-item {
height: 65px;
- .js-value-input {
+ .config-value-form {
+ button {
+ margin-left: 8px;
+ }
+ }
+
+ .config-value-input {
width: 80%;
margin: 0;
}
- .js-show-value,
- .js-delete-value {
+
+ .config-show-value,
+ .config-delete-value {
cursor: pointer;
}
- .js-hidden {
- display: none;
- }
+
.text-center {
text-align: center;
}
button { width: 7%;}
+
+ transition: background-color 100ms;
+}
+
+.config-item:hover {
+ background-color: #e0e0e0;
+}
+
+.table-striped tbody > tr.config-item:nth-child(odd) > td,
+.table-striped tbody > tr.config-item:nth-child(odd) > th {
+ transition: background-color 100ms;
+}
+
+.table-striped tbody > tr.config-item:nth-child(odd):hover > td,
+.table-striped tbody > tr.config-item:nth-child(odd):hover > th {
+ background-color: #e7e7e7;
+}
+
+.config-item-deleting {
+ text-decoration: line-through;
+ text-decoration-color: red;
}
#config-trash {
@@ -55,9 +79,14 @@ table.config {
}
}
-#add-new-section {
+#add-option-button {
line-height: 40px;
cursor: pointer;
+ border-left: 1px solid #ccc;
+ border-right: none;
+ border-top: none;
+ border-bottom: none;
+ padding: 12px 14px;
&:hover {
transition: none;
@@ -67,8 +96,7 @@ table.config {
}
#add-section-button {
- border-left: 1px solid #ccc;
- padding: 12px 14px;
+
}
.add-section-tray {
@@ -112,6 +140,61 @@ table.config {
}
}
+#add-option-popover {
+ background-color: #333;
+ color: white;
+ padding: 20px;
+ border-radius: 0;
+ width: 300px;
+ max-width: none;
+ display: block;
+
+ .popover-content {
+ padding: 0;
+ }
+
+ .popover-title {
+ font-size: 16px;
+ padding: 0;
+ margin: 10px 0;
+ border: none;
+ display: block;
+ text-align: left;
+ color: #fff;
+ text-shadow: none;
+ height: auto;
+ line-height: 1em;
+ background-color: #333;
+ }
+
+ input {
+ width: 100%;
+ }
+
+ a.btn {
+ color: white;
+ background-color: @linkColor;
+ line-height: 1.5em;
+ border: 0;
+ padding: 10px 10px 9px;
+ font-size: 14px;
+ .border-radius(5px);
+
+ &:hover {
+ background-color: #cbcbcb;
+ color: white;
+ }
+ }
+}
+
+.popover.bottom .arrow, .popover.bottom .arrow::after {
+ border-bottom-color: #333 !important;
+}
+
+#add-option-popover::before {
+ display: none;
+}
+
.config-warning-cluster-wrapper {
.config-warning-cluster-container {
margin-left: -50px;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/config/components.react.jsx b/app/addons/config/components.react.jsx
new file mode 100644
index 0000000..a825282
--- /dev/null
+++ b/app/addons/config/components.react.jsx
@@ -0,0 +1,398 @@
+// 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 ReactDOM from "react-dom";
+import Stores from "./stores";
+import Actions from "./actions";
+import {Overlay, Button, Popover} from "react-bootstrap";
+import Components from "../components/react-components.react";
+import FauxtonComponents from "../fauxton/components.react";
+
+const configStore = Stores.configStore;
+
+var ConfigTableController = React.createClass({
+ getStoreState () {
+ return {
+ options: configStore.getOptions(),
+ loading: configStore.isLoading()
+ };
+ },
+
+ getInitialState () {
+ return this.getStoreState();
+ },
+
+ componentDidMount () {
+ configStore.on('change', this.onChange, this);
+ },
+
+ componentWillUnmount () {
+ configStore.off('change', this.onChange, this);
+ },
+
+ onChange () {
+ if (this.isMounted()) {
+ this.setState(this.getStoreState());
+ }
+ },
+
+ saveOption (option) {
+ Actions.saveOption(this.props.node, option);
+ },
+
+ deleteOption (option) {
+ Actions.deleteOption(this.props.node, option);
+ },
+
+ editOption (option) {
+ Actions.editOption(option);
+ },
+
+ cancelEdit () {
+ Actions.cancelEdit();
+ },
+
+ render () {
+ if (this.state.loading) {
+ return (
+ <div className="view">
+ <Components.LoadLines />
+ </div>
+ );
+ } else {
+ return (
+ <ConfigTable
+ onDeleteOption={this.deleteOption}
+ onSaveOption={this.saveOption}
+ onEditOption={this.editOption}
+ onCancelEdit={this.cancelEdit}
+ options={this.state.options}/>
+ );
+ }
+ }
+});
+
+var ConfigTable = React.createClass({
+ createOptions () {
+ return _.map(this.props.options, (option) => (
+ <ConfigOption
+ option={option}
+ onDelete={this.props.onDeleteOption}
+ onSave={this.props.onSaveOption}
+ onEdit={this.props.onEditOption}
+ onCancelEdit={this.props.onCancelEdit}
+ key={`${option.sectionName}/${option.optionName}`}
+ />
+ ));
+ },
+
+ render () {
+ var options = this.createOptions();
+
+ return (
+ <table className="config table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th id="config-section" width="22%">Section</th>
+ <th id="config-option" width="22%">Option</th>
+ <th id="config-value">Value</th>
+ <th id="config-trash"></th>
+ </tr>
+ </thead>
+ <tbody>
+ {options}
+ </tbody>
+ </table>
+ );
+ }
+});
+
+var ConfigOption = React.createClass({
+ onSave (value) {
+ var option = this.props.option;
+ option.value = value;
+ this.props.onSave(option);
+ },
+
+ onDelete () {
+ this.props.onDelete(this.props.option);
+ },
+
+ onEdit () {
+ this.props.onEdit(this.props.option);
+ },
+
+ render () {
+ return (
+ <tr className="config-item">
+ <th>{this.props.option.header && this.props.option.sectionName}</th>
+ <td>{this.props.option.optionName}</td>
+ <ConfigOptionValue
+ value={this.props.option.value}
+ editing={this.props.option.editing}
+ onSave={this.onSave}
+ onEdit={this.onEdit}
+ onCancelEdit={this.props.onCancelEdit}
+ />
+ <ConfigOptionTrash
+ optionName={this.props.option.optionName}
+ sectionName={this.props.option.sectionName}
+ onDelete={this.onDelete}/>
+ </tr>
+ );
+ }
+});
+
+var ConfigOptionValue = React.createClass({
+ getInitialState () {
+ return {
+ value: this.props.value,
+ editing: this.props.editing,
+ saving: this.props.saving
+ };
+ },
+
+ getDefaultProps () {
+ return {
+ value: '',
+ editing: false,
+ saving: false,
+ onSave: () => null,
+ onEdit: () => null,
+ onCancelEdit: () => null
+ };
+ },
+
+ componentWillReceiveProps (nextProps) {
+ if (this.props.value !== nextProps.value) {
+ this.setState({ saving: false });
+ }
+ },
+
+ onChange (event) {
+ this.setState({ value: event.target.value });
+ },
+
+ onSave () {
+ if (this.state.value !== this.props.value) {
+ this.setState({ saving: true });
+ this.props.onSave(this.state.value);
+ } else {
+ this.props.onCancelEdit();
+ }
+ },
+
+ getButtons () {
+ if (this.state.saving) {
+ return null;
+ } else {
+ return (
+ <span>
+ <button
+ className="btn btn-success fonticon-ok-circled btn-small btn-config-save"
+ onClick={this.onSave}
+ />
+ <button
+ className="btn fonticon-cancel-circled btn-small btn-config-cancel"
+ onClick={this.props.onCancelEdit}
+ />
+ </span>
+ );
+ }
+ },
+
+ render () {
+ if (this.props.editing) {
+ return (
+ <td>
+ <div className="config-value-form">
+ <input
+ onChange={this.onChange}
+ defaultValue={this.props.value}
+ disabled={this.state.saving}
+ autoFocus type="text" className="config-value-input"
+ />
+ {this.getButtons()}
+ </div>
+ </td>
+ );
+ } else {
+ return (
+ <td className="config-show-value" onClick={this.props.onEdit}>
+ {this.props.value}
+ </td>
+ );
+ }
+ }
+});
+
+var ConfigOptionTrash = React.createClass({
+ getInitialState () {
+ return {
+ show: false
+ };
+ },
+
+ onDelete () {
+ this.props.onDelete();
+ },
+
+ showModal () {
+ this.setState({ show: true });
+ },
+
+ hideModal () {
+ this.setState({ hide: false });
+ },
+
+ render () {
+ return (
+ <td className="text-center config-item-trash config-delete-value"
+ onClick={this.showModal}>
+ <i className="icon icon-trash"></i>
+ <FauxtonComponents.ConfirmationModal
+ text={`Are you sure you want to delete ${this.props.sectionName}/${this.props.optionName}?`}
+ onClose={this.hideModal}
+ onSubmit={this.onDelete}
+ visible={this.state.show}/>
+ </td>
+ );
+ }
+});
+
+var AddOptionController = React.createClass({
+ addOption (option) {
+ Actions.addOption(this.props.node, option);
+ },
+
+ render () {
+ return (
+ <AddOptionButton onAdd={this.addOption}/>
+ );
+ }
+});
+
+var AddOptionButton = React.createClass({
+ getInitialState () {
+ return {
+ sectionName: '',
+ optionName: '',
+ value: '',
+ show: false
+ };
+ },
+
+ isInputValid () {
+ if (this.state.sectionName !== ''
+ && this.state.optionName !== ''
+ && this.state.value !== '') {
+ return true;
+ }
+
+ return false;
+ },
+
+ updateSectionName (event) {
+ this.setState({ sectionName: event.target.value });
+ },
+
+ updateOptionName (event) {
+ this.setState({ optionName: event.target.value });
+ },
+
+ updateValue (event) {
+ this.setState({ value: event.target.value });
+ },
+
+ reset () {
+ this.setState(this.getInitialState());
+ },
+
+ onAdd () {
+ if (this.isInputValid()) {
+ var option = {
+ sectionName: this.state.sectionName,
+ optionName: this.state.optionName,
+ value: this.state.value
+ };
+
+ this.setState({ show: false });
+ this.props.onAdd(option);
+ }
+ },
+
+ togglePopover () {
+ this.setState({ show: !this.state.show });
+ },
+
+ hidePopover () {
+ this.setState({ show: false });
+ },
+
+ getPopover () {
+ return (
+ <Popover className="tray" id="add-option-popover" title="Add Option">
+ <input
+ className="input-section-name"
+ onChange={this.updateSectionName}
+ type="text" name="section" placeholder="Section" autocomplete="off" autoFocus/>
+ <input
+ className="input-option-name"
+ onChange={this.updateOptionName}
+ type="text" name="name" placeholder="Name"/>
+ <input
+ className="input-value"
+ onChange={this.updateValue}
+ type="text" name="value" placeholder="Value"/>
+ <a
+ className="btn btn-create"
+ onClick={this.onAdd}>
+ Create
+ </a>
+ </Popover>
+ );
+ },
+
+ render () {
+ return (
+ <div id="add-option-panel">
+ <Button
+ id="add-option-button"
+ onClick={this.togglePopover}
+ ref="target">
+ <i className="icon icon-plus header-icon"></i>
+ Add Option
+ </Button>
+
+ <Overlay
+ show={this.state.show}
+ onHide={this.hidePopover}
+ placement="bottom"
+ rootClose={true}
+ target={() => ReactDOM.findDOMNode(this.refs.target)}>
+ {this.getPopover()}
+ </Overlay>
+ </div>
+ );
+ }
+});
+
+export default {
+ ConfigTableController: ConfigTableController,
+ ConfigTable: ConfigTable,
+ ConfigOption: ConfigOption,
+ ConfigOptionValue: ConfigOptionValue,
+ ConfigOptionTrash: ConfigOptionTrash,
+ AddOptionController: AddOptionController,
+ AddOptionButton: AddOptionButton
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/resources.js
----------------------------------------------------------------------
diff --git a/app/addons/config/resources.js b/app/addons/config/resources.js
index 87eb8c3..f010c0d 100644
--- a/app/addons/config/resources.js
+++ b/app/addons/config/resources.js
@@ -15,26 +15,21 @@ import FauxtonAPI from "../../core/api";
var Config = FauxtonAPI.addon();
-
Config.OptionModel = Backbone.Model.extend({
documentation: FauxtonAPI.constants.DOC_URLS.CONFIG,
- initialize: function (_, options) {
- this.node = options.node;
- },
-
- url: function () {
- if (!this.node) {
+ url () {
+ if (!this.get('node')) {
throw new Error('no node set');
}
- return app.host + '/_node/' + this.node + '/_config/' +
- this.get('section') + '/' + encodeURIComponent(this.get('name'));
+ return app.host + '/_node/' + this.get('node') + '/_config/' +
+ this.get('sectionName') + '/' + encodeURIComponent(this.get('optionName'));
},
- isNew: function () { return false; },
+ isNew () { return false; },
- sync: function (method, model, options) {
+ sync (method, model, options) {
var params = {
url: model.url(),
@@ -52,58 +47,20 @@ Config.OptionModel = Backbone.Model.extend({
}
});
-Config.Model = Backbone.Model.extend({});
-Config.Collection = Backbone.Collection.extend({
- model: Config.Model,
-
+Config.ConfigModel = Backbone.Model.extend({
documentation: FauxtonAPI.constants.DOC_URLS.CONFIG,
- initialize: function (_, options) {
- this.node = options.node;
- },
-
- comparator: function (OptionModel) {
- if (OptionModel.get('section')) {
- return OptionModel.get('section');
- }
- },
-
- url: function () {
- if (!this.node) {
+ url () {
+ if (!this.get('node')) {
throw new Error('no node set');
}
- return app.host + '/_node/' + this.node + '/_config';
+ return app.host + '/_node/' + this.get('node') + '/_config';
},
- findEntryInSection: function (sectionName, entry) {
- var section = _.findWhere(this.toJSON(), {'section': sectionName}),
- options;
-
- if (!section) {
- return false;
- }
-
- options = _.findWhere(section.options, {name: entry});
-
- return options;
- },
-
- parse: function (resp) {
- return _.map(resp, function (section, section_name) {
- return {
- section: section_name,
- options: _.map(section, function (option, option_name) {
- return {
- name: option_name,
- value: option
- };
- })
- };
- });
+ parse (resp) {
+ return { sections: resp };
}
});
-
-
export default Config;
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/routes.js
----------------------------------------------------------------------
diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js
index 7d9f210..bbdfe6a 100644
--- a/app/addons/config/routes.js
+++ b/app/addons/config/routes.js
@@ -17,6 +17,8 @@ import Views from "./views";
import CORSComponents from "../cors/components.react";
import CORSActions from "../cors/actions";
import ClusterActions from "../cluster/cluster.actions";
+import ConfigComponents from "./components.react";
+import ConfigActions from "./actions";
var ConfigDisabledRouteObject = FauxtonAPI.RouteObject.extend({
@@ -58,7 +60,7 @@ var ConfigPerNodeRouteObject = FauxtonAPI.RouteObject.extend({
initialize: function (_a, _b, options) {
var node = options[0];
- this.configs = new Config.Collection(null, {node: node});
+ this.configs = new Config.ConfigModel({ node: node });
this.sidebar = this.setView('#sidebar-content', new Views.Tabs({
sidebarItems: [
@@ -76,15 +78,17 @@ var ConfigPerNodeRouteObject = FauxtonAPI.RouteObject.extend({
}));
},
- configForNode: function () {
- this.newSection = this.setView('#right-header', new Views.ConfigHeader({ collection: this.configs }));
- this.setView('#dashboard-lower-content', new Views.Table({ collection: this.configs }));
+ configForNode: function (node) {
+ this.removeComponents();
+ this.setComponent('#right-header', ConfigComponents.AddOptionController, { node });
+ this.setComponent('#dashboard-lower-content', ConfigComponents.ConfigTableController, { node });
+ ConfigActions.fetchAndEditConfig(node);
this.sidebar.setSelectedTab('main');
},
configCorsForNode: function (node) {
- this.removeView('#right-header');
- this.newSection = this.setComponent('#dashboard-content', CORSComponents.CORSController);
+ this.removeComponents();
+ this.setComponent('#dashboard-lower-content', CORSComponents.CORSController);
CORSActions.fetchAndEditCors(node);
this.sidebar.setSelectedTab('cors');
}
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/stores.js
----------------------------------------------------------------------
diff --git a/app/addons/config/stores.js b/app/addons/config/stores.js
new file mode 100644
index 0000000..1c112d8
--- /dev/null
+++ b/app/addons/config/stores.js
@@ -0,0 +1,149 @@
+// 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 './actiontypes';
+
+var ConfigStore = FauxtonAPI.Store.extend({
+ initialize () {
+ this.reset();
+ },
+
+ reset () {
+ this._sections = {};
+ this._loading = true;
+ this._editSectionName = null;
+ this._editOptionName = null;
+ },
+
+ editConfig (sections) {
+ this._sections = sections;
+ this._loading = false;
+ this._editSectionName = null;
+ this._editOptionName = null;
+ },
+
+ getOptions () {
+ var sections = _.sortBy(
+ _.map(this._sections, function (section, sectionName) {
+ return {
+ sectionName,
+ options: this.mapSection(section, sectionName)
+ };
+ }.bind(this)),
+ s => s.sectionName
+ );
+
+ return _.flatten(_.map(sections, s => s.options));
+ },
+
+ mapSection (section, sectionName) {
+ var options = _.sortBy(
+ _.map(section, (value, optionName) => ({
+ editing: this.isEditing(sectionName, optionName),
+ sectionName, optionName, value
+ })), o => o.optionName
+ );
+
+ options[0].header = true;
+
+ return options;
+ },
+
+ editOption (sn, on) {
+ this._editSectionName = sn;
+ this._editOptionName = on;
+ },
+
+ isEditing (sn, on) {
+ return sn == this._editSectionName && on == this._editOptionName;
+ },
+
+ stopEditing () {
+ this._editOptionName = null;
+ this._editSectionName = null;
+ },
+
+ setLoading () {
+ this._loading = true;
+ },
+
+ isLoading () {
+ return this._loading;
+ },
+
+ saveOption (sectionName, optionName, value) {
+ if (!this._sections[sectionName]) {
+ this._sections[sectionName] = {};
+ }
+
+ this._sections[sectionName][optionName] = value || true;
+ },
+
+ deleteOption (sectionName, optionName) {
+ if (this._sections[sectionName]) {
+ delete this._sections[sectionName][optionName];
+
+ if (Object.keys(this._sections[sectionName]).length == 0) {
+ delete this._sections[sectionName];
+ }
+ }
+ },
+
+ dispatch (action) {
+ if (action.options) {
+ var sectionName = action.options.sectionName;
+ var optionName = action.options.optionName;
+ var value = action.options.value;
+ }
+
+ switch (action.type) {
+ case ActionTypes.EDIT_CONFIG:
+ this.editConfig(action.options.sections, action.options.node);
+ break;
+
+ case ActionTypes.LOADING_CONFIG:
+ this.setLoading();
+ break;
+
+ case ActionTypes.EDIT_OPTION:
+ this.editOption(sectionName, optionName);
+ break;
+
+ case ActionTypes.CANCEL_EDIT:
+ this.stopEditing();
+ break;
+
+ case ActionTypes.OPTION_SAVE_SUCCESS:
+ this.saveOption(sectionName, optionName, value);
+ this.stopEditing();
+ break;
+
+ case ActionTypes.OPTION_ADD_SUCCESS:
+ this.saveOption(sectionName, optionName, value);
+ break;
+
+ case ActionTypes.OPTION_DELETE_SUCCESS:
+ this.deleteOption(sectionName, optionName);
+ break;
+ }
+
+ this.triggerChange();
+ }
+});
+
+var configStore = new ConfigStore();
+configStore.dispatchToken = FauxtonAPI.dispatcher.register(configStore.dispatch.bind(configStore));
+
+export default {
+ configStore: configStore
+};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/templates/add_config_option.html
----------------------------------------------------------------------
diff --git a/app/addons/config/templates/add_config_option.html b/app/addons/config/templates/add_config_option.html
deleted file mode 100644
index 13e36bd..0000000
--- a/app/addons/config/templates/add_config_option.html
+++ /dev/null
@@ -1,26 +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.
-*/%>
-
-<a id="add-new-section" class="add-new-section-btn"><i class="icon icon-plus header-icon"></i> Add Option</a>
-
-<div class="add-section-tray tray">
- <div class="add-on">Add Option</div>
-
- <div class="js-form-error-config"></div>
- <input type="text" name="section" placeholder="Section" autocomplete="off" autofocus>
- <input type="text" name="name" placeholder="Name">
- <input type="text" name="value" placeholder="Value">
-
- <a class="btn" id="js-create-config-section">Create</a>
-</div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/templates/dashboard.html
----------------------------------------------------------------------
diff --git a/app/addons/config/templates/dashboard.html b/app/addons/config/templates/dashboard.html
deleted file mode 100644
index 0af857d..0000000
--- a/app/addons/config/templates/dashboard.html
+++ /dev/null
@@ -1,24 +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.
-*/%>
-
-<table class="config table table-striped table-bordered">
- <thead>
- <th id="config-section" width="22%">Section</th>
- <th id="config-option" width="20%">Option</th>
- <th id="config-value">Value</th>
- <th id="config-trash"></th>
- </thead>
- <tbody>
- </tbody>
-</table>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/templates/header.html
----------------------------------------------------------------------
diff --git a/app/addons/config/templates/header.html b/app/addons/config/templates/header.html
deleted file mode 100644
index d2bd7fb..0000000
--- a/app/addons/config/templates/header.html
+++ /dev/null
@@ -1,15 +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.
-*/%>
-
-<div id="add-section-button" class="button"></div>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/templates/item.html
----------------------------------------------------------------------
diff --git a/app/addons/config/templates/item.html b/app/addons/config/templates/item.html
deleted file mode 100644
index 71f0a64..0000000
--- a/app/addons/config/templates/item.html
+++ /dev/null
@@ -1,40 +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.
-*/%>
-
-<% if (option.index === 0) {%>
-<th> <%- option.section %> </th>
-<% } else { %>
-<td></td>
-<% } %>
-<td class="js-edit-value">
- <div class="js-show-value">
- <%- option.name %>
- </div>
- <div class="js-edit-value-form js-hidden">
- <input class="js-value-input" type="text" name="name" value="<%- option.name %>" />
- <button class="js-save-value btn btn-success fonticon-ok-circled btn-small"> </button>
- <button class="js-cancel-value btn btn-small fonticon-cancel-circled"> </button>
- </div>
-</td>
-<td class="js-edit-value">
- <div class="js-show-value">
- <%- option.value %>
- </div>
- <div class="js-edit-value-form js-hidden">
- <input class="js-value-input" type="text" name="value" value="<%- option.value %>" />
- <button class="js-save-value btn btn-success fonticon-ok-circled btn-small"> </button>
- <button class="js-cancel-value btn btn-small fonticon-cancel-circled"> </button>
- </div>
-</td>
-<td class="js-delete-value text-center"> <i class="icon-trash"> </i> </td>
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/tests/actionsSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/config/tests/actionsSpec.js b/app/addons/config/tests/actionsSpec.js
new file mode 100644
index 0000000..0d35cae
--- /dev/null
+++ b/app/addons/config/tests/actionsSpec.js
@@ -0,0 +1,193 @@
+// 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 FauxtonAPI from "../../../core/api";
+import Actions from "../actions";
+import Resources from "../resources";
+import Backbone from "backbone";
+import sinon from "sinon";
+
+const assert = testUtils.assert;
+const restore = testUtils.restore;
+
+describe('Config Actions', function () {
+ var node = 'test';
+ var option = {
+ sectionName: 'test',
+ optionName: 'test',
+ value: 'test'
+ };
+ var failXhr = { responseText: '{}' };
+
+ describe('add', function () {
+ afterEach(function () {
+ restore(Actions.optionAddSuccess);
+ restore(Actions.optionAddFailure);
+ restore(FauxtonAPI.when);
+ restore(FauxtonAPI.addNotification);
+ restore(Backbone.Model.prototype.save);
+ });
+
+ it('calls optionAddSuccess when option add succeeds', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(Actions, 'optionAddSuccess');
+ var promise = FauxtonAPI.Deferred();
+ promise.resolve();
+ stub.returns(promise);
+
+ Actions.addOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('shows notification when option add succeeds', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(FauxtonAPI, 'addNotification');
+ var promise = FauxtonAPI.Deferred();
+ promise.resolve();
+ stub.returns(promise);
+
+ Actions.addOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('calls optionAddFailure when option add fails', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(Actions, 'optionAddFailure');
+ var promise = FauxtonAPI.Deferred();
+ promise.reject(failXhr);
+ stub.returns(promise);
+
+ Actions.addOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('shows notification when option add fails', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(FauxtonAPI, 'addNotification');
+ var promise = FauxtonAPI.Deferred();
+ promise.reject(failXhr);
+ stub.returns(promise);
+
+ Actions.addOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+ });
+
+ describe('save', function () {
+ afterEach(function () {
+ restore(Actions.optionSaveSuccess);
+ restore(Actions.optionSaveFailure);
+ restore(FauxtonAPI.when);
+ restore(FauxtonAPI.addNotification);
+ restore(Backbone.Model.prototype.save);
+ });
+
+ it('calls optionSaveSuccess when option save succeeds', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(Actions, 'optionSaveSuccess');
+ var promise = FauxtonAPI.Deferred();
+ promise.resolve();
+ stub.returns(promise);
+
+ Actions.saveOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('shows notification when option save succeeds', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(FauxtonAPI, 'addNotification');
+ var promise = FauxtonAPI.Deferred();
+ promise.resolve();
+ stub.returns(promise);
+
+ Actions.saveOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('calls optionSaveFailure when option save fails', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(Actions, 'optionSaveFailure');
+ var promise = FauxtonAPI.Deferred();
+ promise.reject(failXhr);
+ stub.returns(promise);
+
+ Actions.saveOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('shows notification when option save fails', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'save');
+ var spy = sinon.spy(FauxtonAPI, 'addNotification');
+ var promise = FauxtonAPI.Deferred();
+ promise.reject(failXhr);
+ stub.returns(promise);
+
+ Actions.saveOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+ });
+
+ describe('delete', function () {
+ afterEach(function () {
+ restore(Actions.optionDeleteSuccess);
+ restore(Actions.optionDeleteFailure);
+ restore(FauxtonAPI.when);
+ restore(FauxtonAPI.addNotification);
+ restore(Backbone.Model.prototype.destroy);
+ });
+
+ it('calls optionDeleteSuccess when option delete succeeds', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'destroy');
+ var spy = sinon.spy(Actions, 'optionDeleteSuccess');
+ var promise = FauxtonAPI.Deferred();
+ promise.resolve();
+ stub.returns(promise);
+
+ Actions.deleteOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('shows notification when option delete succeeds', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'destroy');
+ var spy = sinon.spy(FauxtonAPI, 'addNotification');
+ var promise = FauxtonAPI.Deferred();
+ promise.resolve();
+ stub.returns(promise);
+
+ Actions.deleteOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('calls optionDeleteFailure when option delete fails', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'destroy');
+ var spy = sinon.spy(Actions, 'optionDeleteFailure');
+ var promise = FauxtonAPI.Deferred();
+ promise.reject(failXhr);
+ stub.returns(promise);
+
+ Actions.deleteOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('shows notification when option delete fails', function () {
+ var stub = sinon.stub(Backbone.Model.prototype, 'destroy');
+ var spy = sinon.spy(FauxtonAPI, 'addNotification');
+ var promise = FauxtonAPI.Deferred();
+ promise.reject(failXhr);
+ stub.returns(promise);
+
+ Actions.deleteOption(node, option);
+ assert.ok(spy.calledOnce);
+ });
+ });
+});
+
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/tests/componentsSpec.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/config/tests/componentsSpec.react.jsx b/app/addons/config/tests/componentsSpec.react.jsx
new file mode 100644
index 0000000..5c49760
--- /dev/null
+++ b/app/addons/config/tests/componentsSpec.react.jsx
@@ -0,0 +1,306 @@
+// 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 Views from "../components.react";
+import Actions from "../actions";
+import Stores from "../stores";
+import utils from "../../../../test/mocha/testUtils";
+import React from "react";
+import ReactDOM from "react-dom";
+import TestUtils from "react-addons-test-utils";
+import sinon from "sinon";
+
+FauxtonAPI.router = new FauxtonAPI.Router([]);
+var assert = utils.assert;
+var configStore = Stores.configStore;
+
+describe('Config Components', function () {
+ describe('ConfigTableController', function () {
+ var container, elm, node;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ configStore._loading = false;
+ configStore._sections = {};
+ node = 'node2@127.0.0.1';
+ elm = TestUtils.renderIntoDocument(
+ <Views.ConfigTableController node={node}/>,
+ container
+ );
+ });
+
+ afterEach(function () {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('deletes options', function () {
+ var spy = sinon.stub(Actions, 'deleteOption');
+ var option = {};
+
+ elm.deleteOption(option);
+ assert.ok(spy.calledWith(node, option));
+ });
+
+ it('saves options', function () {
+ var spy = sinon.stub(Actions, 'saveOption');
+ var option = {};
+
+ elm.saveOption(option);
+ assert.ok(spy.calledWith(node, option));
+ });
+
+ it('edits options', function () {
+ var spy = sinon.stub(Actions, 'editOption');
+ var option = {};
+
+ elm.editOption(option);
+ assert.ok(spy.calledWith(option));
+ });
+
+ it('cancels editing', function () {
+ var spy = sinon.stub(Actions, 'cancelEdit');
+
+ elm.cancelEdit();
+ assert.ok(spy.calledOnce);
+ });
+ });
+
+ describe('ConfigOption', function () {
+ var container;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ });
+
+ afterEach(function () {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('renders section name if the option is a header', function () {
+ var option = {
+ sectionName: 'test_section',
+ optionName: 'test_option',
+ value: 'test_value',
+ header: true
+ };
+
+ var el = TestUtils.renderIntoDocument(<Views.ConfigOption option={option}/>, container);
+ assert.equal($(ReactDOM.findDOMNode(el)).find('th').text(), 'test_section');
+ });
+ });
+
+ describe('ConfigOptionValue', function () {
+ var container;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ });
+
+ afterEach(function () {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('displays the value prop', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionValue value={'test_value'}/>, container
+ );
+
+ assert.equal($(ReactDOM.findDOMNode(el)).text(), 'test_value');
+ });
+
+ it('starts editing when clicked', function () {
+ var spy = sinon.spy();
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionValue value={'test_value'} onEdit={spy}/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el))[0]);
+ assert.ok(spy.calledOnce);
+ });
+
+ it('displays editing controls if editing', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionValue value={'test_value'} editing/>, container
+ );
+
+ assert.equal($(ReactDOM.findDOMNode(el)).find('input.config-value-input').length, 1);
+ assert.equal($(ReactDOM.findDOMNode(el)).find('button.btn-config-cancel').length, 1);
+ assert.equal($(ReactDOM.findDOMNode(el)).find('button.btn-config-save').length, 1);
+ });
+
+ it('disables input when save clicked', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionValue value={'test_value'} editing/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button.btn-config-save')[0]);
+ assert.equal($(ReactDOM.findDOMNode(el)).find('input.config-value-input').attr('disabled'));
+ });
+
+ it('saves changed value of input when save clicked', function () {
+ var change = { target: { value: 'new_value' } };
+ var spy = sinon.spy();
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionValue value={'test_value'} editing onSave={spy}/>, container
+ );
+
+ TestUtils.Simulate.change($(ReactDOM.findDOMNode(el)).find('input.config-value-input')[0], change);
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button.btn-config-save')[0]);
+ assert.ok(spy.calledWith('new_value'));
+ });
+
+ it('cancels edit if save clicked with unchanged value', function () {
+ var spy = sinon.spy();
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionValue value={'test_value'} editing onCancelEdit={spy}/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button.btn-config-save')[0]);
+ assert.ok(spy.calledOnce);
+ });
+ });
+
+ describe('ConfigOptionTrash', function () {
+ var container;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ _.each($('div[data-reactroot]'), function (el) {
+ ReactDOM.unmountComponentAtNode(el.parentNode);
+ });
+ });
+
+ afterEach(function () {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('displays delete modal when clicked', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionTrash sectionName='test_section' optionName='test_option'/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el))[0]);
+ assert.equal($('div.confirmation-modal').length, 1);
+ });
+
+ it('calls on delete when confirmation modal Okay button clicked', function () {
+ var spy = sinon.spy();
+ var el = TestUtils.renderIntoDocument(
+ <Views.ConfigOptionTrash onDelete={spy}/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el))[0]);
+ TestUtils.Simulate.click($('div.confirmation-modal button.btn-success')[0]);
+ assert.ok(spy.calledOnce);
+ });
+ });
+
+ describe('AddOptionController', function () {
+ var container, elm;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ elm = TestUtils.renderIntoDocument(
+ <Views.AddOptionController node='node2@127.0.0.1'/>,
+ container
+ );
+ });
+
+ afterEach(function () {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('adds options', function () {
+ var spy = sinon.stub(Actions, 'addOption');
+
+ elm.addOption();
+ assert.ok(spy.calledOnce);
+ });
+ });
+
+ describe('AddOptionButton', function () {
+ var container;
+
+ beforeEach(function () {
+ container = document.createElement('div');
+ _.each($('div[data-reactroot]'), function (el) {
+ ReactDOM.unmountComponentAtNode(el.parentNode);
+ });
+ });
+
+ afterEach(function () {
+ ReactDOM.unmountComponentAtNode(container);
+ });
+
+ it('displays add option controls when clicked', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.AddOptionButton/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button#add-option-button')[0]);
+ assert.equal($('div#add-option-popover .input-section-name').length, 1);
+ assert.equal($('div#add-option-popover .input-option-name').length, 1);
+ assert.equal($('div#add-option-popover .input-value').length, 1);
+ assert.equal($('div#add-option-popover .btn-create').length, 1);
+ });
+
+ it('does not hide popover if create clicked with invalid input', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.AddOptionButton/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button#add-option-button')[0]);
+ TestUtils.Simulate.click($('div#add-option-popover .btn-create')[0]);
+ assert.equal($('div#add-option-popover').length, 1);
+ });
+
+ it('does not add option if create clicked with invalid input', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.AddOptionButton/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button#add-option-button')[0]);
+ TestUtils.Simulate.click($('div#add-option-popover .btn-create')[0]);
+ assert.equal($('div#add-option-popover').length, 1);
+ });
+
+
+ it('does adds option if create clicked with valid input', function () {
+ var el = TestUtils.renderIntoDocument(
+ <Views.AddOptionButton/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button#add-option-button')[0]);
+ TestUtils.Simulate.click($('div#add-option-popover .btn-create')[0]);
+ assert.equal($('div#add-option-popover').length, 1);
+ });
+
+ it('adds option when create clicked with valid input', function () {
+ var spy = sinon.spy();
+ var el = TestUtils.renderIntoDocument(
+ <Views.AddOptionButton onAdd={spy}/>, container
+ );
+
+ TestUtils.Simulate.click($(ReactDOM.findDOMNode(el)).find('button#add-option-button')[0]);
+ TestUtils.Simulate.change($('div#add-option-popover .input-section-name')[0], { target: { value: 'test_section' } });
+ TestUtils.Simulate.change($('div#add-option-popover .input-option-name')[0], { target: { value: 'test_option' } });
+ TestUtils.Simulate.change($('div#add-option-popover .input-value')[0], { target: { value: 'test_value' } });
+ TestUtils.Simulate.click($('div#add-option-popover .btn-create')[0]);
+ assert.ok(spy.calledWith(sinon.match({
+ sectionName: 'test_section',
+ optionName: 'test_option',
+ value: 'test_value'
+ })));
+ });
+ });
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/tests/configSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/config/tests/configSpec.js b/app/addons/config/tests/configSpec.js
deleted file mode 100644
index 885ba24..0000000
--- a/app/addons/config/tests/configSpec.js
+++ /dev/null
@@ -1,162 +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 Resources from "../resources";
-import Views from "../views";
-import testUtils from "../../../../test/mocha/testUtils";
-import sinon from "sinon";
-var assert = testUtils.assert,
- ViewSandbox = testUtils.ViewSandbox,
- collection;
-
-beforeEach(function () {
- var optionModels = [];
-
- _.each([1, 2, 3], function (i) {
- var model = new Resources.OptionModel({
- section: "foo" + i,
- name: "bar" + i,
- options: [{
- name: "testname"
- }]
- }, {node: 'foo'});
-
- optionModels.push(model);
- });
-
- collection = new Resources.Collection(optionModels, {node: 'foo'}, "foo");
-});
-
-describe("Config: Add Option Tray", function () {
- var viewSandbox,
- tray;
-
- beforeEach(function (done) {
- tray = new Views.AddConfigOptionsButton({
- collection: collection
- });
-
- viewSandbox = new ViewSandbox();
- viewSandbox.renderView(tray, done);
- });
-
- afterEach(function () {
- viewSandbox.remove();
- });
-
- it("looks if entries are new", function () {
- tray.$('input[name="section"]').val("foo1");
- tray.$('input[name="name"]').val("testname");
- assert.ok(tray.isUniqueEntryInSection(collection));
-
- tray.$('input[name="name"]').val("testname2");
- assert.notOk(tray.isUniqueEntryInSection(collection));
- });
-
- it("does not send an error for a new section", function () {
- tray.$('input[name="section"]').val("newsection");
- tray.$('input[name="name"]').val("testname");
- tray.$('input[name="value"]').val("testvalue");
- var spy = sinon.spy(tray, "showError");
-
- tray.createConfigOption();
- assert.notOk(spy.called);
- });
-});
-
-describe("Config: Collection", function () {
- it("looks if entries are new", function () {
- assert.ok(collection.findEntryInSection("foo1", "testname"));
- assert.notOk(collection.findEntryInSection("foo1", "testname2"));
- });
-
- it("returns false if findEntryInSection does not have the section", function () {
- assert.notOk(collection.findEntryInSection("foo-not-exists", "testname"));
- });
-});
-
-describe("Config: TableRow", function () {
- var tabMenu, optionModel;
-
- beforeEach(function () {
- optionModel = new Resources.OptionModel({
- section: "foo",
- name: "bar"
- }, {node: 'foo'});
-
- tabMenu = new Views.TableRow({
- model: optionModel,
- uniqueName: function () {
- return false;
- }
- });
- });
-
- describe("editing Items", function () {
- var viewSandbox;
- beforeEach(function (done) {
- viewSandbox = new ViewSandbox();
- viewSandbox.renderView(tabMenu, done);
- });
-
- afterEach(function () {
- viewSandbox.remove();
- });
-
- it("click on save should save the model and render", function () {
- var renderSpy = sinon.stub(tabMenu, 'render');
- var saveSpy = sinon.stub(optionModel, 'save');
-
- var $fields = tabMenu.$('.js-edit-value').filter(function (el) {
- return $(this).find('[name="value"]').length;
- });
-
- $fields.find('.js-edit-value').trigger('dblclick');
- $fields.find('.js-save-value').trigger('click');
-
- assert.ok(renderSpy.calledOnce);
- assert.ok(saveSpy.calledOnce);
- });
-
- it("pressing enter should save the model and render", function () {
- var renderSpy = sinon.stub(tabMenu, 'render');
- var saveSpy = sinon.stub(optionModel, 'save');
-
- var e = $.Event("keyup");
- e.keyCode = 13;
-
- var $fields = tabMenu.$('.js-edit-value').filter(function (el) {
- return $(this).find('[name="value"]').length;
- });
-
- $fields.find('.js-value-input').trigger(e);
-
- assert.ok(renderSpy.calledOnce);
- assert.ok(saveSpy.calledOnce);
- });
-
- it("pressing Esc hides the field", function () {
- var e = $.Event("keyup");
- e.keyCode = 27;
- tabMenu.$('.js-value-input').trigger(e);
-
- assert.ok(tabMenu.$('.js-edit-value-form').hasClass('js-hidden'));
- });
-
- it("pressing Cancel hides the field", function () {
- tabMenu.$('.js-edit-value').trigger('dblclick');
- tabMenu.$('.js-cancel-value').trigger('click');
-
- assert.ok(tabMenu.$('.js-edit-value-form').hasClass('js-hidden'));
- });
- });
-});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/config/tests/storesSpec.js b/app/addons/config/tests/storesSpec.js
new file mode 100644
index 0000000..a36ce12
--- /dev/null
+++ b/app/addons/config/tests/storesSpec.js
@@ -0,0 +1,94 @@
+// 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 Stores from '../stores';
+import utils from '../../../../test/mocha/testUtils';
+
+var assert = utils.assert;
+
+describe("ConfigStore", function () {
+ var configStore = Stores.configStore;
+
+ describe("mapSection", function () {
+ beforeEach(function () {
+ configStore._editOptionName = 'b';
+ configStore._editSectionName = 'test';
+ });
+
+ afterEach(function () {
+ configStore.reset();
+ });
+
+ it("sorts options ascending", function () {
+ var options = configStore.mapSection({ b: 1, c: 2, a: 3 }, 'test');
+ assert.equal(options[0].optionName, 'a');
+ });
+
+ it("sets the first option as the header", function () {
+ var options = configStore.mapSection({ b: 1, c: 2, a: 3 }, 'test');
+ assert.isTrue(options[0].header);
+ });
+
+ it("sets the option that is being edited", function () {
+ var options = configStore.mapSection({ b: 1, c: 2, a: 3 }, 'test');
+ assert.isTrue(options[1].editing);
+ });
+ });
+
+ describe("saveOption", function () {
+ var sectionName, optionName, value;
+
+ beforeEach(function () {
+ sectionName = 'a';
+ optionName = 'b';
+ value = 1;
+ });
+
+ afterEach(function () {
+ configStore.reset();
+ });
+
+ it("saves option to sections", function () {
+ configStore._sections = {};
+
+ configStore.saveOption(sectionName, optionName, value);
+ assert.deepEqual(configStore._sections, { a: { b: 1 } });
+ });
+ });
+
+ describe("deleteOption", function () {
+ var sectionName, optionName;
+
+ beforeEach(function () {
+ sectionName = 'a';
+ optionName = 'b';
+ });
+
+ afterEach(function () {
+ configStore.reset();
+ });
+
+ it("deletes option from section", function () {
+ configStore._sections = { a: { b: 1, c: 2 } };
+
+ configStore.deleteOption(sectionName, optionName);
+ assert.deepEqual(configStore._sections, { a: { c: 2 } });
+ });
+
+ it("deletes section when all options are deleted", function () {
+ configStore._sections = { a: { b: 1 } };
+
+ configStore.deleteOption(sectionName, optionName);
+ assert.deepEqual(configStore._sections, {});
+ });
+ });
+});
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/app/addons/config/views.js
----------------------------------------------------------------------
diff --git a/app/addons/config/views.js b/app/addons/config/views.js
index 398e396..c20b24a 100644
--- a/app/addons/config/views.js
+++ b/app/addons/config/views.js
@@ -10,238 +10,9 @@
// License for the specific language governing permissions and limitations under
// the License.
-import app from "../../app";
import FauxtonAPI from "../../core/api";
-import Config from "./resources";
-import Components from "../fauxton/components";
var Views = {};
-Views.TableRow = FauxtonAPI.View.extend({
- tagName: "tr",
- className: "config-item",
- template: "addons/config/templates/item",
- events: {
- "dblclick .js-edit-value": "editValue",
- "click .js-delete-value": "deleteValue",
- "click .js-cancel-value": "cancelEdit",
- "click .js-save-value": "saveAndRender",
- "keyup .js-value-input": "processKeyEvents"
- },
-
- deleteValue: function () {
- var collection = this.collection,
- result = confirm("Are you sure you want to delete this configuration value?");
-
- if (!result) { return; }
-
- this.model.destroy().done(function () {
- collection.fetch({reset: true}).done(function () {
- FauxtonAPI.Events.trigger("config:rerender");
- });
- });
-
- this.remove();
- },
-
- editValue: function (event) {
- this.$(event.currentTarget).find(".js-show-value").addClass("js-hidden");
- this.$(event.currentTarget).find(".js-edit-value-form").removeClass("js-hidden");
- this.$(event.currentTarget).find(".js-value-input").focus();
- },
-
- processKeyEvents: function (event) {
- // Enter key
- if (event.keyCode === 13) {
- return this.saveAndRender(event);
- }
- // Esc key
- if (event.keyCode === 27) {
- return this.discardValue(event);
- }
- },
-
- discardValue: function (event) {
- this.$(event.currentTarget).parents('td').find(".js-edit-value-form").addClass("js-hidden");
- this.$(event.currentTarget).parents('td').find(".js-show-value").removeClass("js-hidden");
- },
-
- cancelEdit: function (event) {
- this.discardValue(event);
- },
-
- serialize: function () {
- return {option: this.model.toJSON()};
- },
-
- saveAndRender: function (event) {
- var options = {},
- $input = this.$(event.currentTarget).parents('td').find(".js-value-input"),
- sectionName,
- nameInSectionExists;
-
- options[$input.attr('name')] = $input.val();
-
- if ($input.attr('name') === 'name') {
- sectionName = this.model.get("section");
- nameInSectionExists = this.collection.findEntryInSection(sectionName, $input.val());
- if (nameInSectionExists) {
- FauxtonAPI.addNotification({
- msg: "This config already exists, enter a unique name",
- type: "error",
- clear: true
- });
- } else {
- var newModel = this.model.clone();
- newModel.save(options);
- this.model.destroy();
- this.model = newModel;
- this.render();
- }
- } else {
- this.model.save(options);
- this.render();
- }
- }
-
-});
-
-Views.Table = FauxtonAPI.View.extend({
- template: "addons/config/templates/dashboard",
-
- initialize: function () {
- this.listenTo(FauxtonAPI.Events, "config:newSection", this.render);
- this.listenTo(FauxtonAPI.Events, "config:rerender", this.render);
- },
-
- beforeRender: function () {
- var collection = this.collection;
-
- this.collection.each(function (config) {
- _.each(config.get("options"), function (option, index) {
- this.insertView("table.config tbody", new Views.TableRow({
- collection: collection,
- model: new Config.OptionModel({
- section: config.get("section"),
- name: option.name,
- value: option.value,
- index: index
- }, {node: this.collection.node})
- }));
- }, this);
- }, this);
- },
-
- establish: function () {
- return [this.collection.fetch()];
- }
-});
-
-Views.ConfigHeader = FauxtonAPI.View.extend({
- template: 'addons/config/templates/header',
- className: 'header-right',
-
- initialize: function () {
- this.rightHeader = this.setView('#add-section-button', new Views.AddConfigOptionsButton({ collection: this.collection }));
- }
-});
-
-
-Views.AddConfigOptionsButton = Components.Tray.extend({
- template: 'addons/config/templates/add_config_option',
-
- events: {
- 'click #js-create-config-section': 'createConfigOption'
- },
-
- initialize: function () {
- this.initTray({ toggleTrayBtnSelector: '#add-new-section' });
- },
-
- processKey: function (e) {
- if (e.which === 13) {
- e.preventDefault();
- this.createConfigOption();
- }
- },
-
- afterRender: function () {
- this.sectionNames = _.map(this.collection.toJSON(), function (item) {
- return item.section;
- });
-
- this.sectionTypeAhead = new Components.Typeahead({
- source: this.sectionNames,
- el: 'input[name="section"]'
- });
- this.sectionTypeAhead.render();
- },
-
- createConfigOption: function (e) {
- if (e) {
- e.preventDefault();
- }
-
- var section = this.$('input[name="section"]').val(),
- name = this.$('input[name="name"]').val(),
- value = this.$('input[name="value"]').val(),
- collection = this.collection;
-
- // perform a little validation, then submit
- if (!section) {
- this.showError('Please enter or select a section.');
- } else if (!name) {
- this.showError('Please add an option name.');
- } else if (this.isUniqueEntryInSection(collection)) {
- this.showError('The option have a unique name.');
- } else if (!value) {
- this.showError('Please add a value.');
- } else {
- this.submitForm();
- }
- },
-
- submitForm: function () {
-
- var option = new Config.OptionModel({
- section: this.$('input[name="section"]').val(),
- name: this.$('input[name="name"]').val(),
- value: this.$('input[name="value"]').val()
- }, {node: this.collection.node});
- option.save();
-
- var section = this.collection.find(function (section) {
- return section.get('section') === option.get('section');
- });
-
- if (section) {
- section.get('options').push(option.attributes);
- } else {
- this.collection.add({
- section: option.get('section'),
- options: [option.attributes]
- });
- }
-
- this.hideTray();
- FauxtonAPI.Events.trigger('config:newSection');
- },
-
- isUniqueEntryInSection: function (collection) {
- var sectionName = this.$('input[name="section"]').val(),
- entry = this.$('input[name="name"]').val();
-
- return collection.findEntryInSection(sectionName, entry);
- },
-
- showError: function (msg) {
- FauxtonAPI.addNotification({
- msg: msg,
- type: 'error',
- clear: true
- });
- }
-});
-
Views.Tabs = FauxtonAPI.View.extend({
className: "sidenav",
tagName: "nav",
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/devserver.js
----------------------------------------------------------------------
diff --git a/devserver.js b/devserver.js
index 3bef0a8..be2c9d6 100644
--- a/devserver.js
+++ b/devserver.js
@@ -1,4 +1,4 @@
-var spawn = require('child_process').spawn;
+var spawn = require('cross-spawn').spawn;
var path = require("path");
var fs = require("fs");
var _ = require('lodash');
@@ -18,7 +18,7 @@ var loadSettings = function () {
port: process.env.FAUXTON_PORT || 8000,
contentSecurityPolicy: true,
proxy: {
- target: 'http://127.0.0.1:5984',
+ target: 'http://couch:5984',
changeOrigin: false
}
};
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/tasks/helper.js
----------------------------------------------------------------------
diff --git a/tasks/helper.js b/tasks/helper.js
index a995c23..696e1c0 100644
--- a/tasks/helper.js
+++ b/tasks/helper.js
@@ -14,7 +14,7 @@ var fs = require('fs'),
path = require('path');
exports.devServerPort = 8000;
-exports.couch = 'http://localhost:5984/';
+exports.couch = 'http://couch:5984/';
exports.init = function (grunt) {
var _ = grunt.util._,
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/ee1a92b0/webpack.config.dev.js
----------------------------------------------------------------------
diff --git a/webpack.config.dev.js b/webpack.config.dev.js
index edb4833..05f7b92 100644
--- a/webpack.config.dev.js
+++ b/webpack.config.dev.js
@@ -73,5 +73,7 @@ module.exports = {
path: __dirname + '/dist/debug',
publicPath: '/',
filename: 'bundle.js' //All our code is compiled into a single file called bundle.js
- }
+ },
+
+ devtool: 'source-map'
};
[2/2] fauxton commit: updated refs/heads/master to 4f633e5
Posted by ro...@apache.org.
react config: fix small issues
Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/4f633e50
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/4f633e50
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/4f633e50
Branch: refs/heads/master
Commit: 4f633e508bd9cb24cdb1a858c00d624d01732de1
Parents: ee1a92b
Author: Robert Kowalski <ro...@apache.org>
Authored: Tue Sep 13 14:31:00 2016 +0200
Committer: Robert Kowalski <ro...@apache.org>
Committed: Tue Sep 13 14:31:00 2016 +0200
----------------------------------------------------------------------
app/addons/config/assets/less/config.less | 15 ++++++++-------
app/addons/config/components.react.jsx | 2 +-
devserver.js | 4 ++--
3 files changed, 11 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/4f633e50/app/addons/config/assets/less/config.less
----------------------------------------------------------------------
diff --git a/app/addons/config/assets/less/config.less b/app/addons/config/assets/less/config.less
index f74b2ec..a9d7d7c 100644
--- a/app/addons/config/assets/less/config.less
+++ b/app/addons/config/assets/less/config.less
@@ -45,13 +45,13 @@
background-color: #e0e0e0;
}
-.table-striped tbody > tr.config-item:nth-child(odd) > td,
-.table-striped tbody > tr.config-item:nth-child(odd) > th {
+.config.table-striped tbody > tr.config-item:nth-child(odd) > td,
+.config.table-striped tbody > tr.config-item:nth-child(odd) > th {
transition: background-color 100ms;
}
-.table-striped tbody > tr.config-item:nth-child(odd):hover > td,
-.table-striped tbody > tr.config-item:nth-child(odd):hover > th {
+.config.table-striped tbody > tr.config-item:nth-child(odd):hover > td,
+.config.table-striped tbody > tr.config-item:nth-child(odd):hover > th {
background-color: #e7e7e7;
}
@@ -185,10 +185,11 @@ table.config {
color: white;
}
}
-}
-.popover.bottom .arrow, .popover.bottom .arrow::after {
- border-bottom-color: #333 !important;
+ .arrow, .arrow::after {
+ border-bottom-color: #333 !important;
+ }
+
}
#add-option-popover::before {
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/4f633e50/app/addons/config/components.react.jsx
----------------------------------------------------------------------
diff --git a/app/addons/config/components.react.jsx b/app/addons/config/components.react.jsx
index a825282..4432753 100644
--- a/app/addons/config/components.react.jsx
+++ b/app/addons/config/components.react.jsx
@@ -37,7 +37,7 @@ var ConfigTableController = React.createClass({
},
componentWillUnmount () {
- configStore.off('change', this.onChange, this);
+ configStore.off('change', this.onChange);
},
onChange () {
http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/4f633e50/devserver.js
----------------------------------------------------------------------
diff --git a/devserver.js b/devserver.js
index be2c9d6..3bef0a8 100644
--- a/devserver.js
+++ b/devserver.js
@@ -1,4 +1,4 @@
-var spawn = require('cross-spawn').spawn;
+var spawn = require('child_process').spawn;
var path = require("path");
var fs = require("fs");
var _ = require('lodash');
@@ -18,7 +18,7 @@ var loadSettings = function () {
port: process.env.FAUXTON_PORT || 8000,
contentSecurityPolicy: true,
proxy: {
- target: 'http://couch:5984',
+ target: 'http://127.0.0.1:5984',
changeOrigin: false
}
};