Files
peya-nodejs-pool/website_example/pages/home.html
Codex Bot 9fa0178d26
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 26s
Simplify pool homepage overview layout
2026-03-24 12:40:10 +01:00

729 lines
27 KiB
HTML

<section class="page-hero">
<div class="page-hero-copy">
<span class="eyebrow">Peya Merge-Mining Pool</span>
<h3>Live visibility into pool performance, parent-chain state, and payout flow.</h3>
<p class="hero-copy-text">
This dashboard keeps the legacy pool API, but presents it in the same visual language as the explorer:
clearer cards, calmer contrast, and direct paths to connect miners or inspect found blocks.
</p>
<div class="input-group hero-lookup-group">
<input class="form-control" id="heroMinerAddress" type="text" placeholder="Enter wallet address to open worker statistics">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="heroMinerLookup">
<i class="fa fa-search"></i> View Worker Statistics
</button>
</span>
</div>
<div class="hero-actions">
<a class="btn btn-default home-scroll" href="#overview-getting-started">
<i class="fa fa-rocket"></i> <span data-tkey="gettingStarted">Getting Started</span>
</a>
<a class="btn btn-primary home-scroll" href="#overview-pool-blocks">
<i class="fa fa-cubes"></i> <span data-tkey="poolBlocks">Pool Blocks</span>
</a>
<a class="btn btn-primary home-scroll" href="#overview-faq">
<i class="fa fa-comments"></i> FAQ
</a>
</div>
</div>
</section>
<section class="section-block" id="overview-pool-blocks">
<div class="section-head">
<div>
<span class="eyebrow">Overview</span>
<h4>Core metrics and chain context</h4>
</div>
</div>
<div id="poolStats" class="row dashboard-metrics">
<div id="mainPoolStats"></div>
<div id="networkStats"></div>
<div id="poolDetails"></div>
</div>
</section>
<section class="section-block">
<div class="section-head">
<div>
<span class="eyebrow">Realtime</span>
<h4>Operational charts</h4>
</div>
</div>
<div class="row">
<div class="col-sm-6 poolChart push-up-15">
<h4><span data-tkey="hashRate">Hash Rate</span></h4>
<div id="chartHashrate" class="card" data-chart="hashrate">
<div class="chart"></div>
</div>
</div>
<div class="col-sm-6 poolChart push-up-15">
<h4><span data-tkey="difficulty">Difficulty</span></h4>
<div id="chartDifficulty" class="card" data-chart="diff">
<div class="chart"></div>
</div>
</div>
<div class="col-sm-6 poolChart push-up-15">
<h4><span data-tkey="miners">Miners</span></h4>
<div id="chartMiners" class="card" data-chart="miners">
<div class="chart"></div>
</div>
</div>
<div class="col-sm-6 poolChart push-up-15">
<h4><span data-tkey="workers">Workers</span></h4>
<div id="chartWorkers" class="card" data-chart="workers">
<div class="chart"></div>
</div>
</div>
</div>
</section>
<section class="section-block">
<div class="section-head">
<div>
<span class="eyebrow">Recent pool blocks</span>
<h4>Latest found blocks</h4>
</div>
<a class="panel-link hot_link" data-page="pool_blocks.html" href="#pool_blocks">View all</a>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Time Found</th>
<th>Height</th>
<th>Reward</th>
<th>Hash</th>
<th>Status</th>
</tr>
</thead>
<tbody id="homePoolBlocksRows">
</tbody>
</table>
</div>
</div>
</section>
<section class="section-block preview-grid">
<div>
<div class="section-head">
<div>
<span class="eyebrow">Recent payouts</span>
<h4>Latest payment ledger</h4>
</div>
<a class="panel-link hot_link" data-page="payments.html" href="#payments">View all</a>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Date</th>
<th>Hash</th>
<th>Amount</th>
<th>Payees</th>
</tr>
</thead>
<tbody id="homePaymentsRows">
</tbody>
</table>
</div>
</div>
</div>
<div>
<div class="section-head">
<div>
<span class="eyebrow">Top miners</span>
<h4>Current leaders</h4>
</div>
<a class="panel-link hot_link" data-page="top10miners.html" href="#top10miners">View all</a>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>#</th>
<th>Miner</th>
<th>Hashrate</th>
<th>Last Share</th>
</tr>
</thead>
<tbody id="homeTopMinersRows">
</tbody>
</table>
</div>
</div>
</div>
</section>
<section class="section-block">
<div class="section-head">
<div>
<span class="eyebrow">Dual merge-mined blocks</span>
<h4>Accepted on aux and parent</h4>
</div>
<a class="panel-link hot_link" data-page="dual_mm_blocks.html" href="#dual_mm_blocks">View all</a>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Time Found</th>
<th>Height</th>
<th>Hash</th>
<th>Miner</th>
<th>Mode</th>
</tr>
</thead>
<tbody id="homeDualMmRows">
</tbody>
</table>
</div>
</div>
</section>
<section class="section-block" id="overview-getting-started">
<div class="section-head">
<div>
<span class="eyebrow">Getting started</span>
<h4>Connect a miner fast</h4>
</div>
</div>
<div class="preview-grid">
<div class="card getting-started-card">
<div class="getting-started-inner">
<h4>Connection details</h4>
<div class="stats">
<div><i class="fa fa-cloud"></i> Pool host: <span id="homeMiningPoolHost"></span></div>
<div><i class="fa fa-cubes"></i> Algorithm: <span id="homeCnAlgorithm"></span></div>
<div><i class="fa fa-key"></i> Username: your parent-chain wallet address</div>
<div><i class="fa fa-server"></i> Password: optional worker name or miner-specific settings</div>
<div><i class="fa fa-wrench"></i> Fixed diff: <code>wallet.diff</code> or worker naming via <code>@worker</code></div>
</div>
</div>
</div>
<div class="card getting-started-card">
<div class="getting-started-inner">
<h4>Mining ports</h4>
<div class="table-responsive">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Port</th>
<th>Starting difficulty</th>
<th>Description</th>
</tr>
</thead>
<tbody id="homeMiningPortsRows">
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<section class="section-block" id="overview-faq">
<div class="section-head">
<div>
<span class="eyebrow">FAQ</span>
<h4>Operational basics</h4>
</div>
</div>
<div class="preview-grid faq-grid">
<div class="card faq-card"><div class="getting-started-inner"><h4>What is a share?</h4><p>A share proves your miner is doing useful work even when it does not find a full block.</p></div></div>
<div class="card faq-card"><div class="getting-started-inner"><h4>What is luck?</h4><p>Luck compares actual effort to expected effort. Below 100% means the pool found a block faster than statistically expected.</p></div></div>
<div class="card faq-card"><div class="getting-started-inner"><h4>Why no payout yet?</h4><p>Rewards must first mature on-chain and then exceed the configured payout threshold before they are sent.</p></div></div>
<div class="card faq-card"><div class="getting-started-inner"><h4>What is solo mode?</h4><p>Solo mining uses the same pool infrastructure but aims for full-block wins instead of proportional round sharing.</p></div></div>
</div>
</section>
<script id="mainPoolTemplate" type="text/x-handlebars-template">
<!-- Pool Hash Rate -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-dashboard"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="poolHashrate">PROP/SOLO Pool Hash Rate</span></div>
<div class="value"><span id="poolHashrate" class="smallText">N/A</span> <span class="smallText">(<span id="hashPower">0%</span>)</span></div>
<div class="value"><span id="poolHashrateSolo" class="smallText">N/A</span> <span class="smallText">(<span id="hashPowerSolo">0%</span>)</span></div>
</div>
</div>
</div>
<!-- Blocks Found -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect" id="infoBlocksTotal">
<div class="icon">
<span class="fa fa-cubes"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="blocksTotal">Blocks Found</span> (PROP/SOLO)</div>
<div class="value">
{{#blocks}}
<span class="smallText" id="blocksTotal{{coin}}">{{blocks}}/{{blocksSolo}}</span><sup>{{symbol}}</sup>&nbsp;
{{/blocks}}
</div>
</div>
</div>
</div>
<!-- Blocks Found Every -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect" id="infoBlocksFound">
<div class="icon">
<span class="fa fa-history"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="blockSolvedTime">Blocks Found Every</span></div>
<div class="value">
<span class="smallText" id="blockSolvedTime">N/A</span> <span class="smallText">(<span class="smallText">estimated</span>)</span>
</div>
<div class="value">
<span class="smallText">LAST BLOCK: <span id="poolLastBlockFound">Never</span>
</div>
</div>
</div>
</div>
<!-- Current Effort -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-line-chart"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="currentEffort">Current Effort</span></div>
<div class="value">
{{#efforts}}
<span class="smallText" id="currentEffort{{coin}}">{{effort}}</span><sup>{{symbol}}</sup>&nbsp;
{{/efforts}}
</div>
</div>
</div>
</div>
</script>
<script id="siblingTemplate" type="text/x-handlebars-template">
<div id="networkStats{{coin}}">
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-dashboard"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="networkHashrate">Network Hash Rate</span> ({{symbol}})</div>
<div class="value"><span id="networkHashrate{{coin}}">N/A</span></div>
</div>
</div>
</div>
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-unlock-alt"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="networkDifficulty">Difficulty</span> ({{symbol}})</div>
<div class="value"><span id="networkDifficulty{{coin}}">N/A</span></div>
</div>
</div>
</div>
<!-- Blockchain Height -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-bars"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="blockchainHeight">Blockchain Height</span> ({{symbol}})</div>
<div class="value"><span id="blockchainHeight{{coin}}">N/A</span></div>
</div>
</div>
</div>
<!-- Last Reward -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-dollar"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="networkLastReward">Last Reward</span> ({{symbol}})</div>
<div class="value"><span id="networkLastReward{{coin}}">N/A</span></div>
</div>
</div>
</div>
</div>
</script>
<script id="poolDetailTemplate" type="text/x-handlebars-template">
<!-- Connected Miners -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-group"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="propSoloConnectedMiners">Connected Miners</span> (PROP/SOLO)</div>
<div class="value">
{{#blocks}}
<span class="smallText" id="poolMiners{{coin}}">{{miners}}/{{minersSolo}}</span><sup>{{symbol}}</sup>&nbsp;
{{/blocks}}
</div>
</div>
</div>
</div>
<!-- Pool Fee -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-percent"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="poolFee">Pool Fee</span> (PROP/SOLO)</div>
<div class="value"><span id="poolFee" class="smallText">N/A</span><sup>{{symbol}}</sup></div>
</div>
</div>
</div>
<!-- Finder Reward -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-percent"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="finderReward">Finder Reward</span></div>
<div class="value"><span id="finderReward">N/A</span><sup>{{symbol}}</sup></div>
</div>
</div>
</div>
<!-- Minimum Payout -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-dollar"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="paymentsMinimum">Minimum Payout</span></div>
<div class="value"><span id="paymentsMinimum">N/A</span></div>
</div>
</div>
</div>
<!-- Payment Interval -->
<div class="col-lg-3 col-sm-4">
<div class="infoBox hoverExpandEffect">
<div class="icon">
<span class="fa fa-clock-o"></span>
</div>
<div class="content">
<div class="text"><span data-tkey="paymentsInterval">Payment Interval</span></div>
<div class="value"><span id="paymentsInterval">N/A</span></div>
</div>
</div>
</div>
<!-- Last Hash -->
<div class="col-sm-12">
<div class="hashInfo hoverExpandEffect">
<div class="text"><span data-tkey="lastHash">Last Hash</span></div>
<div class="content clearfix">
<div class="value"><a id="lastHash" target="_blank">N/A</a></div>
<div class="time">(<span id="networkLastBlockFound">Never</span>)</div>
</div>
</div>
</div>
</script>
<!-- Javascript -->
<script>
// Update current page
let lastBlockFound = null
let xhrHomeTopMiners = null;
let xhrHomeDualMm = null;
function home_RenderEmptyRow(target, colspan, message) {
$(target).html('<tr><td colspan="' + colspan + '" class="text-center">' + message + '</td></tr>');
}
function home_RenderPoolBlocks(stats) {
let rows = $('#homePoolBlocksRows');
rows.empty();
if (!stats || !stats.pool || !stats.pool.blocks || !stats.pool.blocks.length) {
home_RenderEmptyRow(rows, 5, 'No pool blocks yet.');
return;
}
let rendered = 0;
for (let i = 0; i < stats.pool.blocks.length && rendered < 5; i += 2) {
let block = poolBlocks_ParseBlock(stats.pool.blocks[i + 1], stats.pool.blocks[i], stats);
let statusLabel = block.status;
rows.append(
'<tr>' +
'<td>' + formatDate(block.time) + '</td>' +
'<td>' + block.height + '</td>' +
'<td>' + (typeof block.reward === 'undefined' ? 'Waiting...' : getReadableCoin(stats, block.reward, null, true)) + '</td>' +
'<td>' + '<a target="_blank" href="' + getBlockchainUrl(block.hash, stats) + '">' + block.hash + '</a>' + '</td>' +
'<td><span class="badge-mm-inline badge-status-' + statusLabel + '">' + statusLabel + '</span></td>' +
'</tr>'
);
rendered++;
}
}
function home_RenderPayments(stats) {
let rows = $('#homePaymentsRows');
rows.empty();
if (!stats || !stats.pool || !stats.pool.payments || !stats.pool.payments.length) {
home_RenderEmptyRow(rows, 4, 'No payouts yet.');
return;
}
let rendered = 0;
for (let i = 0; i < stats.pool.payments.length && rendered < 5; i += 2) {
let payment = payments_ParsePayment(stats.pool.payments[i + 1], stats.pool.payments[i]);
rows.append(
'<tr>' +
'<td>' + formatDate(payment.time) + '</td>' +
'<td>' + formatPaymentLink(payment.hash, stats) + '</td>' +
'<td>' + getReadableCoin(stats, payment.amount) + '</td>' +
'<td>' + payment.recipients + '</td>' +
'</tr>'
);
rendered++;
}
}
function home_RenderTopMiners(data) {
let rows = $('#homeTopMinersRows');
rows.empty();
if (!data || !data.length) {
home_RenderEmptyRow(rows, 4, 'No miner data yet.');
return;
}
for (let i = 0; i < Math.min(data.length, 5); i++) {
let miner = data[i];
rows.append(
'<tr>' +
'<td>' + (i + 1) + '</td>' +
'<td>' + miner.miner + '</td>' +
'<td>' + getReadableHashRateString(miner.hashrate || 0) + '/sec</td>' +
'<td>' + (miner.lastShare ? $.timeago(new Date(parseInt(miner.lastShare) * 1000).toISOString()) : 'Never') + '</td>' +
'</tr>'
);
}
}
function home_RenderDualMm(data) {
let rows = $('#homeDualMmRows');
rows.empty();
let items = ((data && data.items) ? data.items : []).filter(function(item) {
return item.mmClassification === 'dual-mm' || (item.auxAccepted && item.parentAccepted);
}).slice(0, 5);
if (!items.length) {
home_RenderEmptyRow(rows, 5, 'No dual-mm blocks observed yet.');
return;
}
items.forEach(function(item) {
rows.append(
'<tr>' +
'<td>' + formatDate(item.timestamp) + '</td>' +
'<td>' + formatNumber(item.height.toString(), ' ') + '</td>' +
'<td>' + '<a target="_blank" href="' + getBlockchainUrl(item.hash, lastStats) + '">' + item.hash + '</a>' + '</td>' +
'<td>' + item.miner + '</td>' +
'<td><span class="badge-mm-inline">dual-mm</span></td>' +
'</tr>'
);
});
}
function home_LoadTopMiners() {
if (xhrHomeTopMiners) {
xhrHomeTopMiners.abort();
}
xhrHomeTopMiners = $.ajax({
url: api + '/get_top10miners',
dataType: 'json',
cache: false,
success: function(data) {
home_RenderTopMiners(data);
},
error: function() {
home_RenderEmptyRow($('#homeTopMinersRows'), 4, 'Failed to load top miners.');
}
});
}
function home_LoadDualMm() {
if (xhrHomeDualMm) {
xhrHomeDualMm.abort();
}
xhrHomeDualMm = $.ajax({
url: api + '/get_mm_blocks',
data: { limit: 200 },
dataType: 'json',
cache: false,
success: function(data) {
home_RenderDualMm(data);
},
error: function() {
home_RenderEmptyRow($('#homeDualMmRows'), 5, 'Failed to load dual-mm blocks.');
}
});
}
function home_RenderGettingStarted(stats) {
updateText('homeMiningPoolHost', poolHost || window.location.hostname);
updateText('homeCnAlgorithm', stats.config.cnAlgorithm || 'randomx');
let rows = $('#homeMiningPortsRows');
rows.empty();
let ports = (stats.config && stats.config.ports) ? stats.config.ports : [];
if (!ports.length) {
home_RenderEmptyRow(rows, 3, 'No mining ports configured.');
return;
}
ports.forEach(function(port) {
rows.append(
'<tr>' +
'<td>' + port.port + '</td>' +
'<td>' + formatNumber(String(port.difficulty), ' ') + '</td>' +
'<td>' + (port.desc || '') + '</td>' +
'</tr>'
);
});
}
function home_HandleHeroLookup() {
let address = $('#heroMinerAddress').val().trim();
if (!address) {
$('#heroMinerAddress').focus();
return;
}
docCookies.setItem(`mining_address_${lastStats.config.coin}`, address, Infinity);
window.location.hash = '#worker_stats';
}
currentPage = {
destroy: function(){
$('#networkLastBlockFound,#poolLastBlockFound').timeago('dispose');
if (xhrHomeTopMiners) xhrHomeTopMiners.abort();
if (xhrHomeDualMm) xhrHomeDualMm.abort();
destroyApexChartsByPrefix(homeChartInstances, 'home_');
$('#heroMinerLookup').off('click');
$('#heroMinerAddress').off('keyup');
$('.home-scroll').off('click');
},
update: function(updateKey){
if (lastStats)
$('#networkLastBlockFound').timeago('update', new Date(lastStats.lastblock.timestamp * 1000).toISOString());
let stats = lastStats;
if (stats) {
home_GenerateNetworkStats(updateKey, stats.config.symbol)
if (!lastBlockFound)
$('#poolLastBlockFound').removeAttr('title').data('ts', '').update('Never')
if (stats.pool.lastBlockFound) {
let lastChildBlockFound = parseInt(stats.pool.lastBlockFound)
if (lastChildBlockFound > lastBlockFound) {
lastBlockFound = lastChildBlockFound
$('#poolLastBlockFound').timeago('update', new Date(lastBlockFound).toISOString());
}
}
updateText(`networkHashrate${updateKey}`, getReadableHashRateString(stats.network.difficulty / stats.config.coinDifficultyTarget) + '/sec');
updateText(`networkDifficulty${updateKey}`, formatNumber(stats.network.difficulty.toString(), ' '));
updateText(`blockchainHeight${updateKey}`, formatNumber(stats.network.height.toString(), ' '));
updateText(`networkLastReward${updateKey}`, getReadableCoin(stats, stats.lastblock.reward));
updateText(`poolMiners${updateKey}`, `${stats.pool.miners}/${stats.pool.minersSolo}`);
updateText(`blocksTotal${updateKey}`, `${stats.pool.totalBlocks}/${stats.pool.totalBlocksSolo}`);
updateText(`currentEffort${updateKey}`, (stats.pool.roundHashes / stats.network.difficulty * 100).toFixed(1) + '%');
let el = updateText('lastHash', lastStats.lastblock.hash)
if (el)
el.setAttribute('href', getBlockchainUrl(lastStats.lastblock.hash, lastStats));
updateText('poolHashrate', 'PROP: ' + getReadableHashRateString(lastStats.pool.hashrate) + '/sec');
updateText('poolHashrateSolo', 'SOLO: ' + getReadableHashRateString(lastStats.pool.hashrateSolo) + '/sec');
var hashPowerSolo = lastStats.pool.hashrateSolo / (lastStats.network.difficulty / lastStats.config.coinDifficultyTarget) * 100;
updateText ('hashPowerSolo', hashPowerSolo.toFixed(2) + '%');
var hashPower = lastStats.pool.hashrate / (lastStats.network.difficulty / lastStats.config.coinDifficultyTarget) * 100;
updateText('hashPower', hashPower.toFixed(2) + '%');
updateText('blockSolvedTime', getReadableTime(lastStats.network.difficulty / lastStats.pool.hashrate));
home_RenderPoolBlocks(stats);
home_RenderPayments(stats);
home_RenderGettingStarted(stats);
home_CreateCharts(stats);
}
}
};
// Enable timeago on last block found
$('#networkLastBlockFound,#poolLastBlockFound').timeago();
// Handle charts tooltip
$(function() {
$('[data-toggle="tooltip"]').tooltip();
});
// Render charts
var xhrRenderCharts;
$(function(){
xhrRenderCharts = $.ajax({
url: api + '/stats',
cache: false,
success: home_CreateCharts
});
});
home_RenderPoolBlocks(lastStats);
home_RenderPayments(lastStats);
home_RenderGettingStarted(lastStats);
home_LoadTopMiners();
home_LoadDualMm();
$('#heroMinerAddress').val(getCurrentAddress(lastStats.config.coin) || '');
$('#heroMinerLookup').click(home_HandleHeroLookup);
$('#heroMinerAddress').keyup(function(e) {
if (e.keyCode === 13) home_HandleHeroLookup();
});
$('.home-scroll').click(function(e) {
e.preventDefault();
let target = $(this).attr('href');
let el = document.querySelector(target);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
home_InitTemplate(lastStats)
</script>