diff --git a/app/handlers/admin/remnawave.py b/app/handlers/admin/remnawave.py index 968a56f2..e63c1c80 100644 --- a/app/handlers/admin/remnawave.py +++ b/app/handlers/admin/remnawave.py @@ -2285,9 +2285,6 @@ async def show_sync_options( "• При полной синхронизации подписки пользователей, отсутствующих в панели, будут деактивированы\n" "• Рекомендуется делать полную синхронизацию ежедневно\n" "• Баланс пользователей НЕ удаляется\n\n" - "⬆️ Обратная синхронизация:\n" - "• Отправляет активных пользователей из бота в панель\n" - "• Используйте при сбоях панели или для восстановления данных\n\n" + "\n".join(status_lines) ) @@ -2298,12 +2295,6 @@ async def show_sync_options( callback_data="sync_all_users", ) ], - [ - types.InlineKeyboardButton( - text="⬆️ Синхронизация в панель", - callback_data="sync_to_panel", - ) - ], [ types.InlineKeyboardButton( text="⚙️ Настройки автосинхронизации", @@ -2663,50 +2654,6 @@ async def sync_all_users( ) await callback.answer() - -@admin_required -@error_handler -async def sync_users_to_panel( - callback: types.CallbackQuery, - db_user: User, - db: AsyncSession, -): - await callback.message.edit_text( - "⬆️ Выполняется синхронизация данных бота в панель Remnawave...\n\n" - "Это может занять несколько минут.", - reply_markup=None, - ) - - remnawave_service = RemnaWaveService() - stats = await remnawave_service.sync_users_to_panel(db) - - if stats["errors"] == 0: - status_emoji = "✅" - status_text = "успешно завершена" - else: - status_emoji = "⚠️" if (stats["created"] + stats["updated"]) > 0 else "❌" - status_text = "завершена с предупреждениями" if status_emoji == "⚠️" else "завершена с ошибками" - - text = ( - f"{status_emoji} Синхронизация в панель {status_text}\n\n" - "📊 Результаты:\n" - f"• 🆕 Создано: {stats['created']}\n" - f"• 🔄 Обновлено: {stats['updated']}\n" - f"• ❌ Ошибок: {stats['errors']}" - ) - - keyboard = [ - [types.InlineKeyboardButton(text="🔄 Повторить", callback_data="sync_to_panel")], - [types.InlineKeyboardButton(text="🔄 Полная синхронизация", callback_data="sync_all_users")], - [types.InlineKeyboardButton(text="⬅️ К синхронизации", callback_data="admin_rw_sync")], - ] - - await callback.message.edit_text( - text, - reply_markup=types.InlineKeyboardMarkup(inline_keyboard=keyboard), - ) - await callback.answer() - @admin_required @error_handler async def show_sync_recommendations( @@ -3179,7 +3126,6 @@ def register_handlers(dp: Dispatcher): dp.callback_query.register(cancel_auto_sync_schedule, F.data == "remnawave_auto_sync_cancel") dp.callback_query.register(run_auto_sync_now, F.data == "remnawave_auto_sync_run") dp.callback_query.register(sync_all_users, F.data == "sync_all_users") - dp.callback_query.register(sync_users_to_panel, F.data == "sync_to_panel") dp.callback_query.register(show_squad_migration_menu, F.data == "admin_rw_migration") dp.callback_query.register(paginate_migration_source, F.data.startswith("admin_migration_source_page_")) dp.callback_query.register(handle_migration_source_selection, F.data.startswith("admin_migration_source_")) diff --git a/app/keyboards/admin.py b/app/keyboards/admin.py index 964984fb..6272a531 100644 --- a/app/keyboards/admin.py +++ b/app/keyboards/admin.py @@ -1118,12 +1118,6 @@ def get_sync_options_keyboard(language: str = "ru") -> InlineKeyboardMarkup: callback_data="sync_all_users" ) ], - [ - InlineKeyboardButton( - text=_t(texts, "ADMIN_SYNC_TO_PANEL", "⬆️ Синхронизация в панель"), - callback_data="sync_to_panel" - ) - ], [ InlineKeyboardButton( text=_t(texts, "ADMIN_SYNC_ONLY_NEW", "🆕 Только новые"), diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json index 3d3aef2c..3519200a 100644 --- a/app/localization/locales/ru.json +++ b/app/localization/locales/ru.json @@ -688,7 +688,6 @@ "ADMIN_SUPPORT_SUBMENU_TITLE": "🛟 **Поддержка**\n\n", "ADMIN_SUPPORT_TICKETS": "🎫 Тикеты поддержки", "ADMIN_SYNC_BACK": "⬅️ К синхронизации", - "ADMIN_SYNC_TO_PANEL": "⬆️ Синхронизация в панель", "ADMIN_SYNC_CLEANUP": "🧹 Очистка", "ADMIN_SYNC_CONFIRM": "✅ Подтвердить", "ADMIN_SYNC_FULL": "🔄 Полная синхронизация", diff --git a/app/localization/locales/ua.json b/app/localization/locales/ua.json index d291f56a..f0b24625 100644 --- a/app/localization/locales/ua.json +++ b/app/localization/locales/ua.json @@ -687,7 +687,6 @@ "ADMIN_SUPPORT_SUBMENU_TITLE": "🛟 **Підтримка**\n\n", "ADMIN_SUPPORT_TICKETS": "🎫 Тікети підтримки", "ADMIN_SYNC_BACK": "⬅️ До синхронізації", - "ADMIN_SYNC_TO_PANEL": "⬆️ Синхронізація в панель", "ADMIN_SYNC_CLEANUP": "🧹 Очищення", "ADMIN_SYNC_CONFIRM": "✅ Підтвердити", "ADMIN_SYNC_FULL": "🔄 Повна синхронізація", diff --git a/app/services/remnawave_service.py b/app/services/remnawave_service.py index c3851c6a..e2cb7db5 100644 --- a/app/services/remnawave_service.py +++ b/app/services/remnawave_service.py @@ -1615,99 +1615,77 @@ class RemnaWaveService: async def sync_users_to_panel(self, db: AsyncSession) -> Dict[str, int]: try: stats = {"created": 0, "updated": 0, "errors": 0} - - batch_size = 100 - offset = 0 - + + users = await get_users_list(db, offset=0, limit=10000) + async with self.get_api_client() as api: - while True: - users = await get_users_list(db, offset=offset, limit=batch_size) - - if not users: - break - - for user in users: - if not user.subscription: - continue - - try: - subscription = user.subscription - hwid_limit = resolve_hwid_device_limit_for_payload(subscription) - - if user.remnawave_uuid: - update_kwargs = dict( - uuid=user.remnawave_uuid, - status=UserStatus.ACTIVE if subscription.is_active else UserStatus.EXPIRED, - expire_at=subscription.end_date, - traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, - traffic_limit_strategy=TrafficLimitStrategy.MONTH, - description=settings.format_remnawave_user_description( - full_name=user.full_name, - username=user.username, - telegram_id=user.telegram_id - ), - active_internal_squads=subscription.connected_squads, - ) - - if hwid_limit is not None: - update_kwargs['hwid_device_limit'] = hwid_limit - - await api.update_user(**update_kwargs) - stats["updated"] += 1 - else: - username = settings.format_remnawave_username( - full_name=user.full_name, - username=user.username, - telegram_id=user.telegram_id, - ) - - create_kwargs = dict( - username=username, - expire_at=subscription.end_date, - status=UserStatus.ACTIVE if subscription.is_active else UserStatus.EXPIRED, - traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, - traffic_limit_strategy=TrafficLimitStrategy.MONTH, - telegram_id=user.telegram_id, - description=settings.format_remnawave_user_description( - full_name=user.full_name, - username=user.username, - telegram_id=user.telegram_id - ), - active_internal_squads=subscription.connected_squads, - ) - - if hwid_limit is not None: - create_kwargs['hwid_device_limit'] = hwid_limit - - new_user = await api.create_user(**create_kwargs) - - user.remnawave_uuid = new_user.uuid - subscription.remnawave_short_uuid = new_user.short_uuid - - stats["created"] += 1 - - except Exception as e: - logger.error(f"Ошибка синхронизации пользователя {user.telegram_id} в панель: {e}") - stats["errors"] += 1 + for user in users: + if not user.subscription: + continue try: - await db.commit() - except Exception as commit_error: - logger.error( - "Ошибка фиксации транзакции при синхронизации в панель: %s", - commit_error, - ) - await db.rollback() - stats["errors"] += len(users) + subscription = user.subscription + hwid_limit = resolve_hwid_device_limit_for_payload(subscription) - if len(users) < batch_size: - break + if user.remnawave_uuid: + update_kwargs = dict( + uuid=user.remnawave_uuid, + status=UserStatus.ACTIVE if subscription.is_active else UserStatus.EXPIRED, + expire_at=subscription.end_date, + traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, + traffic_limit_strategy=TrafficLimitStrategy.MONTH, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), + active_internal_squads=subscription.connected_squads, + ) - offset += batch_size + if hwid_limit is not None: + update_kwargs['hwid_device_limit'] = hwid_limit - logger.info( - f"✅ Синхронизация в панель завершена: создано {stats['created']}, обновлено {stats['updated']}, ошибок {stats['errors']}" - ) + await api.update_user(**update_kwargs) + stats["updated"] += 1 + else: + username = settings.format_remnawave_username( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id, + ) + + create_kwargs = dict( + username=username, + expire_at=subscription.end_date, + status=UserStatus.ACTIVE if subscription.is_active else UserStatus.EXPIRED, + traffic_limit_bytes=subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0, + traffic_limit_strategy=TrafficLimitStrategy.MONTH, + telegram_id=user.telegram_id, + description=settings.format_remnawave_user_description( + full_name=user.full_name, + username=user.username, + telegram_id=user.telegram_id + ), + active_internal_squads=subscription.connected_squads, + ) + + if hwid_limit is not None: + create_kwargs['hwid_device_limit'] = hwid_limit + + new_user = await api.create_user(**create_kwargs) + + await update_user(db, user, remnawave_uuid=new_user.uuid) + subscription.remnawave_short_uuid = new_user.short_uuid + # Убираем немедленный коммит для пакетной обработки + # await db.commit() + + stats["created"] += 1 + + except Exception as e: + logger.error(f"Ошибка синхронизации пользователя {user.telegram_id} в панель: {e}") + stats["errors"] += 1 + + logger.info(f"✅ Синхронизация в панель завершена: создано {stats['created']}, обновлено {stats['updated']}, ошибок {stats['errors']}") return stats except Exception as e: