447775048c
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.
74 lines
1.9 KiB
Python
74 lines
1.9 KiB
Python
"""Grafana service definition."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from media_library_viewer_api.integrations.base import (
|
|
SecretField,
|
|
ServiceBaseUrl,
|
|
ServiceConfigBase,
|
|
ServiceDefinition,
|
|
WidgetConfigBase,
|
|
widget_kind,
|
|
)
|
|
|
|
|
|
class GrafanaConfig(ServiceConfigBase):
|
|
"""Non-secret Grafana connection config."""
|
|
|
|
base_url: ServiceBaseUrl
|
|
timeout_seconds: int = 5
|
|
|
|
|
|
class GrafanaLinkWidgetConfig(WidgetConfigBase):
|
|
"""Deep-link to a Grafana dashboard or panel."""
|
|
|
|
dashboard_uid: str
|
|
panel_id: int | None = None
|
|
|
|
|
|
class GrafanaChartWidgetConfig(WidgetConfigBase):
|
|
"""Render a time-series chart from a Grafana datasource query."""
|
|
|
|
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(
|
|
service_type="grafana",
|
|
name="Grafana",
|
|
description="Dashboards, metrics, and logs.",
|
|
config_model=GrafanaConfig,
|
|
secret_fields=[
|
|
SecretField(key="api_key", label="API key", helper="Service account token (optional)"),
|
|
],
|
|
widget_kinds=[
|
|
widget_kind(
|
|
kind="link",
|
|
name="Dashboard link",
|
|
description="Deep-link to a Grafana dashboard or panel.",
|
|
model_cls=GrafanaLinkWidgetConfig,
|
|
default_config={"dashboard_uid": ""},
|
|
refresh_interval_ms=0,
|
|
),
|
|
widget_kind(
|
|
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,
|
|
),
|
|
],
|
|
)
|