feat: new home page with server status bars, client count, tech info

This commit is contained in:
SashegDev
2026-05-20 18:09:36 +00:00
parent e855c30285
commit a918f669e5
+112 -23
View File
@@ -1034,6 +1034,44 @@ h1{{font-size:24px}}
@app.get("/")
async def home_page():
title = settings.get("general", {}).get("title", "ZernProxy")
da_url = settings.get("payments", {}).get("donationalerts", {}).get("url", "")
conn = get_db()
try:
total = conn.execute("SELECT COUNT(*) as c FROM users").fetchone()["c"]
finally:
conn.close()
statuses = await fetch_servers_status()
servers_html = ""
for s in statuses:
chk = s.get("checks", {})
cpu = chk.get("CPU", {}).get("value")
ram = chk.get("RAM", {}).get("value")
disk = chk.get("Disk /", {}).get("value")
net_raw = chk.get("Net ↓↑", {}).get("value", "")
server_name = s.get("server_name", s["name"].upper())
service_icons = ""
for key in ("Caddy", "Minecraft", "Bio site", "Main site", "3x-UI"):
val = chk.get(key, {}).get("value", "")
if val:
dot = "🟢" if "🟢" in val else ("🔴" if "🔴" in val or "🔴" in str(val) else ("🟡" if "🟡" in val else ""))
service_icons += f'<span title="{key}: {val}" style="font-size:14px;margin-right:4px">{dot or ""}</span>'
servers_html += f'''
<div class="server">
<div class="srv-head"><span class="flag">{get_flag_emoji(s.get("country",""))}</span> {server_name}</div>
<div class="srv-body">
<div class="metric"><span class="ml">CPU</span><div class="mb"><div class="mf" style="width:{cpu if cpu is not None else 0}%"></div></div><span class="mv">{f"{cpu:.1f}%" if cpu is not None else ""}</span></div>
<div class="metric"><span class="ml">RAM</span><div class="mb"><div class="mf" style="width:{ram if ram is not None else 0}%"></div></div><span class="mv">{f"{ram:.1f}%" if ram is not None else ""}</span></div>
<div class="metric"><span class="ml">DISK</span><div class="mb"><div class="mf" style="width:{disk if disk is not None else 0}%"></div></div><span class="mv">{f"{disk:.1f}%" if disk is not None else ""}</span></div>
<div class="metric"><span class="ml">NET</span><div class="mb" style="background:none;padding:0"><span style="font-size:12px;color:#aaa;font-family:monospace">{net_raw}</span></div></div>
</div>
{f'<div class="srv-svc">{service_icons}</div>' if service_icons else ''}
</div>'''
return HTMLResponse(content=f'''<!DOCTYPE html>
<html lang="ru">
<head>
@@ -1043,39 +1081,90 @@ async def home_page():
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
body{{font-family:Roboto,sans-serif;background:linear-gradient(135deg,#1a1a2e,#16213e);min-height:100vh;color:#fff}}
.container{{max-width:700px;margin:0 auto;padding:40px 20px;text-align:center}}
.logo{{font-size:64px;margin-bottom:20px}}
h1{{font-size:32px;font-weight:500;margin-bottom:16px}}
p{{color:#aaa;font-size:16px;line-height:1.6;margin-bottom:32px}}
.btn{{display:inline-block;padding:14px 32px;background:#4CAF50;color:#fff;text-decoration:none;border-radius:12px;font-size:16px;font-weight:500;margin:8px;transition:.3s}}
.btn:hover{{background:#45a049;transform:translateY(-2px)}}
.cards{{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-top:40px}}
.card{{background:rgba(255,255,255,0.05);border-radius:16px;padding:24px;border:1px solid rgba(255,255,255,0.1)}}
.card .icon{{font-size:32px;margin-bottom:12px}}
.card h3{{font-size:16px;margin-bottom:8px}}
.card p{{font-size:13px;color:#888}}
.footer{{margin-top:60px;color:#555;font-size:13px}}
.container{{max-width:800px;margin:0 auto;padding:30px 16px}}
.header{{text-align:center;margin-bottom:24px}}
.header h1{{font-size:26px;font-weight:500;margin-bottom:6px}}
.header p{{color:#888;font-size:14px}}
.stats{{display:flex;gap:12px;justify-content:center;margin-bottom:28px;flex-wrap:wrap}}
.stat{{background:rgba(255,255,255,0.04);border-radius:12px;padding:14px 22px;text-align:center;border:1px solid rgba(255,255,255,0.06)}}
.stat .n{{font-size:26px;font-weight:500}}
.stat .l{{font-size:12px;color:#888;margin-top:2px}}
.servers{{display:grid;gap:12px}}
.server{{background:rgba(255,255,255,0.04);border-radius:14px;padding:14px 16px;border:1px solid rgba(255,255,255,0.06)}}
.srv-head{{font-size:14px;font-weight:500;margin-bottom:10px}}
.srv-head .flag{{font-size:18px;margin-right:6px}}
.srv-body{{display:grid;gap:7px}}
.metric{{display:grid;grid-template-columns:42px 1fr 52px;align-items:center;gap:10px}}
.ml{{font-size:12px;color:#888;font-weight:500}}
.mb{{height:8px;background:rgba(255,255,255,0.08);border-radius:4px;overflow:hidden}}
.mf{{height:100%;border-radius:4px;background:linear-gradient(90deg,#4CAF50,#8BC34A);transition:width .5s}}
.mv{{font-size:11px;color:#888;text-align:right;font-family:monospace}}
.srv-svc{{margin-top:8px;padding-top:8px;border-top:1px solid rgba(255,255,255,0.05)}}
.info{{margin-top:28px;background:rgba(255,255,255,0.03);border-radius:14px;padding:16px;border:1px solid rgba(255,255,255,0.06)}}
.info h3{{font-size:14px;font-weight:500;margin-bottom:8px}}
.info p{{font-size:12px;color:#888;line-height:1.7}}
.info p span{{color:#aaa}}
.btns{{text-align:center;margin-top:24px}}
.btn{{display:inline-block;padding:11px 24px;background:#4CAF50;color:#fff;text-decoration:none;border-radius:10px;font-size:13px;margin:4px;transition:.3s}}
.btn:hover{{background:#45a049}}
.btn.da{{background:#E91E63}}
.btn.da:hover{{background:#c2185b}}
.footer{{text-align:center;margin-top:28px;color:#444;font-size:12px}}
</style>
</head>
<body>
<div class="container">
<div class="logo">⚡</div>
<h1>{title}</h1>
<p>Быстрый и надёжный VPN. Подписка на основе подписки. Безлимитный трафик на всех тарифах.</p>
<div class="cards">
<div class="card"><div class="icon">🆓</div><h3>Free</h3><p>Базовый доступ к серверам. Безлимитный трафик.</p></div>
<div class="card"><div class="icon">🧪</div><h3>Test 7 дней</h3><p>Пробный период за 50₽. 5GB трафика.</p></div>
<div class="card"><div class="icon">⭐</div><h3>Premium</h3><p>Полный доступ, приоритетные серверы. От 150₽/мес.</p></div>
<div class="header">
<h1>{title}</h1>
<p>Быстрый и надёжный VPN-сервис</p>
</div>
<div style="margin-top:32px">
<a href="/admin/users" class="btn">👤 Панель управления</a>
<a href="{settings.get("payments",{}).get("donationalerts",{}).get("url","#")}" class="btn" style="background:#E91E63">❤️ Поддержать проект</a>
<div class="stats">
<div class="stat"><div class="n">{total}</div><div class="l">👥 Клиентов</div></div>
<div class="stat"><div class="n">{len(servers)}</div><div class="l">🌍 Локаций</div></div>
</div>
<div class="footer">© 2026 {title} · <a href="/admin/users" style="color:#555">admin</a></div>
<div class="servers">{servers_html}</div>
<div class="info">
<h3>🔧 Технические детали</h3>
<p>
<span>🔒</span> Шифрование: VLESS + XTLS Vision / Reality<br>
<span>⚡</span> Протоколы: TCP, WebSocket, gRPC<br>
<span>🛡️</span> DDoS-защита на всех серверах<br>
<span>📡</span> Каналы: 1-10 Gbit/s<br>
<span>🔐</span> Zero-log политика — трафик не логируется<br>
<span>🌐</span> Поддержка Happ (iOS/Android/Desktop)
</p>
</div>
<div class="btns">
<a href="{da_url}" class="btn da">❤️ Поддержать проект</a>
</div>
<div class="footer">© 2026 {title}</div>
</div>
</body>
</html>''')
async def fetch_servers_status() -> list:
results = []
async with httpx.AsyncClient(timeout=8.0, verify=False) as client:
for srv in servers:
url = srv.get("status_url", "")
entry = {"name": srv["name"], "country": srv.get("country",""), "checks": {}}
if not url:
results.append(entry)
continue
try:
resp = await client.get(url)
if resp.status_code == 200:
data = resp.json()
entry["server_name"] = data.get("server_name", "")
entry["checks"] = data.get("checks", {})
else:
entry["checks"]["error"] = {"value": f"HTTP {resp.status_code}"}
except Exception as e:
entry["checks"]["error"] = {"value": str(e)[:50]}
results.append(entry)
return results
@app.get("/webhook/tg")
async def tg_webhook():
bot_cfg = settings.get("bot", {})