Add node peers, blue banners, glowing diff ranges, fix back navigation

- Show Bitcoin Core peer count on block height card (hover for in/out)
- BestSharePage disclaimers now use solid blue styling
- Difficulty distribution ranges glow from warm peach to deep red
- Back-to-dashboard buttons always navigate to dashboard instead of
  using browser history
This commit is contained in:
satoshi
2026-05-19 02:34:15 +03:00
parent daa87e2352
commit e341fd7b26
7 changed files with 73 additions and 21 deletions
+14
View File
@@ -343,6 +343,20 @@ func (c *RPC) GetRawMempoolVerbose(ctx context.Context) (map[string]RawMempoolEn
return out, nil return out, nil
} }
type NetworkInfo struct {
Connections int `json:"connections"`
ConnectionsIn int `json:"connections_in"`
ConnectionsOut int `json:"connections_out"`
}
func (c *RPC) GetNetworkInfo(ctx context.Context) (*NetworkInfo, error) {
var out NetworkInfo
if err := c.Call(ctx, "getnetworkinfo", nil, &out); err != nil {
return nil, err
}
return &out, nil
}
// IsRPCError checks if err is an rpcError with a specific code. // IsRPCError checks if err is an rpcError with a specific code.
func IsRPCError(err error, code int) bool { func IsRPCError(err error, code int) bool {
var rerr *rpcError var rerr *rpcError
+12
View File
@@ -98,6 +98,11 @@ type Snapshot struct {
HasLastZMQEvent bool `json:"has_last_zmq_event"` HasLastZMQEvent bool `json:"has_last_zmq_event"`
TipChangedAge float64 `json:"tip_changed_age"` // seconds since tip height last changed TipChangedAge float64 `json:"tip_changed_age"` // seconds since tip height last changed
// Bitcoin Core peer connections.
PeerCount int `json:"peer_count"`
PeersIn int `json:"peers_in"`
PeersOut int `json:"peers_out"`
// Share counters: raw counts (1 submission = 1 share regardless of diff). // Share counters: raw counts (1 submission = 1 share regardless of diff).
// Session = since ckpool started; AllTime = persisted across restarts. // Session = since ckpool started; AllTime = persisted across restarts.
SessionAccepted int64 `json:"session_accepted"` SessionAccepted int64 `json:"session_accepted"`
@@ -460,6 +465,13 @@ func (a *Aggregator) refresh(ctx context.Context) {
} }
} }
// --- bitcoind: peer connections ---
if ni, err := a.RPC.GetNetworkInfo(ctx); err == nil {
next.PeerCount = ni.Connections
next.PeersIn = ni.ConnectionsIn
next.PeersOut = ni.ConnectionsOut
}
// --- predicted difficulty adjustment --- // --- predicted difficulty adjustment ---
// Fetch the timestamp of the first block in the current retarget // Fetch the timestamp of the first block in the current retarget
// epoch (height - height%2016) once per epoch and cache it, then // epoch (height - height%2016) once per epoch and cache it, then
+4 -4
View File
@@ -399,15 +399,15 @@
} }
.disclaimer { .disclaimer {
font-size: 0.82rem; font-size: 0.82rem;
color: var(--fg-dim); color: rgb(160, 195, 240);
background: rgba(245, 196, 71, 0.08); background: rgb(20, 35, 60);
border: 1px solid rgba(245, 196, 71, 0.25); border: 1px solid rgb(60, 100, 170);
border-radius: 6px; border-radius: 6px;
padding: 0.5em 0.85em; padding: 0.5em 0.85em;
line-height: 1.5; line-height: 1.5;
} }
.disclaimer strong { .disclaimer strong {
color: var(--fg); color: rgb(200, 220, 250);
} }
.head { .head {
display: flex; display: flex;
+6 -1
View File
@@ -208,7 +208,12 @@
<div class="card height-card" class:new-block={heightFlash}> <div class="card height-card" class:new-block={heightFlash}>
<div class="stat-label">Block height</div> <div class="stat-label">Block height</div>
<div class="stat-value">{height ? height.toLocaleString() : "—"}</div> <div class="stat-value">{height ? height.toLocaleString() : "—"}</div>
<div class="stat-sub">{data.chain?.chain === "main" ? "mainnet" : data.chain?.chain ?? "—"}</div> <div class="stat-sub">
{data.chain?.chain === "main" ? "mainnet" : data.chain?.chain ?? "—"}
{#if data.peer_count > 0}
<span class="peers" title="{data.peers_in} inbound · {data.peers_out} outbound">&nbsp;· {data.peer_count} peers</span>
{/if}
</div>
</div> </div>
<div class="card"> <div class="card">
+33 -4
View File
@@ -51,6 +51,8 @@
// --- Difficulty distribution --- // --- Difficulty distribution ---
const bucketLabels = ["< 1M", "1M\u2013100M", "100M\u20131G", "1G\u2013100G", "100G\u20131T", "\u2265 1T"]; const bucketLabels = ["< 1M", "1M\u2013100M", "100M\u20131G", "1G\u2013100G", "100G\u20131T", "\u2265 1T"];
// Fire intensity classes: none for <1M, then progressively more intense.
const fireClasses = ["", "fire-1", "fire-2", "fire-3", "fire-4", "fire-5"];
type BucketRow = { type BucketRow = {
label: string; label: string;
@@ -180,7 +182,7 @@
<!-- Difficulty Distribution Table --> <!-- Difficulty Distribution Table -->
<section class="card"> <section class="card">
<h3>Difficulty Distribution</h3> <h3>Difficulty Distribution - Amount of shares in different difficulty ranges</h3>
{#if !hasDistData} {#if !hasDistData}
<div class="empty">No accepted shares yet</div> <div class="empty">No accepted shares yet</div>
{:else} {:else}
@@ -196,9 +198,16 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each bucketRows as row} {#each bucketRows as row, i}
<tr class:dim-row={row.session === 0 && row.alltime === 0}> {@const hasShares = row.session > 0 || row.alltime > 0}
<td>{row.label}</td> <tr class:dim-row={!hasShares && !fireClasses[i]}>
<td>
{#if fireClasses[i]}
<span class="fire-label {fireClasses[i]}" class:fire-active={hasShares} class:fire-dimmed={!hasShares}>{row.label}</span>
{:else}
{row.label}
{/if}
</td>
<td class="num">{row.session.toLocaleString()}</td> <td class="num">{row.session.toLocaleString()}</td>
<td class="num">{row.sessionPct.toFixed(1)}%</td> <td class="num">{row.sessionPct.toFixed(1)}%</td>
<td class="num">{row.alltime.toLocaleString()}</td> <td class="num">{row.alltime.toLocaleString()}</td>
@@ -310,4 +319,24 @@
tr[title]:hover td { color: var(--accent); } tr[title]:hover td { color: var(--accent); }
.dim-row td { opacity: 0.35; } .dim-row td { opacity: 0.35; }
/* --- Glowing range labels --- */
.fire-label {
display: inline-block;
font-weight: 600;
}
.fire-dimmed { opacity: 0.2; }
.fire-active { animation: glow-pulse 2s ease-in-out infinite alternate; }
.fire-1.fire-active { color: #ffcc80; --glow: 255, 180, 100; }
.fire-2.fire-active { color: #ff9a5c; --glow: 255, 140, 60; }
.fire-3.fire-active { color: #ff6e40; --glow: 255, 90, 30; }
.fire-4.fire-active { color: #f44336; --glow: 230, 40, 20; }
.fire-5.fire-active { color: #c62828; --glow: 180, 20, 10; }
@keyframes glow-pulse {
0% { text-shadow: 0 0 8px rgba(var(--glow), 0.4); }
100% { text-shadow: 0 0 8px rgba(var(--glow), 0.85),
0 0 16px rgba(var(--glow), 0.3); }
}
</style> </style>
-11
View File
@@ -74,16 +74,5 @@ export function selectBestShare(): void {
} }
export function clearSelection(): void { export function clearSelection(): void {
if (
window.history.length > 1 &&
(window.location.hash.startsWith(USER_PREFIX) ||
window.location.hash.startsWith(WORKER_PREFIX) ||
window.location.hash === ACCELERATOR_HASH ||
window.location.hash === STATS_HASH ||
window.location.hash === BESTSHARE_HASH)
) {
window.history.back();
} else {
window.location.hash = ""; window.location.hash = "";
}
} }
+3
View File
@@ -147,6 +147,9 @@ export type Snapshot = {
has_last_zmq_event: boolean; has_last_zmq_event: boolean;
last_zmq_event_age?: number; // seconds last_zmq_event_age?: number; // seconds
tip_changed_age: number; // seconds since tip height last changed tip_changed_age: number; // seconds since tip height last changed
peer_count: number;
peers_in: number;
peers_out: number;
// Share counters (raw, 1 submission = 1 share). // Share counters (raw, 1 submission = 1 share).
session_accepted: number; session_accepted: number;
session_rejected: number; session_rejected: number;