From b3595c691b4ca519d5b18218f1575eaafb196898 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:39:01 +0330 Subject: [PATCH] feat(api): Integrate uptime and reboot traffic stats into server API --- .../webpanel/routers/api/v1/schema/server.py | 25 +++-- .../scripts/webpanel/routers/api/v1/server.py | 95 ++++++++++++------- 2 files changed, 80 insertions(+), 40 deletions(-) diff --git a/core/scripts/webpanel/routers/api/v1/schema/server.py b/core/scripts/webpanel/routers/api/v1/schema/server.py index bb3ba4b..f4c4048 100644 --- a/core/scripts/webpanel/routers/api/v1/schema/server.py +++ b/core/scripts/webpanel/routers/api/v1/schema/server.py @@ -5,22 +5,35 @@ from pydantic import BaseModel # It's better to chnage the underlying script to return bytes instead of changing it here # Because of this problem we use str type instead of int as type class ServerStatusResponse(BaseModel): - # disk_usage: int + # System Info + uptime: str + boot_time: str cpu_usage: str - total_ram: str ram_usage: str + total_ram: str online_users: int - uploaded_traffic: str - downloaded_traffic: str - total_traffic: str + # Real-time Network + upload_speed: str + download_speed: str + tcp_connections: int + udp_connections: int + + # Traffic Since Reboot + reboot_uploaded_traffic: str + reboot_downloaded_traffic: str + reboot_total_traffic: str + + # User Traffic (All Time) + user_uploaded_traffic: str + user_downloaded_traffic: str + user_total_traffic: str class ServerServicesStatusResponse(BaseModel): hysteria_server: bool hysteria_webpanel: bool hysteria_iplimit: bool - # hysteria_singbox: bool hysteria_normal_sub: bool hysteria_telegram_bot: bool hysteria_warp: bool diff --git a/core/scripts/webpanel/routers/api/v1/server.py b/core/scripts/webpanel/routers/api/v1/server.py index 5de4753..c975523 100644 --- a/core/scripts/webpanel/routers/api/v1/server.py +++ b/core/scripts/webpanel/routers/api/v1/server.py @@ -11,7 +11,7 @@ async def server_status_api(): Retrieve the server status. This endpoint provides information about the current server status, - including CPU usage, RAM usage, online users, and traffic statistics. + including uptime, CPU usage, RAM usage, online users, and traffic statistics. Returns: ServerStatusResponse: A response model containing server status details. @@ -30,7 +30,6 @@ async def server_status_api(): def __parse_server_status(server_info: str) -> ServerStatusResponse: - # Initial data with default values """ Parse the server information provided by cli_api.server_info() and return a ServerStatusResponse instance. @@ -45,57 +44,87 @@ def __parse_server_status(server_info: str) -> ServerStatusResponse: ValueError: If the server information is invalid or incomplete. """ data = { + 'uptime': 'N/A', + 'boot_time': 'N/A', 'cpu_usage': '0%', 'total_ram': '0MB', 'ram_usage': '0MB', 'online_users': 0, - 'uploaded_traffic': '0KB', - 'downloaded_traffic': '0KB', - 'total_traffic': '0KB' + 'upload_speed': '0 B/s', + 'download_speed': '0 B/s', + 'tcp_connections': 0, + 'udp_connections': 0, + 'reboot_uploaded_traffic': '0 B', + 'reboot_downloaded_traffic': '0 B', + 'reboot_total_traffic': '0 B', + 'user_uploaded_traffic': '0 B', + 'user_downloaded_traffic': '0 B', + 'user_total_traffic': '0 B' } - # Example output(server_info) from cli_api.server_info(): - # 📈 CPU Usage: 9.4% - # 📋 Total RAM: 3815MB - # 💻 Used RAM: 2007MB - # 👥 Online Users: 0 - # - # 🔼 Uploaded Traffic: 0 KB - # 🔽 Downloaded Traffic: 0 KB - # 📊 Total Traffic: 0 KB + current_section = 'general' for line in server_info.splitlines(): + line = line.strip() + if not line: + continue + + if 'Traffic Since Last Reboot' in line: + current_section = 'reboot' + continue + elif 'User Traffic (All Time)' in line: + current_section = 'user' + continue + key, _, value = line.partition(":") key = key.strip().lower() value = value.strip() if not key or not value: - continue # Skip empty or malformed lines - + continue + try: - if 'cpu usage' in key: + if 'uptime' in key: + uptime_part, _, boottime_part = value.partition('(') + data['uptime'] = uptime_part.strip() + data['boot_time'] = boottime_part.replace('since ', '').replace(')', '').strip() + elif 'cpu usage' in key: data['cpu_usage'] = value - elif 'total ram' in key: - data['total_ram'] = value elif 'used ram' in key: - data['ram_usage'] = value + parts = value.split('/') + if len(parts) == 2: + data['ram_usage'] = parts[0].strip() + data['total_ram'] = parts[1].strip() elif 'online users' in key: data['online_users'] = int(value) - elif 'uploaded traffic' in key: - value = value.replace(' ', '') - data['uploaded_traffic'] = value - elif "downloaded traffic" in key: - value = value.replace(' ', '') - data['downloaded_traffic'] = value - elif 'total traffic' in key: - value = value.replace(' ', '') - data["total_traffic"] = value - except ValueError as e: + elif 'upload speed' in key: + data['upload_speed'] = value + elif 'download speed' in key: + data['download_speed'] = value + elif 'tcp connections' in key: + data['tcp_connections'] = int(value) + elif 'udp connections' in key: + data['udp_connections'] = int(value) + elif 'total uploaded' in key or 'uploaded traffic' in key: + if current_section == 'reboot': + data['reboot_uploaded_traffic'] = value + elif current_section == 'user': + data['user_uploaded_traffic'] = value + elif 'total downloaded' in key or 'downloaded traffic' in key: + if current_section == 'reboot': + data['reboot_downloaded_traffic'] = value + elif current_section == 'user': + data['user_downloaded_traffic'] = value + elif 'combined traffic' in key or 'total traffic' in key: + if current_section == 'reboot': + data['reboot_total_traffic'] = value + elif current_section == 'user': + data['user_total_traffic'] = value + except (ValueError, IndexError) as e: raise ValueError(f'Error parsing line \'{line}\': {e}') - # Validate required fields try: - return ServerStatusResponse(**data) # type: ignore + return ServerStatusResponse(**data) except Exception as e: raise ValueError(f'Invalid or incomplete server info: {e}') @@ -141,8 +170,6 @@ def __parse_services_status(services_status: dict[str, bool]) -> ServerServicesS parsed_services_status['hysteria_telegram_bot'] = status elif 'hysteria-normal-sub' in service: parsed_services_status['hysteria_normal_sub'] = status - # elif 'hysteria-singbox' in service: - # parsed_services_status['hysteria_singbox'] = status elif 'wg-quick' in service: parsed_services_status['hysteria_warp'] = status return ServerServicesStatusResponse(**parsed_services_status)