135 lines
3.9 KiB
Python
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,
|
|
)
|