explorer: Add /blocks endpoint with pagination

This commit is contained in:
x
2026-01-27 14:17:35 +00:00
parent eddb0245da
commit df0763da2d
5 changed files with 212 additions and 4 deletions

View File

@@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import asyncio
from datetime import datetime, timezone
from quart import Quart, render_template, abort, request, redirect, url_for, Response
@@ -150,6 +151,70 @@ async def index():
)
@app.route("/blocks")
async def list_blocks():
"""List all blocks with pagination"""
BLOCKS_PER_PAGE = 100
# Get page number from query param (1-indexed for users)
page = request.args.get("page", 1, type=int)
if page < 1:
page = 1
current_difficulty = await rpc.call("current_difficulty", params=[])
current_height = await rpc.call("current_height", params=[])
hashrate = await rpc.call("get_hashrate", params=[])
total = current_height + 1 # blocks 0 to current_height
# Calculate range: newest first
offset = (page - 1) * BLOCKS_PER_PAGE
start_height = current_height - offset
end_height = max(0, start_height - BLOCKS_PER_PAGE + 1)
# Build list of heights to fetch
heights = [h for h in range(start_height, end_height - 1, -1) if h >= 0]
async def fetch_block(height):
try:
block = await rpc.call("get_block", params=[height])
dt = datetime.fromtimestamp(block["timestamp"], tz=timezone.utc)
return {
"height": int(block["height"]),
"size": int(block["size"]),
"n_txs": len(block["txs"]),
"timestamp": dt.strftime("%H:%M UTC %d %b %Y"),
"powtype": block["powtype"],
"hash": block["hash"],
}
except JsonRpcError:
return None
# Fetch all blocks in parallel
results = await asyncio.gather(*[fetch_block(h) for h in heights])
blocks = [b for b in results if b is not None]
# Calculate pagination info
total_pages = (total + BLOCKS_PER_PAGE - 1) // BLOCKS_PER_PAGE
has_prev = page > 1
has_next = page < total_pages
return await render_template(
"blocks.html",
network=app.config["NETWORK"],
current_difficulty=current_difficulty[0],
current_height=current_height,
hashrate=format_hashrate(hashrate),
blocks=blocks,
total=total,
page=page,
total_pages=total_pages,
has_prev=has_prev,
has_next=has_next,
blocks_per_page=BLOCKS_PER_PAGE,
)
@app.route("/block/<int:block_height>")
async def get_block_by_height(block_height: int):
if block_height < 0:

View File

@@ -335,6 +335,10 @@ code {
overflow: hidden;
}
.table-section.full-width {
width: 100%;
}
.table-header {
display: flex;
justify-content: space-between;
@@ -562,6 +566,58 @@ tbody tr:hover td { background: var(--color-hover); }
white-space: pre-wrap;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
padding: 1.5rem;
border-top: 1px solid var(--table-border-color);
}
.pagination .btn-sm {
padding: 0.5rem 1rem;
font-size: 1.3rem;
}
.pagination .btn-sm.disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.pagination-info {
font-size: 1.4rem;
color: var(--color-muted);
padding: 0 1rem;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
padding: 1.5rem;
border-top: 1px solid var(--table-border-color);
}
.pagination .btn-sm {
padding: 0.5rem 1rem;
font-size: 1.3rem;
}
.pagination .btn-sm.disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}
.pagination-info {
font-size: 1.4rem;
color: var(--color-muted);
padding: 0 1rem;
}
.footer {
border-top: 1px solid var(--table-border-color);
padding: 3rem 0;

View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>All Blocks - DarkFi Block Explorer</title>
<link rel="stylesheet" href="/static/layout.css">
</head>
<body>
<header class="header">
<div class="container flex justify-between items-center">
<div class="logo">DarkFi {{ network }} Blocks</div>
<nav class="nav">
<a href="/">Explorer</a>
<a href="/contracts">Contracts</a>
<a href="/stats">Stats</a>
<a href="https://dark.fi/book/">Docs</a>
<a href="https://codeberg.org/darkrenaissance/darkfi/">Code</a>
</nav>
</div>
</header>
<main class="container section">
<div class="table-section">
<div class="table-header">
<h3>All Blocks</h3>
<span class="badge">{{ total }} Total</span>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Height</th>
<th>Size</th>
<th>Txs</th>
<th>Timestamp</th>
<th>PoW</th>
<th>Block Hash</th>
</tr>
</thead>
<tbody>
{% for block in blocks %}
<tr>
<td class="height"><a href="/block/{{ block.height|int }}">{{ block.height|int }}</a></td>
<td>{{ block.size|int }}</td>
<td>{{ block.n_txs|int }}</td>
<td>{{ block.timestamp }}</td>
<td>{{ block.powtype }}</td>
<td><a href="/block/{{ block.height|int }}">{{ block.hash }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="pagination">
{% if has_prev %}
<a href="/blocks?page=1" class="btn btn-sm">&laquo; First</a>
<a href="/blocks?page={{ page - 1 }}" class="btn btn-sm">&lsaquo; Prev</a>
{% else %}
<span class="btn btn-sm disabled">&laquo; First</span>
<span class="btn btn-sm disabled">&lsaquo; Prev</span>
{% endif %}
<span class="pagination-info">Page {{ page }} of {{ total_pages }}</span>
{% if has_next %}
<a href="/blocks?page={{ page + 1 }}" class="btn btn-sm">Next &rsaquo;</a>
<a href="/blocks?page={{ total_pages }}" class="btn btn-sm">Last &raquo;</a>
{% else %}
<span class="btn btn-sm disabled">Next &rsaquo;</span>
<span class="btn btn-sm disabled">Last &raquo;</span>
{% endif %}
</div>
</div>
</main>
<footer class="footer">
<div class="container">
<a href="https://dark.fi">Let there be Dark</a>
</div>
</footer>
</body>
</html>

View File

@@ -79,7 +79,7 @@
<div class="table-section">
<div class="table-header">
<h3>Latest Blocks</h3>
<a href="#">View all →</a>
<a href="/blocks">View all →</a>
</div>
<div class="table-wrapper">
<table>