219 lines
9.7 KiB
HTML
Executable File
219 lines
9.7 KiB
HTML
Executable File
{include file="index/layout_header" title="首页"}
|
||
|
||
<!-- 网络统计卡片 -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-6 col-md-3">
|
||
<div class="card bg-black border-primary h-100">
|
||
<div class="card-body text-center py-3">
|
||
<div class="text-muted small mb-1">当前区块高度</div>
|
||
<div class="fs-3 fw-bold text-primary" id="stat-block">{$stats.currentBlock|default=0}</div>
|
||
<div class="text-muted" style="font-size:11px">CBPP 共识</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card bg-black border-success h-100">
|
||
<div class="card-body text-center py-3">
|
||
<div class="text-muted small mb-1">CBPP 共识状态</div>
|
||
<div class="fs-6 fw-bold">
|
||
<span class="badge {$stats.cbppBadge|default='bg-secondary'}">{$stats.cbppConsensus|default='unknown'}</span>
|
||
</div>
|
||
<div class="text-muted" style="font-size:11px">宪政区块生产协议</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card bg-black border-info h-100">
|
||
<div class="card-body text-center py-3">
|
||
<div class="text-muted small mb-1">CSNP 网络</div>
|
||
<div class="fs-6 fw-bold">
|
||
<span class="badge {$stats.csnpBadge|default='bg-secondary'}">{$stats.csnpNetwork|default='unknown'}</span>
|
||
</div>
|
||
<div class="text-muted" style="font-size:11px">宪政安全网络协议</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card bg-black border-warning h-100">
|
||
<div class="card-body text-center py-3">
|
||
<div class="text-muted small mb-1">宪法层</div>
|
||
<div class="fs-6 fw-bold">
|
||
<span class="badge {$stats.constitutionBadge|default='bg-secondary'}">{$stats.constitutionText|default='未知'}</span>
|
||
</div>
|
||
<div class="text-muted" style="font-size:11px">Charter 智能合约</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第二行统计 -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-6 col-md-2">
|
||
<div class="card bg-dark border-secondary h-100">
|
||
<div class="card-body text-center py-2">
|
||
<div class="text-secondary small">Chain ID</div>
|
||
<div class="fw-bold text-info">{$stats.chainId|default=20260131}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="card bg-dark border-secondary h-100">
|
||
<div class="card-body text-center py-2">
|
||
<div class="text-secondary small">网络</div>
|
||
<div class="fw-bold text-light">{$stats.network|default='mainnet'}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="card bg-dark border-secondary h-100">
|
||
<div class="card-body text-center py-2">
|
||
<div class="text-secondary small">节点数</div>
|
||
<div class="fw-bold text-light">{$stats.nodeCount|default=0}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="card bg-dark border-secondary h-100">
|
||
<div class="card-body text-center py-2">
|
||
<div class="text-secondary small">总交易数</div>
|
||
<div class="fw-bold text-light">{$stats.totalTransactions|default=0}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="card bg-dark border-secondary h-100">
|
||
<div class="card-body text-center py-2">
|
||
<div class="text-secondary small">TPS</div>
|
||
<div class="fw-bold text-light">{$stats.tps|default=0}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-2">
|
||
<div class="card bg-dark border-secondary h-100">
|
||
<div class="card-body text-center py-2">
|
||
<div class="text-secondary small">平均出块</div>
|
||
<div class="fw-bold text-light">{$stats.avgBlockTime|default='3.0'}s</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 最新区块列表 -->
|
||
<div class="card bg-dark border-secondary">
|
||
<div class="card-header d-flex justify-content-between align-items-center border-secondary">
|
||
<span class="fw-bold">
|
||
<span class="text-success">●</span> 最新区块
|
||
</span>
|
||
<a href="/blocks" class="btn btn-sm btn-outline-secondary">查看全部</a>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-dark table-hover mb-0" id="block-table">
|
||
<thead>
|
||
<tr class="text-secondary small">
|
||
<th class="ps-3">区块号</th>
|
||
<th>区块哈希</th>
|
||
<th>出块时间</th>
|
||
<th>交易数</th>
|
||
<th>出块节点</th>
|
||
<th>大小</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="block-tbody">
|
||
{volist name="blocks" id="block"}
|
||
<tr style="{if condition='$block.isHeartbeat'}background:rgba(255,193,7,0.05);{/if}">
|
||
<td class="ps-3">
|
||
<a href="/block?n={$block.number}" class="{if condition='$block.isHeartbeat'}text-warning{else}text-primary{/if} fw-bold">#{$block.number}</a>
|
||
{if condition='$block.isHeartbeat'}<span class="badge bg-warning text-dark ms-1" style="font-size:0.65em" title="{$block.blockTypeNote}">心跳</span>{/if}
|
||
</td>
|
||
<td>
|
||
<a href="/block?n={$block.number}" class="text-info font-monospace small">{$block.shortHash}</a>
|
||
</td>
|
||
<td class="text-secondary small">{$block.timeAgo}</td>
|
||
<td>
|
||
{if condition='$block.isHeartbeat'}<span class="text-warning small">心跳块</span>{else}{$block.txCount|default=0}{/if}
|
||
</td>
|
||
<td class="font-monospace small text-secondary">{$block.shortValidator}</td>
|
||
<td class="text-secondary small">{$block.size|default=0} B</td>
|
||
</tr>
|
||
{/volist}
|
||
{empty name="blocks"}
|
||
<tr>
|
||
<td colspan="6" class="text-center text-secondary py-4">
|
||
暂无区块数据,等待链上数据同步...
|
||
</td>
|
||
</tr>
|
||
{/empty}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- WebSocket 实时推送脚本 -->
|
||
<script>
|
||
(function() {
|
||
var wsUrl = 'wss://' + location.host + '/ws';
|
||
var ws, reconnectTimer;
|
||
var tbodyEl = document.getElementById('block-tbody');
|
||
var statBlock = document.getElementById('stat-block');
|
||
|
||
function shortHash(h, f, b) {
|
||
f = f || 10; b = b || 8;
|
||
if (!h || h.length <= f + b + 3) return h || 'N/A';
|
||
return h.substring(0, f) + '...' + h.substring(h.length - b);
|
||
}
|
||
function timeAgo(ts) {
|
||
var diff = Math.floor(Date.now()/1000) - ts;
|
||
if (diff < 60) return diff + ' 秒前';
|
||
if (diff < 3600) return Math.floor(diff/60) + ' 分钟前';
|
||
return Math.floor(diff/3600) + ' 小时前';
|
||
}
|
||
|
||
function connect() {
|
||
try {
|
||
ws = new WebSocket(wsUrl);
|
||
ws.onopen = function() { clearTimeout(reconnectTimer); };
|
||
ws.onmessage = function(e) {
|
||
try {
|
||
var data = JSON.parse(e.data);
|
||
if (data.type === 'new_block' && data.block) {
|
||
prependBlock(data.block);
|
||
if (statBlock) statBlock.textContent = data.block.number;
|
||
}
|
||
} catch(err) {}
|
||
};
|
||
ws.onclose = function() { reconnectTimer = setTimeout(connect, 5000); };
|
||
ws.onerror = function() { ws.close(); };
|
||
} catch(e) {}
|
||
}
|
||
|
||
function prependBlock(block) {
|
||
if (!tbodyEl) return;
|
||
// 移除"暂无数据"行
|
||
tbodyEl.querySelectorAll('td[colspan]').forEach(function(td) { td.parentNode.remove(); });
|
||
// 限制最多20行
|
||
var rows = tbodyEl.querySelectorAll('tr');
|
||
if (rows.length >= 20) rows[rows.length - 1].remove();
|
||
|
||
var isHb = block.isHeartbeat || block.blockType === 'heartbeat';
|
||
var tr = document.createElement('tr');
|
||
tr.className = 'table-active';
|
||
tr.style.background = isHb ? 'rgba(255,193,7,0.05)' : '';
|
||
var numColor = isHb ? 'text-warning' : 'text-primary';
|
||
var hbBadge = isHb ? '<span class="badge bg-warning text-dark ms-1" style="font-size:0.65em" title="CBPP 宪法原则四:无交易时每60秒产生心跳块">心跳</span>' : '';
|
||
var txCell = isHb ? '<span class="text-warning small">心跳块</span>' : (block.txCount || 0);
|
||
tr.innerHTML = '<td class="ps-3"><a href="/block?n=' + block.number + '" class="' + numColor + ' fw-bold">#' + block.number + '</a>' + hbBadge + '</td>'
|
||
+ '<td><a href="/block?n=' + block.number + '" class="text-info font-monospace small">' + shortHash(block.hash) + '</a></td>'
|
||
+ '<td class="text-secondary small">' + timeAgo(block.timestamp) + '</td>'
|
||
+ '<td>' + txCell + '</td>'
|
||
+ '<td class="font-monospace small text-secondary">' + shortHash(block.validator, 8, 6) + '</td>'
|
||
+ '<td class="text-secondary small">' + (block.size || 0) + ' B</td>';
|
||
tbodyEl.insertBefore(tr, tbodyEl.firstChild);
|
||
setTimeout(function() { tr.className = ''; }, 2000);
|
||
}
|
||
|
||
connect();
|
||
})();
|
||
</script>
|
||
|
||
{include file="index/layout_footer"}
|