Merge branch 'dev' into patch-1

This commit is contained in:
BADtochka
2026-06-04 16:11:34 +03:00
committed by GitHub
172 changed files with 21250 additions and 3254 deletions
+8 -4
View File
@@ -9,7 +9,7 @@
- блокировка пользователей, входящий список тикетов поддержки, рассылки, промокоды и просмотр логов;
- ручная синхронизация с Remnawave;
- редактор разрешенных настроек приложения из manifest-файла;
- раздел **Внешний вид** для логотипа, emoji-логотипа, выбора темы, accent-цвета, масштаба логотипа и предпросмотра тем;
- раздел **Внешний вид** для логотипа, выбора темы, accent-цвета, масштаба логотипа и предпросмотра тем;
- раздел **Инструкции подключения** для встроенной страницы установки, поведения кнопок бота и Remnawave Subscription Page config;
- раздел **Бэкапы** для просмотра локальных ZIP-архивов, загрузки архива и восстановления БД/compose-папки;
- редактор JSON-каталога тарифов;
@@ -49,7 +49,7 @@
В manifest сейчас входят:
- общие параметры: язык, валюта, ссылки поддержки, документы, обязательный канал, Remnawave-доступы и поведение `/start`;
- внешний вид и доступность Web App: название, цвет, логотип, emoji-логотип и `WEBAPP_ENABLED`;
- внешний вид и доступность 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 и Stars, а также текст и иконки кнопок оплаты;
@@ -102,9 +102,11 @@
## Внешний вид
Раздел **Внешний вид** объединяет настройки бренда и темы Web App. Логотип можно загрузить файлом или по HTTPS-ссылке; backend сохраняет файл в `data/webapp-logo/uploads` и подставляет локальный URL. Если включен emoji-логотип, картинка скрывается, а для emoji можно выбрать системный, Twemoji, Noto Color, animated Noto и другие варианты отрисовки.
Раздел **Внешний вид** объединяет настройки бренда и темы Web App. Логотип можно загрузить файлом или по HTTPS-ссылке; backend сохраняет файл в `data/webapp-logo/uploads` и подставляет локальный URL. Если логотип не задан, показывается логотип проекта по умолчанию. Favicon генерируется из логотипа или загружается отдельно.
В блоке тем админка читает каталог из `WEBAPP_THEMES_DIR`, показывает встроенные и кастомные темы, позволяет выбрать текущую тему, изменить accent, включить или выключить тему для админки и настроить масштаб логотипа на главной и экране входа. Кнопка предпросмотра открывает `/home?theme_preview=<key>` и не меняет глобальную тему до сохранения.
Этот же бренд используется в HTML-письмах. Загруженный локальный логотип встраивается в письмо как inline image (`cid:webapp-logo`), поэтому email-клиенту не нужен прямой доступ к `/webapp-uploaded-logo/...`. Логотип по публичной HTTPS-ссылке остается внешней картинкой в письме.
В блоке тем админка читает каталог из `WEBAPP_THEMES_DIR`, показывает встроенные и кастомные темы, позволяет выбрать текущую тему, изменить accent, включить или выключить тему для админки и настроить отдельный масштаб логотипа для desktop и mobile layout. Кнопка предпросмотра открывает `/home?theme_preview=<key>` и не меняет глобальную тему до сохранения.
Подробный формат `theme.json`, CSS/asset-роуты и пошаговый пайплайн создания новой темы описаны в [webapp-themes.md](webapp-themes.md).
@@ -122,6 +124,8 @@
- **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, но выпадающий список не загрузится.
## Практические замечания
+6
View File
@@ -44,6 +44,12 @@ BRUTE_FORCE_WINDOW_SECONDS=900
BRUTE_FORCE_LOCK_SECONDS=900
```
## Брендинг писем
HTML-письма используют тот же бренд, что и Mini App: название из `WEBAPP_TITLE`, accent из внешнего вида и логотип из раздела **Внешний вид**. Если логотип загружен через админку файлом, backend прикладывает его к письму как inline image (`cid:webapp-logo`), поэтому получателю не нужен доступ к внутреннему `/webapp-uploaded-logo/...`.
Если в качестве логотипа задан публичный `https://` URL, письмо использует его как обычный внешний `<img>`. В этом режиме некоторые почтовые клиенты могут скрыть картинку, пока получатель не разрешит загрузку внешних изображений.
Для Brevo обычно подходит порт `587` с STARTTLS. Если основной порт недоступен, приложение пробует порты из `SMTP_FALLBACK_PORTS`; порт `465` используется через SSL wrapper автоматически.
`SMTP_FROM_EMAIL` должен быть подтвержден у SMTP-провайдера, иначе письмо часто отклоняется или попадает в спам. `SMTP_FROM_NAME` можно оставить пустым, тогда используется название Web App.
+3 -1
View File
@@ -6,6 +6,8 @@ Minishop отправляет уведомления в Telegram и на email.
Для уведомлений жизненного цикла подписки есть отдельный флаг `SUBSCRIPTION_EMAIL_NOTIFICATIONS_ENABLED`. Если он включен, пользовательские уведомления об окончании подписки отправляются в Telegram при наличии привязанного Telegram-аккаунта и на email при наличии привязанной почты.
Все HTML-письма используют общий email-шаблон с брендом из Web App: заголовком, accent-цветом и логотипом. Логотип, загруженный через раздел **Внешний вид**, отправляется как inline image, а публичный HTTPS-логотип остается внешней картинкой.
## Сводная таблица
| Событие | Получатель | Telegram | Email | Условия и ограничения |
@@ -16,7 +18,7 @@ Minishop отправляет уведомления в Telegram и на email.
| Успешная покупка отдельного пакета трафика | Пользователь | ✓ | ✓ | Для `traffic` / `traffic_package`; email отправляется, если SMTP настроен и у пользователя есть email. |
| Успешная докупка обычного трафика к тарифу | Пользователь | ✓ | ✓ | Для `topup`; email отправляется, если SMTP настроен и у пользователя есть email. |
| Успешная покупка premium-трафика | Пользователь | ✓ | ✓ | Для `premium_topup`; email отправляется, если SMTP настроен и у пользователя есть email. |
| Успешная покупка HWID-устройств | Пользователь | ✓ | ✓ | Отправляется после оплаты `hwid_devices` или `hwid_devices_renewal`; email отправляется, если SMTP настроен и у пользователя есть email. |
| Успешная покупка HWID-устройств | Пользователь | ✓ | ✓ | Отправляется после отдельной оплаты `hwid_devices`; при продлении устройств вместе с подпиской добавляется примечание к уведомлению об успешной оплате подписки. Email отправляется, если SMTP настроен и у пользователя есть email. |
| Платное повышение тарифа | Пользователь | ✓ | ✓ | Для `tariff_upgrade`; email отправляется, если SMTP настроен и у пользователя есть email. |
| Способ оплаты YooKassa привязан | Пользователь | ✓ | ✓ | Отправляется после успешного сохранения платежного метода через webhook YooKassa; email отправляется, если SMTP настроен и у пользователя есть email. |
| Ошибка оплаты по webhook провайдера | Пользователь | ✓ | ✓ | Отправляется, когда платежный провайдер сообщает о неуспешном платеже; email отправляется, если SMTP настроен и у пользователя есть email. |
+111 -14
View File
@@ -22,6 +22,30 @@
- [Тарифы](tariffs.md) — цены, Telegram Stars и сценарии покупки.
- [Логи](../troubleshooting/logs.md) — проверка webhook и создания платежных ссылок.
## Webhook URL провайдеров
Все платежные webhook URL строятся от `WEBHOOK_BASE_URL` — публичного HTTPS-адреса backend/webhook-домена.
Это должен быть домен, который проксируется на backend-сервер вебхуков (`backend:8080`), а не frontend/Mini App домен из `SUBSCRIPTION_MINI_APP_URL`.
Если `WEBHOOK_BASE_URL=https://bot.example.com`, полный webhook URL получается как `https://bot.example.com` + путь из таблицы.
| Провайдер | URL |
| --- | --- |
| YooKassa | `WEBHOOK_BASE_URL` + `/webhook/yookassa` |
| FreeKassa | `WEBHOOK_BASE_URL` + `/webhook/freekassa` |
| Platega | `WEBHOOK_BASE_URL` + `/webhook/platega` |
| SeverPay | `WEBHOOK_BASE_URL` + `/webhook/severpay` |
| Wata | `WEBHOOK_BASE_URL` + `/webhook/wata` |
| CryptoPay | `WEBHOOK_BASE_URL` + `/webhook/cryptopay` |
| Heleket | `WEBHOOK_BASE_URL` + `/webhook/heleket` |
| PayKilla | `WEBHOOK_BASE_URL` + `/webhook/paykilla` |
| Telegram Stars | `WEBHOOK_BASE_URL` + `/tg/webhook` |
После настройки сделайте тестовый платеж и проверьте, что в логах `backend` виден входящий `POST` на нужный путь.
Если провайдер сообщает, что адрес недоступен, проверьте DNS, HTTPS и reverse proxy для `WEBHOOK_BASE_URL`. Путь должен начинаться с `/webhook/...` без `/api`, `/auth` и frontend-домена.
## YooKassa
YooKassa используется для рублевых оплат. Провайдер также может участвовать в сценариях автопродления period-подписок.
@@ -29,7 +53,7 @@ YooKassa используется для рублевых оплат. Прова
### Настройка
1. Включите `YOOKASSA_ENABLED`.
2. Заполните `YOOKASSA_SHOP_ID`, `YOOKASSA_SECRET_KEY`, `YOOKASSA_RETURN_URL`.
2. Заполните `YOOKASSA_SHOP_ID`, `YOOKASSA_SECRET_KEY` и `YOOKASSA_RETURN_URL`.
3. Скопируйте URL вебхука из админ-панели и укажите его в кабинете YooKassa.
### Справочник
@@ -43,10 +67,10 @@ FreeKassa подключается как отдельный платежный
### Настройка
1. Включите `FREEKASSA_ENABLED`.
2. Заполните `FREEKASSA_MERCHANT_ID`, `FREEKASSA_FIRST_SECRET`, `FREEKASSA_SECOND_SECRET`, `FREEKASSA_API_KEY`.
2. Заполните `FREEKASSA_MERCHANT_ID`, `FREEKASSA_FIRST_SECRET`, `FREEKASSA_SECOND_SECRET` и `FREEKASSA_API_KEY`.
3. Проверьте настройки подписи.
4. Скопируйте URL вебхука из админ-панели и укажите его в кабинете FreeKassa.
5. При необходимости заполните список доверенных IP.
5. При необходимости заполните `FREEKASSA_TRUSTED_IPS`.
### Справочник
@@ -59,11 +83,20 @@ Platega подключается как отдельный платежный п
### Настройка
1. Включите `PLATEGA_ENABLED`.
2. Укажите `PLATEGA_MERCHANT_ID` и `PLATEGA_SECRET`.
2. Укажите `PLATEGA_BASE_URL`, `PLATEGA_MERCHANT_ID` и `PLATEGA_SECRET`.
3. Скопируйте URL вебхука из админ-панели и укажите его в кабинете Platega.
4. Проверьте `PLATEGA_RETURN_URL` и `PLATEGA_FAILED_URL`.
5. При необходимости укажите `PLATEGA_PAYMENT_METHOD`.
### Дополнительные кнопки
- `PLATEGA_SBP_ENABLED` — отдельная кнопка СБП/карта.
- `PLATEGA_SBP_METHOD` — ID метода для СБП/карты.
- `PLATEGA_CRYPTO_ENABLED` — отдельная crypto-кнопка Platega.
- `PLATEGA_CRYPTO_METHOD` — ID метода для crypto-кнопки.
- `PAYMENT_PLATEGA_SBP_*` — текст и иконка кнопки СБП/карта.
- `PAYMENT_PLATEGA_CRYPTO_*` — текст и иконка crypto-кнопки.
### Справочник
- [Platega](../configuration/env-vars.md#platega)
@@ -75,9 +108,11 @@ SeverPay подключается как отдельный платежный
### Настройка
1. Включите `SEVERPAY_ENABLED`.
2. Укажите `SEVERPAY_MID`, `SEVERPAY_TOKEN`, `SEVERPAY_BASE_URL`
3. Скопируйте URL вебхука из админ-панели и укажите его в кабинете SeverPay.
4. При необходимости задайте `SEVERPAY_LIFETIME_MINUTES`.
2. Укажите `SEVERPAY_BASE_URL`.
3. Заполните `SEVERPAY_MID` и `SEVERPAY_TOKEN`.
4. Проверьте `SEVERPAY_RETURN_URL`.
5. Скопируйте URL вебхука из админ-панели и укажите его в кабинете SeverPay.
6. При необходимости задайте `SEVERPAY_LIFETIME_MINUTES`.
### Справочник
@@ -116,8 +151,8 @@ CryptoPay используется для криптовалютных плат
2. Укажите `CRYPTOPAY_TOKEN`.
3. Выберите `CRYPTOPAY_NETWORK`: `mainnet` или `testnet`.
4. Задайте `CRYPTOPAY_CURRENCY_TYPE`: `fiat` или `crypto`.
5. Скопируйте URL вебхука из админ-панели и укажите его в CryptoPay.
6. Проверьте `CRYPTOPAY_ASSET`.
5. Проверьте `CRYPTOPAY_ASSET`, например `RUB`, `USDT` или `BTC`.
6. Скопируйте URL вебхука из админ-панели и укажите его в CryptoPay.
### Проверка
@@ -138,10 +173,12 @@ Heleket используется для крипто-инвойсов с merchan
1. Включите `HELEKET_ENABLED`.
2. Укажите `HELEKET_BASE_URL`, `HELEKET_MERCHANT_ID` и `HELEKET_API_KEY`.
3. Настройте `HELEKET_CURRENCY`.
4. Скопируйте URL вебхука из админ-панели и укажите его в кабинете Heleket.
5. При необходимости задайте `HELEKET_TO_CURRENCY` и `HELEKET_NETWORK`.
7. При необходимости включите `HELEKET_VERIFY_WEBHOOK_SIGNATURE`.
8. Для IP-фильтрации заполните `HELEKET_TRUSTED_IPS`.
4. При необходимости задайте `HELEKET_TO_CURRENCY` и `HELEKET_NETWORK`.
5. Проверьте `HELEKET_RETURN_URL` и `HELEKET_SUCCESS_URL`.
6. Настройте `HELEKET_LIFETIME_SECONDS`.
7. Скопируйте URL вебхука из админ-панели и укажите его в кабинете Heleket.
8. При необходимости включите `HELEKET_VERIFY_WEBHOOK_SIGNATURE`.
9. Для IP-фильтрации заполните `HELEKET_TRUSTED_IPS`.
### Ограничения
@@ -151,6 +188,64 @@ Heleket используется для крипто-инвойсов с merchan
- [Heleket](../configuration/env-vars.md#heleket)
## PayKilla
PayKilla используется для крипто-инвойсов V2 через hosted checkout `https://gopay.paykilla.com/{invoice_id}`.
API-запросы подписываются HMAC-SHA256. Webhook проверяется по заголовку `X-API-SIGN` и raw body.
### Особенности
- PayKilla строго валидирует текстовые поля invoice.
- В `purpose` и `description` Minishop отправляет простой английский текст `<WEBAPP_TITLE> payment <id>`.
- Локализованное описание платежа остается только внутри Minishop.
- ASCII-safe sanitizer допускает ASCII-буквы, цифры, пробелы, `_`, `.`, `,`.
### Валюта invoice
Minishop создает invoice в валюте, которую PayKilla принимает в поле `currency`.
Если валюта тарифа входит в `PAYKILLA_INVOICE_CURRENCIES`, сумма отправляется как есть.
Если валюта тарифа не входит в список, сумма конвертируется в `PAYKILLA_CURRENCY`. По умолчанию рублевые тарифы конвертируются в `USD` через ExchangeRate-API с кэшем `PAYKILLA_EXCHANGE_RATE_CACHE_SECONDS`.
### Payload invoice
Payload создания invoice содержит обязательные поля `type`, `purpose`, `currency`, `totalPrice` и `paymentCurrencies`.
Дополнительно отправляются `clientOrderId`, `description`, `expiredAt`, `userPaysServiceFee` и `userPaysNetworkFee`.
Redirect URLs в PayKilla не отправляются. Завершение платежа обрабатывается через webhook.
### API key
1. В PayKilla Dashboard откройте **Settings -> API keys**.
2. Создайте ключ типа **HMAC**.
3. Для приема оплат включите permission **INVOICE**.
4. Permission **WITHDRAWAL** не нужен для Minishop-платежей.
5. Сохраните `publicKey` в `PAYKILLA_API_KEY`.
6. Сохраните `secretKey` в `PAYKILLA_SECRET_KEY`.
### Webhook
1. В PayKilla Dashboard откройте **Settings -> Webhooks**.
2. Скопируйте URL вебхука из админ-панели и укажите его в PayKilla.
3. Включите минимальные события: `INVOICE_PAID`, `INVOICE_EXPIRED`.
4. Для production также включите `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, `PAYMENT_OVERPAID`, `PAYMENT_UNDERPAID`, `PAYMENT_PARTIAL`, `COMPLIANCE_FAILED`.
5. Оставьте `PAYKILLA_VERIFY_WEBHOOK_SIGNATURE=True`.
### Настройка
1. Включите `PAYKILLA_ENABLED`.
2. Укажите `PAYKILLA_API_KEY` и `PAYKILLA_SECRET_KEY`.
3. Оставьте `PAYKILLA_CURRENCY=USD`, если PayKilla не принимает валюту тарифов как invoice currency.
4. В `PAYKILLA_INVOICE_CURRENCIES` укажите валюты invoice, например `USD,EUR`.
5. В `PAYKILLA_PAYMENT_CURRENCIES` начните с `USDTTRC`.
### Справочник
- [PayKilla](../configuration/env-vars.md#paykilla)
## Telegram Stars
Telegram Stars используются напрямую и поддерживаются в legacy-ценах и JSON-каталоге тарифов.
@@ -171,9 +266,11 @@ Telegram Stars используются напрямую и поддержива
### Ограничения
- Отдельный платежный webhook не нужен.
- Stars-события приходят через webhook Telegram-бота: `WEBHOOK_BASE_URL` + `/tg/webhook`.
- XTR/Stars-докупки не конвертируются без явно заданного курса.
### Справочник
- [Переменные платежей](../configuration/env-vars.md#платежи)
- [Тарифы](tariffs.md)
- [Тарифы](tariffs.md)
+65 -58
View File
@@ -28,6 +28,8 @@ JSON-каталог может содержать несколько тариф
- настройка premium-раздела: названия RU/EN, premium Internal Squads, месячный premium-лимит и пакеты докупки premium-трафика в платежной валюте/Stars;
- настройка базового HWID-лимита и пакетов докупки устройств.
Порядок продаваемых вариантов управляется в админке перетаскиванием строк: это работает для периодов покупки подписки, traffic-пакетов, обычных докупок трафика, premium-докупок и HWID-пакетов. Такой же порядок сохраняется в JSON и затем используется в Web App и Telegram-боте.
После сохранения изменения применяются к новым запросам Web App сразу, потому что конфиг тарифов загружается из JSON при обращении. Уже созданные подписки сохраняют свой `tariff_key`; при удалении или отключении тарифа проверьте, что активные подписки с этим ключом не требуют дальнейшего продления или смены.
Подробности по админ-панели, правам доступа, сохранению настроек и списку разделов есть в [админ-панели](admin-panel.md).
@@ -56,16 +58,16 @@ Legacy-поля остаются алиасами: `prices_rub`, `conversion_rat
Платежные провайдеры не принимают произвольный код валюты одинаково. Бот фильтрует способы оплаты и блокирует создание платежа, если текущая валюта каталога не поддерживается провайдером:
| Провайдер | Валюты по умолчанию |
| --- | --- |
| YooKassa | `RUB` |
| WATA | `RUB`, `USD`, `EUR` |
| FreeKassa | `RUB`, `USD`, `EUR`, `UAH`, `KZT` |
| CryptoPay | fiat: `USD`, `EUR`, `RUB`, `BYN`, `UAH`, `GBP`, `CNY`, `KZT`, `UZS`, `GEL`, `TRY`, `AMD`, `THB`, `INR`, `BRL`, `IDR`, `AZN`, `AED`, `PLN`, `ILS`; crypto: `USDT`, `TON`, `BTC`, `ETH`, `LTC`, `BNB`, `TRX`, `USDC` |
| Heleket | настраиваемый список `HELEKET_SUPPORTED_CURRENCIES` |
| Platega | настраиваемый список `PLATEGA_SUPPORTED_CURRENCIES` |
| SeverPay | настраиваемый список `SEVERPAY_SUPPORTED_CURRENCIES` |
| Telegram Stars | `XTR`, отдельные Stars-цены |
| Провайдер | Валюты по умолчанию |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| YooKassa | `RUB` |
| WATA | `RUB`, `USD`, `EUR` |
| FreeKassa | `RUB`, `USD`, `EUR`, `UAH`, `KZT` |
| CryptoPay | fiat: `USD`, `EUR`, `RUB`, `BYN`, `UAH`, `GBP`, `CNY`, `KZT`, `UZS`, `GEL`, `TRY`, `AMD`, `THB`, `INR`, `BRL`, `IDR`, `AZN`, `AED`, `PLN`, `ILS`; crypto: `USDT`, `TON`, `BTC`, `ETH`, `LTC`, `BNB`, `TRX`, `USDC` |
| Heleket | настраиваемый список `HELEKET_SUPPORTED_CURRENCIES` |
| Platega | настраиваемый список `PLATEGA_SUPPORTED_CURRENCIES` |
| SeverPay | настраиваемый список `SEVERPAY_SUPPORTED_CURRENCIES` |
| Telegram Stars | `XTR`, отдельные Stars-цены |
В админке раздел **Система → Тарифы** показывает текущую платежную валюту и матрицу провайдеров: включен ли метод, настроен ли сервис и будет ли он доступен при выбранной валюте. Для Platega, SeverPay и Heleket список валют нужно держать в соответствии с условиями вашего мерчанта.
@@ -113,43 +115,43 @@ Legacy-поля остаются алиасами: `prices_rub`, `conversion_rat
Основные поля:
| Поле | Назначение |
| --- | --- |
| `default_tariff` | Тариф по умолчанию для первичного выбора и привязки активных подписок без `tariff_key`. |
| `default_currency` | Валюта цен по умолчанию для JSON-каталога. По умолчанию `rub`; `stars` запрещен, потому что Stars используют отдельные цены. |
| `tariffs[].key` | Стабильный ключ тарифа. Используется в платежах, подписках и смене тарифа. |
| `tariffs[].names` | Названия тарифа по языкам. |
| `tariffs[].descriptions` | Описания тарифа по языкам. |
| `tariffs[].enabled` | Доступность тарифа на витрине. |
| `tariffs[].squad_uuids` | Internal Squads Remnawave для пользователей тарифа. |
| `tariffs[].premium_names` | Название premium-раздела по языкам. Используется в карточке лимита, модалке докупки premium-трафика и предупреждениях. Если поле не задано, используется `Premium-серверы` / `Premium servers`. |
| `tariffs[].premium_squad_uuids` | Internal Squads с отдельным premium-лимитом. Ноды для учета берутся автоматически из accessible nodes этих сквадов через API панели. |
| `tariffs[].premium_monthly_gb` | Отдельный месячный лимит трафика по premium-сквадам. `0` или отсутствие поля отключает отдельное ограничение. |
| `tariffs[].premium_topup_packages` | Пакеты докупки premium-трафика в формате `{ "rub": [{ "gb": 10, "price": 99 }], "stars": [...] }`. Требуют `premium_squad_uuids`. |
| `tariffs[].billing_model` | Модель тарифа: `period` или `traffic`. |
| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств. `0` означает безлимит, отсутствие поля использует `USER_HWID_DEVICE_LIMIT`. |
| `tariffs[].hwid_device_packages` | Пакеты докупки устройств. `price` — legacy/monthly fallback, `prices` задаёт полную цену пакета для периодов тарифа (`"1"`, `"3"`, `"6"`, `"12"`), `min_price` задаёт минимальную цену prorate-докупки. |
| Поле | Назначение |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `default_tariff` | Тариф по умолчанию для первичного выбора и привязки активных подписок без `tariff_key`. |
| `default_currency` | Валюта цен по умолчанию для JSON-каталога. По умолчанию `rub`; `stars` запрещен, потому что Stars используют отдельные цены. |
| `tariffs[].key` | Стабильный ключ тарифа. Используется в платежах, подписках и смене тарифа. |
| `tariffs[].names` | Названия тарифа по языкам. |
| `tariffs[].descriptions` | Описания тарифа по языкам. |
| `tariffs[].enabled` | Доступность тарифа на витрине. |
| `tariffs[].squad_uuids` | Internal Squads Remnawave для пользователей тарифа. |
| `tariffs[].premium_names` | Название premium-раздела по языкам. Используется в карточке лимита, модалке докупки premium-трафика и предупреждениях. Если поле не задано, используется `Premium-серверы` / `Premium servers`. |
| `tariffs[].premium_squad_uuids` | Internal Squads с отдельным premium-лимитом. Ноды для учета берутся автоматически из accessible nodes этих сквадов через API панели. |
| `tariffs[].premium_monthly_gb` | Отдельный месячный лимит трафика по premium-сквадам. `0` или отсутствие поля отключает отдельное ограничение. |
| `tariffs[].premium_topup_packages` | Пакеты докупки premium-трафика в формате `{ "rub": [{ "gb": 10, "price": 99 }], "stars": [...] }`. Требуют `premium_squad_uuids`. Порядок строк задает порядок premium-докупок на витрине и меняется drag&drop в админке. |
| `tariffs[].billing_model` | Модель тарифа: `period` или `traffic`. |
| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств. `0` означает безлимит, отсутствие поля использует `USER_HWID_DEVICE_LIMIT`. |
| `tariffs[].hwid_device_packages` | Пакеты докупки устройств. `price` — legacy/monthly fallback, `prices` задаёт полную цену пакета для периодов тарифа (`"1"`, `"3"`, `"6"`, `"12"`), `min_price` задаёт минимальную цену prorate-докупки. Порядок строк задает порядок HWID-докупок и меняется drag&drop в админке. |
Для `period`-тарифа также используются:
| Поле | Назначение |
| --- | --- |
| `monthly_gb` | Базовый месячный лимит трафика тарифа. `0` означает безлимит. |
| `prices` | Generic-цены периодов по валютам, например `{ "usd": { "1": 4.99 } }`. |
| `prices_rub` | Legacy-цены периодов в рублях, ключ - количество месяцев. Эквивалент `prices.rub`. |
| `prices_stars` | Цены периодов в Telegram Stars. |
| `referral_bonus_days_inviter` | Бонус пригласившему в днях для каждого периода. Ключ - количество месяцев, как в `enabled_periods`. |
| `referral_bonus_days_referee` | Бонус приглашенному в днях для каждого периода. Ключ - количество месяцев, как в `enabled_periods`. |
| `enabled_periods` | Периоды, доступные для покупки. |
| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте. |
| Поле | Назначение |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `monthly_gb` | Базовый месячный лимит трафика тарифа. `0` означает безлимит. |
| `prices` | Generic-цены периодов по валютам, например `{ "usd": { "1": 4.99 } }`. |
| `prices_rub` | Legacy-цены периодов в рублях, ключ - количество месяцев. Эквивалент `prices.rub`. |
| `prices_stars` | Цены периодов в Telegram Stars. |
| `referral_bonus_days_inviter` | Бонус пригласившему в днях для каждого периода. Ключ - количество месяцев, как в `enabled_periods`. |
| `referral_bonus_days_referee` | Бонус приглашенному в днях для каждого периода. Ключ - количество месяцев, как в `enabled_periods`. |
| `enabled_periods` | Периоды, доступные для покупки. Порядок элементов в массиве задаёт порядок периодов на витрине (в Telegram-боте и Web App) — отсортируйте их так, как нужно показывать. В веб-админке этот порядок меняется перетаскиванием строк периодов. |
| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. Если поле не задано или списки пустые, докупка для тарифа не показывается в Web App и Telegram-боте. Порядок строк задает порядок докупок на витрине и меняется drag&drop в админке. |
Для `traffic`-тарифа используются:
| Поле | Назначение |
| --- | --- |
| `traffic_packages` | Пакеты трафика в GB по валютам каталога и Telegram Stars. |
| `conversion_rate_per_gb` | Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф в валюте каталога. |
| `conversion_rate_rub_per_gb` | Legacy-алиас для рублевых каталогов. |
| Поле | Назначение |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `traffic_packages` | Пакеты трафика в GB по валютам каталога и Telegram Stars. Порядок пакетов в списке задаёт порядок на витрине (в Telegram-боте и Web App): сначала идут пакеты валюты каталога, затем пакеты, доступные только за Stars. В веб-админке порядок меняется перетаскиванием строк. |
| `conversion_rate_per_gb` | Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф в валюте каталога. |
| `conversion_rate_rub_per_gb` | Legacy-алиас для рублевых каталогов. |
Если у traffic-тарифа нет пакетов в `default_currency`, `conversion_rate_per_gb` обязателен.
@@ -278,7 +280,10 @@ limit_after = current_used + balance_after
- полная цена HWID-пакета берется из `prices[duration_months]`; если периода нет, используется fallback `price * duration_months`;
- фактическая цена докупки считается пропорционально оплачиваемому окну `valid_from -> valid_until` относительно периода подписки и фиксируется в платежe;
- для Telegram Stars цена округляется вверх до целого Stars, для платежной валюты — вверх до копеек; `min_price` защищает от микроплатежей в конце периода;
- при продлении подписки докупленные устройства не продлеваются автоматически: старая докупка действует до прежнего `end_date`, а для нового срока создается отдельная `hwid_devices_renewal`-покупка;
- кнопка докупки устройств всегда покупает устройства только для текущей активной подписки и только до текущего срока ее действия;
- при продлении подписки пользователь видит отдельный чекбокс продления действующих докупленных устройств; чекбокс включен по умолчанию, цена считается по текущему тарифу и добавляется в тот же платеж подписки;
- если пользователь продлил подписку без продления устройств, старая докупка продолжает действовать до своего `valid_until`, а Web App показывает предупреждение о возможном временном возврате к базовому лимиту;
- админские продления, промокоды и реферальные бонусы добавляют фиксированное количество дней отдельно к подписке и к действующим докупкам устройств, не склеивая даты окончания;
- `traffic`-тарифы не показывают и не принимают докупку HWID-устройств, потому что у них нет срока подписки;
- при смене тарифа базовый лимит берется из целевого тарифа, а неиспользованная стоимость HWID-докупок в платежной валюте конвертируется в дни нового period-тарифа или GB traffic-тарифа; XTR/Stars-докупки не конвертируются без явного курса и продолжают жить по своему `valid_until`;
- история докупок пишется в `hwid_device_purchases`;
@@ -292,12 +297,12 @@ limit_after = current_used + balance_after
Варианты расчета:
| Переход | Поведение |
| --- | --- |
| `period -> period` | Остаток оплаченных дней оценивается по legacy-полю `effective_monthly_price_rub`, где хранится месячная цена в платежной валюте каталога, затем пересчитывается в дни целевого тарифа через месячную цену целевого тарифа. Неиспользованная стоимость HWID-докупок в платежной валюте добавляется к этому расчету как дополнительные дни. Количество дней округляется вниз. |
| `period -> period` с доплатой | Если целевой тариф дороже, может быть создан платеж `tariff_upgrade`; неиспользованная стоимость HWID-докупок в платежной валюте уменьшает сумму доплаты. После оплаты применяется целевой тариф, а конвертированные HWID-окна закрываются. |
| `period -> traffic` | Остаток оплаченных дней и неиспользованная стоимость HWID-докупок в платежной валюте конвертируются в GB по `conversion_rate_per_gb` или минимальной цене GB из пакетов целевого тарифа. |
| `traffic -> period` | Пользователь выбирает и оплачивает период целевого тарифа; остаток GB сохраняется как `topup_balance_bytes` поверх лимита period-тарифа. |
| Переход | Поведение |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `period -> period` | Остаток оплаченных дней оценивается по legacy-полю `effective_monthly_price_rub`, где хранится месячная цена в платежной валюте каталога, затем пересчитывается в дни целевого тарифа через месячную цену целевого тарифа. Неиспользованная стоимость HWID-докупок в платежной валюте добавляется к этому расчету как дополнительные дни. Количество дней округляется вниз. |
| `period -> period` с доплатой | Если целевой тариф дороже, может быть создан платеж `tariff_upgrade`; неиспользованная стоимость HWID-докупок в платежной валюте уменьшает сумму доплаты. После оплаты применяется целевой тариф, а конвертированные HWID-окна закрываются. |
| `period -> traffic` | Остаток оплаченных дней и неиспользованная стоимость HWID-докупок в платежной валюте конвертируются в GB по `conversion_rate_per_gb` или минимальной цене GB из пакетов целевого тарифа. |
| `traffic -> period` | Пользователь выбирает и оплачивает период целевого тарифа; остаток GB сохраняется как `topup_balance_bytes` поверх лимита period-тарифа. |
При смене тарифа бот меняет:
@@ -313,15 +318,15 @@ limit_after = current_used + balance_after
В платежах используются поля:
| Поле | Назначение |
| --- | --- |
| `sale_mode` | Тип продажи: `subscription`, `traffic_package`, `topup`, `premium_topup`, `tariff_upgrade`, `hwid_devices`. |
| `tariff_key` | Ключ тарифа, к которому относится платеж. |
| `purchased_gb` | Купленный объем GB для traffic-пакетов и докупки трафика. |
| `purchased_hwid_devices` | Количество устройств при докупке HWID. |
| `hwid_valid_from`, `hwid_valid_until` | Зафиксированное окно действия HWID-докупки на момент создания платежа. |
| `hwid_pricing_period_months`, `hwid_proration_ratio`, `hwid_full_price` | Метаданные расчета цены HWID-докупки: период тарифа, коэффициент prorate и полная цена пакета для периода. |
| `subscription_duration_months` | Количество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки. |
| Поле | Назначение |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `sale_mode` | Тип продажи: `subscription`, `traffic_package`, `topup`, `premium_topup`, `tariff_upgrade`, `hwid_devices`. |
| `tariff_key` | Ключ тарифа, к которому относится платеж. |
| `purchased_gb` | Купленный объем GB для traffic-пакетов и докупки трафика. |
| `purchased_hwid_devices` | Количество устройств при докупке HWID. |
| `hwid_valid_from`, `hwid_valid_until` | Зафиксированное окно действия HWID-докупки на момент создания платежа. |
| `hwid_pricing_period_months`, `hwid_proration_ratio`, `hwid_full_price` | Метаданные расчета цены HWID-докупки: период тарифа, коэффициент prorate и полная цена пакета для периода. |
| `subscription_duration_months` | Количество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки. |
В callback и metadata платежных провайдеров `sale_mode` может передаваться с суффиксом тарифа, например `subscription@standard` или `topup@standard`. При активации платежа тариф сохраняется отдельно в `tariff_key`.
@@ -345,12 +350,14 @@ Remnawave ограничивает доступ при достижении `tra
Автопродление через YooKassa применяется к подпискам на срок. Для режима продажи трафика без JSON-каталога автопродление пропускается. Для traffic-тарифов JSON-каталога покупка является пакетом трафика, а не периодической подпиской.
Пробный период использует настройки `TRIAL_DURATION_DAYS`, `TRIAL_TRAFFIC_LIMIT_GB`, `TRIAL_TRAFFIC_STRATEGY` и `TRIAL_SQUAD_UUIDS`. Он не выбирает тариф из JSON-каталога, но его можно настроить на странице **Система → Тарифы** рядом с каталогом продаж. Если `TRIAL_SQUAD_UUIDS` пустой, для trial применяются squads из `USER_SQUAD_UUIDS`.
Пробный период использует настройки `TRIAL_DURATION_DAYS`, `TRIAL_TRAFFIC_LIMIT_GB`, `TRIAL_TRAFFIC_STRATEGY` и `TRIAL_SQUAD_UUIDS`. Он не выбирает тариф из JSON-каталога, но его можно настроить на странице **Система → Тарифы** рядом с каталогом продаж. Если `TRIAL_SQUAD_UUIDS` пустой, для trial применяются squads из `USER_SQUAD_UUIDS`. Переключатель `TRIAL_WITHOUT_TELEGRAM_ENABLED` управляет активацией trial для аккаунтов без Telegram, а домены из `DISPOSABLE_EMAIL_DOMAINS` требуют привязки Telegram независимо от этого переключателя.
Промокоды с бонусными днями применяются к покупке period-подписки.
Реферальные бонусы за оплату в JSON-каталоге задаются прямо в period-тарифе рядом с ценами периода: `referral_bonus_days_inviter` для пригласившего и `referral_bonus_days_referee` для приглашенного. Ключи этих словарей - месяцы периода (`"1"`, `"3"`, `"6"`, `"12"` или любые другие периоды тарифа, например `"2"`, `"4"`, `"8"`, `"16"`). Для `traffic`-тарифов такие бонусы не применяются.
Приветственный бонус приглашённому (`REFERRAL_WELCOME_BONUS_DAYS`) настраивается в отдельном блоке **Реферальная программа** на странице тарифов. `REFERRAL_WELCOME_BONUS_WITHOUT_TELEGRAM_ENABLED` разрешает или запрещает выдачу этого бонуса аккаунтам без Telegram; disposable email домены из `DISPOSABLE_EMAIL_DOMAINS` всегда требуют Telegram перед начислением.
Если приглашенный покупает один тариф, а пригласивший находится на другом, размер бонуса берется из тарифа и периода, который купил приглашенный. При этом подписка пригласившего только продлевается на бонусные дни: лимиты, Internal Squads и другие параметры его текущего тарифа не пересчитываются под тариф приглашенного.
В Web App и Telegram-меню подробные строки по периодам показываются только для legacy-режима или когда активен один period-тариф. Если включено несколько period-тарифов, Web App показывает сообщение, что бонус зависит от тарифа и периода оплаты друга, затем список тарифов с диапазонами "от N до N дней" и раскрытием подробностей по иконке вопроса. Telegram-меню в этом случае показывает только диапазоны по каждому тарифу.
+1 -1
View File
@@ -67,7 +67,7 @@ SUPPORT_TICKET_RATE_LIMIT_PER_HOUR=5
Если `WEBAPP_ENABLED=False`, пользовательское веб-приложение и админ-панель не регистрируются. Чтобы снова попасть в админку, включите `WEBAPP_ENABLED=True` в `.env` и перезапустите backend/frontend контейнеры.
Внешний вид настраивается в админке: раздел **Внешний вид** управляет логотипом, emoji-логотипом, accent-цветом, выбранной темой и масштабом логотипа. Кастомные темы читаются из `WEBAPP_THEMES_DIR`, а `WEBAPP_DEFAULT_THEME` может принудительно выбрать тему по ключу. Подробный контракт `theme.json`, CSS/asset-роуты и пайплайн создания темы описаны в [webapp-themes.md](webapp-themes.md).
Внешний вид настраивается в админке: раздел **Внешний вид** управляет логотипом, favicon, accent-цветом, выбранной темой и отдельным масштабом логотипа для desktop/mobile layout. Кастомные темы читаются из `WEBAPP_THEMES_DIR`, а `WEBAPP_DEFAULT_THEME` может принудительно выбрать тему по ключу. Подробный контракт `theme.json`, CSS/asset-роуты и пайплайн создания темы описаны в [webapp-themes.md](webapp-themes.md).
## Авторизация
+50 -46
View File
@@ -13,7 +13,6 @@ Web App поддерживает файловые темы, предпросмо
- включить или выключить применение темы в админ-панели;
- настроить масштаб логотипа на главной и экране входа;
- загрузить логотип файлом или по HTTPS-ссылке;
- включить emoji-логотип и выбрать способ его отрисовки;
- открыть предпросмотр темы через `/home?theme_preview=<key>`.
Через файлы темы можно менять намного больше:
@@ -51,7 +50,9 @@ WEBAPP_DEFAULT_THEME=
В compose-примерах `data/themes` - это локальная папка рядом с выбранным `docker-compose.yml`; она монтируется в контейнер как `/app/data/themes`. Правки в `backend/bot/app/web/themes` попадают в прод только при сборке собственного образа; опубликованный образ их не видит.
Важно: `WEBAPP_PRIMARY_COLOR`, `WEBAPP_LOGO_URL`, `WEBAPP_LOGO_USE_EMOJI`, `WEBAPP_LOGO_EMOJI` и `WEBAPP_LOGO_EMOJI_FONT` больше не являются рабочим способом первичной настройки через `.env`. Эти значения редактируются в админке и сохраняются как overrides в базе. Тема при этом может использовать сохраненный primary color как fallback accent.
Важно: `WEBAPP_PRIMARY_COLOR` и `WEBAPP_LOGO_URL` больше не являются рабочим способом первичной настройки через `.env`. Эти значения редактируются в админке и сохраняются как overrides в базе. Тема при этом может использовать сохраненный primary color как fallback accent.
Email-шаблоны берут тот же бренд из настроек внешнего вида. Загруженный логотип добавляется в письма как inline image (`cid:webapp-logo`), а публичный HTTPS-логотип остается внешней картинкой, которую почтовый клиент может скрыть до разрешения загрузки изображений.
## Контракт `theme.json`
@@ -114,7 +115,8 @@ WEBAPP_DEFAULT_THEME=
"font_sans": "Inter, system-ui, sans-serif",
"font_logo": "Inter, system-ui, sans-serif",
"font_mono": "\"JetBrains Mono\", \"Fira Code\", monospace",
"home_logo_scale": 120,
"home_logo_scale_desktop": 120,
"home_logo_scale_mobile": 95,
"admin_bg": "#05040a",
"admin_surface": "#11101c",
"admin_surface_2": "#090815",
@@ -130,52 +132,54 @@ WEBAPP_DEFAULT_THEME=
Поля верхнего уровня:
| Поле | Назначение |
| --- | --- |
| `key` | Уникальный ключ темы, 1-64 символа: латиница, цифры, `_` и `-`. Если ключ не указан, берется имя папки. |
| `names` | Локализованные названия, например `ru` и `en`. |
| `enabled` | Показывать тему пользователям. Отключенная тема не попадает в публичный каталог. |
| `default` | Делает тему выбранной по умолчанию, если `WEBAPP_DEFAULT_THEME` не задан. |
| Поле | Назначение |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| `key` | Уникальный ключ темы, 1-64 символа: латиница, цифры, `_` и `-`. Если ключ не указан, берется имя папки. |
| `names` | Локализованные названия, например `ru` и `en`. |
| `enabled` | Показывать тему пользователям. Отключенная тема не попадает в публичный каталог. |
| `default` | Делает тему выбранной по умолчанию, если `WEBAPP_DEFAULT_THEME` не задан. |
| `use_primary_accent` | Если `true`, тема может получить accent из настройки внешнего вида, когда в `tokens.accent` ничего нет. |
| `use_in_admin` | Если `false`, пользовательская часть использует тему, но админка откатывается на `dark`. |
| `css_file` | CSS-файл внутри папки темы. Может быть `style.css` или вложенный путь вроде `css/theme.css`. |
| `assets_version` | Версия ассетов. Для встроенных тем используется для обновления старых файлов в `data/themes`. |
| `tokens` | Дизайн-токены, которые превращаются в CSS-переменные на `.app-shell`. |
| `use_in_admin` | Если `false`, пользовательская часть использует тему, но админка откатывается на `dark`. |
| `css_file` | CSS-файл внутри папки темы. Может быть `style.css` или вложенный путь вроде `css/theme.css`. |
| `assets_version` | Версия ассетов. Для встроенных тем используется для обновления старых файлов в `data/themes`. |
| `tokens` | Дизайн-токены, которые превращаются в CSS-переменные на `.app-shell`. |
## Токены
Поддерживаемые токены:
| Токен | CSS-переменная | Что меняет |
| --- | --- | --- |
| `color_scheme` | `color-scheme` | Нативная светлая/темная схема браузера: `dark` или `light`. |
| `style_preset` | CSS-класс пресета | Сейчас `win95`/`windows95` добавляет `theme-preset-win95`; остальные значения не дают специального класса. |
| `accent` | `--accent` | Главный акцент: активные элементы, кнопки, прогресс, фокус. Только hex `#RGB` или `#RRGGBB`. |
| `bg` | `--bg` | Основной фон приложения. |
| `panel` | `--panel` | Основные карточки и поверхности. |
| `panel_2` | `--panel-2` | Вторичные поверхности. |
| `panel_3` | `--panel-3` | Поверхности повышенной вложенности, dropdown/popover. |
| `border` | `--border` | Обычные границы. |
| `border_strong` | `--border-strong` | Усиленные границы и hover-состояния. |
| `text` | `--text` | Основной текст. |
| `muted` | `--muted` | Вторичный текст. |
| `dim` | `--dim` | Еще более тихий текст и служебные подписи. |
| `danger` | `--danger` | Ошибки и опасные действия. |
| `blue` | `--blue` | Синий вспомогательный цвет. |
| `radius` | `--radius` | Базовый радиус карточек, кнопок и контролов. |
| `font_sans` | `--font-sans` | Основной шрифт интерфейса. |
| `font_logo` | `--font-logo` | Шрифт бренда и заголовка. |
| `font_mono` | `--font-mono` | Моноширинный шрифт. |
| `home_logo_scale` | `--home-logo-scale` | Масштаб логотипа на главной и входе, от `50` до `300` процентов. |
| `admin_bg` | `--admin-bg` | Фон админ-панели. |
| `admin_surface` | `--admin-surface` | Основные карточки админки. |
| `admin_surface_2` | `--admin-surface-2` | Вторичные поверхности админки. |
| `admin_elev` | `--admin-elev` | Elevated-поверхности админки. |
| `admin_border` | `--admin-border` | Границы админки. |
| `admin_border_strong` | `--admin-border-strong` | Усиленные границы админки. |
| `admin_text` | `--admin-text` | Основной текст админки. |
| `admin_muted` | `--admin-muted` | Вторичный текст админки. |
| `admin_dim` | `--admin-dim` | Тихие подписи админки. |
| Токен | CSS-переменная | Что меняет |
| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `color_scheme` | `color-scheme` | Нативная светлая/темная схема браузера: `dark` или `light`. |
| `style_preset` | CSS-класс пресета | Сейчас `win95`/`windows95` добавляет `theme-preset-win95`; остальные значения не дают специального класса. |
| `accent` | `--accent` | Главный акцент: активные элементы, кнопки, прогресс, фокус. Только hex `#RGB` или `#RRGGBB`. |
| `bg` | `--bg` | Основной фон приложения. |
| `panel` | `--panel` | Основные карточки и поверхности. |
| `panel_2` | `--panel-2` | Вторичные поверхности. |
| `panel_3` | `--panel-3` | Поверхности повышенной вложенности, dropdown/popover. |
| `border` | `--border` | Обычные границы. |
| `border_strong` | `--border-strong` | Усиленные границы и hover-состояния. |
| `text` | `--text` | Основной текст. |
| `muted` | `--muted` | Вторичный текст. |
| `dim` | `--dim` | Еще более тихий текст и служебные подписи. |
| `danger` | `--danger` | Ошибки и опасные действия. |
| `blue` | `--blue` | Синий вспомогательный цвет. |
| `radius` | `--radius` | Базовый радиус карточек, кнопок и контролов. |
| `font_sans` | `--font-sans` | Основной шрифт интерфейса. |
| `font_logo` | `--font-logo` | Шрифт бренда и заголовка. |
| `font_mono` | `--font-mono` | Моноширинный шрифт. |
| `home_logo_scale_desktop` | `--home-logo-scale-desktop` | Масштаб логотипа на desktop layout, от `50` до `300` процентов. |
| `home_logo_scale_mobile` | `--home-logo-scale-mobile` | Масштаб логотипа на mobile layout, от `50` до `300` процентов. |
| `home_logo_scale` | `--home-logo-scale` | Legacy fallback для старых тем; используется, если desktop/mobile token не задан. |
| `admin_bg` | `--admin-bg` | Фон админ-панели. |
| `admin_surface` | `--admin-surface` | Основные карточки админки. |
| `admin_surface_2` | `--admin-surface-2` | Вторичные поверхности админки. |
| `admin_elev` | `--admin-elev` | Elevated-поверхности админки. |
| `admin_border` | `--admin-border` | Границы админки. |
| `admin_border_strong` | `--admin-border-strong` | Усиленные границы админки. |
| `admin_text` | `--admin-text` | Основной текст админки. |
| `admin_muted` | `--admin-muted` | Вторичный текст админки. |
| `admin_dim` | `--admin-dim` | Тихие подписи админки. |
Если `css_file` не задан, интерфейс полностью строится на токенах и общих стилях. Если `css_file` задан, токены все равно применяются первыми, а CSS темы может уточнить или полностью переопределить внешний вид.
@@ -254,7 +258,8 @@ CSS можно писать для пользовательской части
content: "";
width: 16px;
height: 16px;
background: url("/webapp-theme-assets/neon/icons/spark.png") center / contain no-repeat;
background: url("/webapp-theme-assets/neon/icons/spark.png") center / contain
no-repeat;
}
```
@@ -302,7 +307,7 @@ CSS можно писать для пользовательской части
7. Подберите accent и масштаб логотипа.
В админке можно менять accent и `home_logo_scale` без ручного редактирования JSON. При сохранении backend перепишет `theme.json` в `WEBAPP_THEMES_DIR`, выставит ровно один `default` и сбросит кеш публичных настроек.
В админке можно менять accent, `home_logo_scale_desktop` и `home_logo_scale_mobile` без ручного редактирования JSON. При сохранении backend перепишет `theme.json` в `WEBAPP_THEMES_DIR`, выставит ровно один `default` и сбросит кеш публичных настроек. Старый `home_logo_scale` сохраняется как fallback для уже существующих тем.
8. Добавьте `style.css`, если токенов мало.
@@ -329,7 +334,6 @@ CSS можно писать для пользовательской части
11. Сделайте тему дефолтной.
Есть два способа:
- в админке выбрать тему и сохранить;
- указать `WEBAPP_DEFAULT_THEME=neon` в `.env`, если нужен жесткий override на уровне окружения.