mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
Move components to separate files.
This commit is contained in:
parent
e1f1cfc4d3
commit
e7ecae51b7
@ -30,7 +30,7 @@
|
|||||||
"nonbsp" : true,
|
"nonbsp" : true,
|
||||||
"nonew" : true,
|
"nonew" : true,
|
||||||
"plusplus" : false,
|
"plusplus" : false,
|
||||||
"quotmark" : true,
|
"quotmark" : false,
|
||||||
"undef" : true,
|
"undef" : true,
|
||||||
"unused" : true,
|
"unused" : true,
|
||||||
"strict" : false,
|
"strict" : false,
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"start": "NODE_ENV=production node server.js",
|
"start": "NODE_ENV=production node server.js",
|
||||||
"build": "./node_modules/.bin/webpack",
|
"build": "./node_modules/.bin/webpack",
|
||||||
"dev": "NODE_ENV=development supervisor --ignore ./node_modules/,./public/js server.js",
|
"dev": "NODE_ENV=development supervisor --ignore ./node_modules/,./public/js server.js",
|
||||||
"test": "jshint server.js lib test && jsxhint public/js/*.jsx && mocha test test/* && npm run coverage",
|
"test": "jshint server.js lib test && jsxhint public/js/**/*.jsx && mocha test test/* && npm run coverage",
|
||||||
"tdd": "mocha --watch test test/*",
|
"tdd": "mocha --watch test test/*",
|
||||||
"test-bamboo-ci": "mocha test test/*",
|
"test-bamboo-ci": "mocha test test/*",
|
||||||
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec",
|
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec",
|
||||||
|
7
unleash-server/public/js/app.jsx
Normal file
7
unleash-server/public/js/app.jsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var Unleash = require('./components/Unleash');
|
||||||
|
|
||||||
|
React.render(
|
||||||
|
<Unleash pollInterval={5000} />,
|
||||||
|
document.getElementById('content')
|
||||||
|
);
|
21
unleash-server/public/js/components/ErrorMessages.jsx
Normal file
21
unleash-server/public/js/components/ErrorMessages.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var ErrorMessages = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
if (!this.props.errors.length) {
|
||||||
|
return <div/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorNodes = this.props.errors.map(function(e) {
|
||||||
|
return (<li key={e}>{e}</li>);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="alert alert-danger" role="alert">
|
||||||
|
<ul>{errorNodes}</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ErrorMessages;
|
52
unleash-server/public/js/components/FeatureList.jsx
Normal file
52
unleash-server/public/js/components/FeatureList.jsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var SavedFeature = require('./SavedFeature');
|
||||||
|
var UnsavedFeature = require('./UnsavedFeature');
|
||||||
|
|
||||||
|
var FeatureList = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var featureNodes = [];
|
||||||
|
|
||||||
|
this.props.unsavedFeatures.forEach(function(feature, idx) {
|
||||||
|
var key = 'new-' + idx;
|
||||||
|
featureNodes.push(
|
||||||
|
<UnsavedFeature
|
||||||
|
key={key}
|
||||||
|
feature={feature}
|
||||||
|
onSubmit={this.props.onFeatureSubmit}
|
||||||
|
onCancel={this.props.onFeatureCancel} />
|
||||||
|
);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.props.savedFeatures.forEach(function(feature) {
|
||||||
|
featureNodes.push(
|
||||||
|
<SavedFeature
|
||||||
|
key={feature.name}
|
||||||
|
feature={feature}
|
||||||
|
onChange={this.props.onFeatureChanged} />
|
||||||
|
);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className='panel panel-primary'>
|
||||||
|
<div className='panel-heading'>
|
||||||
|
<h3 className='panel-title'>Features</h3>
|
||||||
|
<div className='text-right'>
|
||||||
|
<button type="button"
|
||||||
|
className="btn btn-default btn-sm"
|
||||||
|
onClick={this.props.onNewFeature}>
|
||||||
|
<span className="glyphicon glyphicon-plus"></span> New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='panel-body'>
|
||||||
|
{featureNodes}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = FeatureList;
|
14
unleash-server/public/js/components/Menu.jsx
Normal file
14
unleash-server/public/js/components/Menu.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var Menu = React.createClass({
|
||||||
|
render: function() { return (
|
||||||
|
<nav className='navbar navbar-default' role='navigation'>
|
||||||
|
<div className='container'>
|
||||||
|
<a className='navbar-brand' href='#'>unleash admin</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Menu;
|
32
unleash-server/public/js/components/SavedFeature.jsx
Normal file
32
unleash-server/public/js/components/SavedFeature.jsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SavedFeature = React.createClass({
|
||||||
|
onChange: function(event) {
|
||||||
|
this.props.onChange({
|
||||||
|
name: this.props.feature.name,
|
||||||
|
field: 'enabled',
|
||||||
|
value: event.target.checked
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div className='row'>
|
||||||
|
<div className='col-md-1 text-right'>
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={this.props.feature.enabled}
|
||||||
|
onChange={this.onChange} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className='col-md-4'
|
||||||
|
title='{this.props.feature.description}'>{this.props.feature.name}</div>
|
||||||
|
<div className='col-md-2 col-md-offset-5'>
|
||||||
|
{this.props.feature.strategy}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SavedFeature;
|
131
unleash-server/public/js/components/Unleash.jsx
Normal file
131
unleash-server/public/js/components/Unleash.jsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
var React = require('react');
|
||||||
|
var reqwest = require('reqwest');
|
||||||
|
var Timer = require('../utils/Timer');
|
||||||
|
var Menu = require('./Menu');
|
||||||
|
var ErrorMessages = require('./ErrorMessages');
|
||||||
|
var FeatureList = require('./FeatureList');
|
||||||
|
|
||||||
|
var Unleash = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
savedFeatures: [],
|
||||||
|
unsavedFeatures: [],
|
||||||
|
errors: [],
|
||||||
|
timer: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function () {
|
||||||
|
this.loadFeaturesFromServer();
|
||||||
|
this.state.timer = new Timer(this.loadFeaturesFromServer, this.props.pollInterval);
|
||||||
|
this.state.timer.start();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
if (this.state.timer != null) {
|
||||||
|
this.state.timer.stop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadFeaturesFromServer: function () {
|
||||||
|
reqwest('features').then(this.setFeatures, this.handleError);
|
||||||
|
},
|
||||||
|
|
||||||
|
setFeatures: function (data) {
|
||||||
|
this.setState({savedFeatures: data.features});
|
||||||
|
},
|
||||||
|
|
||||||
|
handleError: function (error) {
|
||||||
|
this.state.errors.push(error);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFeature: function (changeRequest) {
|
||||||
|
var newFeatures = this.state.savedFeatures;
|
||||||
|
newFeatures.forEach(function(f){
|
||||||
|
if(f.name === changeRequest.name) {
|
||||||
|
f[changeRequest.field] = changeRequest.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({features: newFeatures});
|
||||||
|
this.state.timer.stop();
|
||||||
|
|
||||||
|
reqwest({
|
||||||
|
url: 'features/' + changeRequest.name,
|
||||||
|
method: 'patch',
|
||||||
|
type: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(changeRequest)
|
||||||
|
}).then(function() {
|
||||||
|
// all good
|
||||||
|
this.state.timer.start();
|
||||||
|
}.bind(this), this.handleError);
|
||||||
|
},
|
||||||
|
|
||||||
|
createFeature: function (feature) {
|
||||||
|
var unsaved = [], state = this.state;
|
||||||
|
|
||||||
|
this.state.unsavedFeatures.forEach(function(f) {
|
||||||
|
// TODO: make sure we don't overwrite an existing feature
|
||||||
|
if (f.name === feature.name) {
|
||||||
|
state.savedFeatures.unshift(f);
|
||||||
|
} else {
|
||||||
|
unsaved.push(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({unsavedFeatures: unsaved});
|
||||||
|
|
||||||
|
reqwest({
|
||||||
|
url: 'features',
|
||||||
|
method: 'post',
|
||||||
|
type: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(feature)
|
||||||
|
}).then(function(r) {
|
||||||
|
console.log(r.statusText);
|
||||||
|
}.bind(this), this.handleError);
|
||||||
|
},
|
||||||
|
|
||||||
|
newFeature: function() {
|
||||||
|
var blankFeature = {
|
||||||
|
name: '',
|
||||||
|
enabled: false,
|
||||||
|
strategy: 'default',
|
||||||
|
parameters: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state.unsavedFeatures.push(blankFeature);
|
||||||
|
this.forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelNewFeature: function (feature) {
|
||||||
|
var unsaved = [];
|
||||||
|
|
||||||
|
this.state.unsavedFeatures.forEach(function (f) {
|
||||||
|
if (f.name !== feature.name) {
|
||||||
|
unsaved.push(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({unsavedFeatures: unsaved});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Menu />
|
||||||
|
<ErrorMessages errors={this.state.errors} />
|
||||||
|
<FeatureList
|
||||||
|
unsavedFeatures={this.state.unsavedFeatures}
|
||||||
|
savedFeatures={this.state.savedFeatures}
|
||||||
|
onFeatureChanged={this.updateFeature}
|
||||||
|
onFeatureSubmit={this.createFeature}
|
||||||
|
onFeatureCancel={this.cancelNewFeature}
|
||||||
|
onNewFeature={this.newFeature} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Unleash;
|
68
unleash-server/public/js/components/UnsavedFeature.jsx
Normal file
68
unleash-server/public/js/components/UnsavedFeature.jsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var UnsavedFeature = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<div className="bg-info new-feature-form row">
|
||||||
|
<form className="form-inline" role="form" ref="form">
|
||||||
|
<div className="checkbox col-md-1 text-right">
|
||||||
|
<input ref="enabled" type="checkbox" defaultValue={this.props.feature.enabled} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group col-md-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
id="name"
|
||||||
|
ref="name"
|
||||||
|
defaultValue={this.props.feature.name}
|
||||||
|
placeholder="Enter name" />
|
||||||
|
|
||||||
|
<input className="form-control"
|
||||||
|
type="text"
|
||||||
|
ref="description"
|
||||||
|
placeholder="Enter description" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group col-md-1 col-md-offset-5">
|
||||||
|
<select id="strategy"
|
||||||
|
ref="strategy"
|
||||||
|
className=""
|
||||||
|
defaultValue={this.props.feature.strategy}>
|
||||||
|
<option value="default">default</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group col-md-1">
|
||||||
|
<button className="btn btn-primary btn-xs" onClick={this.saveFeature}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="btn btn-xs" onClick={this.cancelFeature}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
saveFeature: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.props.feature.name = this.refs.name.getDOMNode().value;
|
||||||
|
this.props.feature.description = this.refs.description.getDOMNode().value;
|
||||||
|
this.props.feature.strategy = this.refs.strategy.getDOMNode().value;
|
||||||
|
this.props.feature.enabled = this.refs.enabled.getDOMNode().checked;
|
||||||
|
|
||||||
|
this.props.onSubmit(this.props.feature);
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelFeature: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onCancel(this.props.feature);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = UnsavedFeature;
|
@ -1,336 +0,0 @@
|
|||||||
/* jshint quotmark:false */
|
|
||||||
|
|
||||||
var React = require('react');
|
|
||||||
var reqwest = require('reqwest');
|
|
||||||
|
|
||||||
// Unleash
|
|
||||||
// - Menu
|
|
||||||
// - FeatureList
|
|
||||||
// - UnsavedFeature
|
|
||||||
// - SavedFeature
|
|
||||||
//
|
|
||||||
|
|
||||||
var Timer = function(cb, interval) {
|
|
||||||
this.cb = cb;
|
|
||||||
this.interval = interval;
|
|
||||||
this.timerId = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
Timer.prototype.start = function() {
|
|
||||||
if (this.timerId != null) {
|
|
||||||
console.warn("timer already started");
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('starting timer');
|
|
||||||
this.timerId = setInterval(this.cb, this.interval);
|
|
||||||
};
|
|
||||||
|
|
||||||
Timer.prototype.stop = function() {
|
|
||||||
if (this.timerId == null) {
|
|
||||||
console.warn('no timer running');
|
|
||||||
} else {
|
|
||||||
console.log('stopping timer');
|
|
||||||
clearInterval(this.timerId);
|
|
||||||
this.timerId = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var Menu = React.createClass({
|
|
||||||
render: function() { return (
|
|
||||||
<nav className='navbar navbar-default' role='navigation'>
|
|
||||||
<div className='container'>
|
|
||||||
<a className='navbar-brand' href='#'>unleash admin</a>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var UnsavedFeature = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div className="bg-info new-feature-form row">
|
|
||||||
<form className="form-inline" role="form" ref="form">
|
|
||||||
<div className="checkbox col-md-1 text-right">
|
|
||||||
<input ref="enabled" type="checkbox" defaultValue={this.props.feature.enabled} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group col-md-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
id="name"
|
|
||||||
ref="name"
|
|
||||||
defaultValue={this.props.feature.name}
|
|
||||||
placeholder="Enter name" />
|
|
||||||
|
|
||||||
<input className="form-control"
|
|
||||||
type="text"
|
|
||||||
ref="description"
|
|
||||||
placeholder="Enter description" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group col-md-1 col-md-offset-5">
|
|
||||||
<select id="strategy"
|
|
||||||
ref="strategy"
|
|
||||||
className=""
|
|
||||||
defaultValue={this.props.feature.strategy}>
|
|
||||||
<option value="default">default</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group col-md-1">
|
|
||||||
<button className="btn btn-primary btn-xs" onClick={this.saveFeature}>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button className="btn btn-xs" onClick={this.cancelFeature}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
saveFeature: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
this.props.feature.name = this.refs.name.getDOMNode().value;
|
|
||||||
this.props.feature.description = this.refs.description.getDOMNode().value;
|
|
||||||
this.props.feature.strategy = this.refs.strategy.getDOMNode().value;
|
|
||||||
this.props.feature.enabled = this.refs.enabled.getDOMNode().checked;
|
|
||||||
|
|
||||||
this.props.onSubmit(this.props.feature);
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelFeature: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onCancel(this.props.feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var SavedFeature = React.createClass({
|
|
||||||
onChange: function(event) {
|
|
||||||
this.props.onChange({
|
|
||||||
name: this.props.feature.name,
|
|
||||||
field: 'enabled',
|
|
||||||
value: event.target.checked
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div className='row'>
|
|
||||||
<div className='col-md-1 text-right'>
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
checked={this.props.feature.enabled}
|
|
||||||
onChange={this.onChange} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className='col-md-4'
|
|
||||||
title='{this.props.feature.description}'>{this.props.feature.name}</div>
|
|
||||||
<div className='col-md-2 col-md-offset-5'>
|
|
||||||
{this.props.feature.strategy}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var FeatureList = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
var featureNodes = [];
|
|
||||||
|
|
||||||
this.props.unsavedFeatures.forEach(function(feature, idx) {
|
|
||||||
var key = 'new-' + idx;
|
|
||||||
featureNodes.push(
|
|
||||||
<UnsavedFeature
|
|
||||||
key={key}
|
|
||||||
feature={feature}
|
|
||||||
onSubmit={this.props.onFeatureSubmit}
|
|
||||||
onCancel={this.props.onFeatureCancel} />
|
|
||||||
);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
this.props.savedFeatures.forEach(function(feature) {
|
|
||||||
featureNodes.push(
|
|
||||||
<SavedFeature
|
|
||||||
key={feature.name}
|
|
||||||
feature={feature}
|
|
||||||
onChange={this.props.onFeatureChanged} />
|
|
||||||
);
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<div className='panel panel-primary'>
|
|
||||||
<div className='panel-heading'>
|
|
||||||
<h3 className='panel-title'>Features</h3>
|
|
||||||
<div className='text-right'>
|
|
||||||
<button type="button"
|
|
||||||
className="btn btn-default btn-sm"
|
|
||||||
onClick={this.props.onNewFeature}>
|
|
||||||
<span className="glyphicon glyphicon-plus"></span> New
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='panel-body'>
|
|
||||||
{featureNodes}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var ErrorMessages = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
if (!this.props.errors.length) {
|
|
||||||
return <div/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorNodes = this.props.errors.map(function(e) {
|
|
||||||
return (<li key={e}>{e}</li>);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="alert alert-danger" role="alert">
|
|
||||||
<ul>{errorNodes}</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var Unleash = React.createClass({
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
savedFeatures: [],
|
|
||||||
unsavedFeatures: [],
|
|
||||||
errors: [],
|
|
||||||
timer: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function () {
|
|
||||||
this.loadFeaturesFromServer();
|
|
||||||
this.state.timer = new Timer(this.loadFeaturesFromServer, this.props.pollInterval);
|
|
||||||
this.state.timer.start();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function () {
|
|
||||||
if (this.state.timer != null) {
|
|
||||||
this.state.timer.stop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadFeaturesFromServer: function () {
|
|
||||||
reqwest('features').then(this.setFeatures, this.handleError);
|
|
||||||
},
|
|
||||||
|
|
||||||
setFeatures: function (data) {
|
|
||||||
this.setState({savedFeatures: data.features});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleError: function (error) {
|
|
||||||
this.state.errors.push(error);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFeature: function (changeRequest) {
|
|
||||||
var newFeatures = this.state.savedFeatures;
|
|
||||||
newFeatures.forEach(function(f){
|
|
||||||
if(f.name === changeRequest.name) {
|
|
||||||
f[changeRequest.field] = changeRequest.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({features: newFeatures});
|
|
||||||
this.state.timer.stop();
|
|
||||||
|
|
||||||
reqwest({
|
|
||||||
url: 'features/' + changeRequest.name,
|
|
||||||
method: 'patch',
|
|
||||||
type: 'json',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify(changeRequest)
|
|
||||||
}).then(function() {
|
|
||||||
// all good
|
|
||||||
this.state.timer.start();
|
|
||||||
}.bind(this), this.handleError);
|
|
||||||
},
|
|
||||||
|
|
||||||
createFeature: function (feature) {
|
|
||||||
var unsaved = [], state = this.state;
|
|
||||||
|
|
||||||
this.state.unsavedFeatures.forEach(function(f) {
|
|
||||||
// TODO: make sure we don't overwrite an existing feature
|
|
||||||
if (f.name === feature.name) {
|
|
||||||
state.savedFeatures.unshift(f);
|
|
||||||
} else {
|
|
||||||
unsaved.push(f);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({unsavedFeatures: unsaved});
|
|
||||||
|
|
||||||
reqwest({
|
|
||||||
url: 'features',
|
|
||||||
method: 'post',
|
|
||||||
type: 'json',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify(feature)
|
|
||||||
}).then(function(r) {
|
|
||||||
console.log(r.statusText);
|
|
||||||
}.bind(this), this.handleError);
|
|
||||||
},
|
|
||||||
|
|
||||||
newFeature: function() {
|
|
||||||
var blankFeature = {
|
|
||||||
name: '',
|
|
||||||
enabled: false,
|
|
||||||
strategy: 'default',
|
|
||||||
parameters: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.state.unsavedFeatures.push(blankFeature);
|
|
||||||
this.forceUpdate();
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelNewFeature: function (feature) {
|
|
||||||
var unsaved = [];
|
|
||||||
|
|
||||||
this.state.unsavedFeatures.forEach(function (f) {
|
|
||||||
if (f.name !== feature.name) {
|
|
||||||
unsaved.push(f);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({unsavedFeatures: unsaved});
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Menu />
|
|
||||||
<ErrorMessages errors={this.state.errors} />
|
|
||||||
<FeatureList
|
|
||||||
unsavedFeatures={this.state.unsavedFeatures}
|
|
||||||
savedFeatures={this.state.savedFeatures}
|
|
||||||
onFeatureChanged={this.updateFeature}
|
|
||||||
onFeatureSubmit={this.createFeature}
|
|
||||||
onFeatureCancel={this.cancelNewFeature}
|
|
||||||
onNewFeature={this.newFeature}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
React.render(
|
|
||||||
<Unleash pollInterval={5000} />,
|
|
||||||
document.getElementById('content')
|
|
||||||
);
|
|
26
unleash-server/public/js/utils/Timer.js
Normal file
26
unleash-server/public/js/utils/Timer.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
var Timer = function(cb, interval) {
|
||||||
|
this.cb = cb;
|
||||||
|
this.interval = interval;
|
||||||
|
this.timerId = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Timer.prototype.start = function() {
|
||||||
|
if (this.timerId != null) {
|
||||||
|
console.warn("timer already started");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('starting timer');
|
||||||
|
this.timerId = setInterval(this.cb, this.interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
Timer.prototype.stop = function() {
|
||||||
|
if (this.timerId == null) {
|
||||||
|
console.warn('no timer running');
|
||||||
|
} else {
|
||||||
|
console.log('stopping timer');
|
||||||
|
clearInterval(this.timerId);
|
||||||
|
this.timerId = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Timer;
|
@ -3,7 +3,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
context: __dirname + '/public',
|
context: __dirname + '/public',
|
||||||
entry: './js/unleash',
|
entry: './js/app',
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
path: __dirname + '/public/js',
|
path: __dirname + '/public/js',
|
||||||
|
Loading…
Reference in New Issue
Block a user