diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0ca2a5d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM centos:centos6 - -# Enable EPEL for Node.js -RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm -# Install Node.js and npm -RUN yum install -y npm - -# Bundle app source -COPY . /src -# Install app dependencies -RUN cd /src; npm install - -EXPOSE 3000 -CMD ["node", "/src/bin/www"] \ No newline at end of file diff --git a/package.json b/package.json index b76f0c2..9ef28ed 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,13 @@ "start": "node ./bin/www" }, "dependencies": { - "body-parser": "1.12.0", - "debug": "2.1.1", - "express": "^4.11.2", - "geoip-lite": "^1.1.5", + "body-parser": "1.12.2", + "debug": "2.1.3", + "express": "^4.11.3", + "geoip-lite": "^1.1.6", "jade": "^1.9.2", - "lodash": "^3.3.1", - "primus": "^2.4.12", + "lodash": "^3.6.0", + "primus": "^3.0.2", "primus-emit": "^0.1.2", "primus-spark-latency": "^0.1.1", "serve-favicon": "^2.2.0", diff --git a/public/css/style.css b/public/css/style.css index 5a419ad..b86f22d 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -10,19 +10,35 @@ body { -moz-font-smoothing: antialiased; } -.stat-holder { +.bg-success { + background: #7bcc3a; +} +.bg-info { + background: #10a0de; } -.big-info { - padding-bottom: 15px; - padding-top: 15px; +.bg-warning { + background: #FFD162; +} + +.bg-danger { + background: #F74B4B; +} + +.container-fluid { + padding-left: 30px; + padding-right: 30px; +} + +.stat-holder { background: #090909; border: 1px solid rgba(255,255,255,0.05); } -.stats-boxes .big-info { - margin-right: -30px; +.big-info { + padding-bottom: 15px; + padding-top: 15px; } .big-info .icon-full-width i { @@ -32,27 +48,22 @@ body { font-size: 70px; line-height: 70px; margin-right: 15px; + margin-left: -15px; } .big-info span { opacity: 0.7; } -.big-info span.small-title { +.big-info span.small-title, +.big-info div.small-title-miner { display: block; font-weight: 700; font-size: 14px; line-height: 20px; letter-spacing: 1px; text-transform: uppercase; - color: #aaa !important; -} - -.big-info.chart { - padding-left: 14px; - padding-right: 14px; - -webkit-box-sizing: border-box - box-sizing: border-box; + color: #aaa; } .big-info span.big-details { @@ -67,6 +78,46 @@ body { padding-top: 15px; } +.big-info.chart { + height: 120px; + -webkit-box-sizing: border-box + box-sizing: border-box; +} + +.big-info.chart.double-chart { + height: 242px; +} + +.blocks-holder { + padding-top: 5px; +} + +.blocks-holder div.small-title-miner { + font-size: 12px; + font-weight: normal; + letter-spacing: 0px; + text-transform: none; + white-space: nowrap; + -webkit-font-smoothing: subpixel-antialiased; + -moz-font-smoothing: subpixel-antialiased; + color: #777; +} + +.blocks-holder .block-count { + float: right; + line-height: 18px; + color: #aaa; +} + +.blocks-holder .block { + width: 6px; + height: 6px; + margin: 2px 1px 6px 1px; + float: left; + -webkit-border-radius: 1px; + border-radius: 1px; +} + .jqstooltip { } @@ -195,6 +246,6 @@ table td { }*/ } -.ng-cloak { - display: none !important; +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; } diff --git a/public/js/controllers.js b/public/js/controllers.js index 6e9f731..fc240ce 100644 --- a/public/js/controllers.js +++ b/public/js/controllers.js @@ -14,15 +14,20 @@ function StatsCtrl($scope, $filter, socket, _, toastr) { $scope.lastDifficulty = 0; $scope.upTimeTotal = 0; $scope.avgBlockTime = 0; + $scope.bestStats = {}; $scope.lastBlocksTime = []; $scope.difficultyChange = []; $scope.transactionDensity = []; $scope.gasSpending = []; + $scope.miners = []; $scope.nodes = []; $scope.map = []; + $scope.predicate = ['-stats.block.number', 'stats.block.propagation']; + $scope.reverse = false; + $scope.timeout = setInterval(function(){ $scope.$apply(); }, 1000); @@ -131,39 +136,24 @@ function StatsCtrl($scope, $filter, socket, _, toastr) { if(bestBlock > $scope.bestBlock) { $scope.bestBlock = bestBlock; - - $scope.lastBlock = _.max($scope.nodes, function(node) { - return parseInt(node.stats.block.number); - }).stats.block.received; - - $scope.lastBlocksTime = _.max($scope.nodes, function(node) { + $scope.bestStats = _.max($scope.nodes, function(node) { return parseInt(node.stats.block.number); - }).stats.blockTimes; + }).stats; - jQuery('.spark-blocktimes').sparkline($scope.lastBlocksTime.reverse(), {type: 'bar', tooltipSuffix: 's'}); - - $scope.difficultyChange = _.max($scope.nodes, function(node) { - return parseInt(node.stats.block.number); - }).stats.difficulty; + $scope.lastBlock = $scope.bestStats.block.received; + $scope.lastBlocksTime = $scope.bestStats.blockTimes; + $scope.difficultyChange = $scope.bestStats.difficulty; + $scope.transactionDensity = $scope.bestStats.txDensity; + $scope.gasSpending = $scope.bestStats.gasSpending; + $scope.miners = $scope.bestStats.miners; - $scope.difficultyChange.pop(); + $scope.getNumber = function(num) { + return new Array(num); + } + jQuery('.spark-blocktimes').sparkline($scope.lastBlocksTime.reverse(), {type: 'bar', tooltipSuffix: 's'}); jQuery('.spark-difficulty').sparkline($scope.difficultyChange.reverse(), {type: 'bar'}); - - $scope.transactionDensity = _.max($scope.nodes, function(node) { - return parseInt(node.stats.block.number); - }).stats.txDensity; - - $scope.transactionDensity.pop(); - jQuery('.spark-transactions').sparkline($scope.transactionDensity.reverse(), {type: 'bar'}); - - $scope.gasSpending = _.max($scope.nodes, function(node) { - return parseInt(node.stats.block.number); - }).stats.gasSpending; - - $scope.gasSpending.pop(); - jQuery('.spark-gasspending').sparkline($scope.gasSpending.reverse(), {type: 'bar'}); } diff --git a/public/js/filters.js b/public/js/filters.js index db436c7..3147cb5 100644 --- a/public/js/filters.js +++ b/public/js/filters.js @@ -189,7 +189,26 @@ angular.module('netStatsApp.filters', []) .filter('bubbleClass', function() { return function(node, bestBlock) { return mainClass(node, bestBlock).replace('text-', ''); - } + }; +}) +.filter('minerNameFilter', function() { + return function(name) { + return name.replace('0x', ''); + }; +}) +.filter('minerBlocksClass', function() { + return function(blocks) { + if(blocks <= 6) + return 'bg-success'; + + if(blocks <= 12) + return 'bg-info'; + + if(blocks <= 18) + return 'bg-warning'; + + return 'bg-danger'; + }; }); function mainClass(node, bestBlock) diff --git a/public/js/script.js b/public/js/script.js index 982088d..90f429f 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -10,7 +10,6 @@ $.fn.sparkline.defaults.bar.barSpacing = 2; $.fn.sparkline.defaults.bar.tooltipClassname = 'jqstooltip'; $.fn.sparkline.defaults.bar.tooltipOffsetX = 0; - // $.fn.sparkline.defaults.bar.tooltipFormat = $.spformat('

