Add developer API, Prometheus metrics, and community links

This commit is contained in:
2026-10-12 19:59:37 +05:00
parent 3cc3136762
commit 018be5049e
50 changed files with 4087 additions and 31 deletions

236
shared/grafana-dashboard.ts Normal file
View File

@@ -0,0 +1,236 @@
// 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" },
},
};
}),
};
}