feat: SLA dashboard per team + points system (100/flag, +50 SLA bonus) + badges juara/runner-up/3rd + scoreboard API public+admin
Panel: /api/scoreboard + /api/public/scoreboard; teams.py: points.json ledger, probe_team_sla_fast, sla_status_all w/ background refresher; team.html: SLA & Skor tab; index.html: admin SLA tab; leaderboard shows points; Phew checker timeouts raised for slow Paillier keygen
This commit is contained in:
+39
-3
@@ -25,6 +25,15 @@ BASE_DIR = Path(__file__).parent
|
||||
|
||||
app = FastAPI(title="Gemastik A/D Panel")
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def _start_background():
|
||||
"""Warm the SLA scoreboard cache in the background."""
|
||||
import threading
|
||||
threading.Thread(target=orch._build_scoreboard, daemon=True,
|
||||
name="sla-warmup").start()
|
||||
orch.start_sla_refresher()
|
||||
|
||||
CHALLENGES = [
|
||||
{"name": "blogpost", "port": 10000, "ssh": 10022, "category": "web", "desc": "Flask blog with exiftool + SSTI"},
|
||||
{"name": "carbeat", "port": 11000, "ssh": 11022, "category": "pwn", "desc": "Binary exploitation menu"},
|
||||
@@ -276,7 +285,14 @@ async def api_team_status(idx: int, req: Request):
|
||||
ok = False
|
||||
results.append({"name": name, "port": st["ports"][name]["chall"],
|
||||
"ssh": st["ports"][name]["ssh"], "alive": ok})
|
||||
return {"results": results}
|
||||
# award SLA bonus when all services are UP (throttled, see add_sla_bonus)
|
||||
alive = sum(1 for r in results if r["alive"])
|
||||
bonus = orch.add_sla_bonus(idx, alive, len(results))
|
||||
return {"results": results, "sla": {"alive": alive, "total": len(results),
|
||||
"pct": round(100 * alive / max(len(results), 1), 1),
|
||||
"points": orch.get_team_points(idx),
|
||||
"rank": orch.team_rank(idx), "badge": orch.team_badge(idx),
|
||||
"bonus": bonus}}
|
||||
|
||||
@app.get("/api/team/{idx}/activity")
|
||||
async def api_team_activity(idx: int, req: Request):
|
||||
@@ -630,11 +646,31 @@ async def api_leaderboard(req: Request):
|
||||
teams = {}
|
||||
for e in lb["solves"]:
|
||||
name = team_name(e["team"])
|
||||
t = teams.setdefault(e["team"], {"team": e["team"], "name": name, "solves": 0, "challs": []})
|
||||
t = teams.setdefault(e["team"], {"team": e["team"], "name": name, "solves": 0, "challs": [], "points": 0})
|
||||
t["name"] = name
|
||||
t["solves"] += 1
|
||||
t["challs"].append(e["challenge"])
|
||||
return {"solves": lb["solves"], "teams": sorted(teams.values(), key=lambda x: -x["solves"])}
|
||||
# attach live points from points.json
|
||||
for tid, t in teams.items():
|
||||
t["points"] = orch.get_team_points(tid)
|
||||
return {"solves": lb["solves"], "teams": sorted(teams.values(), key=lambda x: (-x["points"], -x["solves"]))}
|
||||
|
||||
|
||||
@app.get("/api/scoreboard")
|
||||
async def api_scoreboard(req: Request):
|
||||
"""Admin + team: full scoreboard with points, SLA, rank, badges."""
|
||||
require_login(req)
|
||||
return orch.sla_status_all()
|
||||
|
||||
|
||||
@app.get("/api/public/scoreboard")
|
||||
async def api_public_scoreboard():
|
||||
"""Public scoreboard (no login) — used by the team portal status tab."""
|
||||
d = orch.sla_status_all()
|
||||
# strip receiver creds / ports internals for the public view
|
||||
for t in d["teams"]:
|
||||
t.pop("domain", None)
|
||||
return d
|
||||
|
||||
|
||||
@app.get("/api/public/teams")
|
||||
|
||||
Reference in New Issue
Block a user