diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..796d98f9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "0.12" diff --git a/README.md b/README.md index 706cdb49..1c49ab88 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -eth-netstats -============ - Ethereum Network Stats +============ +[![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url] To run via Docker @@ -18,3 +17,8 @@ docker run --publish=3000:3000 eth-netstats ``` 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 \ No newline at end of file diff --git a/app.js b/app.js index 7a23383f..38d48e93 100644 --- a/app.js +++ b/app.js @@ -20,6 +20,7 @@ api = new Primus(server, { }); api.use('emit', require('primus-emit')); +api.use('spark-latency', require('primus-spark-latency')); var client = new Primus(server, { transformer: 'websockets', @@ -30,12 +31,14 @@ var client = new Primus(server, { client.use('emit', require('primus-emit')); api.on('connection', function(spark) { + console.log('Latency: ', spark.latency); console.log(spark.id); console.log(spark.address); console.log(spark.query); spark.on('hello', function(data) { + console.log('Latency: ', spark.latency); console.log('got hello data from ', spark.id); console.log(data); @@ -43,6 +46,7 @@ api.on('connection', function(spark) { { data.ip = spark.address.ip; data.spark = spark.id; + data.latency = spark.latency; var info = Nodes.add(data); spark.emit('ready'); @@ -53,11 +57,13 @@ api.on('connection', function(spark) { spark.on('update', function(data) { + console.log('Latency: ', spark.latency); console.log('got update from ' + spark.id); console.log(data); if(typeof data.id !== 'undefined' && typeof data.stats !== 'undefined') { + data.stats.latency = spark.latency; var stats = Nodes.update(data.id, data.stats); client.write({action: 'update', data: stats}); diff --git a/models/node.js b/models/node.js index beaa0f35..5f58bd3f 100644 --- a/models/node.js +++ b/models/node.js @@ -17,10 +17,13 @@ var Node = function Node(data) number: 0, gasLimit: 0, timestamp: 0, - blocktime: 0 + arrival: 0, + propagation: 0 }, blocktimeAvg: 0, + blockTimes: [], difficulty: [], + latency: 0, uptime: 0, lastUpdate: 0 }; @@ -39,6 +42,9 @@ var Node = function Node(data) if(typeof data.spark !== 'undefined') this.spark = data.spark; + if(typeof data.latency !== 'undefined') + this.stats.latency = data.latency; + return this; } diff --git a/package.json b/package.json index eb56f0a1..1e3fdf96 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,16 @@ "start": "node ./bin/www" }, "dependencies": { - "body-parser": "~1.8.1", - "debug": "~2.0.0", + "body-parser": "1.12.0", + "debug": "2.1.1", "express": "^4.11.2", - "geoip-lite": "^1.1.4", - "jade": "~1.6.0", - "lodash": "^3.2.0", + "geoip-lite": "^1.1.5", + "jade": "^1.9.2", + "lodash": "^3.3.1", "primus": "^2.4.12", "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" } } diff --git a/public/css/style.css b/public/css/style.css index d7f2e52b..9400fa4c 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -122,4 +122,8 @@ table td i { .stat-holder { width: 100%; } -} \ No newline at end of file +} + +.ng-cloak { + display: none !important; +} diff --git a/public/js/controllers.js b/public/js/controllers.js index e49a2c4d..2c048e85 100644 --- a/public/js/controllers.js +++ b/public/js/controllers.js @@ -18,6 +18,10 @@ function StatsCtrl($scope, $filter, socket, _, toastr) { $scope.nodes = []; $scope.map = []; + $scope.timeout = setInterval(function(){ + $scope.$apply(); + }, 1000); + // Socket listeners // ---------------- @@ -59,9 +63,9 @@ function StatsCtrl($scope, $filter, socket, _, toastr) { case "add": 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 - toastr['info']("Node reconnected!", "Node is back!"); + toastr['info']("Node "+ $scope.nodes[findIndex({id: data.id})].info.name +" reconnected!", "Node is back!"); break; case "update": @@ -74,7 +78,7 @@ function StatsCtrl($scope, $filter, socket, _, toastr) { case "inactive": $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; } diff --git a/public/js/filters.js b/public/js/filters.js index 658f6e2f..796ca438 100644 --- a/public/js/filters.js +++ b/public/js/filters.js @@ -60,11 +60,46 @@ angular.module('netStatsApp.filters', []) 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() { return function(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() { return function(timestamp) { if(timestamp === 0) @@ -74,7 +109,7 @@ angular.module('netStatsApp.filters', []) var diff = time - timestamp; if(diff < 60) - return Math.round(diff) + ' s'; + return Math.round(diff) + ' s ago'; return moment.duration(Math.round(diff), 's').humanize() + ' ago'; }; diff --git a/public/js/lib/locale/en-gb.js b/public/js/lib/locale/en-gb.js index fee3f0d9..2e260a87 100644 --- a/public/js/lib/locale/en-gb.js +++ b/public/js/lib/locale/en-gb.js @@ -37,7 +37,7 @@ future : 'in %s', past : '%s ago', s : 'a few sec', - m : 'a min', + m : '1 min', mm : '%d min', h : '1 h', hh : '%d h', diff --git a/views/index.jade b/views/index.jade index 04cd27a1..95a9f7dd 100644 --- a/views/index.jade +++ b/views/index.jade @@ -77,6 +77,8 @@ block content i.icon-node(data-toggle="tooltip", data-placement="top", title="Node") th 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 i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining") th @@ -89,9 +91,11 @@ block content th i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Block transactions") 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 + i.icon-clock(data-toggle="tooltip", data-placement="top", title="Propagation time") th - i.icon-clock(data-toggle="tooltip", data-placement="top", title="Up-time") + i.icon-bulb(data-toggle="tooltip", data-placement="top", title="Up-time") tbody tr(ng-repeat='node in nodes', class="{{ node.stats | mainClass : bestBlock }}") td(rel="{{node.id}}") @@ -100,14 +104,16 @@ block content td div.small(ng-bind-html="node.info.node | nodeVersion") //- 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 }}") - 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(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 }}").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}} 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.propagation | propagationTimeClass }}") {{node.stats.block.propagation}} ms td(class="{{ node.stats.uptime | upTimeClass }}") {{ node.stats.uptime | upTimeFilter }}