feat: tune visual of web app admin panel
This commit is contained in:
@@ -556,6 +556,32 @@ class PanelApiService:
|
||||
return response_data.get("response")
|
||||
return None
|
||||
|
||||
async def get_nodes_bandwidth_usage(
|
||||
self,
|
||||
*,
|
||||
start: str,
|
||||
end: str,
|
||||
top_nodes_limit: int = 64,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Per-node usage for a date range (Remnawave GET /bandwidth-stats/nodes).
|
||||
|
||||
Query dates are calendar dates (YYYY-MM-DD), same as the panel UI analytics.
|
||||
Response includes topNodes[{ uuid, name, countryCode, total }, ...] where total is bytes.
|
||||
"""
|
||||
response_data = await self._request(
|
||||
"GET",
|
||||
"/bandwidth-stats/nodes",
|
||||
params={
|
||||
"start": start,
|
||||
"end": end,
|
||||
"topNodesLimit": top_nodes_limit,
|
||||
},
|
||||
log_full_response=False,
|
||||
)
|
||||
if response_data and not response_data.get("error") and "response" in response_data:
|
||||
return response_data.get("response")
|
||||
return None
|
||||
|
||||
async def get_user_bandwidth_stats(self, user_uuid: str) -> Optional[Dict[str, Any]]:
|
||||
endpoint = f"/bandwidth-stats/users/{user_uuid}"
|
||||
response_data = await self._request("GET", endpoint, log_full_response=False)
|
||||
@@ -700,6 +726,61 @@ class PanelApiService:
|
||||
logging.error("Failed to remove users from squad %s. Response: %s", squad_uuid, response_data)
|
||||
return False
|
||||
|
||||
async def get_nodes_online_lookups(self) -> Dict[str, Dict[str, int]]:
|
||||
"""Live ``usersOnline`` per node from ``GET /nodes`` (node directory).
|
||||
|
||||
Newer panels expose Prometheus-style metrics under ``/system/stats/nodes``
|
||||
(``nodes: [{ usersOnline, ... }]``). Older/alternate builds only return
|
||||
historical rows (e.g. ``lastSevenDays``) without live counts. The node
|
||||
directory response always includes ``usersOnline`` and ``uuid``.
|
||||
|
||||
Returns:
|
||||
``{"byUuid": {uuid_lower: int}, "byName": {name_lower: int}}``
|
||||
"""
|
||||
by_uuid: Dict[str, int] = {}
|
||||
by_name: Dict[str, int] = {}
|
||||
page_size = 100
|
||||
start = 0
|
||||
while True:
|
||||
response_data = await self._request(
|
||||
"GET",
|
||||
"/nodes",
|
||||
params={"size": page_size, "start": start},
|
||||
log_full_response=False,
|
||||
)
|
||||
if not response_data or response_data.get("error"):
|
||||
break
|
||||
resp = response_data.get("response")
|
||||
batch: List[Dict[str, Any]] = []
|
||||
if isinstance(resp, list):
|
||||
batch = [x for x in resp if isinstance(x, dict)]
|
||||
elif isinstance(resp, dict):
|
||||
inner = resp.get("nodes") or resp.get("items") or []
|
||||
batch = [x for x in inner if isinstance(x, dict)]
|
||||
if not batch:
|
||||
break
|
||||
for n in batch:
|
||||
uid = n.get("uuid") or n.get("nodeUuid") or n.get("node_uuid")
|
||||
uo = n.get("usersOnline")
|
||||
if uo is None:
|
||||
uo = n.get("users_online")
|
||||
if uo is None:
|
||||
continue
|
||||
try:
|
||||
val = int(uo)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if uid:
|
||||
by_uuid[str(uid).strip().lower()] = val
|
||||
name = n.get("name")
|
||||
if name and isinstance(name, str) and name.strip():
|
||||
by_name[name.strip().lower()] = val
|
||||
if len(batch) < page_size:
|
||||
break
|
||||
start += page_size
|
||||
await asyncio.sleep(0.05)
|
||||
return {"byUuid": by_uuid, "byName": by_name}
|
||||
|
||||
async def get_nodes_statistics(self) -> Optional[Dict[str, Any]]:
|
||||
"""Get nodes statistics"""
|
||||
response_data = await self._request("GET", "/system/stats/nodes", log_full_response=False)
|
||||
|
||||
Reference in New Issue
Block a user