// Grafana's classic import model prompts for the user's Prometheus data source. // This template contains query definitions only, never credentials or player data. const datasource = { type: "prometheus", uid: "${DS_PROMETHEUS}" }; const filter = 'job="minegeo",instance=~"$instance"'; const panels = [ [ "Scrape health", "stat", `min(up{${filter}})`, "short", "Shows whether Prometheus can scrape MineGeo. 1 = healthy, 0 = failed.", ], [ "Registered accounts", "stat", `sum(minegeo_accounts{${filter}})`, "short", "Currently retained profiles.", ], [ "Published maps", "stat", `sum(minegeo_maps{${filter},state="published"})`, "short", "Maps available in the public catalog.", ], [ "Published panoramas", "stat", `sum(minegeo_published_panoramas{${filter}})`, "short", "Playable panorama locations in published maps.", ], [ "Solo games started · last 24h", "stat", `sum(minegeo_games_started_24h{${filter}})`, "short", "Rolling 24-hour window, not a counter or page-view total.", ], [ "Parties created · last 24h", "stat", `sum(minegeo_rooms_created_24h{${filter}})`, "short", "Rolling 24-hour count of created multiplayer rooms.", ], [ "Map catalog over time", "timeseries", `sum by (state) (minegeo_maps{${filter}})`, "short", "Current retained map inventory. Counts can decrease after deletion.", ], [ "Solo games by difficulty and completion", "timeseries", `sum by (difficulty,status) (minegeo_games{${filter}})`, "short", "Incomplete includes abandoned games. Complete means all five rounds ended.", ], [ "Multiplayer room lifecycle", "timeseries", `sum by (state) (minegeo_rooms{${filter}})`, "short", "Stored room lifecycle states. These are not a count of online users.", ], [ "Upload processing", "timeseries", `sum by (state) (minegeo_upload_jobs{${filter}})`, "short", "Queued, running, completed, failed and cancelled jobs.", ], [ "Player bug reports", "timeseries", `sum by (state) (minegeo_bug_reports{${filter}})`, "short", "Retained reports. Closed reports are deleted after three days, so counts may fall.", ], [ "Content moderation reports", "timeseries", `sum by (state) (minegeo_content_reports{${filter}})`, "short", "Open and resolved map-content reports.", ], [ "Discord delivery", "timeseries", `sum by (state) (minegeo_discord_announcements{${filter}})`, "short", "Retained announcement delivery states.", ], [ "Exporter read time", "timeseries", `max(minegeo_exporter_read_duration_seconds{${filter}})`, "s", "Time spent reading and validating a database snapshot; not site request latency.", ], [ "Snapshot age", "timeseries", `time() - min(minegeo_snapshot_timestamp_seconds{${filter}})`, "s", "Snapshot age grows when scraping stops. The database caches aggregates for 30 seconds.", ], ] as const; export function grafanaDashboard() { return { __inputs: [ { name: "DS_PROMETHEUS", label: "Prometheus", description: "Choose the Prometheus data source that scrapes MineGeo.", type: "datasource", pluginId: "prometheus", pluginName: "Prometheus", }, ], id: null, uid: "minegeo-community", title: "MineGeo · Community analytics", version: 1, schemaVersion: 39, tags: ["minegeo", "prometheus"], timezone: "browser", editable: true, graphTooltip: 1, refresh: "1m", time: { from: "now-24h", to: "now" }, description: "Aggregate MineGeo product metrics. All MineGeo series are gauges; no personal data or per-process counters. History starts when Prometheus begins scraping.", templating: { list: [ { name: "instance", label: "MineGeo instance", type: "query", datasource, query: { query: 'label_values(minegeo_accounts{job="minegeo"}, instance)', refId: "instance", }, refresh: 1, multi: false, includeAll: false, sort: 1, }, ], }, panels: panels.map(([title, type, expr, unit, description], index) => { const stat = index < 6; return { id: index + 1, title, type, description, datasource, gridPos: stat ? { x: (index % 3) * 8, y: Math.floor(index / 3) * 4, w: 8, h: 4 } : { x: ((index - 6) % 2) * 12, y: 8 + Math.floor((index - 6) / 2) * 8, w: 12, h: 8, }, targets: [ { refId: "A", expr, datasource, legendFormat: "{{state}} {{difficulty}} {{status}}", range: !stat, instant: stat, }, ], fieldConfig: { defaults: { unit, min: 0, decimals: unit === "short" ? 0 : 2, color: { mode: stat ? "thresholds" : "palette-classic" }, thresholds: { mode: "absolute", steps: index === 0 ? [ { color: "red", value: null }, { color: "green", value: 1 }, ] : [{ color: "green", value: null }], }, ...(stat ? {} : { custom: { drawStyle: "line", lineInterpolation: "smooth", lineWidth: 2, fillOpacity: 12, showPoints: "never", spanNulls: false, }, }), }, overrides: [], }, options: stat ? { colorMode: "value", graphMode: "area", justifyMode: "auto", textMode: "auto", reduceOptions: { calcs: ["lastNotNull"], fields: "", values: false, }, } : { legend: { displayMode: "list", placement: "bottom", showLegend: true, }, tooltip: { mode: "multi", sort: "desc" }, }, }; }), }; }