Files
remnawave-bedolaga-telegram…/app/handlers/server_status.py
T
Fringg eb18994b7d fix: complete datetime.utcnow() → datetime.now(UTC) migration
- Migrate 660+ datetime.utcnow() across 153 files to datetime.now(UTC)
- Migrate 30+ datetime.now() without UTC to datetime.now(UTC)
- Convert all 170 DateTime columns to DateTime(timezone=True)
- Add migrate_datetime_to_timestamptz() in universal_migration with SET LOCAL timezone='UTC' safety
- Remove 70+ .replace(tzinfo=None) workarounds
- Fix utcfromtimestamp → fromtimestamp(..., tz=UTC)
- Fix fromtimestamp() without tz= (system_logs, backup_service, referral_diagnostics)
- Fix fromisoformat/isoparse to ensure aware output (platega, yookassa, wata, miniapp, nalogo)
- Fix strptime() to add .replace(tzinfo=UTC) (backup_service, referral_diagnostics)
- Fix datetime.combine() to include tzinfo=UTC (remnawave_sync, traffic_monitoring)
- Fix datetime.max/datetime.min sentinels with .replace(tzinfo=UTC)
- Rename panel_datetime_to_naive_utc → panel_datetime_to_utc
- Remove DTZ003 from ruff ignore list
2026-02-17 04:45:40 +03:00

210 lines
6.7 KiB
Python

from datetime import UTC, datetime
import structlog
from aiogram import Dispatcher, F, types
from app.config import settings
from app.database.models import User
from app.keyboards.inline import get_server_status_keyboard
from app.localization.texts import get_texts
from app.services.server_status_service import (
ServerStatusEntry,
ServerStatusError,
ServerStatusService,
)
logger = structlog.get_logger(__name__)
_status_service = ServerStatusService()
async def show_server_status(callback: types.CallbackQuery, db_user: User) -> None:
await _render_server_status(callback, db_user, page=1)
async def change_server_status_page(callback: types.CallbackQuery, db_user: User) -> None:
try:
_, page_str = callback.data.split(':', 1)
page = int(page_str)
except (ValueError, AttributeError, IndexError):
page = 1
await _render_server_status(callback, db_user, page=page)
async def _render_server_status(
callback: types.CallbackQuery,
db_user: User,
page: int = 1,
) -> None:
texts = get_texts(db_user.language)
if settings.get_server_status_mode() != 'xray':
await callback.answer(texts.t('SERVER_STATUS_NOT_CONFIGURED', 'Функция недоступна.'), show_alert=True)
return
try:
servers = await _status_service.get_servers()
except ServerStatusError as error:
logger.warning('Server status error', error=error)
await callback.answer(
texts.t('SERVER_STATUS_ERROR_SHORT', 'Не удалось получить данные'),
show_alert=True,
)
return
except Exception as error: # pragma: no cover - defensive logging
logger.error('Unexpected server status error', error=error)
await callback.answer(
texts.t('SERVER_STATUS_ERROR_SHORT', 'Не удалось получить данные'),
show_alert=True,
)
return
message, total_pages, current_page = _build_status_message(servers, texts, page)
keyboard = get_server_status_keyboard(db_user.language, current_page, total_pages)
await callback.message.edit_text(
message,
reply_markup=keyboard,
disable_web_page_preview=True,
)
await callback.answer()
def _build_status_message(
servers: list[ServerStatusEntry],
texts,
page: int,
) -> tuple[str, int, int]:
total_servers = len(servers)
online_servers = [server for server in servers if server.is_online]
offline_servers = [server for server in servers if not server.is_online]
items_per_page = settings.get_server_status_items_per_page()
pages = _split_into_pages(online_servers, offline_servers, items_per_page)
total_pages = max(1, len(pages))
current_index = min(max(page - 1, 0), total_pages - 1)
current_online, current_offline = pages[current_index] if pages else ([], [])
lines: list[str] = [texts.t('SERVER_STATUS_TITLE', '📊 <b>Статус серверов</b>')]
if total_servers == 0:
lines.append('')
lines.append(texts.t('SERVER_STATUS_NO_SERVERS', 'Нет данных о серверах.'))
message = '\n'.join(lines).strip()
return message, 1, 1
summary = texts.t(
'SERVER_STATUS_SUMMARY',
'Всего серверов: {total} (в сети: {online}, вне сети: {offline})',
).format(
total=total_servers,
online=len(online_servers),
offline=len(offline_servers),
)
updated_at = datetime.now(UTC).strftime('%H:%M:%S')
lines.extend(
[
'',
summary,
texts.t('SERVER_STATUS_UPDATED_AT', '⏱ Обновлено: {time}').format(time=updated_at),
'',
]
)
if current_online:
lines.append(texts.t('SERVER_STATUS_AVAILABLE', '✅ <b>Доступны</b>'))
lines.extend(_format_server_lines(current_online, texts, online=True))
lines.append('')
if current_offline:
lines.append(texts.t('SERVER_STATUS_UNAVAILABLE', '❌ <b>Недоступны</b>'))
lines.extend(_format_server_lines(current_offline, texts, online=False))
lines.append('')
if total_pages > 1:
lines.append(
texts.t('SERVER_STATUS_PAGINATION', 'Страница {current} из {total}').format(
current=current_index + 1,
total=total_pages,
)
)
message = '\n'.join(line for line in lines if line is not None)
message = message.strip()
return message, total_pages, current_index + 1
def _split_into_pages(
online: list[ServerStatusEntry],
offline: list[ServerStatusEntry],
items_per_page: int,
) -> list[tuple[list[ServerStatusEntry], list[ServerStatusEntry]]]:
if not online and not offline:
return [([], [])]
pages: list[tuple[list[ServerStatusEntry], list[ServerStatusEntry]]] = []
online_index = 0
offline_index = 0
while online_index < len(online) or offline_index < len(offline):
current_online: list[ServerStatusEntry] = []
current_offline: list[ServerStatusEntry] = []
remaining = max(1, items_per_page)
while remaining > 0 and online_index < len(online):
current_online.append(online[online_index])
online_index += 1
remaining -= 1
while remaining > 0 and offline_index < len(offline):
current_offline.append(offline[offline_index])
offline_index += 1
remaining -= 1
pages.append((current_online, current_offline))
return pages if pages else [([], [])]
def _format_server_lines(
servers: list[ServerStatusEntry],
texts,
*,
online: bool,
) -> list[str]:
lines: list[str] = []
for server in servers:
latency_text: str
if online:
if server.latency_ms and server.latency_ms > 0:
latency_text = texts.t('SERVER_STATUS_LATENCY', '{latency} мс').format(latency=server.latency_ms)
else:
latency_text = texts.t('SERVER_STATUS_LATENCY_UNKNOWN', 'нет данных')
else:
latency_text = texts.t('SERVER_STATUS_OFFLINE', 'нет ответа')
name = server.display_name or server.name
flag_prefix = f'{server.flag} ' if server.flag else ''
server_line = f'{flag_prefix}{name}{latency_text}'
lines.append(f'<blockquote>{server_line}</blockquote>')
return lines
def register_handlers(dp: Dispatcher) -> None:
dp.callback_query.register(
show_server_status,
F.data == 'menu_server_status',
)
dp.callback_query.register(
change_server_status_page,
F.data.startswith('server_status_page:'),
)