dashboard: Inferno client

This commit is contained in:
Kurkó Mihály 2017-09-12 17:30:23 +03:00
parent 163aa99033
commit 6b499a610a
16 changed files with 999 additions and 333 deletions

3
.gitignore vendored
View file

@ -30,3 +30,6 @@ build/_vendor/pkg
# travis
profile.tmp
profile.cov
# dashboard
/go-ethereum/dashboard/bundler/node_modules/

32
dashboard/README.md Normal file
View file

@ -0,0 +1,32 @@
## Go Ethereum Dashboard
### Description
The GED is a data visualizer integrated into geth, intended to collect and visualize useful information of an Ethereum node.
The GED consists of two parts:
* The server listens to connections, collects data with a given refresh rate, and updates the dashboards through the opened connections.
* The client waits for update messages, updates the content and tries to reconnect on connection loss.
The client's UI is maintained by [Inferno][Inferno], a sympathetic React-like JavaScript library.
In order to create the Inferno's virtual DOM using JSX syntax, there is need to use the provided babel plugin.
[Webpack][Webpack] is used for bundling the resources in order to gain cost efficiency and maintainability.
[Node.js][Node.js] is used for installing the necessary dependencies.
### Installation steps
_Server_
1. `cd .../go-ethereum/`
1. `go generate ./dashboard && go install -v ./cmd/geth`
1. run the server with `geth --rinkeby --dashboard --vmodule=dashboard=5 --metrics console`
1. optionally use `--dashboard.assets=<path>` to set the assets' path => useful for debugging
1. enter `localhost:8080` (or change the configuration)
_Module bundler_
1. `cd .../go-ethereum/dashboard/bundler/`
1. `npm install`
1. `./node_modules/.bin/webpack` // check out `webpack.config.js`
[Inferno]: https://infernojs.org/
[Webpack]: https://webpack.github.io/
[Node.js]: https://nodejs.org/en/

File diff suppressed because one or more lines are too long

View file

@ -1,282 +0,0 @@
Chart.defaults.global.legend = { enabled: false };
const Component = React.Component;
// const Component = Inferno.Component;
// isNullOrUndefined returns true if a variable is null or undefined.
let isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
// MenuItem renders an item for a Menu component and the belonging submenu items, if there is any.
class MenuItem extends Component {
render = () => <li>
<a>
<i className={`fa ${this.props.className}`}/>
{this.props.text}
<div className="fa fa-chevron-down"/>
</a>
{
// Render dropdown menu only if there are children.
isNullOrUndefined(this.props.children) || <ul className="nav child_menu">
{React.Children.map(this.props.children, child => <li>{child}</li>)}
</ul>
}
</li>;
}
// Menu renders a menu component.
let Menu = () => <ul className="nav side-menu">
<MenuItem className="fa-home" text="Home">
<a href="dashboard1.html">Dashboard1</a>
<a href="dashboard2.html">Dashboard2</a>
</MenuItem>
<MenuItem className="fa-edit" text="Networking">
<a href="networking.html">Networking</a>
</MenuItem>
<MenuItem className="fa-desktop" text="Txpool">
<a href="txpool.html">Txpool</a>
</MenuItem>
<MenuItem className="fa-table" text="Logs">
<a href="logs.html">Logs</a>
</MenuItem>
<MenuItem className="fa-clone" text="Blockchain">
<a href="blockchain1.html">Blockchain1</a>
<a href="blockchain2.html">Blockchain2</a>
<a href="blockchain3.html">Blockchain3</a>
<a href="blockchain4.html">Blockchain4</a>
<a href="blockchain5.html">Blockchain5</a>
</MenuItem>
<MenuItem className="fa-bar-chart-o" text="System"/>
</ul>;
let Clearfix = () => <div className="clearfix"/>;
// SideBar renders a sidebar component.
let SideBar = () => <div className="col-md-3 left_col">
<div className="left_col scroll-view">
<div className="navbar nav_title" style={{border: 0}}>
<a href="dashboard.html" className="site_title"><i className="fa fa-paw"/> <span>Go Ethereum Dashboard</span></a>
</div>
<Clearfix/>
<div id="sidebar-menu" className="main_menu_side hidden-print main_menu">
<div className="menu_section">
<Menu/>
</div>
</div>
</div>
</div>;
// TopNavigation renders a top navigation component.
let TopNavigation = () => <div className="top_nav">
<div className="nav_menu">
<nav className="" role="navigation">
<div className="nav toggle">
<a id="_______menu_toggle"> {/* TODO (kurkomisi): Resize the main container on toggle */}
<i className="fa fa-bars"/>
</a>
</div>
</nav>
</div>
</div>;
// Chart name is already in use.
// ChartComponent renders a chart component and updates it, when the related data changes.
class ChartComponent extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount = () => this.state.chart = new Chart(this.data, {
type: this.props.type,
data: this.props.data,
});
render = () => {
if (!isNullOrUndefined(this.state.chart)) {
this.state.chart.update();
}
return (
<div className={this.props.className}>
<div className="x_panel">
<div className="x_title">
<h2>{this.props.text}</h2>
<ul className="nav navbar-right panel_toolbox">
<li><a className="collapse-link"><i className="fa fa-chevron-up"/></a></li>
{
// Render dropdown menu only if there are children.
isNullOrUndefined(this.props.children) || <li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button"
aria-expanded="false"><i className="fa fa-wrench"/></a>
<ul className="dropdown-menu" role="menu">
{React.Children.map(this.props.children, child => <li>{child}</li>)}
</ul>
</li>
}
<li><a className="close-link"><i className="fa fa-close"/></a>
</li>
</ul>
<Clearfix/>
</div>
<div className="x_content">
{/* The chart will be generated here after the component is mounted (this.componentDidMount). */}
<canvas ref={data => {
this.data = data
}}/>
</div>
</div>
</div>
);
}
}
// Row renders a row component of charts only if there is any chart.
class Row extends Component {
render = () => isNullOrUndefined(this.props.children) || <div className="row"> {this.props.children} </div>;
}
// PageContent renders a component for the page content.
class PageContent extends Component {
render = () => <div className="right_col" role="main">
<div className="">
<div className="page-title">
<div className="title_left">
<h3>Go Ethereum Dashboard
<small>Statistics</small>
</h3>
</div>
</div>
<Clearfix/>
<Row>
<ChartComponent className="col-md-6 col-sm-6 col-xs-12" text="Memory usage system/memory/inuse" type="line" data={this.props.charts.memory}>
<a href="#">Settings 1</a>
<a href="#">Settings 2</a>
</ChartComponent>
<ChartComponent className="col-md-6 col-sm-6 col-xs-12" text="Inbound traffic p2p/InboundTraffic" type="line" data={this.props.charts.traffic}/>
</Row>
<Clearfix/>
</div>
</div>;
}
// Footer renders a footer.
let Footer = () => <footer>
<div className="pull-right">
Gentelella - Bootstrap Admin Template by <a href="https://colorlib.com">Colorlib</a>
</div>
<Clearfix/>
</footer>;
const MEMORY_SAMPLE_LIMIT = 200; // Maximum number of memory data samples.
const TRAFFIC_SAMPLE_LIMIT = 200; // Maximum number of traffic data samples.
// Dashboard renders a full dashboard component, which makes connection with the server and waits for messages.
// When there is a message, correspondingly updates the page's content.
class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
charts: {
memory: {
labels: [],
datasets: [{
label: "system/memory/inuse",
backgroundColor: "rgba(38, 185, 154, 0.31)",
borderColor: "rgba(38, 185, 154, 0.7)",
pointBorderColor: "rgba(38, 185, 154, 0.7)",
pointBackgroundColor: "rgba(38, 185, 154, 0.7)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointBorderWidth: 1,
data: [],
}],
},
traffic: {
labels: [],
datasets: [{
label: "p2p/InboundTraffic",
backgroundColor: "rgba(3, 88, 106, 0.3)",
borderColor: "rgba(3, 88, 106, 0.70)",
pointBorderColor: "rgba(3, 88, 106, 0.70)",
pointBackgroundColor: "rgba(3, 88, 106, 0.70)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(151,187,205,1)",
pointBorderWidth: 1,
data: [],
}],
},
},
};
}
updateCharts = msg => {
let memory = this.state.charts.memory;
let traffic = this.state.charts.traffic;
// Fill the dashboard with the past data. metrics is set only in the first msg, after the connection is established.
if (msg.metrics !== undefined) {
// Clear the arrays to prevent data confusion with the previous connection.
memory.labels = [];
traffic.labels = [];
memory.datasets[0].data = [];
traffic.datasets[0].data = [];
let mem = msg.metrics.memory;
let traff = msg.metrics.processor; // TODO (kurkomisi): !!!
// Put the past data to the beginning of the arrays. This prevents confusion with the next msg data, which goes to the end.
for (let i = mem.length - 1; i >= 0 && MEMORY_SAMPLE_LIMIT > memory.labels.length; --i) {
memory.labels.unshift(mem[i].time.substring(mem[i].time.length - 5));
traffic.labels.unshift(mem[i].time.substring(mem[i].time.length - 5));
memory.datasets[0].data.unshift(mem[i].value);
traffic.datasets[0].data.unshift(traff[i].value);
}
this.setState({charts: {memory: memory, traffic: traffic,}}); // Update the components.
return;
}
// Put the new data to the end of the arrays.
if (msg.memory !== undefined) {
// Remove the first elements in case the samples' amount exceeds the limit.
if (memory.labels.length === MEMORY_SAMPLE_LIMIT) {
memory.labels.shift();
traffic.labels.shift();
memory.datasets[0].data.shift();
traffic.datasets[0].data.shift();
}
memory.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
traffic.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
memory.datasets[0].data.push(msg.memory.value);
traffic.datasets[0].data.push(msg.processor.value);
this.setState({charts: {memory: memory, traffic: traffic,}}); // Update the components.
}
};
reconnect = () => {
let server = new WebSocket("ws://" + location.host + "/api");
let that = this;
server.onmessage = event => {
let msg = JSON.parse(event.data);
if (isNullOrUndefined(msg)) {
return;
}
that.updateCharts(msg);
};
server.onclose = () => setTimeout(that.reconnect, 3000);
};
componentDidMount = () => this.reconnect();
render = () => <div className="container body">
<div className="main_container">
<SideBar/>
<TopNavigation/>
<PageContent charts={this.state.charts}/>
<Footer/>
</div>
</div>;
}

View file

@ -12,37 +12,22 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/gentelella/1.3.0/css/custom.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router/4.1.2/react-router.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-noty/2.4.1/packaged/jquery.noty.packaged.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0/Chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0/Chart.min.js"></script>
<script>
Chart.defaults.global.legend = { enabled: false };
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.min.js"></script>
</head>
<body>
<!--<script src="Something replacing babel plugin"></script>-->
<!--<script src="https://unpkg.com/inferno@3.7.0/dist/inferno.js"></script>-->
<!--<script src="https://unpkg.com/inferno-component@3.7.0/dist/inferno-component.js"></script>-->
<!--<script src="https://unpkg.com/inferno-router@3.7.0/dist/inferno-router.min.js"></script>-->
<script src="https://unpkg.com/inferno@3.9.0/dist/inferno.min.js"></script>
<script src="https://unpkg.com/inferno-component@3.9.0/dist/inferno-component.min.js"></script>
<div id="dashboard" class="nav-md"></div>
<script src="js/bundle.js"></script>
<script type="text/babel" src="components/dashboard.js"></script>
<script type="text/babel">
ReactDOM.render(<Dashboard/>, document.getElementById('dashboard'));
</script>
<!--<script type="text/babel">-->
<!--Inferno.render(<Dashboard/>, document.getElementById('dashboard'));-->
<!--</script>-->
<script type="text/babel" src="https://cdnjs.cloudflare.com/ajax/libs/gentelella/1.3.0/js/custom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gentelella/1.3.0/js/custom.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,566 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 3);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = Inferno;
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return isNullOrUndefined; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return mapChildren; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Clearfix; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return MEMORY_SAMPLE_LIMIT; });
/* unused harmony export TRAFFIC_SAMPLE_LIMIT */
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_inferno__);
// isNullOrUndefined returns true if a variable is null or undefined.
var isNullOrUndefined = function isNullOrUndefined(variable) {
return variable === null || typeof variable === 'undefined';
};
var mapChildren = function mapChildren(children, mapFunc) {
return !Array.isArray(children) || children.length < 1 || children.length === 1 ? mapFunc(children) : children.map(mapFunc);
};
var Clearfix = function Clearfix() {
return Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "div", "clearfix");
};
var MEMORY_SAMPLE_LIMIT = 200; // Maximum number of memory data samples.
var TRAFFIC_SAMPLE_LIMIT = 200; // Maximum number of traffic data samples.
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = Inferno.Component;
/***/ }),
/* 3 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_inferno__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__components_Dashboard__ = __webpack_require__(4);
// Renders the whole dashboard.
__WEBPACK_IMPORTED_MODULE_0_inferno___default.a.render(Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_1__components_Dashboard__["a" /* default */]), document.getElementById('dashboard'));
/***/ }),
/* 4 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_component__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_component___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_component__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Common__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__SideBar__ = __webpack_require__(5);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__TopNavigation__ = __webpack_require__(6);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__PageContent__ = __webpack_require__(7);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__Footer__ = __webpack_require__(8);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6_inferno__);
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
// 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.
var Dashboard = function (_Component) {
_inherits(Dashboard, _Component);
function Dashboard(props) {
_classCallCheck(this, Dashboard);
var _this = _possibleConstructorReturn(this, _Component.call(this, props));
_this.updateCharts = function (msg) {
var memory = _this.state.charts.memory;
var traffic = _this.state.charts.traffic;
// Fill the dashboard with the past data. metrics is set only in the first msg,
// after the connection is established.
if (msg.metrics !== undefined) {
// Clear the arrays to prevent data confusion with the previous connection.
memory.labels = [];
traffic.labels = [];
memory.datasets[0].data = [];
traffic.datasets[0].data = [];
var mem = msg.metrics.memory;
var traff = msg.metrics.processor; // TODO (kurkomisi): !!!
// Put the past data to the beginning of the arrays. This prevents confusion with the next msg data,
// which goes to the end.
for (var i = mem.length - 1; i >= 0 && __WEBPACK_IMPORTED_MODULE_1__Common__["b" /* MEMORY_SAMPLE_LIMIT */] > memory.labels.length; --i) {
memory.labels.unshift(mem[i].time.substring(mem[i].time.length - 5));
traffic.labels.unshift(mem[i].time.substring(mem[i].time.length - 5));
memory.datasets[0].data.unshift(mem[i].value);
traffic.datasets[0].data.unshift(traff[i].value);
}
_this.setState({ charts: { memory: memory, traffic: traffic } }); // Update the components.
return;
}
// Put the new data to the end of the arrays.
if (msg.memory !== undefined) {
// Remove the first elements in case the samples' amount exceeds the limit.
if (memory.labels.length === __WEBPACK_IMPORTED_MODULE_1__Common__["b" /* MEMORY_SAMPLE_LIMIT */]) {
memory.labels.shift();
traffic.labels.shift();
memory.datasets[0].data.shift();
traffic.datasets[0].data.shift();
}
memory.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
traffic.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
memory.datasets[0].data.push(msg.memory.value);
traffic.datasets[0].data.push(msg.processor.value);
_this.setState({ charts: { memory: memory, traffic: traffic } }); // Update the components.
}
};
_this.reconnect = function () {
var server = new WebSocket("ws://" + location.host + "/api");
server.onmessage = function (event) {
var msg = JSON.parse(event.data);
if (Object(__WEBPACK_IMPORTED_MODULE_1__Common__["c" /* isNullOrUndefined */])(msg)) {
return;
}
_this.updateCharts(msg);
};
server.onclose = function () {
return setTimeout(_this.reconnect, 3000);
};
};
_this.componentDidMount = function () {
return _this.reconnect();
};
_this.render = function () {
return Object(__WEBPACK_IMPORTED_MODULE_6_inferno__["createVNode"])(2, "div", "container body", Object(__WEBPACK_IMPORTED_MODULE_6_inferno__["createVNode"])(2, "div", "main_container", [Object(__WEBPACK_IMPORTED_MODULE_6_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_2__SideBar__["a" /* SideBar */]), Object(__WEBPACK_IMPORTED_MODULE_6_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_3__TopNavigation__["a" /* TopNavigation */]), Object(__WEBPACK_IMPORTED_MODULE_6_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_4__PageContent__["a" /* default */], null, null, {
"charts": _this.state.charts
}), Object(__WEBPACK_IMPORTED_MODULE_6_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_5__Footer__["a" /* Footer */])]));
};
_this.state = {
charts: { // Stores the state of the charts.
memory: {
labels: [],
datasets: [{
label: "system/memory/inuse",
backgroundColor: "rgba(38, 185, 154, 0.31)",
borderColor: "rgba(38, 185, 154, 0.7)",
pointBorderColor: "rgba(38, 185, 154, 0.7)",
pointBackgroundColor: "rgba(38, 185, 154, 0.7)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointBorderWidth: 1,
data: []
}]
},
traffic: {
labels: [],
datasets: [{
label: "p2p/InboundTraffic",
backgroundColor: "rgba(3, 88, 106, 0.3)",
borderColor: "rgba(3, 88, 106, 0.70)",
pointBorderColor: "rgba(3, 88, 106, 0.70)",
pointBackgroundColor: "rgba(3, 88, 106, 0.70)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(151,187,205,1)",
pointBorderWidth: 1,
data: []
}]
}
}
};
return _this;
}
// updateCharts Analyzes the incoming message, and updates the charts' content correspondingly.
// reconnect establishes a websocket connection with the server, listens for incoming messages
// and tries to reconnect on connection loss.
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
// render renders the components of the dashboard.
return Dashboard;
}(__WEBPACK_IMPORTED_MODULE_0_component___default.a);
/* harmony default export */ __webpack_exports__["a"] = (Dashboard);
/***/ }),
/* 5 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return SideBar; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_component__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_component___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_component__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Common__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_inferno__);
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
// MenuItem renders an item for a Menu component and the belonging submenu items, if there is any.
var MenuItem = function (_Component) {
_inherits(MenuItem, _Component);
function MenuItem() {
var _temp, _this, _ret;
_classCallCheck(this, MenuItem);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _Component.call.apply(_Component, [this].concat(args))), _this), _this.render = function () {
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "li", null, [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "i", "fa " + _this.props.className), _this.props.text, Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "fa fa-chevron-down")]),
// Render dropdown menu only if there are children.
Object(__WEBPACK_IMPORTED_MODULE_1__Common__["c" /* isNullOrUndefined */])(_this.props.children) || Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "ul", "nav child_menu", Object(__WEBPACK_IMPORTED_MODULE_1__Common__["d" /* mapChildren */])(_this.props.children, function (child) {
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "li", null, child);
}))]);
}, _temp), _possibleConstructorReturn(_this, _ret);
}
return MenuItem;
}(__WEBPACK_IMPORTED_MODULE_0_component___default.a);
// Menu renders a menu component.
var Menu = function Menu() {
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "ul", "nav side-menu", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, MenuItem, null, null, {
"className": "fa-home",
"text": "Home",
children: [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Dashboard1", {
"href": "dashboard1.html"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Dashboard2", {
"href": "dashboard2.html"
})]
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, MenuItem, null, null, {
"className": "fa-edit",
"text": "Networking",
children: Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Networking", {
"href": "networking.html"
})
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, MenuItem, null, null, {
"className": "fa-desktop",
"text": "Txpool",
children: Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Txpool", {
"href": "txpool.html"
})
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, MenuItem, null, null, {
"className": "fa-table",
"text": "Logs",
children: Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Logs", {
"href": "logs.html"
})
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, MenuItem, null, null, {
"className": "fa-clone",
"text": "Blockchain",
children: [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Blockchain1", {
"href": "blockchain1.html"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Blockchain2", {
"href": "blockchain2.html"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Blockchain3", {
"href": "blockchain3.html"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Blockchain4", {
"href": "blockchain4.html"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Blockchain5", {
"href": "blockchain5.html"
})]
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, MenuItem, null, null, {
"className": "fa-bar-chart-o",
"text": "System"
})]);
};
// SideBar renders a sidebar component.
var SideBar = function SideBar() {
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "col-md-3 left_col", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "left_col scroll-view", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "navbar nav_title", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", "site_title", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "i", "fa fa-paw"), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "span", null, "Go Ethereum Dashboard")], {
"href": "dashboard.html"
}), {
"style": { border: 0 }
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_1__Common__["a" /* Clearfix */]), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "main_menu_side hidden-print main_menu", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "menu_section", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, Menu)), {
"id": "sidebar-menu"
})]));
};
/***/ }),
/* 6 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return TopNavigation; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_inferno__);
// TopNavigation renders a top navigation component.
var TopNavigation = function TopNavigation() {
return Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "div", "top_nav", Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "div", "nav_menu", Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "nav", "", Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "div", "nav toggle", Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "a", null, [" ", Object(__WEBPACK_IMPORTED_MODULE_0_inferno__["createVNode"])(2, "i", "fa fa-bars")], {
"id": "_______menu_toggle"
})), {
"role": "navigation"
})));
};
/***/ }),
/* 7 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_component__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_component___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_component__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Common__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_inferno__);
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
// Chart name is already in use.
// ChartComponent renders a chart component and updates it, when the related data changes.
var ChartComponent = function (_Component) {
_inherits(ChartComponent, _Component);
function ChartComponent(props) {
_classCallCheck(this, ChartComponent);
var _this = _possibleConstructorReturn(this, _Component.call(this, props));
_this.componentDidMount = function () {
return _this.state.chart = new Chart(_this.data, {
type: _this.props.type,
data: _this.props.data
});
};
_this.render = function () {
if (!Object(__WEBPACK_IMPORTED_MODULE_1__Common__["c" /* isNullOrUndefined */])(_this.state.chart)) {
_this.state.chart.update();
}
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", _this.props.className, Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "x_panel", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "x_title", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "h2", null, _this.props.text), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "ul", "nav navbar-right panel_toolbox", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "li", null, Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", "collapse-link", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "i", "fa fa-chevron-up"))),
// Render dropdown menu only if there are children.
Object(__WEBPACK_IMPORTED_MODULE_1__Common__["c" /* isNullOrUndefined */])(_this.props.children) || Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "li", "dropdown", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", "dropdown-toggle", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "i", "fa fa-wrench"), {
"href": "#",
"data-toggle": "dropdown",
"role": "button",
"aria-expanded": "false"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "ul", "dropdown-menu", Object(__WEBPACK_IMPORTED_MODULE_1__Common__["d" /* mapChildren */])(_this.props.children, function (child) {
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "li", null, child);
}), {
"role": "menu"
})]), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "li", null, Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", "close-link", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "i", "fa fa-close")))]), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_1__Common__["a" /* Clearfix */])]), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "x_content", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "canvas", null, null, null, null, function (data) {
_this.data = data;
}))]));
};
_this.state = {};
return _this;
}
return ChartComponent;
}(__WEBPACK_IMPORTED_MODULE_0_component___default.a);
// Row renders a row component of charts only if there is any chart.
var Row = function (_Component2) {
_inherits(Row, _Component2);
function Row() {
var _temp, _this2, _ret;
_classCallCheck(this, Row);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this2 = _possibleConstructorReturn(this, _Component2.call.apply(_Component2, [this].concat(args))), _this2), _this2.render = function () {
return Object(__WEBPACK_IMPORTED_MODULE_1__Common__["c" /* isNullOrUndefined */])(_this2.props.children) || Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "row", [" ", _this2.props.children, " "]);
}, _temp), _possibleConstructorReturn(_this2, _ret);
}
return Row;
}(__WEBPACK_IMPORTED_MODULE_0_component___default.a);
// PageContent renders a component for the page content.
var PageContent = function (_Component3) {
_inherits(PageContent, _Component3);
function PageContent() {
var _temp2, _this3, _ret2;
_classCallCheck(this, PageContent);
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _ret2 = (_temp2 = (_this3 = _possibleConstructorReturn(this, _Component3.call.apply(_Component3, [this].concat(args))), _this3), _this3.render = function () {
return Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "right_col", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "", [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "page-title", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "div", "title_left", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "h3", null, ["Go Ethereum Dashboard", Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "small", null, "Statistics")]))), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_1__Common__["a" /* Clearfix */]), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, Row, null, null, {
children: [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, ChartComponent, null, null, {
"className": "col-md-6 col-sm-6 col-xs-12",
"text": "Memory usage system/memory/inuse",
"type": "line",
"data": _this3.props.charts.memory,
children: [Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Settings 1", {
"href": "#"
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(2, "a", null, "Settings 2", {
"href": "#"
})]
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, ChartComponent, null, null, {
"className": "col-md-6 col-sm-6 col-xs-12",
"text": "Inbound traffic p2p/InboundTraffic",
"type": "line",
"data": _this3.props.charts.traffic
})]
}), Object(__WEBPACK_IMPORTED_MODULE_2_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_1__Common__["a" /* Clearfix */])]), {
"role": "main"
});
}, _temp2), _possibleConstructorReturn(_this3, _ret2);
}
return PageContent;
}(__WEBPACK_IMPORTED_MODULE_0_component___default.a);
/* harmony default export */ __webpack_exports__["a"] = (PageContent);
/***/ }),
/* 8 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Footer; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__Common__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_inferno__ = __webpack_require__(0);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_inferno___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_inferno__);
// Footer renders a footer component.
var Footer = function Footer() {
return Object(__WEBPACK_IMPORTED_MODULE_1_inferno__["createVNode"])(2, "footer", null, [Object(__WEBPACK_IMPORTED_MODULE_1_inferno__["createVNode"])(2, "div", "pull-right", "Copyright 2017 The go-ethereum Authors"), Object(__WEBPACK_IMPORTED_MODULE_1_inferno__["createVNode"])(16, __WEBPACK_IMPORTED_MODULE_0__Common__["a" /* Clearfix */])]);
};
/***/ })
/******/ ]);

View file

@ -0,0 +1,38 @@
{
"jshintConfig": {
"esversion": 6
},
"eslintConfig": {
"parser": "babel-eslint"
},
"babel": {
"presets": [
["es2015", {"loose": true, "modules": false}],
"stage-0"
],
"plugins": [
"babel-plugin-syntax-jsx",
["babel-plugin-inferno", {"imports": true}]
]
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-inferno": "^3.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"eslint": "^4.5.0",
"eslint-config-inferno-app": "^4.2.0",
"eslint-loader": "^1.9.0",
"glob": "^7.1.2",
"inferno-devtools": "^3.8.2",
"path": "^0.12.7",
"url": "^0.11.0",
"webpack": "^3.5.5",
"webpack-dev-server": "^2.7.1"
}
}

View file

@ -0,0 +1,10 @@
// isNullOrUndefined returns true if a variable is null or undefined.
export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
export const mapChildren = (children, mapFunc) => !Array.isArray(children) || children.length < 1 ||
children.length === 1 ? mapFunc(children) : children.map(mapFunc);
export const Clearfix = () => <div className="clearfix"/>;
export const MEMORY_SAMPLE_LIMIT = 200; // Maximum number of memory data samples.
export const TRAFFIC_SAMPLE_LIMIT = 200; // Maximum number of traffic data samples.

View file

@ -0,0 +1,124 @@
import Component from 'component';
import {isNullOrUndefined, MEMORY_SAMPLE_LIMIT} from "./Common";
import {SideBar} from "./SideBar";
import {TopNavigation} from "./TopNavigation";
import PageContent from './PageContent';
import {Footer} from './Footer';
// 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.
export default class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
charts: { // Stores the state of the charts.
memory: {
labels: [],
datasets: [{
label: "system/memory/inuse",
backgroundColor: "rgba(38, 185, 154, 0.31)",
borderColor: "rgba(38, 185, 154, 0.7)",
pointBorderColor: "rgba(38, 185, 154, 0.7)",
pointBackgroundColor: "rgba(38, 185, 154, 0.7)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointBorderWidth: 1,
data: [],
}],
},
traffic: {
labels: [],
datasets: [{
label: "p2p/InboundTraffic",
backgroundColor: "rgba(3, 88, 106, 0.3)",
borderColor: "rgba(3, 88, 106, 0.70)",
pointBorderColor: "rgba(3, 88, 106, 0.70)",
pointBackgroundColor: "rgba(3, 88, 106, 0.70)",
pointHoverBackgroundColor: "#fff",
pointHoverBorderColor: "rgba(151,187,205,1)",
pointBorderWidth: 1,
data: [],
}],
},
},
};
}
// updateCharts Analyzes the incoming message, and updates the charts' content correspondingly.
updateCharts = msg => {
const memory = this.state.charts.memory;
const traffic = this.state.charts.traffic;
// Fill the dashboard with the past data. metrics is set only in the first msg,
// after the connection is established.
if (msg.metrics !== undefined) {
// Clear the arrays to prevent data confusion with the previous connection.
memory.labels = [];
traffic.labels = [];
memory.datasets[0].data = [];
traffic.datasets[0].data = [];
const mem = msg.metrics.memory;
const traff = msg.metrics.processor; // TODO (kurkomisi): !!!
// Put the past data to the beginning of the arrays. This prevents confusion with the next msg data,
// which goes to the end.
for (let i = mem.length - 1; i >= 0 && MEMORY_SAMPLE_LIMIT > memory.labels.length; --i) {
memory.labels.unshift(mem[i].time.substring(mem[i].time.length - 5));
traffic.labels.unshift(mem[i].time.substring(mem[i].time.length - 5));
memory.datasets[0].data.unshift(mem[i].value);
traffic.datasets[0].data.unshift(traff[i].value);
}
this.setState({charts: {memory, traffic,}}); // Update the components.
return;
}
// Put the new data to the end of the arrays.
if (msg.memory !== undefined) {
// Remove the first elements in case the samples' amount exceeds the limit.
if (memory.labels.length === MEMORY_SAMPLE_LIMIT) {
memory.labels.shift();
traffic.labels.shift();
memory.datasets[0].data.shift();
traffic.datasets[0].data.shift();
}
memory.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
traffic.labels.push(msg.memory.time.substring(msg.memory.time.length - 5));
memory.datasets[0].data.push(msg.memory.value);
traffic.datasets[0].data.push(msg.processor.value);
this.setState({charts: {memory, traffic,}}); // Update the components.
}
};
// reconnect establishes a websocket connection with the server, listens for incoming messages
// and tries to reconnect on connection loss.
reconnect = () => {
const server = new WebSocket("ws://" + location.host + "/api");
server.onmessage = event => {
const msg = JSON.parse(event.data);
if (isNullOrUndefined(msg)) {
return;
}
this.updateCharts(msg);
};
server.onclose = () => setTimeout(this.reconnect, 3000);
};
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
componentDidMount = () => this.reconnect();
// render renders the components of the dashboard.
render = () => <div className="container body">
<div className="main_container">
<SideBar/>
<TopNavigation/>
<PageContent charts={this.state.charts}/>
<Footer/>
</div>
</div>;
}

View file

@ -0,0 +1,9 @@
import {Clearfix} from "./Common";
// Footer renders a footer component.
export const Footer = () => <footer>
<div className="pull-right">
Copyright 2017 The go-ethereum Authors
</div>
<Clearfix/>
</footer>;

View file

@ -0,0 +1,83 @@
import Component from 'component';
import {isNullOrUndefined, mapChildren, Clearfix} from "./Common";
// Chart name is already in use.
// ChartComponent renders a chart component and updates it, when the related data changes.
class ChartComponent extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount = () => this.state.chart = new Chart(this.data, {
type: this.props.type,
data: this.props.data,
});
render = () => {
if (!isNullOrUndefined(this.state.chart)) {
this.state.chart.update();
}
return <div className={this.props.className}>
<div className="x_panel">
<div className="x_title">
<h2>{this.props.text}</h2>
<ul className="nav navbar-right panel_toolbox">
<li><a className="collapse-link"><i className="fa fa-chevron-up"/></a></li>
{
// Render dropdown menu only if there are children.
isNullOrUndefined(this.props.children) || <li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button"
aria-expanded="false"><i className="fa fa-wrench"/></a>
<ul className="dropdown-menu" role="menu">
{mapChildren(this.props.children, child => <li>{child}</li>)}
</ul>
</li>
}
<li><a className="close-link"><i className="fa fa-close"/></a>
</li>
</ul>
<Clearfix/>
</div>
<div className="x_content">
{/* The chart will be generated here after the component is mounted (this.componentDidMount). */}
<canvas ref={data => {
this.data = data
}}/>
</div>
</div>
</div>;
}
}
// Row renders a row component of charts only if there is any chart.
class Row extends Component {
render = () => isNullOrUndefined(this.props.children) || <div className="row"> {this.props.children} </div>;
}
// PageContent renders a component for the page content.
export default class PageContent extends Component {
render = () => <div className="right_col" role="main">
<div className="">
<div className="page-title">
<div className="title_left">
<h3>Go Ethereum Dashboard
<small>Statistics</small>
</h3>
</div>
</div>
<Clearfix/>
<Row>
<ChartComponent className="col-md-6 col-sm-6 col-xs-12" text="Memory usage system/memory/inuse"
type="line" data={this.props.charts.memory}>
<a href="#">Settings 1</a>
<a href="#">Settings 2</a>
</ChartComponent>
<ChartComponent className="col-md-6 col-sm-6 col-xs-12" text="Inbound traffic p2p/InboundTraffic"
type="line" data={this.props.charts.traffic}/>
</Row>
<Clearfix/>
</div>
</div>;
}

View file

@ -0,0 +1,60 @@
import Component from 'component';
import {isNullOrUndefined, mapChildren, Clearfix} from "./Common";
// MenuItem renders an item for a Menu component and the belonging submenu items, if there is any.
class MenuItem extends Component {
render = () => <li>
<a>
<i className={`fa ${this.props.className}`}/>
{this.props.text}
<div className="fa fa-chevron-down"/>
</a>
{
// Render dropdown menu only if there are children.
isNullOrUndefined(this.props.children) || <ul className="nav child_menu">
{mapChildren(this.props.children, child => <li>{child}</li>)}
</ul>
}
</li>;
}
// Menu renders a menu component.
const Menu = () => <ul className="nav side-menu">
<MenuItem className="fa-home" text="Home">
<a href="dashboard1.html">Dashboard1</a>
<a href="dashboard2.html">Dashboard2</a>
</MenuItem>
<MenuItem className="fa-edit" text="Networking">
<a href="networking.html">Networking</a>
</MenuItem>
<MenuItem className="fa-desktop" text="Txpool">
<a href="txpool.html">Txpool</a>
</MenuItem>
<MenuItem className="fa-table" text="Logs">
<a href="logs.html">Logs</a>
</MenuItem>
<MenuItem className="fa-clone" text="Blockchain">
<a href="blockchain1.html">Blockchain1</a>
<a href="blockchain2.html">Blockchain2</a>
<a href="blockchain3.html">Blockchain3</a>
<a href="blockchain4.html">Blockchain4</a>
<a href="blockchain5.html">Blockchain5</a>
</MenuItem>
<MenuItem className="fa-bar-chart-o" text="System"/>
</ul>;
// SideBar renders a sidebar component.
export const SideBar = () => <div className="col-md-3 left_col">
<div className="left_col scroll-view">
<div className="navbar nav_title" style={{border: 0}}>
<a href="dashboard.html" className="site_title"><i className="fa fa-paw"/>
<span>Go Ethereum Dashboard</span></a>
</div>
<Clearfix/>
<div id="sidebar-menu" className="main_menu_side hidden-print main_menu">
<div className="menu_section">
<Menu/>
</div>
</div>
</div>
</div>;

View file

@ -0,0 +1,12 @@
// TopNavigation renders a top navigation component.
export const TopNavigation = () => <div className="top_nav">
<div className="nav_menu">
<nav className="" role="navigation">
<div className="nav toggle">
<a id="_______menu_toggle"> {/* TODO (kurkomisi): Resize the main container on toggle */}
<i className="fa fa-bars"/>
</a>
</div>
</nav>
</div>
</div>;

View file

@ -0,0 +1,5 @@
import Inferno from 'inferno';
import Dashboard from './components/Dashboard';
// Renders the whole dashboard.
Inferno.render(<Dashboard/>, document.getElementById('dashboard'));

View file

@ -0,0 +1,21 @@
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, '../assets/js'),
filename: 'bundle.js',
},
externals: { // External libraries, which will not be included in the bundled file(s).
inferno: 'Inferno',
component: 'Inferno.Component',
},
module: {
loaders: [
{
test: /\.js$/, // regexp for JS files
loader: 'babel-loader', // The babel configuration is in the package.json.
},
],
},
};

View file

@ -81,7 +81,7 @@ type status struct {
// New creates a new dashboard instance with the given configuration.
func New(config *Config) (*dashboard, error) {
return &dashboard{
conns: make(map[uint32]*client),
conns: make(map[uint32]*client),
config: config,
Metrics: &metricSamples{},
closing: make(chan chan error),