Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
922c63f13d
|
@@ -42,3 +42,5 @@ yarn-error.log*
|
|||||||
|
|
||||||
source-bestiary/
|
source-bestiary/
|
||||||
source-drops/
|
source-drops/
|
||||||
|
site/server-status.json
|
||||||
|
site/server-status.json.tmp
|
||||||
|
|||||||
Executable
+103
@@ -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()
|
||||||
+4
-26
@@ -27,7 +27,7 @@
|
|||||||
8: { hp: 2.00, atp: 1.08, exp: 2.00 },
|
8: { hp: 2.00, atp: 1.08, exp: 2.00 },
|
||||||
9: { hp: 2.50, atp: 1.09, exp: 2.50 },
|
9: { hp: 2.50, atp: 1.09, exp: 2.50 },
|
||||||
10: { hp: 3.00, atp: 1.10, exp: 3.00 },
|
10: { hp: 3.00, atp: 1.10, exp: 3.00 },
|
||||||
11: { hp: 4.00, atp: 1.10, exp: 3.00 },
|
11: { hp: 4.00, atp: 1.10, exp: null },
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMN_GROUPS = {
|
const COLUMN_GROUPS = {
|
||||||
@@ -148,25 +148,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bpTier() {
|
function bpTier() {
|
||||||
const select = qs("#bestiary-tier");
|
const tier = Number(qs("#bestiary-bp-tier")?.value || 0);
|
||||||
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];
|
return BP_TIERS[tier] || BP_TIERS[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function xpBonusMultiplier() {
|
|
||||||
return Number(qs("#bestiary-xp-bonus")?.value || 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
function displayValue(row, key) {
|
function displayValue(row, key) {
|
||||||
const value = row[key];
|
const value = row[key];
|
||||||
|
|
||||||
if (key === "exp" && row.difficulty !== "Ultimate") {
|
|
||||||
return Math.round(Number(value || 0) * xpBonusMultiplier());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row.difficulty !== "Ultimate") {
|
if (row.difficulty !== "Ultimate") {
|
||||||
return value ?? "";
|
return value ?? "";
|
||||||
}
|
}
|
||||||
@@ -182,22 +170,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key === "exp" && tier.exp !== null) {
|
if (key === "exp" && tier.exp !== null) {
|
||||||
return Math.round(Number(value || 0) * tier.exp * xpBonusMultiplier());
|
return Math.round(Number(value || 0) * tier.exp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value ?? "";
|
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() {
|
function visibleRows() {
|
||||||
const search = state.filters.search.trim().toLowerCase();
|
const search = state.filters.search.trim().toLowerCase();
|
||||||
|
|
||||||
return state.rows.filter((row) => {
|
return state.rows.filter((row) => {
|
||||||
if (!hasAnyBestiaryValue(row)) return false;
|
|
||||||
if (state.filters.mode && row.mode !== state.filters.mode) 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.episode && row.episode !== state.filters.episode) return false;
|
||||||
if (state.filters.difficulty && row.difficulty !== state.filters.difficulty) return false;
|
if (state.filters.difficulty && row.difficulty !== state.filters.difficulty) return false;
|
||||||
@@ -350,15 +332,11 @@
|
|||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
qs("#bestiary-tier")?.addEventListener("change", () => {
|
qs("#bestiary-bp-tier")?.addEventListener("change", () => {
|
||||||
state.page = 1;
|
state.page = 1;
|
||||||
renderTable();
|
renderTable();
|
||||||
});
|
});
|
||||||
|
|
||||||
qs("#bestiary-xp-bonus")?.addEventListener("change", () => {
|
|
||||||
renderTable();
|
|
||||||
});
|
|
||||||
|
|
||||||
qs("#bestiary-placeholder")?.addEventListener("click", (event) => {
|
qs("#bestiary-placeholder")?.addEventListener("click", (event) => {
|
||||||
const pageButton = event.target.closest("[data-bestiary-page]");
|
const pageButton = event.target.closest("[data-bestiary-page]");
|
||||||
if (pageButton) {
|
if (pageButton) {
|
||||||
|
|||||||
+17
-9
@@ -51,13 +51,22 @@
|
|||||||
<option value="">All difficulties</option>
|
<option value="">All difficulties</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label for="bestiary-xp-bonus">XP Bonus</label>
|
<label for="bestiary-bp-tier">BP Tier</label>
|
||||||
<select id="bestiary-xp-bonus">
|
<select id="bestiary-bp-tier">
|
||||||
<option value="1">No modifier</option>
|
<option value="0">No modifier</option>
|
||||||
<option value="5">5x</option>
|
<option value="1">Brutal Peeps +1</option>
|
||||||
<option value="10">10x</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>
|
||||||
</select>
|
</select>
|
||||||
<p class="drops-field-note">Applies EXP multiplier only.</p>
|
<p class="drops-field-note">Applies HP, ATP, and EXP modifiers to Ultimate rows only.</p>
|
||||||
|
|
||||||
<label for="bestiary-view">View</label>
|
<label for="bestiary-view">View</label>
|
||||||
<select id="bestiary-view">
|
<select id="bestiary-view">
|
||||||
@@ -71,8 +80,7 @@
|
|||||||
|
|
||||||
<label for="bestiary-tier" data-bestiary-tier-wrap>BP Tier</label>
|
<label for="bestiary-tier" data-bestiary-tier-wrap>BP Tier</label>
|
||||||
<select id="bestiary-tier" data-bestiary-tier-wrap>
|
<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+4</option><option>BP+5</option><option>BP+6</option>
|
||||||
<option>BP+7</option><option>BP+8</option><option>BP+9</option>
|
<option>BP+7</option><option>BP+8</option><option>BP+9</option>
|
||||||
<option>BP+10</option><option>BP+11</option>
|
<option>BP+10</option><option>BP+11</option>
|
||||||
@@ -114,6 +122,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="bestiary-tables.js?v=hide-zero-bestiary-rows-1" defer></script>
|
<script src="bestiary-tables.js?v=bestiary-table-viewer-20260613-3" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+10
-2
@@ -39,8 +39,16 @@
|
|||||||
`).join("");
|
`).join("");
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch("/api/server-status", { cache: "no-store" })
|
const loadStatus = (url) => fetch(url, { cache: "no-store" })
|
||||||
.then((res) => res.ok ? res.json() : null)
|
.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))
|
.then((data) => data && render(data))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user