3x-ui: create VLESS/WireGuard clients and reload available inbounds

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-25 22:09:59 +03:00
co-authored by Cursor
parent b67e7b80ba
commit 1626c16df0
4 changed files with 525 additions and 136 deletions
+58 -14
View File
@@ -3,9 +3,10 @@
from __future__ import annotations
import json
from urllib.parse import quote
from fastapi import APIRouter, Depends, Form, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse
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
@@ -79,7 +80,10 @@ async def xui_check(
f"/admin/xui?flash=error:{exc}",
status_code=status.HTTP_303_SEE_OTHER,
)
return RedirectResponse("/admin/xui?flash=ok", 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")
@@ -94,6 +98,26 @@ async def xui_delete(
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,
@@ -114,12 +138,13 @@ async def xui_detail(
except Exception as exc: # noqa: BLE001
error = str(exc)
info = None
if panel.panel_info:
created = None
created_raw = request.query_params.get("created")
if created_raw:
try:
info = json.loads(panel.panel_info)
created = json.loads(created_raw)
except json.JSONDecodeError:
info = None
created = None
return request.app.state.templates.TemplateResponse(
"admin/xui_detail.html",
@@ -128,7 +153,7 @@ async def xui_detail(
"admin": admin,
"panel": panel,
"data": data,
"info": info,
"created": created,
"error": error,
"app_name": request.app.state.settings.app_name,
"flash": request.query_params.get("flash"),
@@ -139,10 +164,13 @@ async def xui_detail(
@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),
):
@@ -152,19 +180,35 @@ async def xui_add_client(
if not panel:
return RedirectResponse("/admin/xui", status_code=status.HTTP_303_SEE_OTHER)
try:
await xui_service.add_xui_client(
result = await xui_service.add_xui_client(
panel,
protocol=protocol,
email=email.strip(),
inbound_ids=[inbound_id],
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,
)
return RedirectResponse(
f"/admin/xui/{panel_id}?flash=client_created",
status_code=status.HTTP_303_SEE_OTHER,
)