Track site-created 3x-ui clients and per-panel inbound whitelist for users
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+80
-2
@@ -8,8 +8,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import AdminUser, Protocol, VpnClient, VpnServer
|
||||
from app.models import AdminUser, Protocol, VpnClient, VpnServer, XuiPanel
|
||||
from app.services import vpn as vpn_service
|
||||
from app.services import xui_panels as xui_service
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
@@ -230,18 +231,41 @@ async def update_server(
|
||||
async def clients_page(
|
||||
request: Request,
|
||||
protocol: str | None = None,
|
||||
source: str | None = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(require_admin),
|
||||
):
|
||||
if _is_redirect(admin):
|
||||
return admin
|
||||
|
||||
tab = (source or protocol or "wg").lower()
|
||||
if tab in ("wireguard", "awg2"):
|
||||
tab = "wg"
|
||||
if tab not in ("wg", "xui"):
|
||||
tab = "wg"
|
||||
|
||||
query = select(VpnClient).options(selectinload(VpnClient.server)).order_by(VpnClient.id.desc())
|
||||
if protocol:
|
||||
if protocol in ("wireguard", "awg2"):
|
||||
query = query.join(VpnServer).where(VpnServer.protocol == protocol)
|
||||
tab = "wg"
|
||||
|
||||
clients = (await db.execute(query)).scalars().all()
|
||||
servers = (await db.execute(select(VpnServer).order_by(VpnServer.id))).scalars().all()
|
||||
xui_panels = await xui_service.list_panels(db)
|
||||
xui_clients = await xui_service.list_all_site_clients(db) if tab == "xui" else []
|
||||
user_inbounds_by_panel: dict[int, list] = {}
|
||||
if tab == "xui":
|
||||
for panel in xui_panels:
|
||||
rows = await xui_service.list_user_inbounds(db, panel.id)
|
||||
user_inbounds_by_panel[panel.id] = [
|
||||
{
|
||||
"inbound_id": r.inbound_id,
|
||||
"protocol": r.protocol,
|
||||
"remark": r.remark,
|
||||
"port": r.port,
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
|
||||
return request.app.state.templates.TemplateResponse(
|
||||
"admin/clients.html",
|
||||
@@ -250,6 +274,10 @@ async def clients_page(
|
||||
"admin": admin,
|
||||
"clients": clients,
|
||||
"servers": servers,
|
||||
"xui_panels": xui_panels,
|
||||
"xui_clients": xui_clients,
|
||||
"user_inbounds_by_panel": user_inbounds_by_panel,
|
||||
"tab": tab,
|
||||
"filter_protocol": protocol,
|
||||
"protocols": Protocol,
|
||||
"app_name": request.app.state.settings.app_name,
|
||||
@@ -279,6 +307,56 @@ async def create_client(
|
||||
return RedirectResponse("/admin/clients?flash=created", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@router.post("/clients/xui")
|
||||
async def create_xui_client_from_clients(
|
||||
request: Request,
|
||||
panel_id: int = Form(...),
|
||||
protocol: str = Form(...),
|
||||
email: str = Form(...),
|
||||
inbound_id: int = Form(...),
|
||||
total_gb: int = Form(0),
|
||||
limit_ip: int = Form(0),
|
||||
expiry_days: int = Form(30),
|
||||
start_after_first_use: str = Form(""),
|
||||
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/clients?source=xui&flash=error:Панель не найдена",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
try:
|
||||
await xui_service.add_xui_client(
|
||||
db,
|
||||
panel,
|
||||
protocol=protocol,
|
||||
email=email.strip(),
|
||||
inbound_id=inbound_id,
|
||||
total_gb=total_gb,
|
||||
limit_ip=limit_ip,
|
||||
expiry_days=expiry_days,
|
||||
start_after_first_use=start_after_first_use == "1",
|
||||
flow=flow.strip(),
|
||||
comment=comment.strip(),
|
||||
require_user_available=True,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return RedirectResponse(
|
||||
f"/admin/clients?source=xui&flash=error:{exc}",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
return RedirectResponse(
|
||||
"/admin/clients?source=xui&flash=created",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/clients/{client_id}/toggle")
|
||||
async def toggle_client(
|
||||
client_id: int,
|
||||
|
||||
Reference in New Issue
Block a user