A modular analytics dashboard for Gnosis metrics built with React, ECharts, YAML-driven layouts, and a cached ClickHouse-backed API.
- Connects to ClickHouse Cloud via API
- Server-side caching to minimize ClickHouse queries
- Secure handling of credentials (not exposed to frontend)
- YAML-driven dashboards/tabs/card layouts
- ECharts-based chart rendering with expand/info/download controls
- Header metric search with fuzzy matching and direct dashboard/tab navigation
- Responsive design with dark mode support
- Deployment to Vercel with serverless functions
Configured areas live in YAML and currently include:
- Overview
- Gnosis Pay
- OnChain Activity
- Consensus
- Network
- Bridges
- Tokens
- Yields
- ESG
This application follows a config-driven frontend + API architecture with server-side caching:
-
Frontend Dashboard - React application deployed to Vercel
- Resolves dashboard layout from
public/dashboard.ymlandpublic/dashboards/*.yml - Merges layout entries with metric definitions from
src/queries/*.js - Renders widgets/cards using ECharts and shared UI components
- Resolves dashboard layout from
-
API Proxy - Serverless API functions that interface with ClickHouse
- Handles authentication securely
- Implements caching to minimize ClickHouse queries
- Executes queries and returns results to the dashboard
- Deployed as Vercel Serverless Functions in the same project
-
Caching System - Reduces load on ClickHouse
- Uses
/tmpdirectory in Vercel functions for file-based caching - Falls back to in-memory caching if file operations fail
- Automatic daily refresh of cache data
- Configurable TTL (time to live) for cache entries
- Uses
-
Clone this repository:
git clone https://github.com/yourusername/clickhouse-metrics-dashboard.git cd clickhouse-metrics-dashboard -
Install dependencies (including API dependencies):
# Install project dependencies pnpm install -
Create
.envfile with your configuration:cp .env.example .env # Edit .env with your settings -
Start the development server:
pnpm dev
-
Run tests and production build:
pnpm test pnpm build
├── README.md
├── api/
│ ├── cache.js # Cache management implementation
│ ├── cron.js # Automatic refresh functionality
│ ├── metrics.js # Main API endpoint with caching support
│ ├── mock.js # Mock data generator
│ ├── test.js # Cache status API endpoint
│ ├── package.json # API dependencies
│ └── queries/ # Query definitions as JSON
├── public/ # Static assets
│ ├── dashboard.yml # Main sector index (order + icons + source)
│ └── dashboards/ # Per-sector layouts (metrics/tabs)
├── scripts/
│ └── export-queries.js # Script to export queries from frontend to API
├── src/
│ ├── components/
│ │ ├── Card.js # Card component
│ │ ├── Dashboard.js # Main dashboard component
│ │ ├── Header.js # Top header (search/resources/theme)
│ │ ├── MetricSearchBar.js # Header metric search dropdown
│ │ └── MetricWidget.js # Individual metric display
│ ├── services/
│ │ ├── api.js # API service with cache support
│ │ ├── dashboards.js # Dashboard/tab/layout resolution service
│ │ └── metrics.js # Metric config + data service
│ ├── queries/ # Frontend metric definitions
│ ├── utils/
│ │ ├── dashboardConfig.js # Loads and resolves YAML config
│ │ ├── metricSearch.js # Search index + scoring logic
│ │ ├── config.js # Application configuration
│ │ ├── dates.js # Date utilities
│ │ └── formatter.js # Value formatters
│ └── styles.css # Application styles
└── vercel.json # Vercel deployment configuration
The dashboard config is split into:
public/dashboard.ymlfor sector metadata and ordering.public/dashboards/<sector>.ymlfor each sector layout (metricsortabs).
Main file example:
Overview:
name: Overview
order: 1
icon: "📊"
iconClass: "chart-line"
source: /dashboards/overview.ymlSector file example:
metrics:
- id: overview_stake_api
gridRow: 1
gridColumn: 1 / span 3
minHeight: 130pxHow to add a new sector:
- Create
public/dashboards/<new-sector>.ymlwithmetricsortabs. - Add a top-level entry in
public/dashboard.ymlwithname,order,icon,iconClass, andsource. - Start the app and verify the new sector appears in navigation.
Dashboards opt into named palette presets in public/dashboard.yml.
All palette definitions are centralized in:
/Users/hugser/Documents/Gnosis/repos/metrics-dashboard/src/utils/dashboardPalettes.js
GnosisPay:
name: Gnosis Pay
order: 10
icon: 💳
iconClass: credit-card
palette: gnosis-pay
source: /dashboards/gnosis-pay.yml- Scope is dashboard-level only (set once per top-level dashboard entry).
- Dashboard palette applies automatically to all chart and number widgets in that dashboard.
- Fallback-only precedence is enforced:
- Metric-level explicit color config wins (
color,colors,bandColors,lineColors, map-specific overrides, and explicit heatmap scale). - Dashboard palette is used only when metric-level colors are not explicitly defined.
- If dashboard palette is missing/invalid,
standardis used safely. - If a custom/dashboard palette has fewer colors than required series, the remaining series are filled from the
standardpalette before any repetition.
- Metric-level explicit color config wins (
palettein YAML accepts preset names only (for example:standard,gnosis-pay).
This is fully config-driven:
- Adding new dashboards/tabs/metrics in YAML requires no code changes to inherit palette behavior.
- Any metric placed in YAML under a dashboard automatically receives that dashboard palette fallback.
- New palette presets are added once in
/Users/hugser/Documents/Gnosis/repos/metrics-dashboard/src/utils/dashboardPalettes.jsand then referenced by name in YAML.
- Config load at startup
src/utils/dashboardConfig.jsloads/dashboard.yml.- If a sector defines
source, it loads and merges/dashboards/<sector>.yml. - Resolved YAML is passed to
src/services/dashboards.js.
- Layout resolution
- Each dashboard/tab metric ID is merged with its base metric config from
src/queries/*.js. - Grid placement (
gridRow,gridColumn,minHeight) comes from YAML.
- Each dashboard/tab metric ID is merged with its base metric config from
- Navigation state
src/components/Dashboard.jsmanages active dashboard/tab and syncs?dashboard=&tab=in the URL.
- Widget data flow
MetricWidgetloads metric config frommetricsService.- Data is fetched via
/api/metrics/:metricId. - Text widgets can render static content without API calls.
- Filters
global_filteris a pseudo-metric used only for UI placement in the grid.- Global filter values are fetched once from a suitable metric and then reused across cards in the tab.
- Card text fields
description: subtitle shown under the card title.metricDescription: markdown content shown in the info popover.
- Chart controls
- Chart cards include info popover, PNG download, and expand-to-modal controls.
The header search is designed for fast tab jumps.
- Scope
- Search index is built only from resolved dashboard YAML metrics (
dashboard -> tab -> metrics). - Metrics that exist in
src/queriesbut are not placed in any YAML tab are intentionally excluded.
- Search index is built only from resolved dashboard YAML metrics (
- Exclusions
- Non-navigable pseudo entries like
global_filterare excluded.
- Non-navigable pseudo entries like
- Matching + ranking
- Highest priority: metric name exact/prefix/token matches.
- Then: metric ID token matches.
- Then: tab/dashboard/description/metricDescription context matches.
- Includes light typo tolerance (edit distance 1 for longer tokens).
- Result limits
- Default maximum is 8 results for speed and clarity.
- Duplicate labels
- If multiple results share the same
Metric Name + Dashboard/Tab, the UI appends a qualifier (description, or metricDescription, or metric ID fallback) to disambiguate.
- If multiple results share the same
- Navigation behavior
- Selecting a result jumps directly to its dashboard and tab (no card auto-scroll in this phase).
- Keyboard controls
ArrowUp/ArrowDownto move selectionEnterto navigateEscapeto close suggestions- Outside click closes suggestions
The top-bar Resources menu is fully config-driven from:
/Users/hugser/Documents/Gnosis/repos/metrics-dashboard/src/config/headerLinks.js
To add or update links, edit HEADER_RESOURCE_LINKS using the grouped structure below:
export const HEADER_RESOURCE_LINKS = [
{
id: 'api',
label: 'API',
links: [{ id: 'api-reference', label: 'API Reference', href: 'https://your-url' }]
}
];Each group renders a section in the dropdown, and each link opens in a new tab.
The dashboard implements a server-side caching system to minimize ClickHouse queries:
- First Request: On first request, the system queries ClickHouse and caches the results
- Subsequent Requests: Future requests use cached data (until cache expires)
- Automatic Refresh: Cache is automatically refreshed once per day
- Fallback Mechanism: If ClickHouse queries fail, system uses cached data
Configure caching behavior with these environment variables:
CACHE_TTL_HOURS: How long cache entries are valid (default: 24 hours)CACHE_REFRESH_HOURS: How often the cache is refreshed (default: 24 hours)
When deploying to Vercel's serverless environment:
- File System Limitations: The system uses
/tmpdirectory for caching - In-Memory Fallback: Falls back to in-memory caching if file operations fail
- Ephemeral Storage: Cache might reset between function invocations
Check cache status by visiting:
https://your-deployment-url/api/test
Include your API key in the X-API-Key header.
Force a cache refresh by adding refreshCache=true to your API requests:
https://your-deployment-url/api/metrics?refreshCache=true
- Local development (
NODE_ENV=development):- Frontend request caching is bypassed.
- Requests to
/api/metricsand/api/metrics/:idautomatically includeuseCached=falseunless explicitly provided. - API responses include
Cache-Control: no-store, max-age=0andPragma: no-cache.
- Production:
- Existing cache behavior is unchanged.
useCacheddefaults totruewhen omitted.
- Override support:
- You can explicitly set
useCached=trueoruseCached=falseper request in any environment.
- You can explicitly set
To deploy this dashboard, you'll need:
- A Vercel account
- A ClickHouse instance or ClickHouse Cloud account
- Your ClickHouse connection details
-
Install and log in to Vercel CLI:
npm install -g vercel vercel login
-
Deploy with Vercel:
vercel --prod
-
Configure environment variables in Vercel:
- Go to your project in the Vercel dashboard
- Navigate to Settings > Environment Variables
- Add the following environment variables:
CLICKHOUSE_HOST: Your ClickHouse host URLCLICKHOUSE_USER: ClickHouse usernameCLICKHOUSE_PASSWORD: ClickHouse passwordCLICKHOUSE_DATABASE(optional): Database nameCLICKHOUSE_DBT_SCHEMA(optional): dbt schema prefix used to rewritedbt.in queries (default:dbt)API_KEY: A secure key for API authenticationVITE_API_URL:/api(relative path)VITE_API_KEY: Same value asAPI_KEYVITE_DASHBOARD_TITLE(optional): Custom dashboard titleVITE_DEV_API_PROXY_TARGET(optional): Local API proxy target forpnpm devCACHE_TTL_HOURS(optional): Cache validity period in hoursCACHE_REFRESH_HOURS(optional): Cache refresh interval in hours
Vite-prefixed variables are the primary format:
VITE_API_URLVITE_API_KEYVITE_USE_MOCK_DATAVITE_DASHBOARD_TITLEVITE_PUBLIC_BASE_URL(optional)VITE_DEV_API_PROXY_TARGET(optional, local dev proxy)
Legacy REACT_APP_* variables are still supported as a temporary migration fallback.
If you encounter issues:
-
Check Vercel Logs:
vercel logs your-deployment-url
-
Check Cache Status: Visit
/api/testto see cache information. -
Use Mock Data Temporarily: Set
VITE_USE_MOCK_DATA=truein environment variables during testing.
To add a new metric:
-
Add a new metric query file in
src/queries/:// src/queries/newMetric.js const newMetric = { id: 'newMetricId', name: 'New Metric Name', description: 'Description of the new metric', format: 'formatNumber', // Use existing formatter or add new in formatter.js chartType: 'line', color: '#00BCD4', query: ` SELECT toDate(event_time) AS date, count() AS value FROM your_table WHERE event_time BETWEEN '{from}' AND '{to} 23:59:59' GROUP BY date ORDER BY date ` }; export default newMetric;
-
Ensure the metric is placed in dashboard YAML so it becomes visible and searchable:
- Add it to
public/dashboards/<sector>.ymlunder ametricslist in the target tab. - Metrics not placed in YAML are not rendered and are not included in header search.
- Add it to
-
Run the export script to update the API:
pnpm run export-queries
-
Deploy your changes to Vercel:
vercel --prod
For local development without ClickHouse:
-
Set the
USE_MOCK_DATAenvironment variable totrue:USE_MOCK_DATA=true -
The API will generate mock data instead of querying ClickHouse.
This project is licensed under the MIT License.