Documentation
Embed a live SigNoz dashboard in one afternoon
This service ships as a single Docker image that serves the embed app and proxies the read-only SigNoz API. Bring a SigNoz 0.97.0 backend and an API key with dashboard view permission.
Getting started
Prerequisites: Node >=20 and pnpm@9 for source builds, or Docker for the image. You also need a running SigNoz v0.97.0 backend and an API key with dashboard view permission.
Docker (recommended)
docker run -p 8080:8080 \
-e SIGNOZ_BASE_URL=http://<signoz-host>:30303 \
-e SIGNOZ_API_KEY=<read-only-key> \
taosherio/signoz-open-dashboard:latest
# 1. open a dashboard: http://localhost:8080/embed/<dashboardId>
# 2. embed it:
# <iframe src="http://localhost:8080/embed/<dashboardId>?relativeTime=30m"></iframe>From source
pnpm install
# dev: web on :5173 proxies /api/signoz to the API on :8080
SIGNOZ_BASE_URL=http://<signoz-host>:30303 SIGNOZ_API_KEY=<key> pnpm dev:api
pnpm dev:web
# production
pnpm build
SIGNOZ_BASE_URL=http://<signoz-host>:30303 SIGNOZ_API_KEY=<key> node apps/api/dist/main.jsVerify connectivity against SigNoz directly before debugging the proxy:
curl -H "SIGNOZ-API-KEY: <key>" \
http://<signoz-host>:30303/api/v1/dashboards/<dashboardId>Configuration
The backend is a single upstream fixed by SIGNOZ_BASE_URL; a backend address is never accepted from URL parameters. A non-http(s) base URL crashes at boot, and an empty default key only warns.
| Variable | Required | Default | Notes |
|---|---|---|---|
| SIGNOZ_BASE_URL | Yes | - | The only upstream. http(s) only, never taken from the URL (SSRF guard). |
| SIGNOZ_API_KEY | No | empty | Default key, overridable by the URL apiKey. Empty only warns. |
| PORT | No | 8080 | Listen port. |
| UPSTREAM_TIMEOUT_MS | No | 30000 | query_range timeout; dashboard metadata uses 10000. |
| MAX_REFRESH_SECONDS_FLOOR | No | 10 | Refresh intervals below this are clamped. |
| LOG_LEVEL | No | info | pino JSON logs. |
| CORS_ORIGIN | No | * | Public by design, do not tighten blindly. |
Embed URL
Base: {EMBED_ORIGIN}/embed/:dashboardId. An invalid dashboard id short-circuits to a 404 empty state without calling upstream. The view is fully URL-driven and syncs back via history.replaceState, so every view is a shareable link.
<iframe
src="https://embed.example.com/embed/<dashboardId>?apiKey=<key>&relativeTime=30m&theme=shadcn&mode=light&refresh=30s&var-env=prod"
style="width:100%;border:0"
allowfullscreen>
</iframe>| Param | Example | Default | Notes |
|---|---|---|---|
| apiKey | ?apiKey=<key> | env.SIGNOZ_API_KEY | URL wins over env. Missing key renders a 401 empty state. Never written back unless the caller put it there. |
| relativeTime / startTime+endTime | 30m, or epoch seconds | 30m | Native time params. Legacy from=now-30m&to=now is translated once at mount. |
| theme | shadcn / legacy | shadcn | Unknown values fall back to shadcn. |
| mode | light / dark | light | Color mode, orthogonal to theme. |
| locale | zh / en | zh | Rendered by the active theme. |
| refresh | off / 30s / 1m | dashboard value | Clamped to >= 10s. |
| annotations | true / false | true | Read-only annotation markers. |
| var-<name> | ?var-env=prod | dashboard default | Highest priority, overrides dashboard defaults. One dashboard can serve every customer, region or tier. |
| title / toolbar | ?title=false | true | Chrome toggles for kiosk and big-screen use. |
| timeControl / refreshControl / modeControl / fullscreenControl / localeControl | show / hidden / disabled | show | Per-control visibility. toolbar=false hides the whole bar. |
| fullscreen | ?fullscreen=true | false | Enter fullscreen right after load. |
Auto-resize: the child ships @iframe-resizer/child. Parents may optionally load @iframe-resizer/parent@5; without it the embed falls back to internal scroll.
Themes and variables
Two themes implement the same ThemeModule contract (Tokens, Toolbar, WidgetCard, ErrorState): shadcn (Tailwind + Recharts, default) and legacy (antd + ECharts). Color mode is orthogonal: ?theme=legacy&mode=dark.
Variable handling covers QUERY-type candidates via POST /api/v2/variables/query, DYNAMIC-type candidates via GET /api/v1/fields/values, and expression substitution via POST /api/v5/substitute_vars. A var-<name> parameter wins over the dashboard default.
A custom theme adds themes/<name>/ plus one registry line. The core/ layer stays theme-agnostic and is never touched by a theme.
Proxy matrix
All SigNoz traffic goes through ALL /api/signoz/*, which strips the prefix, forwards to SIGNOZ_BASE_URL, injects SIGNOZ-API-KEY, strips inbound authorization and cookie headers, and returns the response with an x-embed-request-id. The proxy denies by default: only the routes below are allowed.
| Upstream | Method | Policy |
|---|---|---|
| /api/v1/dashboards/:id | GET | Pass through. PUT/POST/DELETE and /lock return 403 EMBED_READONLY. |
| /api/v3/query_range, /query_range/format | POST | Pass through (legacy panels). |
| /api/v4/query_range | POST | Pass through. |
| /api/v5/query_range | POST | Pass through (primary path). |
| /api/v5/substitute_vars | POST | Pass through. |
| /api/v2/variables/query | POST | Pass through (QUERY-type variable candidates). |
| /api/v1/fields/values | GET | Pass through (DYNAMIC-type variable candidates). |
| /api/v1/version, /api/v1/features | GET | Pass through or local stub. |
| /api/v1/rules, /alerts, /channels, /user/*, /org/*, /invite/* | ANY | 403 EMBED_BLOCKED, never proxied. |
| All other /api/* | ANY | Default-deny 403. |
Timeouts: 30s for query_range and 10s for dashboard metadata; the frontend cancels in-flight requests via AbortSignal. Global throttle is about 120 requests/minute/IP.
Error codes
Every failure maps to a typed code and an empty state with Retry where it makes sense. Codes live in packages/shared/errors.ts.
| Code | HTTP | Meaning |
|---|---|---|
| EMBED_MISSING_API_KEY | 401 | No key in the URL and no env default. |
| EMBED_INVALID_API_KEY | 401 | Upstream rejected the effective key. |
| EMBED_DASHBOARD_NOT_FOUND | 404 | Invalid id format (short-circuited) or unknown id upstream. |
| EMBED_UPSTREAM_UNAVAILABLE | 502 | Connection failure or timeout to SigNoz. |
| EMBED_READONLY | 403 | A write was attempted against a read-only route. |
| EMBED_BLOCKED | 403 | Route outside the allowlist. |
Operations
GET /healthz reports liveness and GET /metrics exposes Prometheus metrics (prom-client). JSON logs record the effective key source and an 8-char hash of the key, never the plaintext value; API key fields are stripped from query strings before serialization.
curl http://localhost:8080/healthz
curl http://localhost:8080/metrics | headThe single-image deployment is stateless: no query results are cached and no keys are stored. Scale by running more replicas behind any load balancer.
Security notes
- URL keys end up in browser history, proxy logs and referers. Use short-lived, read-only keys and rotate immediately on leak.
- The embed is public by design: CSP frame-ancestors * and CORS * are intentional.
- The frontend keeps keys in memory only. There is no localStorage, cookie or URL echo of the env default key.
- Write interfaces are not merely hidden: they are blocked at the proxy with 403.