{{prefix}}{{value}}{{suffix}}
'); $.fn.sparkline.defaults.bar.tooltipFormat = $.spformat('
{{prefix}}{{value}}{{suffix}}
'); $.fn.sparkline.defaults.bar.colorMap = $.range_map({ '1:12': '#7bcc3a', diff --git a/views/index.jade b/views/index.jade index 762dfde..53c8edf 100644 --- a/views/index.jade +++ b/views/index.jade @@ -3,142 +3,148 @@ extends layout block content div.container-fluid(ng-controller='StatsCtrl') div.row(ng-cloak) - div.col-xs-12 - //- div.col-sm-12 - //- h1= title - //- p Welcome to #{title} + div.col-xs-2.stat-holder + div.big-info.nodesactive(class="{{ nodesActive | nodesActiveClass : nodesTotal }}") + div.pull-left.icon-full-width + i.icon-node + div.pull-left + span.small-title active nodes + span.big-details {{nodesActive}}/{{nodesTotal}} + div.clearfix + div.col-xs-2.stat-holder + div.big-info.uptime(class="{{ upTimeTotal | upTimeClass }}") + div.pull-left.icon-full-width + i.icon-bulb + div.pull-left + span.small-title up-time + span.big-details {{ upTimeTotal | upTimeFilter }} + div.clearfix + div.col-xs-2.stat-holder + div.big-info.difficulty.text-info + div.pull-left.icon-full-width + i.icon-difficulty + div.pull-left + span.small-title difficulty + span.big-details {{ lastDifficulty }} + div.clearfix + div.col-xs-2.stat-holder + div.big-info.bestblock.text-info + div.pull-left.icon-full-width + i.icon-block + div.pull-left + span.small-title best block + span.big-details {{"#" + bestBlock}} + div.clearfix + div.col-xs-2.stat-holder + div.big-info.blocktime(class="{{ lastBlock | timeClass }}") + div.pull-left.icon-full-width + i.icon-time + div.pull-left + span.small-title last block + span.big-details {{ lastBlock | blockTimeFilter }} + div.clearfix + div.col-xs-2.stat-holder + div.big-info.avgblocktime(class="{{ avgBlockTime | timeClass }}") + div.pull-left.icon-full-width + i.icon-gas + div.pull-left + span.small-title avg block time + span.big-details {{ avgBlockTime | avgTimeFilter }} + div.clearfix - //- div.clearfix - - div.col-xs-2.stat-holder - div.row.big-info.nodesactive(class="{{ nodesActive | nodesActiveClass : nodesTotal }}") - div.pull-left.icon-full-width - i.icon-node - div.pull-left - span.small-title active nodes - span.big-details {{nodesActive}}/{{nodesTotal}} - div.clearfix - div.col-xs-2.stat-holder - div.row.big-info.uptime(class="{{ upTimeTotal | upTimeClass }}") - div.pull-left.icon-full-width - i.icon-bulb - div.pull-left - span.small-title up-time - span.big-details {{ upTimeTotal | upTimeFilter }} - div.clearfix - div.col-xs-2.stat-holder - div.row.big-info.difficulty.text-info - div.pull-left.icon-full-width - i.icon-difficulty - div.pull-left - span.small-title difficulty - span.big-details {{ lastDifficulty }} - div.clearfix - div.col-xs-2.stat-holder - div.row.big-info.bestblock.text-info - div.pull-left.icon-full-width - i.icon-block - div.pull-left - span.small-title best block - span.big-details {{"#" + bestBlock}} - div.clearfix - div.col-xs-2.stat-holder - div.row.big-info.blocktime(class="{{ lastBlock | timeClass }}") - div.pull-left.icon-full-width - i.icon-time - div.pull-left - span.small-title last block - span.big-details {{ lastBlock | blockTimeFilter }} - div.clearfix - div.col-xs-2.stat-holder - div.row.big-info.avgblocktime(class="{{ avgBlockTime | timeClass }}") - div.pull-left.icon-full-width - i.icon-gas - div.pull-left - span.small-title avg block time - span.big-details {{ avgBlockTime | avgTimeFilter }} - div.clearfix - - div.clearfix + div.clearfix - div.col-xs-12 + div.row + div.col-xs-4.stats-boxes(style="padding-top: 30px;") div.row - div.col-xs-4.stats-boxes(style="padding-top: 30px;") - div.row - div.col-xs-6.stat-holder - div.big-info.chart - span.small-title block time - span.big-details.spark-blocktimes + div.col-xs-6.stat-holder + div.big-info.chart + span.small-title block time + span.big-details.spark-blocktimes - div.col-xs-6.stat-holder - div.big-info.chart - span.small-title difficulty - span.big-details.spark-difficulty + div.col-xs-6.stat-holder + div.big-info.chart + span.small-title difficulty + span.big-details.spark-difficulty - div.col-xs-6.stat-holder - div.big-info.chart - span.small-title transactions - span.big-details.spark-transactions + div.col-xs-6.stat-holder + div.big-info.chart + span.small-title transactions + span.big-details.spark-transactions - div.col-xs-6.stat-holder - div.big-info.chart - span.small-title gas spending - span.big-details.spark-gasspending + div.col-xs-6.stat-holder + div.big-info.chart + span.small-title gas spending + span.big-details.spark-gasspending - div.col-xs-4 + div.col-xs-2.stats-boxes(style="padding-top: 30px;") + div.row + div.col-xs-12.stat-holder + div.big-info.chart.double-chart + span.small-title last blocks miners + div.blocks-holder(ng-repeat='miner in miners', data-toggle="tooltip", data-placement="right", title="{{miner.blocks}}") + div.block-count ({{miner.blocks}}) + div.small-title-miner {{miner.miner | minerNameFilter}} + div.block(ng-repeat="i in getNumber(miner.blocks) track by $index", class="{{miner.blocks | minerBlocksClass}}") + div.clearfix + + div.col-xs-2.stats-boxes(style="padding-top: 30px;") + div.row + //- div.col-xs-12.stat-holder + //- div.big-info.chart + //- span.small-title miners + //- span.big-details test - div.col-xs-4 - div.col-xs-12 - nodemap#mapHolder(data="map") + div.col-xs-4 + div.col-xs-12 + nodemap#mapHolder(data="map") div.clearfix - div.col-xs-12 - //- h1 Nodes in detail - - table.table.table-striped - thead - tr.text-info - th - i.icon-node(data-toggle="tooltip", data-placement="top", title="Node") - th.th-nodename - i.icon-laptop(data-toggle="tooltip", data-placement="top", title="Node type") - th.th-latency - i.icon-clock(data-toggle="tooltip", data-placement="top", title="Node latency") - th - i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining") - th - i.icon-group(data-toggle="tooltip", data-placement="top", title="Peers") - th - i.icon-network(data-toggle="tooltip", data-placement="top", title="Pending transactions") - th - i.icon-block(data-toggle="tooltip", data-placement="top", title="Last node block") - th.th-blockhash   - th - i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Block transactions") - th.th-blocktime - i.icon-time(data-toggle="tooltip", data-placement="top", title="Last block time") - th - i.icon-gas(data-toggle="tooltip", data-placement="top", title="Propagation time") - th - 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}}") - span.small(data-toggle="tooltip", data-placement="top", data-original-title="{{node.geo | geoTooltip}}") {{node.info.name}} - span.small  ({{node.info.ip}}) - td - div.small(ng-bind-html="node.info.node | nodeVersion") - //- div.small {{node.info.os}}, {{node.info.os_v}} - td.small(class="{{ node.stats | latencyClass }}") {{node.stats | latencyFilter}} - td(class="{{ node.stats.mining | miningClass }}") - i.small(class="{{ node.stats.mining | miningIconClass }}") - td(class="{{ node.stats.peers | peerClass }}", style="padding-left: 11px;") {{node.stats.peers}} - td(style="padding-left: 15px;") {{node.stats.pending}} - td(class="{{ node.stats.block.number | blockClass : bestBlock }}") {{'#' + node.stats.block.number}} - td(class="{{ node.stats.block.number | blockClass : bestBlock }}") - span.small {{node.stats.block.hash}} - td(style="padding-left: 14px;") {{node.stats.block.transactions.length || 0}} - td(class="{{ node.stats.block.timestamp | timeClass }}") {{node.stats.block.received | blockTimeFilter }} - td(class="{{ node.stats.block.propagation | propagationTimeClass }}") {{node.stats.block.propagation | blockPropagationFilter}} - td(class="{{ node.stats.uptime | upTimeClass }}") {{ node.stats.uptime | upTimeFilter }} + div.row + table.table.table-striped + thead + tr.text-info + th + i.icon-node(data-toggle="tooltip", data-placement="top", title="Node name", ng-click="predicate = 'info.name'; reverse=!reverse") + th.th-nodename + i.icon-laptop(data-toggle="tooltip", data-placement="top", title="Node type") + th.th-latency + i.icon-clock(data-toggle="tooltip", data-placement="top", title="Node latency") + th + i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining") + th + i.icon-group(data-toggle="tooltip", data-placement="top", title="Peers") + th + i.icon-network(data-toggle="tooltip", data-placement="top", title="Pending transactions") + th + i.icon-block(data-toggle="tooltip", data-placement="top", title="Last block", ng-click="predicate = ['-stats.block.number', 'stats.block.propagation']; reverse=!reverse") + th.th-blockhash   + th + i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Block transactions") + th.th-blocktime + i.icon-time(data-toggle="tooltip", data-placement="top", title="Last block time") + th + i.icon-gas(data-toggle="tooltip", data-placement="top", title="Propagation time") + th + i.icon-bulb(data-toggle="tooltip", data-placement="top", title="Up-time") + tbody + tr(ng-repeat='node in nodes | orderBy:predicate:reverse', class="{{ node.stats | mainClass : bestBlock }}") + td(rel="{{node.id}}") + span.small(data-toggle="tooltip", data-placement="top", data-original-title="{{node.geo | geoTooltip}}") {{node.info.name}} + span.small  ({{node.info.ip}}) + td + div.small(ng-bind-html="node.info.node | nodeVersion") + //- div.small {{node.info.os}}, {{node.info.os_v}} + td.small(class="{{ node.stats | latencyClass }}") {{node.stats | latencyFilter}} + td(class="{{ node.stats.mining | miningClass }}") + i.small(class="{{ node.stats.mining | miningIconClass }}") + td(class="{{ node.stats.peers | peerClass }}", style="padding-left: 11px;") {{node.stats.peers}} + td(style="padding-left: 15px;") {{node.stats.pending}} + td(class="{{ node.stats.block.number | blockClass : bestBlock }}") {{'#' + node.stats.block.number}} + td(class="{{ node.stats.block.number | blockClass : bestBlock }}") + span.small {{node.stats.block.hash}} + td(style="padding-left: 14px;") {{node.stats.block.transactions.length || 0}} + td(class="{{ node.stats.block.timestamp | timeClass }}") {{node.stats.block.received | blockTimeFilter }} + td(class="{{ node.stats.block.propagation | propagationTimeClass }}") {{node.stats.block.propagation | blockPropagationFilter}} + td(class="{{ node.stats.uptime | upTimeClass }}") {{ node.stats.uptime | upTimeFilter }}