diff --git a/app/database/crud/server_squad.py b/app/database/crud/server_squad.py
index ce1201d5..85f1dc58 100644
--- a/app/database/crud/server_squad.py
+++ b/app/database/crud/server_squad.py
@@ -1,18 +1,11 @@
import logging
-from datetime import datetime
from typing import Iterable, List, Optional, Sequence, Tuple
from sqlalchemy import select, and_, func, update, delete, text
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
-from app.database.models import (
- PromoGroup,
- ServerSquad,
- SubscriptionServer,
- Subscription,
- User,
-)
+from app.database.models import PromoGroup, ServerSquad, SubscriptionServer, Subscription
logger = logging.getLogger(__name__)
@@ -239,10 +232,10 @@ async def sync_with_remnawave(
db: AsyncSession,
remnawave_squads: List[dict]
) -> Tuple[int, int, int]:
-
+
created = 0
updated = 0
- removed = 0
+ disabled = 0
existing_servers = {}
result = await db.execute(select(ServerSquad))
@@ -273,139 +266,14 @@ async def sync_with_remnawave(
created += 1
for uuid, server in existing_servers.items():
- if uuid in remnawave_uuids:
- continue
-
- subscription_ids_result = await db.execute(
- select(SubscriptionServer.subscription_id)
- .where(SubscriptionServer.server_squad_id == server.id)
- )
- subscription_ids = [row[0] for row in subscription_ids_result.fetchall()]
-
- if subscription_ids:
- subscriptions_result = await db.execute(
- select(Subscription)
- .where(Subscription.id.in_(subscription_ids))
- )
-
- for subscription in subscriptions_result.scalars().all():
- if not subscription.connected_squads:
- continue
-
- updated_squads = [
- squad for squad in subscription.connected_squads
- if squad != server.squad_uuid
- ]
-
- if len(updated_squads) != len(subscription.connected_squads):
- subscription.connected_squads = updated_squads
- subscription.updated_at = datetime.utcnow()
-
- await db.execute(
- delete(SubscriptionServer)
- .where(SubscriptionServer.server_squad_id == server.id)
- )
-
- await db.delete(server)
- removed += 1
-
+ if uuid not in remnawave_uuids and server.is_available:
+ server.is_available = False
+ disabled += 1
+
await db.commit()
-
- logger.info(f"🔄 Синхронизация завершена: +{created} ~{updated} 🗑️{removed}")
- return created, updated, removed
-
-
-async def get_server_users(
- db: AsyncSession,
- server_id: int,
- *,
- page: int = 1,
- limit: int = 10,
-) -> Tuple[List[dict], int]:
-
- count_stmt = (
- select(func.count())
- .select_from(
- select(Subscription.user_id)
- .distinct()
- .join(SubscriptionServer, SubscriptionServer.subscription_id == Subscription.id)
- .where(SubscriptionServer.server_squad_id == server_id)
- .subquery()
- )
- )
- total_result = await db.execute(count_stmt)
- total_users = total_result.scalar() or 0
-
- if total_users == 0:
- return [], 0
-
- offset = max(0, (page - 1) * limit)
-
- user_ids_stmt = (
- select(Subscription.user_id)
- .distinct()
- .join(SubscriptionServer, SubscriptionServer.subscription_id == Subscription.id)
- .where(SubscriptionServer.server_squad_id == server_id)
- .order_by(Subscription.user_id)
- .offset(offset)
- .limit(limit)
- )
- user_ids_result = await db.execute(user_ids_stmt)
- user_ids = [row[0] for row in user_ids_result.fetchall()]
-
- if not user_ids:
- return [], total_users
-
- data_stmt = (
- select(SubscriptionServer, Subscription, User)
- .join(Subscription, Subscription.id == SubscriptionServer.subscription_id)
- .join(User, User.id == Subscription.user_id)
- .where(SubscriptionServer.server_squad_id == server_id)
- .where(User.id.in_(user_ids))
- )
- data_result = await db.execute(data_stmt)
-
- def _status_priority(status: Optional[str]) -> int:
- order = {
- "active": 4,
- "trial": 3,
- "paused": 2,
- "expired": 1,
- }
- return order.get(status or "", 0)
-
- user_map: dict[int, dict] = {}
-
- for sub_server, subscription, user in data_result.fetchall():
- priority = _status_priority(subscription.status)
- existing = user_map.get(user.id)
-
- if not existing or priority > existing["priority"]:
- user_map[user.id] = {
- "user_id": user.id,
- "telegram_id": user.telegram_id,
- "telegram_username": user.telegram_username,
- "full_name": user.full_name,
- "subscription_id": subscription.id,
- "subscription_status": subscription.status,
- "connected_at": sub_server.connected_at,
- "priority": priority,
- }
- elif priority == existing["priority"] and sub_server.connected_at and (
- not existing.get("connected_at")
- or sub_server.connected_at > existing["connected_at"]
- ):
- existing.update({
- "subscription_id": subscription.id,
- "subscription_status": subscription.status,
- "connected_at": sub_server.connected_at,
- })
-
- users = [user_map[user_id] for user_id in user_ids if user_id in user_map]
- for user in users:
- user.pop("priority", None)
-
- return users, total_users
+
+ logger.info(f"🔄 Синхронизация завершена: +{created} ~{updated} -{disabled}")
+ return created, updated, disabled
def _generate_display_name(original_name: str) -> str:
diff --git a/app/handlers/admin/servers.py b/app/handlers/admin/servers.py
index 687c6826..9edc3af0 100644
--- a/app/handlers/admin/servers.py
+++ b/app/handlers/admin/servers.py
@@ -1,6 +1,4 @@
import logging
-from html import escape
-
from aiogram import Dispatcher, types, F
from aiogram.fsm.context import FSMContext
from sqlalchemy.ext.asyncio import AsyncSession
@@ -17,13 +15,11 @@ from app.database.crud.server_squad import (
create_server_squad,
get_available_server_squads,
update_server_squad_promo_groups,
- get_server_users,
)
from app.database.crud.promo_group import get_promo_groups_with_counts
from app.services.remnawave_service import RemnaWaveService
from app.utils.decorators import admin_required, error_handler
from app.utils.cache import cache
-from app.utils.formatters import format_datetime
logger = logging.getLogger(__name__)
@@ -85,11 +81,6 @@ def _build_server_edit_view(server):
text="📝 Описание", callback_data=f"admin_server_edit_desc_{server.id}"
),
],
- [
- types.InlineKeyboardButton(
- text="👥 Пользователи", callback_data=f"admin_server_users_{server.id}"
- )
- ],
[
types.InlineKeyboardButton(
text="❌ Отключить" if server.is_available else "✅ Включить",
@@ -285,7 +276,7 @@ async def sync_servers_with_remnawave(
)
return
- created, updated, removed = await sync_with_remnawave(db, squads)
+ created, updated, disabled = await sync_with_remnawave(db, squads)
await cache.delete_pattern("available_countries*")
@@ -295,7 +286,7 @@ async def sync_servers_with_remnawave(
📊 Результаты:
• Создано новых серверов: {created}
• Обновлено существующих: {updated}
-• Удалено отсутствующих: {removed}
+• Отключено неактивных: {disabled}
• Всего обработано: {len(squads)}
ℹ️ Новые серверы созданы как недоступные.
@@ -323,126 +314,7 @@ async def sync_servers_with_remnawave(
[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_servers")]
])
)
-
- await callback.answer()
-
-
-@admin_required
-@error_handler
-async def show_server_users(
- callback: types.CallbackQuery,
- db_user: User,
- db: AsyncSession,
-):
-
- parts = callback.data.split('_')
- if len(parts) < 4:
- await callback.answer("❌ Некорректные данные", show_alert=True)
- return
-
- try:
- server_id = int(parts[3])
- except ValueError:
- await callback.answer("❌ Некорректный идентификатор сервера", show_alert=True)
- return
-
- page = 1
- if len(parts) >= 6 and parts[4] == "page":
- try:
- page = max(1, int(parts[5]))
- except ValueError:
- page = 1
-
- server = await get_server_squad_by_id(db, server_id)
- if not server:
- await callback.answer("❌ Сервер не найден", show_alert=True)
- return
-
- limit = 10
- users, total_count = await get_server_users(db, server_id, page=page, limit=limit)
- total_pages = max(1, (total_count + limit - 1) // limit)
-
- if total_count > 0 and page > total_pages:
- page = total_pages
- users, _ = await get_server_users(db, server_id, page=page, limit=limit)
-
- text = "👥 Пользователи сервера"
-
- text += f"\nСервер: {escape(server.display_name)}"
- text += f"\nВсего пользователей: {total_count}"
- text += f"\nСтраница: {page}/{total_pages}\n"
-
- if not users:
- text += "\n❌ Пользователи не найдены."
- else:
- start_index = 1 + (page - 1) * limit
- for idx, user in enumerate(users, start=start_index):
- full_name = (
- user.get("full_name")
- or user.get("telegram_username")
- or str(user.get("telegram_id"))
- )
- user_link = f"{escape(full_name)}"
- text += f"\n{idx}. 👤 {user_link}"
- status = user.get("subscription_status") or "неизвестно"
- text += f"\n Статус подписки: {escape(status)}"
- if user.get("connected_at"):
- text += f"\n Подключен: {format_datetime(user['connected_at'])}"
- text += "\n"
-
- keyboard: list[list[types.InlineKeyboardButton]] = []
-
- for user in users:
- full_name = (
- user.get("full_name")
- or user.get("telegram_username")
- or str(user.get("telegram_id"))
- )
- short_name = full_name.replace("<", "‹").replace(">", "›")[:32]
- keyboard.append([
- types.InlineKeyboardButton(
- text=f"👤 {short_name}",
- callback_data=f"admin_user_manage_{user['user_id']}"
- )
- ])
-
- if total_pages > 1:
- nav_row = []
- if page > 1:
- nav_row.append(
- types.InlineKeyboardButton(
- text="⬅️", callback_data=f"admin_server_users_{server_id}_page_{page-1}"
- )
- )
-
- nav_row.append(
- types.InlineKeyboardButton(
- text=f"{page}/{total_pages}", callback_data="current_page"
- )
- )
-
- if page < total_pages:
- nav_row.append(
- types.InlineKeyboardButton(
- text="➡️", callback_data=f"admin_server_users_{server_id}_page_{page+1}"
- )
- )
-
- keyboard.append(nav_row)
-
- keyboard.append([
- types.InlineKeyboardButton(text="⬅️ К серверу", callback_data=f"admin_server_edit_{server_id}")
- ])
- keyboard.append([
- types.InlineKeyboardButton(text="⬅️ Список серверов", callback_data="admin_servers_list")
- ])
-
- await callback.message.edit_text(
- text,
- reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard),
- parse_mode="HTML",
- disable_web_page_preview=True,
- )
+
await callback.answer()
@@ -1221,7 +1093,6 @@ def register_handlers(dp: Dispatcher):
dp.callback_query.register(sync_servers_with_remnawave, F.data == "admin_servers_sync")
dp.callback_query.register(sync_server_user_counts_handler, F.data == "admin_servers_sync_counts")
dp.callback_query.register(show_server_detailed_stats, F.data == "admin_servers_stats")
- dp.callback_query.register(show_server_users, F.data.startswith("admin_server_users_"))
dp.callback_query.register(
show_server_edit_menu,