From e783d2c07be426dfb1253274a07b4ade13d12dc9 Mon Sep 17 00:00:00 2001 From: Developer Date: Tue, 16 Jun 2026 15:20:26 +0000 Subject: [PATCH] refactor(observability): decouple Grafana from Manage - Replace embedded Grafana iframes in ObservabilityPage with external Grafana link cards. Grafana URL is configurable via VITE_GRAFANA_URL (frontend) or GRAFANA_URL (backend env exposed to frontend build). - Remove the grafana scrape job from monitoring/prometheus/prometheus.yml so the main Prometheus stack no longer depends on Grafana. - Pass GRAFANA_URL through docker-compose.yml and docker-compose.dev.yml. --- docker-compose.dev.yml | 1 + docker-compose.yml | 1 + frontend/src/components/ObservabilityPage.tsx | 267 ++++-------------- monitoring/prometheus/prometheus.yml | 5 - 4 files changed, 52 insertions(+), 222 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 53902c7..29190a0 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -16,6 +16,7 @@ services: SSH_KNOWN_HOSTS_PATH: /app/backend/.cache/known_hosts PROMETHEUS_FILE_SD_DIR: /app/backend/.cache/prometheus-file-sd ALERTMANAGER_URL: ${ALERTMANAGER_URL:-http://alertmanager:9093} + GRAFANA_URL: ${GRAFANA_URL:-http://grafana:3000} ports: - "8000:8000" volumes: diff --git a/docker-compose.yml b/docker-compose.yml index a2a4cc9..72e2ee5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,7 @@ services: SSH_KNOWN_HOSTS_PATH: /app/backend/.cache/known_hosts PROMETHEUS_FILE_SD_DIR: ${PROMETHEUS_FILE_SD_DIR:-/app/backend/.cache/prometheus-file-sd} ALERTMANAGER_URL: ${ALERTMANAGER_URL:-http://alertmanager:9093} + GRAFANA_URL: ${GRAFANA_URL:-http://grafana:3000} volumes: - ${BACKEND_CACHE_DIR:-./backend-cache}:/app/backend/.cache restart: unless-stopped diff --git a/frontend/src/components/ObservabilityPage.tsx b/frontend/src/components/ObservabilityPage.tsx index 652e39b..60e845a 100644 --- a/frontend/src/components/ObservabilityPage.tsx +++ b/frontend/src/components/ObservabilityPage.tsx @@ -1,13 +1,4 @@ -import { - Component, - useEffect, - useMemo, - useRef, - useState, - type ElementType, - type ErrorInfo, - type ReactNode, -} from "react"; +import { useMemo, useState, type ElementType, type ReactNode } from "react"; import { Link } from "react-router-dom"; import { Activity, @@ -17,7 +8,6 @@ import { ChevronDown, ExternalLink, Inbox, - PanelTop, Radio, RefreshCw, Server, @@ -47,11 +37,9 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import type { - AlertmanagerAlert, - MonitoringMachine, - PrometheusTarget, -} from "../types"; +import type { AlertmanagerAlert, MonitoringMachine, PrometheusTarget } from "../types"; + +const GRAFANA_BASE_URL = import.meta.env.VITE_GRAFANA_URL || "http://localhost:3000"; function severityVariant( severity: string, @@ -124,9 +112,7 @@ function EmptyState({
{title}
-
- {description} -
+
{description}
{action ?
{action}
: null}
); @@ -164,9 +150,7 @@ function AlertItem({ alert }: { alert: AlertmanagerAlert }) {
{alert.name}
- - {alert.severity} - + {alert.severity}
@@ -184,8 +168,7 @@ function AlertItem({ alert }: { alert: AlertmanagerAlert }) {
{alert.description && (
- Description:{" "} - {alert.description} + Description: {alert.description}
)}
@@ -245,119 +228,30 @@ function TargetsTable({ targets }: { targets: PrometheusTarget[] }) { ); } -class GrafanaErrorBoundary extends Component< - { children: ReactNode; fallback: ReactNode }, - { hasError: boolean } -> { - constructor(props: { children: ReactNode; fallback: ReactNode }) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError() { - return { hasError: true }; - } - - componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error("Grafana panel error:", error, errorInfo); - } - - render() { - if (this.state.hasError) { - return this.props.fallback; - } - return this.props.children; - } -} - -function GrafanaPanel({ src, title }: { src: string; title: string }) { - const [loaded, setLoaded] = useState(false); - const [failed, setFailed] = useState(false); - const [iframeKey, setIframeKey] = useState(0); - const timerRef = useRef | null>(null); - - useEffect(() => { - timerRef.current = setTimeout(() => setFailed(true), 10_000); - return () => { - if (timerRef.current) clearTimeout(timerRef.current); - }; - }, [iframeKey]); - - const handleLoad = () => { - if (timerRef.current) clearTimeout(timerRef.current); - setLoaded(true); - setFailed(false); - }; - - const handleError = () => { - if (timerRef.current) clearTimeout(timerRef.current); - setFailed(true); - }; - - const reload = () => { - setLoaded(false); - setFailed(false); - setIframeKey((k) => k + 1); - }; - - if (!src) { - return ( - - ); - } - - const fallback = ( - - - -
- } - /> - ); - +function GrafanaLinkCard({ + title, + description, + href, +}: { + title: string; + description: string; + href: string; +}) { return ( - -
- {!loaded && !failed && ( -
- -
- )} - {failed ? ( -
- {fallback} -
- ) : ( -