diff --git a/panel/main.py b/panel/main.py index 9f30709..837a299 100644 --- a/panel/main.py +++ b/panel/main.py @@ -278,6 +278,42 @@ async def api_team_status(idx: int, req: Request): "ssh": st["ports"][name]["ssh"], "alive": ok}) return {"results": results} +@app.get("/api/team/{idx}/activity") +async def api_team_activity(idx: int, req: Request): + """Team-scoped activity feed: attack events where this team is attacker or + target (i.e. "ada serangan ke service kita" / "kita menyerang"), plus a + snapshot of own service health. Requires team login + own host.""" + td = orch.TEAMS_DIR / f"team{idx}" + if not (td / "state.json").exists(): + raise HTTPException(404, "Team not found") + st_self = json.loads((td / "state.json").read_text()) + if not _check_team_host(req, st_self): + raise HTTPException(403, "Akses team lain tidak diizinkan") + if not _team_authorized(req, idx): + raise HTTPException(401, "Login portal team dulu") + + # live label lookup (rename team = activity updates everywhere) + def label(i): + try: + s = json.loads((orch.TEAMS_DIR / f"team{i}" / "state.json").read_text()) + return s.get("label") or f"Team {i}" + except Exception: + return f"Team {i}" + + ap = orch.TEAMS_DIR / "attacks.json" + try: + events = json.loads(ap.read_text()).get("events", []) if ap.exists() else [] + except Exception: + events = [] + mine = [] + for e in reversed(events): # newest first + if e.get("attacker") == idx or e.get("target") == idx: + e = dict(e) + e["attacker_label"] = label(e.get("attacker")) + e["target_label"] = label(e.get("target")) + mine.append(e) + return {"events": mine[:100], "team_label": st_self.get("label") or f"Team {idx}"} + @app.post("/api/login") async def api_login(req: Request): data = await req.json() diff --git a/panel/static/team.html b/panel/static/team.html index 22f3c6b..e123a6a 100644 --- a/panel/static/team.html +++ b/panel/static/team.html @@ -60,6 +60,29 @@ .guide ol, .guide ul { padding-left:20px; } .login-note { font-size:12px; color:var(--dim); margin-top:14px; text-align:center; } .login-note a { color:var(--accent); } + /* activity feed */ + .feed { display:flex; flex-direction:column; gap:10px; max-height:420px; overflow-y:auto; } + .feed-item { background:var(--panel2); border:1px solid var(--line); border-left:3px solid var(--line); border-radius:10px; padding:10px 14px; font-size:13px; line-height:1.5; } + .feed-item.in { border-left-color:var(--red); } + .feed-item.out { border-left-color:var(--green); } + .feed-item.ok { border-left-color:var(--green); } + .feed-item.fail { border-left-color:var(--red); } + .feed-time { color:var(--dim); font-size:11px; margin-left:8px; } + .feed-tag { font-size:11px; border-radius:5px; padding:1px 7px; margin-right:6px; font-weight:700; } + .tag-in { background:#3a1414; color:var(--red); } + .tag-out { background:#06352a; color:var(--green); } + code { background:var(--panel2); padding:2px 6px; border-radius:5px; font-size:12px; } + .lb-row { display:flex; align-items:center; gap:12px; padding:10px 12px; border-bottom:1px solid var(--line); font-size:14px; } + .lb-row.me { background:rgba(56,189,248,.08); border-left:3px solid var(--accent); } + .lb-rank { width:28px; font-weight:800; color:var(--dim); } + .lb-name { flex:1; font-weight:600; } + .lb-name small { color:var(--dim); font-weight:400; margin-left:6px; } + .lb-solves { background:var(--panel2); border:1px solid var(--line); border-radius:20px; padding:3px 12px; font-size:12px; color:var(--green); } + .lb-empty { color:var(--dim); font-size:13px; padding:20px; text-align:center; } + .svc-health { display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; } + .svc-chip { border:1px solid var(--line); border-radius:8px; padding:4px 10px; font-size:12px; background:var(--panel2); } + .svc-chip.up { border-color:var(--green); color:var(--green); } + .svc-chip.down { border-color:var(--red); color:var(--red); }
@@ -90,10 +113,12 @@ ++ π΄ masuk = ada yang menyerang service timmu Β· π’ keluar = kamu menyerang tim lain. + Update otomatis tiap 15 detik. +
+Papan skor flag yang berhasil dicuri. Kamu = baris biru.
+ +${esc(e.challenge || '')} ${verdict}` +
+ `${esc(e.ts_human || '')}