feat: target matrix + reset scores/environment buttons

- /api/targets (admin): matrix of all teams' domain:port targets
- /api/reset/scores: wipe leaderboard (admin, confirm dialog)
- /api/reset/environment: stop all teams, remove containers+receivers+
  team dirs+systemd units, wipe scores, drop domains (admin, confirm)
- portal targets now only domain+port (no ssh/labels)
- UI: tab Target Matrix, header buttons Reset Skor / Reset Environment
  with confirm() alerts 'apakah anda yakin ingin mereset...'
This commit is contained in:
root
2026-09-23 17:14:42 +08:00
parent b66530e7fd
commit 44dc1ac852
4 changed files with 140 additions and 10 deletions
+44
View File
@@ -402,6 +402,50 @@ def submit_flag(team_idx: int, chall: str, flag: str, team_name: str = "") -> di
lb_path.write_text(json.dumps(lb, indent=2))
return {"success": True, "team": team_idx, "challenge": chall}
def reset_scores() -> dict:
"""Wipe the leaderboard (all solves removed)."""
lb_path = TEAMS_DIR / "leaderboard.json"
lb_path.write_text(json.dumps({"solves": []}, indent=2))
return {"ok": True, "cleared": True}
def reset_environment() -> dict:
"""Full env reset: stop all teams, remove containers + receiver services,
delete team dirs (fresh for re-create). Keeps panel + images."""
results = []
for d in sorted(TEAMS_DIR.glob("team*")):
sf = d / "state.json"
if not sf.exists():
continue
st = json.loads(sf.read_text())
idx = st["index"]
try:
stop_team(idx) # compose down + stop receiver service + set status
except Exception as e:
results.append({"team": idx, "ok": False, "err": str(e)})
continue
# remove the per-team systemd receiver unit
subprocess.run(["systemctl", "disable", f"gemastik-receiver-team{idx}.service"],
check=False, capture_output=True)
subprocess.run(["systemctl", "stop", f"gemastik-receiver-team{idx}.service"],
check=False, capture_output=True)
unit = Path("/etc/systemd/system") / f"gemastik-receiver-team{idx}.service"
if unit.exists():
unit.unlink()
results.append({"team": idx, "ok": True})
subprocess.run(["systemctl", "daemon-reload"], check=False)
# remove team dirs entirely (fresh for re-create)
for d in sorted(TEAMS_DIR.glob("team*")):
shutil.rmtree(d, ignore_errors=True)
# wipe leaderboard too
reset_scores()
# drop team domains from Traefik (regenerate — no teams left)
try:
ensure_team_domains()
except Exception:
pass
return {"ok": True, "stopped": results, "teams_dir": str(TEAMS_DIR)}
if __name__ == "__main__":
import sys
cmd = sys.argv[1] if len(sys.argv) > 1 else "list"