Files
nathanwoodburn 5b4b1ff33f
Build Docker / BuildImage (push) Successful in 43s
Check Code Quality / RuffCheck (push) Successful in 1m9s
feat: Add debug page
2026-04-05 18:54:18 +10:00

173 lines
6.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firepool | Debug Chain View</title>
<meta name="description" content="Debug view for the latest block hashes and chain tip.">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Firepool">
<meta property="og:title" content="Firepool | Debug Chain View">
<meta property="og:description" content="Inspect the latest block hashes to spot reorgs and forks.">
<meta property="og:url" content="{{ request.url }}">
<meta property="og:image" content="https://explorer.hns.au/assets/img/favicon.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Firepool | Debug Chain View">
<meta name="twitter:description" content="Inspect the latest block hashes to spot reorgs and forks.">
<meta name="twitter:image" content="https://explorer.hns.au/assets/img/favicon.png">
<link rel="icon" href="https://explorer.hns.au/assets/img/favicon.png" type="image/png">
<link rel="stylesheet" href="/assets/css/index.css">
</head>
<body>
<div class="bg-orb orb-a"></div>
<div class="bg-orb orb-b"></div>
<div class="bg-grid"></div>
<main class="layout">
<header class="hero">
<div>
<p class="kicker">Firepool</p>
<h1>Debug Chain View</h1>
<p class="subtitle">Last 10 block hashes from the node RPC.</p>
</div>
<div class="hero-meta">
<span id="lastUpdated">Last update: pending</span>
<span id="debugSource">Source: pending</span>
</div>
</header>
<section class="panel">
<div class="panel-head">
<h2>Tip Summary</h2>
<p class="debug-note">If a recent height changes between refreshes, that points to a reorg or fork near the tip.</p>
</div>
<div class="debug-summary" id="debugSummary"></div>
</section>
<section class="panel">
<div class="panel-head">
<h2>Recent Blocks</h2>
<p class="debug-refresh">Showing the latest 10 heights, newest first.</p>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Height</th>
<th>Hash</th>
</tr>
</thead>
<tbody id="blocksBody"></tbody>
</table>
</div>
</section>
<section class="panel setup-panel">
<div class="panel-head">
<h2>Back to Dashboard</h2>
<p>Return to the main pool view when you are done checking the chain tip.</p>
</div>
<a class="setup-link" href="/">Open Dashboard</a>
</section>
</main>
<script>
const refreshMs = 10000;
function fmtHeight(value) {
return Number(value || 0).toLocaleString();
}
function shortHash(hash) {
if (!hash) return "-";
if (hash.length <= 18) return hash;
return `${hash.slice(0, 10)}...${hash.slice(-8)}`;
}
function renderHash(hash) {
if (!hash) return "-";
if (hash.length <= 10) {
return `<a class="hash-link" href="https://explorer.fistbump.org/block/${hash}" target="_blank" rel="noopener" title="Open block in explorer"><span class="hash-prefix">${hash}</span></a>`;
}
const prefix = hash.slice(0, 5);
const suffix = hash.slice(-5);
const middle = hash.slice(5, -5);
return `
<a class="hash-link" href="https://explorer.fistbump.org/block/${hash}" target="_blank" rel="noopener" title="Open block in explorer">
<span class="hash-prefix">${prefix}</span>
<span class="hash-middle">${middle}</span>
<span class="hash-suffix">${suffix}</span>
</a>
`;
}
function renderCards(cards) {
const el = document.getElementById("debugSummary");
el.innerHTML = cards.map(card => `
<article class="stat-card">
<span>${card.label}</span>
<strong class="mono">${card.value}</strong>
</article>
`).join("");
}
function renderRows(rows) {
const body = document.getElementById("blocksBody");
const orderedRows = [...rows].reverse();
if (!orderedRows.length) {
body.innerHTML = '<tr><td colspan="2" class="empty">No block hashes available.</td></tr>';
return;
}
body.innerHTML = orderedRows.map(row => `
<tr>
<td data-label="Height">${fmtHeight(row.height)}</td>
<td data-label="Hash" class="hash-cell mono" title="${row.hash || ""}">${renderHash(row.hash)}</td>
</tr>
`).join("");
}
function refreshStamp(updatedAt, bestHeight, bestBlockHash, sourceMethod) {
document.getElementById("lastUpdated").textContent = `Last update: ${new Date(updatedAt).toLocaleString()}`;
document.getElementById("debugSource").textContent = `Source: ${sourceMethod} | Tip ${fmtHeight(bestHeight)} | ${shortHash(bestBlockHash)}`;
}
async function loadDebugBlocks() {
const res = await fetch("/api/v1/debug/blocks");
if (!res.ok) {
throw new Error(`Debug request failed (${res.status})`);
}
const data = await res.json();
renderCards([
{ label: "Best Height", value: fmtHeight(data.best_height) },
{ label: "Best Block Hash", value: shortHash(data.best_block_hash) },
{ label: "Tip Blocks Loaded", value: fmtHeight((data.blocks || []).length) }
]);
renderRows(data.blocks || []);
refreshStamp(data.updated_at, data.best_height, data.best_block_hash, data.source_method || "node_rpc");
}
async function refreshDebug() {
try {
await loadDebugBlocks();
} catch (error) {
document.getElementById("debugSummary").innerHTML = `
<article class="stat-card">
<span>Load Error</span>
<strong class="mono">${String(error.message || error)}</strong>
</article>
`;
document.getElementById("blocksBody").innerHTML = '<tr><td colspan="2" class="empty">Unable to load debug data.</td></tr>';
}
}
refreshDebug();
window.setInterval(refreshDebug, refreshMs);
</script>
</body>
</html>