Files
server-manager/core/grafana_client.py
chrome-storm-c442 8ff62d8f11 v1.9.42: full Grafana/Prometheus GUI & CLI improvements
Grafana tab:
- Datasources table (Name, Type, URL, Default)
- Open Grafana button (opens browser)
- Switch to AlertManager endpoint for real-time active alerts

Prometheus tab:
- Quick query buttons (up, CPU, Memory)
- Metrics browser popup with filter (loads all metric names)
- Rules section (recording + alerting rules Treeview)

CLI:
- --grafana-datasources ALIAS
- --prom-rules ALIAS

i18n: 28 new keys (EN/RU/ZH)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:23:33 -05:00

187 lines
6.1 KiB
Python

"""
Grafana API client for ServerManager.
Provides dashboard listing, alert management, datasource queries,
and annotation creation via Grafana HTTP API.
"""
from __future__ import annotations
from typing import Any
from core.logger import log
class GrafanaClient:
"""Client for interacting with a Grafana instance via its HTTP API."""
def __init__(self, server: dict) -> None:
"""
Initialize the Grafana client.
Args:
server: dict with keys: ip, port, api_token (or user+password), use_ssl
"""
self.ip: str = server["ip"]
self.port: int = int(server["port"])
self.api_token: str = server.get("api_token", "")
self.user: str = server.get("user", "")
self.password: str = server.get("password", "")
self.use_ssl: bool = bool(server.get("use_ssl", False))
scheme = "https" if self.use_ssl else "http"
self.base_url: str = f"{scheme}://{self.ip}:{self.port}"
self.headers: dict[str, str] = {"Content-Type": "application/json"}
self.auth: tuple[str, str] | None = None
if self.api_token:
self.headers["Authorization"] = f"Bearer {self.api_token}"
elif self.user and self.password:
self.auth = (self.user, self.password)
self.timeout: int = 10
def _get(self, path: str, params: dict | None = None) -> Any:
"""Send a GET request to the Grafana API."""
import requests
url = f"{self.base_url}{path}"
log.debug("Grafana GET %s", url)
resp = requests.get(
url, headers=self.headers, params=params, auth=self.auth, timeout=self.timeout
)
resp.raise_for_status()
return resp.json()
def _post(self, path: str, json_data: dict | None = None) -> Any:
"""Send a POST request to the Grafana API."""
import requests
url = f"{self.base_url}{path}"
log.debug("Grafana POST %s", url)
resp = requests.post(
url, headers=self.headers, json=json_data, auth=self.auth, timeout=self.timeout
)
resp.raise_for_status()
return resp.json()
def check_connection(self) -> bool:
"""
Check connectivity to Grafana via GET /api/health.
Returns:
True if Grafana responds successfully, False otherwise.
"""
try:
result = self._get("/api/health")
healthy = result.get("database", "") == "ok"
log.info("Grafana health check: %s", "OK" if healthy else "FAIL")
return healthy
except Exception as exc:
log.error("Grafana health check failed: %s", exc)
return False
def list_dashboards(self) -> list[dict]:
"""
List all dashboards via GET /api/search.
Returns:
List of dicts with keys: uid, title, folder, url.
"""
try:
results = self._get("/api/search", params={"type": "dash-db"})
dashboards = [
{
"uid": d.get("uid", ""),
"title": d.get("title", ""),
"folder": d.get("folderTitle", ""),
"url": d.get("url", ""),
}
for d in results
]
log.info("Grafana: found %d dashboards", len(dashboards))
return dashboards
except Exception as exc:
log.error("Grafana list_dashboards failed: %s", exc)
return []
def get_dashboard(self, uid: str) -> dict:
"""
Get a single dashboard by UID via GET /api/dashboards/uid/{uid}.
Args:
uid: Dashboard UID string.
Returns:
Full dashboard JSON dict, or empty dict on error.
"""
try:
result = self._get(f"/api/dashboards/uid/{uid}")
log.info("Grafana: loaded dashboard '%s'", uid)
return result
except Exception as exc:
log.error("Grafana get_dashboard(%s) failed: %s", uid, exc)
return {}
def list_alerts(self) -> list[dict]:
"""
List provisioned alert rules via GET /api/v1/provisioning/alert-rules.
Returns:
List of alert rule dicts, or empty list on error.
"""
try:
results = self._get("/api/v1/provisioning/alert-rules")
log.info("Grafana: found %d alert rules", len(results))
return results
except Exception as exc:
log.error("Grafana list_alerts failed: %s", exc)
return []
def get_active_alerts(self) -> list[dict]:
"""List active (firing) alerts via AlertManager endpoint."""
try:
results = self._get("/api/alertmanager/grafana/api/v2/alerts")
log.info("Grafana: %d active alerts", len(results))
return results
except Exception as exc:
log.error("Grafana get_active_alerts failed: %s", exc)
return []
def list_datasources(self) -> list[dict]:
"""
List all datasources via GET /api/datasources.
Returns:
List of datasource dicts, or empty list on error.
"""
try:
results = self._get("/api/datasources")
log.info("Grafana: found %d datasources", len(results))
return results
except Exception as exc:
log.error("Grafana list_datasources failed: %s", exc)
return []
def create_annotation(self, text: str, tags: list[str] | None = None) -> dict:
"""
Create a global annotation via POST /api/annotations.
Args:
text: Annotation text/description.
tags: Optional list of tag strings.
Returns:
API response dict, or empty dict on error.
"""
payload: dict[str, Any] = {"text": text}
if tags:
payload["tags"] = tags
try:
result = self._post("/api/annotations", json_data=payload)
log.info("Grafana: created annotation id=%s", result.get("id"))
return result
except Exception as exc:
log.error("Grafana create_annotation failed: %s", exc)
return {}