mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 01:13:45 +00:00
Merge ff5987e597 into 50df2b78be
This commit is contained in:
commit
494f2e2823
17 changed files with 38813 additions and 344 deletions
|
|
@ -22,10 +22,9 @@ $ (cd dashboard/assets && ./node_modules/.bin/webpack --watch)
|
|||
$ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5
|
||||
```
|
||||
|
||||
To bundle up the final UI into Geth, run `webpack` and `go generate`:
|
||||
To bundle up the final UI into Geth, run `go generate`:
|
||||
|
||||
```
|
||||
$ (cd dashboard/assets && ./node_modules/.bin/webpack)
|
||||
$ go generate ./dashboard
|
||||
```
|
||||
|
||||
|
|
|
|||
38404
dashboard/assets.go
38404
dashboard/assets.go
File diff suppressed because one or more lines are too long
68
dashboard/assets/components/Body.jsx
Normal file
68
dashboard/assets/components/Body.jsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// 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/withStyles';
|
||||
|
||||
import SideBar from './SideBar.jsx';
|
||||
import Content from "./Content.jsx";
|
||||
|
||||
// Styles for the Body component.
|
||||
const styles = theme => ({
|
||||
body: {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
// Body renders the body of the dashboard.
|
||||
@withStyles(styles)
|
||||
class Body extends Component {
|
||||
render() {
|
||||
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||
|
||||
return (
|
||||
<div className={classes.body}>
|
||||
<SideBar
|
||||
opened={this.props.opened}
|
||||
changeContent={this.props.changeContent}
|
||||
/>
|
||||
<Content
|
||||
active={this.props.active}
|
||||
memory={this.props.memory}
|
||||
traffic={this.props.traffic}
|
||||
logs={this.props.logs}
|
||||
shouldUpdate={this.props.shouldUpdate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Body.propTypes = {
|
||||
opened: PropTypes.bool.isRequired,
|
||||
changeContent: PropTypes.func.isRequired,
|
||||
active: PropTypes.string.isRequired,
|
||||
memory: PropTypes.array.isRequired,
|
||||
traffic: PropTypes.array.isRequired,
|
||||
logs: PropTypes.array.isRequired,
|
||||
shouldUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default Body;
|
||||
48
dashboard/assets/components/ChartGrid.jsx
Normal file
48
dashboard/assets/components/ChartGrid.jsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// 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 {ResponsiveContainer} from 'recharts';
|
||||
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
export default ChartGrid;
|
||||
|
|
@ -22,15 +22,16 @@ export const LIMIT = {
|
|||
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", },
|
||||
home: { title: "Home", icon: "home", },
|
||||
chain: { title: "Chain", icon: "link", },
|
||||
transactions: { title: "Transactions", icon: "credit-card", },
|
||||
network: { title: "Network", icon: "globe", },
|
||||
system: { title: "System", icon: "tachometer", },
|
||||
logs: { title: "Logs", icon: "list", },
|
||||
};
|
||||
// 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.
|
||||
|
|
@ -48,5 +49,4 @@ export const DATA_KEYS = (() => {
|
|||
return DK;
|
||||
})();
|
||||
|
||||
// Temporary - taken from Material-UI
|
||||
export const DRAWER_WIDTH = 240;
|
||||
export const DURATION = 200;
|
||||
|
|
|
|||
71
dashboard/assets/components/Content.jsx
Normal file
71
dashboard/assets/components/Content.jsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// 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/withStyles';
|
||||
|
||||
import Home from './Home.jsx';
|
||||
import {TAGS} from './Common.jsx';
|
||||
|
||||
// Styles for the Content component.
|
||||
const styles = theme => ({
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.background.default,
|
||||
padding: theme.spacing.unit * 3,
|
||||
overflow: 'auto',
|
||||
},
|
||||
});
|
||||
|
||||
// Content renders the chosen content.
|
||||
@withStyles(styles)
|
||||
class Content extends Component {
|
||||
render() {
|
||||
const {classes, active, memory, traffic, logs, shouldUpdate} = this.props;
|
||||
|
||||
let content = null;
|
||||
switch(active) {
|
||||
case TAGS.home.id:
|
||||
content = <Home memory={memory} traffic={traffic} shouldUpdate={shouldUpdate} />;
|
||||
break;
|
||||
case TAGS.chain.id:
|
||||
content = <div>Chain is under construction.</div>;
|
||||
break;
|
||||
case TAGS.transactions.id:
|
||||
content = <div>Transactions is under construction.</div>;
|
||||
break;
|
||||
case TAGS.network.id:
|
||||
content = <div>Network is under construction.</div>;
|
||||
break;
|
||||
case TAGS.system.id:
|
||||
content = <div>System is under construction.</div>;
|
||||
break;
|
||||
case TAGS.logs.id:
|
||||
content = <div>{logs.map((log, index) => <div key={index}>{log}</div>)}</div>;
|
||||
}
|
||||
|
||||
return <div className={classes.content}>{content}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
active: PropTypes.string.isRequired,
|
||||
shouldUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default Content;
|
||||
|
|
@ -15,27 +15,29 @@
|
|||
// 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 withStyles from 'material-ui/styles/withStyles';
|
||||
|
||||
import Header from './Header.jsx';
|
||||
import Main from "./Main.jsx";
|
||||
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
|
||||
import Body from './Body.jsx';
|
||||
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS} from "./Common.jsx";
|
||||
|
||||
// Styles for the Dashboard component.
|
||||
const styles = theme => ({
|
||||
appFrame: {
|
||||
position: 'relative',
|
||||
dashboard: {
|
||||
display: 'flex',
|
||||
flexFlow: 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: theme.palette.background.default,
|
||||
zIndex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
|
||||
// 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.
|
||||
@withStyles(styles)
|
||||
class Dashboard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -45,7 +47,7 @@ class Dashboard extends Component {
|
|||
memory: [],
|
||||
traffic: [],
|
||||
logs: [],
|
||||
shouldUpdate: {},
|
||||
shouldUpdate: {}, // contains the labels of the incoming sample types
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +76,6 @@ class Dashboard extends Component {
|
|||
|
||||
// update analyzes the incoming message, and updates the charts' content correspondingly.
|
||||
update = msg => {
|
||||
console.log(msg);
|
||||
this.setState(prevState => {
|
||||
let newState = [];
|
||||
newState.shouldUpdate = {};
|
||||
|
|
@ -121,9 +122,9 @@ class Dashboard extends Component {
|
|||
});
|
||||
};
|
||||
|
||||
// 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} : {});
|
||||
// changeContent sets the active label, which is used at the content rendering.
|
||||
changeContent = newActive => {
|
||||
this.setState(prevState => prevState.active !== newActive ? {active: newActive} : {});
|
||||
};
|
||||
|
||||
openSideBar = () => {
|
||||
|
|
@ -135,22 +136,18 @@ class Dashboard extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||
|
||||
return (
|
||||
<div className={classes.appFrame}>
|
||||
<div className={classes.dashboard}>
|
||||
<Header
|
||||
opened={this.state.sideBar}
|
||||
open={this.openSideBar}
|
||||
openSideBar={this.openSideBar}
|
||||
closeSideBar={this.closeSideBar}
|
||||
/>
|
||||
<SideBar
|
||||
<Body
|
||||
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}
|
||||
|
|
@ -162,8 +159,4 @@ class Dashboard extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
Dashboard.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Dashboard);
|
||||
export default Dashboard;
|
||||
|
|
|
|||
|
|
@ -16,61 +16,76 @@
|
|||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {withStyles} from 'material-ui/styles';
|
||||
|
||||
import withStyles from 'material-ui/styles/withStyles';
|
||||
import AppBar from 'material-ui/AppBar';
|
||||
import Toolbar from 'material-ui/Toolbar';
|
||||
import Toolbar from "material-ui/Toolbar";
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
import IconButton from "material-ui/IconButton";
|
||||
import Typography from 'material-ui/Typography';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import MenuIcon from 'material-ui-icons/Menu';
|
||||
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
|
||||
|
||||
import {DRAWER_WIDTH} from './Common.jsx';
|
||||
import {DURATION} from './Common.jsx';
|
||||
|
||||
// arrowDefault is the default style of the arrow button.
|
||||
const arrowDefault = {
|
||||
transition: `transform ${DURATION}ms`,
|
||||
};
|
||||
// arrowTransition is the additional style of the arrow button corresponding to the transition's state.
|
||||
const arrowTransition = {
|
||||
entered: { transform: "rotate(180deg)" },
|
||||
};
|
||||
// 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,
|
||||
}),
|
||||
header: {
|
||||
backgroundColor: theme.palette.background.appBar,
|
||||
color: theme.palette.getContrastText(theme.palette.background.appBar),
|
||||
zIndex: theme.zIndex.appBar,
|
||||
},
|
||||
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,
|
||||
}),
|
||||
toolbar: {
|
||||
paddingLeft: theme.spacing.unit,
|
||||
paddingRight: theme.spacing.unit,
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: 12,
|
||||
marginRight: 20,
|
||||
},
|
||||
hide: {
|
||||
display: 'none',
|
||||
mainText: {
|
||||
paddingLeft: theme.spacing.unit,
|
||||
},
|
||||
});
|
||||
|
||||
// Header renders a header, which contains a sidebar opener icon when that is closed.
|
||||
// Header renders the header of the dashboard.
|
||||
@withStyles(styles)
|
||||
class Header extends Component {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.opened !== this.props.opened;
|
||||
}
|
||||
|
||||
// changeSideBar opens or closes the sidebar corresponding to the previous state.
|
||||
changeSideBar = () => {
|
||||
this.props.opened ? this.props.closeSideBar() : this.props.openSideBar();
|
||||
};
|
||||
|
||||
// arrowButton is connected to the sidebar; changes its state.
|
||||
arrowButton = transitionState => (
|
||||
<IconButton onClick={this.changeSideBar}>
|
||||
<ChevronLeftIcon
|
||||
style={{
|
||||
...arrowDefault,
|
||||
...arrowTransition[transitionState],
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
const {classes, opened} = this.props; // The classes property is injected by withStyles().
|
||||
|
||||
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
|
||||
<AppBar position="static" className={classes.header}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<Transition mountOnEnter in={opened} timeout={{enter: DURATION}}>
|
||||
{this.arrowButton}
|
||||
</Transition>
|
||||
<Typography type="title" color="inherit" noWrap className={classes.mainText}>
|
||||
PoC Go Ethereum Dashboard
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
|
@ -79,9 +94,9 @@ class Header extends Component {
|
|||
}
|
||||
|
||||
Header.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
opened: PropTypes.bool.isRequired,
|
||||
open: PropTypes.func.isRequired,
|
||||
opened: PropTypes.bool.isRequired,
|
||||
openSideBar: PropTypes.func.isRequired,
|
||||
closeSideBar: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Header);
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -16,65 +16,48 @@
|
|||
|
||||
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 withTheme from 'material-ui/styles/withTheme';
|
||||
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line} from 'recharts';
|
||||
|
||||
import ChartGrid from './ChartGrid.jsx';
|
||||
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.
|
||||
// Home renders the home content.
|
||||
@withTheme()
|
||||
class Home extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const {theme} = props; // The theme property is injected by withTheme().
|
||||
this.memoryColor = theme.palette.primary[300];
|
||||
this.trafficColor = theme.palette.secondary[300];
|
||||
}
|
||||
|
||||
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];
|
||||
const {memory, traffic} = this.props;
|
||||
|
||||
return (
|
||||
<ChartGrid spacing={24}>
|
||||
<AreaChart xs={6} height={300} values={this.props.memory}>
|
||||
<AreaChart xs={6} height={300} values={memory}>
|
||||
<YAxis />
|
||||
<Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} />
|
||||
<Area type="monotone" dataKey="value" stroke={this.memoryColor} fill={this.memoryColor} />
|
||||
</AreaChart>
|
||||
<LineChart xs={6} height={300} values={this.props.traffic}>
|
||||
<Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} />
|
||||
<LineChart xs={6} height={300} values={traffic}>
|
||||
<Line type="monotone" dataKey="value" stroke={this.trafficColor} dot={false} />
|
||||
</LineChart>
|
||||
<LineChart xs={6} height={300} values={this.props.memory}>
|
||||
<LineChart xs={6} height={300} values={memory}>
|
||||
<YAxis />
|
||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
||||
<Line type="monotone" dataKey="value" stroke={memoryColor} dot={false} />
|
||||
<Line type="monotone" dataKey="value" stroke={this.memoryColor} dot={false} />
|
||||
</LineChart>
|
||||
<AreaChart xs={6} height={300} values={this.props.traffic}>
|
||||
<AreaChart xs={6} height={300} values={traffic}>
|
||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
|
||||
<Area type="monotone" dataKey="value" stroke={trafficColor} fill={trafficColor} />
|
||||
<Area type="monotone" dataKey="value" stroke={this.trafficColor} fill={this.trafficColor} />
|
||||
</AreaChart>
|
||||
</ChartGrid>
|
||||
);
|
||||
|
|
@ -82,8 +65,7 @@ class Home extends Component {
|
|||
}
|
||||
|
||||
Home.propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
shouldUpdate: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withTheme()(Home);
|
||||
export default Home;
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
// 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);
|
||||
|
|
@ -16,35 +16,38 @@
|
|||
|
||||
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';
|
||||
import withStyles from 'material-ui/styles/withStyles';
|
||||
import List, {ListItem, ListItemIcon, ListItemText} from 'material-ui/List';
|
||||
import Icon from 'material-ui/Icon';
|
||||
import Transition from 'react-transition-group/Transition';
|
||||
import {Icon as FontAwesome} from 'react-fa'
|
||||
|
||||
import {TAGS, DURATION} from './Common.jsx';
|
||||
|
||||
// menuDefault is the default style of the menu.
|
||||
const menuDefault = {
|
||||
transition: `margin-left ${DURATION}ms`,
|
||||
};
|
||||
// menu Transition is the additional style of the menu corresponding to the transition's state.
|
||||
const menuTransition = {
|
||||
entered: {marginLeft: -200},
|
||||
};
|
||||
// Styles for the SideBar component.
|
||||
const styles = theme => ({
|
||||
drawerPaper: {
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
width: DRAWER_WIDTH,
|
||||
list: {
|
||||
background: theme.palette.background.appBar,
|
||||
},
|
||||
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,
|
||||
}
|
||||
listItem: {
|
||||
minWidth: theme.spacing.unit * 3,
|
||||
},
|
||||
icon: {
|
||||
fontSize: theme.spacing.unit * 3,
|
||||
},
|
||||
});
|
||||
|
||||
// SideBar renders a sidebar component.
|
||||
// SideBar renders the sidebar of the dashboard.
|
||||
@withStyles(styles)
|
||||
class SideBar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -57,50 +60,58 @@ class SideBar extends Component {
|
|||
const id = TAGS[key].id;
|
||||
this.clickOn[id] = event => {
|
||||
event.preventDefault();
|
||||
console.log(event.target.key);
|
||||
this.props.changeContent(id);
|
||||
props.changeContent(id);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// The classes property is injected by withStyles().
|
||||
const {classes} = this.props;
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.opened !== this.props.opened;
|
||||
}
|
||||
|
||||
// menu renders the list of the menu items.
|
||||
menu = transitionState => {
|
||||
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||
|
||||
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>
|
||||
<div className={classes.list}>
|
||||
<List>
|
||||
{
|
||||
Object.values(TAGS).map(tag => (
|
||||
<ListItem button key={tag.id} onClick={this.clickOn[tag.id]} className={classes.listItem}>
|
||||
<ListItemIcon>
|
||||
<Icon className={classes.icon}>
|
||||
<FontAwesome name={tag.icon} />
|
||||
</Icon>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={tag.title}
|
||||
style={{
|
||||
...menuDefault,
|
||||
...menuTransition[transitionState],
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Transition mountOnEnter in={this.props.opened} timeout={{enter: DURATION}}>
|
||||
{this.menu}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SideBar.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
opened: PropTypes.bool.isRequired,
|
||||
close: PropTypes.func.isRequired,
|
||||
changeContent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SideBar);
|
||||
export default SideBar;
|
||||
|
|
|
|||
25
dashboard/assets/faOnlyWoffLoader.js
Normal file
25
dashboard/assets/faOnlyWoffLoader.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// 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/>.
|
||||
|
||||
// faOnlyWoffLoader removes the .eot, .ttf, .svg dependencies of the FontAwesome, because they produce unused extra blobs.
|
||||
module.exports = function(content) {
|
||||
content = content.replace(/src.*url(?!.*url.*(\.eot)).*(\.eot)[^;]*;/,'');
|
||||
content = content.replace(/url(?!.*url.*(\.eot)).*(\.eot)[^,]*,/,'');
|
||||
content = content.replace(/url(?!.*url.*(\.ttf)).*(\.ttf)[^,]*,/,'');
|
||||
content = content.replace(/,[^,]*url(?!.*url.*(\.svg)).*(\.svg)[^;]*;/,';');
|
||||
|
||||
return content;
|
||||
};
|
||||
|
|
@ -15,7 +15,8 @@
|
|||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import {hydrate} from 'react-dom';
|
||||
import {render} from 'react-dom';
|
||||
|
||||
import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles';
|
||||
|
||||
import Dashboard from './components/Dashboard.jsx';
|
||||
|
|
@ -28,7 +29,7 @@ const theme = createMuiTheme({
|
|||
});
|
||||
|
||||
// Renders the whole dashboard.
|
||||
hydrate(
|
||||
render(
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<Dashboard />
|
||||
</MuiThemeProvider>,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,30 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"material-ui": "^1.0.0-beta.21",
|
||||
"material-ui-icons": "^1.0.0-beta.17",
|
||||
"react-fa": "^5.0.0",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"recharts": "^1.0.0-beta.1",
|
||||
|
||||
"classnames": "^2.2.5",
|
||||
"eslint": "^4.11.0",
|
||||
"eslint-plugin-react": "^7.5.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.1.1",
|
||||
"react-dom": "^16.1.1",
|
||||
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.0.1",
|
||||
"babel-eslint": "^8.0.2",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"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",
|
||||
"css-loader": "^0.28.7",
|
||||
"path": "^0.12.7",
|
||||
"prop-types": "^15.6.0",
|
||||
"recharts": "^1.0.0-beta.0",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"style-loader": "^0.19.0",
|
||||
"url": "^0.11.0",
|
||||
"url-loader": "^0.6.2",
|
||||
"webpack": "^3.5.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@
|
|||
<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 -->
|
||||
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico" />
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
width: 16px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #212121;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="height: 100%; margin: 0">
|
||||
<div id="dashboard" style="height: 100%"></div>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
// 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 webpack = require('webpack');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
|
|
@ -22,14 +23,34 @@ module.exports = {
|
|||
path: path.resolve(__dirname, 'public'),
|
||||
filename: 'bundle.js',
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
comments: false,
|
||||
mangle: false,
|
||||
beautify: true,
|
||||
}),
|
||||
],
|
||||
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']
|
||||
}
|
||||
test: /\.jsx$/, // regexp for JSX files
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
plugins: ['transform-decorators-legacy'], // @withStyles, @withTheme
|
||||
presets: ['env', 'react', 'stage-0'],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /font-awesome\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
path.resolve(__dirname, './faOnlyWoffLoader.js'),
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.woff2?$/,
|
||||
loader: 'url-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package dashboard
|
||||
|
||||
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets/public/...
|
||||
//go:generate ./assets/node_modules/.bin/webpack --config ./assets/webpack.config.js --context ./assets
|
||||
//go:generate gofmt -s -w .
|
||||
//go:generate go-bindata -nometadata -o assets.go -prefix assets -nocompress -pkg dashboard assets/public/...
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -277,6 +279,7 @@ func (db *Dashboard) collectData() {
|
|||
func (db *Dashboard) collectLogs() {
|
||||
defer db.wg.Done()
|
||||
|
||||
id := 1
|
||||
// TODO (kurkomisi): log collection comes here.
|
||||
for {
|
||||
select {
|
||||
|
|
@ -285,8 +288,9 @@ func (db *Dashboard) collectLogs() {
|
|||
return
|
||||
case <-time.After(db.config.Refresh / 2):
|
||||
db.sendToAll(&message{
|
||||
Log: "This is a fake log.",
|
||||
Log: fmt.Sprint(id, ": This is a fake log."),
|
||||
})
|
||||
id++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue