Merge pull request #17 from cubedro/master

Update develop branch
This commit is contained in:
Marian OANCΞA 2015-02-26 19:43:49 +02:00
commit 8f69eac714
10 changed files with 89 additions and 20 deletions

3
.travis.yml Normal file
View File

@ -0,0 +1,3 @@
language: node_js
node_js:
- "0.12"

View File

@ -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
View File

@ -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});

View File

@ -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;
} }

View File

@ -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"
} }
} }

View File

@ -123,3 +123,7 @@ table td i {
width: 100%; width: 100%;
} }
} }
.ng-cloak {
display: none !important;
}

View File

@ -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;
} }

View File

@ -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';
}; };

View File

@ -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',

View File

@ -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 }}