Merge pull request #251 from ReturnFI/beta

Dashboard redesign & server stats upgrade
This commit is contained in:
Whispering Wind
2025-08-19 23:52:37 +03:30
committed by GitHub
6 changed files with 417 additions and 175 deletions

View File

@ -1,22 +1,19 @@
# [1.15.0] - 2025-08-17
# [1.16.0] - 2025-08-19
#### ✨ New Features
* 🚫 **Blocked User Check**
* 📊 **Dashboard Redesign**
* Subscription endpoint now validates blocked users and prevents access
* 🌐 **External Configs in Subscriptions**
* Modernized UI with detailed server stats
* 🖥️ **Server API Enhancements**
* NormalSub links now include external proxy configs
* ⚙️ **Settings Page**
* Added uptime and traffic-since-reboot metrics
* **System Monitor Optimization**
* Added management UI for extra subscription configs
* 🖥️ **Webpanel API**
* Improved performance with async I/O
* Accurate traffic tracking since reboot
* New API endpoints for managing extra configs
* 🛠️ **CLI & Scripts**
#### 🐛 Fixes
* CLI support for extra subscription configs
* New `extra_config.py` script to manage additional proxy configs:
* `vmess`, `vless`, `ss`, `trojan`
* 🔧 Correctly count **actual device connections** instead of unique users
* 🔥 Fixed subscription blocked page to display the right user data

View File

