c0mrade 7f68e2cd5e fix(theme): readable text on any operator palette + admin consistency pass
Контраст-аудит (WCAG-обход всех текстовых узлов, 4 палитры × юзер+админ
страницы) показал системные провалы читаемости: вторичный текст 1.5-2.5
на светлой/кастомных темах, статусные цвета 1.3-1.6, белый текст на
светлых акцентах 1.9.

Корневые фиксы:
- useThemeColors: контраст-кламп производных серых токенов — dark-400/
  champagne-600 (вторичный текст) держат >=5.0 к поверхности, dark-500/
  champagne-500 (подсказки) >=3.8; палитры с достаточным контрастом
  рендерятся байт-в-байт как раньше
- --color-on-accent/-success/-warning/-error: чёрный или белый текст
  по фактической яркости цвета оператора; text-white на accent-заливках
  заменён на text-on-accent (135 мест + variant примитива Button +
  .btn-primary)
- .light: статусные текстовые оттенки *-300/400 ремапятся на тёмные
  *-700 (жёлтая «Админка» 1.26 -> 3.8+, зелёные суммы 1.4 -> 3.4+,
  бейджи состояний)

Замер после: операторская тёмная палитра — 0 нарушений (всё >=4.5),
худшие точки остальных подняты с 1.26-1.6 до 3.2+.

Консистентность:
- заголовки админ-страниц приведены к text-xl font-bold text-dark-100
  (были 18/20/24/30px и 600/700 вперемешку)
- «Создать FAQ»: warning-500/80+белый -> warning-500+text-on-warning
- formatTraffic: единицы из i18n (убран микс «GB»/«ГБ» на одном экране)
- categoryCanvas -> categoryCANVAS в локалях (битый ключ на настройках)
- «Управление сквадами» -> «Управление локациями» (юзерский экран)
- мин-макс сумм на выборе метода: dark-600 -> dark-400
- неактивные пункты нижнего нава: dark-500 -> dark-400
2026-06-11 15:49:17 +03:00
2026-01-15 19:29:46 +03:00
2026-05-28 15:57:34 +00:00
2026-01-14 23:46:14 +03:00
2026-04-15 14:04:39 +03:00
2026-01-15 19:20:17 +03:00

Bedolaga Cabinet - Web Interface

Веб-интерфейс личного кабинета для VPN бота на базе Remnawave Bedolaga Telegram Bot V3.0.0+.

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 файле бота добавьте:

# Включить 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 образ (рекомендуется)

docker pull ghcr.io/bedolaga-dev/bedolaga-cabinet:latest

Извлеките собранные файлы из образа:

# Создать временный контейнер и скопировать статику
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: Сборка из исходников

git clone https://github.com/BEDOLAGA-DEV/bedolaga-cabinet.git
cd bedolaga-cabinet
cp .env.example .env

Отредактируйте .env:

VITE_API_URL=/api
VITE_TELEGRAM_BOT_USERNAME=your_bot_username
VITE_APP_NAME=My VPN
VITE_APP_LOGO=V

Соберите и извлеките:

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 в директорию, которую будет раздавать ваш прокси:

# Создайте директорию на сервере
sudo mkdir -p /srv/cabinet

# Скопируйте файлы (с локальной машины или напрямую на сервере)
sudo cp -r ./cabinet-dist/* /srv/cabinet/

Шаг 4. Настройка обратного прокси

Caddy (рекомендуется)

Caddy автоматически получает и обновляет SSL сертификаты.

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

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. Запуск и проверка

# Перезагрузите 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

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

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 сетях — проверьте и подключите:
    # Проверить сети контейнера
    docker inspect <container> --format='{{json .NetworkSettings.Networks}}' | python3 -m json.tool
    
    # Подключить к нужной сети
    docker network connect <network_name> <container_name>
    
  3. Неправильное имя сервиса в прокси — проверьте через:
    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) присутствует в конфигурации.

Разработка

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       # Пример переменных окружения

Связанные проекты

Контакты

Description
No description provided
Readme 8.2 MiB
Languages
TypeScript 98.2%
CSS 1.2%
JavaScript 0.3%
HTML 0.2%