ctfy.sdk.admin_resources.observability
client.admin.observability — dashboard stats, health, audit log (admin).
1"""``client.admin.observability`` — dashboard stats, health, audit log (admin).""" 2 3from __future__ import annotations 4 5from typing import Any 6 7from ctfy.sdk._helpers import _extract_items, _raise_for_status 8from ctfy.sdk.base import BaseHttpClient 9from ctfy.server.models import ( 10 Activity, 11 ActivityTimeseries, 12 AdminHealthFlags, 13 AdminOverview, 14 AdminSolveMatrix, 15 AdminTimeseries, 16 AdminTrafficSummary, 17) 18 19 20class AdminObservabilityResource: 21 """The admin dashboard's numbers: overview snapshot, solve grid, health 22 flags, metric timeseries, traffic tally, and the unfiltered audit log.""" 23 24 def __init__(self, http: BaseHttpClient) -> None: 25 self._http = http 26 27 def overview(self) -> AdminOverview: 28 """Dashboard snapshot: counts of users / teams / competitions / 29 instances / solves, plus the latest 24h of activity.""" 30 resp = self._http.request("GET", "/admin/overview") 31 _raise_for_status(resp) 32 return AdminOverview.model_validate(resp.json()) 33 34 def solve_matrix(self) -> AdminSolveMatrix: 35 """Per-team × per-challenge solve grid for the admin scoreboard.""" 36 resp = self._http.request("GET", "/admin/solve-matrix") 37 _raise_for_status(resp) 38 return AdminSolveMatrix.model_validate(resp.json()) 39 40 def health_flags(self) -> AdminHealthFlags: 41 """Boolean flags surfaced on the admin top-bar (e.g. node down, 42 OAuth not configured, secret-key ephemeral).""" 43 resp = self._http.request("GET", "/admin/health-flags") 44 _raise_for_status(resp) 45 return AdminHealthFlags.model_validate(resp.json()) 46 47 def timeseries( 48 self, 49 *, 50 metric: str, 51 window: int = 86400, 52 bucket: int = 3600, 53 ) -> AdminTimeseries: 54 """Bucketed counts for one metric. ``metric`` ∈ ``"launches"``, 55 ``"submissions"``, ``"solves"``, ``"running_instances"``, 56 ``"teams"``. ``window`` and ``bucket`` are in seconds.""" 57 resp = self._http.request( 58 "GET", 59 "/admin/timeseries", 60 params={"metric": metric, "window": window, "bucket": bucket}, 61 ) 62 _raise_for_status(resp) 63 return AdminTimeseries.model_validate(resp.json()) 64 65 def traffic_summary(self) -> AdminTrafficSummary: 66 """Per-team and per-instance HTTP request counts (from archived 67 instance records' mitmproxy flow tally).""" 68 resp = self._http.request("GET", "/admin/traffic/summary") 69 _raise_for_status(resp) 70 return AdminTrafficSummary.model_validate(resp.json()) 71 72 def activities( 73 self, 74 *, 75 actor_id: str = "", 76 team_id: str = "", 77 challenge_id: str = "", 78 event: str = "", 79 since: str = "", 80 until: str = "", 81 offset: int = 0, 82 limit: int = 50, 83 ) -> list[Activity]: 84 """Admin activity log — the unfiltered audit surface (every 85 actor, including admin-only events). ``since`` / ``until`` are 86 ISO-8601 timestamps; omit to leave unbounded.""" 87 params: dict[str, Any] = {"offset": offset, "limit": limit} 88 params.update( 89 { 90 k: v 91 for k, v in ( 92 ("actor_id", actor_id), 93 ("team_id", team_id), 94 ("challenge_id", challenge_id), 95 ("event", event), 96 ("since", since), 97 ("until", until), 98 ) 99 if v 100 } 101 ) 102 resp = self._http.request("GET", "/admin/activities", params=params) 103 _raise_for_status(resp) 104 return _extract_items(resp.json(), Activity) 105 106 def activities_timeseries( 107 self, *, window: int = 86400, bucket: int = 3600 108 ) -> ActivityTimeseries: 109 """Bucketed admin activity-event counts. ``window`` + ``bucket`` 110 are in seconds.""" 111 resp = self._http.request( 112 "GET", 113 "/admin/activities/timeseries", 114 params={"window": window, "bucket": bucket}, 115 ) 116 _raise_for_status(resp) 117 return ActivityTimeseries.model_validate(resp.json())
21class AdminObservabilityResource: 22 """The admin dashboard's numbers: overview snapshot, solve grid, health 23 flags, metric timeseries, traffic tally, and the unfiltered audit log.""" 24 25 def __init__(self, http: BaseHttpClient) -> None: 26 self._http = http 27 28 def overview(self) -> AdminOverview: 29 """Dashboard snapshot: counts of users / teams / competitions / 30 instances / solves, plus the latest 24h of activity.""" 31 resp = self._http.request("GET", "/admin/overview") 32 _raise_for_status(resp) 33 return AdminOverview.model_validate(resp.json()) 34 35 def solve_matrix(self) -> AdminSolveMatrix: 36 """Per-team × per-challenge solve grid for the admin scoreboard.""" 37 resp = self._http.request("GET", "/admin/solve-matrix") 38 _raise_for_status(resp) 39 return AdminSolveMatrix.model_validate(resp.json()) 40 41 def health_flags(self) -> AdminHealthFlags: 42 """Boolean flags surfaced on the admin top-bar (e.g. node down, 43 OAuth not configured, secret-key ephemeral).""" 44 resp = self._http.request("GET", "/admin/health-flags") 45 _raise_for_status(resp) 46 return AdminHealthFlags.model_validate(resp.json()) 47 48 def timeseries( 49 self, 50 *, 51 metric: str, 52 window: int = 86400, 53 bucket: int = 3600, 54 ) -> AdminTimeseries: 55 """Bucketed counts for one metric. ``metric`` ∈ ``"launches"``, 56 ``"submissions"``, ``"solves"``, ``"running_instances"``, 57 ``"teams"``. ``window`` and ``bucket`` are in seconds.""" 58 resp = self._http.request( 59 "GET", 60 "/admin/timeseries", 61 params={"metric": metric, "window": window, "bucket": bucket}, 62 ) 63 _raise_for_status(resp) 64 return AdminTimeseries.model_validate(resp.json()) 65 66 def traffic_summary(self) -> AdminTrafficSummary: 67 """Per-team and per-instance HTTP request counts (from archived 68 instance records' mitmproxy flow tally).""" 69 resp = self._http.request("GET", "/admin/traffic/summary") 70 _raise_for_status(resp) 71 return AdminTrafficSummary.model_validate(resp.json()) 72 73 def activities( 74 self, 75 *, 76 actor_id: str = "", 77 team_id: str = "", 78 challenge_id: str = "", 79 event: str = "", 80 since: str = "", 81 until: str = "", 82 offset: int = 0, 83 limit: int = 50, 84 ) -> list[Activity]: 85 """Admin activity log — the unfiltered audit surface (every 86 actor, including admin-only events). ``since`` / ``until`` are 87 ISO-8601 timestamps; omit to leave unbounded.""" 88 params: dict[str, Any] = {"offset": offset, "limit": limit} 89 params.update( 90 { 91 k: v 92 for k, v in ( 93 ("actor_id", actor_id), 94 ("team_id", team_id), 95 ("challenge_id", challenge_id), 96 ("event", event), 97 ("since", since), 98 ("until", until), 99 ) 100 if v 101 } 102 ) 103 resp = self._http.request("GET", "/admin/activities", params=params) 104 _raise_for_status(resp) 105 return _extract_items(resp.json(), Activity) 106 107 def activities_timeseries( 108 self, *, window: int = 86400, bucket: int = 3600 109 ) -> ActivityTimeseries: 110 """Bucketed admin activity-event counts. ``window`` + ``bucket`` 111 are in seconds.""" 112 resp = self._http.request( 113 "GET", 114 "/admin/activities/timeseries", 115 params={"window": window, "bucket": bucket}, 116 ) 117 _raise_for_status(resp) 118 return ActivityTimeseries.model_validate(resp.json())
The admin dashboard's numbers: overview snapshot, solve grid, health flags, metric timeseries, traffic tally, and the unfiltered audit log.
28 def overview(self) -> AdminOverview: 29 """Dashboard snapshot: counts of users / teams / competitions / 30 instances / solves, plus the latest 24h of activity.""" 31 resp = self._http.request("GET", "/admin/overview") 32 _raise_for_status(resp) 33 return AdminOverview.model_validate(resp.json())
Dashboard snapshot: counts of users / teams / competitions / instances / solves, plus the latest 24h of activity.
35 def solve_matrix(self) -> AdminSolveMatrix: 36 """Per-team × per-challenge solve grid for the admin scoreboard.""" 37 resp = self._http.request("GET", "/admin/solve-matrix") 38 _raise_for_status(resp) 39 return AdminSolveMatrix.model_validate(resp.json())
Per-team × per-challenge solve grid for the admin scoreboard.
41 def health_flags(self) -> AdminHealthFlags: 42 """Boolean flags surfaced on the admin top-bar (e.g. node down, 43 OAuth not configured, secret-key ephemeral).""" 44 resp = self._http.request("GET", "/admin/health-flags") 45 _raise_for_status(resp) 46 return AdminHealthFlags.model_validate(resp.json())
Boolean flags surfaced on the admin top-bar (e.g. node down, OAuth not configured, secret-key ephemeral).
48 def timeseries( 49 self, 50 *, 51 metric: str, 52 window: int = 86400, 53 bucket: int = 3600, 54 ) -> AdminTimeseries: 55 """Bucketed counts for one metric. ``metric`` ∈ ``"launches"``, 56 ``"submissions"``, ``"solves"``, ``"running_instances"``, 57 ``"teams"``. ``window`` and ``bucket`` are in seconds.""" 58 resp = self._http.request( 59 "GET", 60 "/admin/timeseries", 61 params={"metric": metric, "window": window, "bucket": bucket}, 62 ) 63 _raise_for_status(resp) 64 return AdminTimeseries.model_validate(resp.json())
Bucketed counts for one metric. metric ∈ "launches",
"submissions", "solves", "running_instances",
"teams". window and bucket are in seconds.
66 def traffic_summary(self) -> AdminTrafficSummary: 67 """Per-team and per-instance HTTP request counts (from archived 68 instance records' mitmproxy flow tally).""" 69 resp = self._http.request("GET", "/admin/traffic/summary") 70 _raise_for_status(resp) 71 return AdminTrafficSummary.model_validate(resp.json())
Per-team and per-instance HTTP request counts (from archived instance records' mitmproxy flow tally).
73 def activities( 74 self, 75 *, 76 actor_id: str = "", 77 team_id: str = "", 78 challenge_id: str = "", 79 event: str = "", 80 since: str = "", 81 until: str = "", 82 offset: int = 0, 83 limit: int = 50, 84 ) -> list[Activity]: 85 """Admin activity log — the unfiltered audit surface (every 86 actor, including admin-only events). ``since`` / ``until`` are 87 ISO-8601 timestamps; omit to leave unbounded.""" 88 params: dict[str, Any] = {"offset": offset, "limit": limit} 89 params.update( 90 { 91 k: v 92 for k, v in ( 93 ("actor_id", actor_id), 94 ("team_id", team_id), 95 ("challenge_id", challenge_id), 96 ("event", event), 97 ("since", since), 98 ("until", until), 99 ) 100 if v 101 } 102 ) 103 resp = self._http.request("GET", "/admin/activities", params=params) 104 _raise_for_status(resp) 105 return _extract_items(resp.json(), Activity)
Admin activity log — the unfiltered audit surface (every
actor, including admin-only events). since / until are
ISO-8601 timestamps; omit to leave unbounded.
107 def activities_timeseries( 108 self, *, window: int = 86400, bucket: int = 3600 109 ) -> ActivityTimeseries: 110 """Bucketed admin activity-event counts. ``window`` + ``bucket`` 111 are in seconds.""" 112 resp = self._http.request( 113 "GET", 114 "/admin/activities/timeseries", 115 params={"window": window, "bucket": bucket}, 116 ) 117 _raise_for_status(resp) 118 return ActivityTimeseries.model_validate(resp.json())
Bucketed admin activity-event counts. window + bucket
are in seconds.