Bladeren bron

explorer: Add /blocks endpoint with pagination

x 6 maanden geleden
bovenliggende
commit
df0763da2d

+ 2 - 3
Cargo.lock

@@ -2308,7 +2308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "337f65eb93d9996551b9442423480eca4532586b337484446eb5138d0cd8fcf0"
 dependencies = [
  "heck 0.5.0",
- "indexmap 2.13.0",
+ "indexmap 1.9.3",
  "itertools 0.14.0",
  "proc-macro-crate",
  "proc-macro2",
@@ -6945,12 +6945,11 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
 [[package]]
 name = "tapes"
 version = "0.1.0"
-source = "git+https://github.com/Cuprate/Tapes#a30a5261564b12b21b126599149a88d5a0c5087c"
+source = "git+https://github.com/Cuprate/Tapes#5bb3fc964341f0d608c13275e3496d43d4b28ab1"
 dependencies = [
  "blake2",
  "borsh",
  "bytemuck",
- "libc",
  "parking_lot 0.12.5",
  "slab",
 ]

+ 65 - 0
bin/explorer/python/explorer.py

@@ -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:

+ 56 - 0
bin/explorer/python/static/layout.css

@@ -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;

+ 88 - 0
bin/explorer/python/templates/blocks.html

@@ -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>

+ 1 - 1
bin/explorer/python/templates/index.html

@@ -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>