mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
* 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>
338 lines
12 KiB
Markdown
338 lines
12 KiB
Markdown
# 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)
|