Revert "Handle RemnaWave server removal and expose server user list"

This commit is contained in:
Egor
2025-09-28 02:01:57 +03:00
committed by GitHub
parent 90d3f3aef2
commit 83a76c928d
2 changed files with 14 additions and 220 deletions

View File

@@ -1,17 +1,11 @@
import logging
from typing import Iterable, List, Optional, Sequence, Tuple
from sqlalchemy import select, func, update, delete, text
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__)
@@ -234,48 +228,14 @@ async def delete_server_squad(db: AsyncSession, server_id: int) -> bool:
return True
async def get_server_users(
db: AsyncSession,
server_id: int,
) -> List[dict]:
result = await db.execute(
select(User, Subscription, SubscriptionServer)
.join(Subscription, Subscription.user_id == User.id)
.join(
SubscriptionServer,
SubscriptionServer.subscription_id == Subscription.id,
)
.where(SubscriptionServer.server_squad_id == server_id)
.order_by(User.id)
)
users_info: List[dict] = []
for user, subscription, link in result.all():
users_info.append(
{
"user_id": user.id,
"telegram_id": user.telegram_id,
"username": user.username,
"first_name": user.first_name,
"subscription_id": subscription.id,
"subscription_status": subscription.status,
"subscription_end_date": subscription.end_date,
"connected_at": link.connected_at,
}
)
return users_info
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))
@@ -305,64 +265,15 @@ async def sync_with_remnawave(
)
created += 1
missing_servers = [
server for uuid, server in existing_servers.items()
if uuid not in remnawave_uuids
]
if missing_servers:
missing_ids = [server.id for server in missing_servers]
missing_uuids = [server.squad_uuid for server in missing_servers]
connections_count_result = await db.execute(
select(func.count(SubscriptionServer.id)).where(
SubscriptionServer.server_squad_id.in_(missing_ids)
)
)
removed_connections = connections_count_result.scalar() or 0
await db.execute(
delete(SubscriptionServer).where(
SubscriptionServer.server_squad_id.in_(missing_ids)
)
)
if removed_connections:
logger.info(
"🔁 Удалено %s привязок подписок к отсутствующим серверам",
removed_connections,
)
subscriptions_result = await db.execute(
select(Subscription).where(Subscription.connected_squads.isnot(None))
)
for subscription in subscriptions_result.scalars().all():
current_squads = list(subscription.connected_squads or [])
if not current_squads:
continue
updated_squads = [
squad_uuid for squad_uuid in current_squads
if squad_uuid not in missing_uuids
]
if updated_squads != current_squads:
subscription.connected_squads = updated_squads
for server in missing_servers:
logger.info(
"🗑️ Удаляем сервер %s (UUID: %s) — отсутствует в RemnaWave",
server.display_name,
server.squad_uuid,
)
await db.delete(server)
removed += 1
for uuid, server in existing_servers.items():
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
logger.info(f"🔄 Синхронизация завершена: +{created} ~{updated} -{disabled}")
return created, updated, disabled
def _generate_display_name(original_name: str) -> str:

View File

@@ -15,7 +15,6 @@ 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
@@ -82,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 "✅ Включить",
@@ -282,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*")
@@ -292,7 +286,7 @@ async def sync_servers_with_remnawave(
📊 <b>Результаты:</b>
• Создано новых серверов: {created}
• Обновлено существующих: {updated}
Удалено отсутствующих: {removed}
Отключено неактивных: {disabled}
Всего обработано: {len(squads)}
Новые серверы созданы как недоступные.
@@ -1025,119 +1019,9 @@ async def save_server_promo_groups(
reply_markup=keyboard,
parse_mode="HTML",
)
await callback.answer("✅ Промогруппы обновлены!")
@admin_required
@error_handler
async def show_server_users(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
server_id = int(callback.data.split('_')[-1])
server = await get_server_squad_by_id(db, server_id)
if not server:
await callback.answer("❌ Сервер не найден!", show_alert=True)
return
users = await get_server_users(db, server_id)
status_emojis = {
"active": "",
"trial": "🧪",
"expired": "",
"disabled": "",
}
status_labels = {
"active": "АКТИВНА",
"trial": "TRIAL",
"expired": "ИСТЕКЛА",
"disabled": "ОТКЛЮЧЕНА",
}
text_lines = [
"👥 <b>Пользователи сервера</b>",
"",
f"Сервер: {server.display_name}",
f"UUID: <code>{server.squad_uuid}</code>",
"",
]
keyboard_rows = []
max_buttons = 50
if not users:
text_lines.append("На этот сервер пока никто не подключен.")
else:
text_lines.append(f"Всего пользователей: {len(users)}")
text_lines.append("Нажмите на пользователя ниже, чтобы открыть управление.")
text_lines.append("")
preview_limit = 10
for user_info in users[:preview_limit]:
name_parts = []
if user_info.get("username"):
name_parts.append(f"@{user_info['username']}")
if user_info.get("first_name"):
name_parts.append(user_info["first_name"])
display_name = " ".join(name_parts) or str(user_info["telegram_id"])
status = (user_info.get("subscription_status") or "unknown").lower()
status_text = status_labels.get(status, status.upper())
emoji = status_emojis.get(status, "")
text_lines.append(f"{display_name}{emoji} {status_text}")
if len(users) > preview_limit:
remaining = len(users) - preview_limit
text_lines.append(f"… и ещё {remaining} пользователей")
for user_info in users[:max_buttons]:
if user_info.get("username"):
label = f"@{user_info['username']}"
elif user_info.get("first_name"):
label = user_info["first_name"]
else:
label = str(user_info["telegram_id"])
status = (user_info.get("subscription_status") or "unknown").lower()
emoji = status_emojis.get(status, "")
button_text = f"{emoji} {label}"[:64]
keyboard_rows.append(
[
types.InlineKeyboardButton(
text=button_text,
callback_data=f"admin_user_manage_{user_info['user_id']}",
)
]
)
if len(users) > max_buttons:
text_lines.append(
f"⚠️ Показаны первые {max_buttons} пользователей. Используйте фильтры в разделе пользователей для подробностей."
)
keyboard_rows.append(
[
types.InlineKeyboardButton(
text="⬅️ Назад", callback_data=f"admin_server_edit_{server.id}"
)
]
)
await callback.message.edit_text(
"\n".join(text_lines),
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard_rows),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def sync_server_user_counts_handler(
@@ -1221,7 +1105,6 @@ def register_handlers(dp: Dispatcher):
& ~F.data.contains("promo"),
)
dp.callback_query.register(toggle_server_availability, F.data.startswith("admin_server_toggle_"))
dp.callback_query.register(show_server_users, F.data.startswith("admin_server_users_"))
dp.callback_query.register(start_server_edit_name, F.data.startswith("admin_server_edit_name_"))
dp.callback_query.register(start_server_edit_price, F.data.startswith("admin_server_edit_price_"))