@ -2,12 +2,17 @@
import sys
import json
from hysteria2_api import Hysteria2Client
import asyncio
import aiofiles
import time
from concurrent.futures import ThreadPoolExecutor
from functools import lru_cache
from hysteria2_api import Hysteria2Client
from init_paths import *
from paths import *
@lru_cache(maxsize=1)
def get_secret() -> str:
if not CONFIG_FILE.exists():
print("Error: config.json file not found!", file=sys.stderr)
@ -25,24 +30,74 @@ def get_secret() -> str:
def convert_bytes(bytes_val: int) -> str:
units = [("TB", 1 << 40), ("GB", 1 << 30), ("MB", 1 << 20), ("KB", 1 << 10)]
for unit, factor in units:
if bytes_val >= factor:
return f"{bytes_val / factor:.2f} {unit}"
if bytes_val >= (1 << 40):
return f"{bytes_val / (1 << 40):.2f} TB"
elif bytes_val >= (1 << 30):
return f"{bytes_val / (1 << 30):.2f} GB"
elif bytes_val >= (1 << 20):
return f"{bytes_val / (1 << 20):.2f} MB"
elif bytes_val >= (1 << 10):
return f"{bytes_val / (1 << 10):.2f} KB"
return f"{bytes_val} B"
def get_cpu_usage(interval: float = 0.1) -> float:
def read_cpu_times():
with open("/proc/stat") as f:
line = f.readline()
def convert_speed(bytes_per_second: int) -> str:
if bytes_per_second >= (1 << 40):
return f"{bytes_per_second / (1 << 40):.2f} TB/s"
elif bytes_per_second >= (1 << 30):
return f"{bytes_per_second / (1 << 30):.2f} GB/s"
elif bytes_per_second >= (1 << 20):
return f"{bytes_per_second / (1 << 20):.2f} MB/s"
elif bytes_per_second >= (1 << 10):
return f"{bytes_per_second / (1 << 10):.2f} KB/s"
return f"{int(bytes_per_second)} B/s"
async def read_file_async(filepath: str) -> str:
try:
async with aiofiles.open(filepath, 'r') as f:
return await f.read()
except FileNotFoundError:
return ""
def format_uptime(seconds: float) -> str:
seconds = int(seconds)
days, remainder = divmod(seconds, 86400)
hours, remainder = divmod(remainder, 3600)
minutes, _ = divmod(remainder, 60)
return f"{days}d {hours}h {minutes}m"
async def get_uptime_and_boottime() -> tuple[str, str]:
try:
content = await read_file_async("/proc/uptime")
uptime_seconds = float(content.split()[0])
boot_time_epoch = time.time() - uptime_seconds
boot_time_str = time.strftime("%Y-%m-%d %H:%M", time.localtime(boot_time_epoch))
uptime_str = format_uptime(uptime_seconds)
return uptime_str, boot_time_str
except (FileNotFoundError, IndexError, ValueError):
return "N/A", "N/A"
def parse_cpu_stats(content: str) -> tuple[int, int]:
if not content:
return 0, 0
line = content.split('\n')[0]
fields = list(map(int, line.strip().split()[1:]))
idle, total = fields[3], sum(fields)
return idle, total
idle1, total1 = read_cpu_times()
time.sleep(interval)
idle2, total2 = read_cpu_times()
async def get_cpu_usage(interval: float = 0.1) -> float:
content1 = await read_file_async("/proc/stat")
idle1, total1 = parse_cpu_stats(content1)
await asyncio.sleep(interval)
content2 = await read_file_async("/proc/stat")
idle2, total2 = parse_cpu_stats(content2)
idle_delta = idle2 - idle1
total_delta = total2 - total1
@ -50,23 +105,18 @@ def get_cpu_usage(interval: float = 0.1) -> float:
return round(cpu_usage, 1)
def parse_meminfo(content: str) -> tuple[int, int]:
if not content:
return 0, 0
def get_memory_usage() -> tuple[int, int]:
mem_info = {}
try:
with open("/proc/meminfo", "r") as f:
for line in f:
for line in content.split('\n'):
if ':' in line:
parts = line.split()
if len(parts) >= 2:
key = parts[0].rstrip(':')
if parts[1].isdigit():
mem_info[key] = int(parts[1])
except FileNotFoundError:
print("Error: /proc/meminfo not found.", file=sys.stderr)
return 0, 0
except Exception as e:
print(f"Error reading /proc/meminfo: {e}", file=sys.stderr)
return 0, 0
mem_total_kb = mem_info.get("MemTotal", 0)
mem_free_kb = mem_info.get("MemFree", 0)
@ -76,19 +126,69 @@ def get_memory_usage() -> tuple[int, int]:
used_kb = mem_total_kb - mem_free_kb - buffers_kb - cached_kb - sreclaimable_kb
if used_kb < 0:
used_kb = mem_total_kb - mem_info.get("MemAvailable", mem_total_kb)
used_kb = max(0, used_kb)
return mem_total_kb // 1024, used_kb // 1024
total_mb = mem_total_kb // 1024
used_mb = used_kb // 1024
return total_mb, used_mb
async def get_memory_usage() -> tuple[int, int]:
content = await read_file_async("/proc/meminfo")
return parse_meminfo(content)
def parse_network_stats(content: str) -> tuple[int, int]:
if not content:
return 0, 0
def get_online_user_count(secret: str) -> int:
rx_bytes, tx_bytes = 0, 0
lines = content.split('\n')
for line in lines[2:]:
if not line.strip():
continue
parts = line.split()
if len(parts) < 10:
continue
iface = parts[0].strip().replace(':', '')
if iface == 'lo':
continue
try:
rx_bytes += int(parts[1])
tx_bytes += int(parts[9])
except (IndexError, ValueError):
continue
return rx_bytes, tx_bytes
async def get_network_stats() -> tuple[int, int]:
content = await read_file_async('/proc/net/dev')
return parse_network_stats(content)
async def get_network_speed(interval: float = 0.5) -> tuple[int, int]:
rx1, tx1 = await get_network_stats()
await asyncio.sleep(interval)
rx2, tx2 = await get_network_stats()
rx_speed = (rx2 - rx1) / interval
tx_speed = (tx2 - tx1) / interval
return int(rx_speed), int(tx_speed)
def parse_connection_counts(tcp_content: str, udp_content: str) -> tuple[int, int]:
tcp_count = len(tcp_content.split('\n')) - 2 if tcp_content else 0
udp_count = len(udp_content.split('\n')) - 2 if udp_content else 0
return max(0, tcp_count), max(0, udp_count)
async def get_connection_counts() -> tuple[int, int]:
tcp_task = read_file_async('/proc/net/tcp')
udp_task = read_file_async('/proc/net/udp')
tcp_content, udp_content = await asyncio.gather(tcp_task, udp_task)
return parse_connection_counts(tcp_content, udp_content)
def get_online_user_count_sync(secret: str) -> int:
try:
client = Hysteria2Client(
base_url=API_BASE_URL,
@ -101,47 +201,83 @@ def get_online_user_count(secret: str) -> int:
return 0
def get_total_traffic() -> tuple[int, int]:
async def get_online_user_count(secret: str) -> int:
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
return await loop.run_in_executor(executor, get_online_user_count_sync, secret)
def parse_total_traffic(content: str) -> tuple[int, int]:
if not content:
return 0, 0
try:
users = json.loads(content)
total_upload = sum(int(user_data.get("upload_bytes", 0) or 0) for user_data in users.values())
total_download = sum(int(user_data.get("download_bytes", 0) or 0) for user_data in users.values())
return total_upload, total_download
except (json.JSONDecodeError, ValueError, AttributeError):
return 0, 0
async def get_user_traffic() -> tuple[int, int]:
if not USERS_FILE.exists():
return 0, 0
try:
with USERS_FILE.open() as f:
users = json.load(f)
total_upload = 0
total_download = 0
for user_data in users.values():
total_upload += int(user_data.get("upload_bytes", 0) or 0)
total_download += int(user_data.get("download_bytes", 0) or 0)
return total_upload, total_download
async with aiofiles.open(USERS_FILE, 'r') as f:
content = await f.read()
return parse_total_traffic(content)
except Exception as e:
print(f"Error parsing traffic data: {e}", file=sys.stderr)
return 0, 0
def main():
async def main():
secret = get_secret()
cpu_usage = get_cpu_usage()
mem_total, mem_used = get_memory_usage()
online_users = get_online_user_count(secret)
tasks = [
get_uptime_and_boottime(),
get_memory_usage(),
get_connection_counts(),
get_online_user_count(secret),
get_user_traffic(),
get_cpu_usage(0.1),
get_network_speed(0.3),
get_network_stats()
]
print(f"📈 CPU Usage: {cpu_usage}")
print(f"📋 Total RAM: {mem_total}MB")
print(f"💻 Used RAM: {mem_used}MB")
results = await asyncio.gather(*tasks)
uptime_str, boot_time_str = results[0]
mem_total, mem_used = results[1]
tcp_connections, udp_connections = results[2]
online_users = results[3]
user_upload, user_download = results[4]
cpu_usage = results[5]
download_speed, upload_speed = results[6]
reboot_rx, reboot_tx = results[7]
print(f"🕒 Uptime: {uptime_str} (since {boot_time_str})")
print(f"📈 CPU Usage: {cpu_usage}%")
print(f"💻 Used RAM: {mem_used}MB / {mem_total}MB")
print(f"👥 Online Users: {online_users}")
print()
total_upload, total_download = get_total_traffic()
print(f"🔼 Uploaded Traffic: {convert_bytes(total_upload)}")
print(f"🔽 Downloaded Traffic: {convert_bytes(total_download)}")
print(f"📊 Total Traffic: {convert_bytes(total_upload + total_download)}")
print(f"🔼 Upload Speed: {convert_speed(upload_speed)}")
print(f"🔽 Download Speed: {convert_speed(download_speed)}")
print(f"📡 TCP Connections: {tcp_connections}")
print(f"📡 UDP Connections: {udp_connections}")
print()
print("📊 Traffic Since Last Reboot:")
print(f" 🔼 Total Uploaded: {convert_bytes(reboot_tx)}")
print(f" 🔽 Total Downloaded: {convert_bytes(reboot_rx)}")
print(f" 📈 Combined Traffic: {convert_bytes(reboot_tx + reboot_rx)}")
print()
print("📊 User Traffic (All Time):")
print(f" 🔼 Uploaded Traffic: {convert_bytes(user_upload)}")
print(f" 🔽 Downloaded Traffic: {convert_bytes(user_download)}")
print(f" 📈 Total Traffic: {convert_bytes(user_upload + user_download)}")
if __name__ == "__main__":
main()
asyncio.run(main())

View File

@ -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

View File

@ -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)

View File

@ -15,63 +15,119 @@
<section class="content">
<div class="container-fluid">
<!-- Core System & Network Stats -->
<div class="row">
<div class="col-lg-3 col-6">
<!-- small box -->
<div class="small-box bg-info">
<div class="inner">
<h3 id="cpu-usage">--<sup style="font-size: 20px">%</sup></h3>
<p>CPU Usage</p>
</div>
<div class="icon">
<i class="fas fa-microchip"></i>
<div class="col-md-3 col-sm-6 col-12">
<div class="info-box shadow-sm">
<span class="info-box-icon bg-info"><i class="fas fa-microchip"></i></span>
<div class="info-box-content">
<span class="info-box-text">CPU Usage</span>
<span class="info-box-number" id="cpu-usage">--</span>
</div>
</div>
</div>
<div class="col-lg-3 col-6">
<!-- small box -->
<div class="small-box bg-warning">
<div class="inner">
<h3 id="ram-usage">--</h3>
<p>RAM Usage</p>
</div>
<div class="icon">
<i class="fas fa-memory"></i>
<div class="col-md-3 col-sm-6 col-12">
<div class="info-box shadow-sm">
<span class="info-box-icon bg-warning"><i class="fas fa-memory"></i></span>
<div class="info-box-content">
<span class="info-box-text">RAM Usage</span>
<span class="info-box-number" id="ram-usage">-- / --</span>
</div>
</div>
</div>
<div class="col-lg-3 col-6">
<!-- small box -->
<div class="small-box bg-secondary">
<div class="inner">
<h3 id="total-traffic">--</h3>
<p>Total Traffic</p>
</div>
<div class="icon">
<i class="fas fa-network-wired"></i>
<div class="col-md-3 col-sm-6 col-12">
<div class="info-box shadow-sm">
<span class="info-box-icon bg-success"><i class="fas fa-users"></i></span>
<div class="info-box-content">
<span class="info-box-text">Online Users</span>
<span class="info-box-number" id="online-users">--</span>
</div>
</div>
</div>
<div class="col-lg-3 col-6">
<!-- small box -->
<div class="small-box bg-success">
<div class="inner">
<h3 id="online-users">--</h3>
<p>Online Users</p>
<div class="col-md-3 col-sm-6 col-12">
<div class="info-box shadow-sm">
<span class="info-box-icon bg-secondary"><i class="fas fa-clock"></i></span>
<div class="info-box-content">
<span class="info-box-text">Uptime</span>
<span class="info-box-number" id="uptime">--</span>
</div>
<div class="icon">
<i class="fas fa-users"></i>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 col-12">
<div class="info-box shadow-sm">
<span class="info-box-icon bg-primary"><i class="fas fa-exchange-alt"></i></span>
<div class="info-box-content">
<span class="info-box-text">Download / Upload Speed</span>
<span class="info-box-number" id="network-speed">-- / --</span>
</div>
</div>
</div>
<div class="col-md-6 col-12">
<div class="info-box shadow-sm">
<span class="info-box-icon bg-dark"><i class="fas fa-project-diagram"></i></span>
<div class="info-box-content">
<span class="info-box-text">TCP / UDP Connections</span>
<span class="info-box-number" id="network-connections">-- / --</span>
</div>
</div>
</div>
</div>
<!-- Traffic Monitoring -->
<div class="row">
<div class="col-lg-6">
<div class="card card-outline card-danger">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-power-off mr-2"></i>Traffic Since Last Reboot</h3>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center border-bottom mb-3 pb-2">
<p class="text-lg text-success"><i class="fas fa-arrow-up mr-2"></i>Uploaded</p>
<p class="text-lg" id="reboot-uploaded-traffic">--</p>
</div>
<div class="d-flex justify-content-between align-items-center border-bottom mb-3 pb-2">
<p class="text-lg text-primary"><i class="fas fa-arrow-down mr-2"></i>Downloaded</p>
<p class="text-lg" id="reboot-downloaded-traffic">--</p>
</div>
<div class="d-flex justify-content-between align-items-center">
<p class="text-lg text-info"><i class="fas fa-chart-line mr-2"></i>Combined</p>
<p class="text-lg" id="reboot-total-traffic">--</p>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-outline card-info">
<div class="card-header">
<h3 class="card-title"><i class="fas fa-history mr-2"></i>User Traffic (All Time)</h3>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center border-bottom mb-3 pb-2">
<p class="text-lg text-success"><i class="fas fa-arrow-up mr-2"></i>Uploaded</p>
<p class="text-lg" id="user-uploaded-traffic">--</p>
</div>
<div class="d-flex justify-content-between align-items-center border-bottom mb-3 pb-2">
<p class="text-lg text-primary"><i class="fas fa-arrow-down mr-2"></i>Downloaded</p>
<p class="text-lg" id="user-downloaded-traffic">--</p>
</div>
<div class="d-flex justify-content-between align-items-center">
<p class="text-lg text-info"><i class="fas fa-chart-line mr-2"></i>Combined</p>
<p class="text-lg" id="user-total-traffic">--</p>
</div>
</div>
</div>
</div>
</div>
<!-- Service Statuses -->
<div class="row">
<div class="col-lg-3 col-6">
<div class="small-box" id="hysteria2-status-box">
<div class="inner">
<h3 id="hysteria2-status">--</h3>
<p>Hysteria2</p>
<p>Hysteria2 Core</p>
</div>
<div class="icon">
<i class="fas fa-bolt"></i>
@ -93,10 +149,10 @@
<div class="small-box" id="iplimit-status-box">
<div class="inner">
<h3 id="iplimit-status">--</h3>
<p>IP Limit</p>
<p>IP Limit Service</p>
</div>
<div class="icon">
<i class="fas fa-box"></i>
<i class="fas fa-ban"></i>
</div>
</div>
</div>
@ -104,7 +160,7 @@
<div class="small-box" id="normalsub-status-box">
<div class="inner">
<h3 id="normalsub-status">--</h3>
<p>Normal Subscription</p>
<p>Subscription Service</p>
</div>
<div class="icon">
<i class="fas fa-rss"></i>
@ -122,21 +178,34 @@
fetch('{{ url_for("server_status_api") }}')
.then(response => response.json())
.then(data => {
// Core Stats
document.getElementById('cpu-usage').textContent = data.cpu_usage;
document.getElementById('ram-usage').textContent = data.ram_usage;
document.getElementById('ram-usage').textContent = `${data.ram_usage} / ${data.total_ram}`;
document.getElementById('online-users').textContent = data.online_users;
document.getElementById('total-traffic').textContent = data.total_traffic;
document.getElementById('uptime').textContent = data.uptime;
// Network Stats
document.getElementById('network-speed').innerHTML = `🔽 ${data.download_speed} / 🔼 ${data.upload_speed}`;
document.getElementById('network-connections').textContent = `${data.tcp_connections} / ${data.udp_connections}`;
// Traffic Since Reboot
document.getElementById('reboot-uploaded-traffic').textContent = data.reboot_uploaded_traffic;
document.getElementById('reboot-downloaded-traffic').textContent = data.reboot_downloaded_traffic;
document.getElementById('reboot-total-traffic').textContent = data.reboot_total_traffic;
// User Traffic (All Time)
document.getElementById('user-uploaded-traffic').textContent = data.user_uploaded_traffic;
document.getElementById('user-downloaded-traffic').textContent = data.user_downloaded_traffic;
document.getElementById('user-total-traffic').textContent = data.user_total_traffic;
})
.catch(error => console.error('Error fetching server info:', error));
}
function updateServiceStatuses() {
// Add services api in fetch
fetch('{{ url_for("server_services_status_api") }}')
.then(response => response.json())
.then(data => {
updateServiceBox('hysteria2', data.hysteria_server);
updateServiceBox('telegrambot', data.hysteria_telegram_bot);
updateServiceBox('iplimit', data.hysteria_iplimit);
@ -152,20 +221,19 @@
if (status === true) {
statusElement.textContent = 'Active';
statusBox.classList.remove('bg-danger');
statusBox.classList.add('bg-success'); // Add green
statusBox.classList.add('bg-success');
} else {
statusElement.textContent = 'Inactive';
statusBox.classList.remove('bg-success');
statusBox.classList.add('bg-danger'); // Add red
statusBox.classList.add('bg-danger');
}
}
document.addEventListener('DOMContentLoaded', function () {
updateServerInfo();
updateServiceStatuses();
setInterval(updateServerInfo, 5000);
setInterval(updateServerInfo, 2000);
setInterval(updateServiceStatuses, 10000);
});
</script>
{% endblock %}

View File

@ -22,6 +22,7 @@ urllib3==2.5.0
yarl==1.20.1
hysteria2-api==0.1.3
schedule==1.2.2
aiofiles==24.1.0
# webpanel
annotated-types==0.7.0