Compare commits

...

24 Commits

Author SHA1 Message Date
Your Name 48890f96b4 Allow current-region takeover of empty draining locks 2026-06-14 03:54:39 -04:00
Your Name 5e663c68be change draining state 2026-06-14 03:28:16 -04:00
incentive 759a1a4089 Merge pull request 'server status' (#11) from feature/home-server-status-prometheus into main
Reviewed-on: #11
2026-06-14 01:07:21 -04:00
Your Name 922c63f13d server status 2026-06-14 01:07:03 -04:00
incentive 9e76165638 Merge pull request 'Add Prometheus-backed home server status' (#10) from feature/home-server-status-prometheus into main
Reviewed-on: #10
2026-06-14 01:06:28 -04:00
Your Name 8ae301ab2d Wire homepage server status to Prometheus 2026-06-13 21:52:27 -04:00
Your Name c04356f6a0 Add Caddy-compatible server status route 2026-06-13 21:46:37 -04:00
Your Name d757468b7c Split home server status by region 2026-06-13 21:14:59 -04:00
Your Name 79915431fa Narrow home server status list 2026-06-13 21:11:19 -04:00
Your Name 2a0b162a02 Stretch home right column 2026-06-13 21:10:03 -04:00
Your Name 7ce98d6275 Stretch home hardcore leaderboard card 2026-06-13 21:08:44 -04:00
Your Name 3fdc4fda38 Show top ten hardcore leaderboard on home 2026-06-13 21:07:21 -04:00
Your Name 8d4d1772ca Fix home server status markup 2026-06-13 21:04:12 -04:00
Your Name 733e3149c4 Add Prometheus-backed home server status 2026-06-13 20:55:19 -04:00
incentive 1865a385ad Merge pull request 'feature/fix-bestiary-xp-bonus-bp-tier' (#9) from feature/fix-bestiary-xp-bonus-bp-tier into main
Reviewed-on: #9
2026-06-13 20:40:42 -04:00
Your Name b9e0cccfe1 Hide zero-value bestiary rows 2026-06-13 20:38:26 -04:00
Your Name 934581772d Fix bestiary BP tier none option and BP11 EXP display 2026-06-13 20:20:02 -04:00
Your Name 7c8dd807da Fix bestiary BP tier selection 2026-06-13 20:17:47 -04:00
Your Name 8c816e2e6b Use multiplier labels for bestiary XP bonus 2026-06-13 20:14:21 -04:00
Your Name cc51e1c9a7 Apply bestiary XP bonus to all difficulties 2026-06-13 20:12:24 -04:00
Your Name ae5105f40e Fix bestiary XP bonus rerender 2026-06-13 20:11:34 -04:00
Your Name 4a9b9dd40b Fix bestiary XP bonus and BP tier controls 2026-06-13 20:08:38 -04:00
Your Name 1e822a410e Remove local source artifact folders 2026-06-13 20:03:22 -04:00
incentive 1dfbff91c7 Merge pull request 'feature/bestiary-table-viewer' (#8) from feature/bestiary-table-viewer into main
Reviewed-on: #8
2026-06-13 19:53:56 -04:00
15 changed files with 435 additions and 239779 deletions
+5
View File
@@ -39,3 +39,8 @@ yarn-error.log*
.DS_Store
.idea/
.vscode/
source-bestiary/
source-drops/
site/server-status.json
site/server-status.json.tmp
+135 -11
View File
@@ -712,23 +712,32 @@ def newserv_account_lock_acquire():
"lock": lock_row_payload(row),
})
if row["holder_source"] != source:
return jsonify({
"ok": False,
"message": f"$C6Account is already active\\non {row['holder_source']}.",
"holder_source": row["holder_source"],
"state": row["state"],
"lock": lock_row_payload(row),
})
sessions = row["sessions"] or {}
if isinstance(sessions, str):
sessions = json.loads(sessions)
if row["holder_source"] != source:
target_region_current = False
if row["state"] == "draining" and not sessions and source_region:
_all_current, sync = account_sync_current_on_all_regions(account_id)
region_status = (sync.get("regions") or {}).get(source_region) or {}
target_region_current = region_status.get("status") == "current"
if not target_region_current:
return jsonify({
"ok": False,
"message": f"$C6Account is already active\\non {row['holder_source']}.",
"holder_source": row["holder_source"],
"state": row["state"],
"lock": lock_row_payload(row),
})
sessions[session_nonce] = session_info
cur.execute("""
UPDATE account_session_locks
SET state = 'active',
SET holder_source = %s,
state = 'active',
source_region = %s,
source_ship = %s,
account_store = %s,
@@ -739,6 +748,7 @@ def newserv_account_lock_acquire():
RETURNING account_id, holder_source, source_region, source_ship,
account_store, state, sessions, created_at, updated_at, expires_at
""", (
source,
source_region,
source_ship,
account_store,
@@ -780,7 +790,7 @@ def newserv_account_lock_heartbeat():
SET updated_at = now(),
expires_at = %s
WHERE holder_source = %s
AND state IN ('active', 'draining')
AND state = 'active'
""", (expires_at, source))
refreshed = cur.rowcount
@@ -1939,3 +1949,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"]},
]},
],
},
],
})
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
import json
import os
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
PROMETHEUS_URL = os.environ.get("PROMETHEUS_URL", "http://5.0.0.20:9090").rstrip("/")
SOURCE_URL = os.environ.get("SERVER_STATUS_SOURCE_URL", "").strip()
OUTPUT_PATH = Path(os.environ.get("SERVER_STATUS_JSON", "site/server-status.json"))
TIMEOUT_SECONDS = float(os.environ.get("PROMETHEUS_TIMEOUT_SECONDS", "5"))
def write_data(data):
if not isinstance(data.get("servers"), list):
raise SystemExit("server status JSON missing servers array")
data["generated_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
tmp_path = OUTPUT_PATH.with_name(OUTPUT_PATH.name + ".tmp")
tmp_path.write_text(json.dumps(data, separators=(",", ":")) + "\n")
os.replace(tmp_path, OUTPUT_PATH)
print(f"wrote {OUTPUT_PATH}")
def prom_value(query):
url = PROMETHEUS_URL + "/api/v1/query?" + urllib.parse.urlencode({"query": query})
with urllib.request.urlopen(url, timeout=TIMEOUT_SECONDS) as response:
body = json.loads(response.read().decode("utf-8"))
result = body.get("data", {}).get("result", [])
if not result:
return 0
return int(float(result[0]["value"][1]))
def q(metric, labels):
label_text = ",".join(f'{key}="{value}"' for key, value 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,
}))
def row(label, players):
return {"label": label, "players": int(players)}
def main():
if SOURCE_URL:
with urllib.request.urlopen(SOURCE_URL, timeout=TIMEOUT_SECONDS) as response:
write_data(json.loads(response.read().decode("utf-8")))
return
us_alis_v2 = newserv("us", "us-newserv-live", "live", "v2")
us_alis_v3 = newserv("us", "us-newserv-live", "live", "v3")
us_alis_bb = newserv("us", "us-newserv-live", "live", "v4")
us_abion_hcbb = newserv("us", "us-newserv-hardcore", "hardcore", "v4")
us_adhoc_psp1 = adhoc("us", "psp1")
us_adhoc_psp2i = adhoc("us", "psp2i")
eu_palma_v2 = newserv("eu", "eu-newserv-live", "live", "v2")
eu_palma_v3 = newserv("eu", "eu-newserv-live", "live", "v3")
eu_palma_bb = newserv("eu", "eu-newserv-live", "live", "v4")
eu_aiedo_hcbb = newserv("eu", "eu-newserv-hardcore", "hardcore", "v4")
eu_adhoc_psp1 = adhoc("eu", "psp1")
eu_adhoc_psp2i = adhoc("eu", "psp2i")
write_data({
"servers": [
{
"label": "US Server",
"players": us_alis_v2 + us_alis_v3 + us_alis_bb + us_abion_hcbb + us_adhoc_psp1 + us_adhoc_psp2i,
"ships": [
{"label": "Alis", "rows": [row("V2", us_alis_v2), row("V3", us_alis_v3), row("BB", us_alis_bb)]},
{"label": "Abion", "rows": [row("HC/BB", us_abion_hcbb)]},
{"label": "AdHoc-US", "rows": [row("PSP1", us_adhoc_psp1), row("PSP2i", us_adhoc_psp2i)]},
],
},
{
"label": "EU Server",
"players": eu_palma_v2 + eu_palma_v3 + eu_palma_bb + eu_aiedo_hcbb + eu_adhoc_psp1 + eu_adhoc_psp2i,
"ships": [
{"label": "Palma", "rows": [row("V2", eu_palma_v2), row("V3", eu_palma_v3), row("BB", eu_palma_bb)]},
{"label": "Aiedo", "rows": [row("HC/BB", eu_aiedo_hcbb)]},
{"label": "AdHoc-EU", "rows": [row("PSP1", eu_adhoc_psp1), row("PSP2i", eu_adhoc_psp2i)]},
],
},
],
})
if __name__ == "__main__":
main()
+26 -4
View File
@@ -27,7 +27,7 @@
8: { hp: 2.00, atp: 1.08, exp: 2.00 },
9: { hp: 2.50, atp: 1.09, exp: 2.50 },
10: { hp: 3.00, atp: 1.10, exp: 3.00 },
11: { hp: 4.00, atp: 1.10, exp: null },
11: { hp: 4.00, atp: 1.10, exp: 3.00 },
};
const COLUMN_GROUPS = {
@@ -148,13 +148,25 @@
}
function bpTier() {
const tier = Number(qs("#bestiary-bp-tier")?.value || 0);
const select = qs("#bestiary-tier");
const raw = String(select?.value || "");
const label = String(select?.selectedOptions?.[0]?.textContent || "");
const match = raw.match(/(\d+)$/) || label.match(/(\d+)/);
const tier = match ? Number(match[1]) : 0;
return BP_TIERS[tier] || BP_TIERS[0];
}
function xpBonusMultiplier() {
return Number(qs("#bestiary-xp-bonus")?.value || 1);
}
function displayValue(row, key) {
const value = row[key];
if (key === "exp" && row.difficulty !== "Ultimate") {
return Math.round(Number(value || 0) * xpBonusMultiplier());
}
if (row.difficulty !== "Ultimate") {
return value ?? "";
}
@@ -170,16 +182,22 @@
}
if (key === "exp" && tier.exp !== null) {
return Math.round(Number(value || 0) * tier.exp);
return Math.round(Number(value || 0) * tier.exp * xpBonusMultiplier());
}
return value ?? "";
}
function hasAnyBestiaryValue(row) {
const keys = ["hp", "atp", "dfp", "mst", "ata", "evp", "lck", "esp", "exp", "efr", "eic", "eth", "elt", "edk"];
return keys.some((key) => Number(row[key] || 0) !== 0);
}
function visibleRows() {
const search = state.filters.search.trim().toLowerCase();
return state.rows.filter((row) => {
if (!hasAnyBestiaryValue(row)) return false;
if (state.filters.mode && row.mode !== state.filters.mode) return false;
if (state.filters.episode && row.episode !== state.filters.episode) return false;
if (state.filters.difficulty && row.difficulty !== state.filters.difficulty) return false;
@@ -332,11 +350,15 @@
renderTable();
});
qs("#bestiary-bp-tier")?.addEventListener("change", () => {
qs("#bestiary-tier")?.addEventListener("change", () => {
state.page = 1;
renderTable();
});
qs("#bestiary-xp-bonus")?.addEventListener("change", () => {
renderTable();
});
qs("#bestiary-placeholder")?.addEventListener("click", (event) => {
const pageButton = event.target.closest("[data-bestiary-page]");
if (pageButton) {
+9 -17
View File
@@ -51,22 +51,13 @@
<option value="">All difficulties</option>
</select>
<label for="bestiary-bp-tier">BP Tier</label>
<select id="bestiary-bp-tier">
<option value="0">No modifier</option>
<option value="1">Brutal Peeps +1</option>
<option value="2">Brutal Peeps +2</option>
<option value="3">Brutal Peeps +3</option>
<option value="4">Brutal Peeps +4</option>
<option value="5">Brutal Peeps +5</option>
<option value="6">Brutal Peeps +6</option>
<option value="7">Brutal Peeps +7</option>
<option value="8">Brutal Peeps +8</option>
<option value="9">Brutal Peeps +9</option>
<option value="10">Brutal Peeps +10</option>
<option value="11">Brutal Peeps +11</option>
<label for="bestiary-xp-bonus">XP Bonus</label>
<select id="bestiary-xp-bonus">
<option value="1">No modifier</option>
<option value="5">5x</option>
<option value="10">10x</option>
</select>
<p class="drops-field-note">Applies HP, ATP, and EXP modifiers to Ultimate rows only.</p>
<p class="drops-field-note">Applies EXP multiplier only.</p>
<label for="bestiary-view">View</label>
<select id="bestiary-view">
@@ -80,7 +71,8 @@
<label for="bestiary-tier" data-bestiary-tier-wrap>BP Tier</label>
<select id="bestiary-tier" data-bestiary-tier-wrap>
<option>BP+1</option><option>BP+2</option><option>BP+3</option>
<option value="0">No modifier</option><option>BP+1</option><option>BP+2</option><option>BP+3</option>
<option>BP+4</option><option>BP+5</option><option>BP+6</option>
<option>BP+7</option><option>BP+8</option><option>BP+9</option>
<option>BP+10</option><option>BP+11</option>
@@ -122,6 +114,6 @@
</div>
</footer>
</div>
<script src="bestiary-tables.js?v=bestiary-table-viewer-20260613-3" defer></script>
<script src="bestiary-tables.js?v=hide-zero-bestiary-rows-1" defer></script>
</body>
</html>
+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>
+54
View File
@@ -0,0 +1,54 @@
(() => {
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("");
};
const loadStatus = (url) => fetch(url, { cache: "no-store" })
.then((res) => {
if (!res.ok) {
throw new Error(`status ${res.status}`);
}
return res.json();
});
loadStatus(`/server-status.json?ts=${Date.now()}`)
.catch(() => loadStatus("/api/server-status"))
.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;
}
}
-2
View File
@@ -1,2 +0,0 @@
# Raw binary battle parameter files are local source artifacts.
*.dat
File diff suppressed because it is too large Load Diff
@@ -1,38 +0,0 @@
/opt/newserv-hardcore/system/blueburst/BattleParamEntry.dat
/opt/newserv-hardcore/system/blueburst/BattleParamEntry_ep4.dat
/opt/newserv-hardcore/system/blueburst/BattleParamEntry_ep4_on.dat
/opt/newserv-hardcore/system/blueburst/BattleParamEntry_lab.dat
/opt/newserv-hardcore/system/blueburst/BattleParamEntry_lab_on.dat
/opt/newserv-hardcore/system/blueburst/BattleParamEntry_on.dat
/opt/newserv-hardcore/system/client-functions.disabled-hardcore-20260523T032841Z/EnemyHPBarsBB.s
/opt/newserv-hardcore/system/client-functions.disabled-hardcore-20260523T032841Z/EnemyHPBarsGC.s
/opt/newserv-hardcore/system/client-functions.disabled-hardcore-20260523T032841Z/EnemyHPBarsXB.s
/opt/newserv-hardcore/system/client-functions/EnemyDamageSyncBB.s
/opt/newserv-hardcore/system/client-functions/EnemyDamageSyncGC.s
/opt/newserv-hardcore/system/client-functions/EnemyDamageSyncXB.s
/opt/newserv-hardcore/system/client-functions/System/GetEnemyEntity.inc.s
/opt/newserv-hardcore/system/maps/gc-ep3/map_city_on_battle_e.dat
/opt/newserv-hardcore/system/maps/gc-ep3/map_city_on_battle_o.dat
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyDamageSyncBB.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyDamageSync/EnemyDamageSync.3___.patch.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyDamageSync/EnemyDamageSync.4___.patch.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyDamageSync/EnemyDamageSync.59NL.patch.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyDamageSyncGC.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyDamageSyncXB.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyHPBarsBB.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyHPBars/EnemyHPBars.3___.patch.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyHPBars/EnemyHPBars.4___.patch.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyHPBars/EnemyHPBars.59NL.patch.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyHPBarsGC.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/EnemyHPBarsXB.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/System/GetEnemyEntity-59NL.x86.inc.s
/opt/newserv-hardcore/system/psopeeps-backups/client-functions-before-test-sync-20260516T021108Z/System/GetEnemyEntity.inc.s
/opt/newserv-hardcore/system/tables/battle-params.json
/opt/newserv-hardcore/system/tables/item-parameter-table-bb-v4.json
/opt/newserv-hardcore/system/tables/item-parameter-table-dc-11-2000.json
/opt/newserv-hardcore/system/tables/item-parameter-table-dc-nte.json
/opt/newserv-hardcore/system/tables/item-parameter-table-dc-v1.json
/opt/newserv-hardcore/system/tables/item-parameter-table-gc-nte.json
/opt/newserv-hardcore/system/tables/item-parameter-table-gc-v3.json
/opt/newserv-hardcore/system/tables/item-parameter-table-pc-v2.json
/opt/newserv-hardcore/system/tables/item-parameter-table-xb-v3.json
@@ -1,22 +0,0 @@
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/EnemyDamageSyncBB.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/EnemyDamageSyncGC.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/EnemyDamageSyncXB.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/EnemyHPBarsBB.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/EnemyHPBarsGC.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/EnemyHPBarsXB.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/client-functions/System/GetEnemyEntity.inc.s
/home/rbatty/.local/share/github/psopeeps-newserv/system/maps/gc-ep3/map_city_on_battle_e.dat
/home/rbatty/.local/share/github/psopeeps-newserv/system/maps/gc-ep3/map_city_on_battle_o.dat
/home/rbatty/.local/share/github/psopeeps-newserv/system/patch-pc-10x/Media/PSO/BattleParamEntry.dat
/home/rbatty/.local/share/github/psopeeps-newserv/system/patch-pc-10x/Media/PSO/BattleParamEntry_on.dat
/home/rbatty/.local/share/github/psopeeps-newserv/system/patch-pc-5x/Media/PSO/BattleParamEntry.dat
/home/rbatty/.local/share/github/psopeeps-newserv/system/patch-pc-5x/Media/PSO/BattleParamEntry_on.dat
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/battle-params.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-bb-v4.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-dc-11-2000.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-dc-nte.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-dc-v1.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-gc-nte.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-gc-v3.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-pc-v2.json
/home/rbatty/.local/share/github/psopeeps-newserv/system/tables/item-parameter-table-xb-v3.json
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff