194 lines
7.1 KiB
JavaScript
Raw Normal View History

2025-10-20 15:42:12 +02:00
// static/pages/BlocksTable.js
2025-10-17 14:46:44 +02:00
import { h } from 'preact';
import { useEffect, useRef } from 'preact/hooks';
2025-10-30 15:00:06 +01:00
import { PAGE, API } from '../lib/api.js';
import { TABLE_SIZE } from '../lib/constants.js';
import { streamNdjson, ensureFixedRowCount, shortenHex } from '../lib/utils.js';
2025-10-17 14:46:44 +02:00
export default function BlocksTable() {
2025-10-20 15:42:12 +02:00
const bodyRef = useRef(null);
const countRef = useRef(null);
const abortRef = useRef(null);
2025-10-17 14:46:44 +02:00
const seenKeysRef = useRef(new Set());
useEffect(() => {
2025-10-20 15:42:12 +02:00
const body = bodyRef.current;
const counter = countRef.current;
2025-10-20 15:42:12 +02:00
2025-10-30 11:48:34 +01:00
// 6 columns: ID | Slot | Hash | Parent | Block Root | Transactions
ensureFixedRowCount(body, 6, TABLE_SIZE);
2025-10-17 14:46:44 +02:00
abortRef.current?.abort();
abortRef.current = new AbortController();
2025-10-17 14:46:44 +02:00
2025-10-20 15:42:12 +02:00
const pruneAndPad = () => {
2025-10-30 11:48:34 +01:00
// remove any placeholder rows that snuck in
2025-10-20 15:42:12 +02:00
for (let i = body.rows.length - 1; i >= 0; i--) {
if (body.rows[i].classList.contains('ph')) body.deleteRow(i);
2025-10-17 14:46:44 +02:00
}
2025-10-30 11:48:34 +01:00
// keep at most TABLE_SIZE non-placeholder rows
2025-10-20 15:42:12 +02:00
while ([...body.rows].filter((r) => !r.classList.contains('ph')).length > TABLE_SIZE) {
const last = body.rows[body.rows.length - 1];
2025-10-17 14:46:44 +02:00
const key = last?.dataset?.key;
if (key) seenKeysRef.current.delete(key);
2025-10-20 15:42:12 +02:00
body.deleteRow(-1);
2025-10-17 14:46:44 +02:00
}
2025-10-30 11:48:34 +01:00
// pad with placeholders to TABLE_SIZE (6 cols)
ensureFixedRowCount(body, 6, TABLE_SIZE);
2025-10-20 15:42:12 +02:00
const real = [...body.rows].filter((r) => !r.classList.contains('ph')).length;
counter.textContent = String(real);
};
2025-10-17 14:46:44 +02:00
2025-10-20 15:42:12 +02:00
const navigateToBlockDetail = (blockId) => {
history.pushState({}, '', PAGE.BLOCK_DETAIL(blockId));
window.dispatchEvent(new PopStateEvent('popstate'));
};
const appendRow = (b, key) => {
const tr = document.createElement('tr');
tr.dataset.key = key;
// ID (clickable)
const tdId = document.createElement('td');
const linkId = document.createElement('a');
linkId.className = 'linkish mono';
linkId.href = PAGE.BLOCK_DETAIL(b.id);
linkId.textContent = String(b.id);
linkId.addEventListener('click', (e) => {
e.preventDefault();
navigateToBlockDetail(b.id);
});
tdId.appendChild(linkId);
// Slot
const tdSlot = document.createElement('td');
const spSlot = document.createElement('span');
spSlot.className = 'mono';
spSlot.textContent = String(b.slot);
tdSlot.appendChild(spSlot);
2025-10-30 11:48:34 +01:00
// Hash
const tdHash = document.createElement('td');
const spHash = document.createElement('span');
spHash.className = 'mono';
spHash.title = b.hash;
spHash.textContent = shortenHex(b.hash);
tdHash.appendChild(spHash);
2025-10-20 15:42:12 +02:00
2025-10-30 11:48:34 +01:00
// Parent (block.parent_block_hash)
2025-10-20 15:42:12 +02:00
const tdParent = document.createElement('td');
const spParent = document.createElement('span');
spParent.className = 'mono';
spParent.title = b.parent;
spParent.textContent = shortenHex(b.parent);
tdParent.appendChild(spParent);
2025-10-30 11:48:34 +01:00
// Block Root
const tdRoot = document.createElement('td');
const spRoot = document.createElement('span');
spRoot.className = 'mono';
spRoot.title = b.root;
spRoot.textContent = shortenHex(b.root);
tdRoot.appendChild(spRoot);
2025-10-20 15:42:12 +02:00
// Transactions (array length)
const tdCount = document.createElement('td');
const spCount = document.createElement('span');
spCount.className = 'mono';
spCount.textContent = String(b.transactionCount);
tdCount.appendChild(spCount);
2025-10-30 11:48:34 +01:00
tr.append(tdId, tdSlot, tdHash, tdParent, tdRoot, tdCount);
2025-10-20 15:42:12 +02:00
body.insertBefore(tr, body.firstChild);
pruneAndPad();
};
2025-10-17 14:46:44 +02:00
const normalize = (raw) => {
2025-10-30 11:48:34 +01:00
// New backend:
// { id, hash, slot, block_root, parent_block_hash, transactions: [...] }
// Back-compat (header.* / raw.parent_block) just in case.
const header = raw.header ?? null;
2025-10-20 15:42:12 +02:00
const txLen = Array.isArray(raw.transactions)
? raw.transactions.length
: Array.isArray(raw.txs)
? raw.txs.length
: 0;
2025-10-17 14:46:44 +02:00
return {
id: Number(raw.id ?? 0),
2025-10-30 11:48:34 +01:00
slot: Number(raw.slot ?? header?.slot ?? 0),
hash: raw.hash ?? header?.hash ?? '',
parent: raw.parent_block_hash ?? header?.parent_block ?? raw.parent_block ?? '',
root: raw.block_root ?? header?.block_root ?? '',
2025-10-20 15:42:12 +02:00
transactionCount: txLen,
2025-10-17 14:46:44 +02:00
};
};
2025-10-17 14:46:44 +02:00
streamNdjson(
2025-10-20 15:42:12 +02:00
`${API.BLOCKS_STREAM}?prefetch-limit=${encodeURIComponent(TABLE_SIZE)}`,
2025-10-17 14:46:44 +02:00
(raw) => {
2025-10-20 15:42:12 +02:00
const b = normalize(raw);
const key = `${b.id}:${b.slot}`;
2025-10-17 14:46:44 +02:00
if (seenKeysRef.current.has(key)) {
pruneAndPad();
2025-10-17 14:46:44 +02:00
return;
}
seenKeysRef.current.add(key);
2025-10-20 15:42:12 +02:00
appendRow(b, key);
2025-10-17 14:46:44 +02:00
},
{
signal: abortRef.current.signal,
2025-10-20 15:42:12 +02:00
onError: (e) => {
console.error('Blocks stream error:', e);
},
2025-10-17 14:46:44 +02:00
},
2025-10-20 15:42:12 +02:00
);
2025-10-17 14:46:44 +02:00
return () => abortRef.current?.abort();
2025-10-17 14:46:44 +02:00
}, []);
return h(
'div',
{ class: 'card' },
h(
'div',
{ class: 'card-header' },
h('div', null, h('strong', null, 'Blocks '), h('span', { class: 'pill', ref: countRef }, '0')),
2025-10-20 15:42:12 +02:00
h('div', { style: 'color:var(--muted); fontSize:12px;' }),
2025-10-17 14:46:44 +02:00
),
h(
'div',
{ class: 'table-wrapper' },
h(
'table',
{ class: 'table--blocks' },
h(
'colgroup',
null,
2025-10-20 15:42:12 +02:00
h('col', { style: 'width:80px' }), // ID
h('col', { style: 'width:90px' }), // Slot
2025-10-30 11:48:34 +01:00
h('col', { style: 'width:240px' }), // Hash
2025-10-20 15:42:12 +02:00
h('col', { style: 'width:240px' }), // Parent
2025-10-30 11:48:34 +01:00
h('col', { style: 'width:240px' }), // Block Root
2025-10-20 15:42:12 +02:00
h('col', { style: 'width:120px' }), // Transactions
2025-10-17 14:46:44 +02:00
),
h(
'thead',
null,
2025-10-20 15:42:12 +02:00
h(
'tr',
null,
h('th', null, 'ID'),
h('th', null, 'Slot'),
2025-10-30 11:48:34 +01:00
h('th', null, 'Hash'),
2025-10-20 15:42:12 +02:00
h('th', null, 'Parent'),
2025-10-30 11:48:34 +01:00
h('th', null, 'Block Root'),
2025-10-20 15:42:12 +02:00
h('th', null, 'Transactions'),
),
2025-10-17 14:46:44 +02:00
),
2025-10-20 15:42:12 +02:00
h('tbody', { ref: bodyRef }),
2025-10-17 14:46:44 +02:00
),
),
);
}