├── .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 | 
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