"use client"; import { useEffect, useState } from "react"; import { Activity, Copy, Download } from "lucide-react"; import { api } from "@/lib/client-api"; import { prometheusConfig, type PrometheusStatus } from "@/shared/prometheus"; import { grafanaDashboard } from "@/shared/grafana-dashboard"; import { useI18n } from "./I18nProvider"; export function PrometheusIntegration() { const { t, locale } = useI18n(); const [status, setStatus] = useState(null); const [token, setToken] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [message, setMessage] = useState(""); const [confirm, setConfirm] = useState<"rotate" | "disable" | null>(null); async function load() { try { setStatus(await api("moderation/prometheus")); setError(""); } catch (e) { setError((e as Error).message); } } useEffect(() => { void load(); }, []); async function configure(action: "rotate" | "disable") { setBusy(true); setError(""); setMessage(""); try { const result = await api( "moderation/prometheus", { action }, ); const { token: nextToken, ...nextStatus } = result; setStatus(nextStatus); setToken(nextToken); setConfirm(null); setMessage( action === "rotate" ? "New metrics token generated. Copy the config now; it cannot be recovered later." : "Metrics access disabled. Existing scrape credentials no longer work.", ); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } async function copy(value: string) { try { await navigator.clipboard.writeText(value); setMessage("Copied to clipboard."); setError(""); } catch { setError("Could not copy. Select the text and copy it manually."); } } function dashboard() { const url = URL.createObjectURL( new Blob([JSON.stringify(grafanaDashboard(), null, 2)], { type: "application/json", }), ); const link = document.createElement("a"); link.href = url; link.download = "minegeo-grafana-dashboard.json"; link.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); } const config = status && token && status.endpoint.startsWith("https:") ? prometheusConfig(status.endpoint, token) : ""; return (

{t( "Track games, maps, multiplayer, uploads, and reports in your own Grafana dashboard. Admin permission is required to manage access.", )}

{t( "Only aggregate counts are exported. Usernames, messages, emails, and map identifiers are excluded.", )}

{!status && !error &&

{t("Loading metrics setup…")}

} {status && ( <>

{status.enabled ? t("Metrics enabled") : t("Metrics disabled")}

{t("Last successful scrape: {time}", { time: status.lastScrapedAt ? new Date(status.lastScrapedAt).toLocaleString( locale === "ru" ? "ru-RU" : "en-GB", ) : t("Not scraped yet"), })}

{!status.endpoint.startsWith("https:") && (

{t( "Use the hosted HTTPS site to generate a production scrape config.", )}

)}
  1. {t( "Generate a token below, then copy the config into prometheus.yml. If scrape_configs already exists, add only the MineGeo job beneath it.", )}
  2. {t( "Reload or restart Prometheus. Open its Targets page and check that the minegeo job is UP.", )}
  3. {t( "In Grafana, add your Prometheus server as a data source. Import the downloaded dashboard JSON and select that data source.", )}

{t( "History starts with the first scrape. Counts are cached for 30 seconds; room states are not online presence. This integration does not export page views or Vercel CPU statistics.", )}

{status.enabled && ( )}
{confirm && (

{confirm === "rotate" ? t( "Rotating immediately invalidates the old token. Update Prometheus with the new config afterward.", ) : t( "Disabling immediately stops all authenticated scrapes. Prometheus will report this target as DOWN.", )}

)} {token && (

{t( "Keep this config private. The token is shown once and grants read-only access to aggregate metrics. Rotating or disabling it revokes access.", )}

{config ? ( <>