diff --git a/api/internal/bitcoind/rpc.go b/api/internal/bitcoind/rpc.go
index 186e944..de5042e 100644
--- a/api/internal/bitcoind/rpc.go
+++ b/api/internal/bitcoind/rpc.go
@@ -343,6 +343,20 @@ func (c *RPC) GetRawMempoolVerbose(ctx context.Context) (map[string]RawMempoolEn
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.
func IsRPCError(err error, code int) bool {
var rerr *rpcError
diff --git a/api/internal/state/aggregator.go b/api/internal/state/aggregator.go
index 02caee3..5e105f1 100644
--- a/api/internal/state/aggregator.go
+++ b/api/internal/state/aggregator.go
@@ -98,6 +98,11 @@ type Snapshot struct {
HasLastZMQEvent bool `json:"has_last_zmq_event"`
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).
// Session = since ckpool started; AllTime = persisted across restarts.
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 ---
// Fetch the timestamp of the first block in the current retarget
// epoch (height - height%2016) once per epoch and cache it, then
diff --git a/ui/src/lib/BestSharePage.svelte b/ui/src/lib/BestSharePage.svelte
index b8a7366..7d04931 100644
--- a/ui/src/lib/BestSharePage.svelte
+++ b/ui/src/lib/BestSharePage.svelte
@@ -399,15 +399,15 @@
}
.disclaimer {
font-size: 0.82rem;
- color: var(--fg-dim);
- background: rgba(245, 196, 71, 0.08);
- border: 1px solid rgba(245, 196, 71, 0.25);
+ color: rgb(160, 195, 240);
+ background: rgb(20, 35, 60);
+ border: 1px solid rgb(60, 100, 170);
border-radius: 6px;
padding: 0.5em 0.85em;
line-height: 1.5;
}
.disclaimer strong {
- color: var(--fg);
+ color: rgb(200, 220, 250);
}
.head {
display: flex;
diff --git a/ui/src/lib/PoolOverview.svelte b/ui/src/lib/PoolOverview.svelte
index 8c679e1..0e0f297 100644
--- a/ui/src/lib/PoolOverview.svelte
+++ b/ui/src/lib/PoolOverview.svelte
@@ -208,7 +208,12 @@
Block height
{height ? height.toLocaleString() : "—"}
-
{data.chain?.chain === "main" ? "mainnet" : data.chain?.chain ?? "—"}
+
+ {data.chain?.chain === "main" ? "mainnet" : data.chain?.chain ?? "—"}
+ {#if data.peer_count > 0}
+ · {data.peer_count} peers
+ {/if}
+
diff --git a/ui/src/lib/StatsPage.svelte b/ui/src/lib/StatsPage.svelte
index 88841d5..c22d5a0 100644
--- a/ui/src/lib/StatsPage.svelte
+++ b/ui/src/lib/StatsPage.svelte
@@ -51,6 +51,8 @@
// --- Difficulty distribution ---
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 = {
label: string;
@@ -180,7 +182,7 @@
- Difficulty Distribution
+ Difficulty Distribution - Amount of shares in different difficulty ranges
{#if !hasDistData}
No accepted shares yet
{:else}
@@ -196,9 +198,16 @@
- {#each bucketRows as row}
-
- | {row.label} |
+ {#each bucketRows as row, i}
+ {@const hasShares = row.session > 0 || row.alltime > 0}
+
+ |
+ {#if fireClasses[i]}
+ {row.label}
+ {:else}
+ {row.label}
+ {/if}
+ |
{row.session.toLocaleString()} |
{row.sessionPct.toFixed(1)}% |
{row.alltime.toLocaleString()} |
@@ -310,4 +319,24 @@
tr[title]:hover td { color: var(--accent); }
.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); }
+ }
+
diff --git a/ui/src/stores/selection.svelte.ts b/ui/src/stores/selection.svelte.ts
index 9113f51..c33a8d7 100644
--- a/ui/src/stores/selection.svelte.ts
+++ b/ui/src/stores/selection.svelte.ts
@@ -74,16 +74,5 @@ export function selectBestShare(): 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 = "";
}
diff --git a/ui/src/types.ts b/ui/src/types.ts
index 150274a..489980f 100644
--- a/ui/src/types.ts
+++ b/ui/src/types.ts
@@ -147,6 +147,9 @@ export type Snapshot = {
has_last_zmq_event: boolean;
last_zmq_event_age?: number; // seconds
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).
session_accepted: number;
session_rejected: number;