Refactor environment configuration and update documentation for MVP status
- Removed deprecated Telegram user ID configuration from `.env.example` and added a new setting for admin Telegram user IDs. - Updated `CLAUDE.md` to reflect the current MVP status, detailing completed features and testing coverage. - Enhanced `README.md` with quick start instructions for Docker setup and clarified project status. - Revised API design documentation to include updated error handling and request/response structures. - Improved frontend documentation to outline the project structure and technologies used.
This commit is contained in:
+106
-78
@@ -1,119 +1,147 @@
|
||||
# Frontend
|
||||
|
||||
SPA на **React 19 + Vite + TypeScript**. Общается с бэком по REST (JWT Bearer) и получает
|
||||
живые обновления по SignalR. Типы API генерируются из OpenAPI-схемы бэкенда.
|
||||
живые обновления по SignalR.
|
||||
|
||||
> **Раздача из единого контейнера.** В проде собранный фронт (`dist/`) кладётся в `wwwroot`
|
||||
> ASP.NET Core и раздаётся тем же приложением (SPA-fallback на `index.html`). Фронт и бек — один
|
||||
> origin, база API — относительный `/api`, SignalR — `/hubs/panel`. В dev Vite-сервер проксирует
|
||||
> `/api` и `/hubs` на бэкенд. Детали упаковки — [architecture.md](architecture.md#развёртывание-единый-контейнер-приложения).
|
||||
> `/api` и `/hubs` на бэкенд (`vite.config.ts`, цель — `http://localhost:8080` по умолчанию,
|
||||
> переопределяется `VITE_API_TARGET`). Детали упаковки — [architecture.md](architecture.md#развёртывание-единый-контейнер-приложения).
|
||||
|
||||
## Стек
|
||||
|
||||
| Задача | Выбор |
|
||||
| ----------------- | --------------------------------------- |
|
||||
| Сборка/dev | Vite |
|
||||
| Язык | TypeScript (strict) |
|
||||
| Данные с сервера | TanStack Query |
|
||||
| Роутинг | TanStack Router (типобезопасный) |
|
||||
| UI-компоненты | shadcn/ui + Tailwind CSS v4 |
|
||||
| Иконки | lucide-react |
|
||||
| Клиентский стейт | Zustand (auth, тема) |
|
||||
| Формы | react-hook-form + zod |
|
||||
| Realtime | @microsoft/signalr |
|
||||
| Графики | Recharts |
|
||||
| QR-коды | qrcode.react |
|
||||
| Типы API | openapi-typescript / orval (codegen) |
|
||||
| i18n | react-i18next (RU + EN) |
|
||||
| Пакетный менеджер | pnpm |
|
||||
| Задача | Выбор |
|
||||
| ----------------- | ------------------------------------------------------------ |
|
||||
| Сборка/dev | Vite (Rolldown-based) |
|
||||
| Язык | TypeScript (strict) |
|
||||
| Данные с сервера | TanStack Query |
|
||||
| Роутинг | TanStack Router (файловый, `src/routes/`, кодогенерация `routeTree.gen.ts`) |
|
||||
| UI-компоненты | shadcn-стиль поверх Radix (`@radix-ui/react-*`) + Tailwind CSS v4 |
|
||||
| Иконки | lucide-react |
|
||||
| Клиентский стейт | Zustand — только auth-стор (`features/auth/store.ts`); тема — React Context + localStorage, не Zustand |
|
||||
| Формы | react-hook-form + zod |
|
||||
| Realtime | @microsoft/signalr |
|
||||
| QR-коды | qrcode.react (рендерит QR из готовой строки на клиенте) |
|
||||
| Типы API | openapi-typescript (`pnpm gen:api`) — генерирует `schema.gen.ts` для сверки; фичи импортируют руками написанный `shared/api/types.ts` |
|
||||
| i18n | react-i18next (RU + EN) |
|
||||
| Линт | oxlint (не ESLint) |
|
||||
| Пакетный менеджер | pnpm |
|
||||
|
||||
Установлены, но **не используются в MVP**: `recharts` (админская статистика — карточки с цифрами,
|
||||
без графиков), `@tanstack/react-table` (админские таблицы написаны руками, без TanStack Table).
|
||||
Оставлены как задел, если/когда понадобятся графики трафика или сложные таблицы с сортировкой.
|
||||
|
||||
## Структура
|
||||
|
||||
Фактическая структура (`frontend/src/`):
|
||||
|
||||
```
|
||||
frontend/
|
||||
src/
|
||||
app/ # провайдеры (Query, Router, Auth, Theme), корневой layout
|
||||
routes/ # маршруты TanStack Router (login, dashboard, configs, instructions, admin/*)
|
||||
main.tsx # точка входа: QueryClientProvider > ThemeProvider > ToastProvider > RealtimeProvider > RouterProvider
|
||||
router.tsx # createRouter из routeTree.gen.ts
|
||||
routeTree.gen.ts # сгенерировано @tanstack/router-plugin, не редактируется руками
|
||||
index.css # Tailwind v4 (@import), без отдельной styles/-папки
|
||||
routes/ # файловый роутинг TanStack Router
|
||||
__root.tsx # шапка (лого, нав, переключатель языка/темы), Outlet
|
||||
index.tsx, login.tsx, register.tsx, dashboard.tsx, instructions.tsx, settings.tsx
|
||||
admin.tsx # layout админки (вкладки) + Outlet
|
||||
admin/
|
||||
index.tsx, activation.tsx, users.tsx, roles.tsx, nodes.tsx, apps.tsx, audit.tsx
|
||||
features/
|
||||
auth/ # формы, хуки useLogin/useRegister, стор авторизации
|
||||
configs/ # список/создание/редактирование/детали конфигов, QR, подписка
|
||||
instructions/ # страница инструкций + каталог приложений по ОС
|
||||
nodes/ # (admin) управление нодами
|
||||
apps/ # (admin) CRUD каталога приложений
|
||||
admin/ # пользователи, роли, аудит, статистика
|
||||
theme/ # провайдер темы (light/dark/system) + переключатель
|
||||
auth/ # api.ts, store.ts (zustand), guards.ts, LoginForm.tsx, RegisterForm.tsx
|
||||
activation/ # api.ts, ActivationGate.tsx (экран "запросить активацию")
|
||||
configs/ # api.ts, ConfigCard.tsx, CreateConfigDialog.tsx, SubscriptionCard.tsx
|
||||
apps/ # api.ts, AppsCatalog.tsx (для /instructions)
|
||||
telegram/ # api.ts, TelegramLoginButton.tsx
|
||||
settings/ # ChangePasswordForm.tsx, TelegramLinkCard.tsx, DeleteAccountSection.tsx
|
||||
admin/
|
||||
users/, roles/, activation/, nodes/, inbounds/, apps/, audit/, stats/ # api.ts + диалоги CRUD в каждой
|
||||
theme/
|
||||
ThemeProvider.tsx # React Context + localStorage (`pnv-theme`), НЕ zustand
|
||||
shared/
|
||||
api/ # http-клиент (fetch + JWT/refresh), сгенерированные типы, query-хуки
|
||||
realtime/ # инициализация SignalR, подписки → инвалидация Query-кэша
|
||||
ui/ # обёртки над shadcn/ui, общие компоненты
|
||||
lib/ # утилиты, форматирование (байты, даты)
|
||||
config/ # env, константы
|
||||
styles/ # tailwind, темы
|
||||
api/
|
||||
client.ts # apiRequest(), HttpError, access-token в памяти модуля, 401 → silent refresh → повтор
|
||||
types.ts # руками написанные типы ответов/запросов (сверены со schema.gen.ts)
|
||||
schema.gen.ts # генерируется pnpm gen:api, не импортируется напрямую фичами
|
||||
lib/
|
||||
i18n.ts, cn.ts, format.ts
|
||||
realtime/
|
||||
connection.ts, RealtimeProvider.tsx
|
||||
ui/
|
||||
button.tsx, input.tsx, label.tsx, card.tsx, dialog.tsx, select.tsx, badge.tsx,
|
||||
progress.tsx, toast-store.tsx, toaster.tsx
|
||||
index.html
|
||||
vite.config.ts
|
||||
package.json
|
||||
```
|
||||
|
||||
Нет `app/`-папки с провайдерами (они прямо в `main.tsx`), нет `shared/config/` (переменные окружения
|
||||
читаются точечно через `import.meta.env`), нет `styles/` (один `index.css` с Tailwind).
|
||||
|
||||
## Дизайн / UX
|
||||
|
||||
- **Тема оформления**: светлая и тёмная (переключатель в шапке; вариант «системная»). Реализация —
|
||||
Tailwind `dark` (класс на `html`) + shadcn/ui; выбор сохраняется (localStorage).
|
||||
- **Современный и чистый вид**: shadcn/ui + Tailwind, адаптивность, аккуратная типографика.
|
||||
- **Дашборд пользователя**: карточки конфигов (протокол, локация, трафик прогресс-баром, срок, статус),
|
||||
быстрые действия (копировать ссылку, показать QR, перевыпустить, отозвать) + карточка «Общая подписка»
|
||||
(агрегированная ссылка/QR со всеми конфигами). Для неактивированного — экран «запросить активацию».
|
||||
- **Создание конфига**: выбор локации/inbound (по `DisplayName`) + метка + лимит устройств →
|
||||
мгновенная выдача ссылки + QR + ссылка на страницу инструкций.
|
||||
- **Страница инструкций** (`/instructions`): общие шаги «как импортировать ссылку/QR» + каталог
|
||||
приложений (`GET /api/apps`), **сгруппированный по ОС**; клик по приложению открывает ссылку на
|
||||
скачивание. Данные ведёт админ (каталог `ClientApp`).
|
||||
- **Редактирование конфига**: изменить метку и лимит устройств.
|
||||
- **Настройки аккаунта**: смена пароля, привязка/отвязка Telegram, **удаление аккаунта** (с подтверждением).
|
||||
- **Админка**: таблицы (TanStack Table) с пагинацией/фильтрами для нод, пользователей, конфигов, ролей,
|
||||
каталога приложений, журнала аудита; очередь запросов активации; графики трафика (Recharts).
|
||||
Блокировка пользователя — с подтверждением (гасит VPN). Управление приложениями (название, ссылка, ОС, вкл/выкл).
|
||||
- **Состояния**: скелетоны при загрузке, аккуратные пустые состояния и toasts на ошибки/успех.
|
||||
- **Тема оформления**: светлая/тёмная/системная (переключатель в шапке). Реализация — класс `dark` на
|
||||
`<html>` + Tailwind, выбор в `localStorage` (`ThemeProvider`, React Context).
|
||||
- **Дашборд пользователя** (`/dashboard`): карточки конфигов (протокол, локация, использованный
|
||||
трафик, статус), кнопки на карточке — показать ссылку/QR (запрашивает `GET .../link` по клику,
|
||||
не сразу при создании), перевыпустить, отозвать; отдельная карточка «Общая подписка». Для
|
||||
неактивированного — `ActivationGate` вместо дашборда.
|
||||
- **Создание конфига**: диалог — выбор инбаунда (по `displayName`) + метка + лимит устройств.
|
||||
После успеха карточка конфига появляется в списке; ссылку/QR пользователь открывает отдельно.
|
||||
- **Страница инструкций** (`/instructions`): статичные шаги + каталог приложений (`GET /api/apps`),
|
||||
сгруппированный по ОС; клик по приложению открывает ссылку на скачивание.
|
||||
- **Настройки** (`/settings`): смена пароля, привязка/отвязка Telegram (`TelegramLinkCard`),
|
||||
удаление аккаунта с подтверждением (`DeleteAccountSection`).
|
||||
- **Админка** (`/admin/*`): вкладки — обзор (карточки статистики, без графиков), запросы активации,
|
||||
пользователи, роли, ноды (+ публикация инбаундов), приложения, аудит. Таблицы — обычные `<table>`,
|
||||
без TanStack Table. Блокировка пользователя — с подтверждением.
|
||||
- **Состояния**: `isLoading`/`isError`/пусто различаются явно везде (ошибка сети не выглядит как
|
||||
«пусто» — паттерн закреплён после находки в `ActivationGate`, распространён на все admin-списки).
|
||||
|
||||
## Работа с API
|
||||
|
||||
- HTTP-клиент оборачивает `fetch`: подставляет access-token, при 401 — прозрачно обновляет через
|
||||
refresh-cookie и повторяет запрос; при неуспехе — разлогин.
|
||||
- Все запросы/мутации — через TanStack Query (ключи по фичам, инвалидация после мутаций).
|
||||
- Типы ответов/запросов — из codegen по OpenAPI (никакого ручного дублирования DTO).
|
||||
- `shared/api/client.ts`: `apiRequest<T>()` оборачивает `fetch`, подставляет access-token в
|
||||
`Authorization`; на `401` — один прозрачный `POST /api/auth/refresh` и повтор запроса; при неуспехе
|
||||
рефреша — `setUnauthorizedHandler` колбэк (разлогин).
|
||||
- Запросы/мутации — через TanStack Query, ключи по фиче, инвалидация после мутаций.
|
||||
- Типы — из `shared/api/types.ts` (см. таблицу стека выше и [tech-stack.md](tech-stack.md)).
|
||||
|
||||
## Авторизация на клиенте
|
||||
|
||||
- **Access-token** — в памяти (не в localStorage), кладётся в `Authorization: Bearer`.
|
||||
- **Refresh-token** — httpOnly Secure cookie (JS не читает), ротация на сервере.
|
||||
- Стор авторизации (Zustand) хранит профиль/роли/`isActivated`; guard-маршруты по роли (`admin` vs обычный)
|
||||
и по активации (неактивированного ведём на экран «запросить активацию»).
|
||||
|
||||
- **Вход — по username** (email не используется). «Забыли пароль?» ведёт: при привязанном
|
||||
Telegram — восстановление через бота; иначе — подсказка обратиться к админу.
|
||||
- **Баннер привязки Telegram**: пока Telegram не привязан, показываем настойчивый, но не блокирующий
|
||||
баннер/напоминание — это единственный self-service способ восстановить доступ. Настройки: смена пароля.
|
||||
- **Access-token** — в памяти (модуль `client.ts`, не React state и не localStorage) — переживает
|
||||
ре-рендеры, но не пережить reload (тогда его молча восстанавливает silent refresh по cookie).
|
||||
- **Refresh-token** — httpOnly Secure cookie (`pnv_refresh_token`, `Path=/api/auth`), JS его не видит.
|
||||
- Стор авторизации (`features/auth/store.ts`, Zustand) хранит текущего пользователя/`isActivated`.
|
||||
Guard-хуки `useRequireAuth()`/`useRequireGuest()`/`useRequireAdmin()` (`features/auth/guards.ts`)
|
||||
редиректят через `useNavigate()` в `useEffect`, если условие не выполнено.
|
||||
- **Вход — по username** (email не используется). «Забыли пароль?»: при привязанном Telegram —
|
||||
вход через бота и смена пароля в настройках; иначе — обратиться к админу.
|
||||
|
||||
### Вход и привязка через Telegram
|
||||
- **«Войти через Telegram»**: `POST /api/auth/telegram/login-request` → показать deep-link/QR на
|
||||
бота, затем ждать подтверждения (поллинг `GET …/login-request/{id}` или событие SignalR). При
|
||||
`Approved` — сохранить access, refresh уже в cookie, редирект в панель.
|
||||
- **«Привязать Telegram»** (в настройках, для вошедшего): `POST /api/auth/telegram/link-token` →
|
||||
показать deep-link/QR; статус привязки обновить по факту (поллинг/SignalR). Отвязка — `unlink`.
|
||||
- QR для deep-link — `qrcode.react`.
|
||||
- **«Войти через Telegram»** (`TelegramLoginButton`, на `/login`): `POST .../login-request` →
|
||||
показывает `deepLink` как QR (`qrcode.react`), поллит `GET .../login-request/{id}` до
|
||||
`Approved`/`Rejected`/`Expired`. При `Approved` — сохраняет `accessToken` в память клиента
|
||||
(refresh уже пришёл в cookie от бэка) и редиректит в панель.
|
||||
- **«Привязать Telegram»** (`TelegramLinkCard`, в настройках): `POST .../link-token` → QR из
|
||||
`deepLink`; статус обновляется поллингом. Отвязка — `POST .../unlink`.
|
||||
|
||||
## Realtime
|
||||
|
||||
- Одно SignalR-подключение к `/hubs/panel` с JWT; реконнект с бэкоффом.
|
||||
- Обработчики событий (`configTrafficUpdated`, `configStatusChanged`, `nodeStatusChanged`) точечно
|
||||
обновляют/инвалидируют кэш TanStack Query — UI обновляется без перезагрузки.
|
||||
- Одно SignalR-подключение к `/hubs/panel` с JWT (`RealtimeProvider`, `shared/realtime/connection.ts`).
|
||||
- Обработчики `configTrafficUpdated`/`configStatusChanged`/`nodeStatusChanged`/`activationRequested`/
|
||||
`userActivated` точечно инвалидируют/обновляют кэш TanStack Query — UI обновляется без перезагрузки.
|
||||
|
||||
## Скрипты (ожидаемые)
|
||||
## Скрипты
|
||||
|
||||
```bash
|
||||
pnpm dev # dev-сервер Vite
|
||||
pnpm build # прод-сборка
|
||||
pnpm preview # предпросмотр сборки
|
||||
pnpm lint # ESLint
|
||||
pnpm typecheck # tsc --noEmit
|
||||
pnpm gen:api # генерация типов из OpenAPI-схемы бэкенда
|
||||
pnpm build # tsc -b && vite build (прод-сборка)
|
||||
pnpm preview # предпросмотр prod-сборки
|
||||
pnpm lint # oxlint
|
||||
pnpm typecheck # tsc -b
|
||||
pnpm gen:api # openapi-typescript по /openapi/v1.json живого бэкенда -> schema.gen.ts
|
||||
```
|
||||
|
||||
`pnpm gen:api` требует запущенный бэкенд на `http://localhost:8080` (или `docker compose up`).
|
||||
|
||||
Reference in New Issue
Block a user