commit
8f69eac714
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "0.12"
|
10
README.md
10
README.md
@ -1,7 +1,6 @@
|
|||||||
eth-netstats
|
|
||||||
============
|
|
||||||
|
|
||||||
Ethereum Network Stats
|
Ethereum Network Stats
|
||||||
|
============
|
||||||
|
[![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url]
|
||||||
|
|
||||||
To run via Docker
|
To run via Docker
|
||||||
|
|
||||||
@ -18,3 +17,8 @@ docker run --publish=3000:3000 eth-netstats
|
|||||||
```
|
```
|
||||||
|
|
||||||
see the interface at http://localhost:3000
|
see the interface at http://localhost:3000
|
||||||
|
|
||||||
|
[travis-image]: https://travis-ci.org/cubedro/eth-netstats.svg
|
||||||
|
[travis-url]: https://travis-ci.org/cubedro/eth-netstats
|
||||||
|
[dep-image]: https://david-dm.org/cubedro/eth-netstats.svg
|
||||||
|
[dep-url]: https://david-dm.org/cubedro/eth-netstats
|
6
app.js
6
app.js
@ -20,6 +20,7 @@ api = new Primus(server, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
api.use('emit', require('primus-emit'));
|
api.use('emit', require('primus-emit'));
|
||||||
|
api.use('spark-latency', require('primus-spark-latency'));
|
||||||
|
|
||||||
var client = new Primus(server, {
|
var client = new Primus(server, {
|
||||||
transformer: 'websockets',
|
transformer: 'websockets',
|
||||||
@ -30,12 +31,14 @@ var client = new Primus(server, {
|
|||||||
client.use('emit', require('primus-emit'));
|
client.use('emit', require('primus-emit'));
|
||||||
|
|
||||||
api.on('connection', function(spark) {
|
api.on('connection', function(spark) {
|
||||||
|
console.log('Latency: ', spark.latency);
|
||||||
console.log(spark.id);
|
console.log(spark.id);
|
||||||
console.log(spark.address);
|
console.log(spark.address);
|
||||||
console.log(spark.query);
|
console.log(spark.query);
|
||||||
|
|
||||||
spark.on('hello', function(data)
|
spark.on('hello', function(data)
|
||||||
{
|
{
|
||||||
|
console.log('Latency: ', spark.latency);
|
||||||
console.log('got hello data from ', spark.id);
|
console.log('got hello data from ', spark.id);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
@ -43,6 +46,7 @@ api.on('connection', function(spark) {
|
|||||||
{
|
{
|
||||||
data.ip = spark.address.ip;
|
data.ip = spark.address.ip;
|
||||||
data.spark = spark.id;
|
data.spark = spark.id;
|
||||||
|
data.latency = spark.latency;
|
||||||
|
|
||||||
var info = Nodes.add(data);
|
var info = Nodes.add(data);
|
||||||
spark.emit('ready');
|
spark.emit('ready');
|
||||||
@ -53,11 +57,13 @@ api.on('connection', function(spark) {
|
|||||||
|
|
||||||
spark.on('update', function(data)
|
spark.on('update', function(data)
|
||||||
{
|
{
|
||||||
|
console.log('Latency: ', spark.latency);
|
||||||
console.log('got update from ' + spark.id);
|
console.log('got update from ' + spark.id);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
if(typeof data.id !== 'undefined' && typeof data.stats !== 'undefined')
|
if(typeof data.id !== 'undefined' && typeof data.stats !== 'undefined')
|
||||||
{
|
{
|
||||||
|
data.stats.latency = spark.latency;
|
||||||
var stats = Nodes.update(data.id, data.stats);
|
var stats = Nodes.update(data.id, data.stats);
|
||||||
|
|
||||||
client.write({action: 'update', data: stats});
|
client.write({action: 'update', data: stats});
|
||||||
|
@ -17,10 +17,13 @@ var Node = function Node(data)
|
|||||||
number: 0,
|
number: 0,
|
||||||
gasLimit: 0,
|
gasLimit: 0,
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
blocktime: 0
|
arrival: 0,
|
||||||
|
propagation: 0
|
||||||
},
|
},
|
||||||
blocktimeAvg: 0,
|
blocktimeAvg: 0,
|
||||||
|
blockTimes: [],
|
||||||
difficulty: [],
|
difficulty: [],
|
||||||
|
latency: 0,
|
||||||
uptime: 0,
|
uptime: 0,
|
||||||
lastUpdate: 0
|
lastUpdate: 0
|
||||||
};
|
};
|
||||||
@ -39,6 +42,9 @@ var Node = function Node(data)
|
|||||||
if(typeof data.spark !== 'undefined')
|
if(typeof data.spark !== 'undefined')
|
||||||
this.spark = data.spark;
|
this.spark = data.spark;
|
||||||
|
|
||||||
|
if(typeof data.latency !== 'undefined')
|
||||||
|
this.stats.latency = data.latency;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
package.json
13
package.json
@ -10,15 +10,16 @@
|
|||||||
"start": "node ./bin/www"
|
"start": "node ./bin/www"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "~1.8.1",
|
"body-parser": "1.12.0",
|
||||||
"debug": "~2.0.0",
|
"debug": "2.1.1",
|
||||||
"express": "^4.11.2",
|
"express": "^4.11.2",
|
||||||
"geoip-lite": "^1.1.4",
|
"geoip-lite": "^1.1.5",
|
||||||
"jade": "~1.6.0",
|
"jade": "^1.9.2",
|
||||||
"lodash": "^3.2.0",
|
"lodash": "^3.3.1",
|
||||||
"primus": "^2.4.12",
|
"primus": "^2.4.12",
|
||||||
"primus-emit": "^0.1.2",
|
"primus-emit": "^0.1.2",
|
||||||
"serve-favicon": "~2.1.3",
|
"primus-spark-latency": "^0.1.1",
|
||||||
|
"serve-favicon": "^2.2.0",
|
||||||
"ws": "^0.7.1"
|
"ws": "^0.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,4 +122,8 @@ table td i {
|
|||||||
.stat-holder {
|
.stat-holder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ng-cloak {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
@ -18,6 +18,10 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
|
|||||||
$scope.nodes = [];
|
$scope.nodes = [];
|
||||||
$scope.map = [];
|
$scope.map = [];
|
||||||
|
|
||||||
|
$scope.timeout = setInterval(function(){
|
||||||
|
$scope.$apply();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
// Socket listeners
|
// Socket listeners
|
||||||
// ----------------
|
// ----------------
|
||||||
|
|
||||||
@ -59,9 +63,9 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
|
|||||||
|
|
||||||
case "add":
|
case "add":
|
||||||
if(addNewNode(data))
|
if(addNewNode(data))
|
||||||
toastr['success']("New node connected!", "New node!");
|
toastr['success']("New node "+ $scope.nodes[findIndex({id: data.id})].info.name +" connected!", "New node!");
|
||||||
else
|
else
|
||||||
toastr['info']("Node reconnected!", "Node is back!");
|
toastr['info']("Node "+ $scope.nodes[findIndex({id: data.id})].info.name +" reconnected!", "Node is back!");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
@ -74,7 +78,7 @@ function StatsCtrl($scope, $filter, socket, _, toastr) {
|
|||||||
|
|
||||||
case "inactive":
|
case "inactive":
|
||||||
$scope.nodes[findIndex({id: data.id})].stats = data.stats;
|
$scope.nodes[findIndex({id: data.id})].stats = data.stats;
|
||||||
toastr['error']("Node went away!", "Node connection was lost!");
|
toastr['error']("Node "+ $scope.nodes[findIndex({id: data.id})].info.name +" went away!", "Node connection was lost!");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,11 +60,46 @@ angular.module('netStatsApp.filters', [])
|
|||||||
return (typeof gas !== 'undefined' ? parseInt(gas) : '?');
|
return (typeof gas !== 'undefined' ? parseInt(gas) : '?');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.filter('latencyFilter', function() {
|
||||||
|
return function(stats) {
|
||||||
|
if(stats.active === false)
|
||||||
|
return 'offline';
|
||||||
|
else
|
||||||
|
return stats.latency + ' ms';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter('hashFilter', function() {
|
||||||
|
return function(hash) {
|
||||||
|
return hash.substr(0, 6) + '...' + hash.substr(58, 6);
|
||||||
|
}
|
||||||
|
})
|
||||||
.filter('timeClass', function() {
|
.filter('timeClass', function() {
|
||||||
return function(timestamp) {
|
return function(timestamp) {
|
||||||
return timeClass(timestamp);
|
return timeClass(timestamp);
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.filter('propagationTimeClass', function() {
|
||||||
|
return function(propagation) {
|
||||||
|
if(propagation <= 3000)
|
||||||
|
return 'text-success';
|
||||||
|
|
||||||
|
if(propagation <= 7000)
|
||||||
|
return 'text-warning';
|
||||||
|
|
||||||
|
return 'text-danger'
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter('latencyClass', function() {
|
||||||
|
return function(time) {
|
||||||
|
if(time <= 100)
|
||||||
|
return 'text-success';
|
||||||
|
|
||||||
|
if(time <= 1000)
|
||||||
|
return 'text-warning';
|
||||||
|
|
||||||
|
return 'text-danger'
|
||||||
|
};
|
||||||
|
})
|
||||||
.filter('blockTimeFilter', function() {
|
.filter('blockTimeFilter', function() {
|
||||||
return function(timestamp) {
|
return function(timestamp) {
|
||||||
if(timestamp === 0)
|
if(timestamp === 0)
|
||||||
@ -74,7 +109,7 @@ angular.module('netStatsApp.filters', [])
|
|||||||
var diff = time - timestamp;
|
var diff = time - timestamp;
|
||||||
|
|
||||||
if(diff < 60)
|
if(diff < 60)
|
||||||
return Math.round(diff) + ' s';
|
return Math.round(diff) + ' s ago';
|
||||||
|
|
||||||
return moment.duration(Math.round(diff), 's').humanize() + ' ago';
|
return moment.duration(Math.round(diff), 's').humanize() + ' ago';
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
future : 'in %s',
|
future : 'in %s',
|
||||||
past : '%s ago',
|
past : '%s ago',
|
||||||
s : 'a few sec',
|
s : 'a few sec',
|
||||||
m : 'a min',
|
m : '1 min',
|
||||||
mm : '%d min',
|
mm : '%d min',
|
||||||
h : '1 h',
|
h : '1 h',
|
||||||
hh : '%d h',
|
hh : '%d h',
|
||||||
|
@ -77,6 +77,8 @@ block content
|
|||||||
i.icon-node(data-toggle="tooltip", data-placement="top", title="Node")
|
i.icon-node(data-toggle="tooltip", data-placement="top", title="Node")
|
||||||
th
|
th
|
||||||
i.icon-laptop(data-toggle="tooltip", data-placement="top", title="Node type")
|
i.icon-laptop(data-toggle="tooltip", data-placement="top", title="Node type")
|
||||||
|
th
|
||||||
|
i.icon-gas(data-toggle="tooltip", data-placement="top", title="Node latency")
|
||||||
th
|
th
|
||||||
i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining")
|
i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining")
|
||||||
th
|
th
|
||||||
@ -89,9 +91,11 @@ block content
|
|||||||
th
|
th
|
||||||
i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Block transactions")
|
i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Block transactions")
|
||||||
th
|
th
|
||||||
i.icon-time(data-toggle="tooltip", data-placement="top", title="Last node time")
|
i.icon-time(data-toggle="tooltip", data-placement="top", title="Last block time")
|
||||||
th
|
th
|
||||||
i.icon-clock(data-toggle="tooltip", data-placement="top", title="Up-time")
|
i.icon-clock(data-toggle="tooltip", data-placement="top", title="Propagation time")
|
||||||
|
th
|
||||||
|
i.icon-bulb(data-toggle="tooltip", data-placement="top", title="Up-time")
|
||||||
tbody
|
tbody
|
||||||
tr(ng-repeat='node in nodes', class="{{ node.stats | mainClass : bestBlock }}")
|
tr(ng-repeat='node in nodes', class="{{ node.stats | mainClass : bestBlock }}")
|
||||||
td(rel="{{node.id}}")
|
td(rel="{{node.id}}")
|
||||||
@ -100,14 +104,16 @@ block content
|
|||||||
td
|
td
|
||||||
div.small(ng-bind-html="node.info.node | nodeVersion")
|
div.small(ng-bind-html="node.info.node | nodeVersion")
|
||||||
//- div.small {{node.info.os}}, {{node.info.os_v}}
|
//- div.small {{node.info.os}}, {{node.info.os_v}}
|
||||||
|
td.small(class="{{ node.stats.latency | latencyClass }}") {{node.stats | latencyFilter}}
|
||||||
td(class="{{ node.stats.mining | miningClass }}")
|
td(class="{{ node.stats.mining | miningClass }}")
|
||||||
i(class="{{ node.stats.mining | miningIconClass }}")
|
i.small(class="{{ node.stats.mining | miningIconClass }}")
|
||||||
td(class="{{ node.stats.peers | peerClass }}", style="padding-left: 18px;") {{node.stats.peers}}
|
td(class="{{ node.stats.peers | peerClass }}", style="padding-left: 18px;") {{node.stats.peers}}
|
||||||
td(style="padding-left: 18px;") {{node.stats.pending}}
|
td(style="padding-left: 18px;") {{node.stats.pending}}
|
||||||
td(class="{{ node.stats.block.number | blockClass : bestBlock }}") {{'#' + node.stats.block.number}}
|
td(class="{{ node.stats.block.number | blockClass : bestBlock }}") {{'#' + node.stats.block.number}}
|
||||||
td(class="{{ node.stats.block.number | blockClass : bestBlock }}").hidden-sm.hidden-xs
|
td(class="{{ node.stats.block.number | blockClass : bestBlock }}").hidden-sm.hidden-xs
|
||||||
span.small {{node.stats.block.hash}}
|
span.small {{node.stats.block.hash | hashFilter}}
|
||||||
//- div.small Difficulty: {{node.stats.block.difficulty | gasFilter}} | Gas used: {{node.stats.block.gasUsed | gasFilter}} | Min gas price: {{node.stats.block.minGasPrice | gasFilter}} | Gas limit: {{node.stats.block.gasLimit | gasFilter}}
|
//- div.small Difficulty: {{node.stats.block.difficulty | gasFilter}} | Gas used: {{node.stats.block.gasUsed | gasFilter}} | Min gas price: {{node.stats.block.minGasPrice | gasFilter}} | Gas limit: {{node.stats.block.gasLimit | gasFilter}}
|
||||||
td(style="padding-left: 18px;") {{node.stats.block.txCount}}
|
td(style="padding-left: 18px;") {{node.stats.block.txCount}}
|
||||||
td(class="{{ node.stats.block.timestamp | timeClass }}") {{node.stats.block.timestamp | blockTimeFilter }}
|
td(class="{{ node.stats.block.timestamp | timeClass }}") {{node.stats.block.timestamp | blockTimeFilter }}
|
||||||
|
td(class="{{ node.stats.block.propagation | propagationTimeClass }}") {{node.stats.block.propagation}} ms
|
||||||
td(class="{{ node.stats.uptime | upTimeClass }}") {{ node.stats.uptime | upTimeFilter }}
|
td(class="{{ node.stats.uptime | upTimeClass }}") {{ node.stats.uptime | upTimeFilter }}
|
||||||
|
Loading…
Reference in New Issue
Block a user