mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-26 14:21:25 +00:00
Revert "Resolve duplicate handlers and cleanup imports"
This commit is contained in:
@@ -571,7 +571,8 @@ class Subscription(Base):
|
||||
return 0.0
|
||||
|
||||
def extend_subscription(self, days: int):
|
||||
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
if self.end_date > datetime.utcnow():
|
||||
self.end_date = self.end_date + timedelta(days=days)
|
||||
else:
|
||||
|
||||
1
app/external/remnawave_api.py
vendored
1
app/external/remnawave_api.py
vendored
@@ -4,6 +4,7 @@ import ssl
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Union, Any
|
||||
from urllib.parse import urlparse
|
||||
import aiohttp
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -128,6 +128,7 @@ async def show_support_submenu(
|
||||
# Moderators have access only to tickets and not to settings
|
||||
is_moderator_only = (not settings.is_admin(callback.from_user.id) and SupportSettingsService.is_moderator(callback.from_user.id))
|
||||
|
||||
from app.keyboards.admin import get_admin_support_submenu_keyboard
|
||||
kb = get_admin_support_submenu_keyboard(db_user.language)
|
||||
if is_moderator_only:
|
||||
# Rebuild keyboard to include only tickets and back to main menu
|
||||
|
||||
@@ -4,7 +4,6 @@ import html
|
||||
import contextlib
|
||||
from aiogram import Dispatcher, types, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database.models import User
|
||||
@@ -153,6 +152,24 @@ async def toggle_sla(callback: types.CallbackQuery, db_user: User, db: AsyncSess
|
||||
await show_support_settings(callback, db_user, db)
|
||||
|
||||
|
||||
from app.states import SupportSettingsStates
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def start_set_sla_minutes(callback: types.CallbackQuery, db_user: User, db: AsyncSession, state: FSMContext):
|
||||
await callback.message.edit_text(
|
||||
"⏳ <b>Настройка SLA</b>\n\nВведите количество минут ожидания ответа (целое число > 0):",
|
||||
parse_mode="HTML",
|
||||
reply_markup=types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_support_settings")]]
|
||||
)
|
||||
)
|
||||
await state.set_state(SupportSettingsStates.waiting_for_desc) # temporary reuse replaced below
|
||||
# we'll manage separate state below
|
||||
|
||||
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
class SupportAdvancedStates(StatesGroup):
|
||||
waiting_for_sla_minutes = State()
|
||||
waiting_for_moderator_id = State()
|
||||
|
||||
@@ -3,6 +3,7 @@ from aiogram import Dispatcher, types, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.database.models import User
|
||||
from app.states import AdminStates
|
||||
from app.keyboards.admin import get_welcome_text_keyboard, get_admin_main_keyboard
|
||||
@@ -43,16 +44,16 @@ async def show_welcome_text_panel(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
status_emoji = "🟢" if welcome_settings['is_enabled'] else "🔴"
|
||||
status_text = "включено" if welcome_settings['is_enabled'] else "отключено"
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
status_emoji = "🟢" if settings['is_enabled'] else "🔴"
|
||||
status_text = "включено" if settings['is_enabled'] else "отключено"
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"👋 Управление приветственным текстом\n\n"
|
||||
f"{status_emoji} <b>Статус:</b> {status_text}\n\n"
|
||||
f"Здесь вы можете управлять текстом, который показывается новым пользователям после регистрации.\n\n"
|
||||
f"💡 Доступные плейсхолдеры для автозамены:",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
@@ -88,11 +89,11 @@ async def show_current_welcome_text(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
current_text = welcome_settings['text']
|
||||
is_enabled = welcome_settings['is_enabled']
|
||||
|
||||
if not welcome_settings['id']:
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
current_text = settings['text']
|
||||
is_enabled = settings['is_enabled']
|
||||
|
||||
if not settings['id']:
|
||||
status = "📝 Используется стандартный текст:"
|
||||
else:
|
||||
status = "📝 Текущий приветственный текст:"
|
||||
@@ -120,7 +121,7 @@ async def show_placeholders_help(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
placeholders = get_available_placeholders()
|
||||
placeholders_text = "\n".join([f"• <code>{key}</code>\n {desc}" for key, desc in placeholders.items()])
|
||||
|
||||
@@ -136,7 +137,7 @@ async def show_placeholders_help(
|
||||
|
||||
await callback.message.edit_text(
|
||||
help_text,
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
@@ -148,12 +149,12 @@ async def show_formatting_help(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
formatting_info = get_telegram_formatting_info()
|
||||
|
||||
await callback.message.edit_text(
|
||||
formatting_info,
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
await callback.answer()
|
||||
@@ -166,8 +167,8 @@ async def start_edit_welcome_text(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
current_text = welcome_settings['text']
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
current_text = settings['text']
|
||||
|
||||
placeholders = get_available_placeholders()
|
||||
placeholders_text = "\n".join([f"• <code>{key}</code> - {desc}" for key, desc in placeholders.items()])
|
||||
@@ -205,9 +206,9 @@ async def process_welcome_text_edit(
|
||||
success = await set_welcome_text(db, new_text, db_user.id)
|
||||
|
||||
if success:
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
status_emoji = "🟢" if welcome_settings['is_enabled'] else "🔴"
|
||||
status_text = "включено" if welcome_settings['is_enabled'] else "отключено"
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
status_emoji = "🟢" if settings['is_enabled'] else "🔴"
|
||||
status_text = "включено" if settings['is_enabled'] else "отключено"
|
||||
|
||||
placeholders = get_available_placeholders()
|
||||
placeholders_text = "\n".join([f"• <code>{key}</code>" for key in placeholders.keys()])
|
||||
@@ -218,14 +219,14 @@ async def process_welcome_text_edit(
|
||||
f"Новый текст:\n"
|
||||
f"<code>{new_text}</code>\n\n"
|
||||
f"💡 Будут заменяться плейсхолдеры: {placeholders_text}",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
await message.answer(
|
||||
"❌ Ошибка при сохранении текста. Попробуйте еще раз.",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled'])
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled'])
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
@@ -241,9 +242,9 @@ async def reset_welcome_text(
|
||||
success = await set_welcome_text(db, default_text, db_user.id)
|
||||
|
||||
if success:
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
status_emoji = "🟢" if welcome_settings['is_enabled'] else "🔴"
|
||||
status_text = "включено" if welcome_settings['is_enabled'] else "отключено"
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
status_emoji = "🟢" if settings['is_enabled'] else "🔴"
|
||||
status_text = "включено" if settings['is_enabled'] else "отключено"
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"✅ Приветственный текст сброшен на стандартный!\n\n"
|
||||
@@ -251,14 +252,14 @@ async def reset_welcome_text(
|
||||
f"Стандартный текст:\n"
|
||||
f"<code>{default_text}</code>\n\n"
|
||||
f"💡 Плейсхолдер <code>{{user_name}}</code> будет заменяться на имя пользователя",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
await callback.message.edit_text(
|
||||
"❌ Ошибка при сбросе текста. Попробуйте еще раз.",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled'])
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled'])
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
@@ -280,14 +281,14 @@ async def show_preview_welcome_text(
|
||||
test_user = TestUser()
|
||||
preview_text = await get_welcome_text_for_user(db, test_user)
|
||||
|
||||
welcome_settings = await get_current_welcome_text_settings(db)
|
||||
settings = await get_current_welcome_text_settings(db)
|
||||
|
||||
if preview_text:
|
||||
await callback.message.edit_text(
|
||||
f"👁️ Предварительный просмотр\n\n"
|
||||
f"Как будет выглядеть текст для пользователя 'Иван' (@test_user):\n\n"
|
||||
f"<code>{preview_text}</code>",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
else:
|
||||
@@ -295,7 +296,7 @@ async def show_preview_welcome_text(
|
||||
f"👁️ Предварительный просмотр\n\n"
|
||||
f"🔴 Приветственные сообщения отключены.\n"
|
||||
f"Новые пользователи не будут получать приветственный текст после регистрации.",
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, welcome_settings['is_enabled']),
|
||||
reply_markup=get_welcome_text_keyboard(db_user.language, settings['is_enabled']),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from app.database.crud.user import get_user_by_telegram_id, update_user
|
||||
from app.keyboards.inline import get_main_menu_keyboard, get_language_selection_keyboard
|
||||
from app.localization.texts import get_texts, get_rules
|
||||
from app.database.models import User
|
||||
from app.utils.user_utils import mark_user_as_had_paid_subscription
|
||||
from app.database.crud.user_message import get_random_active_message
|
||||
from app.services.subscription_checkout_service import (
|
||||
has_subscription_checkout_draft,
|
||||
@@ -64,6 +65,17 @@ async def show_main_menu(
|
||||
await callback.answer()
|
||||
|
||||
|
||||
async def mark_user_as_had_paid_subscription(
|
||||
db: AsyncSession,
|
||||
user: User
|
||||
) -> None:
|
||||
if not user.has_had_paid_subscription:
|
||||
user.has_had_paid_subscription = True
|
||||
user.updated_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
logger.info(f"🎯 Пользователь {user.telegram_id} отмечен как имевший платную подписку")
|
||||
|
||||
|
||||
async def show_service_rules(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
|
||||
@@ -146,6 +146,12 @@ async def _prepare_subscription_summary(
|
||||
data: Dict[str, Any],
|
||||
texts,
|
||||
) -> Tuple[str, Dict[str, Any]]:
|
||||
from app.utils.pricing_utils import (
|
||||
calculate_months_from_days,
|
||||
format_period_description,
|
||||
validate_pricing_calculation,
|
||||
apply_percentage_discount,
|
||||
)
|
||||
|
||||
summary_data = dict(data)
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
@@ -1122,6 +1128,7 @@ async def return_to_saved_cart(
|
||||
)
|
||||
return
|
||||
|
||||
from app.utils.pricing_utils import calculate_months_from_days, format_period_description
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
selected_countries_names = []
|
||||
@@ -1326,6 +1333,7 @@ async def apply_countries_changes(
|
||||
db: AsyncSession,
|
||||
state: FSMContext
|
||||
):
|
||||
from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
|
||||
|
||||
logger.info(f"🔧 Применение изменений стран")
|
||||
|
||||
@@ -1626,6 +1634,7 @@ async def confirm_change_devices(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
|
||||
|
||||
new_devices_count = int(callback.data.split('_')[2])
|
||||
texts = get_texts(db_user.language)
|
||||
@@ -1739,6 +1748,7 @@ async def execute_change_devices(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
|
||||
|
||||
callback_parts = callback.data.split('_')
|
||||
new_devices_count = int(callback_parts[3])
|
||||
@@ -1869,6 +1879,7 @@ async def show_devices_page(
|
||||
page: int = 1
|
||||
):
|
||||
|
||||
from app.utils.pagination import paginate_list
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
devices_per_page = 5
|
||||
@@ -1967,6 +1978,7 @@ async def handle_single_device_reset(
|
||||
if response and 'response' in response:
|
||||
devices_list = response['response'].get('devices', [])
|
||||
|
||||
from app.utils.pagination import paginate_list
|
||||
devices_per_page = 5
|
||||
pagination = paginate_list(devices_list, page=page, per_page=devices_per_page)
|
||||
|
||||
@@ -2272,6 +2284,7 @@ async def confirm_add_devices(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
|
||||
|
||||
devices_count = int(callback.data.split('_')[2])
|
||||
texts = get_texts(db_user.language)
|
||||
@@ -2407,6 +2420,11 @@ async def confirm_extend_subscription(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import (
|
||||
calculate_months_from_days,
|
||||
validate_pricing_calculation,
|
||||
apply_percentage_discount,
|
||||
)
|
||||
from app.services.admin_notification_service import AdminNotificationService
|
||||
|
||||
days = int(callback.data.split('_')[2])
|
||||
@@ -3016,6 +3034,7 @@ async def select_country(
|
||||
return
|
||||
|
||||
period_base_price = PERIOD_PRICES[data['period_days']]
|
||||
from app.utils.pricing_utils import apply_percentage_discount
|
||||
|
||||
discounted_base_price, _ = apply_percentage_discount(
|
||||
period_base_price,
|
||||
@@ -3151,6 +3170,7 @@ async def confirm_purchase(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import calculate_months_from_days, validate_pricing_calculation
|
||||
from app.services.admin_notification_service import AdminNotificationService
|
||||
|
||||
data = await state.get_data()
|
||||
@@ -3870,6 +3890,7 @@ async def create_paid_subscription_with_traffic_mode(
|
||||
traffic_gb: Optional[int] = None
|
||||
):
|
||||
from app.config import settings
|
||||
from app.database.crud.subscription import create_paid_subscription
|
||||
|
||||
if traffic_gb is None:
|
||||
if settings.is_traffic_fixed():
|
||||
@@ -5330,6 +5351,7 @@ async def confirm_switch_traffic(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import get_remaining_months, calculate_prorated_price
|
||||
|
||||
new_traffic_gb = int(callback.data.split('_')[2])
|
||||
texts = get_texts(db_user.language)
|
||||
@@ -5443,6 +5465,7 @@ async def execute_switch_traffic(
|
||||
db_user: User,
|
||||
db: AsyncSession
|
||||
):
|
||||
from app.utils.pricing_utils import get_remaining_months
|
||||
|
||||
callback_parts = callback.data.split('_')
|
||||
new_traffic_gb = int(callback_parts[3])
|
||||
@@ -5527,6 +5550,7 @@ def get_traffic_switch_keyboard(
|
||||
subscription_end_date: datetime = None,
|
||||
discount_percent: int = 0,
|
||||
) -> InlineKeyboardMarkup:
|
||||
from app.utils.pricing_utils import get_remaining_months
|
||||
from app.config import settings
|
||||
|
||||
months_multiplier = 1
|
||||
|
||||
@@ -412,6 +412,7 @@ async def show_my_tickets(
|
||||
# Добавим кнопку перехода к закрытым
|
||||
keyboard.inline_keyboard.insert(0, [types.InlineKeyboardButton(text=texts.t("VIEW_CLOSED_TICKETS", "🟢 Закрытые тикеты"), callback_data="my_tickets_closed")])
|
||||
# Всегда используем фото-рендер с логотипом (утилита сама сделает фоллбек при необходимости)
|
||||
from app.utils.photo_message import edit_or_answer_photo
|
||||
await edit_or_answer_photo(
|
||||
callback=callback,
|
||||
caption=texts.t("MY_TICKETS_TITLE", "📋 Ваши тикеты:"),
|
||||
@@ -455,6 +456,7 @@ async def show_my_tickets_closed(
|
||||
data = [{'id': t.id, 'title': t.title, 'status_emoji': t.status_emoji} for t in tickets]
|
||||
kb = get_my_tickets_keyboard(data, current_page=current_page, total_pages=total_pages, language=db_user.language, page_prefix="my_tickets_closed_page_")
|
||||
kb.inline_keyboard.insert(0, [types.InlineKeyboardButton(text=texts.t("BACK_TO_OPEN_TICKETS", "🔴 Открытые тикеты"), callback_data="my_tickets")])
|
||||
from app.utils.photo_message import edit_or_answer_photo
|
||||
await edit_or_answer_photo(
|
||||
callback=callback,
|
||||
caption=texts.t("CLOSED_TICKETS_TITLE", "🟢 Закрытые тикеты:"),
|
||||
|
||||
@@ -701,7 +701,7 @@ class AdminNotificationService:
|
||||
if not payment_method:
|
||||
return '💰 С баланса'
|
||||
|
||||
return method_names.get(payment_method, '💰 С баланса')
|
||||
return method_names.get(payment_method, f'💰 С баланса')
|
||||
|
||||
def _format_traffic(self, traffic_gb: int) -> str:
|
||||
if traffic_gb == 0:
|
||||
@@ -748,32 +748,40 @@ class AdminNotificationService:
|
||||
if details.get("auto_enabled", False):
|
||||
icon = "⚠️"
|
||||
title = "АВТОМАТИЧЕСКОЕ ВКЛЮЧЕНИЕ ТЕХРАБОТ"
|
||||
alert_type = "warning"
|
||||
else:
|
||||
icon = "🔧"
|
||||
title = "ВКЛЮЧЕНИЕ ТЕХРАБОТ"
|
||||
alert_type = "info"
|
||||
|
||||
elif event_type == "disable":
|
||||
icon = "✅"
|
||||
title = "ОТКЛЮЧЕНИЕ ТЕХРАБОТ"
|
||||
alert_type = "success"
|
||||
|
||||
elif event_type == "api_status":
|
||||
if status == "online":
|
||||
icon = "🟢"
|
||||
title = "API REMNAWAVE ВОССТАНОВЛЕНО"
|
||||
alert_type = "success"
|
||||
else:
|
||||
icon = "🔴"
|
||||
title = "API REMNAWAVE НЕДОСТУПНО"
|
||||
alert_type = "error"
|
||||
|
||||
elif event_type == "monitoring":
|
||||
if status == "started":
|
||||
icon = "🔍"
|
||||
title = "МОНИТОРИНГ ЗАПУЩЕН"
|
||||
alert_type = "info"
|
||||
else:
|
||||
icon = "⏹️"
|
||||
title = "МОНИТОРИНГ ОСТАНОВЛЕН"
|
||||
alert_type = "info"
|
||||
else:
|
||||
icon = "ℹ️"
|
||||
title = "СИСТЕМА ТЕХРАБОТ"
|
||||
alert_type = "info"
|
||||
|
||||
message_parts = [f"{icon} <b>{title}</b>", ""]
|
||||
|
||||
@@ -963,6 +971,103 @@ class AdminNotificationService:
|
||||
logger.error(f"Ошибка отправки уведомления о статусе панели Remnawave: {e}")
|
||||
return False
|
||||
|
||||
async def send_remnawave_panel_status_notification(
|
||||
self,
|
||||
status: str,
|
||||
details: Dict[str, Any] = None
|
||||
) -> bool:
|
||||
if not self._is_enabled():
|
||||
return False
|
||||
|
||||
try:
|
||||
details = details or {}
|
||||
|
||||
status_config = {
|
||||
"online": {"icon": "🟢", "title": "ПАНЕЛЬ REMNAWAVE ДОСТУПНА", "alert_type": "success"},
|
||||
"offline": {"icon": "🔴", "title": "ПАНЕЛЬ REMNAWAVE НЕДОСТУПНА", "alert_type": "error"},
|
||||
"degraded": {"icon": "🟡", "title": "ПАНЕЛЬ REMNAWAVE РАБОТАЕТ СО СБОЯМИ", "alert_type": "warning"},
|
||||
"maintenance": {"icon": "🔧", "title": "ПАНЕЛЬ REMNAWAVE НА ОБСЛУЖИВАНИИ", "alert_type": "info"}
|
||||
}
|
||||
|
||||
config = status_config.get(status, status_config["offline"])
|
||||
|
||||
message_parts = [
|
||||
f"{config['icon']} <b>{config['title']}</b>",
|
||||
""
|
||||
]
|
||||
|
||||
if details.get("api_url"):
|
||||
message_parts.append(f"🔗 <b>URL:</b> {details['api_url']}")
|
||||
|
||||
if details.get("response_time"):
|
||||
message_parts.append(f"⚡ <b>Время отклика:</b> {details['response_time']} сек")
|
||||
|
||||
if details.get("last_check"):
|
||||
last_check = details["last_check"]
|
||||
if isinstance(last_check, str):
|
||||
from datetime import datetime
|
||||
last_check = datetime.fromisoformat(last_check)
|
||||
message_parts.append(f"🕐 <b>Последняя проверка:</b> {last_check.strftime('%H:%M:%S')}")
|
||||
|
||||
if status == "online":
|
||||
if details.get("uptime"):
|
||||
message_parts.append(f"⏱️ <b>Время работы:</b> {details['uptime']}")
|
||||
|
||||
if details.get("users_online"):
|
||||
message_parts.append(f"👥 <b>Пользователей онлайн:</b> {details['users_online']}")
|
||||
|
||||
message_parts.append("")
|
||||
message_parts.append("✅ Все системы работают нормально.")
|
||||
|
||||
elif status == "offline":
|
||||
if details.get("error"):
|
||||
error_msg = str(details["error"])[:150]
|
||||
message_parts.append(f"❌ <b>Ошибка:</b> {error_msg}")
|
||||
|
||||
if details.get("consecutive_failures"):
|
||||
message_parts.append(f"🔄 <b>Неудачных попыток:</b> {details['consecutive_failures']}")
|
||||
|
||||
message_parts.append("")
|
||||
message_parts.append("⚠️ Панель недоступна. Проверьте соединение и статус сервера.")
|
||||
|
||||
elif status == "degraded":
|
||||
if details.get("issues"):
|
||||
issues = details["issues"]
|
||||
if isinstance(issues, list):
|
||||
message_parts.append("⚠️ <b>Обнаруженные проблемы:</b>")
|
||||
for issue in issues[:3]:
|
||||
message_parts.append(f" • {issue}")
|
||||
else:
|
||||
message_parts.append(f"⚠️ <b>Проблема:</b> {issues}")
|
||||
|
||||
message_parts.append("")
|
||||
message_parts.append("Панель работает, но возможны задержки или сбои.")
|
||||
|
||||
elif status == "maintenance":
|
||||
if details.get("maintenance_reason"):
|
||||
message_parts.append(f"🔧 <b>Причина:</b> {details['maintenance_reason']}")
|
||||
|
||||
if details.get("estimated_duration"):
|
||||
message_parts.append(f"⏰ <b>Ожидаемая длительность:</b> {details['estimated_duration']}")
|
||||
|
||||
if details.get("manual_message"):
|
||||
message_parts.append(f"💬 <b>Сообщение:</b> {details['manual_message']}")
|
||||
|
||||
message_parts.append("")
|
||||
message_parts.append("Панель временно недоступна для обслуживания.")
|
||||
|
||||
from datetime import datetime
|
||||
message_parts.append("")
|
||||
message_parts.append(f"⏰ <i>{datetime.now().strftime('%d.%m.%Y %H:%M:%S')}</i>")
|
||||
|
||||
message = "\n".join(message_parts)
|
||||
|
||||
return await self._send_message(message)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка отправки уведомления о статусе панели Remnawave: {e}")
|
||||
return False
|
||||
|
||||
async def send_subscription_update_notification(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import json
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
@@ -987,6 +987,15 @@ class RemnaWaveService:
|
||||
logger.error(f"Error removing users from squad: {e}")
|
||||
return False
|
||||
|
||||
async def delete_squad(self, squad_uuid: str) -> bool:
|
||||
try:
|
||||
async with self.get_api_client() as api:
|
||||
response = await api.delete_internal_squad(squad_uuid)
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting squad: {e}")
|
||||
return False
|
||||
|
||||
async def get_all_inbounds(self) -> List[Dict]:
|
||||
try:
|
||||
async with self.get_api_client() as api:
|
||||
@@ -1021,6 +1030,15 @@ class RemnaWaveService:
|
||||
logger.error(f"Error renaming squad: {e}")
|
||||
return False
|
||||
|
||||
async def create_squad(self, name: str, inbound_uuids: List[str]) -> bool:
|
||||
try:
|
||||
async with self.get_api_client() as api:
|
||||
squad = await api.create_internal_squad(name, inbound_uuids)
|
||||
return squad is not None
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating squad: {e}")
|
||||
return False
|
||||
|
||||
async def get_node_user_usage_by_range(self, node_uuid: str, start_date, end_date) -> List[Dict[str, Any]]:
|
||||
try:
|
||||
async with self.get_api_client() as api:
|
||||
|
||||
@@ -189,7 +189,7 @@ async def get_detailed_referral_list(db: AsyncSession, user_id: int, limit: int
|
||||
and_(
|
||||
Transaction.user_id == referral.id,
|
||||
Transaction.type == TransactionType.DEPOSIT.value,
|
||||
Transaction.is_completed.is_(True)
|
||||
Transaction.is_completed == True
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user