diff --git a/spikes/graphs-fe/rickshaw/.babelrc b/spikes/graphs-fe/rickshaw/.babelrc new file mode 100644 index 00000000..82cc857a --- /dev/null +++ b/spikes/graphs-fe/rickshaw/.babelrc @@ -0,0 +1,15 @@ +{ + "presets": [ + "react", + "es2015" + ], + "plugins": [ + ["transform-object-rest-spread", { + "useBuiltIns": true + }], + "add-module-exports", + "transform-es2015-modules-commonjs", + "react-hot-loader/babel" + ], + "sourceMaps": "both" +} diff --git a/spikes/graphs-fe/rickshaw/.eslintignore b/spikes/graphs-fe/rickshaw/.eslintignore new file mode 100644 index 00000000..683e721c --- /dev/null +++ b/spikes/graphs-fe/rickshaw/.eslintignore @@ -0,0 +1,3 @@ +/node_modules +coverage +.nyc_output diff --git a/spikes/graphs-fe/rickshaw/.eslintrc b/spikes/graphs-fe/rickshaw/.eslintrc new file mode 100644 index 00000000..19bd88dc --- /dev/null +++ b/spikes/graphs-fe/rickshaw/.eslintrc @@ -0,0 +1,29 @@ +{ + "extends": "semistandard", + "parser": "babel-eslint", + "parserOptions": { + "ecmaVersion": 7, + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "module" + }, + "plugins": [ + "babel", + "react" + ], + "rules": { + "generator-star-spacing": 0, + "babel/generator-star-spacing": 1, + "space-before-function-paren": [2, "never"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2, + "object-curly-newline": ["error", { + "minProperties": 1 + }], + "sort-vars": ["error", { + "ignoreCase": true + }] + } +} \ No newline at end of file diff --git a/spikes/graphs-fe/rickshaw/.gitignore b/spikes/graphs-fe/rickshaw/.gitignore new file mode 100644 index 00000000..c21b0ba0 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/.gitignore @@ -0,0 +1,4 @@ +/node_modules +coverage +.nyc_output +npm-debug.log diff --git a/spikes/graphs-fe/rickshaw/client/chart.js b/spikes/graphs-fe/rickshaw/client/chart.js new file mode 100644 index 00000000..e6d0f0c0 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/client/chart.js @@ -0,0 +1,77 @@ +const Rickshaw = require('rickshaw'); +const ReactRedux = require('react-redux'); +const React = require('react'); +const style = require('./style.css'); + +const { + connect +} = ReactRedux; + +const { + Graph +} = Rickshaw; + +let i = 0; + +const Component = React.createClass({ + ref: function(name) { + this._refs = this._refs || {}; + + return (el) => { + this._refs[name] = el; + }; + }, + fromData: function(data) { + return (data || []).map((d, i) => { + return { + y: d.cpu, + x: i + }; + }); + }, + componentDidMount: function() { + this._chart = new Graph({ + element: this._refs.component, + renderer: 'bar', + width: 500, + height: 200, + series: [{ + data: this.fromData(this.props.data) + }] + }); + + this._chart.render(); + }, + componentWillReceiveProps: function(nextProps) { + this._chart.series[0].data = this.fromData(this.props.data); + this._chart.update(); + }, + render: function() { + const { + data = [] + } = this.props; + + const className = (data.length && data[data.length - 1].cpu > 50) + ? style.red + : style.blue; + + return ( +
+ ); + } +}); + +const mapStateToProps = ({ + data +}) => { + return { + data + }; +}; + +module.exports = connect( + mapStateToProps +)(Component); diff --git a/spikes/graphs-fe/rickshaw/client/index.js b/spikes/graphs-fe/rickshaw/client/index.js new file mode 100644 index 00000000..e4830cde --- /dev/null +++ b/spikes/graphs-fe/rickshaw/client/index.js @@ -0,0 +1,46 @@ +const ReactDOM = require('react-dom'); +const React = require('react'); +const store = require('./store')(); +const nes = require('nes/dist/client'); + +const { + Client +} = nes; + +const client = new Client(`ws://${document.location.host}`); + +client.connect((err) => { + if (err) { + throw err; + } + + console.log('connected'); + + client.subscribe('/stats/5', (update, flag) => { + store.dispatch({ + type: 'UPDATE_STATS', + payload: update + }) + }, (err) => { + if (err) { + throw err; + } + + console.log('subscribed'); + }); +}); + +const render = () => { + const Root = require('./root'); + + ReactDOM.render( + , + document.getElementById('root') + ); +}; + +render(); + +if (module.hot) { + module.hot.accept('./root', render); +} diff --git a/spikes/graphs-fe/rickshaw/client/root.js b/spikes/graphs-fe/rickshaw/client/root.js new file mode 100644 index 00000000..a584d4f2 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/client/root.js @@ -0,0 +1,25 @@ +const React = require('react'); +const ReactHotLoader = require('react-hot-loader'); +const ReactRedux = require('react-redux'); +const Chart = require('./chart'); + + +const { + AppContainer +} = ReactHotLoader; + +const { + Provider +} = ReactRedux; + +module.exports = ({ + store +}) => { + return ( + + + + + + ); +}; diff --git a/spikes/graphs-fe/rickshaw/client/store.js b/spikes/graphs-fe/rickshaw/client/store.js new file mode 100644 index 00000000..b0e8763d --- /dev/null +++ b/spikes/graphs-fe/rickshaw/client/store.js @@ -0,0 +1,25 @@ +const takeRight = require('lodash.takeright'); +const redux = require('redux'); + +const { + createStore, + compose, + applyMiddleware +} = redux; + +const reducer = (state, action) => { + if (action.type !== 'UPDATE_STATS') { + return state; + } + + const data = (state.data || []).concat([action.payload]); + + return { + ...state, + data: takeRight(data, 50) + }; +}; + +module.exports = (state = Object.freeze({})) => { + return createStore(reducer, state); +}; diff --git a/spikes/graphs-fe/rickshaw/client/style.css b/spikes/graphs-fe/rickshaw/client/style.css new file mode 100644 index 00000000..a5373319 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/client/style.css @@ -0,0 +1,9 @@ +.rickshaw_graph.red rect { + fill: red; + border-top: 1px brown solid; +} + +.rickshaw_graph.blue rect { + fill: blue; + border-top: 1px darkblue solid; +} diff --git a/spikes/graphs-fe/rickshaw/package.json b/spikes/graphs-fe/rickshaw/package.json new file mode 100644 index 00000000..810dce72 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/package.json @@ -0,0 +1,57 @@ +{ + "name": "rickshaw-graphing-spike", + "private": true, + "license": "private", + "main": "server/index.js", + "dependencies": { + "autoprefixer": "^6.5.1", + "babel-eslint": "^7.0.0", + "babel-loader": "^6.2.5", + "babel-plugin-add-module-exports": "^0.2.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.16.0", + "babel-plugin-transform-object-rest-spread": "^6.16.0", + "babel-plugin-transform-runtime": "^6.15.0", + "babel-preset-es2015": "^6.16.0", + "babel-preset-react": "^6.16.0", + "babel-preset-react-hmre": "^1.1.1", + "babel-runtime": "^6.11.6", + "build-array": "^1.0.0", + "component-emitter": "^1.2.1", + "css-loader": "^0.25.0", + "hapi": "^15.2.0", + "hapi-webpack-dev-plugin": "^1.1.4", + "inert": "^4.0.2", + "lodash.takeright": "^4.1.1", + "nes": "^6.3.1", + "postcss-loader": "^1.0.0", + "postcss-modules-values": "^1.2.2", + "postcss-nested": "^1.0.0", + "react": "^15.3.2", + "react-dom": "^15.3.2", + "react-hot-loader": "^3.0.0-beta.6", + "react-redux": "^4.4.5", + "redux": "^3.6.0", + "require-dir": "^0.3.1", + "rickshaw": "^1.6.0", + "style-loader": "^0.13.1", + "webpack": "^1.13.2", + "webpack-dev-server": "^1.16.2" + }, + "devDependencies": { + "babel-register": "^6.16.3", + "eslint": "^3.8.1", + "eslint-config-semistandard": "^7.0.0", + "eslint-config-standard": "^6.2.0", + "eslint-plugin-babel": "^3.3.0", + "eslint-plugin-promise": "^3.3.0", + "eslint-plugin-react": "^6.4.1", + "eslint-plugin-standard": "^2.0.1", + "json-loader": "^0.5.4" + }, + "ava": { + "require": [ + "babel-register" + ], + "babel": "inherit" + } +} diff --git a/spikes/graphs-fe/rickshaw/readme.md b/spikes/graphs-fe/rickshaw/readme.md new file mode 100644 index 00000000..1de00e6b --- /dev/null +++ b/spikes/graphs-fe/rickshaw/readme.md @@ -0,0 +1,8 @@ +# rickshaw + +![](http://g.recordit.co/4fybFRWBl4.gif) + +## summary + + - [x] fast (handles 100ms updates well, or even 16ms) + - [x] can be styled with css (it's just an svg with 's) diff --git a/spikes/graphs-fe/rickshaw/server/index.js b/spikes/graphs-fe/rickshaw/server/index.js new file mode 100644 index 00000000..b2a6a529 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/index.js @@ -0,0 +1,29 @@ +const requireDir = require('require-dir'); +const plugins = require('./plugins'); +const routes = requireDir('./routes'); +const Hapi = require('hapi'); +const path = require('path'); +const fs = require('fs'); + +const server = new Hapi.Server(); + +server.connection({ + host: 'localhost', + port: 8000 +}); + +server.register(plugins, (err) => { + if (err) { + throw err; + } + + Object.keys(routes).forEach((name) => { + routes[name](server); + }); + + server.start((err) => { + server.connections.forEach((conn) => { + console.log(`started at: ${conn.info.uri}`); + }); + }); +}); diff --git a/spikes/graphs-fe/rickshaw/server/metric.js b/spikes/graphs-fe/rickshaw/server/metric.js new file mode 100644 index 00000000..24015f70 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/metric.js @@ -0,0 +1,34 @@ +const Emitter = require('component-emitter'); + +const cdm = {}; + +module.exports = (server) => ({ + on: (id) => { + console.log('on', cdm[id]); + if (cdm[id] && (cdm[id].sockets > 0)) { + cdm[id].sockets +=1; + return; + } + + + let messageId = 0; + const interval = setInterval(() => { + console.log(`publishing /stats/${id}`); + + server.publish(`/stats/${id}`, { + when: new Date().getTime(), + cpu: Math.random() * 100 + }); + }, 100); + + cdm[id] = { + interval, + sockets: 1 + }; + }, + off: (id) => { + if (!(cdm[id].sockets -= 1)) { + clearInterval(cdm[id].interval); + } + } +}); diff --git a/spikes/graphs-fe/rickshaw/server/plugins.js b/spikes/graphs-fe/rickshaw/server/plugins.js new file mode 100644 index 00000000..84944329 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/plugins.js @@ -0,0 +1,15 @@ +const webpack = require('webpack'); +const path = require('path'); + +const cfg = require('../webpack.config.js'); + +module.exports = [ + require('inert'), + require('nes'), { + register: require('hapi-webpack-dev-plugin'), + options: { + compiler: webpack(cfg), + devIndex: path.join(__dirname, '../static') + } + } +]; diff --git a/spikes/graphs-fe/rickshaw/server/routes/home.js b/spikes/graphs-fe/rickshaw/server/routes/home.js new file mode 100644 index 00000000..48ead969 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/routes/home.js @@ -0,0 +1,11 @@ +const path = require('path'); + +module.exports = (server) => { + server.route({ + method: 'GET', + path: '/', + handler: (request, reply) => { + reply.file(path.join(__dirname, '../../static/index.html')); + } + }); +}; diff --git a/spikes/graphs-fe/rickshaw/server/routes/metrics.js b/spikes/graphs-fe/rickshaw/server/routes/metrics.js new file mode 100644 index 00000000..e6e1c261 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/routes/metrics.js @@ -0,0 +1,18 @@ +const Metric = require('../metric'); + +module.exports = (server) => { + const metric = Metric(server); + + server.subscription('/stats/{id}', { + onSubscribe: (socket, path, params, next) => { + console.log('onSubscribe'); + metric.on(params.id); + next(); + }, + onUnsubscribe: (socket, path, params, next) => { + console.log('onUnsubscribe'); + metric.off(params.id); + next(); + } + }); +}; diff --git a/spikes/graphs-fe/rickshaw/server/routes/static.js b/spikes/graphs-fe/rickshaw/server/routes/static.js new file mode 100644 index 00000000..5a41f602 --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/routes/static.js @@ -0,0 +1,15 @@ +const path = require('path'); + +module.exports = (server) => { + // server.route({ + // method: 'GET', + // path: '/{param*}', + // handler: { + // directory: { + // path: path.join(__dirname, '../../static'), + // redirectToSlash: true, + // index: true + // } + // } + // }); +}; diff --git a/spikes/graphs-fe/rickshaw/server/routes/version.js b/spikes/graphs-fe/rickshaw/server/routes/version.js new file mode 100644 index 00000000..987747cb --- /dev/null +++ b/spikes/graphs-fe/rickshaw/server/routes/version.js @@ -0,0 +1,18 @@ +const Pkg = require('../../package.json'); + +const internals = { + response: { + version: Pkg.version + } +}; + +module.exports = (server) => { + server.route({ + method: 'GET', + path: '/ops/version', + config: { + description: 'Returns the version of the server', + handler: (request, reply) => reply(internals.response) + } + }); +}; diff --git a/spikes/graphs-fe/rickshaw/static/index.html b/spikes/graphs-fe/rickshaw/static/index.html new file mode 100644 index 00000000..5ef85ffd --- /dev/null +++ b/spikes/graphs-fe/rickshaw/static/index.html @@ -0,0 +1,22 @@ + + + + React Boilerplate + + + + + +
+ + + \ No newline at end of file diff --git a/spikes/graphs-fe/rickshaw/webpack.config.js b/spikes/graphs-fe/rickshaw/webpack.config.js new file mode 100644 index 00000000..7295526a --- /dev/null +++ b/spikes/graphs-fe/rickshaw/webpack.config.js @@ -0,0 +1,69 @@ +const webpack = require('webpack'); +const path = require('path'); + +const config = { + debug: true, + devtool: 'source-map', + context: path.join(__dirname, './client'), + app: path.join(__dirname, './client/index.js'), + entry: [ + 'webpack-dev-server/client?http://localhost:8080', + 'webpack/hot/only-dev-server', + 'react-hot-loader/patch', + './index.js' + ], + output: { + path: path.join(__dirname, './static'), + publicPath: '/static/', + filename: 'bundle.js' + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin() + ], + postcss: () => { + return [ + require('postcss-modules-values'), + require('postcss-nested'), + require('autoprefixer') + ]; + }, + module: { + loaders: [{ + test: /js?$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, './client') + ], + loaders: ['babel'] + }, { + test: /\.json?$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, './client') + ], + loaders: ['json'] + }, { + test: /\.css$/, + exclude: /node_modules/, + include: [ + path.join(__dirname, './client') + ], + loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader' + }] + } +}; + +const devServer = { + hot: true, + compress: true, + lazy: false, + publicPath: config.output.publicPath, + historyApiFallback: { + index: './static/index.html' + } +}; + +module.exports = Object.assign({ + devServer +}, config);