Revamp dashboard and persist pool stats across restarts

Dashboard now renders 10 tiles in a 5x2 overview: hashrate, best
share, miners, network hashrate, and expected block on the top row;
difficulty, block height, block reward, total work, and the
difficulty-adjustment countdown on the bottom row. Difficulty is
rendered with T/P suffixes instead of scientific notation, the main
hashrate card shows the 1-minute value, and the block-height tile
pulses orange when the network tip advances.

Added a 24-hour hashrate area chart below the overview, sampled
once per minute. Samples are persisted to a new hashrate_samples
SQLite table and restored on startup so the chart doesn't reset
every time kamado-api is restarted.

Cumulative pool work (sum of accepted diff-1-normalized shares) is
now tracked across ckpool restarts. The aggregator integrates only
positive deltas on pool.Shares — a regression means ckpool's
counter reset to zero and the baseline is refreshed without losing
the running total. A hasPoolSharesBaseline flag prevents double-
counting on the first refresh after a kamado-api restart. The
value is persisted to a new kv table once per minute.

Next-block reward (subsidy + fees) is fetched from bitcoind
getblocktemplate at most once per minute and surfaced as a tile.

Header's block-height badge now reads prevHeight via untrack() so
the effect doesn't form a dependency cycle with its own write.
This commit is contained in:
satoshi
2026-04-22 21:07:22 +03:00
parent 8de767646d
commit e881bc930d
10 changed files with 2180 additions and 14 deletions
+18
View File
@@ -175,3 +175,21 @@ func (c *RPC) GetNetworkHashPS(ctx context.Context, blocks, height int) (float64
}
return out, nil
}
// BlockTemplate is the subset of getblocktemplate we care about: the
// coinbase value (subsidy + fees) and height of the next block.
type BlockTemplate struct {
CoinbaseValue int64 `json:"coinbasevalue"` // satoshis
Height int64 `json:"height"`
}
// GetBlockTemplate fetches the next block template with segwit rules.
// The call is relatively expensive; rate-limit callers to ~1/min.
func (c *RPC) GetBlockTemplate(ctx context.Context) (*BlockTemplate, error) {
var out BlockTemplate
params := []any{map[string]any{"rules": []string{"segwit"}}}
if err := c.Call(ctx, "getblocktemplate", params, &out); err != nil {
return nil, err
}
return &out, nil
}