aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/assets
diff options
context:
space:
mode:
authorKurkó Mihály <kurkomisi@users.noreply.github.com>2017-11-15 01:34:00 +0800
committerPéter Szilágyi <peterke@gmail.com>2017-11-15 01:34:00 +0800
commitba62215d9ef8655743ce7b1380056755943e3d72 (patch)
treeca74e2d737759f44930469f5c285443efe5f99cc /dashboard/assets
parent984c25ac406e86255b289a3d7fec4cd91a17707f (diff)
downloadgo-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.tar
go-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.tar.gz
go-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.tar.bz2
go-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.tar.lz
go-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.tar.xz
go-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.tar.zst
go-tangerine-ba62215d9ef8655743ce7b1380056755943e3d72.zip
cmd, dashboard: dashboard using React, Material-UI, Recharts (#15393)
* cmd, dashboard: dashboard using React, Material-UI, Recharts * cmd, dashboard, metrics: initial proof of concept dashboard * dashboard: delete blobs * dashboard: gofmt -s -w . * dashboard: minor text and code polishes
Diffstat (limited to 'dashboard/assets')
-rw-r--r--dashboard/assets/.eslintrc52
-rw-r--r--dashboard/assets/components/Common.jsx52
-rw-r--r--dashboard/assets/components/Dashboard.jsx169
-rw-r--r--dashboard/assets/components/Header.jsx87
-rw-r--r--dashboard/assets/components/Home.jsx89
-rw-r--r--dashboard/assets/components/Main.jsx109
-rw-r--r--dashboard/assets/components/SideBar.jsx106
-rw-r--r--dashboard/assets/index.jsx36
-rw-r--r--dashboard/assets/package.json22
-rw-r--r--dashboard/assets/public/dashboard.html17
-rw-r--r--dashboard/assets/webpack.config.js36
11 files changed, 775 insertions, 0 deletions
diff --git a/dashboard/assets/.eslintrc b/dashboard/assets/.eslintrc
new file mode 100644
index 000000000..04ae15d01
--- /dev/null
+++ b/dashboard/assets/.eslintrc
@@ -0,0 +1,52 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
+{
+ "plugins": [
+ "react"
+ ],
+ "parser": "babel-eslint",
+ "parserOptions": {
+ "ecmaFeatures": {
+ "jsx": true,
+ "modules": true
+ }
+ },
+ "rules": {
+ "react/prefer-es6-class": 2,
+ "react/prefer-stateless-function": 2,
+ "react/jsx-pascal-case": 2,
+ "react/jsx-closing-bracket-location": [1, {"selfClosing": "tag-aligned", "nonEmpty": "tag-aligned"}],
+ "react/jsx-closing-tag-location": 1,
+ "jsx-quotes": ["error", "prefer-double"],
+ "no-multi-spaces": "error",
+ "react/jsx-tag-spacing": 2,
+ "react/jsx-curly-spacing": [2, {"when": "never", "children": true}],
+ "react/jsx-boolean-value": 2,
+ "react/no-string-refs": 2,
+ "react/jsx-wrap-multilines": 2,
+ "react/self-closing-comp": 2,
+ "react/jsx-no-bind": 2,
+ "react/require-render-return": 2,
+ "react/no-is-mounted": 2,
+ "key-spacing": ["error", {"align": {
+ "beforeColon": false,
+ "afterColon": true,
+ "on": "value"
+ }}]
+ }
+}
diff --git a/dashboard/assets/components/Common.jsx b/dashboard/assets/components/Common.jsx
new file mode 100644
index 000000000..5129939c5
--- /dev/null
+++ b/dashboard/assets/components/Common.jsx
@@ -0,0 +1,52 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+// isNullOrUndefined returns true if the given variable is null or undefined.
+export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
+
+export const LIMIT = {
+ memory: 200, // Maximum number of memory data samples.
+ traffic: 200, // Maximum number of traffic data samples.
+ log: 200, // Maximum number of logs.
+};
+// The sidebar menu and the main content are rendered based on these elements.
+export const TAGS = (() => {
+ const T = {
+ home: { title: "Home", },
+ chain: { title: "Chain", },
+ transactions: { title: "Transactions", },
+ network: { title: "Network", },
+ system: { title: "System", },
+ logs: { title: "Logs", },
+ };
+ // Using the key is circumstantial in some cases, so it is better to insert it also as a value.
+ // This way the mistyping is prevented.
+ for(let key in T) {
+ T[key]['id'] = key;
+ }
+ return T;
+})();
+
+export const DATA_KEYS = (() => {
+ const DK = {};
+ ["memory", "traffic", "logs"].map(key => {
+ DK[key] = key;
+ });
+ return DK;
+})();
+
+// Temporary - taken from Material-UI
+export const DRAWER_WIDTH = 240;
diff --git a/dashboard/assets/components/Dashboard.jsx b/dashboard/assets/components/Dashboard.jsx
new file mode 100644
index 000000000..740acf959
--- /dev/null
+++ b/dashboard/assets/components/Dashboard.jsx
@@ -0,0 +1,169 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import {withStyles} from 'material-ui/styles';
+
+import SideBar from './SideBar.jsx';
+import Header from './Header.jsx';
+import Main from "./Main.jsx";
+import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
+
+// Styles for the Dashboard component.
+const styles = theme => ({
+ appFrame: {
+ position: 'relative',
+ display: 'flex',
+ width: '100%',
+ height: '100%',
+ background: theme.palette.background.default,
+ },
+});
+
+// Dashboard is the main component, which renders the whole page, makes connection with the server and listens for messages.
+// When there is an incoming message, updates the page's content correspondingly.
+class Dashboard extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ active: TAGS.home.id, // active menu
+ sideBar: true, // true if the sidebar is opened
+ memory: [],
+ traffic: [],
+ logs: [],
+ shouldUpdate: {},
+ };
+ }
+
+ // componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
+ componentDidMount() {
+ this.reconnect();
+ }
+
+ // reconnect establishes a websocket connection with the server, listens for incoming messages
+ // and tries to reconnect on connection loss.
+ reconnect = () => {
+ const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
+
+ server.onmessage = event => {
+ const msg = JSON.parse(event.data);
+ if (isNullOrUndefined(msg)) {
+ return;
+ }
+ this.update(msg);
+ };
+
+ server.onclose = () => {
+ setTimeout(this.reconnect, 3000);
+ };
+ };
+
+ // update analyzes the incoming message, and updates the charts' content correspondingly.
+ update = msg => {
+ console.log(msg);
+ this.setState(prevState => {
+ let newState = [];
+ newState.shouldUpdate = {};
+ const insert = (key, values, limit) => {
+ newState[key] = [...prevState[key], ...values];
+ while (newState[key].length > limit) {
+ newState[key].shift();
+ }
+ newState.shouldUpdate[key] = true;
+ };
+ // (Re)initialize the state with the past data.
+ if (!isNullOrUndefined(msg.history)) {
+ const memory = DATA_KEYS.memory;
+ const traffic = DATA_KEYS.traffic;
+ newState[memory] = [];
+ newState[traffic] = [];
+ if (!isNullOrUndefined(msg.history.memorySamples)) {
+ newState[memory] = msg.history.memorySamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
+ while (newState[memory].length > LIMIT.memory) {
+ newState[memory].shift();
+ }
+ newState.shouldUpdate[memory] = true;
+ }
+ if (!isNullOrUndefined(msg.history.trafficSamples)) {
+ newState[traffic] = msg.history.trafficSamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
+ while (newState[traffic].length > LIMIT.traffic) {
+ newState[traffic].shift();
+ }
+ newState.shouldUpdate[traffic] = true;
+ }
+ }
+ // Insert the new data samples.
+ if (!isNullOrUndefined(msg.memory)) {
+ insert(DATA_KEYS.memory, [isNullOrUndefined(msg.memory.value) ? 0 : msg.memory.value], LIMIT.memory);
+ }
+ if (!isNullOrUndefined(msg.traffic)) {
+ insert(DATA_KEYS.traffic, [isNullOrUndefined(msg.traffic.value) ? 0 : msg.traffic.value], LIMIT.traffic);
+ }
+ if (!isNullOrUndefined(msg.log)) {
+ insert(DATA_KEYS.logs, [msg.log], LIMIT.log);
+ }
+
+ return newState;
+ });
+ };
+
+ // The change of the active label on the SideBar component will trigger a new render in the Main component.
+ changeContent = active => {
+ this.setState(prevState => prevState.active !== active ? {active: active} : {});
+ };
+
+ openSideBar = () => {
+ this.setState({sideBar: true});
+ };
+
+ closeSideBar = () => {
+ this.setState({sideBar: false});
+ };
+
+ render() {
+ // The classes property is injected by withStyles().
+ const {classes} = this.props;
+
+ return (
+ <div className={classes.appFrame}>
+ <Header
+ opened={this.state.sideBar}
+ open={this.openSideBar}
+ />
+ <SideBar
+ opened={this.state.sideBar}
+ close={this.closeSideBar}
+ changeContent={this.changeContent}
+ />
+ <Main
+ opened={this.state.sideBar}
+ active={this.state.active}
+ memory={this.state.memory}
+ traffic={this.state.traffic}
+ logs={this.state.logs}
+ shouldUpdate={this.state.shouldUpdate}
+ />
+ </div>
+ );
+ }
+}
+
+Dashboard.propTypes = {
+ classes: PropTypes.object.isRequired,
+};
+
+export default withStyles(styles)(Dashboard);
diff --git a/dashboard/assets/components/Header.jsx b/dashboard/assets/components/Header.jsx
new file mode 100644
index 000000000..7cf57c9c0
--- /dev/null
+++ b/dashboard/assets/components/Header.jsx
@@ -0,0 +1,87 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import {withStyles} from 'material-ui/styles';
+import AppBar from 'material-ui/AppBar';
+import Toolbar from 'material-ui/Toolbar';
+import Typography from 'material-ui/Typography';
+import IconButton from 'material-ui/IconButton';
+import MenuIcon from 'material-ui-icons/Menu';
+
+import {DRAWER_WIDTH} from './Common.jsx';
+
+// Styles for the Header component.
+const styles = theme => ({
+ appBar: {
+ position: 'absolute',
+ transition: theme.transitions.create(['margin', 'width'], {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.leavingScreen,
+ }),
+ },
+ appBarShift: {
+ marginLeft: DRAWER_WIDTH,
+ width: `calc(100% - ${DRAWER_WIDTH}px)`,
+ transition: theme.transitions.create(['margin', 'width'], {
+ easing: theme.transitions.easing.easeOut,
+ duration: theme.transitions.duration.enteringScreen,
+ }),
+ },
+ menuButton: {
+ marginLeft: 12,
+ marginRight: 20,
+ },
+ hide: {
+ display: 'none',
+ },
+});
+
+// Header renders a header, which contains a sidebar opener icon when that is closed.
+class Header extends Component {
+ render() {
+ // The classes property is injected by withStyles().
+ const {classes} = this.props;
+
+ return (
+ <AppBar className={classNames(classes.appBar, this.props.opened && classes.appBarShift)}>
+ <Toolbar disableGutters={!this.props.opened}>
+ <IconButton
+ color="contrast"
+ aria-label="open drawer"
+ onClick={this.props.open}
+ className={classNames(classes.menuButton, this.props.opened && classes.hide)}
+ >
+ <MenuIcon />
+ </IconButton>
+ <Typography type="title" color="inherit" noWrap>
+ Go Ethereum Dashboard
+ </Typography>
+ </Toolbar>
+ </AppBar>
+ );
+ }
+}
+
+Header.propTypes = {
+ classes: PropTypes.object.isRequired,
+ opened: PropTypes.bool.isRequired,
+ open: PropTypes.func.isRequired,
+};
+
+export default withStyles(styles)(Header);
diff --git a/dashboard/assets/components/Home.jsx b/dashboard/assets/components/Home.jsx
new file mode 100644
index 000000000..f67bac555
--- /dev/null
+++ b/dashboard/assets/components/Home.jsx
@@ -0,0 +1,89 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import Grid from 'material-ui/Grid';
+import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line, ResponsiveContainer} from 'recharts';
+import {withTheme} from 'material-ui/styles';
+
+import {isNullOrUndefined, DATA_KEYS} from "./Common.jsx";
+
+// ChartGrid renders a grid container for responsive charts.
+// The children are Recharts components extended with the Material-UI's xs property.
+class ChartGrid extends Component {
+ render() {
+ return (
+ <Grid container spacing={this.props.spacing}>
+ {
+ React.Children.map(this.props.children, child => (
+ <Grid item xs={child.props.xs}>
+ <ResponsiveContainer width="100%" height={child.props.height}>
+ {React.cloneElement(child, {data: child.props.values.map(value => ({value: value}))})}
+ </ResponsiveContainer>
+ </Grid>
+ ))
+ }
+ </Grid>
+ );
+ }
+}
+
+ChartGrid.propTypes = {
+ spacing: PropTypes.number.isRequired,
+};
+
+// Home renders the home component.
+class Home extends Component {
+ shouldComponentUpdate(nextProps) {
+ return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) ||
+ !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]);
+ }
+
+ render() {
+ const {theme} = this.props;
+ const memoryColor = theme.palette.primary[300];
+ const trafficColor = theme.palette.secondary[300];
+
+ return (
+ <ChartGrid spacing={24}>
+ <AreaChart xs={6} height={300} values={this.props.memory}>
+ <YAxis />
+ <Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} />
+ </AreaChart>
+ <LineChart xs={6} height={300} values={this.props.traffic}>
+ <Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} />
+ </LineChart>
+ <LineChart xs={6} height={300} values={this.props.memory}>
+ <YAxis />
+ <CartesianGrid stroke="#eee" strokeDasharray="5 5" />
+ <Line type="monotone" dataKey="value" stroke={memoryColor} dot={false} />
+ </LineChart>
+ <AreaChart xs={6} height={300} values={this.props.traffic}>
+ <CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
+ <Area type="monotone" dataKey="value" stroke={trafficColor} fill={trafficColor} />
+ </AreaChart>
+ </ChartGrid>
+ );
+ }
+}
+
+Home.propTypes = {
+ theme: PropTypes.object.isRequired,
+ shouldUpdate: PropTypes.object.isRequired,
+};
+
+export default withTheme()(Home);
diff --git a/dashboard/assets/components/Main.jsx b/dashboard/assets/components/Main.jsx
new file mode 100644
index 000000000..b119d1ffd
--- /dev/null
+++ b/dashboard/assets/components/Main.jsx
@@ -0,0 +1,109 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import {withStyles} from 'material-ui/styles';
+
+import {TAGS, DRAWER_WIDTH} from "./Common.jsx";
+import Home from './Home.jsx';
+
+// ContentSwitch chooses and renders the proper page content.
+class ContentSwitch extends Component {
+ render() {
+ switch(this.props.active) {
+ case TAGS.home.id:
+ return <Home memory={this.props.memory} traffic={this.props.traffic} shouldUpdate={this.props.shouldUpdate} />;
+ case TAGS.chain.id:
+ return null;
+ case TAGS.transactions.id:
+ return null;
+ case TAGS.network.id:
+ // Only for testing.
+ return null;
+ case TAGS.system.id:
+ return null;
+ case TAGS.logs.id:
+ return <div>{this.props.logs.map((log, index) => <div key={index}>{log}</div>)}</div>;
+ }
+ return null;
+ }
+}
+
+ContentSwitch.propTypes = {
+ active: PropTypes.string.isRequired,
+ shouldUpdate: PropTypes.object.isRequired,
+};
+
+// styles contains the styles for the Main component.
+const styles = theme => ({
+ content: {
+ width: '100%',
+ marginLeft: -DRAWER_WIDTH,
+ flexGrow: 1,
+ backgroundColor: theme.palette.background.default,
+ padding: theme.spacing.unit * 3,
+ transition: theme.transitions.create('margin', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.leavingScreen,
+ }),
+ marginTop: 56,
+ overflow: 'auto',
+ [theme.breakpoints.up('sm')]: {
+ content: {
+ height: 'calc(100% - 64px)',
+ marginTop: 64,
+ },
+ },
+ },
+ contentShift: {
+ marginLeft: 0,
+ transition: theme.transitions.create('margin', {
+ easing: theme.transitions.easing.easeOut,
+ duration: theme.transitions.duration.enteringScreen,
+ }),
+ },
+});
+
+// Main renders a component for the page content.
+class Main extends Component {
+ render() {
+ // The classes property is injected by withStyles().
+ const {classes} = this.props;
+
+ return (
+ <main className={classNames(classes.content, this.props.opened && classes.contentShift)}>
+ <ContentSwitch
+ active={this.props.active}
+ memory={this.props.memory}
+ traffic={this.props.traffic}
+ logs={this.props.logs}
+ shouldUpdate={this.props.shouldUpdate}
+ />
+ </main>
+ );
+ }
+}
+
+Main.propTypes = {
+ classes: PropTypes.object.isRequired,
+ opened: PropTypes.bool.isRequired,
+ active: PropTypes.string.isRequired,
+ shouldUpdate: PropTypes.object.isRequired,
+};
+
+export default withStyles(styles)(Main);
diff --git a/dashboard/assets/components/SideBar.jsx b/dashboard/assets/components/SideBar.jsx
new file mode 100644
index 000000000..ef077f1e0
--- /dev/null
+++ b/dashboard/assets/components/SideBar.jsx
@@ -0,0 +1,106 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import {withStyles} from 'material-ui/styles';
+import Drawer from 'material-ui/Drawer';
+import {IconButton} from "material-ui";
+import List, {ListItem, ListItemText} from 'material-ui/List';
+import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
+
+import {TAGS, DRAWER_WIDTH} from './Common.jsx';
+
+// Styles for the SideBar component.
+const styles = theme => ({
+ drawerPaper: {
+ position: 'relative',
+ height: '100%',
+ width: DRAWER_WIDTH,
+ },
+ drawerHeader: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
+ padding: '0 8px',
+ ...theme.mixins.toolbar,
+ transitionDuration: {
+ enter: theme.transitions.duration.enteringScreen,
+ exit: theme.transitions.duration.leavingScreen,
+ }
+ },
+});
+
+// SideBar renders a sidebar component.
+class SideBar extends Component {
+ constructor(props) {
+ super(props);
+
+ // clickOn contains onClick event functions for the menu items.
+ // Instantiate only once, and reuse the existing functions to prevent the creation of
+ // new function instances every time the render method is triggered.
+ this.clickOn = {};
+ for(let key in TAGS) {
+ const id = TAGS[key].id;
+ this.clickOn[id] = event => {
+ event.preventDefault();
+ console.log(event.target.key);
+ this.props.changeContent(id);
+ };
+ }
+ }
+
+ render() {
+ // The classes property is injected by withStyles().
+ const {classes} = this.props;
+
+ return (
+ <Drawer
+ type="persistent"
+ classes={{paper: classes.drawerPaper,}}
+ open={this.props.opened}
+ >
+ <div>
+ <div className={classes.drawerHeader}>
+ <IconButton onClick={this.props.close}>
+ <ChevronLeftIcon />
+ </IconButton>
+ </div>
+ <List>
+ {
+ Object.values(TAGS).map(tag => {
+ return (
+ <ListItem button key={tag.id} onClick={this.clickOn[tag.id]}>
+ <ListItemText primary={tag.title} />
+ </ListItem>
+ );
+ })
+ }
+ </List>
+ </div>
+ </Drawer>
+ );
+ }
+}
+
+SideBar.propTypes = {
+ classes: PropTypes.object.isRequired,
+ opened: PropTypes.bool.isRequired,
+ close: PropTypes.func.isRequired,
+ changeContent: PropTypes.func.isRequired,
+};
+
+export default withStyles(styles)(SideBar);
diff --git a/dashboard/assets/index.jsx b/dashboard/assets/index.jsx
new file mode 100644
index 000000000..1e5fdc892
--- /dev/null
+++ b/dashboard/assets/index.jsx
@@ -0,0 +1,36 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+import React from 'react';
+import {hydrate} from 'react-dom';
+import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles';
+
+import Dashboard from './components/Dashboard.jsx';
+
+// Theme for the dashboard.
+const theme = createMuiTheme({
+ palette: {
+ type: 'dark',
+ },
+});
+
+// Renders the whole dashboard.
+hydrate(
+ <MuiThemeProvider theme={theme}>
+ <Dashboard />
+ </MuiThemeProvider>,
+ document.getElementById('dashboard')
+);
diff --git a/dashboard/assets/package.json b/dashboard/assets/package.json
new file mode 100644
index 000000000..53376e5c8
--- /dev/null
+++ b/dashboard/assets/package.json
@@ -0,0 +1,22 @@
+{
+ "dependencies": {
+ "babel-core": "^6.26.0",
+ "babel-eslint": "^8.0.1",
+ "babel-loader": "^7.1.2",
+ "babel-preset-env": "^1.6.1",
+ "babel-preset-react": "^6.24.1",
+ "babel-preset-stage-0": "^6.24.1",
+ "classnames": "^2.2.5",
+ "eslint": "^4.5.0",
+ "eslint-plugin-react": "^7.4.0",
+ "material-ui": "^1.0.0-beta.18",
+ "material-ui-icons": "^1.0.0-beta.17",
+ "path": "^0.12.7",
+ "prop-types": "^15.6.0",
+ "recharts": "^1.0.0-beta.0",
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0",
+ "url": "^0.11.0",
+ "webpack": "^3.5.5"
+ }
+}
diff --git a/dashboard/assets/public/dashboard.html b/dashboard/assets/public/dashboard.html
new file mode 100644
index 000000000..e064a2b51
--- /dev/null
+++ b/dashboard/assets/public/dashboard.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en" style="height: 100%">
+ <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>Go Ethereum Dashboard</title>
+ <link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico"/>
+
+ <!-- TODO (kurkomisi): Return to the external libraries to speed up the bundling during development -->
+ </head>
+ <body style="height: 100%; margin: 0">
+ <div id="dashboard" style="height: 100%"></div>
+ <script src="bundle.js"></script>
+ </body>
+</html>
diff --git a/dashboard/assets/webpack.config.js b/dashboard/assets/webpack.config.js
new file mode 100644
index 000000000..13f8c3fbc
--- /dev/null
+++ b/dashboard/assets/webpack.config.js
@@ -0,0 +1,36 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+const path = require('path');
+
+module.exports = {
+ entry: './index.jsx',
+ output: {
+ path: path.resolve(__dirname, 'public'),
+ filename: 'bundle.js',
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.jsx$/, // regexp for JSX files
+ loader: 'babel-loader', // The babel configuration is in the package.json.
+ query: {
+ presets: ['env', 'react', 'stage-0']
+ }
+ },
+ ],
+ },
+};