Skip to content

SEC-359: Replace compare app with server-rendered RPC performance overview#9

Open
smypmsa wants to merge 8 commits into
mainfrom
feature/SEC-359-grafana-iframe
Open

SEC-359: Replace compare app with server-rendered RPC performance overview#9
smypmsa wants to merge 8 commits into
mainfrom
feature/SEC-359-grafana-iframe

Conversation

@smypmsa
Copy link
Copy Markdown
Member

@smypmsa smypmsa commented May 22, 2026

Summary

Replaces the interactive comparison flow on compare.chainstack.com with a server-rendered RPC provider performance overview built from the Grafana Cloud Prometheus API. The routes that proxied the backend /scenarios/* endpoints (the SSRF surface) are removed. Pairs with backend PR #19, which scales /scenarios to 0.

SEC-359 — https://chainstack.myjetbrains.com/youtrack/issue/SEC-359

Changes

  • src/lib/grafana.js — server-only Prometheus client (instant + range queries) against the Grafana Cloud proxy, GRAFANA_API_TOKEN bearer, 60s ISR.
  • src/lib/queries.js — 8 chains with public dashboard tokens; PromQL for per-provider/per-region p50/p95/p99 latency, per-provider success rate, and a p95 trend series (successes only, TEST_* excluded).
  • src/lib/chain-data.js — fetches and shapes each chain's provider list (sorted by p95).
  • src/app/page.js + src/components/** — server components rendering a chain nav and a per-chain card: p50/p95/p99 bar, p95 24h sparkline, per-provider success rate, and a p95-by-region heatmap, plus an "Open in Grafana ↗" link. Streamed with Suspense skeletons.
  • Routes removedcompare-single, compare-double, injection-start, injection-result-double. next.config.js 301-redirects /compare-single and /compare-double to /; the injection routes now 404. /dashboard still 301s to the prod Grafana dashboard.
  • next.config.js — dropped the open /api/* CORS rule; added CSP (frame-ancestors 'none'), HSTS, nosniff, Referrer-Policy.
  • .env.sampleNEXT_PUBLIC_BACKEND_APP_URL removed, GRAFANA_API_TOKEN added. Store emptied; dead compare-app components and icons removed.

Deploy

Set GRAFANA_API_TOKEN (Grafana Cloud service account, Viewer role) in Vercel for Production and Preview.

Testing

npm run build green. PromQL validated against the source dashboards' regional p95 panels.

Follow-ups (not in this PR)

  • CSP still allows frame-src https://chainstack.grafana.net — leftover from the iframe approach; nothing frames Grafana now.
  • package.json likely has dead deps (react-google-charts, react-type-animation, simpler-state, immer, @mdx-js/*) — run depcheck.

Summary by CodeRabbit

  • New Features

    • Server-rendered homepage: per-chain RPC provider overview with provider tables, percentile "bullet" bars, 24h trend sparklines, and Grafana links; cached server data fetching for telemetry.
  • Refactoring

    • Removed legacy client-side comparison/injection flows and many deprecated UI components in favor of server-driven dashboard components.
  • Chores

    • Global security headers (CSP, HSTS, Referrer-Policy) added.
    • Env template updated: documented client domain and new server-only Grafana API token placeholder.
    • Added small server-only dependency.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chainstack-compare Ready Ready Preview, Comment May 31, 2026 8:44am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b0219d95-ed18-4e3b-a069-779ba7a02f6c

📥 Commits

Reviewing files that changed from the base of the PR and between c5aae6b and 7a597f8.

📒 Files selected for processing (1)
  • src/components/Chain/ChainCard.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Chain/ChainCard.js

📝 Walkthrough

Walkthrough

The PR replaces the client-side RPC comparison UI with a server-rendered Grafana/Prometheus-backed dashboard: adds server Grafana/Prometheus helpers and queries, cached chain data aggregation, server async components (ChainTOC, ChainCard) with Suspense, and removes the old comparison pages and related client state/components.

Changes

Server-Side Performance Dashboard Implementation

Layer / File(s) Summary
Configuration and dependencies for Grafana integration
.env.sample, package.json, next.config.js
Environment template adds GRAFANA_API_TOKEN; package.json adds server-only; next.config.js applies global CSP headers and updates redirects to Grafana/dashboard.
Grafana Prometheus API client and query builders
src/lib/grafana.js, src/lib/queries.js
Server-only Grafana datasource proxy helpers with auth/timeout/revalidate and PromQL builders plus CHAINS metadata.
Chain data fetching and display formatting
src/lib/chain-data.js, src/lib/format.js
Cached fetchChainData() aggregates per-provider/region quantiles, success rates, and trends; formatting helpers for latency, percent, region labels, and anchors.
Home page server-side architecture and store cleanup
src/app/page.js, src/app/store/store.js
Home converted to a server component (revalidate = 60) using Suspense for ChainTOC and per-chain ChainCard; client store reduced to an empty stub.
Chain performance table and percentile visualization
src/components/Chain/ChainCard.js, src/components/Chain/BulletBar.js, src/components/Chain/Sparkline.js, src/components/Chain/ChainCardSkeleton.js
Async ChainCard fetches telemetry and renders provider table with heatmapped per-region p95, stacked p50/p95/p99 bars via BulletBar, and 24h trend sparklines; skeleton provides loading UI.
Chain table-of-contents overview components
src/components/ChainTOC.js, src/components/ChainTOCSkeleton.js
Async ChainTOC concurrently fetches chain summaries and renders a grid of chain links with leader metrics and sparklines; skeleton provides placeholders.
Header update and removed client UI components
src/components/Header/Header.js, deleted icon module(s)
Header removes Grafana icon and Dashboard link; several small icon and legacy UI components were removed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through code at break of dawn,
Grafana tokens freshly drawn,
Sparklines sing and tables hum,
Old client pages now are gone,
Server-side dashboard, bright as morn. 🌤️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: replacing an interactive compare application with a new server-rendered performance overview page powered by Grafana data.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/SEC-359-grafana-iframe

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Home page rewritten as server-rendered RPC provider overview via Grafana Cloud Prometheus API; ISR revalidate=60
- Streaming SSR: per-ChainCard + Headline + ChainTOC <Suspense> boundaries so cards stream as their PromQL resolves
- Bullet bars: p50/p95/p99 layered horizontal bars per provider, leader emerald, others slate
- Inline-SVG 24h sparkline per provider row, no chart library
- Regional heatmap per chain: provider × region p95 grid, HSL-tinted from chain-wide min/max
- Headline strip: fastest-right-now, most-p95-wins, tail-latency leader across all chains
- Mini-sparkline TOC: 4×2 chain grid with leader trend + anchor jumps
- lib/grafana.js: runPromQuery + runPromRangeQuery
- lib/chain-data.js: React.cache-wrapped per-chain fetcher, parallel queries, partial-data tolerant; TOC/Headline/Cards share results
- lib/queries.js: providerByRegionQuery(chain, q) for any quantile + providerTrendQuery for range data
- Route deletions: compare-single/, compare-double/, injection-start/, injection-result-double/ — all hit the backend's /scenarios/* SSRF surface; legacy URLs 301 to / in next.config.js
- next.config.js: dropped wide-open /api/* CORS; CSP with frame-ancestors 'none', HSTS, nosniff, Referrer-Policy
- store/store.js emptied; .env.sample drops NEXT_PUBLIC_BACKEND_APP_URL and adds GRAFANA_API_TOKEN
- Dead components removed: Bento, Faq, Performance, ProtocolIcon (+ SVGs), ResultCard, orphaned icons
- Subtitle: drop jargon and middle dot, cap line width
- Remove the dynamic headline stat line
- Per-provider success rate (sum by provider) replaces the chain aggregate;
  shown under each provider, amber below 99.9%
- Region codes shown as short country labels (US, DE, SG …) with full
  name in the column tooltip
- Merge the region heatmap into the provider table as right-hand columns,
  sticky provider column; no duplicated provider names
- Rename Trend column to "p95, 24h"; fix trend query comment
@smypmsa smypmsa marked this pull request as ready for review May 27, 2026 07:33
@smypmsa smypmsa changed the title SEC-359: Replace compare app with embedded Grafana dashboard SEC-359: Replace compare app with server-rendered RPC performance overview May 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 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 @.env.sample:
- Line 2: The env sample has spaces around the equals sign for assignments
(e.g., NEXT_PUBLIC_CLIENT_DOMAIN =), which can cause parsing issues; update the
assignment to remove spaces so it reads NEXT_PUBLIC_CLIENT_DOMAIN= and similarly
normalize other keys mentioned (line 8) to KEY= format throughout the file to
ensure valid .env syntax.

In `@next.config.js`:
- Line 26: Update the Referrer-Policy header value to a stricter setting to
reduce cross-origin data leakage: locate the header object with key
'Referrer-Policy' in next.config.js and change its value from
'no-referrer-when-downgrade' to 'strict-origin-when-cross-origin' (or a stricter
policy if desired), ensuring the header entry remains otherwise unchanged.

In `@src/lib/grafana.js`:
- Around line 20-23: The Grafana fetch call currently has no abort path; wrap
the request in an AbortController: create an AbortController before calling
fetch, pass controller.signal in the fetch options alongside headers:
authHeaders() and next: { revalidate }, start a timer (configurable timeout,
e.g. 3–5s) that calls controller.abort() on expiry, and clear the timer once the
response is received; also ensure any fetch errors from abort are
handled/translated appropriately where res is used so SSR doesn't hang.
- Around line 39-44: runPromRangeQuery currently passes start, end, and step
straight into fetchProm which can produce "undefined" query params; update
runPromRangeQuery to validate that start, end, and step are provided and not
undefined/null (and optionally coercible to valid values) before calling
fetchProm, and throw a clear error (e.g., TypeError or rejected Promise) if any
are missing; reference the runPromRangeQuery function and its call to
fetchProm('/api/v1/query_range', { query: promql, start, end, step }, opts) so
you enforce the checks there and only build/pass the params when valid.
🪄 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: 275bc1dd-78bd-4675-ac0f-7a4f46d89dbc

📥 Commits

Reviewing files that changed from the base of the PR and between aaf3404 and d2d9db9.

⛔ Files ignored due to path filters (25)
  • package-lock.json is excluded by !**/package-lock.json
  • src/components/ProtocolIcon/aptos.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/arbitrum.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/aurora.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/avalanche.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/base.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/bitcoin.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/bnb.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/cronos.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/ethereum.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/fantom.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/filecoin.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/fuse.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/gnosis.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/harmony.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/near.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/optimism.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/polygonPOS.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/polygonZkEvm.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/ronin.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/scroll.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/solana.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/starknet.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/tezos.svg is excluded by !**/*.svg
  • src/components/ProtocolIcon/zkSync.svg is excluded by !**/*.svg
