Merge pull request 'Add Prometheus-backed home server status' (#10) from feature/home-server-status-prometheus into main

Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
2026-06-14 01:06:28 -04:00
5 changed files with 263 additions and 38 deletions
+114
View File
@@ -1939,3 +1939,117 @@ def hardcore_leaderboard_points_combined():
return jsonify(rows[:100])
# --- end Hardcore stats aggregator -------------------------------------------
@app.get("/server-status")
@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"]},
]},
],
},
],
})
+1 -1
View File
@@ -65,7 +65,7 @@
return;
}
const top = rows.slice(0, 5);
const top = rows.slice(0, 10);
if (!top.length) {
list.innerHTML = `<li><span class="rank">1.</span><span>—</span></li>`;
+45 -37
View File
@@ -8,8 +8,9 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css?v=home-hardcore-preline-20260610-1">
<link rel="stylesheet" href="style.css?v=home-status-two-boxes-2">
<script src="app.js?v=account-status-label-20260609" defer></script>
<script src="server-status.js?v=home-status-prometheus-2" defer></script>
</head>
<body>
<div class="site-shell">
@@ -52,30 +53,48 @@
<h1 id="server-status-heading" class="section-title">Server Status</h1>
<div class="status-list" role="list" aria-label="Current server player counts">
<div class="status-row status-parent" role="listitem"><span>US Server</span><span></span></div>
<div class="status-list status-list--regions" role="list" aria-label="Current server player counts" data-server-status="loading">
<div class="status-region" role="group" aria-label="US Server player counts">
<div class="status-region-header">
<span>US Server</span><span>Loading…</span>
</div>
<div class="status-ship">
<div class="status-ship-name">Alis</div>
<div class="status-row"><span>V2</span><span></span></div>
<div class="status-row"><span>V3</span><span></span></div>
<div class="status-row"><span>BB</span><span></span></div>
</div>
<div class="status-ship">
<div class="status-ship-name">Abion</div>
<div class="status-row"><span>HC/BB</span><span></span></div>
</div>
<div class="status-ship">
<div class="status-ship-name">AdHoc-US</div>
<div class="status-row"><span>PSP1</span><span></span></div>
<div class="status-row"><span>PSP2i</span><span></span></div>
</div>
</div>
<div class="status-row status-parent" role="listitem"><span>Alis</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>V2</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>V3</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>BB</span><span>0 Players</span></div>
<div class="status-row status-parent" role="listitem"><span>Abion</span><span></span></div>
<div class="status-row status-child" role="listitem"><span>HC</span><span>0 Players</span></div>
<div class="status-row status-parent" role="listitem"><span>EU Server</span><span></span></div>
<div class="status-row status-parent" role="listitem"><span>Palma</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>V2</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>V3</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>BB</span><span>0 Players</span></div>
<div class="status-row status-parent" role="listitem"><span>Aiedo</span><span></span></div>
<div class="status-row status-child" role="listitem"><span>HC</span><span>0 Players</span></div>
<div class="status-row status-parent" role="listitem"><span>PSP Ship</span><span></span></div>
<div class="status-row status-child" role="listitem"><span>PSP1</span><span>0 Players</span></div>
<div class="status-row status-child" role="listitem"><span>PSP2i</span><span>0 Players</span></div>
<div class="status-region" role="group" aria-label="EU Server player counts">
<div class="status-region-header">
<span>EU Server</span><span>Loading…</span>
</div>
<div class="status-ship">
<div class="status-ship-name">Palma</div>
<div class="status-row"><span>V2</span><span></span></div>
<div class="status-row"><span>V3</span><span></span></div>
<div class="status-row"><span>BB</span><span></span></div>
</div>
<div class="status-ship">
<div class="status-ship-name">Aiedo</div>
<div class="status-row"><span>HC/BB</span><span></span></div>
</div>
<div class="status-ship">
<div class="status-ship-name">AdHoc-EU</div>
<div class="status-row"><span>PSP1</span><span></span></div>
<div class="status-row"><span>PSP2i</span><span></span></div>
</div>
</div>
</div>
</section>
@@ -90,22 +109,11 @@
<section class="card leaderboard-card" aria-labelledby="hardcore-heading">
<h2 id="hardcore-heading" class="section-title">Hardcore Leaderboard</h2>
<ol class="leaderboard-list leaderboard-list--home-hardcore" id="home-hardcore-leaderboard-body" aria-label="Top five hardcore players">
<ol class="leaderboard-list leaderboard-list--home-hardcore" id="home-hardcore-leaderboard-body" aria-label="Top ten hardcore players">
<li><span class="rank">1.</span><span>Loading...</span></li>
</ol>
<a class="small-link home-leaderboard-more" href="leaderboards.html">more</a>
</section>
<section class="card leaderboard-card" aria-labelledby="crank-heading">
<h2 id="crank-heading" class="section-title">C Rank Points</h2>
<ol class="leaderboard-list" aria-label="Top five C Rank point totals">
<li><span class="rank">1.</span><span></span></li>
<li><span class="rank">2.</span><span></span></li>
<li><span class="rank">3.</span><span></span></li>
<li><span class="rank">4.</span><span></span></li>
<li><span class="rank">5.</span><span></span></li>
</ol>
</section>
</aside>
</main>
@@ -139,6 +147,6 @@
</footer>
</div>
<script src="hero-cycle.js?v=force-cycle-3" defer></script>
<script src="home-leaderboard.js?v=home-hardcore-preline-20260610-1" defer></script>
<script src="home-leaderboard.js?v=home-hardcore-top10-1" defer></script>
</body>
</html>
+46
View File
@@ -0,0 +1,46 @@
(() => {
const list = document.querySelector(".status-list");
if (!list) return;
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (ch) => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"'": "&#39;",
}[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.classList.add("status-list--regions");
list.innerHTML = servers.map((server) => `
<div class="status-region" role="group" aria-label="${esc(server.label)} player counts">
<div class="status-row status-parent status-server" role="listitem">
<span>${esc(server.label)}</span><span>${players(server.players)}</span>
</div>
${(server.ships || []).map((ship) => `
<div class="status-row status-parent status-ship" role="listitem">
<span>${esc(ship.label)}</span><span></span>
</div>
${(ship.rows || []).map((row) => `
<div class="status-row status-child" role="listitem">
<span>${esc(row.label)}</span><span>${players(row.players)}</span>
</div>
`).join("")}
`).join("")}
</div>
`).join("");
};
fetch("/api/server-status", { cache: "no-store" })
.then((res) => res.ok ? res.json() : null)
.then((data) => data && render(data))
.catch(() => {});
})();
+57
View File
@@ -1857,4 +1857,61 @@ button.inline-link,
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}\n\n
/* Home right column: stretch Hardcore leaderboard after C Rank removal */
.right-stack {
display: flex;
flex-direction: column;
}
.right-stack .leaderboard-card {
flex: 1;
}
\n
/* Home layout: stretch right column to match server status card */
.home-grid {
align-items: stretch;
}
.right-stack {
display: flex;
flex-direction: column;
height: 100%;
}
.right-stack .leaderboard-card {
flex: 1 1 auto;
}
/* Home server status: split US/EU into two inner boxes */
.server-card .status-list.status-list--regions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
background: transparent;
border: 0;
padding: 0;
}
.server-card .status-region {
display: grid;
gap: 0.35rem;
padding: 1rem;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.45rem;
background: rgba(0, 0, 0, 0.35);
}
.server-card .status-region .status-row {
grid-template-columns: minmax(0, 1fr) max-content;
}
@media (max-width: 720px) {
.server-card .status-list.status-list--regions {
grid-template-columns: 1fr;
}
}