Enhance documentation with new features: added dark/light/system theme support, instructions page, and application catalog. Updated API and domain model for app management and automatic migrations on startup. Improved frontend structure with new routes and features for user instructions and app management.

This commit is contained in:
Leonid Pershin
2026-07-01 22:38:01 +03:00
parent d8930409fe
commit 1a8d33efa3
229 changed files with 9226 additions and 20 deletions
+12 -2
View File
@@ -170,8 +170,11 @@ POST /api/configs
сразу активирована и с ролью `admin`.
- **Telegram id админов** (`AdminSeed__TelegramUserIds`) — авторизуют админ-действия в боте и
адресуют уведомления (например, запросы на активацию).
- **Каталог приложений** (`ClientApp`): если таблица пуста — сидируется из
[`seed/client-apps.json`](../seed/client-apps.json) (стартовый набор клиентов по ОС). Дальше — правки через админ-CRUD.
Сидинг не перезаписывает существующие данные; смена пароля админа после первого старта — через приложение.
Сидинг не перезаписывает существующие данные. Принудительной смены сид-пароля при первом входе
**нет** — задавайте сильный `AdminSeed__Password` сразу; сменить пароль можно в приложении.
## RBAC — динамические роли и активация
@@ -228,11 +231,18 @@ PostgreSQL:
2. `dotnet sdk``dotnet publish` Api; статика фронта копируется в `wwwroot`.
3. `dotnet aspnet` runtime — финальный образ запускает Api.
- **docker-compose**: сервис `app` (этот образ) + сервис `db` (PostgreSQL). Всё приложение — в `app`.
- Миграции применяются на старте (dev) / отдельным шагом (prod).
- **TLS — внешний**: HTTPS терминирует внешний прокси/шлюз (nginx/Traefik/cloud LB администратора),
вне нашего compose; `app` внутри отдаёт HTTP. Приложение доверяет `X-Forwarded-Proto/For` через
`ForwardedHeaders`-middleware — иначе Secure-cookie и определение схемы за прокси работать не будут.
Отдельный nginx/Caddy в compose **не** вводим.
- **Миграции**: применяются **автоматически на старте** приложения (в MVP; при масштабировании на
несколько инстансов — вынести в отдельный шаг/джобу).
- Конфигурация через `appsettings.{Env}.json` + переменные окружения / secrets (строка подключения,
JWT-ключ, ключ шифрования секретов, `Telegram:BotToken`, `PublicSiteUrl`).
```
[ внешний прокси/шлюз: TLS termination ] ← HTTPS, вне нашего compose
│ HTTP + X-Forwarded-*
┌────────────────── docker-compose ──────────────────┐
│ app (единый образ) db (postgres) │
│ ├─ REST /api └─ том с данными │
+1
View File
@@ -24,6 +24,7 @@ backend/
Configs/ # CreateVpnConfig, EditVpnConfig, RotateVpnConfig, RevokeVpnConfig, GetMyConfigs, GetConfigLink, GetSubscription, ...
Nodes/ # RegisterNode, SyncNode, ListNodes, ...
Inbounds/ # PublishInbound, ListInbounds, ...
Apps/ # (admin) CRUD каталога ClientApp; GetApps (по ОС) для юзера
Admin/ # ListUsers, BlockUser/UnblockUser, ChangeUserRole, GetStats, Audit, ...
PnvPanel.Infrastructure/
Persistence/
+5 -3
View File
@@ -49,12 +49,12 @@ ClientApp (каталог приложений-клиен
| ----------------- | ------------- | ---------------------------------------------------------- |
| `Id` | `Guid` | PK (внутренний) |
| `NodeId` | `Guid` | FK → Node |
| `RemoteInboundId` | `int` | Id inbound в 3x-ui |
| `RemoteInboundId` | `string` | Id inbound в 3x-ui (ThreeXui.Net отдаёт его как string, не число) |
| `Protocol` | `VpnProtocol` | `Vless` / `Vmess` / `Trojan` / `Shadowsocks` |
| `Remark` | `string` | Метка из 3x-ui |
| `Port` | `int` | |
| `IsPublished` | `bool` | Доступен ли для самообслуживания пользователями |
| `AllowedRoles` | `AppRole[]` (M:N) | Роли, которым разрешено создавать конфиги в этом инбаунде |
| `AllowedRoleIds` | `Guid[]` | Id ролей, которым разрешено создавать конфиги (native PostgreSQL `uuid[]`; не навигация на `AppRole` — тот в Infrastructure/Identity, Domain на него не ссылается) |
| `DisplayName` | `string?` | Витринное имя для пользователя, напр. «Германия (Trojan)» |
| `MaxClients` | `int?` | Лимит клиентов (null = без лимита) |
| `LastSyncAt` | `DateTimeOffset?` | |
@@ -76,7 +76,7 @@ ClientApp (каталог приложений-клиен
| `InboundId` | `Guid` | FK → Inbound |
| `Label` | `string?` | Пользовательская метка («Мой телефон»); редактируется юзером |
| `ClientEmail` | `string` | Уникальный ключ клиента в 3x-ui; схема `pnv_{userIdShort}_{rand}` (уникален в рамках панели, виден владелец) |
| `ClientUuid` | `Guid` | UUID клиента (VLESS/VMess) |
| `ClientExternalId` | `string` | Идентификатор клиента, который вернула панель (UUID для VLESS/VMess, пароль для Trojan/Shadowsocks — ThreeXui.Net отдаёт его как string) |
| `Protocol` | `VpnProtocol` | Денормализовано с inbound |
| `DeviceLimit` | `int` | Лимит одновременных устройств/IP (0 = без лимита); задаёт юзер → `limitIp` в 3x-ui |
| `TrafficLimit` | `TrafficLimit` (VO) | Лимит в байтах (0 = безлимит) |
@@ -146,6 +146,7 @@ ClientApp (каталог приложений-клиен
| `IsEnabled` | `bool` | Показывать пользователям |
Управляется админом (CRUD). Пользователю отдаётся только `IsEnabled`, сгруппировано по `OperatingSystem`.
Стартовый набор сидируется из [`seed/client-apps.json`](../seed/client-apps.json), если таблица пуста.
### AuditLog — журнал действий
Аудит значимых действий (прежде всего админских) для расследований и прозрачности.
@@ -218,6 +219,7 @@ UI **настойчиво напоминает** привязать его (ед
| `Status` | `ActivationStatus` | `Pending` / `Approved` / `Rejected` |
| `DecidedBy` | `Guid?` | Админ, принявший решение |
| `DecidedAt` | `DateTimeOffset?` | |
| `RejectionReason` | `string?` | Комментарий админа при отклонении (опционально) |
| `CreatedAt` | `DateTimeOffset` | |
Инварианты: одновременно не более одного `Pending`-запроса на пользователя; `Approved`
+12 -6
View File
@@ -33,12 +33,15 @@ SPA на **React 19 + Vite + TypeScript**. Общается с бэком по R
frontend/
src/
app/ # провайдеры (Query, Router, Auth, Theme), корневой layout
routes/ # маршруты TanStack Router (login, dashboard, configs, admin/*)
routes/ # маршруты TanStack Router (login, dashboard, configs, instructions, admin/*)
features/
auth/ # формы, хуки useLogin/useRegister, стор авторизации
configs/ # список/создание/детали конфигов, QR, ссылка-подписка
configs/ # список/создание/редактирование/детали конфигов, QR, подписка
instructions/ # страница инструкций + каталог приложений по ОС
nodes/ # (admin) управление нодами
admin/ # пользователи, статистика
apps/ # (admin) CRUD каталога приложений
admin/ # пользователи, роли, аудит, статистика
theme/ # провайдер темы (light/dark/system) + переключатель
shared/
api/ # http-клиент (fetch + JWT/refresh), сгенерированные типы, query-хуки
realtime/ # инициализация SignalR, подписки → инвалидация Query-кэша
@@ -60,12 +63,15 @@ frontend/
быстрые действия (копировать ссылку, показать QR, перевыпустить, отозвать) + карточка «Общая подписка»
(агрегированная ссылка/QR со всеми конфигами). Для неактивированного — экран «запросить активацию».
- **Создание конфига**: выбор локации/inbound (по `DisplayName`) + метка + лимит устройств →
мгновенная выдача ссылки + QR + краткие инструкции по подключению (iOS/Android/Windows).
мгновенная выдача ссылки + QR + ссылка на страницу инструкций.
- **Страница инструкций** (`/instructions`): общие шаги «как импортировать ссылку/QR» + каталог
приложений (`GET /api/apps`), **сгруппированный по ОС**; клик по приложению открывает ссылку на
скачивание. Данные ведёт админ (каталог `ClientApp`).
- **Редактирование конфига**: изменить метку и лимит устройств.
- **Настройки аккаунта**: смена пароля, привязка/отвязка Telegram, **удаление аккаунта** (с подтверждением).
- **Админка**: таблицы (TanStack Table) с пагинацией/фильтрами для нод, пользователей, конфигов, ролей,
журнала аудита; очередь запросов активации; графики трафика (Recharts). Блокировка пользователя — с
подтверждением (гасит VPN).
каталога приложений, журнала аудита; очередь запросов активации; графики трафика (Recharts).
Блокировка пользователя — с подтверждением (гасит VPN). Управление приложениями (название, ссылка, ОС, вкл/выкл).
- **Состояния**: скелетоны при загрузке, аккуратные пустые состояния и toasts на ошибки/успех.
## Работа с API
+9 -5
View File
@@ -6,11 +6,13 @@
- Solution + 4 проекта (Domain/Application/Infrastructure/Api), ссылки по Clean Architecture.
- `Directory.Build.props`, `.editorconfig`, nullable + анализаторы, `dotnet format` в CI.
- EF Core + Npgsql, первая миграция.
- Scaffolding фронта: Vite + React + TS + Tailwind + shadcn/ui + TanStack Query/Router; dev-прокси `/api`,`/hubs` на бэк.
- Scaffolding фронта: Vite + React + TS + Tailwind + shadcn/ui + TanStack Query/Router; **тема light/dark/system** (провайдер + переключатель); i18n (RU/EN); dev-прокси `/api`,`/hubs` на бэк.
- **Единый контейнер**: multi-stage Dockerfile (node → dotnet publish → aspnet), Api раздаёт SPA из
`wwwroot` (fallback на `index.html`); docker-compose `app` + `db` (PostgreSQL).
`wwwroot` (fallback на `index.html`); docker-compose `app` + `db` (PostgreSQL); `ForwardedHeaders`
(TLS — внешним прокси); авто-применение миграций на старте.
- Health-check `/health`, Serilog, OpenAPI + Scalar.
- **Готово, когда**: единый образ поднимается в docker-compose рядом с postgres, отдаёт заглушку SPA и `/health`, есть базовая миграция.
- **CI (GitHub Actions)**: `dotnet build/test` + `pnpm build/lint/typecheck` (без деплоя).
- **Готово, когда**: единый образ поднимается в docker-compose рядом с postgres, отдаёт заглушку SPA и `/health`, есть базовая миграция, CI зелёный.
## M1 — Аутентификация и сидинг
- ASP.NET Core Identity (`AppUser`/`AppRole` c `MaxConfigs`); `DbInitializer`: системные роли
@@ -44,7 +46,8 @@
- Подписка: агрегированная `/sub/{userToken}` (все конфиги) + по конфигу `/sub/{configToken}`;
заголовки `Subscription-Userinfo` / `profile-update-interval`.
- Самоудаление аккаунта (`DELETE /api/auth/me`): отзыв всех конфигов + удаление данных.
- Фронт: дашборд (метки, лимит устройств), создание/редактирование, инструкции подключения, копирование, QR, отзыв, перевыпуск, настройки аккаунта.
- Каталог приложений `ClientApp` (домен + `GET /api/apps` по ОС; сид из `seed/client-apps.json`) + **страница инструкций** на фронте.
- Фронт: дашборд (метки, лимит устройств), создание/редактирование, страница инструкций, копирование, QR, отзыв, перевыпуск, настройки аккаунта.
- **Готово, когда**: активированный юзер создаёт рабочий конфиг в доступном инбаунде в пределах квоты; работает агрегированная подписка.
## M5 — Синхронизация трафика и realtime
@@ -57,6 +60,7 @@
## M6 — Админ-статистика, управление пользователями, аудит
- ListUsers, BlockUser (→ отключение конфигов в 3x-ui) / UnblockUser, ChangeUserRole, ResetUserPassword (без привязки TG), GetUserConfigs, force-revoke, GetStats.
- `AuditLog`: запись значимых действий (Web/Telegram/System) + эндпоинт `/api/admin/audit`.
- Каталог приложений: админ-CRUD `ClientApp` (`/api/admin/apps`) — название, ссылка, ОС, порядок, вкл/выкл.
- Фронт: таблицы пользователей/конфигов/ролей, журнал аудита, графики трафика (Recharts), сводки.
- **Готово, когда**: админ видит статистику и журнал, управляет пользователями/ролями/конфигами; блокировка гасит VPN.
@@ -75,7 +79,7 @@
## M8 — Закалка (hardening)
- Полный набор тестов (Domain/Application/Integration с Testcontainers).
- Rate-limiting, аудит-лог действий, единообразные ProblemDetails, ретеншн `TrafficSample`.
- Прод-конфиг docker-compose (secrets, миграции отдельным шагом, опц. reverse-proxy для TLS).
- Прод-конфиг docker-compose (secrets, том для key-ring Data Protection, healthchecks); TLS — внешним прокси.
- **Готово, когда**: зелёный CI, покрытие ключевых сценариев, готовность к деплою.
## Backlog (после MVP)
+8 -2
View File
@@ -125,8 +125,12 @@ MVP (не нужен публичный webhook, проще в одиночно
+ fallback на `index.html`), фронт и бек — один origin.
- **docker-compose**: `app` (единый образ) + `db` (PostgreSQL) с томом.
- Почему не отдельный nginx: единый origin упрощает CORS/куки/деплой и укладывается в требование
«фронт+бек в одном контейнере». Nginx/reverse-proxy — опция для прод (TLS-терминация) поверх, но не обязателен.
- **CI**: сборка/тесты бэка (`dotnet test`), линт/сборка фронта (`pnpm build`), сборка единого образа.
«фронт+бек в одном контейнере».
- **TLS — внешний** (решение): HTTPS терминирует внешний прокси/шлюз (nginx/Traefik/cloud LB) вне
compose; `app` отдаёт HTTP и доверяет `X-Forwarded-*` через `ForwardedHeaders`. Свой nginx/Caddy не вводим.
- **Миграции** — авто на старте приложения (MVP).
- **CI** — GitHub Actions, **только сборка/тесты**: `dotnet build`/`test`, `pnpm build`/`lint`/`typecheck`.
Публикация образа и деплой — вручную/позже (в MVP не автоматизируем).
- **Пакетный менеджер фронта**: pnpm (быстрый, экономный по диску).
## Принятые решения (по открытым вопросам)
@@ -166,6 +170,8 @@ MVP (не нужен публичный webhook, проще в одиночно
| Самоудаление аккаунта | Разрешено: отзыв всех конфигов + удаление данных, аудит анонимизируется |
| Версионирование API | Без версий в MVP (`/api` без `v1`) |
| Подписка (заголовки) | `Subscription-Userinfo` (used/total/expire) + `profile-update-interval` |
| Тема сайта | Светлая + тёмная (+ системная); Tailwind `dark`, выбор в localStorage |
| Инструкции/приложения | Отдельная страница инструкций + каталог `ClientApp` (админ CRUD, юзер — по ОС); стартовый сид из `seed/client-apps.json` |
| Реконсиляция с 3x-ui | На синхронизации сверяем проекцию с панелью, помечаем дрейф, не «воскрешаем» молча |
Также заложены: CSRF-защита refresh-cookie + Identity lockout; проверка квоты в транзакции; схема
+3 -1
View File
@@ -25,7 +25,7 @@ PnvPanel **не заменяет** Xray/3x-ui — он оркестрирует
| ----------- | -------------------------------------------------------------------------------------------- |
| **Guest** | Регистрация, вход, публичный эндпоинт подписки (`/sub/{token}`). |
| **User** | После **активации** — CRUD своих конфигов (в рамках квоты роли и доступных инбаундов), просмотр трафика/срока, ссылка/QR, отзыв. |
| **Admin** | Всё выше без лимитов + ноды, публикация inbounds с выбором ролей, роли/квоты, активация пользователей, стата. |
| **Admin** | Всё выше без лимитов + ноды, публикация inbounds с выбором ролей, роли/квоты, активация пользователей, каталог приложений, стата. |
| *(кастомные)* | Админ создаёт роли (напр. `vip`) со своей квотой конфигов и назначает их пользователям. |
### RBAC — динамические роли с квотой
@@ -107,6 +107,8 @@ PnvPanel **не заменяет** Xray/3x-ui — он оркестрирует
- Синхронизация трафика (фоновая) + realtime-обновления по SignalR.
- Базовая статистика для админа.
- Telegram-бот: ссылка на сайт, просмотр конфигов, привязка Telegram и passwordless-вход.
- Светлая/тёмная тема сайта.
- Страница инструкций по подключению + каталог приложений по ОС (админ ведёт, юзер видит сгруппировано).
- Единый Docker-образ (фронт+бек) + PostgreSQL в docker-compose.
**За рамками MVP (backlog):**