diff --git a/app/routers/admin.py b/app/routers/admin.py index 7638f27..5e3cb2a 100644 --- a/app/routers/admin.py +++ b/app/routers/admin.py @@ -8,7 +8,7 @@ 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, XuiPanel +from app.models import AdminUser, Protocol, VpnClient, VpnServer, XuiPanel, XuiPanelInbound, XuiSiteClient from app.services import vpn as vpn_service from app.services import xui_panels as xui_service @@ -35,47 +35,41 @@ async def dashboard(request: Request, db: AsyncSession = Depends(get_db), admin= if _is_redirect(admin): return admin - servers = (await db.execute(select(VpnServer).order_by(VpnServer.id))).scalars().all() - clients_total = await db.scalar(select(func.count()).select_from(VpnClient)) or 0 - clients_active = await db.scalar( - select(func.count()).select_from(VpnClient).where(VpnClient.is_enabled.is_(True)) + xui_panels = await xui_service.list_panels(db) + xui_clients_total = await db.scalar(select(func.count()).select_from(XuiSiteClient)) or 0 + user_inbounds_total = await db.scalar( + select(func.count()) + .select_from(XuiPanelInbound) + .where(XuiPanelInbound.is_available_for_users.is_(True)) ) or 0 + user_inbounds_by_panel: dict[int, list] = {} + 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/dashboard.html", { "request": request, "admin": admin, - "servers": servers, - "clients_total": clients_total, - "clients_active": clients_active, + "xui_panels": xui_panels, + "xui_clients_total": xui_clients_total, + "user_inbounds_total": user_inbounds_total, + "user_inbounds_by_panel": user_inbounds_by_panel, "app_name": request.app.state.settings.app_name, }, ) @router.get("/servers", response_class=HTMLResponse) -async def servers_page(request: Request, db: AsyncSession = Depends(get_db), admin=Depends(require_admin)): +async def servers_page(admin=Depends(require_admin)): + """WG/AWG servers UI временно скрыт — редирект на клиентов 3x-ui.""" if _is_redirect(admin): return admin - servers = ( - await db.execute( - select(VpnServer).options(selectinload(VpnServer.clients)).order_by(VpnServer.id) - ) - ).scalars().all() - probes = await vpn_service.probe_servers(list(servers)) - return request.app.state.templates.TemplateResponse( - "admin/servers.html", - { - "request": request, - "admin": admin, - "servers": servers, - "probes": probes, - "protocols": Protocol, - "app_name": request.app.state.settings.app_name, - "flash": request.query_params.get("flash"), - }, - ) + return RedirectResponse("/admin/clients", status_code=status.HTTP_303_SEE_OTHER) @router.post("/servers") @@ -230,83 +224,43 @@ async def update_server( @router.get("/clients", response_class=HTMLResponse) 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 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 [] + xui_clients = await xui_service.list_all_site_clients(db) 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 - ] + 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 + ] + panels_with_inbounds = [p for p in xui_panels if user_inbounds_by_panel.get(p.id)] return request.app.state.templates.TemplateResponse( "admin/clients.html", { "request": request, "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, + "panels_with_inbounds": panels_with_inbounds, "app_name": request.app.state.settings.app_name, "flash": request.query_params.get("flash"), }, ) -@router.post("/clients") -async def create_client( - request: Request, - name: str = Form(...), - server_id: int = Form(...), - notes: str = Form(""), - db: AsyncSession = Depends(get_db), - admin=Depends(require_admin), -): - if _is_redirect(admin): - return admin - try: - await vpn_service.create_client(db, server_id=server_id, name=name, notes=notes or None) - except Exception as exc: # noqa: BLE001 - return RedirectResponse( - f"/admin/clients?flash=error:{exc}", - status_code=status.HTTP_303_SEE_OTHER, - ) - 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, @@ -328,7 +282,7 @@ async def create_xui_client_from_clients( panel = await db.get(XuiPanel, panel_id) if not panel: return RedirectResponse( - "/admin/clients?source=xui&flash=error:Панель не найдена", + "/admin/clients?flash=error:Панель не найдена", status_code=status.HTTP_303_SEE_OTHER, ) try: @@ -348,11 +302,11 @@ async def create_xui_client_from_clients( ) except Exception as exc: # noqa: BLE001 return RedirectResponse( - f"/admin/clients?source=xui&flash=error:{exc}", + f"/admin/clients?flash=error:{exc}", status_code=status.HTTP_303_SEE_OTHER, ) return RedirectResponse( - "/admin/clients?source=xui&flash=created", + "/admin/clients?flash=created", status_code=status.HTTP_303_SEE_OTHER, ) diff --git a/app/templates/admin/clients.html b/app/templates/admin/clients.html index ed859ae..9ead12f 100644 --- a/app/templates/admin/clients.html +++ b/app/templates/admin/clients.html @@ -4,12 +4,7 @@

Клиенты

- WireGuard / AWG - 3x-ui - {% if tab == 'wg' %} - WireGuard - AWG 2.0 - {% endif %} + Управление 3x-ui
@@ -23,90 +18,77 @@ {% endif %} {% endif %} -{% if tab == 'wg' %}
-
Новый доступ (WireGuard / AWG)
-
-
- - - - -
+
+ Доступные серверы 3x-ui + добавленные панели и inbound’ы, включённые для пользователей
-
- -
+ {% if not xui_panels %} +
+

Нет добавленных панелей. Подключите 3x-ui на странице 3x-ui.

+
+ {% else %} - - - + - + - {% for c in clients %} + {% for p in xui_panels %} + {% set inbounds = user_inbounds_by_panel.get(p.id, []) %} - - - + - - + - {% else %} - {% endfor %}
ИмяПротоколIPСервер СтатусСозданInbound’ы для пользователей
{{ c.name }}{{ c.server.protocol }}{{ c.address }} - {% if c.is_enabled %} - on + {{ p.name }} +
{{ p.base_url }}
+
+ {% if p.is_reachable %} + online {% else %} - off + offline {% endif %} {{ c.created_at.strftime('%Y-%m-%d %H:%M') if c.created_at else '—' }} - Открыть -
- -
-
- -
+
+ {% if inbounds %} +
+ {% for ib in inbounds %} + + #{{ ib.inbound_id }} {{ ib.remark or ib.protocol }} · {{ ib.protocol }}{% if ib.port %}:{{ ib.port }}{% endif %} + + {% endfor %} +
+ {% else %} + нет включённых — откройте панель и отметьте inbound’ы + {% endif %} +
+ Настроить
Клиентов пока нет
+ {% endif %}
-{% else %} -{# ——— 3x-ui ——— #}
- Новый клиент 3x-ui - только inbound’ы, отмеченные на сервере как доступные пользователям + Новый клиент + только по включённым inbound’ам
- {% if not xui_panels %} -

Сначала добавьте панель на странице 3x-ui.

+ {% if not panels_with_inbounds %} +

Нет серверов с включёнными inbound’ами. Добавьте панель в 3x-ui и отметьте inbound’ы «для пользователей».

{% else %}
-
Клиенты, созданные через сайт (3x-ui)
+
Клиенты через сайт
@@ -191,12 +173,13 @@ {% else %} - + {% endfor %}
Клиентов 3x-ui пока нет
Клиентов пока нет
+{% if panels_with_inbounds %}