diff --git a/app/handlers/admin/bot_configuration.py b/app/handlers/admin/bot_configuration.py
index 32533e3f..76120f61 100644
--- a/app/handlers/admin/bot_configuration.py
+++ b/app/handlers/admin/bot_configuration.py
@@ -1,6 +1,5 @@
import math
-from dataclasses import dataclass
-from typing import Iterable, List, Tuple
+from typing import Tuple
from aiogram import Dispatcher, F, types
from aiogram.fsm.context import FSMContext
@@ -13,95 +12,10 @@ from app.states import BotConfigStates
from app.utils.decorators import admin_required, error_handler
+CATEGORY_PAGE_SIZE = 10
SETTINGS_PAGE_SIZE = 8
-@dataclass(frozen=True)
-class _CategoryPresentation:
- key: str
- label: str
- icon: str
-
-
-@dataclass(frozen=True)
-class _CategoryGroup:
- title: str
- categories: Tuple[_CategoryPresentation, ...]
-
-
-_CATEGORY_GROUPS: Tuple[_CategoryGroup, ...] = (
- _CategoryGroup(
- "⚙️ Основные настройки",
- (
- _CategoryPresentation("REMNAWAVE", "Основные параметры", "⚙️"),
- _CategoryPresentation("DEFAULT", "Значения по умолчанию", "🧭"),
- _CategoryPresentation("VERSION", "Версии и обновления", "🆕"),
- _CategoryPresentation("MAINTENANCE", "Техработы", "🧹"),
- _CategoryPresentation("DEBUG", "Отладка", "🐞"),
- _CategoryPresentation("LOG", "Логи", "📄"),
- ),
- ),
- _CategoryGroup(
- "🛠️ Инфраструктура",
- (
- _CategoryPresentation("DATABASE", "База данных", "🗄️"),
- _CategoryPresentation("POSTGRES", "PostgreSQL", "🐘"),
- _CategoryPresentation("SQLITE", "SQLite", "🧱"),
- _CategoryPresentation("REDIS", "Redis", "🧠"),
- _CategoryPresentation("SERVER", "Серверы", "🖥️"),
- _CategoryPresentation("MONITORING", "Мониторинг", "📡"),
- _CategoryPresentation("BACKUP", "Резервные копии", "💾"),
- _CategoryPresentation("WEBHOOK", "Вебхуки", "🪝"),
- ),
- ),
- _CategoryGroup(
- "💳 Оплаты и продления",
- (
- _CategoryPresentation("PAYMENT", "Оплаты", "💳"),
- _CategoryPresentation("YOOKASSA", "YooKassa", "🇷🇺"),
- _CategoryPresentation("CRYPTOBOT", "CryptoBot", "🪙"),
- _CategoryPresentation("MULENPAY", "MulenPay", "💠"),
- _CategoryPresentation("PAL24", "PayPalych", "💼"),
- _CategoryPresentation("AUTOPAY", "Автопродление", "🔁"),
- ),
- ),
- _CategoryGroup(
- "🧪 Продукт и тарифы",
- (
- _CategoryPresentation("TRIAL", "Триал и лимиты", "🧪"),
- _CategoryPresentation("PRICE", "Цены", "💰"),
- _CategoryPresentation("TRAFFIC", "Трафик", "🚦"),
- _CategoryPresentation("REFERRAL", "Реферальная программа", "🤝"),
- _CategoryPresentation("TRIBUTE", "Tribute", "🎖️"),
- _CategoryPresentation("HAPP", "Happ", "🎯"),
- _CategoryPresentation("CONNECT", "Кнопка подключения", "🔌"),
- ),
- ),
- _CategoryGroup(
- "💬 Коммуникации и поддержка",
- (
- _CategoryPresentation("CHANNEL", "Каналы", "📣"),
- _CategoryPresentation("SUPPORT", "Поддержка", "🆘"),
- _CategoryPresentation("ADMIN", "Администрирование", "🛡️"),
- _CategoryPresentation("TELEGRAM", "Telegram Stars", "⭐"),
- ),
- ),
-)
-
-
-def _chunked(iterable: Iterable[types.InlineKeyboardButton], size: int) -> List[List[types.InlineKeyboardButton]]:
- chunk: List[types.InlineKeyboardButton] = []
- rows: List[List[types.InlineKeyboardButton]] = []
- for button in iterable:
- chunk.append(button)
- if len(chunk) == size:
- rows.append(chunk)
- chunk = []
- if chunk:
- rows.append(chunk)
- return rows
-
-
def _parse_category_payload(payload: str) -> Tuple[str, int]:
parts = payload.split(":")
if len(parts) == 3:
@@ -118,74 +32,47 @@ def _parse_category_payload(payload: str) -> Tuple[str, int]:
def _build_categories_keyboard(language: str, page: int = 1) -> types.InlineKeyboardMarkup:
categories = bot_configuration_service.get_categories()
- catalog: dict[str, tuple[str, str, int]] = {}
- for category_key, label, count in categories:
- catalog[category_key.upper()] = (category_key, label, count)
+ total_pages = max(1, math.ceil(len(categories) / CATEGORY_PAGE_SIZE))
+ page = max(1, min(page, total_pages))
+
+ start = (page - 1) * CATEGORY_PAGE_SIZE
+ end = start + CATEGORY_PAGE_SIZE
+ sliced = categories[start:end]
- processed: set[str] = set()
rows: list[list[types.InlineKeyboardButton]] = []
-
- for group in _CATEGORY_GROUPS:
- group_buttons: list[types.InlineKeyboardButton] = []
- for item in group.categories:
- stored = catalog.get(item.key)
- if not stored:
- continue
-
- category_key, fallback_label, count = stored
- processed.add(category_key)
-
- label = item.label or fallback_label
- button_text = f"{item.icon} {label} · {count}"
- group_buttons.append(
- types.InlineKeyboardButton(
- text=button_text,
- callback_data=f"botcfg_cat:{category_key}:1",
- )
- )
-
- if not group_buttons:
- continue
-
- rows.append(
- [
- types.InlineKeyboardButton(
- text=group.title, callback_data="botcfg_categories:noop"
- )
- ]
- )
- rows.extend(_chunked(group_buttons, 2))
-
- leftover_buttons: list[types.InlineKeyboardButton] = []
- for category_key, label, count in categories:
- if category_key in processed:
- continue
-
- button_text = f"📁 {label} · {count}"
- leftover_buttons.append(
+ for category_key, label, count in sliced:
+ button_text = f"{label} ({count})"
+ rows.append([
types.InlineKeyboardButton(
text=button_text,
callback_data=f"botcfg_cat:{category_key}:1",
)
- )
+ ])
- if leftover_buttons:
- rows.append(
- [
+ if total_pages > 1:
+ nav_row: list[types.InlineKeyboardButton] = []
+ if page > 1:
+ nav_row.append(
types.InlineKeyboardButton(
- text="📁 Прочие категории", callback_data="botcfg_categories:noop"
+ text="⬅️", callback_data=f"botcfg_categories:{page - 1}"
)
- ]
- )
- rows.extend(_chunked(leftover_buttons, 2))
-
- rows.append(
- [
- types.InlineKeyboardButton(
- text="⬅️ Назад", callback_data="admin_submenu_settings"
)
- ]
- )
+ nav_row.append(
+ types.InlineKeyboardButton(
+ text=f"{page}/{total_pages}", callback_data="botcfg_categories:noop"
+ )
+ )
+ if page < total_pages:
+ nav_row.append(
+ types.InlineKeyboardButton(
+ text="➡️", callback_data=f"botcfg_categories:{page + 1}"
+ )
+ )
+ rows.append(nav_row)
+
+ rows.append([
+ types.InlineKeyboardButton(text="⬅️ Назад", callback_data="admin_submenu_settings")
+ ])
return types.InlineKeyboardMarkup(inline_keyboard=rows)
@@ -207,19 +94,7 @@ def _build_settings_keyboard(
for definition in sliced:
value_preview = bot_configuration_service.format_value_for_list(definition.key)
- type_icon = {
- bool: "🔘",
- int: "🔢",
- float: "🔢",
- str: "📝",
- }.get(definition.python_type, "⚙️")
-
- override_icon = "⭐ " if bot_configuration_service.has_override(definition.key) else ""
- optional_suffix = " (опц.)" if definition.is_optional else ""
- display_name = definition.display_name
- button_text = (
- f"{override_icon}{type_icon} {display_name}{optional_suffix} · {value_preview}"
- )
+ button_text = f"{definition.key} = {value_preview}"
rows.append([
types.InlineKeyboardButton(
text=button_text,
@@ -302,8 +177,6 @@ def _render_setting_text(key: str) -> str:
lines = [
"🧩 Настройка",
- f"Название: {summary['name']}",
- f"Категория: {summary['category_label']}",
f"Ключ: {summary['key']}",
f"Тип: {summary['type']}",
f"Текущее значение: {summary['current']}",