Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

web: remove max-width; make responsive #48

Merged
merged 1 commit into from
May 21, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ GoStats - GoChain Network Stats Dashboard
============
[![CircleCI](https://circleci.com/gh/gochain-io/netstats/tree/master.svg?style=svg)](https://circleci.com/gh/gochain-io/netstats/tree/master)

This is a visual interface for tracking GoChain network status. It uses WebSockets to receive stats from running nodes and output them through an angular interface. It is the front-end implementation for [net-intelligence-api](https://github.com/gochain-io/net-intelligence-api).
This is a visual interface for tracking GoChain network status. It uses WebSockets to receive stats from running nodes and output them through an angular interface.

![Screenshot](screenshot.png?raw=true)

15 changes: 11 additions & 4 deletions handler.go
Original file line number Diff line number Diff line change
@@ -447,7 +447,9 @@ func (h *Handler) handlePrimusIncoming(conn *Conn, ready chan struct{}) {
}

var msg Message
if err := json.Unmarshal(buf, &msg); err != nil {
d := json.NewDecoder(bytes.NewReader(buf))
d.UseNumber()
if err := d.Decode(&msg); err != nil {
log.Printf("[primus] cannot unmarshal: %s", buf)
continue
}
@@ -463,9 +465,14 @@ func (h *Handler) handlePrimusIncoming(conn *Conn, ready chan struct{}) {
}
case "client-pong":
if len(msg.Emit) > 1 {
prevServerTime, _ := msg.Emit[1].(int)
if err := emit(conn, "client-latency", ClientLatencyMessageData{Latency: (h.DB.Now() - int64(prevServerTime)) / 2}); err != nil {
log.Printf("[api] node-pong emit error: %s", err)
data := msg.Emit[1].(map[string]interface{})
prevServerTime, err := data["serverTime"].(json.Number).Int64()
if err != nil {
log.Printf("[api] node-pong error: %s\n", err)
}
latency := ClientLatencyMessageData{Latency: (h.DB.Now() - prevServerTime) / 2}
if err := emit(conn, "client-latency", latency); err != nil {
log.Printf("[api] node-pong emit error: %s\n", err)
return
}
}
192 changes: 115 additions & 77 deletions src/css/style.css
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ html {

body {
width: 100%;
min-width: 1000px;
font-smooth: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -81,22 +80,19 @@ table td {

.stat-holder {
background: #090909;
border: 1px solid rgba(255,255,255,0.05);
border: 1px solid #171717;
}

.big-info {
padding-top: 15px;
padding-bottom: 15px;
.big-info>div {
margin-top: 10px;
margin-bottom: 10px;
}

.big-info .icon-full-width i {
display: block;
width: 85px;
height: 70px;
font-size: 70px;
line-height: 70px;
margin-right: 15px;
margin-left: -15px;
height: 60px;
font-size: 60px;
margin-left:-10px;
}

.big-info span.small-title,
@@ -125,24 +121,13 @@ span.small-title span.small {
}

.big-info .big-details {
display: block;
font-weight: 200;
font-size: 50px;
line-height: 55px;
letter-spacing: -4px;
word-spacing: nowrap !important;
font-size: 30px;
}

.big-info .big-details .small-hash {
font-size: 87%;
}

.big-info .big-details-holder {
position: absolute;
top: 15px;
left: 99px;
}

.big-info.chart {
padding-top: 12px;
}
@@ -155,7 +140,7 @@ span.small-title span.small {

.big-info.chart {
height: 120px;
-webkit-box-sizing: border-box
-webkit-box-sizing: border-box;
box-sizing: border-box;
}

@@ -233,7 +218,6 @@ span.small-title span.small {
}

.small-value {
font-weight: 300;
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: auto;
float: right;
@@ -248,34 +232,34 @@ span.small-title span.small {
top: 10px;
}

table i {
.nodeTable i {
-webkit-font-smoothing: subpixel-antialiased;
-moz-osx-font-smoothing: auto;
}

table th,
table td {
.nodeHeader, .nodeCell {
border-color: #222 !important;
}

table td {
.nodeCell {
line-height: 18px !important;
text-overflow: ellipsis;
}

table th {
.nodeHeader, .nodeCellLabel {
color: #888;
}

table th i {
.nodeHeader i {
line-height: 1em;
font-size: 20px;
font-size: 25px;
}
table td i {
.nodeCell i {
position: relative;
top: 2px;
left: 2px;
}
table td.peerPropagationChart {
.nodeCell.peerPropagationChart {
padding: 4px 5px !important;
}
nodepropagchart {
@@ -285,50 +269,10 @@ nodepropagchart {
vertical-align: top;
}

.table>tbody>tr>td,
.table>thead>tr>th {
.nodeCell, .nodeHeader {
padding: 5px;
}

.th-nodecheck,
.td-nodecheck {
width: 38px;
text-align: center;
}

.td-nodecheck i {
left: 0px;
}

.th-nodename {
width: 300px;
text-overflow: ellipsis;
}

.th-nodetype {
width: 220px;
}

.th-latency {
width: 100px;
}

.th-blockhash {
width: 150px;
}

.th-blocktime {
width: 110px;
}

.th-peerPropagationTime {
width: 120px;
}

.th-peerPropagationChart {
width: 140px;
}

.nodeInfo .tooltip .tooltip-inner {
max-width: 400px;
text-align: left;
@@ -363,7 +307,6 @@ nodepropagchart {
padding: 5px 0;
width: auto;
left: -50%;
word-wrap: wrap;
text-align: center;
}

@@ -439,7 +382,7 @@ svg .line {
stroke-width: 1.3px;
stroke-linejoin: round;
stroke-linecap: round;
shape-rendering: geometric-precision;
shape-rendering: geometricPrecision;
/*-webkit-svg-shadow: 0 0 7px #fff;*/
}

@@ -466,4 +409,99 @@ svg .axis text {

svg .y.axis .tick:first-child text {
opacity: 0;
}

.nodeTable {
display: table;
border-collapse: collapse;
border: 1px solid #171717;
}
.nodeHeaders {
display: table-header-group;
}
.nodeRow {
display: table-row;
}
.nodeHeader {
display: table-cell;
text-align:center;
}
.nodeBody {
display: table-row-group;
}
.nodeBody>.nodeRow:nth-child(odd) {
background: #090909;
}
.nodeCell {
display: table-cell;
text-align:center;
}
.nodeCellLabel {
display: none;
}

@media only screen and (max-width: 1200px) {
.big-info .big-details {
font-size: 25px;
}

.big-info .icon-full-width i {
height: 50px;
font-size: 50px;
}

.second-row .box {
height: unset;
}
}

@media only screen and (max-width: 992px) {
.nodeTable {
display: block;
}
.nodeHeaders {
display: block;
}
.nodeHeaders>.nodeRow {
display: block;
}
.nodeHeader {
display: block;
float: left;
text-align: unset;
}
.nodeBody {
display: block;
}
.nodeBody>.nodeRow {
display: flex;
flex-flow: wrap;
}
.nodeRow {
padding: 1em;
}
.nodeCell {
display: block;
float: left;
text-align: unset;
}
.nodeCellLabel {
display: unset;
}

.big-info>div {
margin-top: 5px;
margin-bottom: 5px;
}
}

@media only screen and (max-width: 768px) {
.big-info .big-details {
font-size: unset;
}

.big-info .icon-full-width i {
height: 40px;
font-size: 40px;
}
}
4 changes: 2 additions & 2 deletions src/js/controllers.js
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, soc
$scope.nodesActive = 0;
$scope.bestBlock = 0;
$scope.lastBlock = 0;
$scope.lastDifficulty = 0;
$scope.lastTotalDifficulty = 0;
$scope.upTimeTotal = 0;
$scope.avgBlockTime = 0;
$scope.blockPropagationAvg = 0;
@@ -576,7 +576,7 @@ netStatsApp.controller('StatsCtrl', function($scope, $filter, $localStorage, soc
}).stats;

$scope.lastBlock = $scope.bestStats.block.arrived;
$scope.lastDifficulty = $scope.bestStats.block.difficulty;
$scope.lastTotalDifficulty = $scope.bestStats.block.totalDifficulty;
}
}
}
161 changes: 3 additions & 158 deletions src/js/filters.js
Original file line number Diff line number Diff line change
@@ -26,11 +26,6 @@ angular.module('netStatsApp.filters', [])
return 'icon-loader';
};
})
.filter('mainClass', function() {
return function(node, bestBlock) {
return mainClass(node, bestBlock);
};
})
.filter('peerClass', function() {
return function(peers, active) {
return peerClass(peers, active);
@@ -44,113 +39,13 @@ angular.module('netStatsApp.filters', [])
return (! mining ? 'text-danger' : 'text-success');
};
})
.filter('miningIconClass', function() {
return function(mining) {
return (! mining ? 'icon-cancel' : 'icon-check');
};
})
.filter('hashrateClass', function() {
return function(mining, active) {
var icon = mining ? 'icon-check' : 'icon-cancel';
if(! mining || ! active)
return 'text-gray';

return 'text-success';
};
})
.filter('hashrateFilter', ['$sce', '$filter', function($sce, filter) {
return function(hashes, isMining) {
var result = 0;
var unit = 'K';

if( !isMining )
return $sce.trustAsHtml('<i class="icon-cancel"></i>');

if(hashes !== 0 && hashes < 1000) {
result = hashes;
unit = '';
}

if(hashes >= 1000 && hashes < Math.pow(1000, 2)) {
result = hashes / 1000;
unit = 'K';
}

if(hashes >= Math.pow(1000, 2) && hashes < Math.pow(1000, 3)) {
result = hashes / Math.pow(1000, 2);
unit = 'M';
}

if(hashes >= Math.pow(1000, 3) && hashes < Math.pow(1000, 4)) {
result = hashes / Math.pow(1000, 3);
unit = 'G';
}

if(hashes >= Math.pow(1000, 4) && hashes < Math.pow(1000, 5)) {
result = hashes / Math.pow(1000, 4);
unit = 'T';
}

return $sce.trustAsHtml('<span class="small">' + filter('number')(result.toFixed(1)) + ' <span class="small-hash">' + unit + 'H/s</span></span>');
};
}])
.filter('totalDifficultyFilter', function() {
return function(hashes) {
var result = 0;
var unit = '';

if(hashes !== 0 && hashes < 1000) {
result = hashes;
unit = '';
}

if(hashes >= 1000 && hashes < Math.pow(1000, 2)) {
result = hashes / 1000;
unit = 'K';
}

if(hashes >= Math.pow(1000, 2) && hashes < Math.pow(1000, 3)) {
result = hashes / Math.pow(1000, 2);
unit = 'M';
}
return 'text-gray ' + icon;

if(hashes >= Math.pow(1000, 3) && hashes < Math.pow(1000, 4)) {
result = hashes / Math.pow(1000, 3);
unit = 'G';
}

if(hashes >= Math.pow(1000, 4) && hashes < Math.pow(1000, 5)) {
result = hashes / Math.pow(1000, 4);
unit = 'T';
}

return result.toFixed(2) + ' ' + unit + 'H';
};
})
.filter('nodeVersion', function($sce) {
return function(version) {
if(version)
{
var tmp = version.split('/');

if(tmp[1] && tmp[1][0] !== 'v' && tmp[1][2] !== '.')
{
tmp.splice(1,1);
}

if(tmp[2] && tmp[2] === 'Release'){
tmp.splice(2,1);
}

if(tmp[2] && tmp[2].indexOf('Linux') === 0)
tmp[2] = 'linux';

if(tmp[2] && tmp[2].indexOf('Darwin') === 0)
tmp[2] = 'darwin';

return $sce.trustAsHtml(tmp.join('/'));
}

return '';
return 'text-success ' + icon;
};
})
.filter('blockClass', function() {
@@ -315,45 +210,6 @@ angular.module('netStatsApp.filters', [])
return moment.duration(Math.round(diff), 's').humanize() + ' ago';
};
})
.filter('networkHashrateFilter', ['$sce', '$filter', function($sce, filter) {
return function(hashes, isMining) {
if(hashes === null)
hashes = 0;

var result = 0;
var unit = 'K';

if(hashes !== 0 && hashes < 1000) {
result = hashes;
unit = '';
}

if(hashes >= 1000 && hashes < Math.pow(1000, 2)) {
result = hashes / 1000;
unit = 'K';
}

if(hashes >= Math.pow(1000, 2) && hashes < Math.pow(1000, 3)) {
result = hashes / Math.pow(1000, 2);
unit = 'M';
}

if(hashes >= Math.pow(1000, 3) && hashes < Math.pow(1000, 4)) {
result = hashes / Math.pow(1000, 3);
unit = 'G';
}

if(hashes >= Math.pow(1000, 4) && hashes < Math.pow(1000, 5)) {
result = hashes / Math.pow(1000, 4);
unit = 'T';
}

if( !isMining )
return $sce.trustAsHtml(filter('number')(result.toFixed(1)) + ' <span class="small-hash">' + unit + 'H/s</span>');

return $sce.trustAsHtml('? <span class="small-hash">' + unit + 'KH/s</span>');
};
}])
.filter('transactionRateFilter', ['$sce', '$filter', function ($sce, filter) {
return function (rate) {
if (rate === null)
@@ -580,17 +436,6 @@ angular.module('netStatsApp.filters', [])
return prefix + 'danger';
};
})
.filter('nodeClientClass', function() {
return function(info, current) {
if(typeof info === 'undefined' || typeof info.client === 'undefined' || typeof info.client === '')
return 'text-danger';

if(compareVersions(info.client, '<', current))
return 'text-danger';

return 'hidden';
};
})
.filter('consensusClass', function() {
return function(nodes, bestBlock) {
var status = 'success';
194 changes: 88 additions & 106 deletions src/views/index.jade
Original file line number Diff line number Diff line change
@@ -4,123 +4,109 @@ extends ./layout.jade
block content
div.container-fluid(ng-controller='StatsCtrl')
div.row(ng-cloak)
div.col-xs-3.stat-holder
div.col-xs-12.col-sm-6.col-md-3.col-lg-3.stat-holder
div.big-info.bestblock.text-info
div.pull-left.icon-full-width
i.icon-block
div.big-details-holder
div.pull-left
span.small-title best block
span.big-details {{'#'}}{{ bestBlock | number}}
div.clearfix
div.col-xs-3.stat-holder
div.col-xs-12.col-sm-6.col-md-3.col-lg-3.stat-holder
div.big-info.difficulty.text-orange
div.pull-left.icon-full-width
i.icon-hashrate
div.big-details-holder
span.small-title avg transaction rate
div.pull-left
span.small-title avg rate
span.big-details(ng-bind-html="avgTransactionRate | transactionRateFilter")
div.clearfix
div.col-xs-3.stat-holder
div.col-xs-12.col-sm-6.col-md-3.col-lg-3.stat-holder
div.big-info.blocktime(class="{{ lastBlock | timeClass : true }}")
div.pull-left.icon-full-width
i.icon-time
div.big-details-holder
div.pull-left
span.small-title last block
span.big-details {{ lastBlock | blockTimeFilter }}
//- span.big-details(time-ago="lastBlock")
div.clearfix
div.col-xs-3.stat-holder
div.col-xs-12.col-sm-6.col-md-3.col-lg-3.stat-holder
div.big-info.avgblocktime(class="{{ avgBlockTime | avgTimeClass }}")
div.pull-left.icon-full-width
i.icon-gas
div.big-details-holder
div.pull-left
span.small-title avg block time
span.big-details {{ avgBlockTime | avgTimeFilter }}
div.clearfix
//- div.col-xs-2.stat-holder
//- div.big-info.difficulty.text-danger
//- div.pull-left.icon-full-width
//- i.icon-difficulty
//- div.big-details-holder
//- //- span.small-title difficulty
//- //- span.big-details
//- //- span.small-hash {{ lastDifficulty | totalDifficultyFilter }}
//- div.clearfix

div.clearfix


div.row(ng-cloak)
div.col-xs-12.stats-boxes(style="padding-top: 0px;")
div.row.second-row
div.col-xs-3.stat-holder.box
div.col-xs-12.col-sm-6.col-md-4.col-lg-4.stat-holder.box
div.active-nodes(class="{{ nodesActive | nodesActiveClass : nodesTotal }}")
i.icon-node
span.small-title active nodes
span.small-value {{nodesActive}}/{{nodesTotal}}
div.col-xs-3.stat-holder.box
div.col-xs-12.col-sm-6.col-md-4.col-lg-4.stat-holder.box
div.gasprice.text-info
i.icon-gasprice
span.small-title gas price
span.small-value {{ bestStats.gasPrice.toString() | gasPriceFilter }}
div.col-xs-3.stat-holder.box
div.col-xs-12.col-sm-6.col-md-4.col-lg-4.stat-holder.box
div.gasprice.text-info
i.icon-gasprice
span.small-title gas limit
span.small-value {{ bestStats.block.gasLimit }} gas
//- div.col-xs-2.stat-holder.box
//- div.page-latency(class="{{ {active: true, latency: latency} | latencyClass }}")
//- i.icon-clock
//- span.small-title page latency
//- span.small-value {{latency}} ms
div.col-xs-3.stat-holder.box
div.col-xs-12.col-sm-6.col-md-4.col-lg-4.stat-holder.box
div.uptime(class="{{ upTimeTotal | upTimeClass : true }}")
i.icon-bulb
span.small-title uptime
span.small-value {{ upTimeTotal | upTimeFilter }}
//- div.col-xs-2.stat-holder.box
div.col-xs-12.col-sm-6.col-md-4.col-lg-4.stat-holder.box
div.difficulty.text-info
i.icon-difficulty
span.small-title total difficulty
span.small-value {{ lastTotalDifficulty }}
div.col-xs-12.col-sm-6.col-md-4.col-lg-4.stat-holder.box
div.page-latency(class="{{ {active: true, latency: latency} | latencyClass }}")
i.icon-clock
span.small-title page latency
span.small-value {{latency}} ms

div.row
div.col-xs-8.col-lg-6.stat-holder.map-holder.pull-right
div.col-xs-12.col-sm-12.col-md-8.col-lg-6.stat-holder.map-holder.pull-right
nodemap#mapHolder(data="map")

div.col-xs-4.col-lg-3.stat-holder
div.col-xs-12.col-sm-6.col-md-4.col-lg-3.stat-holder
div.big-info.chart.text-info
//- (class="{{ avgBlockTime | avgTimeClass }}")
//- i.icon-time
span.small-title block time
//- span.small-value {{ avgBlockTime | avgTimeFilter }}
sparkchart.big-details.spark-blocktimes(data="{{lastBlocksTime.join(',')}}", tooltipsuffix="s")

div.col-xs-4.col-lg-3.stat-holder
div.col-xs-12.col-sm-6.col-md-4.col-lg-3.stat-holder
div.big-info.chart.text-info
//- i.icon-uncle
span.small-title transactions per block
sparkchart.big-details.spark-transactions(data="{{transactionDensity.join(',')}}")

div.col-xs-4.col-lg-3.stat-holder
div.col-xs-12.col-sm-6.col-md-4.col-lg-3.stat-holder
div.big-info.chart.text-info
span.small-title tps per block
sparkchart.big-details.spark-transactionsrate(data="{{transactionRate.join(',')}}")

div.col-xs-4.col-lg-3.stat-holder
div.col-xs-12.col-sm-6.col-md-4.col-lg-3.stat-holder
div.big-info.chart.text-info
//- i.icon-difficulty
span.small-title difficulty
//- span.small-value {{ lastDifficulty | number }}
sparkchart.big-details.spark-difficulty(data="{{difficultyChart.join(',')}}")

div.col-xs-4.col-lg-3.stat-holder
div.col-xs-12.col-sm-6.col-md-4.col-lg-3.stat-holder
div.big-info.chart.text-info
//- i.icon-gasprice
span.small-title gas spending
sparkchart.big-details.spark-gasspending(data="{{gasSpending.join(',')}}")

div.col-xs-4.col-lg-3.stat-holder
div.col-xs-12.col-sm-6.col-md-4.col-lg-3.stat-holder
div.big-info.chart.xdouble-chart(class="{{ blockPropagationAvg | propagationAvgTimeClass : true }}")
//- i.icon-gas
span.small-title block propagation
//- span.small-value {{ blockPropagationAvg | blockPropagationFilter : '' }}
histogram.big-details.d3-blockpropagation(data="blockPropagationChart")


@@ -131,84 +117,80 @@ block content
span.small-value
i.icon-warning-o
span.small-title ATTENTION!
span This does not represent the entire GoChain network - only authorized signer and proxy nodes - and information may not be 100% accurate.
//- div.col-xs-12.stat-holder.box
//- div.active-nodes.text-danger
//- i.icon-hashrate
//- span.small-title SECURITY ALERT
//- span.small-value
//- a(href="https://blog.ethereum.org/2015/09/10/security-alert-previous-security-patch-can-lead-to-invalid-state-root-on-go-clients-with-a-specific-transaction-sequence-fixed-please-update/", target="_blank", class="text-danger") Read the blog post
//- div.clearfix
span This does not represent the entire GoChain network and information may not be 100% accurate.

div.row(ng-cloak, style="padding-top: 10px")
table.table.table-striped
thead
tr.text-info
th.th-nodecheck
.nodeTable.table.table-striped.table-hover
.nodeHeaders
.nodeRow
.nodeHeader.th-nodecheck
i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Pin nodes to display first", ng-click="orderTable(['-stats.block.number', 'stats.block.propagation'], false)")
th.th-nodename
.nodeHeader
i.icon-node(data-toggle="tooltip", data-placement="top", title="Node name", ng-click="orderTable(['info.name'], false)")
th.th-nodetype
.nodeHeader
i.icon-laptop(data-toggle="tooltip", data-placement="top", title="Node type", ng-click="orderTable(['info.node'], false)")
th.th-latency
.nodeHeader
i.icon-clock(data-toggle="tooltip", data-placement="top", title="Node latency", ng-click="orderTable(['stats.latency'], false)")
th
i.icon-mining(data-toggle="tooltip", data-placement="top", title="Is mining", ng-click="orderTable(['-stats.hashrate'], false)")
th
.nodeHeader
i.icon-database(data-toggle="tooltip", data-placement="top", title="Is signing", ng-click="orderTable(['-stats.hashrate'], false)")
.nodeHeader
i.icon-group(data-toggle="tooltip", data-placement="top", title="Peers", ng-click="orderTable(['-stats.peers'], false)")
th
.nodeHeader
i.icon-network(data-toggle="tooltip", data-placement="top", title="Pending transactions", ng-click="orderTable(['-stats.pending'], false)")
th
.nodeHeader
i.icon-check-o(data-toggle="tooltip", data-placement="top", title="Block transactions", ng-click="orderTable(['-stats.block.transactions.length'], false)")
th.th-blocktime
.nodeHeader
i.icon-time(data-toggle="tooltip", data-placement="top", title="Last block time", ng-click="orderTable(['-stats.block.received'], false)")
th
.nodeHeader
i.icon-block(data-toggle="tooltip", data-placement="top", title="Last block", ng-click="orderTable(['-stats.block.number', 'stats.block.propagation'], false)")
th.th-blockhash &nbsp;
//- th.th-blockhash
//- i.icon-difficulty(data-toggle="tooltip", data-placement="top", title="Total difficulty", ng-click="orderTable(['-stats.block.totalDifficulty'], false)")
//- th
//- i.icon-uncle(data-toggle="tooltip", data-placement="top", title="Uncles", ng-click="orderTable(['-stats.block.uncles.length'], false)")
th.th-peerPropagationTime
i.icon-gas(data-toggle="tooltip", data-placement="top", title="Propagation time", ng-click="orderTable(['-stats.block.number', 'stats.block.propagation'], false)")
th.th-peerPropagationChart
th.th-peerPropagationAvg
.nodeHeader
i.icon-gas(data-toggle="tooltip", data-placement="top", title="Average propagation time", ng-click="orderTable(['stats.propagationAvg'], false)")
th
.nodeHeader
i.icon-gas(data-toggle="tooltip", data-placement="top", title="Propagation time", ng-click="orderTable(['-stats.block.number', 'stats.block.propagation'], false)")
.nodeHeader
i.icon-bulb(data-toggle="tooltip", data-placement="top", title="Up-time", ng-click="orderTable(['-stats.uptime'], false)")
tbody(ng-cloak)
tr(ng-repeat='node in nodes | orderBy:predicate track by node.id', class="{{ node.stats | mainClass : bestBlock }}", id="node_{{node.id}}")
td.td-nodecheck
div.clearfix
.nodeBody(ng-cloak)
.nodeRow(ng-repeat='node in nodes | orderBy:predicate track by node.id', id="node_{{node.id}}")
.nodeCell
i(ng-click="pinNode(node.id)", class="{{ node.pinned | nodePinClass }}", data-toggle="tooltip", data-placement="right", data-original-title="Click to {{ node.pinned ? 'un' : '' }}pin")
td.nodeInfo(rel="{{node.id}}")
span.small(data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="{{node | geoTooltip}}") {{node.info.name}}
//- span.small &nbsp;({{node.info.ip}})
a.small(href="https://github.com/gochain-io/gochain/wiki/Network-Status#updating", target="_blank", data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="Netstats client needs update.<br>Click this icon for instructions.", class="{{ node.info | nodeClientClass : currentApiVersion }}")
i.icon-warning-o
td
div.small(ng-bind-html="node.info.node | nodeVersion")
td(class="{{ node.readable.latencyClass }}")
.nodeCell.text-success.nodeInfo(rel="{{node.id}}")
strong(data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="{{node | geoTooltip}}") {{node.info.name}}
.nodeCell.text-info
span.nodeCellLabel Version:&nbsp;
span.small {{ node.info.node }}
.nodeCell(class="{{ node.readable.latencyClass }}")
span.nodeCellLabel Latency:&nbsp;
span.small {{ node.readable.latency }}
td(class="{{ node.stats.mining | hashrateClass : node.stats.active }}", ng-bind-html="node.stats.hashrate | hashrateFilter : node.stats.mining")
td(class="{{ node.stats.peers | peerClass : node.stats.active }}", style="padding-left: 11px;") {{node.stats.peers}}
td(style="padding-left: 15px;") {{node.stats.pending}}
td(style="padding-left: 14px;") {{node.stats.block.transactions.length || 0}}
td(class="{{ node.stats.block.received | timeClass : node.stats.active }}") {{node.stats.block.received | blockTimeFilter }}
td(class="{{ node.stats | blockClass : bestBlock }}")
.nodeCell
span.nodeCellLabel Signing:&nbsp;
i(class="{{ node.stats.mining | hashrateClass : node.stats.active }}")
.nodeCell(class="{{ node.stats.peers | peerClass : node.stats.active }}")
span.nodeCellLabel Peers:&nbsp;
span {{node.stats.peers}}
.nodeCell.text-info
span.nodeCellLabel Pending:&nbsp;
span {{node.stats.pending}}
span.nodeCellLabel &nbsp;txs
.nodeCell.text-info
span.nodeCellLabel Block:&nbsp;
span {{node.stats.block.transactions.length || 0}}
span.nodeCellLabel &nbsp;txs
.nodeCell(class="{{ node.stats.block.received | timeClass : node.stats.active }}")
span {{node.stats.block.received | blockTimeFilter }}
.nodeCell(class="{{ node.stats | blockClass : bestBlock }}")
span(class="{{ node.readable.forkMessage ? node.readable.forkClass : '' }}") {{'#'}}{{ node.stats.block.number | number }}
//- a.small(data-toggle="tooltip", data-placement="top", data-html="true", data-original-title="{{ node.readable.forkMessage }}", class="{{ node.readable.forkClass }}")
i.icon-warning-o
td(class="{{ node.stats | blockClass : bestBlock }}")
span.small {{node.stats.block.hash | hashFilter}}
//- td(class="{{ node.stats | blockClass : bestBlock }}")
//- span.small {{node.stats.block.totalDifficulty | number}}
//- td(style="padding-left: 14px;") {{node.stats.block.uncles.length || 0}}
td(class="{{ node.stats | propagationTimeClass : bestBlock }}")
.nodeCell(class="{{ node.stats | blockClass : bestBlock }}")
code.small {{node.stats.block.hash | hashFilter}}
.nodeCell(class="{{ node.stats | propagationTimeClass : bestBlock }}")
div.propagationBox
span {{node.stats.block.propagation | blockPropagationFilter}}
td.peerPropagationChart(class="{{node.id}}")
.nodeCell(class="{{ node.stats | propagationNodeAvgTimeClass : bestBlock }}")
span.nodeCellLabel Block Prop:&nbsp;
span {{ node.stats | blockPropagationAvgFilter : bestBlock }}
.nodeCell.peerPropagationChart(class="{{node.id}}")
nodepropagchart(data="{{node.history.join(',')}}")
td(class="{{ node.stats | propagationNodeAvgTimeClass : bestBlock }}") {{ node.stats | blockPropagationAvgFilter : bestBlock }}
td(class="{{ node.stats.uptime | upTimeClass : node.stats.active }}") {{ node.stats.uptime | upTimeFilter }}
.nodeCell(class="{{ node.stats.uptime | upTimeClass : node.stats.active }}")
span.nodeCellLabel Uptime:&nbsp;
span {{ node.stats.uptime | upTimeFilter }}
div.clearfix