Files
vpn/app/services/xui_panels.py
T

135 lines
3.9 KiB
Python

"""CRUD + connection tests for linked 3x-ui panels."""
from __future__ import annotations
import json
from datetime import datetime, timezone
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models import XuiPanel
from app.services.xui import XuiClient, XuiApiError, normalize_base_url
async def list_panels(session: AsyncSession) -> list[XuiPanel]:
result = await session.execute(select(XuiPanel).order_by(XuiPanel.id.desc()))
return list(result.scalars().all())
async def create_panel(
session: AsyncSession,
*,
name: str,
base_url: str,
username: str = "",
password: str = "",
api_token: str = "",
verify_ssl: bool = True,
) -> XuiPanel:
token = api_token.strip() or None
user = username.strip() or None
pwd = password.strip() or None
if not token and not (user and pwd):
raise ValueError("Укажите API Token или логин и пароль")
url = normalize_base_url(base_url)
panel = XuiPanel(
name=(name.strip() or url),
base_url=url,
username=user,
password=pwd,
api_token=token,
verify_ssl=verify_ssl,
is_enabled=True,
)
session.add(panel)
await session.commit()
await session.refresh(panel)
# Verify immediately
await check_panel(session, panel.id)
await session.refresh(panel)
if not panel.is_reachable:
raise ValueError(panel.last_error or "Не удалось подключиться к 3x-ui")
return panel
async def check_panel(session: AsyncSession, panel_id: int) -> XuiPanel:
panel = await session.get(XuiPanel, panel_id)
if not panel:
raise ValueError("Панель не найдена")
now = datetime.now(timezone.utc)
try:
async with XuiClient(
panel.base_url,
username=panel.username,
password=panel.password,
api_token=panel.api_token,
verify_ssl=panel.verify_ssl,
) as client:
info = await client.test_connection()
panel.is_reachable = True
panel.last_error = None
panel.panel_info = json.dumps(info, ensure_ascii=False, default=str)[:8000]
panel.last_checked = now
except (XuiApiError, ValueError, OSError) as exc:
panel.is_reachable = False
panel.last_error = str(exc)[:2000]
panel.last_checked = now
await session.commit()
await session.refresh(panel)
raise ValueError(str(exc)) from exc
await session.commit()
await session.refresh(panel)
return panel
async def delete_panel(session: AsyncSession, panel_id: int) -> None:
panel = await session.get(XuiPanel, panel_id)
if panel:
await session.delete(panel)
await session.commit()
async def fetch_panel_data(panel: XuiPanel) -> dict:
async with XuiClient(
panel.base_url,
username=panel.username,
password=panel.password,
api_token=panel.api_token,
verify_ssl=panel.verify_ssl,
) as client:
status = await client.get_status()
inbounds = await client.list_inbounds()
try:
clients = await client.list_clients()
except XuiApiError:
clients = []
return {"status": status, "inbounds": inbounds, "clients": clients}
async def add_xui_client(
panel: XuiPanel,
*,
email: str,
inbound_ids: list[int],
total_gb: int = 0,
limit_ip: int = 0,
) -> dict:
async with XuiClient(
panel.base_url,
username=panel.username,
password=panel.password,
api_token=panel.api_token,
verify_ssl=panel.verify_ssl,
) as client:
return await client.add_client(
email=email,
inbound_ids=inbound_ids,
total_gb=total_gb * 1024 * 1024 * 1024 if total_gb > 0 else 0,
limit_ip=limit_ip,
)