📒 Files selected for processing (33)
  • .env.sample
  • next.config.js
  • package.json
  • src/app/compare-double/page.js
  • src/app/compare-single/page.js
  • src/app/injection-result-double/page.js
  • src/app/injection-start/page.js
  • src/app/page.js
  • src/app/store/store.js
  • src/components/Bento/Bento.js
  • src/components/Chain/BulletBar.js
  • src/components/Chain/ChainCard.js
  • src/components/Chain/ChainCardSkeleton.js
  • src/components/Chain/Sparkline.js
  • src/components/ChainTOC.js
  • src/components/ChainTOCSkeleton.js
  • src/components/Faq/FaqAccordion.js
  • src/components/Faq/FaqBasic.js
  • src/components/Faq/post.mdx
  • src/components/Header/Header.js
  • src/components/Icons/BarChartIcon.js
  • src/components/Icons/Customization.js
  • src/components/Icons/ExplainResultsIcon.js
  • src/components/Icons/GrafanaIcon.js
  • src/components/Icons/Profiling.js
  • src/components/Performance/Compare.js
  • src/components/Performance/Preformance.js
  • src/components/ProtocolIcon/ProtocolIcon.js
  • src/components/ResultCard/ResultCard.js
  • src/lib/chain-data.js
  • src/lib/format.js
  • src/lib/grafana.js
  • src/lib/queries.js
