/** * Grafana Dashboard API Client * * Fetches dashboard data and panel snapshots from Grafana. * Requires service account token with Viewer role. * * Setup: * 1. Grafana UI > Administration > Service Accounts > Add * 2. Create token, set GRAFANA_API_TOKEN env var * 3. Or use Grafana API directly: POST /api/serviceaccounts */ const GRAFANA_URL = process.env.GRAFANA_URL || 'https://grafana.riotpiao.com'; const GRAFANA_API_TOKEN = process.env.GRAFANA_API_TOKEN || ''; interface GrafanaHeaders { 'Content-Type': string; Authorization?: string; } function headers(): GrafanaHeaders { const h: GrafanaHeaders = { 'Content-Type': 'application/json' }; if (GRAFANA_API_TOKEN) { h.Authorization = `Bearer ${GRAFANA_API_TOKEN}`; } return h; } export interface Dashboard { uid: string; title: string; panels: Panel[]; } export interface Panel { id: number; title: string; type: string; gridPos: { h: number; w: number; x: number; y: number }; } export interface DashboardMeta { uid: string; title: string; url: string; slug: string; } /** * Get dashboard by UID */ export async function getDashboard(uid: string): Promise { const response = await fetch(`${GRAFANA_URL}/api/dashboards/uid/${uid}`, { headers: headers(), }); if (!response.ok) { throw new Error(`Grafana API error: ${response.status} ${await response.text()}`); } const data = await response.json(); return data.dashboard; } /** * List all dashboards */ export async function listDashboards(): Promise { const response = await fetch(`${GRAFANA_URL}/api/search?type=dash-db`, { headers: headers(), }); if (!response.ok) { throw new Error(`Grafana API error: ${response.status}`); } return response.json(); } /** * Query Prometheus datasource directly via Grafana proxy * Bypasses need for direct Prometheus access */ export async function queryPrometheus( query: string, start?: number, end?: number, step?: number ): Promise { const params = new URLSearchParams({ query, start: String(start || Math.floor(Date.now() / 1000) - 3600), end: String(end || Math.floor(Date.now() / 1000)), step: String(step || 60), }); const response = await fetch( `${GRAFANA_URL}/api/datasources/proxy/uid/prometheus/api/v1/query_range?${params}`, { headers: headers() } ); if (!response.ok) { throw new Error(`Prometheus query error: ${response.status}`); } return response.json(); } export interface PrometheusResult { status: string; data: { resultType: string; result: { metric: Record; values: [number, string][]; }[]; }; } /** * Get LLM-specific metrics */ export async function getLLMMetrics(): Promise { const [podsReady, cpuUsage, memoryUsage] = await Promise.all([ queryPrometheus('sum(kube_pod_status_ready{namespace="llm-serving",condition="true"})'), queryPrometheus('sum(rate(container_cpu_usage_seconds_total{namespace="llm-serving"}[5m])) by (pod)'), queryPrometheus('sum(container_memory_working_set_bytes{namespace="llm-serving"}) by (pod)'), ]); return { podsReady: extractLatestValue(podsReady), cpuByPod: extractSeriesLatest(cpuUsage), memoryByPod: extractSeriesLatest(memoryUsage), }; } export interface LLMMetrics { podsReady: number; cpuByPod: Record; memoryByPod: Record; } function extractLatestValue(result: PrometheusResult): number { const values = result.data?.result?.[0]?.values; if (!values || values.length === 0) return 0; return parseFloat(values[values.length - 1][1]); } function extractSeriesLatest(result: PrometheusResult): Record { const out: Record = {}; for (const series of result.data?.result || []) { const pod = series.metric.pod || 'unknown'; const values = series.values; if (values && values.length > 0) { out[pod] = parseFloat(values[values.length - 1][1]); } } return out; } /** * Generate iframe embed URL for a panel * Requires Grafana security.allow_embedding=true */ export function getPanelEmbedUrl( dashboardUid: string, panelId: number, from = 'now-6h', to = 'now', refresh = '30s' ): string { return `${GRAFANA_URL}/d-solo/${dashboardUid}?panelId=${panelId}&from=${from}&to=${to}&refresh=${refresh}`; } /** * Generate full dashboard URL */ export function getDashboardUrl(dashboardUid: string): string { return `${GRAFANA_URL}/d/${dashboardUid}`; }