mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	remove old unleash-frontend from master
This commit is contained in:
		
							parent
							
								
									8f6932fb78
								
							
						
					
					
						commit
						b8b6e1eab8
					
				@ -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