Grafana Plugin
TraceHouse ships as a Grafana app plugin (tracehouse-app). It reuses the standalone frontend (same page components, same shared packages) and wraps it with the plugin scaffolding needed to run inside Grafana's sandbox: plugin.json, module.ts, webpack config, and adapter shims.
The key difference from the standalone app is how ClickHouse connectivity works. The standalone app connects directly (via the built-in proxy or user-provided credentials), while the Grafana plugin routes all queries through Grafana's ClickHouse datasource plugin. ClickHouse connectivity is managed entirely by Grafana's datasource configuration, not by TraceHouse itself.
All pages are available as Grafana navigation entries: Overview, Engine Internals, Cluster, Explorer, Time Travel, Queries, Merges, Replication, and Analytics.
Running with Docker
The easiest way to run TraceHouse inside Grafana is with the full Docker Compose profile:
just docker-start-full
This builds the plugin first (just grafana-plugin-build), then starts ClickHouse, Prometheus, and Grafana together. Grafana is available at http://localhost:3001.
The Grafana service mounts the built plugin from grafana-app-plugin/dist/ and auto-installs the ClickHouse datasource plugin. Anonymous auth is enabled for local development.
Development with watch mode
For iterating on the plugin:
# Terminal 1: start infra (builds the plugin once, then starts everything)
just docker-start-full
# Terminal 2: watch mode, rebuilds on file changes
just grafana-plugin-dev
Reload the Grafana page after each rebuild.
How the Integration Works
- Plugin type: Grafana
appplugin, registered viaAppPluginfrom@grafana/runtimeinmodule.ts - Pages: Declared in
plugin.jsonunderincludes[]. Each page gets a Grafana nav entry at/a/tracehouse-app/<slug> - Data flow: Queries go through Grafana's
/api/ds/querybackend endpoint viaGrafanaAdapter(inpackages/core), using whichever ClickHouse datasource the user selects - Configuration: Admin settings (refresh rates) stored in Grafana plugin
jsonData, accessed at runtime viaPluginConfigContext - Theme bridge: Reads
config.theme2.isDarkfrom Grafana and sets thedata-themeattribute on<html>so the frontend's CSS variables match Grafana's theme - Shared code: Pages and utilities come from workspace packages (
@tracehouse/core,@tracehouse/ui-shared) and fromfrontend/src/via webpack aliases. The plugin reuses the same page components as the standalone app
Workarounds
- No React Router: Grafana's plugin sandbox doesn't provide a Router context. The plugin stubs out
react-router-domentirely (seesrc/stubs/react-router-dom.tsx) and replaces navigation hooks with a customLocationContext-based implementation (src/hooks/useAppLocation.ts) - Store aliasing: The standalone app's
connectionStoreandClickHouseProviderare aliased at webpack level to Grafana-compatible shims that source the connection from the selected datasource instead of user-entered credentials useUrlStatevialocationService: Analytics state syncs to URL query params using Grafana'slocationService, so links like/a/tracehouse-app/analytics?tab=misc&preset=3are fully shareable, same as in the standalone app- AMD output format: Grafana's plugin loader requires AMD modules. The webpack config sets
output.library.type: 'amd'withuniqueName: 'tracehouse-app'for namespace isolation - Node.js polyfill stubs: Webpack config stubs all Node.js core modules (
stream,zlib,crypto, etc.) tofalsesince the plugin runs in the browser but some transitive dependencies reference them - Unsigned plugin allowlist: For local development,
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=tracehouse-appis set in the Docker Compose config
Building the Plugin
# One-off build
just grafana-plugin-build
# Output: grafana-app-plugin/dist/ (module.js, plugin.json, README.md)