Enhance promo code management and add bulk creation functionality

- Refactored the promo code creation process into a step-by-step flow, improving user guidance and validation.
- Introduced bulk promo code creation with a structured multi-step process, allowing admins to create multiple codes efficiently.
- Added new states for managing the promo code creation process and bulk operations.
- Implemented panel statistics retrieval to provide admins with insights into system performance and user activity.
- Updated localization files to support new messages and prompts related to promo code management and statistics.
This commit is contained in:
machka-pasla
2025-08-03 18:20:47 +03:00
parent 0f26c9bdba
commit 4f6822f8e4
6 changed files with 1022 additions and 88 deletions
File diff suppressed because it is too large Load Diff
+44
View File
@@ -8,6 +8,7 @@ from config.settings import Settings
from db.dal import user_dal, payment_dal, panel_sync_dal from db.dal import user_dal, payment_dal, panel_sync_dal
from db.models import Payment, PanelSyncStatus from db.models import Payment, PanelSyncStatus
from bot.services.panel_api_service import PanelApiService
from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard
from bot.middlewares.i18n import JsonI18n from bot.middlewares.i18n import JsonI18n
@@ -137,6 +138,49 @@ async def show_statistics_handler(callback: types.CallbackQuery,
else: else:
stats_text_parts.append(f"\n{_('admin_sync_status_never_run')}") stats_text_parts.append(f"\n{_('admin_sync_status_never_run')}")
# Panel Statistics
try:
panel_service = PanelApiService(settings)
# Get panel system statistics
panel_stats = await panel_service.get_panel_statistics()
nodes_stats = await panel_service.get_nodes_statistics()
online_count = await panel_service.get_online_users_count()
users_activity = await panel_service.get_users_activity_stats()
stats_text_parts.append(f"\n<b>🖥 {_('admin_panel_stats_header', default='Статистика панели')}</b>")
if online_count is not None:
stats_text_parts.append(f"🟢 Онлайн сейчас: <b>{online_count}</b>")
if users_activity:
today_connected = users_activity.get('today_connected', 'N/A')
week_connected = users_activity.get('week_connected', 'N/A')
never_connected = users_activity.get('never_connected', 'N/A')
stats_text_parts.append(f"📅 Подключались сегодня: <b>{today_connected}</b>")
stats_text_parts.append(f"📅 Подключались за неделю: <b>{week_connected}</b>")
stats_text_parts.append(f"❌ Никогда не подключались: <b>{never_connected}</b>")
if nodes_stats:
active_nodes = len([node for node in nodes_stats if node.get('status') == 'active'])
total_nodes = len(nodes_stats)
stats_text_parts.append(f"🔗 Активных нод: <b>{active_nodes}/{total_nodes}</b>")
if panel_stats:
system_info = panel_stats.get('system', {})
if system_info:
memory_usage = system_info.get('memory_usage_percent', 'N/A')
cpu_usage = system_info.get('cpu_usage_percent', 'N/A')
stats_text_parts.append(f"💾 Использование RAM: <b>{memory_usage}%</b>")
stats_text_parts.append(f"🔄 Загрузка CPU: <b>{cpu_usage}%</b>")
await panel_service.close()
except Exception as e:
logging.warning(f"Failed to fetch panel statistics: {e}")
stats_text_parts.append(f"\n<b>🖥 Статистика панели</b>")
stats_text_parts.append("❌ Не удалось получить данные с панели")
final_text = "\n".join(stats_text_parts) final_text = "\n".join(stats_text_parts)
try: try:
+40
View File
@@ -460,3 +460,43 @@ class PanelApiService:
async def get_bot_db_last_sync_status( async def get_bot_db_last_sync_status(
self, session: AsyncSession) -> Optional[PanelSyncStatus]: self, session: AsyncSession) -> Optional[PanelSyncStatus]:
return await panel_sync_dal.get_panel_sync_status(session) return await panel_sync_dal.get_panel_sync_status(session)
async def get_panel_statistics(self) -> Optional[Dict[str, Any]]:
"""Get general panel statistics"""
response_data = await self._request("GET", "/admin/stats", log_full_response=False)
if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response")
return None
async def get_nodes_statistics(self) -> Optional[List[Dict[str, Any]]]:
"""Get nodes statistics"""
response_data = await self._request("GET", "/admin/nodes/stats", log_full_response=False)
if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response", {}).get("nodes", [])
return None
async def get_system_info(self) -> Optional[Dict[str, Any]]:
"""Get system information"""
response_data = await self._request("GET", "/admin/system/info", log_full_response=False)
if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response")
return None
async def get_online_users_count(self) -> Optional[int]:
"""Get count of currently online users"""
response_data = await self._request("GET", "/admin/stats/online-users", log_full_response=False)
if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response", {}).get("count", 0)
return None
async def get_users_activity_stats(self) -> Optional[Dict[str, Any]]:
"""Get users activity statistics (today, week, never connected)"""
response_data = await self._request("GET", "/admin/stats/users-activity", log_full_response=False)
if response_data and not response_data.get("error") and "response" in response_data:
return response_data.get("response")
return None
+9
View File
@@ -6,8 +6,17 @@ class AdminStates(StatesGroup):
waiting_for_broadcast_message = State() waiting_for_broadcast_message = State()
confirming_broadcast = State() confirming_broadcast = State()
waiting_for_promo_details = State() waiting_for_promo_details = State()
waiting_for_promo_code = State()
waiting_for_promo_bonus_days = State()
waiting_for_promo_max_activations = State()
waiting_for_promo_validity_days = State()
waiting_for_promo_description = State()
waiting_for_promo_edit_details = State() waiting_for_promo_edit_details = State()
waiting_for_bulk_promo_details = State() waiting_for_bulk_promo_details = State()
waiting_for_bulk_promo_quantity = State()
waiting_for_bulk_promo_bonus_days = State()
waiting_for_bulk_promo_max_activations = State()
waiting_for_bulk_promo_validity_days = State()
waiting_for_user_id_to_ban = State() waiting_for_user_id_to_ban = State()
waiting_for_user_id_to_unban = State() waiting_for_user_id_to_unban = State()
+38 -1
View File
@@ -323,5 +323,42 @@
"admin_csv_header_raw_update_preview": "Update Preview", "admin_csv_header_raw_update_preview": "Update Preview",
"admin_banned_users_empty": "📋 Banned Users\n\nList is empty", "admin_banned_users_empty": "📋 Banned Users\n\nList is empty",
"admin_banned_users_list": "📋 Banned Users ({count}):\n\n{users}" "admin_banned_users_list": "📋 Banned Users ({count}):\n\n{users}",
"admin_panel_stats_header": "Panel Statistics",
"admin_bulk_promo_invalid_values": "❌ Invalid values: {error}",
"admin_bulk_promo_step1_quantity": "📦 <b>Bulk Promo Code Creation</b>\n\n<b>Step 1 of 4:</b> Quantity\n\nEnter the number of promo codes to create (1-1000):",
"admin_bulk_promo_step2_bonus_days": "📦 <b>Bulk Promo Code Creation</b>\n\n<b>Step 2 of 4:</b> Bonus Days\n\nQuantity: <b>{quantity}</b>\n\nEnter the number of bonus days (1-365):",
"admin_bulk_promo_step3_max_activations": "📦 <b>Bulk Promo Code Creation</b>\n\n<b>Step 3 of 4:</b> Max Activations\n\nQuantity: <b>{quantity}</b>\nBonus days: <b>{bonus_days}</b>\n\nEnter the maximum number of activations for each promo code (1-10000):",
"admin_bulk_promo_step4_validity": "📦 <b>Bulk Promo Code Creation</b>\n\n<b>Step 4 of 4:</b> Validity Period\n\nQuantity: <b>{quantity}</b>\nBonus days: <b>{bonus_days}</b>\nMax activations: <b>{max_activations}</b>\n\nChoose the validity period for promo codes:",
"admin_bulk_promo_invalid_quantity": "❌ Quantity must be between 1 and 1000",
"admin_bulk_promo_invalid_bonus_days": "❌ Bonus days must be between 1 and 365",
"admin_bulk_promo_invalid_max_activations": "❌ Max activations must be between 1 and 10000",
"admin_bulk_promo_invalid_validity_days": "❌ Validity days must be between 1 and 365",
"admin_bulk_promo_unlimited_validity": "♾️ Unlimited",
"admin_bulk_promo_set_validity": "⏰ Set Period",
"admin_bulk_promo_enter_validity_days": "⏰ Enter the number of validity days for promo codes (1-365):",
"admin_bulk_promo_creating": "⏳ Creating {quantity} promo codes...",
"admin_promo_step1_code": "🎟 <b>Create Promo Code</b>\n\n<b>Step 1 of 5:</b> Promo Code\n\nEnter promo code (3-30 characters, letters and numbers only):",
"admin_promo_step2_bonus_days": "🎟 <b>Create Promo Code</b>\n\n<b>Step 2 of 5:</b> Bonus Days\n\nCode: <b>{code}</b>\n\nEnter the number of bonus days (1-365):",
"admin_promo_step3_max_activations": "🎟 <b>Create Promo Code</b>\n\n<b>Step 3 of 5:</b> Max Activations\n\nCode: <b>{code}</b>\nBonus days: <b>{bonus_days}</b>\n\nEnter the maximum number of activations (1-10000):",
"admin_promo_step4_validity": "🎟 <b>Create Promo Code</b>\n\n<b>Step 4 of 5:</b> Validity Period\n\nCode: <b>{code}</b>\nBonus days: <b>{bonus_days}</b>\nMax activations: <b>{max_activations}</b>\n\nChoose the validity period for the promo code:",
"admin_promo_step5_description": "🎟 <b>Create Promo Code</b>\n\n<b>Step 5 of 5:</b> Description (optional)\n\nCode: <b>{code}</b>\nBonus days: <b>{bonus_days}</b>\nMax activations: <b>{max_activations}</b>\nValid until: <b>{validity}</b>\n\nEnter promo code description or click \"Skip\":",
"admin_promo_invalid_code_format": "❌ Promo code must contain 3-30 characters (letters and numbers only)",
"admin_promo_code_already_exists": "❌ A promo code with this code already exists",
"admin_promo_processing_error": "❌ Error processing promo code",
"admin_promo_unlimited_validity": "♾️ Unlimited",
"admin_promo_set_validity": "⏰ Set Period",
"admin_promo_enter_validity_days": "⏰ Enter the number of validity days for the promo code (1-365):",
"admin_promo_skip_description": "⏭️ Skip",
"admin_promo_description_too_long": "❌ Description must not exceed 200 characters",
"admin_promo_creating": "⏳ Creating promo code...",
"admin_promo_created_success": "✅ Promo code created!\n\n🎟 Code: <b>{code}</b>\n🎁 Bonus days: <b>{days}</b>\n🔢 Max activations: <b>{max_act}</b>\n⏰ Valid until: <b>{validity}</b>\n📝 Description: {description}",
"admin_promo_creation_failed": "❌ Error creating promo code: {error}"
} }
+38 -1
View File
@@ -323,5 +323,42 @@
"admin_csv_header_raw_update_preview": "Превью обновления", "admin_csv_header_raw_update_preview": "Превью обновления",
"admin_banned_users_empty": "📋 Заблокированные пользователи\n\nСписок пуст", "admin_banned_users_empty": "📋 Заблокированные пользователи\n\nСписок пуст",
"admin_banned_users_list": "📋 Заблокированные пользователи ({count}):\n\n{users}" "admin_banned_users_list": "📋 Заблокированные пользователи ({count}):\n\n{users}",
"admin_panel_stats_header": "Статистика панели",
"admin_bulk_promo_invalid_values": "❌ Неверные значения: {error}",
"admin_bulk_promo_step1_quantity": "📦 <b>Массовое создание промокодов</b>\n\n<b>Шаг 1 из 4:</b> Количество промокодов\n\nВведите количество промокодов для создания (1-1000):",
"admin_bulk_promo_step2_bonus_days": "📦 <b>Массовое создание промокодов</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКоличество: <b>{quantity}</b>\n\nВведите количество бонусных дней (1-365):",
"admin_bulk_promo_step3_max_activations": "📦 <b>Массовое создание промокодов</b>\n\n<b>Шаг 3 из 4:</b> Максимальные активации\n\nКоличество: <b>{quantity}</b>\nБонусные дни: <b>{bonus_days}</b>\n\nВведите максимальное количество активаций для каждого промокода (1-10000):",
"admin_bulk_promo_step4_validity": "📦 <b>Массовое создание промокодов</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКоличество: <b>{quantity}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активации: <b>{max_activations}</b>\n\nВыберите срок действия промокодов:",
"admin_bulk_promo_invalid_quantity": "❌ Количество должно быть от 1 до 1000",
"admin_bulk_promo_invalid_bonus_days": "❌ Бонусные дни должны быть от 1 до 365",
"admin_bulk_promo_invalid_max_activations": "❌ Максимальные активации должны быть от 1 до 10000",
"admin_bulk_promo_invalid_validity_days": "❌ Дни действия должны быть от 1 до 365",
"admin_bulk_promo_unlimited_validity": "♾️ Неограниченно",
"admin_bulk_promo_set_validity": "⏰ Установить срок",
"admin_bulk_promo_enter_validity_days": "⏰ Введите количество дней действия промокодов (1-365):",
"admin_bulk_promo_creating": "⏳ Создаю {quantity} промокодов...",
"admin_promo_step1_code": "🎟 <b>Создание промокода</b>\n\n<b>Шаг 1 из 5:</b> Код промокода\n\nВведите код промокода (3-30 символов, только буквы и цифры):",
"admin_promo_step2_bonus_days": "🎟 <b>Создание промокода</b>\n\n<b>Шаг 2 из 5:</b> Бонусные дни\n\nКод: <b>{code}</b>\n\nВведите количество бонусных дней (1-365):",
"admin_promo_step3_max_activations": "🎟 <b>Создание промокода</b>\n\n<b>Шаг 3 из 5:</b> Максимальные активации\n\nКод: <b>{code}</b>\nБонусные дни: <b>{bonus_days}</b>\n\nВведите максимальное количество активаций (1-10000):",
"admin_promo_step4_validity": "🎟 <b>Создание промокода</b>\n\n<b>Шаг 4 из 5:</b> Срок действия\n\nКод: <b>{code}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активации: <b>{max_activations}</b>\n\nВыберите срок действия промокода:",
"admin_promo_step5_description": "🎟 <b>Создание промокода</b>\n\n<b>Шаг 5 из 5:</b> Описание (опционально)\n\nКод: <b>{code}</b>\nБонусные дни: <b>{bonus_days}</b>\nМакс. активации: <b>{max_activations}</b>\nДействует: <b>{validity}</b>\n\nВведите описание промокода или нажмите \"Пропустить\":",
"admin_promo_invalid_code_format": "❌ Код промокода должен содержать 3-30 символов (только буквы и цифры)",
"admin_promo_code_already_exists": "❌ Промокод с таким кодом уже существует",
"admin_promo_processing_error": "❌ Ошибка обработки промокода",
"admin_promo_unlimited_validity": "♾️ Неограниченно",
"admin_promo_set_validity": "⏰ Установить срок",
"admin_promo_enter_validity_days": "⏰ Введите количество дней действия промокода (1-365):",
"admin_promo_skip_description": "⏭️ Пропустить",
"admin_promo_description_too_long": "❌ Описание не должно превышать 200 символов",
"admin_promo_creating": "⏳ Создаю промокод...",
"admin_promo_created_success": "✅ Промокод создан!\n\n🎟 Код: <b>{code}</b>\n🎁 Бонусные дни: <b>{days}</b>\n🔢 Максимальные активации: <b>{max_act}</b>\n⏰ Действует до: <b>{validity}</b>\n📝 Описание: {description}",
"admin_promo_creation_failed": "❌ Ошибка создания промокода: {error}"
} }