Files
bedolaga-cabinet/README.md
Egor ac89343cc1 Dev (#394)
* Make SBP default and first top-up option (#389)

* fix(connection): happ cryptolink flow + ui fixes (#385)

* fix(connection): respect happ_cryptolink mode

- read connect_mode from connection-link endpoint
- auto-redirect to happ cryptolink flow when mode is HAPP_CRYPTOLINK
- keep installation guide behavior for other modes

* docs(readme): fix source build step 2

- use compose service name for build/create commands
- copy static files from created compose container
- remove compose container via docker compose rm

* fix(connection): handle mode and ws path

- treat any non-guide connect mode as direct happ redirect
- wait for connection-link response before rendering installation guide
- build websocket URL from VITE_API_URL (supports /api and absolute URLs)

* docs(readme): fix docker cp static path

- copy /usr/share/nginx/html contents via html/.
- prevent nested /srv/cabinet/html deployment and 404 on root

* fix(connection): keep guide, use happ links

- restore guide page behavior for /connection (no auto redirect)
- use happ cryptolink URL in /connection/qr when happ crypt mode is active
- replace subscription page connection URL with happ cryptolink from connection-link API

* fix(connection): avoid wrong redirect flow

- force happ scheme deeplink in HAPP_CRYPTOLINK mode for guide connect buttons
- use cabinet redirect page only inside Telegram Mini App
- open deeplink directly in regular browsers

* fix(connection): enforce happ cryptolink

- prioritize backend crypt deeplink fields in HAPP_CRYPTOLINK mode
- fallback to local crypt4/crypt3 generation from subscription URL when backend returns plain happ://sub...
- apply same resolution for guide open action, qr page source and subscription page link display/copy

* fix(subscription): truncate long connect link

- render connection URL in a single line with ellipsis on /subscriptions
- preserve full URL in tooltip for easier manual copy
- keep copy button behavior unchanged

* fix(connection): build cryptolink from happ sub

- allow cryptolink fallback generation from happ://sub... URLs in addition to http(s)
- prevent plain happ://sub links from leaking into UI in HAPP_CRYPTOLINK mode

* fix: allow saving 0% period discount in promo groups

Changed condition from percent > 0 to percent >= 0 so that
admins can explicitly set 0% discount for a period. Previously
0% entries were silently dropped and not sent to the backend.

* fix: always send period_discounts in promo group updates

When all period discounts were removed, the frontend sent undefined
(field absent from JSON), so the backend never cleared them.
Now always sends the field - empty object {} to clear, or populated
object to update.

---------

Co-authored-by: zavul0nn <34007368+zavul0nn@users.noreply.github.com>
Co-authored-by: Dxnil <62987903+D4nilKO@users.noreply.github.com>
2026-04-15 14:04:39 +03:00

338 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bedolaga Cabinet - Web Interface
Веб-интерфейс личного кабинета для VPN бота на базе [Remnawave Bedolaga Telegram Bot V3.0.0+](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot).
React + Vite + TypeScript | Авторизация через Telegram | Мультиязычность (EN/RU) | Адаптивный дизайн
## Требования
- Docker и Docker Compose
- Запущенный backend бота с включенным Cabinet API
- Обратный прокси (Caddy / Nginx / Traefik)
## Архитектура
```
Браузер → Caddy/Nginx → /api/* → remnawave_bot:8080 (backend API)
→ /* → /srv/cabinet (статика frontend)
```
Frontend — это статические файлы (HTML, JS, CSS). Обратный прокси выполняет две задачи:
1. Раздает статику frontend
2. Проксирует `/api/*` запросы на backend бота (с удалением префикса `/api`)
## Установка
### Шаг 1. Настройка backend бота
В `.env` файле бота добавьте:
```env
# Включить Cabinet API
CABINET_ENABLED=true
# JWT секрет (сгенерируйте случайную строку: openssl rand -hex 32)
CABINET_JWT_SECRET=your_random_secret_key_here
# Разрешенные origins для CORS (домен, на котором будет кабинет)
CABINET_ALLOWED_ORIGINS=https://cabinet.example.com
```
Перезапустите бота после изменений.
### Шаг 2. Получение frontend файлов
#### Вариант A: Готовый Docker образ (рекомендуется)
```bash
docker pull ghcr.io/bedolaga-dev/bedolaga-cabinet:latest
```
Извлеките собранные файлы из образа:
```bash
# Создать временный контейнер и скопировать статику
docker create --name tmp_cabinet ghcr.io/bedolaga-dev/bedolaga-cabinet:latest
mkdir -p ./cabinet-dist
docker cp tmp_cabinet:/usr/share/nginx/html/. ./cabinet-dist/
docker rm tmp_cabinet
```
#### Вариант B: Сборка из исходников
```bash
git clone https://github.com/BEDOLAGA-DEV/bedolaga-cabinet.git
cd bedolaga-cabinet
cp .env.example .env
```
Отредактируйте `.env`:
```env
VITE_API_URL=/api
VITE_TELEGRAM_BOT_USERNAME=your_bot_username
VITE_APP_NAME=My VPN
VITE_APP_LOGO=V
```
Соберите и извлеките:
```bash
docker compose build cabinet-frontend
docker compose create cabinet-frontend
mkdir -p ./cabinet-dist
docker cp cabinet_frontend:/usr/share/nginx/html/. ./cabinet-dist/
docker compose rm -sf cabinet-frontend
```
### Шаг 3. Размещение файлов на сервере
Скопируйте содержимое `cabinet-dist` в директорию, которую будет раздавать ваш прокси:
```bash
# Создайте директорию на сервере
sudo mkdir -p /srv/cabinet
# Скопируйте файлы (с локальной машины или напрямую на сервере)
sudo cp -r ./cabinet-dist/* /srv/cabinet/
```
### Шаг 4. Настройка обратного прокси
#### Caddy (рекомендуется)
Caddy автоматически получает и обновляет SSL сертификаты.
```caddyfile
https://cabinet.example.com {
encode gzip zstd
# API запросы → backend бота (удаляет /api префикс)
handle /api/* {
uri strip_prefix /api
reverse_proxy remnawave_bot:8080
}
# Frontend статика
handle {
root * /srv/cabinet
try_files {path} /index.html
file_server
# Кэширование статических ассетов (JS, CSS, шрифты, изображения)
@static path *.js *.css *.woff *.woff2 *.ttf *.ico *.png *.jpg *.jpeg *.svg *.webp *.gif
header @static Cache-Control "public, max-age=31536000, immutable"
# HTML без кэша (для обновлений SPA)
@html path *.html /
header @html Cache-Control "no-cache, must-revalidate"
}
}
```
> **Примечание:** `remnawave_bot:8080` — имя контейнера бота в Docker сети.
> Если Caddy запущен на хосте, а не в Docker, используйте `localhost:8080` или IP сервера.
#### Nginx
```nginx
server {
listen 443 ssl http2;
server_name cabinet.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
root /srv/cabinet;
index index.html;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml image/svg+xml;
# API запросы → backend бота
location /api/ {
rewrite ^/api/(.*) /$1 break;
proxy_pass http://remnawave_bot:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Кэширование статических ассетов
location ~* \.(?:js|css|woff2?|ttf|ico|png|jpe?g|svg|webp|gif)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# SPA fallback
location / {
try_files $uri /index.html;
add_header Cache-Control "no-cache, must-revalidate";
}
}
```
### Шаг 5. Запуск и проверка
```bash
# Перезагрузите Caddy
docker exec <caddy_container> caddy reload --config /etc/caddy/Caddyfile
# Или Nginx
docker exec <nginx_container> nginx -s reload
```
Откройте `https://cabinet.example.com` в браузере.
## Альтернативная установка: Docker контейнер с nginx внутри
Если вы не хотите раздавать статику напрямую, можно проксировать запросы на Docker контейнер cabinet.
### docker-compose.yml
```yaml
services:
cabinet-frontend:
image: ghcr.io/bedolaga-dev/bedolaga-cabinet:latest
container_name: cabinet_frontend
restart: unless-stopped
# Порты НЕ пробрасываем — доступ только через Docker сеть
networks:
- bot_network
networks:
bot_network:
external: true
name: remnawave-bedolaga-telegram-bot_bot_network # Сеть вашего бота
```
### Caddyfile
```caddyfile
https://cabinet.example.com {
encode gzip zstd
# API запросы → backend бота
handle /api/* {
uri strip_prefix /api
reverse_proxy remnawave_bot:8080
}
# Frontend → nginx контейнер (порт 80 внутри Docker сети)
handle {
reverse_proxy cabinet_frontend:80
}
}
```
> В этом варианте кэшированием статики занимается nginx внутри контейнера.
## Переменные окружения
### Build-time (вшиваются в JS при сборке)
| Переменная | Описание | По умолчанию |
|---|---|---|
| `VITE_API_URL` | Путь к API (`/api` или полный URL) | `/api` |
| `VITE_TELEGRAM_BOT_USERNAME` | Username Telegram бота (без @) | — |
| `VITE_APP_NAME` | Название в шапке и вкладке браузера | `Cabinet` |
| `VITE_APP_LOGO` | Текст логотипа (1-2 символа) | `V` |
### Runtime (только для Docker контейнера)
| Переменная | Описание | По умолчанию |
|---|---|---|
| `CABINET_PORT` | Порт контейнера на хосте | `3020` |
### Backend бота (.env бота)
| Переменная | Описание | По умолчанию |
|---|---|---|
| `CABINET_ENABLED` | Включить Cabinet API | `false` |
| `CABINET_JWT_SECRET` | Секретный ключ для JWT | `BOT_TOKEN` |
| `CABINET_ALLOWED_ORIGINS` | CORS origins (через запятую) | — |
| `CABINET_ACCESS_TOKEN_EXPIRE_MINUTES` | Время жизни access token | `15` |
| `CABINET_REFRESH_TOKEN_EXPIRE_DAYS` | Время жизни refresh token | `7` |
## Устранение проблем
### CORS ошибка в консоли браузера
Домен кабинета не добавлен в `CABINET_ALLOWED_ORIGINS` в `.env` бота. Добавьте и перезапустите бота.
### API возвращает HTML вместо JSON
Прокси настроен неправильно — запросы `/api/*` попадают на frontend вместо backend. Проверьте порядок блоков `handle` в Caddyfile (API должен быть первым).
### 502 Bad Gateway
1. Backend бота не запущен — проверьте `docker ps`
2. Контейнеры в разных Docker сетях — проверьте и подключите:
```bash
# Проверить сети контейнера
docker inspect <container> --format='{{json .NetworkSettings.Networks}}' | python3 -m json.tool
# Подключить к нужной сети
docker network connect <network_name> <container_name>
```
3. Неправильное имя сервиса в прокси — проверьте через:
```bash
docker exec <caddy_container> wget -qO- http://remnawave_bot:8080/health
```
### Telegram авторизация не работает
1. `VITE_TELEGRAM_BOT_USERNAME` должен быть без `@`
2. Домен кабинета добавлен в BotFather → Bot Settings → Domain
### Белый экран / SPA не работает
Прокси не настроен на fallback к `index.html`. Убедитесь что `try_files {path} /index.html` (Caddy) или `try_files $uri /index.html` (Nginx) присутствует в конфигурации.
## Разработка
```bash
git clone https://github.com/BEDOLAGA-DEV/bedolaga-cabinet.git
cd bedolaga-cabinet
npm install
cp .env.example .env
# Отредактируйте .env
npm run dev
```
Dev-сервер запустится на `http://localhost:5173` с автоматическим проксированием `/api` на `localhost:8080`.
## Структура проекта
```
bedolaga-cabinet/
├── src/
│ ├── api/ # API клиенты (axios)
│ ├── components/ # React компоненты (UI kit)
│ ├── contexts/ # React контексты (auth, theme)
│ ├── hooks/ # Custom hooks
│ ├── locales/ # Переводы (i18n)
│ ├── pages/ # Страницы приложения
│ ├── types/ # TypeScript типы
│ └── utils/ # Утилиты
├── public/ # Статические файлы
├── Dockerfile # Multi-stage сборка (node → nginx)
├── docker-compose.yml # Docker Compose для сборки
├── nginx.conf # Nginx конфиг внутри контейнера
└── .env.example # Пример переменных окружения
```
## Связанные проекты
- [Remnawave Bedolaga Telegram Bot](https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot) — Backend бота
- [Bedolaga Chat](https://t.me/+wTdMtSWq8YdmZmVi) — Чат поддержки
## Контакты
- Telegram: [@fringg](https://t.me/fringg)
- Telegram: [@pedzeo](https://t.me/pedzeo)
- Чат: [Bedolaga Chat](https://t.me/+wTdMtSWq8YdmZmVi)