From 52c4f83dd11da9e2b5979fa37015b81ac4c780b3 Mon Sep 17 00:00:00 2001 From: machka-pasla Date: Sun, 3 Aug 2025 21:50:44 +0300 Subject: [PATCH] Enhance inline and admin statistics features - Updated inline user statistics message to provide clearer information about bot usage and user status. - Refactored system statistics retrieval to utilize the PanelApiService for improved data accuracy and detail, including memory usage and bandwidth statistics. - Simplified the success message for bulk promo code creation by removing the codes list from the message, ensuring clarity. - Enhanced localization files to reflect changes in success messages and added new labels for better user experience. --- bot/handlers/admin/promo_codes.py | 35 ++++--- bot/handlers/inline_mode.py | 153 +++++++++++++++++++----------- locales/en.json | 4 +- locales/ru.json | 4 +- 4 files changed, 122 insertions(+), 74 deletions(-) diff --git a/bot/handlers/admin/promo_codes.py b/bot/handlers/admin/promo_codes.py index e8e9403..6f2a175 100644 --- a/bot/handlers/admin/promo_codes.py +++ b/bot/handlers/admin/promo_codes.py @@ -1140,15 +1140,13 @@ async def create_bulk_promo_codes_final(callback_or_message, await session.commit() # Send success message - codes_text = " ".join(created_codes) success_text = _( "admin_bulk_promo_created_success", - default="✅ Создано {count} промокодов на {days} дней!\n\nДействуют до: {validity}\nМакс. активации: {max_act}\n\nПромокоды:\n{codes}", + default="✅ Создано {count} промокодов на {days} дней!\n\nДействуют до: {validity}\nМакс. активации: {max_act}", count=quantity, days=bonus_days, validity=valid_until_str_display, - max_act=max_activations, - codes=codes_text[:3500] + "..." if len(codes_text) > 3500 else codes_text + max_act=max_activations ) if hasattr(callback_or_message, 'message'): @@ -1162,22 +1160,21 @@ async def create_bulk_promo_codes_final(callback_or_message, parse_mode="HTML" ) - # Send codes as file if too many - if len(codes_text) > 3500: - codes_file_content = "\n".join(created_codes) - codes_file = types.BufferedInputFile( - codes_file_content.encode('utf-8'), - filename=f"bulk_promo_codes_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" - ) - - await target_message.answer_document( - codes_file, - caption=_( - "admin_bulk_promo_codes_file", - default="📄 Все созданные промокоды в файле", - count=quantity - ) + # Always send codes as file + codes_file_content = "\n".join(created_codes) + codes_file = types.BufferedInputFile( + codes_file_content.encode('utf-8'), + filename=f"bulk_promo_codes_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" + ) + + await target_message.answer_document( + codes_file, + caption=_( + "admin_bulk_promo_codes_file", + default="📄 Все созданные промокоды в файле ({count} шт.)", + count=quantity ) + ) except Exception as e: logging.error(f"Error creating bulk promo codes: {e}") diff --git a/bot/handlers/inline_mode.py b/bot/handlers/inline_mode.py index 2224371..9355b0c 100644 --- a/bot/handlers/inline_mode.py +++ b/bot/handlers/inline_mode.py @@ -152,16 +152,14 @@ async def create_user_stats_result(session: AsyncSession, i18n_instance, lang: s stats_text = _( "inline_user_stats_message", - default="👥 Статистика пользователей\n\n" + default="📊 Статистика Бота\n👥 Пользователи\n\n" "📊 Всего: {total}\n" - "📈 Активных сегодня: {active_today}\n" "💳 С платной подпиской: {paid}\n" "🆓 На пробном периоде: {trial}\n" "😴 Неактивных: {inactive}\n" "🚫 Заблокированных: {banned}\n" - "🎁 По реферальной программе: {referral}", + "🎁 Привлечено по реферальной программе: {referral}", total=user_stats['total_users'], - active_today=user_stats['active_today'], paid=user_stats['paid_subscriptions'], trial=user_stats['trial_users'], inactive=user_stats['inactive_users'], @@ -173,11 +171,11 @@ async def create_user_stats_result(session: AsyncSession, i18n_instance, lang: s id="admin_user_stats", title=_( "inline_admin_user_stats_title", - default="👥 Статистика пользователей" + default="📊 Статистика пользователей" ), description=_( - "inline_stats_description", - default="Всего: {total}, Активных: {active}", + "inline_user_stats_description", + default="Всего: {total}, Платных: {active}", total=user_stats['total_users'], active=user_stats['paid_subscriptions'] ), @@ -240,53 +238,88 @@ async def create_financial_stats_result(session: AsyncSession, i18n_instance, la async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: str, settings: Settings) -> Optional[InlineQueryResultArticle]: - """Create system statistics result with online/offline/expired/limited info""" + """Create panel statistics result with system/nodes/bandwidth info""" _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) try: - from datetime import datetime, timezone - from sqlalchemy import select, func, and_ - from db.models import User, Subscription + from bot.services.panel_api_service import PanelApiService - now = datetime.now(timezone.utc) - - # Count active subscriptions (online) - active_subs_stmt = select(func.count(Subscription.subscription_id)).where( - and_( - Subscription.is_active == True, - Subscription.end_date > now - ) - ) - active_subs = (await session.execute(active_subs_stmt)).scalar() or 0 - - # Count expired subscriptions - expired_subs_stmt = select(func.count(Subscription.subscription_id)).where( - and_( - Subscription.is_active == True, - Subscription.end_date <= now - ) - ) - expired_subs = (await session.execute(expired_subs_stmt)).scalar() or 0 - - # Count total users (approximation for "total") - total_users_stmt = select(func.count(User.user_id)) - total_users = (await session.execute(total_users_stmt)).scalar() or 0 - - # Offline = users without active subscriptions - offline_users = total_users - active_subs - - stats_text = _( - "inline_system_stats_message", - default="🖥 Системная статистика\n\n" - "🟢 Онлайн: {online}\n" - "🔴 Офлайн: {offline}\n" - "⏰ Истекшие: {expired}\n" - "👥 Всего пользователей: {total}", - online=active_subs, - offline=max(0, offline_users), - expired=expired_subs, - total=total_users - ) + # Get panel stats similar to main statistics + async with PanelApiService(settings) as panel_service: + system_stats = await panel_service.get_system_stats() + bandwidth_stats = await panel_service.get_bandwidth_stats() + nodes_stats = await panel_service.get_nodes_statistics() + + if system_stats: + users = system_stats.get('users', {}) + status_counts = users.get('statusCounts', {}) + online_stats = system_stats.get('onlineStats', {}) + + active_users = status_counts.get('ACTIVE', 0) + disabled_users = status_counts.get('DISABLED', 0) + expired_users = status_counts.get('EXPIRED', 0) + limited_users = status_counts.get('LIMITED', 0) + total_users = users.get('totalUsers', 0) + online_now = online_stats.get('onlineNow', 0) + + # Memory usage + memory = system_stats.get('memory', {}) + memory_usage = 0 + if memory: + memory_total = memory.get('total', 1) + memory_used = memory.get('used', 0) + memory_usage = (memory_used / memory_total) * 100 if memory_total > 0 else 0 + + # Bandwidth + week_traffic = "N/A" + month_traffic = "N/A" + if bandwidth_stats: + week_data = bandwidth_stats.get('bandwidthLastSevenDays', {}) + month_data = bandwidth_stats.get('bandwidthLast30Days', {}) or bandwidth_stats.get('bandwidthLastThirtyDays', {}) + + week_traffic = week_data.get('current', 'N/A') if week_data else 'N/A' + month_traffic = month_data.get('current', 'N/A') if month_data else 'N/A' + + # Nodes + active_nodes = 0 + total_nodes = 0 + if nodes_stats and 'lastSevenDays' in nodes_stats: + unique_nodes = set() + for node_data in nodes_stats.get('lastSevenDays', []): + unique_nodes.add(node_data.get('nodeName', '')) + total_nodes = len(unique_nodes) + active_nodes = total_nodes # Assume all are active + elif system_stats and 'nodes' in system_stats: + active_nodes = system_stats.get('nodes', {}).get('totalOnline', 0) + total_nodes = active_nodes + + stats_text = _( + "inline_system_stats_message", + default="🖥 Статистика панели\n\n" + "🟢 Онлайн: {online}\n" + "📊 Активных: {active}\n" + "🔴 Отключенных: {disabled}\n" + "⏰ Истекшие: {expired}\n" + "⚠️ Ограниченные: {limited}\n" + "👥 Всего пользователей: {total}\n" + "💾 Использование RAM: {memory:.1f}%\n" + "📊 Трафик за неделю: {week_traffic}\n" + "📊 Трафик за месяц: {month_traffic}\n" + "🔗 Активных нод: {active_nodes}/{total_nodes}", + online=online_now, + active=active_users, + disabled=disabled_users, + expired=expired_users, + limited=limited_users, + total=total_users, + memory=memory_usage, + week_traffic=week_traffic, + month_traffic=month_traffic, + active_nodes=active_nodes, + total_nodes=total_nodes + ) + else: + stats_text = _("inline_panel_stats_error", default="❌ Ошибка получения данных с панели") return InlineQueryResultArticle( id="admin_system_stats", @@ -296,9 +329,7 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: ), description=_( "inline_system_description", - default="Онлайн: {online}, Офлайн: {offline}", - online=active_subs, - offline=max(0, offline_users) + default="Панель: онлайн, ноды, трафик" ), input_message_content=InputTextMessageContent( message_text=stats_text, @@ -309,6 +340,22 @@ async def create_system_stats_result(session: AsyncSession, i18n_instance, lang: except Exception as e: logging.error(f"Error creating system stats result: {e}") + # Fallback error message + error_text = _("inline_panel_stats_error", default="❌ Ошибка получения данных с панели") + + return InlineQueryResultArticle( + id="admin_system_stats", + title=_( + "inline_admin_system_stats_title", + default="🖥 Системная статистика" + ), + description=_("inline_system_error", default="Ошибка получения данных"), + input_message_content=InputTextMessageContent( + message_text=error_text, + parse_mode="HTML" + ), + thumbnail_url=settings.INLINE_SYSTEM_STATS_THUMBNAIL_URL + ) return None diff --git a/locales/en.json b/locales/en.json index 50d6ab8..48ebd31 100644 --- a/locales/en.json +++ b/locales/en.json @@ -305,7 +305,7 @@ "admin_promo_codes_menu_bulk_create": "📦 Bulk Creation", "admin_bulk_promo_create_prompt": "📦 Bulk Promo Code Creation\n\nEnter data in format:\nquantity|days|description\n\nExample:\n10|7|Weekly promo", "admin_bulk_promo_invalid_format": "❌ Invalid format. Use: quantity|days|description", - "admin_bulk_promo_created_success": "✅ Created {count} promo codes for {days} days!\n\nDescription: {description}\nCodes: {codes}", + "admin_bulk_promo_created_success": "✅ Created {count} promo codes for {days} days!", "admin_logs_export_csv": "📄 Export to CSV", "admin_logs_csv_export_started": "📄 Starting log export to CSV...", @@ -425,6 +425,8 @@ "admin_panel_stats_header": "Panel Statistics", "admin_panel_online_label": "Online", + "admin_panel_active_label": "Active", + "admin_panel_disabled_label": "Disabled", "admin_panel_offline_label": "Offline", "admin_panel_expired_label": "Expired", "admin_panel_limited_label": "Limited", diff --git a/locales/ru.json b/locales/ru.json index 86f83ff..b8fce60 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -305,7 +305,7 @@ "admin_promo_codes_menu_bulk_create": "📦 Массовое создание", "admin_bulk_promo_create_prompt": "📦 Массовое создание промокодов\n\nВведите данные в формате:\nколичество|дни|описание\n\nПример:\n10|7|Промо на неделю", "admin_bulk_promo_invalid_format": "❌ Неверный формат. Используйте: количество|дни|описание", - "admin_bulk_promo_created_success": "✅ Создано {count} промокодов на {days} дней!\n\nОписание: {description}\nПромокоды: {codes}", + "admin_bulk_promo_created_success": "✅ Создано {count} промокодов на {days} дней!", "admin_logs_export_csv": "📄 Экспорт в CSV", "admin_logs_csv_export_started": "📄 Начинаю экспорт логов в CSV...", @@ -425,6 +425,8 @@ "admin_panel_stats_header": "Статистика панели", "admin_panel_online_label": "Онлайн", + "admin_panel_active_label": "Активных", + "admin_panel_disabled_label": "Отключенных", "admin_panel_offline_label": "Офлайн", "admin_panel_expired_label": "Истекшие", "admin_panel_limited_label": "Ограниченные",