Replace Grafana iframe panel with server-side chart widget

The iframe-based 'panel' widget didn't work: the browser couldn't
authenticate against the OIDC-protected Grafana (Authentik), and
iframes can't carry Bearer tokens or share cross-origin session
cookies. Result: blank iframe or login redirect.

Replace it with a 'chart' widget that queries Grafana's datasource
API server-side:

Backend (GrafanaWidgetSource): POSTs to /api/ds/query with the stored
api_key (which bypasses OIDC), using the widget's configured PromQL
query, datasource_uid, time range, and resolution. Normalizes Grafana's
frame-based response into a simple {series: [{label, points: [{t, v}]}]}
shape. The api_key is never exposed to the browser.

Frontend (GrafanaChartWidget): renders the series data as a recharts
LineChart with dark-mode-aware colors (Tailwind --chart-* tokens),
responsive container, custom tooltip, and per-series lines. Loading
skeleton, error Alert, and empty state. recharts ^3.9.2 added.

The 'link' widget kind (deep-link URL) is unchanged. The 'panel' kind
and GrafanaPanelWidget are fully removed.

Backend: 279 tests pass (+1 net: -2 panel + 3 chart). Frontend: 127
tests pass (net 0: -3 panel + 3 chart). Lint/build green both sides.
This commit is contained in:
Developer
2026-07-06 10:19:57 +00:00
parent b877a32ad8
commit 447775048c
15 changed files with 1023 additions and 185 deletions
@@ -26,13 +26,15 @@ class GrafanaLinkWidgetConfig(WidgetConfigBase):
panel_id: int | None = None
class GrafanaPanelWidgetConfig(WidgetConfigBase):
"""Embed a single Grafana panel via iframe."""
class GrafanaChartWidgetConfig(WidgetConfigBase):
"""Render a time-series chart from a Grafana datasource query."""
dashboard_uid: str
panel_id: int
datasource_uid: str = "prometheus"
query: str = ""
from_ts: str = "now-1h"
to_ts: str = "now"
interval_ms: int = 30_000
max_data_points: int = 100
DEFINITION = ServiceDefinition(
@@ -53,12 +55,19 @@ DEFINITION = ServiceDefinition(
refresh_interval_ms=0,
),
widget_kind(
kind="panel",
name="Panel embed",
description="Embed a Grafana panel directly.",
model_cls=GrafanaPanelWidgetConfig,
default_config={"dashboard_uid": "", "panel_id": 1, "from_ts": "now-1h", "to_ts": "now"},
refresh_interval_ms=0,
kind="chart",
name="Chart",
description="Live time-series chart from a Grafana datasource query.",
model_cls=GrafanaChartWidgetConfig,
default_config={
"datasource_uid": "prometheus",
"query": "",
"from_ts": "now-1h",
"to_ts": "now",
"interval_ms": 30_000,
"max_data_points": 100,
},
refresh_interval_ms=60_000,
),
],
)