From 733e3149c45ab28b59f1ab18b28484db7cd7f0a5 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 13 Jun 2026 20:55:19 -0400 Subject: [PATCH] Add Prometheus-backed home server status --- backend/app.py | 113 ++++++++++++++++++++++++++++++++++++++++++ site/index.html | 25 +++++++++- site/server-status.js | 43 ++++++++++++++++ 3 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 site/server-status.js diff --git a/backend/app.py b/backend/app.py index b857724..44d790c 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1939,3 +1939,116 @@ def hardcore_leaderboard_points_combined(): return jsonify(rows[:100]) # --- end Hardcore stats aggregator ------------------------------------------- + + +@app.get("/api/server-status") +def public_server_status(): + import json + import os + import urllib.parse + import urllib.request + + prometheus_url = os.environ.get("PROMETHEUS_URL", "http://5.0.0.20:9090").rstrip("/") + + def prom_value(query): + url = prometheus_url + "/api/v1/query?" + urllib.parse.urlencode({"query": query}) + try: + with urllib.request.urlopen(url, timeout=2.5) as resp: + data = json.loads(resp.read().decode("utf-8")) + except Exception: + return 0 + + if data.get("status") != "success": + return 0 + + total = 0.0 + for result in data.get("data", {}).get("result", []): + value = result.get("value", [None, "0"])[1] + try: + total += float(value) + except (TypeError, ValueError): + pass + + return int(total) + + def q(metric, labels): + label_text = ",".join(f'{k}="{v}"' for k, v in labels.items()) + return f'sum({metric}{{{label_text}}}) or vector(0)' + + def newserv(region, service, ship, version): + return prom_value(q("pso_newserv_clients_connected", { + "region": region, + "service": service, + "ship": ship, + "version": version, + })) + + def adhoc(region, game): + return prom_value(q("psppeeps_adhoc_connected_clients_by_product", { + "region": region, + "service": f"{region}-psppeeps-adhoc", + "ship": "psp", + "game": game, + })) + + us = { + "alis_v2": newserv("us", "us-newserv-live", "live", "v2"), + "alis_v3": newserv("us", "us-newserv-live", "live", "v3"), + "alis_bb": newserv("us", "us-newserv-live", "live", "v4"), + "abion_hcbb": newserv("us", "us-newserv-hardcore", "hardcore", "v4"), + "adhoc_psp1": adhoc("us", "psp1"), + "adhoc_psp2i": adhoc("us", "psp2i"), + } + + eu = { + "palma_v2": newserv("eu", "eu-newserv-live", "live", "v2"), + "palma_v3": newserv("eu", "eu-newserv-live", "live", "v3"), + "palma_bb": newserv("eu", "eu-newserv-live", "live", "v4"), + "aiedo_hcbb": newserv("eu", "eu-newserv-hardcore", "hardcore", "v4"), + "adhoc_psp1": adhoc("eu", "psp1"), + "adhoc_psp2i": adhoc("eu", "psp2i"), + } + + us_total = sum(us.values()) + eu_total = sum(eu.values()) + + return jsonify({ + "servers": [ + { + "label": "US Server", + "players": us_total, + "ships": [ + {"label": "Alis", "rows": [ + {"label": "V2", "players": us["alis_v2"]}, + {"label": "V3", "players": us["alis_v3"]}, + {"label": "BB", "players": us["alis_bb"]}, + ]}, + {"label": "Abion", "rows": [ + {"label": "HC/BB", "players": us["abion_hcbb"]}, + ]}, + {"label": "AdHoc-US", "rows": [ + {"label": "PSP1", "players": us["adhoc_psp1"]}, + {"label": "PSP2i", "players": us["adhoc_psp2i"]}, + ]}, + ], + }, + { + "label": "EU Server", + "players": eu_total, + "ships": [ + {"label": "Palma", "rows": [ + {"label": "V2", "players": eu["palma_v2"]}, + {"label": "V3", "players": eu["palma_v3"]}, + {"label": "BB", "players": eu["palma_bb"]}, + ]}, + {"label": "Aiedo", "rows": [ + {"label": "HC/BB", "players": eu["aiedo_hcbb"]}, + ]}, + {"label": "AdHoc-EU", "rows": [ + {"label": "PSP1", "players": eu["adhoc_psp1"]}, + {"label": "PSP2i", "players": eu["adhoc_psp2i"]}, + ]}, + ], + }, + ], + }) diff --git a/site/index.html b/site/index.html index 935531d..28d0285 100644 --- a/site/index.html +++ b/site/index.html @@ -10,6 +10,7 @@ +
@@ -53,9 +54,29 @@

Server Status

-
US Server
+
US Server0 Players
+
Alis
+
V20 Players
+
V30 Players
+
BB0 Players
+
Abion
+
HC/BB0 Players
+
AdHoc-US
+
PSP10 Players
+
PSP2i0 Players
-
Alis0 Players
+
EU Server0 Players
+
Palma
+
V20 Players
+
V30 Players
+
BB0 Players
+
Aiedo
+
HC/BB0 Players
+
AdHoc-EU
+
PSP10 Players
+
PSP2i0 Players
+
+
Alis0 Players
V20 Players
V30 Players
BB0 Players
diff --git a/site/server-status.js b/site/server-status.js new file mode 100644 index 0000000..2697c26 --- /dev/null +++ b/site/server-status.js @@ -0,0 +1,43 @@ +(() => { + const list = document.querySelector(".status-list"); + if (!list) return; + + const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'", + }[ch])); + + const players = (value) => { + const n = Number(value || 0); + return `${n.toLocaleString()} ${n === 1 ? "Player" : "Players"}`; + }; + + const render = (data) => { + const servers = Array.isArray(data?.servers) ? data.servers : []; + if (!servers.length) return; + + list.innerHTML = servers.map((server) => ` +
+ ${esc(server.label)}${players(server.players)} +
+ ${(server.ships || []).map((ship) => ` +
+ ${esc(ship.label)} +
+ ${(ship.rows || []).map((row) => ` +
+ ${esc(row.label)}${players(row.players)} +
+ `).join("")} + `).join("")} + `).join(""); + }; + + fetch("/api/server-status", { cache: "no-store" }) + .then((res) => res.ok ? res.json() : null) + .then((data) => data && render(data)) + .catch(() => {}); +})();