215 lines
6.6 KiB
Python
215 lines
6.6 KiB
Python
"""Admin routes for 3x-ui API panels."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from urllib.parse import quote
|
|
|
|
from fastapi import APIRouter, Depends, Form, Query, Request, status
|
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import get_db
|
|
from app.models import XuiPanel
|
|
from app.routers.admin import _is_redirect, require_admin
|
|
from app.services import xui_panels as xui_service
|
|
|
|
router = APIRouter(prefix="/admin/xui", tags=["xui"])
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
|
async def xui_list(request: Request, db: AsyncSession = Depends(get_db), admin=Depends(require_admin)):
|
|
if _is_redirect(admin):
|
|
return admin
|
|
panels = await xui_service.list_panels(db)
|
|
return request.app.state.templates.TemplateResponse(
|
|
"admin/xui.html",
|
|
{
|
|
"request": request,
|
|
"admin": admin,
|
|
"panels": panels,
|
|
"app_name": request.app.state.settings.app_name,
|
|
"flash": request.query_params.get("flash"),
|
|
},
|
|
)
|
|
|
|
|
|
@router.post("")
|
|
async def xui_create(
|
|
name: str = Form(""),
|
|
base_url: str = Form(...),
|
|
username: str = Form(""),
|
|
password: str = Form(""),
|
|
api_token: str = Form(""),
|
|
verify_ssl: str = Form(""),
|
|
db: AsyncSession = Depends(get_db),
|
|
admin=Depends(require_admin),
|
|
):
|
|
if _is_redirect(admin):
|
|
return admin
|
|
try:
|
|
await xui_service.create_panel(
|
|
db,
|
|
name=name,
|
|
base_url=base_url,
|
|
username=username,
|
|
password=password,
|
|
api_token=api_token,
|
|
verify_ssl=verify_ssl == "1",
|
|
)
|
|
except Exception as exc: # noqa: BLE001
|
|
return RedirectResponse(
|
|
f"/admin/xui?flash=error:{exc}",
|
|
status_code=status.HTTP_303_SEE_OTHER,
|
|
)
|
|
return RedirectResponse("/admin/xui?flash=created", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.post("/{panel_id}/check")
|
|
async def xui_check(
|
|
panel_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
admin=Depends(require_admin),
|
|
):
|
|
if _is_redirect(admin):
|
|
return admin
|
|
try:
|
|
await xui_service.check_panel(db, panel_id)
|
|
except Exception as exc: # noqa: BLE001
|
|
return RedirectResponse(
|
|
f"/admin/xui?flash=error:{exc}",
|
|
status_code=status.HTTP_303_SEE_OTHER,
|
|
)
|
|
return RedirectResponse(
|
|
f"/admin/xui/{panel_id}?flash=ok",
|
|
status_code=status.HTTP_303_SEE_OTHER,
|
|
)
|
|
|
|
|
|
@router.post("/{panel_id}/delete")
|
|
async def xui_delete(
|
|
panel_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
admin=Depends(require_admin),
|
|
):
|
|
if _is_redirect(admin):
|
|
return admin
|
|
await xui_service.delete_panel(db, panel_id)
|
|
return RedirectResponse("/admin/xui?flash=deleted", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
|
|
@router.get("/{panel_id}/inbounds")
|
|
async def xui_inbounds_api(
|
|
panel_id: int,
|
|
protocol: str | None = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
admin=Depends(require_admin),
|
|
):
|
|
"""JSON: available inbounds for dropdown (optionally filtered by protocol)."""
|
|
if _is_redirect(admin):
|
|
return JSONResponse({"success": False, "error": "unauthorized"}, status_code=401)
|
|
panel = await db.get(XuiPanel, panel_id)
|
|
if not panel:
|
|
return JSONResponse({"success": False, "error": "not found"}, status_code=404)
|
|
try:
|
|
items = await xui_service.load_available_inbounds(panel, protocol=protocol)
|
|
return JSONResponse({"success": True, "obj": items})
|
|
except Exception as exc: # noqa: BLE001
|
|
return JSONResponse({"success": False, "error": str(exc)}, status_code=400)
|
|
|
|
|
|
@router.get("/{panel_id}", response_class=HTMLResponse)
|
|
async def xui_detail(
|
|
panel_id: int,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
admin=Depends(require_admin),
|
|
):
|
|
if _is_redirect(admin):
|
|
return admin
|
|
panel = await db.get(XuiPanel, panel_id)
|
|
if not panel:
|
|
return RedirectResponse("/admin/xui", status_code=status.HTTP_303_SEE_OTHER)
|
|
|
|
data = None
|
|
error = None
|
|
try:
|
|
data = await xui_service.fetch_panel_data(panel)
|
|
except Exception as exc: # noqa: BLE001
|
|
error = str(exc)
|
|
|
|
created = None
|
|
created_raw = request.query_params.get("created")
|
|
if created_raw:
|
|
try:
|
|
created = json.loads(created_raw)
|
|
except json.JSONDecodeError:
|
|
created = None
|
|
|
|
return request.app.state.templates.TemplateResponse(
|
|
"admin/xui_detail.html",
|
|
{
|
|
"request": request,
|
|
"admin": admin,
|
|
"panel": panel,
|
|
"data": data,
|
|
"created": created,
|
|
"error": error,
|
|
"app_name": request.app.state.settings.app_name,
|
|
"flash": request.query_params.get("flash"),
|
|
},
|
|
)
|
|
|
|
|
|
@router.post("/{panel_id}/clients")
|
|
async def xui_add_client(
|
|
panel_id: int,
|
|
protocol: str = Form(...),
|
|
email: str = Form(...),
|
|
inbound_id: int = Form(...),
|
|
total_gb: int = Form(0),
|
|
limit_ip: int = Form(0),
|
|
flow: str = Form(""),
|
|
comment: str = Form(""),
|
|
db: AsyncSession = Depends(get_db),
|
|
admin=Depends(require_admin),
|
|
):
|
|
if _is_redirect(admin):
|
|
return admin
|
|
panel = await db.get(XuiPanel, panel_id)
|
|
if not panel:
|
|
return RedirectResponse("/admin/xui", status_code=status.HTTP_303_SEE_OTHER)
|
|
try:
|
|
result = await xui_service.add_xui_client(
|
|
panel,
|
|
protocol=protocol,
|
|
email=email.strip(),
|
|
inbound_id=inbound_id,
|
|
total_gb=total_gb,
|
|
limit_ip=limit_ip,
|
|
flow=flow.strip(),
|
|
comment=comment.strip(),
|
|
)
|
|
# Keep redirect short: store essentials only
|
|
slim = {
|
|
"protocol": result.get("protocol"),
|
|
"email": result.get("email"),
|
|
"inbound_id": result.get("inbound_id"),
|
|
"uuid": result.get("uuid"),
|
|
"flow": result.get("flow"),
|
|
"privateKey": result.get("privateKey"),
|
|
"publicKey": result.get("publicKey"),
|
|
"links": result.get("links") or [],
|
|
"inbound": result.get("inbound"),
|
|
}
|
|
payload = quote(json.dumps(slim, ensure_ascii=False))
|
|
return RedirectResponse(
|
|
f"/admin/xui/{panel_id}?flash=client_created&created={payload}",
|
|
status_code=status.HTTP_303_SEE_OTHER,
|
|
)
|
|
except Exception as exc: # noqa: BLE001
|
|
return RedirectResponse(
|
|
f"/admin/xui/{panel_id}?flash=error:{exc}",
|
|
status_code=status.HTTP_303_SEE_OTHER,
|
|
)
|