Custom Dashboards
Build any visualisation from your telemetry data by writing SQL and choosing a chart type. Dashboards are saved to your tenant's D1 database and shared across your team.
Screenshot needed
Dashboards list page: 3 dashboard cards in a grid. Card 1: 'Production Overview', 5 panels, updated 2h ago. Card 2: 'Payment Service', 3 panels, updated 1d ago. Card 3: 'API Performance', 2 panels. Each card has a Trash icon in top-right corner (visible on hover). '+ New Dashboard' button in top right.
Creating a dashboard
Click + New Dashboard, type a name, and press Enter or click Create. The empty dashboard opens immediately — add your first panel.
Adding panels
Click + Add panel to open the panel editor. Give it a title, optionally a description (shown as a tooltip on the panel header), pick a starter template or write your own SQL, choose a chart type, and size it on the 12-column grid.
Screenshot needed
Panel editor modal: Title field='P99 Latency by Service', SQL textarea showing a SELECT query with AVG(p99) grouped by service, Chart type buttons showing 'line' selected (cyan), Width buttons showing 'Full' selected. Preview button at bottom. Small preview table showing results: api-gateway 342ms, payment-service 1840ms.
Quick-start templates
The panel editor has a row of one-click templates for common queries — click one to fill in the SQL and switch to a matching chart type, then tweak it however you like:
| Template | What it shows | Chart |
|---|---|---|
| Request rate over time | Requests per hour, by service | line |
| Error rate by service | Percentage of requests that errored, per service | bar |
| P99 latency by service | From reported metrics, last 24h | bar |
| Total errors today | Single number — errors in the last 24h | stat |
| Slowest spans | Top 10 slowest spans in the last hour | table |
| Memory usage over time | Heap used (MB), by service | area |
| Uptime check latency | Response time per check, last 24h | line |
| Known services | Every service ever seen, with lifetime error rate | table |
Schema browser
Click Schema next to the SQL editor to open a sidebar listing every queryable table and column — click any column to insert it into your query. The same list, kept in sync with the actual database schema, is reproduced below.
Panel SQL
Any SELECT query against your hot index tables works:
-- Request rate over time
SELECT
(timestamp / 3600000) * 3600000 AS bucket,
service,
COUNT(*) AS requests
FROM recent_spans
WHERE timestamp > 1785150241875
GROUP BY bucket, service
ORDER BY bucket ASC
LIMIT 500
-- Error rate by service
SELECT
service,
COUNT(*) AS total,
SUM(is_error) AS errors,
ROUND(SUM(is_error) * 100.0 / COUNT(*), 1) AS error_pct
FROM recent_spans
GROUP BY service
ORDER BY error_pct DESC
LIMIT 100
-- p99 latency from metrics
SELECT
service,
AVG(p99) AS avg_p99,
MAX(p99) AS max_p99
FROM metrics_samples
WHERE metric_name = 'http.server.request.duration'
AND timestamp > 1785085441875
GROUP BY service
LIMIT 100LIMIT clause — the query API rejects anything without one (max 1000) to prevent accidental full-table scans.Chart types
| Type | Best for |
|---|---|
| Line | Time-series data — latency, request rate over time |
| Bar | Comparing values across categories — errors per service |
| Area | Volume over time — memory usage, throughput |
| Stat | Single large number — current error count, total requests today |
| Table | Raw tabular data — top 10 slowest spans, recent errors |
Display options
Line, bar, and area panels support additional display config:
| Option | Effect |
|---|---|
| Unit | Formats axis ticks and tooltip values as ms, %, count (k/M), or bytes (KB/MB/GB) |
| Axis label | Adds a label to the Y axis |
| Stacking | Bar and area charts only — stacks series instead of overlapping/grouping them |
| Series colors | Override the default palette per series, once a preview has run |
Screenshot needed
Dashboard with 2 panels: top-left full-width line chart 'Request Rate' with 3 service lines (api-gateway=cyan, payment-service=purple, user-service=emerald). Bottom: stat panel on left showing '847' (total errors today) and table panel on right showing 5 rows of slowest spans.
Saving
After adding or editing panels, click Save in the top right. The dashboard is saved to D1 and immediately available to all team members.
Full schema reference
Every table available to panel SQL, and every column on it:
recent_spans
Hot index of traces/logs — last 48h only (older data lives in R2 cold storage).
| Column | Type | Description |
|---|---|---|
trace_id | TEXT | Distributed trace ID — groups spans belonging to one request. |
span_id | TEXT | Unique ID for this span. |
parent_span_id | TEXT | Parent span ID, or NULL for a root span. |
service | TEXT | Service name that emitted this span. |
span_name | TEXT | Operation name, e.g. "GET /api/users". |
span_kind | INTEGER | OTel span.kind — 1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER. |
status_code | INTEGER | HTTP status code, if applicable. |
duration_ms | REAL | Span duration in milliseconds. |
is_error | INTEGER | 1 if this span is an error, 0 otherwise. |
severity | TEXT | Log level: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. |
timestamp | INTEGER | Unix epoch milliseconds. |
environment | TEXT | Deployment environment, e.g. production. |
body | TEXT | Log message body (for log-type spans). |
host_name | TEXT | Host the span was emitted from. |
pod_name | TEXT | Kubernetes pod name, if applicable. |
node_name | TEXT | Kubernetes node name, if applicable. |
db_system | TEXT | Database system for CLIENT spans, e.g. postgresql, redis. |
peer_service | TEXT | Downstream service name for CLIENT spans. |
scope_name | TEXT | OTel instrumentation scope, e.g. @opentelemetry/instrumentation-fetch. |
attrs_json | TEXT | Extra span attributes as JSON (capped at 2048 bytes). |
metrics_samples
OTel metrics — 7 day TTL.
| Column | Type | Description |
|---|---|---|
id | INTEGER | Row ID. |
metric_name | TEXT | OTel metric name, e.g. http.server.request.duration. |
metric_type | TEXT | gauge, sum, or histogram. |
service | TEXT | Service that reported this sample. |
environment | TEXT | Deployment environment. |
host_name | TEXT | Host the sample was reported from. |
value | REAL | Sample value (gauges/sums). |
count | INTEGER | Sample count (histograms). |
p50 | REAL | Median value (histograms). |
p99 | REAL | 99th percentile value (histograms). |
labels_json | TEXT | Extra metric labels as JSON. |
timestamp | INTEGER | Unix epoch milliseconds. |
services
Persistent registry of every service seen — survives the 48h recent_spans TTL.
| Column | Type | Description |
|---|---|---|
name | TEXT | Service name (primary key). |
environment | TEXT | Deployment environment. |
first_seen | INTEGER | Unix epoch seconds — first span ever seen. |
last_seen | INTEGER | Unix epoch seconds — most recent span seen. |
total_spans | INTEGER | Lifetime span count. |
error_spans | INTEGER | Lifetime error span count. |
uptime_checks
Configured uptime monitors.
| Column | Type | Description |
|---|---|---|
id | TEXT | Check ID. |
name | TEXT | Display name. |
url | TEXT | URL being monitored. |
interval_minutes | INTEGER | How often the check runs. |
expected_status | INTEGER | HTTP status considered "up". |
enabled | INTEGER | 1 if the check is active. |
notify_on_down | INTEGER | 1 if a status change should notify. |
last_status | TEXT | 'up', 'down', or NULL if never checked. |
created_at | INTEGER | Unix epoch milliseconds. |
uptime_results
Historical probe results for each uptime check.
| Column | Type | Description |
|---|---|---|
id | INTEGER | Row ID. |
check_id | TEXT | References uptime_checks.id. |
timestamp | INTEGER | Unix epoch milliseconds. |
status_code | INTEGER | HTTP status code returned, if any. |
latency_ms | INTEGER | Response latency in milliseconds. |
is_up | INTEGER | 1 if the check passed. |
error_message | TEXT | Failure reason, if any. |
alert_rules
Configured alert rules.
| Column | Type | Description |
|---|---|---|
id | TEXT | Rule ID. |
name | TEXT | Display name. |
metric | TEXT | Metric name being evaluated. |
service | TEXT | Service filter, or NULL for all. |
operator | TEXT | 'gt', 'lt', or 'anomaly'. |
threshold | REAL | Threshold value for gt/lt rules. |
z_threshold | REAL | Z-score threshold for anomaly rules. |
window_minutes | INTEGER | Evaluation window. |
state | TEXT | 'ok', 'pending', 'firing', 'cooldown', or 'resolved'. |
last_fired_at | INTEGER | Unix epoch milliseconds, or NULL. |
notification_channels | TEXT | JSON array of notification channels. |
dashboards
Saved custom dashboards.
| Column | Type | Description |
|---|---|---|
id | TEXT | Dashboard ID. |
name | TEXT | Display name. |
panels_json | TEXT | JSON array of panel definitions. |
created_at | INTEGER | Unix epoch seconds. |
updated_at | INTEGER | Unix epoch seconds. |