feat(ui): node-health surface — indexer self-repair status endpoint + real charts - #151
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (16)
📝 WalkthroughWalkthroughAdds indexer health snapshots and status plumbing, exposes ChangesIndexer Health Data Layer
Indexer Status API
Dashboard Charts UI
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant ErgoApi
participant IndexerHandle
participant IndexerStore
Browser->>ErgoApi: GET /api/v1/indexer/status
ErgoApi->>IndexerHandle: status()
ErgoApi->>IndexerHandle: health()
IndexerHandle->>IndexerStore: health_snapshot()
IndexerStore-->>IndexerHandle: StoreHealthSnapshot
IndexerHandle-->>ErgoApi: IndexerHealthDto
ErgoApi-->>Browser: ApiIndexerStatus JSON
Browser->>Browser: render sync pipeline and charts
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
ergo-api/web/js/overview.js (1)
584-629: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winUnhandled rejection risk in
refreshChartData.
refreshChartData()is invoked fire-and-forget (line 264, line 634) with no.catch. If either fetch rejects, thefinallystill resetsinFlight, but the rejection is unhandled — noisy console errors with no surfaced diagnostics.♻️ Proposed fix: swallow/log errors explicitly
} finally { charts.inFlight = false; } -} +} + +function refreshChartDataSafe() { + refreshChartData().catch((err) => console.error('chart refresh failed', err)); +}Then use
refreshChartDataSafe()at the two call sites instead ofrefreshChartData().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ergo-api/web/js/overview.js` around lines 584 - 629, refreshChartData currently lets Promise rejections escape when api.difficultyHistory or api.poolHistogram fails, which becomes an unhandled rejection because the call sites invoke it fire-and-forget. Update refreshChartData to catch and log errors internally (while still clearing charts.inFlight in the existing finally), or introduce a refreshChartDataSafe wrapper that handles the rejection, and switch both fire-and-forget call sites to use the safe entrypoint. Reference the refreshChartData function and its current Promise.all/api calls when implementing the fix.ergo-api/src/blockchain.rs (1)
204-232: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated status-label mapping vs.
indexed_height_handler.
indexed_height_handler(Line 164-177 in this file) derives its status label viaIndexerStatusLabel::from_status(&status). This handler instead hand-rolls the sameSyncing/CaughtUp/Halted→ string mapping inline. Two independent copies of this mapping can silently drift (e.g. a future relabeling applied to only one handler) even though both surfaces document "the same camelCase label set as indexedHeight."Consider deriving the string from
IndexerStatusLabel::from_status(&status)(e.g. via aDisplay/to_string()if available) to keep the two endpoints' status wire format single-sourced.♻️ Possible consolidation
- status: match &status { - IndexerStatus::Syncing => "syncing".to_string(), - IndexerStatus::CaughtUp => "caughtUp".to_string(), - IndexerStatus::Halted(_) => "halted".to_string(), - }, + status: IndexerStatusLabel::from_status(&status).to_string(),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ergo-api/src/blockchain.rs` around lines 204 - 232, The status label mapping in indexer_status_handler is duplicated and can drift from indexed_height_handler. Replace the inline Syncing/CaughtUp/Halted string conversion with the same single source used by indexed_height_handler, namely IndexerStatusLabel::from_status(&status), and derive the API string from that shared representation so both endpoints stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ergo-api/web/js/chart.js`:
- Around line 211-224: The bar rendering in chart.js has a `y`/`height` mismatch
for tiny values: `height` is clamped in the `bins.forEach` rect creation, but
`y` still uses the raw `bh`, so small nonzero bars get clipped. Update the bar
placement logic in the `bins.forEach` block so `y` is derived from the same
clamped height used for `height`, keeping the bottom edge aligned with the
baseline; use the existing `svgEl('rect', ...)`, `bh`, and `ui.svg.append` code
path as the place to fix it.
---
Nitpick comments:
In `@ergo-api/src/blockchain.rs`:
- Around line 204-232: The status label mapping in indexer_status_handler is
duplicated and can drift from indexed_height_handler. Replace the inline
Syncing/CaughtUp/Halted string conversion with the same single source used by
indexed_height_handler, namely IndexerStatusLabel::from_status(&status), and
derive the API string from that shared representation so both endpoints stay
aligned.
In `@ergo-api/web/js/overview.js`:
- Around line 584-629: refreshChartData currently lets Promise rejections escape
when api.difficultyHistory or api.poolHistogram fails, which becomes an
unhandled rejection because the call sites invoke it fire-and-forget. Update
refreshChartData to catch and log errors internally (while still clearing
charts.inFlight in the existing finally), or introduce a refreshChartDataSafe
wrapper that handles the rejection, and switch both fire-and-forget call sites
to use the safe entrypoint. Reference the refreshChartData function and its
current Promise.all/api calls when implementing the fix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c5d4f440-feb2-497d-90be-68bac3474fbe
📒 Files selected for processing (16)
ergo-api/src/blockchain.rsergo-api/src/server.rsergo-api/src/types.rsergo-api/src/web.rsergo-api/tests/fixtures/openapi_native.yamlergo-api/tests/indexer_status_endpoint.rsergo-api/tests/wallet_ui_headers.rsergo-api/web/dashboard.cssergo-api/web/js/api-client.jsergo-api/web/js/chart.jsergo-api/web/js/overview.jsergo-indexer-types/src/lib.rsergo-indexer-types/src/query.rsergo-indexer/src/handle.rsergo-indexer/src/lib.rsergo-indexer/src/store/mod.rs
Phase 2A of the first-class UI effort: the node's health machinery becomes observable, and the Charts view graduates from session sparklines to real server-history charts. Indexer health endpoint: - New defaulted `IndexerQuery::health()` (ergo-indexer-types) returning IndexerHealthDto: the durable self-repair markers from the #147 incident machinery (repair_pending / repair_next_gi rebuild cursor / repair_skipped honest marker), the process-lifetime drift-skip counter, and the global box/tx totals. IndexerHandle overrides it with best-effort store reads (a degraded store degrades a field, never errors the snapshot); stubs inherit the healthy default. - New `GET /api/v1/indexer/status` (utoipa-documented, native openapi snapshot regenerated): always-200 and never status-gated — like indexedHeight it must answer while syncing, repairing, or halted. `/blockchain/indexedHeight` stays pinned to its Scala-parity shape; the repair/totals superset lives here. 404 on indexer-less wiring = disabled. Pinned by a new 4-case endpoint test. Overview integration: - The Sync-pipeline panel now surfaces index health, silent when healthy: a progress row while a rebuild runs (cursor/total boxes), a queued row during the wipe phase, a "done · N box(es) skipped" honest-marker row after a knowingly-incomplete repair, and a red halt row with the reason. Real charts (new zero-dep chart.js — axis-labelled SVG line/area + bar histogram with pointer readouts; text lives in HTML around the stretched SVG so nothing distorts; data via textContent only): - Difficulty (last 720 blocks) from /api/v1/difficulty/history — the string-typed difficulty parsed once per point. - Estimated hashrate per point (difficulty / target interval — the same derivation as the KPI band). - Block-interval distribution histogram from the same series' timestamp deltas (<30s … >10m bins). - Mempool age histogram from /transactions/poolHistogram (waiting tx by wait-time bucket). Charts build once and update in place (hover state survives the 4s tick); the series refetches only when the tip height advances. Verified against live mainnet data via the dev proxy (real difficulty epoch steps, 2-minute-modal interval distribution, live pool histogram). fmt/clippy/cargo test --all green; openapi snapshot + runtime-mount + header tests updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bbdc082 to
e2bccf5
Compare
For 0 < raw-height < 2 the bar rect's y was computed from the raw height while the height itself was clamped to the 2-unit minimum, pushing the bottom edge past the baseline (masked by viewBox clipping). Clamp first, derive y from the clamped value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
Phase 2A of the first-class UI effort: the node's health machinery becomes observable, and the Charts view graduates from session sparklines to real server-history charts.
Indexer health endpoint
IndexerQuery::health()(ergo-indexer-types) returning the durable self-repair markers from the fix(indexer): degrade-not-halt + self-repair the secondary index (network-wide db-corruption halt) #147 incident machinery —repair_pending, therepair_next_girebuild cursor, therepair_skippedhonest marker — plus the process-lifetime drift-skip counter and global box/tx totals.IndexerHandleoverrides it via a newIndexerStore::health_snapshot()that captures meta + all three markers under one redb read txn (mutually consistent, one txn per dashboard poll; unlikeindexed_height, this isn't on the per-request middleware path, so no cached mirror is needed). Best-effort: a degraded store degrades the snapshot to defaults, never errors the surface.GET /api/v1/indexer/status— always-200 and never status-gated (likeindexedHeight, it must answer while syncing/repairing/halted; that's exactly when the operator needs it)./blockchain/indexedHeightstays pinned to its Scala-parity shape; the repair/totals superset lives here. 404 on indexer-less wiring = disabled. utoipa-documented, native openapi snapshot regenerated, pinned by a 6-case endpoint test including the degraded wire shape and the wipe-phasepending=true-with-nextGi-absent distinction.Overview integration
The Sync-pipeline panel surfaces index health, silent when healthy: a progress bar while a rebuild runs (cursor / total boxes), "queued — wipe phase", a
done · N box(es) skippedhonest-marker row after a knowingly-incomplete repair, and a red halt row with the reason.Real charts
New zero-dep
chart.js(axis-labelled SVG line/area + bar histogram, pointer readouts; axis text lives in HTML around the stretched SVG so nothing distorts; all data viatextContent):/api/v1/difficulty/history— deliberate approximateNumber()parse, documented against the string-typed wire contract./transactions/poolHistogram.Charts build once and update in place (hover state survives the 4s tick); the series refetches only when the tip advances.
Review & testing
codexadversarial pass: no blocking issues; its Medium (4 redb txns per poll → single-txn snapshot) and all 4 Lows (wire-contract test coverage, difficulty-parse documentation, u64 contract note, chart expando hygiene) fixed in this commit.-D warnings/cargo test --allgreen; openapi snapshot + runtime-mount + SPA-header tests updated.Phase 2B (events feed — node-side ring buffer +
/api/v1/events+ UI feed) follows as a separate PR.🤖 Generated with Claude Code
Summary by CodeRabbit