Show WireGuard QR, full .conf and ZIP download after 3x-ui client create
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+143
-1
@@ -1,4 +1,5 @@
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
import qrcode
|
||||
from fastapi import APIRouter, Depends, Form, Request, status
|
||||
@@ -286,7 +287,7 @@ async def create_xui_client_from_clients(
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
try:
|
||||
await xui_service.add_xui_client(
|
||||
result = await xui_service.add_xui_client(
|
||||
db,
|
||||
panel,
|
||||
protocol=protocol,
|
||||
@@ -305,12 +306,153 @@ async def create_xui_client_from_clients(
|
||||
f"/admin/clients?flash=error:{exc}",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
site_id = result.get("site_client_id")
|
||||
if site_id:
|
||||
return RedirectResponse(
|
||||
f"/admin/clients/xui/{site_id}?flash=created",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
return RedirectResponse(
|
||||
"/admin/clients?flash=created",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/clients/xui/{site_client_id}", response_class=HTMLResponse)
|
||||
async def xui_site_client_detail(
|
||||
site_client_id: int,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(require_admin),
|
||||
):
|
||||
if _is_redirect(admin):
|
||||
return admin
|
||||
row = await xui_service.get_site_client(db, site_client_id)
|
||||
if not row:
|
||||
return RedirectResponse("/admin/clients", status_code=status.HTTP_303_SEE_OTHER)
|
||||
stem, config = await xui_service.site_client_download_payload(row)
|
||||
return request.app.state.templates.TemplateResponse(
|
||||
"admin/xui_client_detail.html",
|
||||
{
|
||||
"request": request,
|
||||
"admin": admin,
|
||||
"client": row,
|
||||
"config": config,
|
||||
"filename_stem": stem,
|
||||
"app_name": request.app.state.settings.app_name,
|
||||
"flash": request.query_params.get("flash"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/clients/xui/{site_client_id}/config")
|
||||
async def xui_site_client_config(
|
||||
site_client_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(require_admin),
|
||||
):
|
||||
if _is_redirect(admin):
|
||||
return admin
|
||||
row = await xui_service.get_site_client(db, site_client_id)
|
||||
if not row:
|
||||
return RedirectResponse("/admin/clients", status_code=status.HTTP_303_SEE_OTHER)
|
||||
stem, config = await xui_service.site_client_download_payload(row)
|
||||
if not config:
|
||||
return RedirectResponse(
|
||||
f"/admin/clients/xui/{site_client_id}?flash=error:Нет конфига",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
ext = "conf" if row.protocol == "wireguard" else "txt"
|
||||
return Response(
|
||||
content=config,
|
||||
media_type="text/plain",
|
||||
headers={"Content-Disposition": f'attachment; filename="{stem}.{ext}"'},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/clients/xui/{site_client_id}/qr")
|
||||
async def xui_site_client_qr(
|
||||
site_client_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(require_admin),
|
||||
):
|
||||
if _is_redirect(admin):
|
||||
return admin
|
||||
row = await xui_service.get_site_client(db, site_client_id)
|
||||
if not row:
|
||||
return RedirectResponse("/admin/clients", status_code=status.HTTP_303_SEE_OTHER)
|
||||
_, config = await xui_service.site_client_download_payload(row)
|
||||
if not config:
|
||||
return Response(status_code=404)
|
||||
img = qrcode.make(config)
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="PNG")
|
||||
buf.seek(0)
|
||||
return StreamingResponse(buf, media_type="image/png")
|
||||
|
||||
|
||||
@router.get("/clients/xui/{site_client_id}/zip")
|
||||
async def xui_site_client_zip(
|
||||
site_client_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(require_admin),
|
||||
):
|
||||
if _is_redirect(admin):
|
||||
return admin
|
||||
row = await xui_service.get_site_client(db, site_client_id)
|
||||
if not row:
|
||||
return RedirectResponse("/admin/clients", status_code=status.HTTP_303_SEE_OTHER)
|
||||
stem, config = await xui_service.site_client_download_payload(row)
|
||||
if not config:
|
||||
return RedirectResponse(
|
||||
f"/admin/clients/xui/{site_client_id}?flash=error:Нет конфига",
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
ext = "conf" if row.protocol == "wireguard" else "txt"
|
||||
buf = io.BytesIO()
|
||||
with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr(f"{stem}.{ext}", config)
|
||||
if row.protocol == "wireguard":
|
||||
zf.writestr(
|
||||
f"{stem}-info.txt",
|
||||
"\n".join(
|
||||
[
|
||||
f"Email: {row.email}",
|
||||
f"Protocol: {row.protocol}",
|
||||
f"Address: {row.address or ''}",
|
||||
f"Endpoint: {row.endpoint or ''}",
|
||||
f"DNS: {row.dns or ''}",
|
||||
f"MTU: {row.mtu or ''}",
|
||||
f"Panel: {row.panel.name if row.panel else ''}",
|
||||
"",
|
||||
"Import the .conf into WireGuard / AmneziaWG, or scan the QR on the panel.",
|
||||
"",
|
||||
]
|
||||
),
|
||||
)
|
||||
else:
|
||||
zf.writestr(
|
||||
f"{stem}-info.txt",
|
||||
"\n".join(
|
||||
[
|
||||
f"Email: {row.email}",
|
||||
f"Protocol: {row.protocol}",
|
||||
f"UUID: {row.uuid or ''}",
|
||||
f"Flow: {row.flow or ''}",
|
||||
"",
|
||||
"Import the link into a VLESS client, or scan the QR on the panel.",
|
||||
"",
|
||||
]
|
||||
),
|
||||
)
|
||||
buf.seek(0)
|
||||
return Response(
|
||||
content=buf.getvalue(),
|
||||
media_type="application/zip",
|
||||
headers={"Content-Disposition": f'attachment; filename="{stem}.zip"'},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/clients/{client_id}/toggle")
|
||||
async def toggle_client(
|
||||
client_id: int,
|
||||
|
||||
Reference in New Issue
Block a user