forked from jahway603/knomp
webworker01
5 years ago
23 changed files with 4766 additions and 0 deletions
@ -0,0 +1,86 @@ |
|||
<!doctype html> |
|||
|
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
|
|||
<link rel="shortcut icon" type="image/png" href="/static/kmdfavicon.svg"/> |
|||
|
|||
<link href='http://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'> |
|||
|
|||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.3/css/font-awesome.min.css"> |
|||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/normalize/3.0.1/normalize.min.css"> |
|||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.4.2/pure-min.css"> |
|||
|
|||
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script> |
|||
|
|||
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.5/d3.min.js"></script> |
|||
<script src="/static/nvd3.js"></script> |
|||
<link rel="stylesheet" href="/static/nvd3.css"> |
|||
|
|||
<script src="/static/main.js"></script> |
|||
<link rel="stylesheet" href="/static/style.css"> |
|||
|
|||
<title>minerpond - komodo assetchains mining pool</title> |
|||
|
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<header> |
|||
<div class="home-menu pure-menu pure-menu-open pure-menu-horizontal"> |
|||
|
|||
<a class="pure-menu-heading hot-swapper" href="/"><i class="fa fa-home"></i> Home</a> |
|||
|
|||
<ul> |
|||
<li class="{{? it.selected === 'getting_started' }}pure-menu-selected{{?}}"> |
|||
<a class="hot-swapper" href="/getting_started"> |
|||
<i class="fa fa-rocket"></i> |
|||
Getting Started |
|||
</a> |
|||
</li> |
|||
<li class="{{? it.selected === 'stats' }}pure-menu-selected{{?}}"> |
|||
<a class="hot-swapper" href="/stats"> |
|||
<i class="fa fa-bar-chart-o"></i> |
|||
Graph Stats |
|||
</a> |
|||
</li> |
|||
<li class="{{? it.selected === 'tbs' }}pure-menu-selected{{?}}"> |
|||
<a class="hot-swapper" href="/tbs"> |
|||
<i class="fa fa-table"></i> |
|||
Tab Stats |
|||
</a> |
|||
</li> |
|||
<li class="{{? it.selected === 'workers' }}pure-menu-selected{{?}}"> |
|||
<a class="hot-swapper" href="/workers"> |
|||
<i class="fa fa-cogs"></i> |
|||
Workers Stats |
|||
</a> |
|||
</li> |
|||
<li class="{{? it.selected === 'payments' }}pure-menu-selected{{?}}"> |
|||
<a class="hot-swapper" href="/payments"> |
|||
<i class="fa fa-bitcoin"></i> |
|||
Payments |
|||
</a> |
|||
</li> |
|||
<li class="{{? it.selected === 'api' }}pure-menu-selected{{?}}"> |
|||
<a class="hot-swapper" href="/api"> |
|||
<i class="fa fa-code"></i> |
|||
API |
|||
</a> |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</header> |
|||
|
|||
<main> |
|||
{{=it.page}} |
|||
</main> |
|||
|
|||
|
|||
<footer> |
|||
<3 <a href="https://webworker.sh" target="_blank">🐸</a> · <a href="https://github.com/webworker01/knomp" target="_blank">knomp on Github</a> · <a href="https://discord.gg/QaPwxKB" target="_blank" rel="nofollow noreferrer noopener">Discord Chat</a> |
|||
</footer> |
|||
|
|||
</body> |
|||
</html> |
File diff suppressed because it is too large
@ -0,0 +1,50 @@ |
|||
<div> |
|||
|
|||
<style> |
|||
#passwordForm, #adminCenter{ |
|||
display: none; |
|||
} |
|||
#adminCenter{ |
|||
display: flex; |
|||
flex-flow: row; |
|||
} |
|||
#leftMenu{ |
|||
flex: 0 0 200px; |
|||
} |
|||
#editForm{ |
|||
flex: 1 1 auto; |
|||
} |
|||
</style> |
|||
|
|||
<form class="pure-form pure-form-stacked" id="passwordForm"> |
|||
<fieldset> |
|||
<legend>Password</legend> |
|||
|
|||
<input id="password" type="password" placeholder="Password"> |
|||
|
|||
<label for="remember" class="pure-checkbox"> |
|||
<input id="remember" type="checkbox"> Stay Logged In |
|||
</label> |
|||
|
|||
<button type="submit" class="pure-button pure-button-primary">Log In</button> |
|||
</fieldset> |
|||
</form> |
|||
|
|||
<div id="adminCenter"> |
|||
|
|||
<div class="pure-menu pure-menu-open" id="leftMenu"> |
|||
<a class="pure-menu-heading">Administration</a> |
|||
<ul> |
|||
<li id="addPool"><a href="#">Add Pool</a></li> |
|||
<li class="pure-menu-heading" id="poolList">Current Pools</li> |
|||
</ul> |
|||
</div> |
|||
|
|||
<div id="editForm"></div> |
|||
|
|||
</div> |
|||
|
|||
|
|||
<script src="/static/admin.js"></script> |
|||
|
|||
</div> |
@ -0,0 +1,11 @@ |
|||
<div style="margin: 18px;"> |
|||
API - The API is work in progress and is subject to change during development. |
|||
<ul> |
|||
<li><a href="/api/stats">/api/stats</a> global pool stats</li> |
|||
<li><a href="/api/blocks">/api/blocks</a> global block stats</li> |
|||
<li><a href="/api/pool_stats">/api/pool_stats</a> - historical stats</li> |
|||
<li><a href="/api/payments">/api/payments</a> - payment history</li> |
|||
<li><a href="/api/worker_stats?taddr">/api/worker_stats?taddr</a> - historical time per pool json </li> |
|||
<li><a href="/api/live_stats">/api/live_stats</a> - live stats (websocket)</li> |
|||
</ul> |
|||
</div> |
@ -0,0 +1,325 @@ |
|||
<style> |
|||
#holder{ |
|||
display: flex; |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.glow{ |
|||
box-shadow: inset 0 0 12px 4px #ff6c00; |
|||
} |
|||
|
|||
.hidden{ |
|||
display: none !important; |
|||
} |
|||
|
|||
#menu{ |
|||
background-color: #3d3d3d; |
|||
min-width: 170px; |
|||
} |
|||
|
|||
#menu > .menuHeader{ |
|||
color: #e3f7ff; |
|||
border-bottom: 1px solid #7f878b; |
|||
font-size: 1.2em; |
|||
padding: 16px 16px 4px 15px; |
|||
} |
|||
|
|||
.menuList{ |
|||
transition-duration: 200ms; |
|||
} |
|||
|
|||
.menuList > a:first-child{ |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.menuList > a{ |
|||
display: inline-block; |
|||
color: #e3f7ff; |
|||
text-decoration: none; |
|||
padding: 7px; |
|||
padding-left: 25px; |
|||
width:40px; |
|||
} |
|||
|
|||
.menuList > a:hover{ |
|||
color: #f69b3a; |
|||
} |
|||
|
|||
#main{ |
|||
flex: 1 1 auto; |
|||
display: flex; |
|||
flex-direction: column; |
|||
margin: 18px; |
|||
} |
|||
.miningOption{ |
|||
color: white; |
|||
/* display: flex; |
|||
flex: 1 1 auto; |
|||
flex-direction: row; |
|||
flex-wrap: wrap; */ |
|||
min-height: 215px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
a.miningOption:hover{ |
|||
color: #f69b3a; |
|||
} |
|||
|
|||
.miningOption:first-child{ |
|||
background-color: #0eafc7; |
|||
} |
|||
.miningOption:last-child{ |
|||
background-color: #b064e1; |
|||
} |
|||
.miningOptionNum{ |
|||
font-size: 6em; |
|||
padding-right: 20px; |
|||
width: 140px; |
|||
text-align: center; |
|||
} |
|||
.miningOptionInstructions{ |
|||
flex: 1 1 auto; |
|||
padding:10px; |
|||
} |
|||
|
|||
.miningOptionInstructions > div:first-child{ |
|||
font-size: 2.4em; |
|||
} |
|||
.miningOptionInstructions > div:last-child{ |
|||
margin-top: 20px; |
|||
font-size: 1.3em; |
|||
} |
|||
|
|||
#orHolder{ |
|||
height: 37px; |
|||
text-align: center; |
|||
} |
|||
#orLine{ |
|||
border-bottom: 1px solid #c2cacf; |
|||
height: 19px; |
|||
margin-bottom: -13px; |
|||
} |
|||
#orText{ |
|||
background-color: #ebf4fa; |
|||
color: #5c5c5c; |
|||
display: inline-block; |
|||
width: 35px; |
|||
font-style: italic; |
|||
} |
|||
|
|||
#coinInfoBackground{ |
|||
transition-duration: 400ms; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: black; |
|||
opacity: 0.0; |
|||
} |
|||
#coinInfo{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
color: white; |
|||
width: 750px; |
|||
min-height: 400px; |
|||
top: 50px; |
|||
left: 50%; |
|||
margin-left: -375px; |
|||
position: absolute; |
|||
background-color: #f06350; |
|||
} |
|||
#coinInfo .coinInfoName{ |
|||
text-transform: capitalize; |
|||
} |
|||
#coinInfo > div:first-of-type{ |
|||
font-size: 1.8em; |
|||
text-align: center; |
|||
margin-top: 40px; |
|||
margin-bottom: 35px; |
|||
} |
|||
#coinInfoRows{ |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
flex: 1 1 auto; |
|||
margin-bottom: 70px; |
|||
} |
|||
#coinInfoRows > div{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
} |
|||
#coinInfoRows > div > div{ |
|||
padding: 3px; |
|||
} |
|||
#coinInfoRowKeys{ |
|||
font-weight: bold; |
|||
padding-right: 30px; |
|||
} |
|||
#coinInfoRowKeys .coinInfoSubtle{ |
|||
font-weight: normal; |
|||
} |
|||
#coinInfoRowValues{ |
|||
|
|||
} |
|||
#coinInfoClose{ |
|||
position: absolute; |
|||
font-size: 3em; |
|||
top: 0; |
|||
right: 0; |
|||
width: 60px; |
|||
height: 60px; |
|||
text-align: center; |
|||
color: white; |
|||
text-decoration: none; |
|||
} |
|||
#coinInfoClose:hover{ |
|||
color: #50f0e3; |
|||
} |
|||
</style> |
|||
|
|||
<div id="holder"> |
|||
<a href="#" class="miningOption" id="coinGlowTrigger"> |
|||
<div class="miningOptionInstructions"> |
|||
<div>Select a coin for connection details</div> |
|||
<div>Configurations for each coin are available for advanced miners</div> |
|||
</div> |
|||
</a> |
|||
|
|||
<div id="menu"> |
|||
|
|||
{{? (function(){ |
|||
if (!it.portalConfig.switching) return false; |
|||
for (var p in it.portalConfig.switching){ |
|||
if (it.portalConfig.switching[p].enabled) |
|||
return true; |
|||
} |
|||
return false; |
|||
})() |
|||
}} |
|||
<div class="menuHeader">Coin-Switching Ports</div> |
|||
{{?}} |
|||
|
|||
<div class="menuList"> |
|||
{{ for (var p in it.portalConfig.switching){ |
|||
if (!it.portalConfig.switching[p].enabled) continue; |
|||
var info = { |
|||
algo: p, |
|||
ports: {}, |
|||
host: it.portalConfig.website.stratumHost |
|||
}; |
|||
info.ports[it.portalConfig.switching[p].port] = {diff: it.portalConfig.switching[p].diff}; |
|||
info = JSON.stringify(info).replace(/"/g, '"'); |
|||
}} |
|||
<a href="#" class="poolOption" data-info="{{=info}}">{{=p}}</a> |
|||
{{ } }} |
|||
</div> |
|||
|
|||
<div class="menuHeader">Coins</div> |
|||
<div class="menuList" id="coinList"> |
|||
{{ for(var pool in it.poolsConfigs) { |
|||
var info = JSON.stringify({ |
|||
coin: it.poolsConfigs[pool].coin, |
|||
algo: it.poolsConfigs[pool].coin.algorithm, |
|||
ports: it.poolsConfigs[pool].ports, |
|||
host: it.portalConfig.website.stratumHost |
|||
}).replace(/"/g, '"'); |
|||
}} |
|||
<a href="#" class="poolOption" data-info="{{=info}}">{{=pool}}</a> |
|||
{{ } }} |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- <div id="main"> |
|||
<a href="#" class="miningOption" id="nompAppDownload"> |
|||
<div class="miningOptionNum">1.</div> |
|||
<div class="miningOptionInstructions"> |
|||
<div>Download NOMP App</div> |
|||
<div>Our preconfigured app makes mining that easy</div> |
|||
</div> |
|||
</a> |
|||
<div id="orHolder"> |
|||
<div id="orLine"></div> |
|||
<div id="orText">or</div> |
|||
</div> |
|||
<a href="#" class="miningOption" id="coinGlowTrigger"> |
|||
<div class="miningOptionNum">2.</div> |
|||
<div class="miningOptionInstructions"> |
|||
<div>Select a coin for connection details</div> |
|||
<div>Configurations for each coin are available for advanced miners</div> |
|||
</div> |
|||
</a> |
|||
</div> --> |
|||
</div> |
|||
|
|||
<a href="#" id="coinInfoBackground" class="hidden"></a> |
|||
|
|||
<div id="coinInfo" class="hidden"> |
|||
<a href="#" id="coinInfoClose">×</a> |
|||
<div><span class="coinInfoName"></span> Configuration:</div> |
|||
<div id="coinInfoRows"> |
|||
<div id="coinInfoRowKeys"> |
|||
<div>Username:</div> |
|||
<div>Password:</div> |
|||
</div> |
|||
<div id="coinInfoRowValues"> |
|||
<div id="coinInfoUsername"></div> |
|||
<div>anything</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<script> |
|||
|
|||
function showCoinConfig(info){ |
|||
|
|||
var htmlKeys = '<div class="coinInfoData">Algorithm:</div>'; |
|||
var htmlValues = '<div class="coinInfoData">' + info.algo + '</div>'; |
|||
|
|||
for (var port in info.ports){ |
|||
htmlKeys += '<div class="coinInfoData">URL <span class="coinInfoSubtle">(difficulty ' + info.ports[port].diff + ')</span>:</div>'; |
|||
htmlValues += '<div class="coinInfoData">stratum+tcp://' + info.host + ':' + port + '</div>'; |
|||
} |
|||
|
|||
if (info.coin) |
|||
$('#coinInfoUsername').text('your ' + info.coin.name + ' wallet address'); |
|||
else |
|||
$('#coinInfoUsername').text('your public key'); |
|||
$('.coinInfoData').remove(); |
|||
$('#coinInfoRowKeys').append(htmlKeys); |
|||
$('#coinInfoRowValues').append(htmlValues); |
|||
} |
|||
|
|||
$('#coinGlowTrigger').click(function(event){ |
|||
event.preventDefault(); |
|||
$('.menuList').addClass('glow'); |
|||
setTimeout(function(){ |
|||
$('.menuList').removeClass('glow'); |
|||
}, 200); |
|||
return false; |
|||
}); |
|||
|
|||
$('.poolOption').click(function(event){ |
|||
event.preventDefault(); |
|||
showCoinConfig($(this).data('info')); |
|||
$('#coinInfoBackground,#coinInfo').removeClass('hidden'); |
|||
$('#coinInfoBackground').css('opacity', 0.7); |
|||
return false; |
|||
}); |
|||
|
|||
$('#coinInfoBackground,#coinInfoClose').click(function(event){ |
|||
event.preventDefault(); |
|||
$('#coinInfoBackground,#coinInfo').addClass('hidden'); |
|||
$('#coinInfoBackground').css('opacity', 0.0); |
|||
return false; |
|||
}); |
|||
|
|||
$('#nompAppDownload').click(function(event){ |
|||
event.preventDefault(); |
|||
alert('NOMP App development still in progress...'); |
|||
return false; |
|||
}); |
|||
</script> |
@ -0,0 +1,134 @@ |
|||
<style> |
|||
#boxWelcome{ |
|||
background-color: #0eafc7; |
|||
color: white; |
|||
margin: 18px; |
|||
} |
|||
#logoImg{ |
|||
height: 285px; |
|||
margin: 55px; |
|||
} |
|||
#welcomeText{ |
|||
font-size: 2.7em; |
|||
margin: 50px 18px 10px 18px; |
|||
} |
|||
#welcomeItems{ |
|||
list-style-type: none; |
|||
font-size: 1.3em; |
|||
padding: 0 !important; |
|||
margin: 0 0 0 18px !important; |
|||
} |
|||
#welcomeItems > li{ |
|||
margin: 30px !important; |
|||
} |
|||
#boxesLower { |
|||
margin: 0 9px; |
|||
} |
|||
#boxesLower > div { |
|||
display: flex; |
|||
} |
|||
#boxesLower > div > div { |
|||
flex: 1 1 auto; |
|||
margin: 0 9px 18px 9px; |
|||
padding: 10px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
.boxLowerHeader{ |
|||
font-size: 1.3em; |
|||
margin: 0 0 5px 10px; |
|||
} |
|||
|
|||
|
|||
#boxStatsLeft{ |
|||
background-color: #b064e1; |
|||
} |
|||
#boxStatsRight{ |
|||
background-color: #10bb9c; |
|||
} |
|||
.boxStats{ |
|||
color: white; |
|||
} |
|||
.boxStatsList{ |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
justify-content: space-around; |
|||
opacity: 0.77; |
|||
margin-bottom: 5px; |
|||
flex: 1 1 auto; |
|||
/* align-content: center; */ |
|||
} |
|||
.boxStatsList i.fa{ |
|||
height: 15px; |
|||
width: 33px; |
|||
text-align: center; |
|||
} |
|||
.boxStatsList > div{ |
|||
padding: 5px 20px; |
|||
} |
|||
.boxStatsList > div > div{ |
|||
padding: 3px; |
|||
} |
|||
|
|||
|
|||
</style> |
|||
|
|||
<div class="pure-g-r" id="boxWelcome"> |
|||
<!-- <img src="/static/komodo-logo-horizontal-01.png" style="margin: 0 auto; height:150px;"> --> |
|||
|
|||
<div class="pure-u-1"> |
|||
<div style="font-size: 2em; text-align:center; margin-bottom:10px;">MinerPond 🐸</div> |
|||
<div style="text-align:center; margin-bottom:10px;">Low 0.5% fee! Min payouts of 1!</div> |
|||
<div style="text-align:center; margin-bottom:10px;">KMDICE: Use a KMD compatible R address! Currently 15 minute payouts.</div> |
|||
<div style="text-align:center; margin-bottom:10px;">HUSH3Z: Use a zs1 sapling address! Currently 1 hour payouts.</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="pure-g-r" id="boxesLower"> |
|||
<div class="pure-u-1"> |
|||
<div class="boxStats" id="boxStatsLeft"> |
|||
<div class="boxLowerHeader">Global Stats</div> |
|||
<div class="boxStatsList"> |
|||
{{ for(var algo in it.stats.algos) { }} |
|||
<div> |
|||
<div><i class="fa fa-flask"></i>{{=algo}}</div> |
|||
<div><i class="fa fa-users"></i><span id="statsMiners{{=algo}}">{{=it.stats.algos[algo].workers}}</span> Miners</div> |
|||
<div><i class="fa fa-tachometer"></i><span id="statsHashrate{{=algo}}">{{=it.stats.algos[algo].hashrateString}}</span></div> |
|||
</div> |
|||
{{ } }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="pure-u-1"> |
|||
<div class="boxStats" id="boxStatsRight"> |
|||
<div class="boxLowerHeader">Pools / Coins</div> |
|||
<div class="boxStatsList"> |
|||
{{ for(var pool in it.stats.pools) { }} |
|||
<div> |
|||
<div><i class="fa fa-dot-circle-o"></i>{{=pool}}</div> |
|||
<div><i class="fa fa-users"></i><span id="statsMiners{{=pool}}">{{=it.stats.pools[pool].workerCount}}</span> Miners</div> |
|||
<div><i class="fa fa-tachometer"></i><span id="statsHashrate{{=pool}}">{{=it.stats.pools[pool].hashrateString}}</span></div> |
|||
</div> |
|||
{{ } }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<script> |
|||
$(function() { |
|||
statsSource.addEventListener('message', function (e) { |
|||
var stats = JSON.parse(e.data); |
|||
for (algo in stats.algos) { |
|||
$('#statsMiners' + algo).text(stats.algos[algo].workers); |
|||
$('#statsHashrate' + algo).text(stats.algos[algo].hashrateString); |
|||
} |
|||
for (var pool in stats.pools) { |
|||
$('#statsMiners' + pool).text(stats.pools[pool].workerCount); |
|||
$('#statsHashrate' + pool).text(stats.pools[pool].hashrateString); |
|||
} |
|||
}); |
|||
}); |
|||
</script> |
@ -0,0 +1,105 @@ |
|||
<style> |
|||
#topCharts{ |
|||
padding-left: 18px; |
|||
padding-right: 18px; |
|||
padding-top: 18px; |
|||
padding-bottom: 0px; |
|||
} |
|||
#topCharts > div > div > svg{ |
|||
display: block; |
|||
height: 280px; |
|||
} |
|||
.chartWrapper{ |
|||
border: solid 1px #c7c7c7; |
|||
border-radius: 5px; |
|||
padding: 5px; |
|||
margin-bottom: 18px; |
|||
} |
|||
.chartLabel{ |
|||
font-size: 1.2em; |
|||
text-align: center; |
|||
padding: 4px; |
|||
} |
|||
.chartHolder{ |
|||
|
|||
} |
|||
|
|||
#boxesWorkers { |
|||
margin: 0 9px; |
|||
} |
|||
#boxesWorkers > div { |
|||
display: flex; |
|||
} |
|||
#boxesWorkers > div > div { |
|||
flex: 1 1 auto; |
|||
margin: 0 9px 18px 9px; |
|||
padding: 10px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
.boxLowerHeader{ |
|||
font-size: 1.3em; |
|||
margin: 0 0 5px 10px; |
|||
} |
|||
|
|||
#boxStatsLeft{ |
|||
color: black; |
|||
background-color: #cccccc; |
|||
} |
|||
#boxStatsRight{ |
|||
color: black; |
|||
background-color: #cccccc; |
|||
} |
|||
.boxStats{ |
|||
color: white; |
|||
} |
|||
.boxStatsList{ |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
justify-content: space-around; |
|||
opacity: 0.77; |
|||
margin-bottom: 5px; |
|||
flex: 1 1 auto; |
|||
align-content: center; |
|||
} |
|||
.boxStatsList i.fa{ |
|||
height: 15px; |
|||
width: 33px; |
|||
text-align: center; |
|||
} |
|||
.boxStatsList > div{ |
|||
padding: 5px 20px; |
|||
} |
|||
.boxStatsList > div > div{ |
|||
padding: 3px; |
|||
} |
|||
|
|||
</style> |
|||
|
|||
<div id="topCharts"> |
|||
<div class="chartWrapper"> |
|||
<div class="chartLabel"> |
|||
<!--<div style="float:left; padding-right: 18px;"><i class="fa fa-users"></i><span id="statsWorkers">...</span></div>--> |
|||
<div style="float:left; margin-right: 9px;">{{=String(it.stats.address).split(".")[0]}}</div> |
|||
<div style="float:right; padding-left: 18px;"><small><i class="fa fa-tachometer"></i> <span id="statsHashrateAvg">...</span> (Avg)</small></div> |
|||
<div style="float:right; padding-left: 18px;"><small><i class="fa fa-tachometer"></i> <span id="statsHashrate">...</span> (Now)</small></div> |
|||
<div style="float:right; padding-left: 18px;"><small><i class="fa fa-gavel"></i> Luck <span id="statsLuckDays">...</span> Days</small></div> |
|||
</div> |
|||
<div class="chartHolder"><svg id="workerHashrate" /></div> |
|||
<div> |
|||
<div style="float:right; padding-top: 9px; padding-right: 18px;"><i class="fa fa-cog"></i> Shares: <span id="statsTotalShares">...</span></div> |
|||
<div style="float:left; padding-top: 9px; padding-left: 18px; padding-right: 18px;"><i class="fa fa-money"></i> Immature: <span id="statsTotalImmature">...</span> </div> |
|||
<div style="float:left; padding-top: 9px; padding-left: 18px; padding-right: 18px;"><i class="fa fa-money"></i> Bal: <span id="statsTotalBal">...</span> </div> |
|||
<div style="padding-top: 9px; padding-left: 18px;"><i class="fa fa-money"></i> Paid: <span id="statsTotalPaid">...</span> </div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div id="boxesWorkers"> </div> |
|||
|
|||
<script> |
|||
var _miner = "{{=String(it.stats.address).split(".")[0]}}"; |
|||
var _workerCount = 0; |
|||
window.statsSource = new EventSource("/api/live_stats"); |
|||
document.querySelector('main').appendChild(document.createElement('script')).src = '/static/miner_stats.js'; |
|||
</script> |
@ -0,0 +1,25 @@ |
|||
<style> |
|||
#miningKeyPage{ |
|||
margin: 15px; |
|||
} |
|||
#keyFrame{ |
|||
padding: 0; |
|||
border: 0; |
|||
width: 100%; |
|||
height: 750px; |
|||
display: block; |
|||
} |
|||
</style> |
|||
|
|||
<div id="miningKeyPage"> |
|||
|
|||
<p> |
|||
This script run client-side (in your browser). For maximum security <a href="/key.html" download="key.html">download</a> the script and run it locally and |
|||
offline in a modern web browser. |
|||
</p> |
|||
|
|||
<iframe id="keyFrame" src="/key.html"> |
|||
|
|||
</iframe> |
|||
|
|||
</div> |
@ -0,0 +1,94 @@ |
|||
<style> |
|||
#bottomNotes { |
|||
display: block; |
|||
padding-left: 18px; |
|||
padding-right: 18px; |
|||
padding-bottom: 18px; |
|||
} |
|||
#topPool { |
|||
padding-top: 18px; |
|||
padding-left: 18px; |
|||
padding-right: 18px; |
|||
} |
|||
#topPool > div > div > svg { |
|||
display: block; |
|||
height: 280px; |
|||
} |
|||
.poolWrapper { |
|||
border: solid 1px #c7c7c7; |
|||
border-radius: 5px; |
|||
padding: 5px; |
|||
margin-bottom: 18px; |
|||
} |
|||
.poolLabel { |
|||
font-size: 1.2em; |
|||
text-align: center; |
|||
padding: 4px; |
|||
} |
|||
.poolMinerTable { |
|||
|
|||
} |
|||
table { |
|||
width: 100%; |
|||
} |
|||
table td { |
|||
padding: 6px 12px; |
|||
} |
|||
</style> |
|||
<script type="text/javascript"> |
|||
$(function () { |
|||
$(document).tooltip({ |
|||
content: function () { |
|||
return $(this).prop('title'); |
|||
}, |
|||
show: null, |
|||
close: function (event, ui) { |
|||
ui.tooltip.hover( |
|||
|
|||
function () { |
|||
$(this).stop(true).fadeTo(400, 1); |
|||
}, |
|||
|
|||
function () { |
|||
$(this).fadeOut("400", function () { |
|||
$(this).remove(); |
|||
}) |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
</script> |
|||
{{ function readableDate(a){ return new Date(parseInt(a)).toISOString().substring(0, 16).replace('T', ' ') + ' UTC'; } }} |
|||
{{ for(var pool in it.stats.pools) { }} |
|||
<table class="puretable"> |
|||
<thead> |
|||
<tr> |
|||
<th>Blocks</th> |
|||
<th>Time</th> |
|||
<th>Miners</th> |
|||
<th>Shares</th> |
|||
<th>Amount</th> |
|||
</tr> |
|||
</thead> |
|||
{{ for(var p in it.stats.pools[pool].payments) { }} |
|||
<tr> |
|||
<td style="max-width: 475px; word-wrap: break-word;"> |
|||
{{if (String(it.stats.pools[pool].name) == 'kmd') { }} |
|||
<a href="https://kmdexplorer.io/tx/{{=it.stats.pools[pool].payments[p].txid}}" title="View transaction" target="_blank" rel="noopener noreferrer">{{=it.stats.pools[pool].payments[p].blocks}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name) == 'hush3z') { }} |
|||
<a href="https://hush.explorer.dexstats.info/tx/{{=it.stats.pools[pool].payments[p].txid}}" target="_blank" rel="noopener noreferrer">{{=it.stats.pools[pool].payments[p].blocks}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name) == 'kmdice') { }} |
|||
<a href="http://kmdice.explorer.dexstats.info/tx/{{=it.stats.pools[pool].payments[p].txid}}" target="_blank" rel="noopener noreferrer">{{=it.stats.pools[pool].payments[p].blocks}}</a> |
|||
{{ } else { }} |
|||
{{=it.stats.pools[pool].payments[p].blocks}} |
|||
{{ } }} |
|||
</td> |
|||
<td>{{=readableDate(it.stats.pools[pool].payments[p].time)}}</td> |
|||
<td>{{=it.stats.pools[pool].payments[p].miners}}</td> |
|||
<td>{{=Math.round(it.stats.pools[pool].payments[p].shares)}}</td> |
|||
<td>{{=it.stats.pools[pool].payments[p].paid}} {{=it.stats.pools[pool].symbol}}</td> |
|||
</tr> |
|||
{{ } }} |
|||
</table> |
|||
</div> |
|||
{{ } }} |
@ -0,0 +1,360 @@ |
|||
<style> |
|||
#topCharts{ |
|||
padding: 18px; |
|||
} |
|||
#topCharts > div > div > svg{ |
|||
display: block; |
|||
height: 280px; |
|||
} |
|||
.chartWrapper{ |
|||
border: solid 1px #c7c7c7; |
|||
border-radius: 5px; |
|||
padding: 5px; |
|||
margin-bottom: 18px; |
|||
} |
|||
.chartLabel{ |
|||
font-size: 1.2em; |
|||
text-align: center; |
|||
padding: 4px; |
|||
} |
|||
#boxesLower { |
|||
margin: 0 9px; |
|||
} |
|||
#boxesLower > div { |
|||
display: flex; |
|||
} |
|||
#boxesLower > div > div { |
|||
flex: 1 1 auto; |
|||
margin: 0 9px 18px 9px; |
|||
padding: 10px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
.boxLowerHeader{ |
|||
font-size: 1.3em; |
|||
margin: 0 0 5px 10px; |
|||
} |
|||
#boxStatsLeft{ |
|||
color: black; |
|||
background-color: #cccccc; |
|||
} |
|||
#boxStatsRight{ |
|||
color: black; |
|||
background-color: #cccccc; |
|||
} |
|||
.boxStats{ |
|||
color: white; |
|||
} |
|||
.boxStatsList{ |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
justify-content: space-around; |
|||
opacity: 0.77; |
|||
margin-bottom: 5px; |
|||
flex: 1 1 auto; |
|||
align-content: center; |
|||
} |
|||
.boxStatsList i.fa{ |
|||
height: 15px; |
|||
width: 33px; |
|||
text-align: center; |
|||
} |
|||
.boxStatsList > div{ |
|||
padding: 5px 20px; |
|||
} |
|||
.boxStatsList > div > div{ |
|||
padding: 3px; |
|||
} |
|||
|
|||
div.tooltip { |
|||
position: absolute; |
|||
text-align: center; |
|||
width: 60px; |
|||
height: 28px; |
|||
padding: 2px; |
|||
font: 12px sans-serif; |
|||
background: lightsteelblue; |
|||
border: 0px; |
|||
border-radius: 8px; |
|||
pointer-events: none; |
|||
} |
|||
#tooltip.hidden { |
|||
opacity: 0; |
|||
} |
|||
</style> |
|||
|
|||
<div id="topCharts"> |
|||
<div class="chartWrapper"> |
|||
<div class="chartLabel">Pool Historical Hashrate</div> |
|||
<div class="chartHolder"><svg id="poolHashrate"/></div> |
|||
</div> |
|||
</div> |
|||
|
|||
{{ function capitalizeFirstLetter(t){return t.charAt(0).toUpperCase()+t.slice(1)} }} |
|||
{{ function readableDate(a){ return new Date(parseInt(a)).toISOString().substring(0, 16).replace('T', ' ') + ' UTC'; } }} |
|||
|
|||
<div class="pure-g-r" id="boxesLower"> |
|||
{{ for(var pool in it.stats.pools) { }} |
|||
<div class="pure-u-1-2"> |
|||
<div class="boxStats" id="boxStatsLeft"> |
|||
<div class="boxLowerHeader">{{=it.stats.pools[pool].name}} Pool Stats</div> |
|||
<div class="boxStatsList"> |
|||
<div> |
|||
<div><i class="fa fa-users"></i><span id="statsMiners{{=pool}}">{{=it.stats.pools[pool].minerCount}}</span> Miners</div> |
|||
<div><i class="fa fa-rocket"></i><span id="statsWorkers{{=pool}}">{{=it.stats.pools[pool].workerCount}}</span> Workers</div> |
|||
<div><i class="fa fa-tachometer"></i><span id="statsHashrate{{=pool}}">{{=it.stats.pools[pool].hashrateString}}</span> (Now)</div> |
|||
<div><i class="fa fa-tachometer"></i><span id="statsHashrateAvg{{=pool}}">...</span> (Avg)</div> |
|||
<div><i class="fa fa-gavel"></i>Luck <span id="statsLuckDays{{=pool}}">{{=it.stats.pools[pool].luckDays}}</span> Days</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="pure-u-1-2"> |
|||
<div class="boxStats" id="boxStatsRight"> |
|||
<div class="boxLowerHeader">{{=it.stats.pools[pool].name}} Network Stats</div> |
|||
<div class="boxStatsList"> |
|||
<div> |
|||
<div><i class="fa fa-bars" aria-hidden="true"></i><small>Block Height:</small> <span id="statsNetworkBlocks{{=pool}}">{{=it.stats.pools[pool].poolStats.networkBlocks}}</span></div> |
|||
<div><i class="fa fa-tachometer"></i><small>Network Hash/s:</small> <span id="statsNetworkSols{{=pool}}">{{=it.stats.pools[pool].poolStats.networkSolsString}}</span></div> |
|||
<div><i class="fa fa-unlock-alt" aria-hidden="true"></i><small>Difficulty:</small> <span id="statsNetworkDiff{{=pool}}">{{=it.stats.pools[pool].poolStats.networkDiff}}</span></div> |
|||
<div><i class="fa fa-users"></i><small>Node Connections:</small> <span id="statsNetworkConnections{{=pool}}">{{=it.stats.pools[pool].poolStats.networkConnections}}</span></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{{ } }} |
|||
</div> |
|||
|
|||
{{ for(var pool in it.stats.pools) { }} |
|||
{{ var blockscomb = new Array; }} |
|||
<div class="pure-g-r" id="boxesLower"> |
|||
<div class="pure-u-1-1"> |
|||
<div class="boxStats" id="boxStatsRight"> |
|||
<div class="boxLowerHeader">{{=it.stats.pools[pool].name}} Blocks Found |
|||
<span style="float:right;"><small> |
|||
<i class="fa fa-bars"></i> <span id="statsValidBlocks{{=pool}}">{{=it.stats.pools[pool].poolStats.validBlocks}}</span> Blocks |
|||
<i class="fa fa-money"></i> Paid: <span id="statsTotalPaid{{=pool}}">{{=(parseFloat(it.stats.pools[pool].poolStats.totalPaid)).toFixed(8)}}</span> {{=it.stats.pools[pool].symbol}}</small> </span> |
|||
</div> |
|||
|
|||
<div class="boxStatsList" style="margin-top: 9px;"> |
|||
<!--<div id="{{=it.stats.pools[pool].name}}NewBlocks"></div>--> |
|||
{{ for(var b in it.stats.pools[pool].pending.blocks) { }} |
|||
{{ var block = it.stats.pools[pool].pending.blocks[b].split(":"); }} |
|||
<div style="margin-bottom: 9px; background-color: #eeeeee; min-width:600px;" title="{{if (it.stats.pools[pool].pending.confirms && it.stats.pools[pool].pending.confirms[block[0]]) { }}{{if (it.stats.pools[pool].pending.confirms[block[0]] == 1) { }}Waiting for dPoW notarization{{} else if (it.stats.pools[pool].pending.confirms[block[0]] < it.poolsConfigs[pool].paymentProcessing.minConf*2) { }}Waiting for min confirmations{{ } else { }}Queued for payment{{ } }}{{ } else { }}Waiting for payment processor to review{{ } }}"> |
|||
<i class="fa fa-bars"></i> |
|||
<small>Block:</small> |
|||
{{if (String(it.stats.pools[pool].name) == "kmd") { }} |
|||
<a href="https://kmdexplorer.io/blocks/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name) == "rkt") { }} |
|||
<a href="http://rkt.explorer.dexstats.info/block/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name) == "kmdice") { }} |
|||
<a href="https://kmdice.explorer.dexstats.info/block/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name) == "hush3z") { }} |
|||
<a href="https://hush.explorer.dexstats.info/block/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else { }} |
|||
{{=block[2]}} |
|||
{{ } }} |
|||
|
|||
{{if (block[4] != null) { }} |
|||
<span style="padding-left: 18px;"><small>{{=readableDate(block[4])}}</small></span> |
|||
{{ } }} |
|||
{{if (it.stats.pools[pool].pending.confirms && it.stats.pools[pool].pending.confirms[block[0]]) { }} |
|||
{{if (it.stats.pools[pool].pending.confirms[block[0]] == 1) { }} |
|||
<span style="float:right; color: red;"><small>Waiting for Notarization</small></span> |
|||
{{ } else { }} |
|||
<span style="float:right; color: red;"><small>{{=it.stats.pools[pool].pending.confirms[block[0]]}} of {{=it.poolsConfigs[pool].paymentProcessing.minConf*2}} Confirmations</small></span> |
|||
{{ } }} |
|||
{{ } else { }} |
|||
<span style="float:right; color: red;"><small>*PENDING*</small></span> |
|||
{{ } }} |
|||
<div><i class="fa fa-gavel"></i><small>Mined By:</small> <a href="/workers/{{=block[3].split('.')[0]}}">{{=block[3].length > 40 ? block[3].substring(0, 20) + '...' + block[3].substring(block[3].length-20, block[3].length): block[3]}}</a></div> |
|||
</div> |
|||
{{ blockscomb.push(block);}} |
|||
{{ } }} |
|||
|
|||
{{ var i=0; for(var b in it.stats.pools[pool].confirmed.blocks) { }} |
|||
{{ if (i < 8) { i++; }} |
|||
{{ var block = it.stats.pools[pool].confirmed.blocks[b].split(":"); }} |
|||
<div style="margin-bottom: 9px; background-color: #eeeeee; min-width:600px;"><i class="fa fa-bars"></i> |
|||
<small>Block:</small> |
|||
{{if (String(it.stats.pools[pool].name) == "kmd") { }} |
|||
<a href="https://kmdexplorer.io/blocks/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name).startsWith("rkt")) { }} |
|||
<a href="http://rkt.explorer.dexstats.info/block/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name).startsWith("kmdice")) { }} |
|||
<a href="http://kmdice.explorer.dexstats.info/block/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else if (String(it.stats.pools[pool].name) == "hush3z") { }} |
|||
<a href="https://hush.explorer.dexstats.info/block/{{=block[0]}}" target="_blank">{{=block[2]}}</a> |
|||
{{ } else { }} |
|||
{{=block[2]}} |
|||
{{ } }} |
|||
{{if (block[4] != null) { }} |
|||
<span style="padding-left: 18px;"><small>{{=readableDate(block[4])}}</small></span> |
|||
{{ } }} |
|||
<span style="float:right; padding-left: 18px; color: green;"><small>*PAID*</small></span> |
|||
<div><i class="fa fa-gavel"></i><small>Mined By:</small> <a href="/workers/{{=block[3].split('.')[0]}}">{{=block[3].length > 40 ? block[3].substring(0, 20) + '...' + block[3].substring(block[3].length-20, block[3].length): block[3]}}</a></div> |
|||
</div> |
|||
{{blockscomb.push(block);}} |
|||
{{ } }} |
|||
{{ } }} |
|||
|
|||
{{if (blockscomb.length > 0) { }} |
|||
<!--{{=JSON.stringify(blockscomb)}}--> |
|||
<script> |
|||
var blockscomb = ({{=JSON.stringify(blockscomb)}}) |
|||
</script> |
|||
{{ } }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
{{if (blockscomb.length > 0) { }} |
|||
<center><div id="bottomCharts{{=pool}}" style="text-align:center;" align="center"> |
|||
<div class="chartWrapper" style="text-align:center;"> |
|||
<div class="chartLabel">Finders of the last {{=blockscomb.length}} blocks</div> |
|||
<div class="hidden" id="tooltip{{=pool}}"><p><span id="value{{=pool}}"></span> blocks found by <span id="finderr{{=pool}}"></span></p></div> |
|||
<div class="chartHolder" id="pie{{=pool}}"><svg id="blocksPie{{=pool}}" style="display: block; margin: auto; text-align:center;"/></div> |
|||
</div> |
|||
</div></center> |
|||
|
|||
<script> |
|||
|
|||
var groupedByFinder = {}; |
|||
var data = []; |
|||
|
|||
for (var i=0; i < blockscomb.length; i++) { |
|||
finder=blockscomb[i][3]; // if other doesn 't already have a property for the current letter |
|||
// create it and assign it to a new empty array |
|||
if (!(finder in groupedByFinder)) |
|||
groupedByFinder[finder] = []; |
|||
|
|||
groupedByFinder[finder].push(blockscomb[i]); |
|||
} |
|||
|
|||
Object.keys(groupedByFinder).forEach(function(i) { |
|||
var obj = {}; |
|||
obj.label = i |
|||
obj.value = groupedByFinder[i].length |
|||
data.push(obj) |
|||
}); |
|||
|
|||
console.log(JSON.stringify(data)) |
|||
|
|||
var w = 400; |
|||
var h = 400; |
|||
var r = h/2; |
|||
var legendRectSize = 18; |
|||
var legendSpacing = 5; |
|||
|
|||
var color = d3.scale.category20c(); |
|||
var div = d3.select("#pie{{=pool}}").append("div") |
|||
.attr("class", "tooltip") |
|||
.style("opacity", 0); |
|||
|
|||
var vis = d3.select('#blocksPie{{=pool}}') |
|||
.data([data]) |
|||
.attr("width", 1000) |
|||
.attr("height", h) |
|||
.attr("style", "display: block; margin: auto;") |
|||
.attr("preserveAspectRatio", "xMidYMin") |
|||
.append("svg:g") |
|||
.attr("transform", "translate(" + r + "," + r + ")"); |
|||
|
|||
|
|||
var pie = d3.layout.pie().value(function(d){return d.value;}); |
|||
|
|||
// declare an arc generator function |
|||
var arc = d3.svg.arc().outerRadius(r); |
|||
|
|||
// select paths, use arc generator to draw |
|||
var arcs = vis.selectAll("g.slice{{=pool}}") |
|||
.data(pie) |
|||
.enter() |
|||
.append("svg:g") |
|||
.attr("class", "slice{{=pool}}") |
|||
.attr("id", "slice") |
|||
.on("mouseover", function(d){ |
|||
d3.select("#tooltip{{=pool}}") |
|||
.style("left", d3.event.pageX + "px") |
|||
.style("top", d3.event.pageY + "px") |
|||
.style("opacity", 1) |
|||
.select("#value{{=pool}}") |
|||
.text(d.data.value); |
|||
d3.select("#tooltip{{=pool}}") |
|||
.select("#finderr{{=pool}}") |
|||
.text(d.data.label); |
|||
}); |
|||
|
|||
arcs.append("svg:path") |
|||
.attr("fill", function(d, i){ |
|||
return color(i); |
|||
}) |
|||
.attr("d", function (d) { |
|||
return arc(d); |
|||
}); |
|||
|
|||
var legend = vis.selectAll('.legend') |
|||
.data(color.domain()) |
|||
.enter() |
|||
.append('g') |
|||
.attr('class', 'legend') |
|||
.attr('id', {{=JSON.stringify(pool)}}) |
|||
.attr('transform', function(d, i) { |
|||
var height = legendRectSize + legendSpacing; |
|||
var offset = height * color.domain().length / 2; |
|||
var horz = 12 * legendRectSize; |
|||
var vert = i * height; |
|||
return 'translate(' + horz + ',' + vert + ')'; |
|||
}); |
|||
|
|||
legend.append('rect') |
|||
.attr('width', legendRectSize) |
|||
.attr('height', legendRectSize) |
|||
.style('fill', color) |
|||
.style('stroke', color); |
|||
|
|||
legend.append('text') |
|||
.attr('x', legendRectSize + legendSpacing) |
|||
.attr('y', legendRectSize - legendSpacing) |
|||
.text(function(d, i) { |
|||
return data[i].label; |
|||
}); |
|||
</script> |
|||
{{ } }} |
|||
{{ } }} |
|||
|
|||
<script> |
|||
document.querySelector('main').appendChild(document.createElement('script')).src = '/static/stats.js'; |
|||
|
|||
$(function() { |
|||
window.statsSource = new EventSource("/api/live_stats"); |
|||
|
|||
statsSource.addEventListener('message', function (e) { |
|||
var stats = JSON.parse(e.data); |
|||
for (var pool in stats.pools) { |
|||
$('#statsMiners' + pool).text(stats.pools[pool].minerCount); |
|||
$('#statsWorkers' + pool).text(stats.pools[pool].workerCount); |
|||
$('#statsHashrate' + pool).text(stats.pools[pool].hashrateString); |
|||
$('#statsHashrateAvg' + pool).text(getReadableHashRateString(calculateAverageHashrate(pool))); |
|||
$('#statsLuckDays' + pool).text(stats.pools[pool].luckDays); |
|||
$('#statsValidBlocks' + pool).text(stats.pools[pool].poolStats.validBlocks); |
|||
$('#statsTotalPaid' + pool).text((parseFloat(stats.pools[pool].poolStats.totalPaid)).toFixed(8)); |
|||
$('#statsNetworkBlocks' + pool).text(stats.pools[pool].poolStats.networkBlocks); |
|||
$('#statsNetworkDiff' + pool).text(stats.pools[pool].poolStats.networkDiff); |
|||
$('#statsNetworkSols' + pool).text(getReadableNetworkHashRateString(stats.pools[pool].poolStats.networkSols)); |
|||
$('#statsNetworkConnections' + pool).text(stats.pools[pool].poolStats.networkConnections); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
function getReadableNetworkHashRateString(hashrate){ |
|||
hashrate = (hashrate * 1000000); |
|||
if (hashrate < 1000000) |
|||
return '0 Sol'; |
|||
var byteUnits = [ ' Sol/s', ' KSol/s', ' MSol/s', ' GSol/s', ' TSol/s', ' PSol/s' ]; |
|||
var i = Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1); |
|||
hashrate = (hashrate/1000) / Math.pow(1000, i + 1); |
|||
return hashrate.toFixed(2) + byteUnits[i]; |
|||
} |
|||
</script> |
@ -0,0 +1,64 @@ |
|||
<style> |
|||
|
|||
#topCharts { |
|||
padding: 18px; |
|||
} |
|||
|
|||
#topCharts > div > div > svg { |
|||
display: block; |
|||
height: 280px; |
|||
} |
|||
|
|||
.chartWrapper { |
|||
border: solid 1px #c7c7c7; |
|||
border-radius: 5px; |
|||
padding: 5px; |
|||
margin-bottom: 18px; |
|||
} |
|||
|
|||
.chartLabel { |
|||
font-size: 1.2em; |
|||
text-align: center; |
|||
padding: 4px; |
|||
} |
|||
|
|||
.chartHolder { |
|||
|
|||
} |
|||
|
|||
table { |
|||
width: 100%; |
|||
} |
|||
|
|||
</style> |
|||
|
|||
<table class="pure-table"> |
|||
<thead> |
|||
<tr> |
|||
<th>Pool</th> |
|||
<th>Algo</th> |
|||
<th>Workers</th> |
|||
<th>Valid Shares</th> |
|||
<th>Invalid Shares</th> |
|||
<th>Total Blocks</th> |
|||
<th>Pending</th> |
|||
<th>Confirmed</th> |
|||
<th>Orphaned</th> |
|||
<th>Hashrate</th> |
|||
</tr> |
|||
</thead> |
|||
{{ for(var pool in it.stats.pools) { }} |
|||
<tr class="pure-table-odd"> |
|||
<td>{{=it.stats.pools[pool].name}}</td> |
|||
<td>{{=it.stats.pools[pool].algorithm}}</td> |
|||
<td>{{=Object.keys(it.stats.pools[pool].workers).length}}</td> |
|||
<td>{{=it.stats.pools[pool].poolStats.validShares}}</td> |
|||
<td>{{=it.stats.pools[pool].poolStats.invalidShares}}</td> |
|||
<td>{{=it.stats.pools[pool].poolStats.validBlocks}}</td> |
|||
<td>{{=it.stats.pools[pool].blocks.pending}}</td> |
|||
<td>{{=it.stats.pools[pool].blocks.confirmed}}</td> |
|||
<td>{{=it.stats.pools[pool].blocks.orphaned}}</td> |
|||
<td>{{=it.stats.pools[pool].hashrateString}}</td> |
|||
</tr> |
|||
{{ } }} |
|||
</table> |
@ -0,0 +1,94 @@ |
|||
<style> |
|||
#bottomNotes { |
|||
display: block; |
|||
padding-left: 18px; |
|||
padding-right: 18px; |
|||
padding-bottom: 18px; |
|||
} |
|||
#topPool { |
|||
padding-top: 18px; |
|||
padding-left: 18px; |
|||
padding-right: 18px; |
|||
} |
|||
#topPool > div > div > svg { |
|||
display: block; |
|||
height: 280px; |
|||
} |
|||
.poolWrapper { |
|||
border: solid 1px #c7c7c7; |
|||
border-radius: 5px; |
|||
padding: 5px; |
|||
margin-bottom: 18px; |
|||
} |
|||
.poolLabel { |
|||
font-size: 1.2em; |
|||
text-align: center; |
|||
padding: 4px; |
|||
} |
|||
.poolMinerTable { |
|||
|
|||
} |
|||
table { |
|||
width: 100%; |
|||
} |
|||
</style> |
|||
<script type="text/javascript"> |
|||
function searchKeyPress(e) |
|||
{ |
|||
// look for window.event in case event isn't passed in |
|||
e = e || window.event; |
|||
if (e.keyCode == 13) |
|||
{ |
|||
document.getElementById('btnSearch').click(); |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
$(document).ready(function(){ |
|||
$('.btn-lg').click(function(){ |
|||
window.location = "workers/" + $('.input-lg').val(); |
|||
}); |
|||
}); |
|||
</script> |
|||
{{ function capitalizeFirstLetter(t){return t.charAt(0).toUpperCase()+t.slice(1)} }} |
|||
{{ var i=0; for(var pool in it.stats.pools) { }} |
|||
<div id="topPool"> |
|||
<div class="poolWrapper"> |
|||
<div class="poolLabel"> |
|||
<span style="float:right; margin-bottom: 8px;"> |
|||
<small>Miner Lookup: |
|||
<input type="text" class="form-control input-lg" onkeypress="return searchKeyPress(event);"> |
|||
<span class="input-group-btn"> |
|||
<button class="btn btn-default btn-lg" type="button">Lookup</button> |
|||
</span> |
|||
</small> |
|||
</span> |
|||
{{=capitalizeFirstLetter(it.stats.pools[pool].name)}} Top Miners |
|||
<small><i class="fa fa-users"></i> <span id="statsMiners{{=pool}}">{{=it.stats.pools[pool].minerCount}}</span> Miners |
|||
<i class="fa fa-rocket"></i> <span id="statsWorkers{{=pool}}">{{=it.stats.pools[pool].workerCount}}</span> Workers |
|||
<i class="fa fa-cog"></i> <span id="statsWorkers{{=pool}}">{{=it.stats.pools[pool].shareCount}}</span> Shares </small> |
|||
</div> |
|||
<div class="poolMinerTable"> |
|||
<table class="pure-table"> |
|||
<thead> |
|||
<tr> |
|||
<th>Address</th> |
|||
<th>Shares</th> |
|||
<th>Efficiency</th> |
|||
<th>Hashrate</th> |
|||
</tr> |
|||
</thead> |
|||
{{ for(var worker in it.stats.pools[pool].miners) { }} |
|||
{{var workerstat = it.stats.pools[pool].miners[worker];}} |
|||
<tr class="pure-table-odd"> |
|||
<td><a href="/workers/{{=worker.split('.')[0]}}">{{=worker.length > 40 ? worker.substring(0, 20) + '...' + worker.substring(worker.length-20, worker.length): worker}}</a></td> |
|||
<td>{{=Math.round(workerstat.currRoundShares * 100) / 100}}</td> |
|||
<td>{{? workerstat.shares > 0}} {{=Math.floor(10000 * workerstat.shares / (workerstat.shares + workerstat.invalidshares)) / 100}}% {{??}} 0% {{?}}</td> |
|||
<td>{{=workerstat.hashrateString}}</td> |
|||
</tr> |
|||
{{ } }} |
|||
</table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{{ } }} |
@ -0,0 +1,100 @@ |
|||
var docCookies = { |
|||
getItem: function (sKey) { |
|||
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null; |
|||
}, |
|||
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) { |
|||
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; } |
|||
var sExpires = ""; |
|||
if (vEnd) { |
|||
switch (vEnd.constructor) { |
|||
case Number: |
|||
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd; |
|||
break; |
|||
case String: |
|||
sExpires = "; expires=" + vEnd; |
|||
break; |
|||
case Date: |
|||
sExpires = "; expires=" + vEnd.toUTCString(); |
|||
break; |
|||
} |
|||
} |
|||
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : ""); |
|||
return true; |
|||
}, |
|||
removeItem: function (sKey, sPath, sDomain) { |
|||
if (!sKey || !this.hasItem(sKey)) { return false; } |
|||
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : ""); |
|||
return true; |
|||
}, |
|||
hasItem: function (sKey) { |
|||
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie); |
|||
} |
|||
}; |
|||
|
|||
var password = docCookies.getItem('password'); |
|||
|
|||
|
|||
function showLogin(){ |
|||
$('#adminCenter').hide(); |
|||
$('#passwordForm').show(); |
|||
} |
|||
|
|||
function showAdminCenter(){ |
|||
$('#passwordForm').hide(); |
|||
$('#adminCenter').show(); |
|||
} |
|||
|
|||
function tryLogin(){ |
|||
apiRequest('pools', {}, function(response){ |
|||
showAdminCenter(); |
|||
displayMenu(response.result) |
|||
}); |
|||
} |
|||
|
|||
function displayMenu(pools){ |
|||
$('#poolList').after(Object.keys(pools).map(function(poolName){ |
|||
return '<li class="poolMenuItem"><a href="#">' + poolName + '</a></li>'; |
|||
}).join('')); |
|||
} |
|||
|
|||
function apiRequest(func, data, callback){ |
|||
var httpRequest = new XMLHttpRequest(); |
|||
httpRequest.onreadystatechange = function(){ |
|||
if (httpRequest.readyState === 4 && httpRequest.responseText){ |
|||
if (httpRequest.status === 401){ |
|||
docCookies.removeItem('password'); |
|||
$('#password').val(''); |
|||
showLogin(); |
|||
alert('Incorrect Password'); |
|||
} |
|||
else{ |
|||
var response = JSON.parse(httpRequest.responseText); |
|||
callback(response); |
|||
} |
|||
} |
|||
}; |
|||
httpRequest.open('POST', '/api/admin/' + func); |
|||
data.password = password; |
|||
httpRequest.setRequestHeader('Content-Type', 'application/json'); |
|||
httpRequest.send(JSON.stringify(data)); |
|||
} |
|||
|
|||
if (password){ |
|||
tryLogin(); |
|||
} |
|||
else{ |
|||
showLogin(); |
|||
} |
|||
|
|||
$('#passwordForm').submit(function(event){ |
|||
event.preventDefault(); |
|||
password = $('#password').val(); |
|||
if (password){ |
|||
if ($('#remember').is(':checked')) |
|||
docCookies.setItem('password', password, Infinity); |
|||
else |
|||
docCookies.setItem('password', password); |
|||
tryLogin(); |
|||
} |
|||
return false; |
|||
}); |
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 797 B |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,30 @@ |
|||
$(function(){ |
|||
|
|||
var hotSwap = function(page, pushSate){ |
|||
if (pushSate) history.pushState(null, null, '/' + page); |
|||
$('.pure-menu-selected').removeClass('pure-menu-selected'); |
|||
$('a[href="/' + page + '"]').parent().addClass('pure-menu-selected'); |
|||
$.get("/get_page", {id: page}, function(data){ |
|||
$('main').html(data); |
|||
}, 'html') |
|||
}; |
|||
|
|||
$('.hot-swapper').click(function(event){ |
|||
if (event.which !== 1) return; |
|||
var pageId = $(this).attr('href').slice(1); |
|||
hotSwap(pageId, true); |
|||
event.preventDefault(); |
|||
return false; |
|||
}); |
|||
|
|||
window.addEventListener('load', function() { |
|||
setTimeout(function() { |
|||
window.addEventListener("popstate", function(e) { |
|||
hotSwap(location.pathname.slice(1)); |
|||
}); |
|||
}, 0); |
|||
}); |
|||
|
|||
window.statsSource = new EventSource("/api/live_stats"); |
|||
|
|||
}); |
@ -0,0 +1,246 @@ |
|||
var workerHashrateData; |
|||
var workerHashrateChart; |
|||
var workerHistoryMax = 160; |
|||
|
|||
var statData; |
|||
var totalHash; |
|||
var totalImmature; |
|||
var totalBal; |
|||
var totalPaid; |
|||
var totalShares; |
|||
|
|||
function getReadableHashRateString(hashrate){ |
|||
hashrate = (hashrate * 2); |
|||
if (hashrate < 1000000) { |
|||
return (Math.round(hashrate / 1000) / 1000 ).toFixed(2)+' Sol/s'; |
|||
} |
|||
var byteUnits = [ ' Sol/s', ' KSol/s', ' MSol/s', ' GSol/s', ' TSol/s', ' PSol/s' ]; |
|||
var i = Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1); |
|||
hashrate = (hashrate/1000) / Math.pow(1000, i + 1); |
|||
return hashrate.toFixed(2) + byteUnits[i]; |
|||
} |
|||
|
|||
function timeOfDayFormat(timestamp){ |
|||
var dStr = d3.time.format('%I:%M %p')(new Date(timestamp)); |
|||
if (dStr.indexOf('0') === 0) dStr = dStr.slice(1); |
|||
return dStr; |
|||
} |
|||
|
|||
function getWorkerNameFromAddress(w) { |
|||
var worker = w; |
|||
if (w.split(".").length > 1) { |
|||
worker = w.split(".")[1]; |
|||
if (worker == null || worker.length < 1) { |
|||
worker = "noname"; |
|||
} |
|||
} else { |
|||
worker = "noname"; |
|||
} |
|||
return worker; |
|||
} |
|||
|
|||
function buildChartData(){ |
|||
var workers = {}; |
|||
for (var w in statData.history) { |
|||
var worker = getWorkerNameFromAddress(w); |
|||
var a = workers[worker] = (workers[worker] || { |
|||
hashrate: [] |
|||
}); |
|||
for (var wh in statData.history[w]) { |
|||
a.hashrate.push([statData.history[w][wh].time * 1000, statData.history[w][wh].hashrate]); |
|||
} |
|||
if (a.hashrate.length > workerHistoryMax) { |
|||
workerHistoryMax = a.hashrate.length; |
|||
} |
|||
} |
|||
|
|||
var i=0; |
|||
workerHashrateData = []; |
|||
for (var worker in workers){ |
|||
workerHashrateData.push({ |
|||
key: worker, |
|||
disabled: (i > Math.min((_workerCount-1), 3)), |
|||
values: workers[worker].hashrate |
|||
}); |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
function updateChartData(){ |
|||
var workers = {}; |
|||
for (var w in statData.history) { |
|||
var worker = getWorkerNameFromAddress(w); |
|||
// get a reference to lastest workerhistory
|
|||
for (var wh in statData.history[w]) { } |
|||
//var wh = statData.history[w][statData.history[w].length - 1];
|
|||
var foundWorker = false; |
|||
for (var i = 0; i < workerHashrateData.length; i++) { |
|||
if (workerHashrateData[i].key === worker) { |
|||
foundWorker = true; |
|||
if (workerHashrateData[i].values.length >= workerHistoryMax) { |
|||
workerHashrateData[i].values.shift(); |
|||
} |
|||
workerHashrateData[i].values.push([statData.history[w][wh].time * 1000, statData.history[w][wh].hashrate]); |
|||
break; |
|||
} |
|||
} |
|||
if (!foundWorker) { |
|||
var hashrate = []; |
|||
hashrate.push([statData.history[w][wh].time * 1000, statData.history[w][wh].hashrate]); |
|||
workerHashrateData.push({ |
|||
key: worker, |
|||
values: hashrate |
|||
}); |
|||
rebuildWorkerDisplay(); |
|||
return true; |
|||
} |
|||
} |
|||
triggerChartUpdates(); |
|||
return false; |
|||
} |
|||
|
|||
function calculateAverageHashrate(worker) { |
|||
var count = 0; |
|||
var total = 1; |
|||
var avg = 0; |
|||
for (var i = 0; i < workerHashrateData.length; i++) { |
|||
count = 0; |
|||
for (var ii = 0; ii < workerHashrateData[i].values.length; ii++) { |
|||
if (worker == null || workerHashrateData[i].key === worker) { |
|||
count++; |
|||
avg += parseFloat(workerHashrateData[i].values[ii][1]); |
|||
} |
|||
} |
|||
if (count > total) |
|||
total = count; |
|||
} |
|||
avg = avg / total; |
|||
return avg; |
|||
} |
|||
|
|||
function triggerChartUpdates(){ |
|||
workerHashrateChart.update(); |
|||
} |
|||
|
|||
function displayCharts() { |
|||
nv.addGraph(function() { |
|||
workerHashrateChart = nv.models.lineChart() |
|||
.margin({left: 80, right: 30}) |
|||
.x(function(d){ return d[0] }) |
|||
.y(function(d){ return d[1] }) |
|||
.useInteractiveGuideline(true); |
|||
|
|||
workerHashrateChart.xAxis.tickFormat(timeOfDayFormat); |
|||
|
|||
workerHashrateChart.yAxis.tickFormat(function(d){ |
|||
return getReadableHashRateString(d); |
|||
}); |
|||
d3.select('#workerHashrate').datum(workerHashrateData).call(workerHashrateChart); |
|||
return workerHashrateChart; |
|||
}); |
|||
} |
|||
|
|||
function updateStats() { |
|||
totalHash = statData.totalHash; |
|||
totalPaid = statData.paid; |
|||
totalBal = statData.balance; |
|||
totalImmature = statData.immature; |
|||
totalShares = statData.totalShares; |
|||
// do some calculations
|
|||
var _blocktime = 250; |
|||
var _networkHashRate = parseFloat(statData.networkSols) * 1.2; |
|||
var _myHashRate = (totalHash / 1000000) * 2; |
|||
var luckDays = ((_networkHashRate / _myHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); |
|||
// update miner stats
|
|||
$("#statsHashrate").text(getReadableHashRateString(totalHash)); |
|||
$("#statsHashrateAvg").text(getReadableHashRateString(calculateAverageHashrate(null))); |
|||
$("#statsLuckDays").text(luckDays); |
|||
$("#statsTotalImmature").text(totalImmature); |
|||
$("#statsTotalBal").text(totalBal); |
|||
$("#statsTotalPaid").text(totalPaid); |
|||
$("#statsTotalShares").text(totalShares.toFixed(2)); |
|||
} |
|||
function updateWorkerStats() { |
|||
// update worker stats
|
|||
var i=0; |
|||
for (var w in statData.workers) { i++; |
|||
var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, ''); |
|||
var saneWorkerName = getWorkerNameFromAddress(w); |
|||
$("#statsHashrate"+htmlSafeWorkerName).text(getReadableHashRateString(statData.workers[w].hashrate)); |
|||
$("#statsHashrateAvg"+htmlSafeWorkerName).text(getReadableHashRateString(calculateAverageHashrate(saneWorkerName))); |
|||
$("#statsLuckDays"+htmlSafeWorkerName).text(statData.workers[w].luckDays); |
|||
$("#statsPaid"+htmlSafeWorkerName).text(statData.workers[w].paid); |
|||
$("#statsBalance"+htmlSafeWorkerName).text(statData.workers[w].balance); |
|||
$("#statsShares"+htmlSafeWorkerName).text(Math.round(statData.workers[w].currRoundShares * 100) / 100); |
|||
$("#statsDiff"+htmlSafeWorkerName).text(statData.workers[w].diff); |
|||
} |
|||
} |
|||
function addWorkerToDisplay(name, htmlSafeName, workerObj) { |
|||
var htmlToAdd = ""; |
|||
htmlToAdd = '<div class="boxStats" id="boxStatsLeft" style="float:left; margin: 9px; min-width: 260px;"><div class="boxStatsList">'; |
|||
if (htmlSafeName.indexOf("_") >= 0) { |
|||
htmlToAdd+= '<div class="boxLowerHeader">'+htmlSafeName.substr(htmlSafeName.indexOf("_")+1,htmlSafeName.length)+'</div>'; |
|||
} else { |
|||
htmlToAdd+= '<div class="boxLowerHeader">noname</div>'; |
|||
} |
|||
htmlToAdd+='<div><i class="fa fa-tachometer"></i> <span id="statsHashrate'+htmlSafeName+'">'+getReadableHashRateString(workerObj.hashrate)+'</span> (Now)</div>'; |
|||
htmlToAdd+='<div><i class="fa fa-tachometer"></i> <span id="statsHashrateAvg'+htmlSafeName+'">'+getReadableHashRateString(calculateAverageHashrate(name))+'</span> (Avg)</div>'; |
|||
htmlToAdd+='<div><i class="fa fa-shield"></i> <small>Diff:</small> <span id="statsDiff'+htmlSafeName+'">'+workerObj.diff+'</span></div>'; |
|||
htmlToAdd+='<div><i class="fa fa-cog"></i> <small>Shares:</small> <span id="statsShares'+htmlSafeName+'">'+(Math.round(workerObj.currRoundShares * 100) / 100)+'</span></div>'; |
|||
htmlToAdd+='<div><i class="fa fa-gavel"></i> <small>Luck <span id="statsLuckDays'+htmlSafeName+'">'+workerObj.luckDays+'</span> Days</small></div>'; |
|||
htmlToAdd+='<div><i class="fa fa-money"></i> <small>Bal: <span id="statsBalance'+htmlSafeName+'">'+workerObj.balance+'</span></small></div>'; |
|||
htmlToAdd+='<div><i class="fa fa-money"></i> <small>Paid: <span id="statsPaid'+htmlSafeName+'">'+workerObj.paid+'</span></small></div>'; |
|||
htmlToAdd+='</div></div></div>'; |
|||
$("#boxesWorkers").html($("#boxesWorkers").html()+htmlToAdd); |
|||
} |
|||
|
|||
function rebuildWorkerDisplay() { |
|||
$("#boxesWorkers").html(""); |
|||
var i=0; |
|||
for (var w in statData.workers) { i++; |
|||
var htmlSafeWorkerName = w.split('.').join('_').replace(/[^\w\s]/gi, ''); |
|||
var saneWorkerName = getWorkerNameFromAddress(w); |
|||
addWorkerToDisplay(saneWorkerName, htmlSafeWorkerName, statData.workers[w]); |
|||
} |
|||
} |
|||
|
|||
// resize chart on window resize
|
|||
nv.utils.windowResize(triggerChartUpdates); |
|||
|
|||
// grab initial stats
|
|||
$.getJSON('/api/worker_stats?'+_miner, function(data){ |
|||
statData = data; |
|||
for (var w in statData.workers) { _workerCount++; } |
|||
buildChartData(); |
|||
displayCharts(); |
|||
rebuildWorkerDisplay(); |
|||
updateStats(); |
|||
}); |
|||
|
|||
// live stat updates
|
|||
statsSource.addEventListener('message', function(e){ |
|||
// TODO, create miner_live_stats...
|
|||
// miner_live_stats will return the same josn except without the worker history
|
|||
// FOR NOW, use this to grab updated stats
|
|||
$.getJSON('/api/worker_stats?'+_miner, function(data){ |
|||
statData = data; |
|||
// check for missing workers
|
|||
var wc = 0; |
|||
var rebuilt = false; |
|||
// update worker stats
|
|||
for (var w in statData.workers) { wc++; } |
|||
// TODO, this isn't 100% fool proof!
|
|||
if (_workerCount != wc) { |
|||
if (_workerCount > wc) { |
|||
rebuildWorkerDisplay(); |
|||
rebuilt = true; |
|||
} |
|||
_workerCount = wc; |
|||
} |
|||
rebuilt = (rebuilt || updateChartData()); |
|||
updateStats(); |
|||
if (!rebuilt) { |
|||
updateWorkerStats(); |
|||
} |
|||
}); |
|||
}); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,143 @@ |
|||
var poolHashrateData; |
|||
var poolHashrateChart; |
|||
|
|||
var statData; |
|||
var poolKeys; |
|||
|
|||
function buildChartData(){ |
|||
var pools = {}; |
|||
|
|||
poolKeys = []; |
|||
for (var i = 0; i < statData.length; i++){ |
|||
for (var pool in statData[i].pools){ |
|||
if (poolKeys.indexOf(pool) === -1) |
|||
poolKeys.push(pool); |
|||
} |
|||
} |
|||
|
|||
for (var i = 0; i < statData.length; i++) { |
|||
var time = statData[i].time * 1000; |
|||
for (var f = 0; f < poolKeys.length; f++){ |
|||
var pName = poolKeys[f]; |
|||
var a = pools[pName] = (pools[pName] || { |
|||
hashrate: [] |
|||
}); |
|||
if (pName in statData[i].pools){ |
|||
a.hashrate.push([time, statData[i].pools[pName].hashrate]); |
|||
} |
|||
else{ |
|||
a.hashrate.push([time, 0]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
poolHashrateData = []; |
|||
for (var pool in pools){ |
|||
poolHashrateData.push({ |
|||
key: pool, |
|||
values: pools[pool].hashrate |
|||
}); |
|||
$('#statsHashrateAvg' + pool).text(getReadableHashRateString(calculateAverageHashrate(pool))); |
|||
} |
|||
} |
|||
|
|||
function calculateAverageHashrate(pool) { |
|||
var count = 0; |
|||
var total = 1; |
|||
var avg = 0; |
|||
for (var i = 0; i < poolHashrateData.length; i++) { |
|||
count = 0; |
|||
for (var ii = 0; ii < poolHashrateData[i].values.length; ii++) { |
|||
if (pool == null || poolHashrateData[i].key === pool) { |
|||
count++; |
|||
avg += parseFloat(poolHashrateData[i].values[ii][1]); |
|||
} |
|||
} |
|||
if (count > total) |
|||
total = count; |
|||
} |
|||
avg = avg / total; |
|||
return avg; |
|||
} |
|||
|
|||
function getReadableHashRateString(hashrate){ |
|||
hashrate = (hashrate * 2); |
|||
if (hashrate < 1000000) { |
|||
return (Math.round(hashrate / 1000) / 1000 ).toFixed(2)+' Sol/s'; |
|||
} |
|||
var byteUnits = [ ' Sol/s', ' KSol/s', ' MSol/s', ' GSol/s', ' TSol/s', ' PSol/s' ]; |
|||
var i = Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1); |
|||
hashrate = (hashrate/1000) / Math.pow(1000, i + 1); |
|||
return hashrate.toFixed(2) + byteUnits[i]; |
|||
} |
|||
|
|||
function timeOfDayFormat(timestamp){ |
|||
var dStr = d3.time.format('%I:%M %p')(new Date(timestamp)); |
|||
if (dStr.indexOf('0') === 0) dStr = dStr.slice(1); |
|||
return dStr; |
|||
} |
|||
|
|||
function displayCharts(){ |
|||
nv.addGraph(function() { |
|||
poolHashrateChart = nv.models.lineChart() |
|||
.margin({left: 80, right: 30}) |
|||
.x(function(d){ return d[0] }) |
|||
.y(function(d){ return d[1] }) |
|||
.useInteractiveGuideline(true); |
|||
|
|||
poolHashrateChart.xAxis.tickFormat(timeOfDayFormat); |
|||
|
|||
poolHashrateChart.yAxis.tickFormat(function(d){ |
|||
return getReadableHashRateString(d); |
|||
}); |
|||
|
|||
d3.select('#poolHashrate').datum(poolHashrateData).call(poolHashrateChart); |
|||
|
|||
return poolHashrateChart; |
|||
}); |
|||
} |
|||
|
|||
function triggerChartUpdates(){ |
|||
poolHashrateChart.update(); |
|||
} |
|||
|
|||
nv.utils.windowResize(triggerChartUpdates); |
|||
|
|||
$.getJSON('/api/pool_stats', function(data){ |
|||
statData = data; |
|||
buildChartData(); |
|||
displayCharts(); |
|||
}); |
|||
|
|||
statsSource.addEventListener('message', function(e){ |
|||
var stats = JSON.parse(e.data); |
|||
statData.push(stats); |
|||
|
|||
var newPoolAdded = (function(){ |
|||
for (var p in stats.pools){ |
|||
if (poolKeys.indexOf(p) === -1) |
|||
return true; |
|||
} |
|||
return false; |
|||
})(); |
|||
|
|||
if (newPoolAdded || Object.keys(stats.pools).length > poolKeys.length){ |
|||
buildChartData(); |
|||
displayCharts(); |
|||
} |
|||
else { |
|||
var time = stats.time * 1000; |
|||
for (var f = 0; f < poolKeys.length; f++) { |
|||
var pool = poolKeys[f]; |
|||
for (var i = 0; i < poolHashrateData.length; i++) { |
|||
if (poolHashrateData[i].key === pool) { |
|||
poolHashrateData[i].values.shift(); |
|||
poolHashrateData[i].values.push([time, pool in stats.pools ? stats.pools[pool].hashrate : 0]); |
|||
$('#statsHashrateAvg' + pool).text(getReadableHashRateString(calculateAverageHashrate(pool))); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
triggerChartUpdates(); |
|||
} |
|||
}); |
@ -0,0 +1,68 @@ |
|||
html, button, input, select, textarea, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"]{ |
|||
font-family: 'Open Sans', sans-serif; |
|||
} |
|||
|
|||
html{ |
|||
background: #2d2d2d; |
|||
overflow-y: scroll; |
|||
} |
|||
|
|||
body{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
max-width: 1160px; |
|||
margin: 0 auto; |
|||
} |
|||
|
|||
header > .home-menu{ |
|||
background: inherit !important; |
|||
height: 54px; |
|||
display: flex; |
|||
} |
|||
|
|||
header > .home-menu > a.pure-menu-heading, header > .home-menu > ul, header > .home-menu > ul > li{ |
|||
display: flex !important; |
|||
align-items: center; |
|||
justify-content: center; |
|||
line-height: normal !important; |
|||
} |
|||
|
|||
header > .home-menu > a.pure-menu-heading{ |
|||
color: white; |
|||
font-size: 1.5em; |
|||
} |
|||
|
|||
header > .home-menu > ul > li > a{ |
|||
color: #ced4d9; |
|||
} |
|||
|
|||
header > .home-menu > ul > li > a:hover, header > .home-menu > ul > li > a:focus{ |
|||
background: inherit !important; |
|||
} |
|||
|
|||
header > .home-menu > ul > li > a:hover, header > .home-menu > ul > li.pure-menu-selected > a{ |
|||
color: white; |
|||
} |
|||
|
|||
main{ |
|||
background-color: #ebf4fa; |
|||
position: relative; |
|||
} |
|||
|
|||
footer{ |
|||
text-align: center; |
|||
color: #b3b3b3; |
|||
text-decoration: none; |
|||
font-size: 0.8em; |
|||
padding: 15px; |
|||
line-height: 24px; |
|||
} |
|||
|
|||
footer a{ |
|||
color: #fff; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
footer iframe{ |
|||
vertical-align: middle; |
|||
} |
Loading…
Reference in new issue