feat: Реализован новый веб-интерфейс и бэкенд для управления VPN-клиентом, включая списки серверов, элементы управления прокси и опции конфигурации.

This commit is contained in:
2026-01-15 18:39:39 +03:00
parent c4915389a7
commit 6e97bb9f61
22 changed files with 2412 additions and 2275 deletions

176
web/app/vless.py Normal file
View File

@@ -0,0 +1,176 @@
from urllib.parse import unquote
import json
import urllib.request
from .config import PROXY_PORT, DATA_DIR, CONFIG_FILE
def parse_vless_url(url: str) -> dict:
"""Parse VLESS URL and extract connection parameters"""
if not url.startswith("vless://"):
raise ValueError("URL must start with vless://")
# Remove scheme
url_no_scheme = url[8:]
# Split by fragment (#tag)
if '#' in url_no_scheme:
url_part, tag = url_no_scheme.split('#', 1)
tag = unquote(tag)
else:
url_part = url_no_scheme
tag = "reality"
# Split by query (?)
if '?' in url_part:
uuid_host_port, query_string = url_part.split('?', 1)
else:
raise ValueError("Missing query parameters")
# Parse UUID@host:port
if '@' not in uuid_host_port:
raise ValueError("Missing @ separator")
uuid_str, host_port = uuid_host_port.split('@', 1)
if ':' not in host_port:
raise ValueError("Missing port")
host, port_str = host_port.rsplit(':', 1)
port = int(port_str)
# Parse query parameters
params = {}
for param in query_string.split('&'):
if '=' in param:
key, value = param.split('=', 1)
params[key] = unquote(value)
# Extract required parameters
pbk = params.get('pbk', '')
sid = params.get('sid', '')
sni = params.get('sni', host)
fp = params.get('fp', 'chrome')
flow = params.get('flow', '')
if not pbk or not sid:
raise ValueError("Missing required parameters: pbk or sid")
return {
'uuid': uuid_str,
'server': host,
'server_port': port,
'tag': tag,
'public_key': pbk,
'short_id': sid,
'server_name': sni,
'fingerprint': fp,
'flow': flow
}
def generate_vless_config(vless_params: dict) -> dict:
"""Generate sing-box configuration from VLESS parameters"""
config = {
"dns": {
"independent_cache": True
},
"log": {
"level": "debug",
"disabled": True,
"timestamp": True
},
"route": {
"final": vless_params['tag'],
"auto_detect_interface": True
},
"inbounds": [
{
"tag": "mixed-in",
"type": "mixed",
"sniff": True,
"users": [],
"listen": "0.0.0.0",
"listen_port": PROXY_PORT,
"set_system_proxy": False
}
],
"outbounds": [
{
"type": "vless",
"tag": vless_params['tag'],
"server": vless_params['server'],
"server_port": vless_params['server_port'],
"flow": vless_params['flow'],
"tls": {
"enabled": True,
"server_name": vless_params['server_name'],
"reality": {
"enabled": True,
"public_key": vless_params['public_key'],
"short_id": vless_params['short_id']
},
"utls": {
"enabled": True,
"fingerprint": vless_params['fingerprint']
}
},
"uuid": vless_params['uuid']
},
{
"tag": "direct",
"type": "direct"
}
]
}
return config
def generate_direct_config() -> bool:
"""Generate a direct connection config (bypass all proxies)"""
try:
config = {
"dns": {
"independent_cache": True
},
"log": {
"level": "debug",
"disabled": True,
"timestamp": True
},
"route": {
"final": "direct",
"auto_detect_interface": True
},
"inbounds": [
{
"tag": "mixed-in",
"type": "mixed",
"sniff": True,
"users": [],
"listen": "0.0.0.0",
"listen_port": PROXY_PORT,
"set_system_proxy": False
}
],
"outbounds": [
{
"tag": "direct",
"type": "direct"
}
]
}
DATA_DIR.mkdir(parents=True, exist_ok=True)
CONFIG_FILE.write_text(json.dumps(config, indent=2, ensure_ascii=False))
# Reload sing-box
try:
urllib.request.urlopen("http://127.0.0.1:9090/reload", timeout=3)
except Exception:
pass
return True
except Exception as e:
print(f"[WebUI] Failed to generate direct config: {e}")
return False