├── .gitignore ├── README.md ├── config ├── env.js ├── jest │ ├── CSSStub.js │ └── FileStub.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── package.json ├── public ├── favicon.ico └── index.html ├── scripts ├── build.js ├── start.js └── test.js ├── server.json └── src ├── App.js ├── App.scss ├── actions └── index.js ├── assets ├── react-table.css └── screenshot.png ├── components ├── atoms │ ├── Button │ │ └── index.js │ ├── Heading │ │ └── index.js │ ├── Label │ │ └── index.js │ ├── Link │ │ └── index.js │ ├── Loader │ │ └── index.js │ ├── Section │ │ └── index.js │ └── index.js ├── globals.js ├── index.js ├── molecules │ ├── Box │ │ └── index.js │ ├── ModalAddPlugin │ │ └── index.js │ ├── ModalDashboard │ │ └── index.js │ ├── Navigation │ │ └── index.js │ ├── NavigationItem │ │ └── index.js │ ├── Widget │ │ └── index.js │ └── index.js ├── organisms │ ├── WidgetList │ │ └── index.js │ └── index.js ├── pages │ ├── DashboardContainer │ │ └── index.js │ └── index.js └── templates │ ├── Dashboard │ └── index.js │ └── index.js ├── hocs ├── index.js └── withPluginData │ └── index.js ├── index.js ├── plugins ├── react-redux-dashboard-column-chart-plugin │ ├── index.js │ └── package.json ├── react-redux-dashboard-info-plugin │ ├── index.js │ └── package.json ├── react-redux-dashboard-line-chart-plugin │ ├── index.js │ └── package.json ├── react-redux-dashboard-pie-chart-plugin │ ├── index.js │ └── package.json └── react-redux-dashboard-table-plugin │ ├── index.js │ └── package.json ├── reducers ├── dashboardsReducer.js ├── index.js └── pluginsReducer.js ├── sagas └── pluginSaga.js ├── selectors ├── dashboardsSelector.js ├── index.js └── pluginsSelector.js ├── store.js └── util ├── Api.js ├── dashboards.json ├── endpoints.json ├── helpers.js └── plugins.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Redux Dashboard 2 | A json-configurable dashboard bootstrapped with `create-react-app` made with `react`, `redux`, `redux-saga` and `react-google-charts` 3 | 4 | ![Screenshot](/src/assets/screenshot.png?raw=true) 5 | 6 | ## Table of Contents 7 | - [Installation](#installation) 8 | - [Run](#run) 9 | - [Usage](#usage) 10 | - [Add an Endpoint](#add-an-endpoint) 11 | - [Dashboard configuration](#dashboard-configuration) 12 | - [Plugin options](#plugin-options) 13 | - [General](#general) 14 | - [Plugin specific](#plugin-specific) 15 | - [Info](#info-plugin) 16 | - [ColumnChart](#columnchart) 17 | - [PieChart](#piechart) 18 | - [LineChart](#linechart) 19 | - [TableChart](#tablechart) 20 | - [Define a plugin](#define-a-plugin) 21 | 22 | ## Installation 23 | First install the dependencies: 24 | ``` 25 | npm install 26 | ``` 27 | 28 | Then you need to have a server running to load data inside plugins. You can install `json-server`, a JSON configurable server: 29 | 30 | ``` 31 | npm install -g json-server 32 | ``` 33 | 34 | and then run: 35 | 36 | ``` 37 | json-server --watch server.json --port 3001 38 | ``` 39 | to start the server, using the configuration inside `server.json` file. 40 | 41 | ## Run 42 | By default the app is listening on `localhost:3001`. Run the app: 43 | ``` 44 | npm start 45 | ``` 46 | 47 | ## Usage 48 | 49 | ### Add an Endpoint 50 | To add an endpoint edit `src/util/endpoints.json` with: 51 | 52 | ```json 53 | { 54 | "ENDPOINT_NAME": { 55 | "url": "ENDPOINT_URL" 56 | } 57 | } 58 | ``` 59 | 60 | ### Dashboard configuration 61 | The `Demo` dashboard is pre-configured. You can add/remove rendered plugins by editing `src/util/dashboards.json`. 62 | 63 | A plugin has this configuration: 64 | 65 | ```json 66 | { 67 | "title": "TITLE", 68 | "name": "TYPE", 69 | "endpoints": [ 70 | { 71 | "url": "ENDPOINT_NAME", 72 | "mapping": { 73 | "keys": { 74 | "label": "LABEL_KEY", 75 | "value": { 76 | "name": "VALUE_KEY" 77 | } 78 | } 79 | } 80 | } 81 | ], 82 | "layout": { 83 | "x": "X_POSITION", 84 | "y": "Y_POSITION", 85 | "w": "WIDTH", 86 | "h": "HEIGHT", 87 | "minW": "MIN_WIDTH", 88 | "maxW": "MAX_WIDTH", 89 | "minH": "MIN_HEIGHT", 90 | "maxH": "MAX_HEIGHT" 91 | } 92 | } 93 | ``` 94 | 95 | #### Plugin options 96 | 97 | ##### General 98 | 99 | `TITLE`: Title of the plugin in the Dashboard 100 | 101 | `TYPE`: Plugin type (e.g. `PieChart`, `ColumnChart`, `LineChart`, `Info`, `TableChart`) 102 | 103 | `ENDPOINT_NAME`: Endpoint name in `src/util/endpoints.json` 104 | 105 | `LABEL_KEY`: Key in the object response used as single point/element label in a graph (e.g. `name`) 106 | 107 | `VALUE_KEY`: Key in the object response used as single point/element value in a graph (e.g. `count`) 108 | 109 | ##### Plugin Specific 110 | 111 | ###### Info Plugin 112 | `VALUE_TYPE`: Type of the value in the response (e.g. `string` [default], `number`, `double`, `date`, `timeAgo`) 113 | 114 | ```json 115 | { 116 | "endpoints": [ 117 | { 118 | "mapping": { 119 | "keys": { 120 | "value": { 121 | "type": "VALUE_TYPE" 122 | } 123 | } 124 | } 125 | } 126 | ] 127 | } 128 | ``` 129 | 130 | ###### ColumnChart 131 | `VALUE_LABEL_KEY`: Columns section label (e.g. `Snacks` or `Candies`) 132 | 133 | ```json 134 | { 135 | "endpoints": [ 136 | { 137 | "mapping": { 138 | "keys": { 139 | "value": { 140 | "label": "VALUE_LABEL_KEY" 141 | } 142 | } 143 | } 144 | } 145 | ] 146 | } 147 | ``` 148 | 149 | ###### PieChart 150 | See general configuration 151 | 152 | ###### LineChart 153 | `1ST_STATUS", 2ND_STATUS`: Label for different types of data in a LineChart (e.g.`view` or `purchase`) 154 | 155 | `1ST_COLOR, 2ND_COLOR`: Colors for different types of data in a LineChart (e.g.`green` or `#b4da55`) 156 | 157 | `1ST_LABEL, 2ND_LABEL, 3RD_LABEL`: Labels for LineChart lines (e.g. [`Time`, `Views`, `Purchases`]) 158 | 159 | ```json 160 | { 161 | "endpoints": [ 162 | { 163 | "mapping": { 164 | "statuses": ["1ST_STATUS", "2ND_STATUS"], 165 | "colors": ["1ST_COLOR", "2ND_COLOR"], 166 | "labels": ["1ST_LABEL", "2ND_LABEL", "3RD_LABEL"] 167 | } 168 | } 169 | ] 170 | } 171 | ``` 172 | 173 | ###### TableChart 174 | `TABLE_ROW_LABEL`: Table row label (e.g. `Date`) 175 | 176 | `TABLE_ROW_KEY`: Table row key in the response (e.g. `timestamp`) 177 | 178 | `TABLE_ROW_TYPE`: Table row type (e.g. `date`) [optional] 179 | 180 | `TABLE_ROW_FORMAT`: Table row format (e.g. `YYYY/MM/DD hh:mm`) [optional] 181 | 182 | ```json 183 | { 184 | "endpoints": [ 185 | { 186 | "columns": [ 187 | { 188 | "label": "TABLE_ROW_LABEL", 189 | "value": "TABLE_ROW_KEY", 190 | "mapping": { 191 | "type": "TABLE_ROW_TYPE", 192 | "format": "TABLE_ROW_FORMAT" 193 | } 194 | } 195 | ] 196 | } 197 | ] 198 | } 199 | ``` 200 | 201 | ##### Layout 202 | The dashboard is a 2-column grid which contains plugins with different size and position. It is divided like this: 203 | 204 | ``` 205 | +---------------------------------+ 206 | | x: 0, y: 0 | x: 1, y: 0 | 207 | +---------------------------------+ 208 | | x: 0, y: 1 | x: 1, y: 1 | 209 | +---------------------------------+ 210 | | x: 0, y: 2 | x: 1, y: 2 | 211 | +---------------------------------+ 212 | | ... | 213 | ``` 214 | 215 | `X_POSITION`: X coordinate position of the plugin in the dashboard 216 | 217 | `Y_POSITION`: Y coordinate position of the plugin in the dashboard 218 | 219 | `WIDTH`: Width of the plugin (`1` for half / `2` for the whole line) 220 | 221 | `HEIGHT`: height of the plugin (`1` for `Info` / `2` for others) 222 | 223 | `MIN_WIDTH`: Minimum width of the plugin (`1` for half / `2` for the whole line) 224 | 225 | `MAX_WIDTH`: Maximum width of the plugin (`1` for half / `2` for the whole line) 226 | 227 | `MIN_HEIGHT`: Minimum height of the plugin (`1` for `Info` / `2` for others) 228 | 229 | `MAX_HEIGHT`: Maximum height of the plugin (`1` for `Info` / `2` for others) 230 | 231 | 232 | ### Define a Plugin 233 | 234 | To add a plugin edit `src/util/plugins.json`. For the configuration see `Dashboard configuration`. 235 | 236 | It is not necessary to add in the `Layout` part the `X_POSITION` and `Y_POSITION` keys. 237 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | 4 | var REACT_APP = /^REACT_APP_/i; 5 | 6 | function getClientEnvironment(publicUrl) { 7 | var processEnv = Object 8 | .keys(process.env) 9 | .filter(key => REACT_APP.test(key)) 10 | .reduce((env, key) => { 11 | env[key] = JSON.stringify(process.env[key]); 12 | return env; 13 | }, { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | 'NODE_ENV': JSON.stringify( 17 | process.env.NODE_ENV || 'development' 18 | ), 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': JSON.stringify(publicUrl) 24 | }); 25 | return {'process.env': processEnv}; 26 | } 27 | 28 | module.exports = getClientEnvironment; 29 | -------------------------------------------------------------------------------- /config/jest/CSSStub.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /config/jest/FileStub.js: -------------------------------------------------------------------------------- 1 | module.exports = "test-file-stub"; 2 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | var appDirectory = fs.realpathSync(process.cwd()); 7 | function resolveApp(relativePath) { 8 | return path.resolve(appDirectory, relativePath); 9 | } 10 | 11 | // We support resolving modules according to `NODE_PATH`. 12 | // This lets you use absolute paths in imports inside large monorepos: 13 | // https://github.com/facebookincubator/create-react-app/issues/253. 14 | 15 | // It works similar to `NODE_PATH` in Node itself: 16 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 17 | 18 | // We will export `nodePaths` as an array of absolute paths. 19 | // It will then be used by Webpack configs. 20 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 21 | 22 | var nodePaths = (process.env.NODE_PATH || '') 23 | .split(process.platform === 'win32' ? ';' : ':') 24 | .filter(Boolean) 25 | .map(resolveApp); 26 | 27 | // config after eject: we're in ./config/ 28 | module.exports = { 29 | appBuild: resolveApp('build'), 30 | appPublic: resolveApp('public'), 31 | appHtml: resolveApp('public/index.html'), 32 | appIndexJs: resolveApp('src/index.js'), 33 | appPackageJson: resolveApp('package.json'), 34 | appSrc: resolveApp('src'), 35 | testsSetup: resolveApp('src/setupTests.js'), 36 | appNodeModules: resolveApp('node_modules'), 37 | ownNodeModules: resolveApp('node_modules'), 38 | nodePaths: nodePaths 39 | }; 40 | 41 | 42 | 43 | // config before publish: we're in ./packages/react-scripts/config/ 44 | if (__dirname.indexOf(path.join('packages', 'react-scripts', 'config')) !== -1) { 45 | module.exports = { 46 | appBuild: resolveOwn('../../../build'), 47 | appPublic: resolveOwn('../template/public'), 48 | appHtml: resolveOwn('../template/public/index.html'), 49 | appIndexJs: resolveOwn('../template/src/index.js'), 50 | appPackageJson: resolveOwn('../package.json'), 51 | appSrc: resolveOwn('../template/src'), 52 | testsSetup: resolveOwn('../template/src/setupTests.js'), 53 | appNodeModules: resolveOwn('../node_modules'), 54 | ownNodeModules: resolveOwn('../node_modules'), 55 | nodePaths: nodePaths 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise === 'undefined') { 2 | // Rejection tracking prevents a common issue where React gets into an 3 | // inconsistent state due to an error, but it gets swallowed by a Promise, 4 | // and the user has no idea what causes React's erratic future behavior. 5 | require('promise/lib/rejection-tracking').enable(); 6 | window.Promise = require('promise/lib/es6-extensions.js'); 7 | } 8 | 9 | // fetch() polyfill for making API calls. 10 | require('whatwg-fetch'); 11 | 12 | // Object.assign() is commonly used with React. 13 | // It will use the native implementation if it's present and isn't buggy. 14 | Object.assign = require('object-assign'); 15 | -------------------------------------------------------------------------------- /config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var autoprefixer = require('autoprefixer'); 3 | var webpack = require('webpack'); 4 | var findCacheDir = require('find-cache-dir'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); 7 | var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); 8 | var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); 9 | var getClientEnvironment = require('./env'); 10 | var paths = require('./paths'); 11 | 12 | // Webpack uses `publicPath` to determine where the app is being served from. 13 | // In development, we always serve from the root. This makes config easier. 14 | var publicPath = '/'; 15 | // `publicUrl` is just like `publicPath`, but we will provide it to our app 16 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. 17 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. 18 | var publicUrl = ''; 19 | // Get environment variables to inject into our app. 20 | var env = getClientEnvironment(publicUrl); 21 | 22 | // This is the development configuration. 23 | // It is focused on developer experience and fast rebuilds. 24 | // The production configuration is different and lives in a separate file. 25 | module.exports = { 26 | // This makes the bundle appear split into separate modules in the devtools. 27 | // We don't use source maps here because they can be confusing: 28 | // https://github.com/facebookincubator/create-react-app/issues/343#issuecomment-237241875 29 | // You may want 'cheap-module-source-map' instead if you prefer source maps. 30 | devtool: 'eval', 31 | // These are the "entry points" to our application. 32 | // This means they will be the "root" imports that are included in JS bundle. 33 | // The first two entry points enable "hot" CSS and auto-refreshes for JS. 34 | entry: [ 35 | // Include an alternative client for WebpackDevServer. A client's job is to 36 | // connect to WebpackDevServer by a socket and get notified about changes. 37 | // When you save a file, the client will either apply hot updates (in case 38 | // of CSS changes), or refresh the page (in case of JS changes). When you 39 | // make a syntax error, this client will display a syntax error overlay. 40 | // Note: instead of the default WebpackDevServer client, we use a custom one 41 | // to bring better experience for Create React App users. You can replace 42 | // the line below with these two lines if you prefer the stock client: 43 | // require.resolve('webpack-dev-server/client') + '?/', 44 | // require.resolve('webpack/hot/dev-server'), 45 | require.resolve('react-dev-utils/webpackHotDevClient'), 46 | // We ship a few polyfills by default: 47 | require.resolve('./polyfills'), 48 | // Finally, this is your app's code: 49 | paths.appIndexJs 50 | // We include the app code last so that if there is a runtime error during 51 | // initialization, it doesn't blow up the WebpackDevServer client, and 52 | // changing JS code would still trigger a refresh. 53 | ], 54 | output: { 55 | // Next line is not used in dev but WebpackDevServer crashes without it: 56 | path: paths.appBuild, 57 | // Add /* filename */ comments to generated require()s in the output. 58 | pathinfo: true, 59 | // This does not produce a real file. It's just the virtual path that is 60 | // served by WebpackDevServer in development. This is the JS bundle 61 | // containing code from all our entry points, and the Webpack runtime. 62 | filename: 'static/js/bundle.js', 63 | // This is the URL that app is served from. We use "/" in development. 64 | publicPath: publicPath 65 | }, 66 | resolve: { 67 | // This allows you to set a fallback for where Webpack should look for modules. 68 | // We read `NODE_PATH` environment variable in `paths.js` and pass paths here. 69 | // We use `fallback` instead of `root` because we want `node_modules` to "win" 70 | // if there any conflicts. This matches Node resolution mechanism. 71 | // https://github.com/facebookincubator/create-react-app/issues/253 72 | fallback: paths.nodePaths, 73 | // These are the reasonable defaults supported by the Node ecosystem. 74 | // We also include JSX as a common component filename extension to support 75 | // some tools, although we do not recommend using it, see: 76 | // https://github.com/facebookincubator/create-react-app/issues/290 77 | extensions: ['.js', '.json', '.jsx', ''], 78 | alias: { 79 | // Support React Native Web 80 | // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 81 | 'react-native': 'react-native-web' 82 | }, 83 | root: [ 84 | path.resolve('./src'), 85 | ], 86 | modulesDirectories: ['src', 'node_modules'] 87 | }, 88 | 89 | module: { 90 | // First, run the linter. 91 | // It's important to do this before Babel processes the JS. 92 | preLoaders: [ 93 | { 94 | test: /\.(js|jsx)$/, 95 | loader: 'eslint', 96 | include: paths.appSrc, 97 | } 98 | ], 99 | loaders: [ 100 | // Process JS with Babel. 101 | { 102 | test: /\.(js|jsx)$/, 103 | include: paths.appSrc, 104 | loader: 'babel', 105 | query: { 106 | 107 | // This is a feature of `babel-loader` for webpack (not Babel itself). 108 | // It enables caching results in ./node_modules/.cache/react-scripts/ 109 | // directory for faster rebuilds. We use findCacheDir() because of: 110 | // https://github.com/facebookincubator/create-react-app/issues/483 111 | cacheDirectory: findCacheDir({ 112 | name: 'react-scripts' 113 | }) 114 | } 115 | }, 116 | // "postcss" loader applies autoprefixer to our CSS. 117 | // "css" loader resolves paths in CSS and adds assets as dependencies. 118 | // "style" loader turns CSS into JS modules that inject