# Админ-панель Web App Админ-панель доступна пользователям, чей Telegram ID указан в `ADMIN_IDS`. В Web App такие пользователи видят раздел администрирования; все API `/api/admin/*` дополнительно проверяют сессию и `ADMIN_IDS` на сервере. Доступ проверяется по **Telegram ID**, сохранённому у записи пользователя в базе: аккаунт только с email без привязки Telegram не получит права администратора, даже если его идентификатор ошибочно указан в `ADMIN_IDS`. ## Возможности - дашборд со статистикой пользователей, платежей и синхронизации с Remnawave; - список пользователей с поиском, фильтрами и колонкой premium-трафика; карточка пользователя с активной подпиской, обычным и premium-трафиком, платежами и действиями (подробнее в разделе «Пользователи» ниже); - блокировка пользователей, входящий список тикетов поддержки, рассылки, промокоды и просмотр логов; - ручная синхронизация с Remnawave; - редактор разрешенных настроек приложения из manifest-файла; - раздел **Внешний вид** для логотипа, выбора темы, accent-цвета, масштаба логотипа и предпросмотра тем; - раздел **Инструкции подключения** для встроенной страницы установки, поведения кнопок бота и Remnawave Subscription Page config; - раздел **Бэкапы** для просмотра локальных ZIP-архивов, загрузки архива и восстановления БД/compose-папки; - редактор JSON-каталога тарифов; - загрузка Internal Squads из Remnawave для выбора в тарифах. ## Пользователи Раздел **Пользователи** — таблица с **пагинацией по 25 записей**. Строка поиска ищет по внутреннему числовому ID, Telegram ID, фрагменту `@username`, имени или email; применение — кнопка «Найти» или клавиша Enter в поле поиска. Из этого же раздела администратор управляет основными пользовательскими операциями: - просматривает подписку, статус, трафик, premium-лимиты, платежи и служебные события; - блокирует пользователя; - запускает ручную синхронизацию с Remnawave Panel; - открывает тикеты поддержки и отвечает пользователю. **Фильтры:** - состояние аккаунта: все / не забанены / забанены; - наличие Telegram или email; - связь с панелью Remnawave (`panel_user_uuid`); - **статус подписки в панели** для активной подписки в базе бота: active, expired, limited; - **premium-трафик**: все; без лимита premium в тарифе; безлимитный оверрайд; норма (ниже 85% квоты); предупреждение (от 85% до 100%); исчерпана квота. **Сортировка:** по дате регистрации, имени, ID, а также по **доле использования premium-квоты** (если у активной подписки есть конечный premium-лимит). В колонке premium для конечной квоты показывается израсходовано относительно лимита; лимит складывается из базовой premium-квоты тарифа, докупок и бонусов. Отображение **не опирается** на служебный флаг «limited» в базе как на признак «есть premium-трафик» — он отражает другую семантику на стороне панели. Карточка пользователя по переходу из списка объединяет просмотр подписки, трафика, платежей и действий (блокировка, сообщения, продление, оверрайды и т.д.) в соответствии с доступными эндпоинтами `/api/admin/users/*`. ## Настройки Раздел **Система -> Настройки** позволяет менять приложение без редактирования `.env`, но только в пределах allowlist из `backend/bot/app/web/admin_settings_manifest.py`. Даже администратор не может менять через UI произвольные атрибуты `Settings`. Изменения сохраняются как overrides в базе данных и применяются поверх `.env` без перезапуска. Если значение сброшено, снова используется значение из `.env`. После сохранения публичный кеш настроек Web App сбрасывается, поэтому пользователи видят изменения при следующих запросах. В manifest сейчас входят: - общие параметры: язык, валюта, ссылки поддержки, документы, обязательный канал, Remnawave-доступы и поведение `/start`; - внешний вид и доступность Web App: название, цвет, логотип и `WEBAPP_ENABLED`; - инструкции подключения: `SUBSCRIPTION_GUIDES_ENABLED`, `SUBSCRIPTION_GUIDES_BOT_MENU_ENABLED`, чтение конфига из Remnawave Panel, JSON-переопределение и резервный путь к файлу; - legacy-тарифы без JSON-каталога: периоды подписки, RUB/Stars цены, реферальные бонусы и пакеты трафика; - платежные провайдеры: включение методов, порядок кнопок, публичные параметры и секреты YooKassa, FreeKassa, Platega, SeverPay, Wata, CryptoPay, Heleket, LAVA и Stars, а также текст и иконки кнопок оплаты; - пробный период, приветственный реферальный бонус, уведомления, логирование, Telegram антифлуд, поддержка, раздел устройств, лимит устройств и legacy-лимиты трафика. Секретные поля помечены как secret и не должны использоваться для произвольного просмотра старых значений. Настройки, которых нет в manifest, остаются только в `.env` или коде. Для каждого платежного метода в разделе провайдера доступны настройки отображения `PAYMENT__WEBAPP_LABEL_RU`, `PAYMENT__WEBAPP_LABEL_EN`, `PAYMENT__WEBAPP_ICON`, `PAYMENT__TELEGRAM_LABEL_RU`, `PAYMENT__TELEGRAM_LABEL_EN` и `PAYMENT__TELEGRAM_EMOJI`. Пустое значение возвращает мультиязычное значение по умолчанию из модуля платежного провайдера. Иконка Web App выбирается из уже подключенных lucide-иконок (`frontend/src/lib/components/ui/icons.js`) через модалку в админке. ## Бэкапы Раздел **Система -> Бэкапы** показывает архивы из `data/backups`, принимает upload ZIP-файла и запускает восстановление БД/compose-папки с предварительной проверкой архива. Подробная эксплуатационная инструкция: [бэкапы и восстановление](backups.md). ## Переводы Раздел **Система -> Переводы** позволяет переопределять отдельные строки из `locales/ru.json` и `locales/en.json` без монтирования полного файла локализации. Строки сгруппированы по месту применения: админка, Mini App, Telegram-бот, платежи, подписки, поддержка и другие группы. В разделе строки разделены по аудитории: пользовательские тексты Mini App/бота/платежей и внутренние тексты админки, логов и синхронизации. Дополнительные языки можно добавлять прямо в интерфейсе по коду локали, например `uk`, `de` или `pt-BR`; для таких языков оверрайды хранятся без отдельного базового файла локали, а отсутствующие строки берутся из языка по умолчанию. Файл `data/locales-overrides.json` считается источником правды, а таблица `locale_overrides` хранит его DB-зеркало. Если валидный JSON-файл есть, при старте backend и worker полностью синхронизируют БД с файлом, включая удаления строк. Если файл отсутствует, но каталог доступен для записи, он автоматически создается из текущих DB-оверрайдов или как пустой `{}`. Если файл не примонтирован, недоступен или временно сломан по JSON, используется fallback из БД. Сохранение из админки записывает полный итоговый снапшот и в JSON-файл, и в БД; если активный JSON-файл есть, но его нельзя перезаписать, сохранение отклоняется, чтобы БД не разъехалась с главным файлом. Формат файла: ```json { "ru": { "menu_personal_account_button": "Личный кабинет" }, "en": { "menu_personal_account_button": "Account" } } ``` ## Инструкции подключения Секция **Система -> Настройки -> Инструкции подключения** управляет встроенным экраном установки. `SUBSCRIPTION_GUIDES_ENABLED` включает `/install` в личном кабинете, а `SUBSCRIPTION_GUIDES_BOT_MENU_ENABLED` заставляет кнопки подключения в Telegram-боте открывать Mini App вместо финальной Remnawave Subscription Page. Оба переключателя включены по умолчанию. По умолчанию Minishop читает конфиг Remnawave Subscription Page из панели (`SUBSCRIPTION_PAGE_CONFIG_PANEL_ENABLED=True`). Это основной режим, потому что один и тот же конфиг используется и в панели, и во встроенной инструкции. JSON-поле `SUBSCRIPTION_PAGE_CONFIG_JSON` применяется только когда явно включен `SUBSCRIPTION_PAGE_CONFIG_JSON_OVERRIDE_ENABLED`; иначе оно может храниться в админке, но не влияет на пользователей. `SUBSCRIPTION_PAGE_CONFIG_PATH` остается резервным путем к локальному v1 JSON-файлу, если конфиг панели отключен или недоступен. При сохранении backend валидирует JSON-переопределение как конфиг Remnawave Subscription Page v1. Ошибки показываются как обычные ошибки валидации настроек, а если рабочий конфиг недоступен, пользовательская кнопка подключения откатывается к старой финальной ссылке подписки. ## Поддержка Раздел **Коммуникации -> Поддержка** показывает входящий список тикетов из Mini App. В списке доступны фильтры по статусу, приоритету, категории и назначенному администратору, поиск по теме и пользователю, сортировка по обновлению, созданию или важности. В карточке тикета администратор видит диалог, пользовательский контекст и действия: ответить пользователю, оставить внутреннюю заметку, изменить статус, приоритет, категорию или исполнителя, закрыть тикет и перейти в карточку пользователя. Внутренние заметки не показываются пользователю. Счетчик непрочитанных обращений отображается в навигации админки. Уведомления о новых тикетах и ответах пользователя настраиваются через `LOG_SUPPORT`, `LOG_SUPPORT_THREAD_ID` и параметры `SUPPORT_*`. Подробности: [поддержка пользователей / тикеты](support.md). ## Внешний вид Раздел **Внешний вид** объединяет настройки бренда и темы Web App. Логотип можно загрузить файлом или по HTTPS-ссылке; backend сохраняет файл в `data/webapp-logo/uploads` и подставляет локальный URL. Если логотип не задан, показывается логотип проекта по умолчанию. Favicon генерируется из логотипа или загружается отдельно. Этот же бренд используется в HTML-письмах. Загруженный локальный логотип встраивается в письмо как inline image (`cid:webapp-logo`), поэтому email-клиенту не нужен прямой доступ к `/webapp-uploaded-logo/...`. Логотип по публичной HTTPS-ссылке остается внешней картинкой в письме. В блоке тем админка читает каталог из `WEBAPP_THEMES_DIR`, показывает встроенные и кастомные темы, позволяет выбрать текущую тему, изменить accent, включить или выключить тему для админки и настроить отдельный масштаб логотипа для desktop и mobile layout. Кнопка предпросмотра открывает `/home?theme_preview=` и не меняет глобальную тему до сохранения. Подробный формат `theme.json`, CSS/asset-роуты и пошаговый пайплайн создания новой темы описаны в [webapp-themes.md](webapp-themes.md). ## Тарифы Раздел **Система -> Тарифы** работает с файлом `TARIFFS_CONFIG_PATH` (по умолчанию `data/tariffs.json`). При сохранении backend валидирует payload через `TariffsConfig`, пишет JSON в UTF-8 и сбрасывает кеш публичных данных Web App. Если файла еще нет, админка открывает пустой каталог. Перед сохранением должен быть хотя бы один включенный тариф, а `default_tariff` должен ссылаться на включенный тариф. Редактор тарифа разделен на вкладки: - **Основное**: ключ, модель `period`/`traffic`, видимость, названия и описания RU/EN, базовые Internal Squads, HWID-лимит, месячный лимит или курс конвертации; - **Цены**: периоды, цены и реферальные бонусы для `period`, пакеты GB и цены для `traffic`; - **Докупки**: обычные пакеты докупки трафика для `period`; для `traffic` отдельные докупки не нужны, пользователь повторно покупает пакеты из `traffic_packages`; - **Premium**: названия premium-раздела RU/EN, premium Internal Squads, месячный premium-лимит и RUB/Stars пакеты premium-докупки; - **Устройства**: RUB/Stars пакеты докупки HWID-устройств. Порядок периодов, traffic-пакетов, обычных докупок, premium-докупок и HWID-пакетов меняется перетаскиванием строк в редакторе. Этот порядок сохраняется в JSON и используется на витрине Web App и в Telegram-боте. Базовые и premium Internal Squads выбираются из Remnawave через `/api/admin/panel/internal-squads`. Если панель недоступна, можно сохранить уже существующие UUID в JSON, но выпадающий список не загрузится. ## Практические замечания Не меняйте `key` опубликованного тарифа без миграции активных подписок: подписки, платежи и смена тарифа ссылаются на этот ключ. Отключенный тариф исчезает с витрины, но активные подписки с его `tariff_key` продолжают существовать. Удаление тарифа безопасно только если не осталось активных подписок, которым нужна докупка, продление или смена с этого тарифа. Для Docker важно, чтобы путь `TARIFFS_CONFIG_PATH` был доступен на запись контейнеру. Если каталог `./data` смонтирован в `/app/data`, админка может сохранять `data/tariffs.json`.