💤 Files with no reviewable changes (18)
  • src/components/Faq/post.mdx
  • src/components/Faq/FaqAccordion.js
  • src/components/Icons/Customization.js
  • src/components/Faq/FaqBasic.js
  • src/app/injection-start/page.js
  • src/components/Performance/Compare.js
  • src/components/Icons/GrafanaIcon.js
  • src/components/Icons/BarChartIcon.js
  • src/components/Bento/Bento.js
  • src/components/Performance/Preformance.js
  • src/components/Icons/ExplainResultsIcon.js
  • src/components/Icons/Profiling.js
  • src/components/ProtocolIcon/ProtocolIcon.js
  • src/components/Header/Header.js
  • src/components/ResultCard/ResultCard.js
  • src/app/compare-single/page.js
  • src/app/compare-double/page.js
  • src/app/injection-result-double/page.js

Comment thread .env.sample Outdated
Comment thread next.config.js Outdated
Comment thread src/lib/grafana.js Outdated
Comment thread src/lib/grafana.js
The CSP added in this PR blocked the Vercel toolbar (vercel.live + Pusher
websockets), so comments/live feedback couldn't load or be submitted on
preview deployments. Allowlist vercel.live and Pusher in script/style/font/
frame/connect-src, gated to VERCEL_ENV=preview so production stays locked down.
The latency table is w-full with auto layout, and the BulletBar column was
the only flexible one, so it absorbed all leftover width. Chains with fewer
region columns (e.g. TON) left more slack, stretching the bar and pushing the
p50/p95/p99 numbers further from the provider name. Add a flexible spacer
column before the region block so slack lands there instead, pinning every
data column to a consistent width across all chains and keeping the region
heatmap flush-right.
Each chain rendered its own auto-layout table, so column widths were computed
per-card from content: chains with long provider names (TON) pushed the bars
and numbers further right, and slack pooled unpredictably. The earlier spacer
column made it worse by parking all slack between p99 and the heatmap.

Switch to table-fixed with a generated colgroup: fixed widths on the metric
columns (provider/bar/sparkline/p50/p95/p99) so they're identical in every
card — bars start at the same x and numbers line up regardless of name length
(long names truncate) — and auto-width region columns that split the remaining
space, keeping the heatmap flush-right with no gap. Sparkline/p99 columns
collapse to 0 width on mobile in step with their hidden cells.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant