mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-08 01:15:49 +02:00
remove old unleash-frontend from master
This commit is contained in:
parent
46c2809120
commit
43d20ee75c
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["es2015", "stage-2", "react"]
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"finn",
|
|
||||||
"finn/node"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
// preprocessor.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const ReactTools = require('react-tools');
|
|
||||||
module.exports = {
|
|
||||||
process (src) {
|
|
||||||
return ReactTools.transform(src);
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
publicFolder: path.join(__dirname, '..', 'public'),
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const server = require('unleash-api');
|
|
||||||
|
|
||||||
const unleash = server.start({});
|
|
||||||
const app = unleash.app;
|
|
||||||
const config = unleash.config;
|
|
||||||
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const webpackDevMiddleware = require('webpack-dev-middleware');
|
|
||||||
const webpackConfig = require('./webpack.config');
|
|
||||||
const compiler = webpack(webpackConfig);
|
|
||||||
|
|
||||||
app.use(config.baseUriPath, webpackDevMiddleware(compiler, {
|
|
||||||
publicPath: '/js',
|
|
||||||
noInfo: true,
|
|
||||||
}));
|
|
@ -1,79 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "unleash-frontend",
|
|
||||||
"description": "unleash your features",
|
|
||||||
"version": "1.0.0-alpha.2",
|
|
||||||
"keywords": [
|
|
||||||
"unleash",
|
|
||||||
"feature toggle",
|
|
||||||
"feature",
|
|
||||||
"toggle"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"public",
|
|
||||||
"README.md",
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "ssh://git@github.com:finn-no/unleash.git"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/finn-no/unleash/issues"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "6"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "webpack -p",
|
|
||||||
"start": "NODE_ENV=development supervisor --ignore ./node_modules/,./public/js lib/server-dev.js",
|
|
||||||
"test": "jest",
|
|
||||||
"test:ci": "npm run test",
|
|
||||||
"prepublish": "npm run build"
|
|
||||||
},
|
|
||||||
"main": "./lib/index.js",
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": "^3.5.0",
|
|
||||||
"moment": "^2.13.0",
|
|
||||||
"react": "^0.13.1",
|
|
||||||
"react-router": "^0.13.2",
|
|
||||||
"reflux": "^0.2.10",
|
|
||||||
"reqwest": "^2.0.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-core": "^6.9.1",
|
|
||||||
"babel-loader": "^6.2.4",
|
|
||||||
"babel-preset-es2015": "^6.9.0",
|
|
||||||
"babel-preset-react": "^6.5.0",
|
|
||||||
"babel-preset-stage-2": "^6.5.0",
|
|
||||||
"chai": "3.5.0",
|
|
||||||
"coveralls": "^2.11.9",
|
|
||||||
"istanbul": "^0.4.3",
|
|
||||||
"jest-cli": "0.5.8",
|
|
||||||
"mocha": "^2.4.5",
|
|
||||||
"mocha-lcov-reporter": "1.2.0",
|
|
||||||
"nsp": "^2.3.2",
|
|
||||||
"react-tools": "^0.13.1",
|
|
||||||
"supertest": "^1.2.0",
|
|
||||||
"supervisor": "^0.10.0",
|
|
||||||
"unleash-api": "1.0.0-alpha.2",
|
|
||||||
"webpack": "^1.13.2",
|
|
||||||
"webpack-dev-middleware": "^1.6.1"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"scriptPreprocessor": "<rootDir>/jest-preprocessor.js",
|
|
||||||
"modulePathIgnorePatterns": [
|
|
||||||
"<rootDir>/node_modules/npm"
|
|
||||||
],
|
|
||||||
"unmockedModulePathPatterns": [
|
|
||||||
"<rootDir>/node_modules/react",
|
|
||||||
"<rootDir>/node_modules/reflux"
|
|
||||||
],
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"jsx",
|
|
||||||
"js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pre-commit": [
|
|
||||||
"lint"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"finn",
|
|
||||||
"finn-react"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"commonjs": true
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"react/sort-comp": "off"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 34 KiB |
@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>unleash admin</title>
|
|
||||||
<meta name="description" content="unleash">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="//finncdn.no/bb/css/so/5.5.28/so.min.css">
|
|
||||||
<link rel="stylesheet" href="unleash.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content"></div>
|
|
||||||
<script src="bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,99 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const Router = require('react-router');
|
|
||||||
const Menu = require('./components/Menu');
|
|
||||||
const ErrorMessages = require('./components/ErrorMessages');
|
|
||||||
const initalizer = require('./stores/initalizer');
|
|
||||||
const FeatureToggleStore = require('./stores/FeatureToggleStore');
|
|
||||||
const StrategyStore = require('./stores/StrategyStore');
|
|
||||||
const ArchiveStore = require('./stores/ArchivedToggleStore');
|
|
||||||
const Link = Router.Link;
|
|
||||||
const RouteHandler = Router.RouteHandler;
|
|
||||||
|
|
||||||
const UnleashApp = React.createClass({
|
|
||||||
contextTypes: {
|
|
||||||
router: React.PropTypes.func,
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
features: FeatureToggleStore.getFeatureToggles(),
|
|
||||||
strategies: StrategyStore.getStrategies(),
|
|
||||||
archivedFeatures: ArchiveStore.getArchivedToggles(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onFeatureToggleChange () {
|
|
||||||
this.setState({
|
|
||||||
features: FeatureToggleStore.getFeatureToggles(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onStrategiesChange () {
|
|
||||||
this.setState({
|
|
||||||
strategies: StrategyStore.getStrategies(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onArchiveChange () {
|
|
||||||
this.setState({
|
|
||||||
archivedFeatures: ArchiveStore.getArchivedToggles(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.unsubscribeFS = FeatureToggleStore.listen(this.onFeatureToggleChange);
|
|
||||||
this.unsubscribeSS = StrategyStore.listen(this.onStrategiesChange);
|
|
||||||
this.unsubscribeAS = ArchiveStore.listen(this.onArchiveChange);
|
|
||||||
},
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.unsubscribeFS();
|
|
||||||
this.unsubscribeSS();
|
|
||||||
this.unsubscribeAS();
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
initalizer(30);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderLink (id, label) {
|
|
||||||
return (
|
|
||||||
<Link to={id} className="nav-element centerify" activeClassName="nav-active">
|
|
||||||
<span className="topbar-nav-svg-caption caption showbydefault no-break">{label}</span>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Menu>
|
|
||||||
{this.renderLink('features', 'Toggles')}
|
|
||||||
{this.renderLink('strategies', 'Strategies')}
|
|
||||||
{this.renderLink('log', 'Log')}
|
|
||||||
{this.renderLink('archive', 'Archive')}
|
|
||||||
</Menu>
|
|
||||||
<div className="container">
|
|
||||||
<div className="page">
|
|
||||||
<ErrorMessages />
|
|
||||||
<div className="mod shadow mrn pan">
|
|
||||||
<div className="inner pan">
|
|
||||||
<div className="bd">
|
|
||||||
<RouteHandler
|
|
||||||
features={this.state.features}
|
|
||||||
strategies={this.state.strategies}
|
|
||||||
archivedFeatures={this.state.archivedFeatures}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = UnleashApp;
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"finn",
|
|
||||||
"finn/node"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"mocha": true
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"it": false,
|
|
||||||
"jest": false,
|
|
||||||
"beforeEach": false,
|
|
||||||
"expect": false,
|
|
||||||
"describe": false,
|
|
||||||
"afterEach": false
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
/** @jsx React.DOM */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
jest.dontMock('../../components/Menu');
|
|
||||||
|
|
||||||
const Menu = require('../../components/Menu');
|
|
||||||
const React = require('react/addons');
|
|
||||||
const TestUtils = React.addons.TestUtils;
|
|
||||||
|
|
||||||
describe('Menu test', () => {
|
|
||||||
it('should include unleash in menu', () => {
|
|
||||||
const Compononent = TestUtils .renderIntoDocument(<Menu />);
|
|
||||||
expect(Compononent.getDOMNode().textContent).toMatch('unleash');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,41 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
jest.dontMock('../../../components/feature/ArchiveFeatureComponent');
|
|
||||||
jest.mock('../../../stores/FeatureToggleActions');
|
|
||||||
jest.autoMockOff();
|
|
||||||
|
|
||||||
const React = require('react/addons');
|
|
||||||
const TestUtils = React.addons.TestUtils;
|
|
||||||
const FeatureArchive = require('../../../components/feature/ArchiveFeatureComponent');
|
|
||||||
const FeatureActions = require('../../../stores/FeatureToggleActions');
|
|
||||||
|
|
||||||
describe('FeatureForm', () => {
|
|
||||||
let Component;
|
|
||||||
beforeEach(() => {
|
|
||||||
const archivedToggles = [
|
|
||||||
{ name: 'featureX' },
|
|
||||||
{ name: 'featureY' },
|
|
||||||
];
|
|
||||||
|
|
||||||
Component = TestUtils.renderIntoDocument(
|
|
||||||
<FeatureArchive archivedFeatures={archivedToggles} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
React.unmountComponentAtNode(document.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render two archived features', () => {
|
|
||||||
const rows = Component.getDOMNode().querySelectorAll('tbody tr');
|
|
||||||
|
|
||||||
expect(rows.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should revive archived feature toggle', () => {
|
|
||||||
const button = Component.getDOMNode().querySelector('tbody button');
|
|
||||||
TestUtils.Simulate.click(button);
|
|
||||||
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(FeatureActions.revive.triggerPromise).toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,36 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
jest.dontMock('../../../components/feature/FeatureForm');
|
|
||||||
|
|
||||||
const React = require('react/addons');
|
|
||||||
const TestUtils = React.addons.TestUtils;
|
|
||||||
const FeatureForm = require('../../../components/feature/FeatureForm');
|
|
||||||
|
|
||||||
describe('FeatureForm', () => {
|
|
||||||
let Component;
|
|
||||||
const strategies = [
|
|
||||||
{ name: 'default' },
|
|
||||||
];
|
|
||||||
afterEach(() => {
|
|
||||||
React.unmountComponentAtNode(document.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('new', () => {
|
|
||||||
it('should render empty form', () => {
|
|
||||||
Component = TestUtils .renderIntoDocument(<FeatureForm strategies={strategies} />);
|
|
||||||
const value = Component.getDOMNode().querySelectorAll('input');
|
|
||||||
expect(value[0].value).toEqual('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('edit', () => {
|
|
||||||
const feature = { name: 'Test', strategy: 'unknown' };
|
|
||||||
|
|
||||||
it('should show unknown strategy as default', () => {
|
|
||||||
Component = TestUtils .renderIntoDocument(<FeatureForm feature={feature} strategies={strategies} />);
|
|
||||||
|
|
||||||
const strategySelect = Component.getDOMNode().querySelector('select');
|
|
||||||
expect(strategySelect.value).toEqual('default');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,59 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
jest.dontMock('../../../components/feature/FeatureList');
|
|
||||||
jest.dontMock('../../../components/feature/Feature');
|
|
||||||
|
|
||||||
const React = require('react/addons');
|
|
||||||
const TestUtils = React.addons.TestUtils;
|
|
||||||
const FeatureList = require('../../../components/feature/FeatureList');
|
|
||||||
|
|
||||||
describe('FeatureList', () => {
|
|
||||||
let Component;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const features = [
|
|
||||||
{ name: 'featureX', strategy: 'other' },
|
|
||||||
{ name: 'group.featureY', strategy: 'default' },
|
|
||||||
];
|
|
||||||
const strategies = [
|
|
||||||
{ name: 'default' },
|
|
||||||
];
|
|
||||||
Component = TestUtils .renderIntoDocument(<FeatureList features={features} strategies={strategies} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
React.unmountComponentAtNode(document.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render all features', () => {
|
|
||||||
const featuresElement = Component.getDOMNode().querySelectorAll('.feature');
|
|
||||||
expect(featuresElement.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter list of features', () => {
|
|
||||||
const filterNode = Component.refs.filter.getDOMNode();
|
|
||||||
TestUtils.Simulate.change(filterNode, { target: { value: 'group' } });
|
|
||||||
|
|
||||||
const featuresElement = Component.getDOMNode().querySelectorAll('.feature');
|
|
||||||
expect(featuresElement.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter list of features ignoring case', () => {
|
|
||||||
const filterNode = Component.refs.filter.getDOMNode();
|
|
||||||
TestUtils.Simulate.change(filterNode, { target: { value: 'GROUP' } });
|
|
||||||
|
|
||||||
const featuresElement = Component.getDOMNode().querySelectorAll('.feature');
|
|
||||||
expect(featuresElement.length).toEqual(1);
|
|
||||||
expect(featuresElement[0].textContent).toMatch('group');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter list of features by strategy name', () => {
|
|
||||||
const searchString = 'other';
|
|
||||||
const filterNode = Component.refs.filter.getDOMNode();
|
|
||||||
TestUtils.Simulate.change(filterNode, { target: { value: searchString } });
|
|
||||||
|
|
||||||
const featuresElement = Component.getDOMNode().querySelectorAll('.feature');
|
|
||||||
expect(featuresElement.length).toEqual(1);
|
|
||||||
expect(featuresElement[0].textContent).toMatch(searchString);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,79 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
jest.autoMockOff();
|
|
||||||
jest.dontMock('../../stores/FeatureToggleActions');
|
|
||||||
jest.dontMock('../../stores/FeatureToggleStore');
|
|
||||||
|
|
||||||
describe('FeatureToggleStore', () => {
|
|
||||||
let Actions;
|
|
||||||
let Store;
|
|
||||||
let toggles;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
Actions = require('../../stores/FeatureToggleActions');
|
|
||||||
Store = require('../../stores/FeatureToggleStore');
|
|
||||||
toggles = [
|
|
||||||
{ name: 'app.feature', enabled: true, strategy: 'default' },
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be an empty store', () => {
|
|
||||||
expect(Store.getFeatureToggles().length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should inititialize the store', () => {
|
|
||||||
Actions.init.completed(toggles);
|
|
||||||
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(Store.getFeatureToggles().length).toBe(1);
|
|
||||||
expect(Store.getFeatureToggles()[0].name).toEqual('app.feature');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a another toggle', () => {
|
|
||||||
Actions.init.completed(toggles);
|
|
||||||
|
|
||||||
const newToggle = { name: 'app.featureB', enabled: true, strategy: 'default' };
|
|
||||||
|
|
||||||
Actions.create.completed(newToggle);
|
|
||||||
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(Store.getFeatureToggles().length).toBe(2);
|
|
||||||
expect(Store.getFeatureToggles()[1].name).toEqual('app.featureB');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should archive toggle', () => {
|
|
||||||
Actions.init.completed(toggles);
|
|
||||||
|
|
||||||
Actions.archive.completed(toggles[0]);
|
|
||||||
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(Store.getFeatureToggles().length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep toggles in sorted order', () => {
|
|
||||||
Actions.init.completed([
|
|
||||||
{ name: 'A' },
|
|
||||||
{ name: 'B' },
|
|
||||||
{ name: 'C' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
Actions.create.completed({ name: 'AA' });
|
|
||||||
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(Store.getFeatureToggles()[0].name).toEqual('A');
|
|
||||||
expect(Store.getFeatureToggles()[1].name).toEqual('AA');
|
|
||||||
expect(Store.getFeatureToggles()[3].name).toEqual('C');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update toggle', () => {
|
|
||||||
Actions.init.completed(toggles);
|
|
||||||
const toggle = toggles[0];
|
|
||||||
|
|
||||||
toggle.enabled = false;
|
|
||||||
Actions.update.completed(toggle);
|
|
||||||
|
|
||||||
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(Store.getFeatureToggles()[0].enabled).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,12 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const Router = require('react-router');
|
|
||||||
const UserStore = require('./stores/UserStore');
|
|
||||||
const routes = require('./routes');
|
|
||||||
|
|
||||||
UserStore.init();
|
|
||||||
|
|
||||||
Router.run(routes, Handler => {
|
|
||||||
React.render(<Handler/>, document.getElementById('content'));
|
|
||||||
});
|
|
@ -1,40 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const Ui = require('./ErrorMessages.ui');
|
|
||||||
const ErrorStore = require('../stores/ErrorStore');
|
|
||||||
const ErrorActions = require('../stores/ErrorActions');
|
|
||||||
|
|
||||||
const ErrorMessages = React.createClass({
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
errors: ErrorStore.getErrors(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onStoreChange () {
|
|
||||||
this.setState({
|
|
||||||
errors: ErrorStore.getErrors(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.unsubscribe = ErrorStore.listen(this.onStoreChange);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.unsubscribe();
|
|
||||||
},
|
|
||||||
|
|
||||||
onClearErrors () {
|
|
||||||
ErrorActions.clear();
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<Ui errors={this.state.errors} onClearErrors={this.onClearErrors} />
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = ErrorMessages;
|
|
@ -1,35 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
|
|
||||||
const ErrorMessages = React.createClass({
|
|
||||||
render () {
|
|
||||||
if (!this.props.errors.length) {
|
|
||||||
return <div/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorNodes = this.props.errors.map((e, i) => <li key={e + i} className="largetext">{e}</li>);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container">
|
|
||||||
<div className="mod shadow mtm mrn">
|
|
||||||
<div className="inner bg-red-lt">
|
|
||||||
<div className="bd">
|
|
||||||
<div className="media centerify">
|
|
||||||
<div className="imgExt">
|
|
||||||
<a onClick={this.props.onClearErrors}
|
|
||||||
className="icon-kryss1 linkblock sharp" />
|
|
||||||
</div>
|
|
||||||
<div className="bd">
|
|
||||||
<ul>{errorNodes}</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = ErrorMessages;
|
|
@ -1,65 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const User = require('./User');
|
|
||||||
|
|
||||||
const Menu = React.createClass({
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="topbar mbl">
|
|
||||||
<div className="container">
|
|
||||||
<div className="page">
|
|
||||||
<div className="fright-ht768">
|
|
||||||
<User />
|
|
||||||
</div>
|
|
||||||
<div className="nav-level1 h4">
|
|
||||||
<a href="#" className="homelink pln">
|
|
||||||
<span className="topbar-nav-svg-home">
|
|
||||||
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53M
|
|
||||||
y5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MjcuNDExIiBoZWlnaHQ9IjE2OS4z
|
|
||||||
OTgiIHZpZXdCb3g9IjAgMCA1MjcuNDExIDE2OS4zOTgiPjxwYXRoIGZpbGw
|
|
||||||
9IiNmZmYiIGQ9Ik00NjguNTA3IDBoLTI1Ni4xODdjLTIxLjcwNyAwLTQwLj
|
|
||||||
Y5NSAxMS44MTItNTAuOTEyIDI5LjMzNy0xMC4yMTYtMTcuNTI1LTI5LjIwN
|
|
||||||
C0yOS4zMzctNTAuOTExLTI5LjMzN2gtNTEuNTk1Yy0zMi40NzkgMC01OC45
|
|
||||||
MDIgMjYuNDI1LTU4LjkwMiA1OC45MDV2NTEuNTg3YzAgMzIuNDgxIDI2LjQ
|
|
||||||
yMyA1OC45MDYgNTguOTAyIDU4LjkwNmg0MDkuNjA1YzMyLjQ3OSAwIDU4Lj
|
|
||||||
kwMy0yNi40MjUgNTguOTAzLTU4LjkwNnYtNTEuNTg3Yy4wMDEtMzIuNDgtM
|
|
||||||
jYuNDIzLTU4LjkwNS01OC45MDMtNTguOTA1eiIvPjxwYXRoIGZpbGw9IiMw
|
|
||||||
OWYiIGQ9Ik00NjguNTA3IDE1My4zODNjMjMuNjg3IDAgNDIuODg4LTE5LjE
|
|
||||||
5OSA0Mi44ODgtNDIuODl2LTUxLjU4OGMwLTIzLjY5MS0xOS4yMDEtNDIuOD
|
|
||||||
ktNDIuODg4LTQyLjg5aC0yNTYuMTg3Yy0yMy42ODYgMC00Mi44ODcgMTkuM
|
|
||||||
Tk4LTQyLjg4NyA0Mi44OXY5NC40NzhoMjk5LjA3NHoiLz48cGF0aCBmaWxs
|
|
||||||
PSIjMDA2IiBkPSJNMTUzLjM4NCAxNTMuMzgzdi05NC40NzhjMC0yMy42OTE
|
|
||||||
tMTkuMjAxLTQyLjg5LTQyLjg4Ny00Mi44OWgtNTEuNTk1Yy0yMy42ODYgMC
|
|
||||||
00Mi44ODcgMTkuMTk4LTQyLjg4NyA0Mi44OXY1MS41ODdjMCAyMy42OTEgM
|
|
||||||
TkuMjAxIDQyLjg5IDQyLjg4NyA0Mi44OWg5NC40ODJ6Ii8%2BPHJlY3QgeD
|
|
||||||
0iMzIwLjE1NiIgeT0iNzUuMjc1IiBmaWxsPSIjZmZmIiB3aWR0aD0iMTkuN
|
|
||||||
jIxIiBoZWlnaHQ9IjUzLjIxMSIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0y
|
|
||||||
NjIuOTEyIDg2LjI4MWMwLTUuNTI5IDMuODEzLTExLjAwNiAxMy4wNjktMTE
|
|
||||||
uMDA2aDI4LjQyMXYxNS42MTNoLTE4LjYxMmMtMi40OTggMC0zLjI1NS45OT
|
|
||||||
ItMy4yNTUgMi42NjR2Ny40NzJoMjEuODY3djE1LjYxaC0yMS44Njd2MTEuO
|
|
||||||
DUyaC0xOS42MjN2LTQyLjIwNXpNMzc1LjE2NSA5MS4wOTloMTAuMzk5YzIu
|
|
||||||
NDA5IDAgMy4yNDYuODMyIDMuMjQ2IDMuMjM1bC0uMDA4IDM0LjE1MmgxOS4
|
|
||||||
2MzJ2LTQxLjk5NmMwLTUuNTI3LTMuODE1LTExLjAwNC0xMy4wNjktMTEuMD
|
|
||||||
A0aC0zOS44MjRsLS4wMSA1M2gxOS42MzR2LTM3LjM4N3pNNDQyLjcxOSA5M
|
|
||||||
S4wOTloMTAuNGMyLjQwOCAwIDMuMjQ1LjgzMiAzLjI0NSAzLjIzNWwtLjAw
|
|
||||||
OSAzNC4xNTJoMTkuNjM0di00MS45OTZjMC01LjUyNy0zLjgxNS0xMS4wMDQ
|
|
||||||
tMTMuMDctMTEuMDA0aC0zOS44MjNsLS4wMSA1M2gxOS42MzN2LTM3LjM4N3
|
|
||||||
oiLz48L3N2Zz4%3D" width="106" height="34" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
className="topbar-nav-svg-caption caption showbydefault hide-lt900">
|
|
||||||
unleash admin
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Menu;
|
|
@ -1,25 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const UserStore = require('../stores/UserStore');
|
|
||||||
|
|
||||||
const User = React.createClass({
|
|
||||||
|
|
||||||
onSave () {
|
|
||||||
const value = this.refs.username.getDOMNode().value.trim();
|
|
||||||
UserStore.set(value);
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="r-pvm">
|
|
||||||
<input type="text" placeholder="username"
|
|
||||||
ref="username"
|
|
||||||
defaultValue={UserStore.get()}
|
|
||||||
onBlur={this.onSave} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = User;
|
|
@ -1,48 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const FeatureActions = require('../../stores/FeatureToggleActions');
|
|
||||||
|
|
||||||
const ArchiveFeatureComponent = React.createClass({
|
|
||||||
|
|
||||||
onRevive (item) {
|
|
||||||
FeatureActions.revive.triggerPromise(item);
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Archived Feature Toggles</h1>
|
|
||||||
<hr />
|
|
||||||
<table className="outerborder man">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.props.archivedFeatures.map(this.renderArchivedItem)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderArchivedItem (f) {
|
|
||||||
return (
|
|
||||||
<tr key={f.name}>
|
|
||||||
<td>
|
|
||||||
{f.name}<br />
|
|
||||||
<span className="opaque smalltext word-break">{f.description}</span>
|
|
||||||
</td>
|
|
||||||
<td className="rightify" width="150">
|
|
||||||
<button onClick={this.onRevive.bind(this, f)} title="Revive feature toggle">
|
|
||||||
<span className="icon-svar" />
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = ArchiveFeatureComponent;
|
|
@ -1,124 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const FeatureForm = require('./FeatureForm');
|
|
||||||
const LogEntryList = require('../log/LogEntryList');
|
|
||||||
const eventStore = require('../../stores/EventStore');
|
|
||||||
|
|
||||||
const Feature = React.createClass({
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
editMode: false,
|
|
||||||
showHistory: false,
|
|
||||||
events: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEventsResponse (response) {
|
|
||||||
this.setState({ events: response });
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleHistory () {
|
|
||||||
eventStore.getEventsByName(this.props.feature.name).then(this.handleEventsResponse);
|
|
||||||
this.setState({ showHistory: !this.state.showHistory });
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
toggleEditMode () {
|
|
||||||
this.setState({ editMode: !this.state.editMode });
|
|
||||||
},
|
|
||||||
|
|
||||||
saveFeature (feature) {
|
|
||||||
this.props.onChange(feature);
|
|
||||||
this.toggleEditMode();
|
|
||||||
},
|
|
||||||
|
|
||||||
archiveFeature () {
|
|
||||||
if (window.confirm(`Are you sure you want to delete ${this.props.feature.name}?`)) { // eslint-disable-line no-alert
|
|
||||||
this.props.onArchive(this.props.feature);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
renderEditMode () {
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td colSpan="4" className="pan man no-border">
|
|
||||||
<FeatureForm
|
|
||||||
feature={this.props.feature}
|
|
||||||
onSubmit={this.saveFeature}
|
|
||||||
onCancel={this.toggleEditMode}
|
|
||||||
strategies={this.props.strategies} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<tbody className="feature">
|
|
||||||
<tr className={this.state.editMode ? 'edit bg-lilac-xlt' : ''}>
|
|
||||||
<td width="20">
|
|
||||||
<span className=
|
|
||||||
{this.props.feature.enabled ? 'toggle-active' : 'toggle-inactive'} title="Status" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{this.props.feature.name} <br />
|
|
||||||
<span className="opaque smalltext word-break">
|
|
||||||
{this.props.feature.description || '\u00a0'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td className="hide-lt480">
|
|
||||||
{this.props.feature.strategy}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td width="150">
|
|
||||||
<div className="line">
|
|
||||||
<div className="unit size1of3">
|
|
||||||
<button
|
|
||||||
title="Archive"
|
|
||||||
onClick={this.archiveFeature}>
|
|
||||||
<span className="icon-kryss1" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="unit size1of3">
|
|
||||||
<button
|
|
||||||
className={this.state.editMode ? 'primary' : ''}
|
|
||||||
title="Edit"
|
|
||||||
onClick={this.toggleEditMode}>
|
|
||||||
<span className="icon-redigere" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="unit size1of3">
|
|
||||||
<button
|
|
||||||
className={this.state.showHistory ? 'primary' : ''}
|
|
||||||
title="History"
|
|
||||||
onClick={this.toggleHistory}>
|
|
||||||
<span className="icon-visning_liste" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{this.state.editMode ? this.renderEditMode() : this.renderEmptyRow()}
|
|
||||||
{this.state.showHistory ? this.renderHistory() : this.renderEmptyRow()}
|
|
||||||
</tbody>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderEmptyRow () {
|
|
||||||
return (<tr />);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderHistory () {
|
|
||||||
return (<tr>
|
|
||||||
<td colSpan="4" className="no-border">
|
|
||||||
<LogEntryList events={this.state.events} />
|
|
||||||
</td>
|
|
||||||
</tr>);
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Feature;
|
|
@ -1,175 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const TextInput = require('../form/TextInput');
|
|
||||||
|
|
||||||
const FeatureForm = React.createClass({
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
strategyOptions: this.props.strategies,
|
|
||||||
requiredParams: [],
|
|
||||||
currentStrategy: this.props.feature ? this.props.feature.strategy : 'default',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
if (this.props.feature) {
|
|
||||||
this.setSelectedStrategy(this.props.feature.strategy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onStrategyChange (e) {
|
|
||||||
this.setSelectedStrategy(e.target.value);
|
|
||||||
this.setState({ currentStrategy: e.target.value });
|
|
||||||
},
|
|
||||||
|
|
||||||
getParameterValue (feature) {
|
|
||||||
if (this.props.feature && this.props.feature.parameters) {
|
|
||||||
return this.props.feature.parameters[feature];
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedStrategy (strategyName) {
|
|
||||||
const selectedStrategy = this.props.strategies.filter(strategy => strategy.name === strategyName)[0];
|
|
||||||
|
|
||||||
if (selectedStrategy) {
|
|
||||||
this.setStrategyParams(selectedStrategy);
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
currentStrategy: 'default',
|
|
||||||
requiredParams: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setStrategyParams (strategy) {
|
|
||||||
const requiredParams = [];
|
|
||||||
let key;
|
|
||||||
for (key in strategy.parametersTemplate) {
|
|
||||||
if (Object.hasOwnProperty.call(strategy.parametersTemplate, key)) {
|
|
||||||
requiredParams.push({ name: key, value: this.getParameterValue(key) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({ requiredParams });
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const feature = this.props.feature || {
|
|
||||||
name: '',
|
|
||||||
strategy: 'default',
|
|
||||||
enabled: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const idPrefix = this.props.feature ? this.props.feature.name : 'new';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bg-lilac-xlt r-pam">
|
|
||||||
<form ref="form" className="r-size1of2">
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
{this.props.feature ? '' : <legend>Create new toggle</legend>}
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id={`${idPrefix}-name`}
|
|
||||||
name="name"
|
|
||||||
label="Name"
|
|
||||||
value={feature.name}
|
|
||||||
disabled={feature.name.length}
|
|
||||||
ref="name"
|
|
||||||
placeholder="Toggle name" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id={`${idPrefix}-description`}
|
|
||||||
name="description"
|
|
||||||
label="Description"
|
|
||||||
value={feature.description}
|
|
||||||
ref="description"
|
|
||||||
placeholder="Enter description" />
|
|
||||||
|
|
||||||
<div className="formelement">
|
|
||||||
<label htmlFor={`${idPrefix}-strategy`}>Strategy</label>
|
|
||||||
<div className="input select">
|
|
||||||
<select id={`${idPrefix}-strategy`}
|
|
||||||
ref="strategy"
|
|
||||||
value={this.state.currentStrategy}
|
|
||||||
onChange={this.onStrategyChange}>
|
|
||||||
{this.renderStrategyOptions()}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="formelement">
|
|
||||||
<div className="input">
|
|
||||||
<ul className="inputs-list">
|
|
||||||
<li>
|
|
||||||
<input id={`${idPrefix}-active`}
|
|
||||||
ref="enabled"
|
|
||||||
type="checkbox"
|
|
||||||
defaultChecked={feature.enabled} />
|
|
||||||
<label htmlFor={`${idPrefix}-active`}>
|
|
||||||
Active
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.renderStrategyProperties()}
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div className="actions">
|
|
||||||
<button className="primary mrs" onClick={this.saveFeature}>Save</button>
|
|
||||||
<button className="" onClick={this.cancelFeature}>Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderStrategyOptions () {
|
|
||||||
return this.props.strategies.map(strategy => <option key={strategy.name} value={strategy.name}>
|
|
||||||
{strategy.name}
|
|
||||||
</option>);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderStrategyProperties () {
|
|
||||||
return this.state.requiredParams.map(param => <TextInput
|
|
||||||
id={`param-${param.name}`}
|
|
||||||
key={`param-${param.name}`}
|
|
||||||
name={param.name}
|
|
||||||
label={param.name}
|
|
||||||
ref={param.name}
|
|
||||||
value={param.value} />);
|
|
||||||
},
|
|
||||||
|
|
||||||
saveFeature (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const feature = {
|
|
||||||
name: this.refs.name.getValue(),
|
|
||||||
description: this.refs.description.getValue(),
|
|
||||||
strategy: this.state.currentStrategy,
|
|
||||||
enabled: this.refs.enabled.getDOMNode().checked,
|
|
||||||
parameters: this.getParameters(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.props.onSubmit(feature);
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelFeature (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onCancel();
|
|
||||||
},
|
|
||||||
|
|
||||||
getParameters () {
|
|
||||||
const parameters = {};
|
|
||||||
this.state.requiredParams.forEach(param => {
|
|
||||||
parameters[param.name] = this.refs[param.name].getValue();
|
|
||||||
});
|
|
||||||
return parameters;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureForm;
|
|
@ -1,82 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const Feature = require('./Feature');
|
|
||||||
|
|
||||||
const noop = function () {};
|
|
||||||
|
|
||||||
const FeatureList = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
features: React.PropTypes.array.isRequired,
|
|
||||||
strategies: React.PropTypes.array.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps () {
|
|
||||||
return {
|
|
||||||
onFeatureChanged: noop,
|
|
||||||
onFeatureArchive: noop,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
filter: undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilterChange (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.setState({ filter: e.target.value.trim() });
|
|
||||||
},
|
|
||||||
|
|
||||||
filteredFeatures () {
|
|
||||||
if (this.state.filter) {
|
|
||||||
const regex = new RegExp(this.state.filter, 'i');
|
|
||||||
|
|
||||||
return this.props.features.filter(item => regex.test(item.name) || regex.test(item.strategy));
|
|
||||||
}
|
|
||||||
return this.props.features;
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const featureNodes = this.filteredFeatures().map(feature => <Feature
|
|
||||||
key={feature.name}
|
|
||||||
feature={feature}
|
|
||||||
onChange={this.props.onFeatureChanged}
|
|
||||||
onArchive={this.props.onFeatureArchive}
|
|
||||||
strategies={this.props.strategies}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="">
|
|
||||||
<table className="outerborder man">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th />
|
|
||||||
<th>Name</th>
|
|
||||||
<th className="hide-lt480">Strategy</th>
|
|
||||||
<th />
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colSpan="4">
|
|
||||||
<input
|
|
||||||
name="filter"
|
|
||||||
ref="filter"
|
|
||||||
type="text"
|
|
||||||
placeholder="Filter by name or strategy"
|
|
||||||
onChange={this.onFilterChange}
|
|
||||||
value={this.state.filter}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
{featureNodes}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureList;
|
|
@ -1,70 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const FeatureList = require('./FeatureList');
|
|
||||||
const FeatureForm = require('./FeatureForm');
|
|
||||||
const FeatureActions = require('../../stores/FeatureToggleActions');
|
|
||||||
const ErrorActions = require('../../stores/ErrorActions');
|
|
||||||
|
|
||||||
const FeatureTogglesComponent = React.createClass({
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
createView: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFeature (feature) {
|
|
||||||
FeatureActions.update.triggerPromise(feature);
|
|
||||||
},
|
|
||||||
|
|
||||||
archiveFeature (feature) {
|
|
||||||
FeatureActions.archive.triggerPromise(feature);
|
|
||||||
},
|
|
||||||
|
|
||||||
createFeature (feature) {
|
|
||||||
FeatureActions.create.triggerPromise(feature)
|
|
||||||
.then(this.cancelNewFeature);
|
|
||||||
},
|
|
||||||
|
|
||||||
newFeature () {
|
|
||||||
this.setState({ createView: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelNewFeature () {
|
|
||||||
this.setState({ createView: false });
|
|
||||||
ErrorActions.clear();
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<h1>Feature Toggles</h1>
|
|
||||||
|
|
||||||
{this.state.createView ? this.renderCreateView() : this.renderCreateButton()}
|
|
||||||
|
|
||||||
<FeatureList
|
|
||||||
features={this.props.features}
|
|
||||||
onFeatureChanged={this.updateFeature}
|
|
||||||
onFeatureArchive={this.archiveFeature}
|
|
||||||
onFeatureSubmit={this.createFeature}
|
|
||||||
onFeatureCancel={this.cancelNewFeature}
|
|
||||||
onNewFeature={this.newFeature}
|
|
||||||
strategies={this.props.strategies} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderCreateView () {
|
|
||||||
return (<FeatureForm
|
|
||||||
onCancel={this.cancelNewFeature}
|
|
||||||
onSubmit={this.createFeature}
|
|
||||||
strategies={this.props.strategies} />);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderCreateButton () {
|
|
||||||
return <button className="mal" onClick={this.newFeature}>Create feature toggle</button>;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureTogglesComponent;
|
|
@ -1,48 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
|
|
||||||
const TextInput = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
name: React.PropTypes.string.isRequired,
|
|
||||||
label: React.PropTypes.string.isRequired,
|
|
||||||
id: React.PropTypes.string.isRequired,
|
|
||||||
placeholder: React.PropTypes.string,
|
|
||||||
value: React.PropTypes.string,
|
|
||||||
required: React.PropTypes.bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps () {
|
|
||||||
return {
|
|
||||||
required: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState () {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
|
|
||||||
getValue () {
|
|
||||||
return this.refs.input.getDOMNode().value.trim();
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="formelement required">
|
|
||||||
<label htmlFor={this.props.id} className="t4">{this.props.label}</label>
|
|
||||||
<div className="input">
|
|
||||||
<input type="text"
|
|
||||||
id={this.props.id}
|
|
||||||
name={this.props.name}
|
|
||||||
defaultValue={this.props.value}
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
ref="input" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = TextInput;
|
|
@ -1,37 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const LogEntryList = require('./LogEntryList');
|
|
||||||
const eventStore = require('../../stores/EventStore');
|
|
||||||
const ErrorActions = require('../../stores/ErrorActions');
|
|
||||||
|
|
||||||
const LogEntriesComponent = React.createClass({
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
createView: false,
|
|
||||||
events: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
eventStore.getEvents().then(res => {
|
|
||||||
this.setState({ events: res.events });
|
|
||||||
}, this.initError);
|
|
||||||
},
|
|
||||||
|
|
||||||
initError () {
|
|
||||||
ErrorActions.error('Could not load events from server');
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Log</h1>
|
|
||||||
<hr />
|
|
||||||
<LogEntryList events={this.state.events} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = LogEntriesComponent;
|
|
@ -1,88 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const moment = require('moment');
|
|
||||||
|
|
||||||
const DIFF_PREFIXES = {
|
|
||||||
A: ' ',
|
|
||||||
E: ' ',
|
|
||||||
D: '-',
|
|
||||||
N: '+',
|
|
||||||
};
|
|
||||||
|
|
||||||
const SPADEN_CLASS = {
|
|
||||||
A: 'blue', // array edited
|
|
||||||
E: 'blue', // edited
|
|
||||||
D: 'negative', // deleted
|
|
||||||
N: 'positive', // added
|
|
||||||
};
|
|
||||||
|
|
||||||
const LogEntry = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
event: React.PropTypes.object.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const date = moment(this.props.event.createdAt);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
{date.format('ll')}<br />
|
|
||||||
{date.format('HH:mm')}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<strong>{this.props.event.data.name}</strong><em>[{this.props.event.type}]</em>
|
|
||||||
</td>
|
|
||||||
<td style={{ maxWidth: 300 }}>
|
|
||||||
{this.renderEventDiff()}
|
|
||||||
</td>
|
|
||||||
<td>{this.props.event.createdBy}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderFullEventData () {
|
|
||||||
const localEventData = JSON.parse(JSON.stringify(this.props.event.data));
|
|
||||||
delete localEventData.description;
|
|
||||||
delete localEventData.name;
|
|
||||||
|
|
||||||
const prettyPrinted = JSON.stringify(localEventData, null, 2);
|
|
||||||
|
|
||||||
return (<code className="JSON smalltext man">{prettyPrinted}</code>);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderEventDiff () {
|
|
||||||
if (!this.props.showFullEvents && this.props.event.diffs) {
|
|
||||||
const changes = this.props.event.diffs.map(this.buildDiff);
|
|
||||||
return (
|
|
||||||
<code className="smalltext man">{changes.length === 0 ? '(no changes)' : changes}</code>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.renderFullEventData();
|
|
||||||
},
|
|
||||||
|
|
||||||
buildDiff (diff, idx) {
|
|
||||||
let change;
|
|
||||||
const key = diff.path.join('.');
|
|
||||||
|
|
||||||
if (diff.lhs !== undefined && diff.rhs !== undefined) {
|
|
||||||
change = (
|
|
||||||
<div>
|
|
||||||
<div className={SPADEN_CLASS.D}>- {key}: {JSON.stringify(diff.lhs)}</div>
|
|
||||||
<div className={SPADEN_CLASS.N}>+ {key}: {JSON.stringify(diff.rhs)}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const spadenClass = SPADEN_CLASS[diff.kind];
|
|
||||||
const prefix = DIFF_PREFIXES[diff.kind];
|
|
||||||
|
|
||||||
change = (<div className={spadenClass}>{prefix} {key}: {JSON.stringify(diff.rhs)}</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<div key={idx}>{change}</div>);
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = LogEntry;
|
|
@ -1,57 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const LogEntry = require('./LogEntry');
|
|
||||||
|
|
||||||
const LogEntryList = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
events: React.PropTypes.array.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
showFullEvents: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const logEntryNodes = this.props.events.map(evt =>
|
|
||||||
<LogEntry event={evt} key={evt.id} showFullEvents={this.state.showFullEvents} />);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label className="prs fright-ht768 smalltext">
|
|
||||||
Show full events
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="mlm"
|
|
||||||
value={this.state.fullEvents}
|
|
||||||
onChange={this.toggleFullEvents} />
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<table className="outerborder zebra-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>When</th>
|
|
||||||
<th>Action</th>
|
|
||||||
<th>
|
|
||||||
Data
|
|
||||||
</th>
|
|
||||||
<th>Author</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{logEntryNodes}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleFullEvents () {
|
|
||||||
this.setState({ showFullEvents: !this.state.showFullEvents });
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = LogEntryList;
|
|
@ -1,63 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const StrategyList = require('./StrategyList');
|
|
||||||
const StrategyForm = require('./StrategyForm');
|
|
||||||
const StrategyActions = require('../../stores/StrategyActions');
|
|
||||||
|
|
||||||
const StrategiesComponent = React.createClass({
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
createView: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onNewStrategy () {
|
|
||||||
this.setState({ createView: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
onCancelNewStrategy () {
|
|
||||||
this.setState({ createView: false });
|
|
||||||
},
|
|
||||||
|
|
||||||
onSave (strategy) {
|
|
||||||
StrategyActions.create.triggerPromise(strategy)
|
|
||||||
.then(this.onCancelNewStrategy);
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemove (strategy) {
|
|
||||||
StrategyActions.remove.triggerPromise(strategy);
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Activation Strategies</h1>
|
|
||||||
{this.state.createView ?
|
|
||||||
this.renderCreateView() : this.renderCreateButton()}
|
|
||||||
<hr />
|
|
||||||
<StrategyList
|
|
||||||
strategies={this.props.strategies}
|
|
||||||
onRemove={this.onRemove} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderCreateView () {
|
|
||||||
return (
|
|
||||||
<StrategyForm
|
|
||||||
onCancelNewStrategy={this.onCancelNewStrategy}
|
|
||||||
onSave={this.onSave}
|
|
||||||
/>);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderCreateButton () {
|
|
||||||
return (
|
|
||||||
<button className="mal" onClick={this.onNewStrategy}>
|
|
||||||
Create strategy
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = StrategiesComponent;
|
|
@ -1,32 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
|
|
||||||
const Strategy = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
strategy: React.PropTypes.object.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemove (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
if (window.confirm(`Are you sure you want to delete strategy '${this.props.strategy.name}'?`)) { // eslint-disable-line no-alert
|
|
||||||
this.props.onRemove(this.props.strategy);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="line mal">
|
|
||||||
<div className="unit">
|
|
||||||
<strong>{this.props.strategy.name} </strong>
|
|
||||||
<a href=""
|
|
||||||
title="Delete strategy"
|
|
||||||
onClick={this.onRemove}>(remove)</a><br />
|
|
||||||
<em>{this.props.strategy.description}</em><br />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = Strategy;
|
|
@ -1,140 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const TextInput = require('../form/TextInput');
|
|
||||||
|
|
||||||
const StrategyForm = React.createClass({
|
|
||||||
|
|
||||||
getDefaultProps () {
|
|
||||||
return {
|
|
||||||
maxParams: 4,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState () {
|
|
||||||
return {
|
|
||||||
parameters: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubmit (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
const strategy = {};
|
|
||||||
strategy.name = this.refs.name.getValue();
|
|
||||||
strategy.description = this.refs.description.getValue();
|
|
||||||
strategy.parametersTemplate = {};
|
|
||||||
|
|
||||||
this.state.parameters.forEach(parameter => {
|
|
||||||
const value = this.refs[parameter.name].getDOMNode().value.trim();
|
|
||||||
if (value) {
|
|
||||||
strategy.parametersTemplate[value] = 'string';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onSave(strategy);
|
|
||||||
},
|
|
||||||
|
|
||||||
onCancel (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
this.props.onCancelNewStrategy();
|
|
||||||
},
|
|
||||||
|
|
||||||
onAddParam (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
const id = this.state.parameters.length + 1;
|
|
||||||
const params = this.state.parameters.concat([{ id, name: `param_${id}`, label: `Parameter ${id}` }]);
|
|
||||||
this.setState({ parameters: params });
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemoveParam (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
const params = this.state.parameters.slice(0, -1);
|
|
||||||
|
|
||||||
this.setState({ parameters: params });
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="line r-pam bg-lilac-xlt">
|
|
||||||
<div className="unit r-size1of2">
|
|
||||||
<form onSubmit={this.onSubmit}>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Create strategy</legend>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
label="Name"
|
|
||||||
ref="name"
|
|
||||||
placeholder="Strategy name" />
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
label="Description"
|
|
||||||
ref="description"
|
|
||||||
placeholder="Please write a short description" />
|
|
||||||
|
|
||||||
{this.renderParameters()}
|
|
||||||
{this.renderRemoveLink()}
|
|
||||||
|
|
||||||
<div className="actions">
|
|
||||||
<input type="submit" value="Save" className="primary mrs" />
|
|
||||||
<button onClick={this.onCancel} className="mrs">Cancel</button>
|
|
||||||
{this.renderAddLink()}
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderParameters () {
|
|
||||||
return this.state.parameters.map(param => <div className="formelement" key={param.name}>
|
|
||||||
<label className="t4">{param.label}</label>
|
|
||||||
<div className="input">
|
|
||||||
<div className="line">
|
|
||||||
|
|
||||||
<div className="unit size2of3">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={param.name}
|
|
||||||
ref={param.name}
|
|
||||||
placeholder="Parameter name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="unit size1of3">
|
|
||||||
<select defaultValue="string">
|
|
||||||
<option value="string">string</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderAddLink () {
|
|
||||||
if (this.state.parameters.length < this.props.maxParams) {
|
|
||||||
return <a href="#add" onClick={this.onAddParam}>+ Add required parameter</a>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderRemoveLink () {
|
|
||||||
if (this.state.parameters.length > 0) {
|
|
||||||
return (
|
|
||||||
<div className="formelement mtn">
|
|
||||||
<a href="#add"
|
|
||||||
className="negative"
|
|
||||||
onClick={this.onRemoveParam}>
|
|
||||||
- Remove parameter
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = StrategyForm;
|
|
@ -1,20 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const Strategy = require('./Strategy');
|
|
||||||
|
|
||||||
const StrategyList = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
strategies: React.PropTypes.array.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const strategyNodes = this.props.strategies.map(strategy =>
|
|
||||||
<Strategy strategy={strategy} key={strategy.name} onRemove={this.props.onRemove} />);
|
|
||||||
return (
|
|
||||||
<div>{strategyNodes}</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = StrategyList;
|
|
@ -1,22 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
const Router = require('react-router');
|
|
||||||
const UnleashApp = require('./UnleashApp');
|
|
||||||
const LogEntriesComponent = require('./components/log/LogEntriesComponent');
|
|
||||||
const FeatureTogglesComponent = require('./components/feature/FeatureTogglesComponent');
|
|
||||||
const StrategiesComponent = require('./components/strategy/StrategiesComponent');
|
|
||||||
const ArchiveFeatureComponent = require('./components/feature/ArchiveFeatureComponent');
|
|
||||||
const DefaultRoute = Router.DefaultRoute;
|
|
||||||
const Route = Router.Route;
|
|
||||||
|
|
||||||
const routes = (
|
|
||||||
<Route name="app" path="/" handler={UnleashApp}>
|
|
||||||
<Route name="strategies" handler={StrategiesComponent}/>
|
|
||||||
<Route name="log" handler={LogEntriesComponent}/>
|
|
||||||
<Route name="archive" handler={ArchiveFeatureComponent}/>
|
|
||||||
<DefaultRoute name="features" handler={FeatureTogglesComponent}/>
|
|
||||||
</Route>
|
|
||||||
);
|
|
||||||
|
|
||||||
module.exports = routes;
|
|
@ -1,47 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
const FeatureActions = require('./FeatureToggleActions');
|
|
||||||
const filter = require('lodash/collection/filter');
|
|
||||||
const sortBy = require('lodash/collection/sortBy');
|
|
||||||
|
|
||||||
let _archivedToggles = [];
|
|
||||||
|
|
||||||
// Creates a DataStore
|
|
||||||
const FeatureStore = Reflux.createStore({
|
|
||||||
|
|
||||||
// Initial setup
|
|
||||||
init () {
|
|
||||||
this.listenTo(FeatureActions.initArchive.completed, this.onInit);
|
|
||||||
this.listenTo(FeatureActions.archive.completed, this.onArchive);
|
|
||||||
this.listenTo(FeatureActions.revive.completed, this.onRevive);
|
|
||||||
},
|
|
||||||
|
|
||||||
onInit (toggles) {
|
|
||||||
_archivedToggles = toggles;
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
onArchive (feature) {
|
|
||||||
const toggles = _archivedToggles.concat([feature]);
|
|
||||||
_archivedToggles = sortBy(toggles, 'name');
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
onRevive (item) {
|
|
||||||
const newStore = filter(_archivedToggles, f => f.name !== item.name);
|
|
||||||
|
|
||||||
_archivedToggles = sortBy(newStore, 'name');
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
getArchivedToggles () {
|
|
||||||
return _archivedToggles;
|
|
||||||
},
|
|
||||||
|
|
||||||
initStore (archivedToggles) {
|
|
||||||
_archivedToggles = archivedToggles;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureStore;
|
|
@ -1,10 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
|
|
||||||
const ErrorActions = Reflux.createActions([
|
|
||||||
'clear',
|
|
||||||
'error',
|
|
||||||
]);
|
|
||||||
|
|
||||||
module.exports = ErrorActions;
|
|
@ -1,70 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
const FeatureActions = require('./FeatureToggleActions');
|
|
||||||
const ErrorActions = require('./ErrorActions');
|
|
||||||
|
|
||||||
// Creates a DataStore
|
|
||||||
const FeatureStore = Reflux.createStore({
|
|
||||||
// Initial setup
|
|
||||||
init () {
|
|
||||||
this.listenTo(FeatureActions.create.failed, this.onError);
|
|
||||||
this.listenTo(FeatureActions.init.failed, this.onError);
|
|
||||||
this.listenTo(FeatureActions.update.failed, this.onError);
|
|
||||||
this.listenTo(FeatureActions.archive.failed, this.onError);
|
|
||||||
this.listenTo(FeatureActions.revive.failed, this.onError);
|
|
||||||
this.listenTo(ErrorActions.error, this.onError);
|
|
||||||
this.listenTo(ErrorActions.clear, this.onClear);
|
|
||||||
this.errors = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
onError (error) {
|
|
||||||
if (this.isClientError(error)) {
|
|
||||||
const errors = JSON.parse(error.responseText);
|
|
||||||
errors.forEach(e => {
|
|
||||||
this.addError(e.msg);
|
|
||||||
});
|
|
||||||
} else if (error.status === 0) {
|
|
||||||
this.addError('server unreachable');
|
|
||||||
} else {
|
|
||||||
this.addError(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onClear () {
|
|
||||||
this.errors = [];
|
|
||||||
this.trigger([]);
|
|
||||||
},
|
|
||||||
|
|
||||||
addError (msg) {
|
|
||||||
const errors = this.errors;
|
|
||||||
if (errors[errors.length - 1] !== msg) {
|
|
||||||
errors.push(msg);
|
|
||||||
this.errors = errors;
|
|
||||||
this.trigger(errors);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isClientError (error) {
|
|
||||||
try {
|
|
||||||
return error.status >= 400 &&
|
|
||||||
error.status < 500 &&
|
|
||||||
JSON.parse(error.responseText);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof SyntaxError) {
|
|
||||||
// fall through;
|
|
||||||
console.log('Syntax error!'); // eslint-disable-line no-console
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getErrors () {
|
|
||||||
return this.errors;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureStore;
|
|
@ -1,26 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const reqwest = require('reqwest');
|
|
||||||
|
|
||||||
const TYPE = 'json';
|
|
||||||
|
|
||||||
const EventStore = {
|
|
||||||
getEvents () {
|
|
||||||
return reqwest({
|
|
||||||
url: 'events',
|
|
||||||
method: 'get',
|
|
||||||
type: TYPE,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getEventsByName (eventName) {
|
|
||||||
return reqwest({
|
|
||||||
url: `events/${eventName}`,
|
|
||||||
method: 'get',
|
|
||||||
type: TYPE,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = EventStore;
|
|
@ -1,75 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
const Server = require('./FeatureToggleServerFacade');
|
|
||||||
|
|
||||||
const FeatureToggleActions = Reflux.createActions({
|
|
||||||
init: { asyncResult: true },
|
|
||||||
initArchive: { asyncResult: true },
|
|
||||||
create: { asyncResult: true },
|
|
||||||
update: { asyncResult: true },
|
|
||||||
archive: { asyncResult: true },
|
|
||||||
revive: { asyncResult: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
FeatureToggleActions.init.listen(function () {
|
|
||||||
Server.getFeatures((error, features) => {
|
|
||||||
if (error) {
|
|
||||||
this.failed(error);
|
|
||||||
} else {
|
|
||||||
this.completed(features);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
FeatureToggleActions.initArchive.listen(function () {
|
|
||||||
Server.getArchivedFeatures((error, archivedToggles) => {
|
|
||||||
if (error) {
|
|
||||||
this.failed(error);
|
|
||||||
} else {
|
|
||||||
this.completed(archivedToggles);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
FeatureToggleActions.create.listen(function (feature) {
|
|
||||||
Server.createFeature(feature, error => {
|
|
||||||
if (error) {
|
|
||||||
this.failed(error);
|
|
||||||
} else {
|
|
||||||
this.completed(feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
FeatureToggleActions.update.listen(function (feature) {
|
|
||||||
Server.updateFeature(feature, error => {
|
|
||||||
if (error) {
|
|
||||||
this.failed(error);
|
|
||||||
} else {
|
|
||||||
this.completed(feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
FeatureToggleActions.archive.listen(function (feature) {
|
|
||||||
Server.archiveFeature(feature, error => {
|
|
||||||
if (error) {
|
|
||||||
this.failed(error);
|
|
||||||
} else {
|
|
||||||
this.completed(feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
FeatureToggleActions.revive.listen(function (feature) {
|
|
||||||
Server.reviveFeature(feature, error => {
|
|
||||||
if (error) {
|
|
||||||
this.failed(error);
|
|
||||||
} else {
|
|
||||||
this.completed(feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureToggleActions;
|
|
@ -1,100 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const reqwest = require('reqwest');
|
|
||||||
|
|
||||||
const TYPE = 'json';
|
|
||||||
const CONTENT_TYPE = 'application/json';
|
|
||||||
|
|
||||||
const FeatureToggleServerFacade = {
|
|
||||||
updateFeature (feature, cb) {
|
|
||||||
reqwest({
|
|
||||||
url: `features/${feature.name}`,
|
|
||||||
method: 'put',
|
|
||||||
type: TYPE,
|
|
||||||
contentType: CONTENT_TYPE,
|
|
||||||
data: JSON.stringify(feature),
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success () {
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
createFeature (feature, cb) {
|
|
||||||
reqwest({
|
|
||||||
url: 'features',
|
|
||||||
method: 'post',
|
|
||||||
type: TYPE,
|
|
||||||
contentType: CONTENT_TYPE,
|
|
||||||
data: JSON.stringify(feature),
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success () {
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
archiveFeature (feature, cb) {
|
|
||||||
reqwest({
|
|
||||||
url: `features/${feature.name}`,
|
|
||||||
method: 'delete',
|
|
||||||
type: TYPE,
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success () {
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getFeatures (cb) {
|
|
||||||
reqwest({
|
|
||||||
url: 'features',
|
|
||||||
method: 'get',
|
|
||||||
type: TYPE,
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success (data) {
|
|
||||||
cb(null, data.features);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getArchivedFeatures (cb) {
|
|
||||||
reqwest({
|
|
||||||
url: 'archive/features',
|
|
||||||
method: 'get',
|
|
||||||
type: TYPE,
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success (data) {
|
|
||||||
cb(null, data.features);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
reviveFeature (feature, cb) {
|
|
||||||
reqwest({
|
|
||||||
url: 'archive/revive',
|
|
||||||
method: 'post',
|
|
||||||
type: TYPE,
|
|
||||||
contentType: CONTENT_TYPE,
|
|
||||||
data: JSON.stringify(feature),
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success () {
|
|
||||||
cb();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = FeatureToggleServerFacade;
|
|
@ -1,57 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
const FeatureActions = require('./FeatureToggleActions');
|
|
||||||
const filter = require('lodash/collection/filter');
|
|
||||||
const sortBy = require('lodash/collection/sortBy');
|
|
||||||
const findIndex = require('lodash/array/findIndex');
|
|
||||||
|
|
||||||
let _featureToggles = [];
|
|
||||||
|
|
||||||
const FeatureStore = Reflux.createStore({
|
|
||||||
|
|
||||||
// Initial setup
|
|
||||||
init () {
|
|
||||||
this.listenTo(FeatureActions.init.completed, this.setToggles);
|
|
||||||
this.listenTo(FeatureActions.create.completed, this.onCreate);
|
|
||||||
this.listenTo(FeatureActions.update.completed, this.onUpdate);
|
|
||||||
this.listenTo(FeatureActions.archive.completed, this.onArchive);
|
|
||||||
this.listenTo(FeatureActions.revive.completed, this.onRevive);
|
|
||||||
},
|
|
||||||
|
|
||||||
onCreate (feature) {
|
|
||||||
this.setToggles([feature].concat(_featureToggles));
|
|
||||||
},
|
|
||||||
|
|
||||||
setToggles (toggles) {
|
|
||||||
_featureToggles = sortBy(toggles, 'name');
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
onUpdate (feature) {
|
|
||||||
const idx = findIndex(_featureToggles, 'name', feature.name);
|
|
||||||
_featureToggles[idx] = feature;
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
onArchive (feature) {
|
|
||||||
const featureToggles = filter(_featureToggles, item => item.name !== feature.name);
|
|
||||||
this.setToggles(featureToggles);
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
onRevive (item) {
|
|
||||||
this.setToggles(_featureToggles.concat([item]));
|
|
||||||
this.trigger();
|
|
||||||
},
|
|
||||||
|
|
||||||
getFeatureToggles () {
|
|
||||||
return _featureToggles;
|
|
||||||
},
|
|
||||||
|
|
||||||
initStore (toggles) {
|
|
||||||
_featureToggles = toggles;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FeatureStore;
|
|
@ -1,54 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const reqwest = require('reqwest');
|
|
||||||
|
|
||||||
const TYPE = 'json';
|
|
||||||
const CONTENT_TYPE = 'application/json';
|
|
||||||
|
|
||||||
const StrategyAPI = {
|
|
||||||
createStrategy (strategy, cb) {
|
|
||||||
reqwest({
|
|
||||||
url: 'strategies',
|
|
||||||
method: 'post',
|
|
||||||
type: TYPE,
|
|
||||||
contentType: CONTENT_TYPE,
|
|
||||||
data: JSON.stringify(strategy),
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success () {
|
|
||||||
cb(null, strategy);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeStrategy (strategy, cb) {
|
|
||||||
reqwest({
|
|
||||||
url: `strategies/${strategy.name}`,
|
|
||||||
method: 'delete',
|
|
||||||
type: TYPE,
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success () {
|
|
||||||
cb(null, strategy);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getStrategies (cb) {
|
|
||||||
reqwest({
|
|
||||||
url: 'strategies',
|
|
||||||
method: 'get',
|
|
||||||
type: TYPE,
|
|
||||||
error (error) {
|
|
||||||
cb(error);
|
|
||||||
},
|
|
||||||
success (data) {
|
|
||||||
cb(null, data.strategies);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = StrategyAPI;
|
|
@ -1,42 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
const StrategyAPI = require('./StrategyAPI');
|
|
||||||
|
|
||||||
const StrategyActions = Reflux.createActions({
|
|
||||||
init: { asyncResult: true },
|
|
||||||
create: { asyncResult: true },
|
|
||||||
remove: { asyncResult: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
StrategyActions.init.listen(function () {
|
|
||||||
StrategyAPI.getStrategies((err, strategies) => {
|
|
||||||
if (err) {
|
|
||||||
this.failed(err);
|
|
||||||
} else {
|
|
||||||
this.completed(strategies);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
StrategyActions.create.listen(function (feature) {
|
|
||||||
StrategyAPI.createStrategy(feature, err => {
|
|
||||||
if (err) {
|
|
||||||
this.failed(err);
|
|
||||||
} else {
|
|
||||||
this.completed(feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
StrategyActions.remove.listen(function (feature) {
|
|
||||||
StrategyAPI.removeStrategy(feature, err => {
|
|
||||||
if (err) {
|
|
||||||
this.failed(err);
|
|
||||||
} else {
|
|
||||||
this.completed(feature);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = StrategyActions;
|
|
@ -1,42 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const Reflux = require('reflux');
|
|
||||||
const StrategyActions = require('./StrategyActions');
|
|
||||||
const filter = require('lodash/collection/filter');
|
|
||||||
|
|
||||||
let _strategies = [];
|
|
||||||
|
|
||||||
// Creates a DataStore
|
|
||||||
const StrategyStore = Reflux.createStore({
|
|
||||||
|
|
||||||
// Initial setup
|
|
||||||
init () {
|
|
||||||
this.listenTo(StrategyActions.init.completed, this.setStrategies);
|
|
||||||
this.listenTo(StrategyActions.create.completed, this.onCreate);
|
|
||||||
this.listenTo(StrategyActions.remove.completed, this.onRemove);
|
|
||||||
},
|
|
||||||
|
|
||||||
onCreate (strategy) {
|
|
||||||
this.setStrategies(_strategies.concat([strategy]));
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemove (strategy) {
|
|
||||||
const strategies = filter(_strategies, item => item.name !== strategy.name);
|
|
||||||
this.setStrategies(strategies);
|
|
||||||
},
|
|
||||||
|
|
||||||
setStrategies (strategies) {
|
|
||||||
_strategies = strategies;
|
|
||||||
this.trigger(_strategies);
|
|
||||||
},
|
|
||||||
|
|
||||||
getStrategies () {
|
|
||||||
return _strategies;
|
|
||||||
},
|
|
||||||
|
|
||||||
initStore (strategies) {
|
|
||||||
_strategies = strategies;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = StrategyStore;
|
|
@ -1,36 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
let _username;
|
|
||||||
|
|
||||||
// Ref: http://stackoverflow.com/questions/10730362/get-cookie-by-name
|
|
||||||
function readCookie (cookieName) {
|
|
||||||
const nameEQ = `${cookieName}=`;
|
|
||||||
const ca = document.cookie.split(';');
|
|
||||||
for (let i = 0;i < ca.length;i++) {
|
|
||||||
let c = ca[i];
|
|
||||||
while (c.charAt(0) == ' ') { // eslint-disable-line eqeqeq
|
|
||||||
c = c.substring(1, c.length);
|
|
||||||
}
|
|
||||||
if (c.indexOf(nameEQ) === 0) {
|
|
||||||
return c.substring(nameEQ.length, c.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserStore = {
|
|
||||||
init () {
|
|
||||||
_username = readCookie('username');
|
|
||||||
},
|
|
||||||
|
|
||||||
set (username) {
|
|
||||||
_username = username;
|
|
||||||
document.cookie = `username=${_username}; expires=Thu, 18 Dec 2099 12:00:00 UTC`;
|
|
||||||
},
|
|
||||||
|
|
||||||
get () {
|
|
||||||
return _username;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = UserStore;
|
|
@ -1,19 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const FeatureToggleActions = require('./FeatureToggleActions');
|
|
||||||
const StrategyActions = require('./StrategyActions');
|
|
||||||
const Timer = require('../utils/Timer');
|
|
||||||
|
|
||||||
let _timer;
|
|
||||||
|
|
||||||
function load () {
|
|
||||||
FeatureToggleActions.init.triggerPromise();
|
|
||||||
StrategyActions.init.triggerPromise();
|
|
||||||
FeatureToggleActions.initArchive.triggerPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function (pollInterval) {
|
|
||||||
const intervall = pollInterval || 30;
|
|
||||||
_timer = new Timer(load, intervall * 1000);
|
|
||||||
_timer.start();
|
|
||||||
};
|
|
@ -1,29 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const 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'); // eslint-disable-line no-console
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('starting timer'); // eslint-disable-line no-console
|
|
||||||
this.timerId = setInterval(this.cb, this.interval);
|
|
||||||
this.cb();
|
|
||||||
};
|
|
||||||
|
|
||||||
Timer.prototype.stop = function () {
|
|
||||||
if (this.timerId == null) {
|
|
||||||
console.warn('no timer running'); // eslint-disable-line no-console
|
|
||||||
} else {
|
|
||||||
console.log('stopping timer'); // eslint-disable-line no-console
|
|
||||||
clearInterval(this.timerId);
|
|
||||||
this.timerId = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Timer;
|
|
@ -1,37 +0,0 @@
|
|||||||
.toggle-active {
|
|
||||||
border: 1px solid black;
|
|
||||||
display: inline-block;
|
|
||||||
background-color: lawngreen;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.toggle-inactive {
|
|
||||||
border: 1px solid black;
|
|
||||||
display: inline-block;
|
|
||||||
background-color: red;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-border {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
code > .diff-N {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
code > .diff-D {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
code > .diff-A, .diff-E {
|
|
||||||
color: black;
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
// docs: http://webpack.github.io/docs/configuration.html
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const publicRoot = path.join(__dirname, 'public');
|
|
||||||
const jsroot = path.join(publicRoot, 'js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
context: jsroot,
|
|
||||||
entry: './app.jsx',
|
|
||||||
|
|
||||||
output: {
|
|
||||||
path: publicRoot,
|
|
||||||
filename: 'bundle.js',
|
|
||||||
publicPath: '/js/',
|
|
||||||
},
|
|
||||||
|
|
||||||
resolve: {
|
|
||||||
root: [jsroot],
|
|
||||||
extensions: ['', '.js', '.jsx'],
|
|
||||||
modulesDirectories: ['web_modules', 'node_modules'],
|
|
||||||
},
|
|
||||||
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
loader: 'babel',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
devtool: 'source-map',
|
|
||||||
|
|
||||||
externals: {
|
|
||||||
// stuff not in node_modules can be resolved here.
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user