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: