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 = (
-
-
-
- Reload
-
-
-
-
- Open in Grafana
-
-
-
- }
- />
- );
-
+function GrafanaLinkCard({
+ title,
+ description,
+ href,
+}: {
+ title: string;
+ description: string;
+ href: string;
+}) {
return (
-
-
- {!loaded && !failed && (
-
-
-
- )}
- {failed ? (
-
- {fallback}
-
- ) : (
-
- )}
+
);
}
@@ -389,24 +283,20 @@ export function ObservabilityPage() {
const [selectedMachineId, setSelectedMachineId] = useState
("");
const selectedMachine = useMemo(
- () =>
- machines.find((m) => m.id === selectedMachineId) ?? machines[0] ?? null,
+ () => machines.find((m) => m.id === selectedMachineId) ?? machines[0] ?? null,
[machines, selectedMachineId],
);
- const grafanaBase = "/grafana";
-
const nodeExporterDashboardUrl = useMemo(() => {
if (!selectedMachine) return "";
const instance = `${selectedMachine.host || "localhost"}:9100`;
- return `${grafanaBase}/d/node-exporter-overview/node-exporter-overview?kiosk&var-instance=${encodeURIComponent(instance)}`;
+ return `${GRAFANA_BASE_URL}/d/node-exporter-overview/node-exporter-overview?kiosk&var-instance=${encodeURIComponent(instance)}`;
}, [selectedMachine]);
const logsUrl = useMemo(() => {
if (!selectedMachine) return "";
- const container =
- selectedMachine.mode === "local" ? "backend" : selectedMachine.name;
- return `${grafanaBase}/explore?orgId=1&left=${encodeURIComponent(
+ const container = selectedMachine.mode === "local" ? "backend" : selectedMachine.name;
+ return `${GRAFANA_BASE_URL}/explore?orgId=1&left=${encodeURIComponent(
JSON.stringify({
datasource: "Loki",
queries: [{ refId: "A", expr: `{container="${container}"}` }],
@@ -453,8 +343,8 @@ export function ObservabilityPage() {
Observability
- Unified view of metrics, logs, and alerts from Prometheus, Grafana,
- Loki, and Alertmanager.
+ Unified view of metrics, logs, and alerts from Prometheus, Loki, and
+ Alertmanager. Deep dashboards live in Grafana.
@@ -462,13 +352,7 @@ export function ObservabilityPage() {
{statusError && (
-
+
)}
{alertsError && (
-
+
)}
{targetsError && (
-
+
)}
{machinesError && (
-
+
)}
@@ -532,8 +400,7 @@ export function ObservabilityPage() {
Alertmanager unreachable
- The UI cannot reach Alertmanager right now. Alerts shown here may be
- stale.
+ The UI cannot reach Alertmanager right now. Alerts shown here may be stale.
)}
@@ -567,12 +434,8 @@ export function ObservabilityPage() {
))}
{alertsSummary.total > alertsSummary.alerts.length && (
- {alertsSummary.total - alertsSummary.alerts.length} more
- alert
- {alertsSummary.total - alertsSummary.alerts.length === 1
- ? ""
- : "s"}{" "}
- in Alertmanager
+ {alertsSummary.total - alertsSummary.alerts.length} more alert
+ {alertsSummary.total - alertsSummary.alerts.length === 1 ? "" : "s"} in Alertmanager
)}
>
@@ -637,52 +500,22 @@ export function ObservabilityPage() {
{selectedMachine ? (
<>
-
-
-
-
>
) : (
Open Settings
diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml
index 324fecf..1c02edc 100644
--- a/monitoring/prometheus/prometheus.yml
+++ b/monitoring/prometheus/prometheus.yml
@@ -41,11 +41,6 @@ scrape_configs:
- targets:
- alertmanager:9093
- - job_name: grafana
- static_configs:
- - targets:
- - grafana:3000
-
- job_name: node-exporter-remote
file_sd_configs:
- files: