Add the admin panel scaffold

A self-contained template: an Express backend with login, JWT, rate limiting
and SPA serving, and a React 18 + Vite shell with the same tab registry and
warm Material 3 design system the panel uses. No domain logic — routes and tabs
are meant to be hung off it.

node_modules and the frontend build stay out through the existing ignores.
This commit is contained in:
2026-09-10 00:24:18 +03:00
parent a11cfc88de
commit 66ae59e1d0
19 changed files with 4942 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
# Admin — каркас админ-панели
Самодостаточный шаблон админки: бэкенд с авторизацией и фронтенд-оболочка
с системой вкладок. Предметной логики нет — это основа, поверх которой
навешиваются свои роуты и вкладки.
```
admin/
backend/ Express: /login, /logout, JWT, rate-limit, отдача SPA
server.ts
frontend/ React 18 + Vite: логин, тема, drawer/сайдбар, реестр вкладок
src/App.tsx оболочка
src/App.css дизайн-система (Material 3, тёплая палитра)
src/tabs/ вкладки — см. src/tabs/README.md
src/components/ общие компоненты и иконки (+ карта ICONS)
src/lib/shared.ts общие типы/утилиты + PageProps
src/lib/branding.ts название и версия панели
```
## Запуск в деве
Два терминала:
```bash
cd admin/backend && npm install && ADMIN_PASS=admin npm start # :8080
cd admin/frontend && npm install && npm run dev # :5173
```
Открыть http://localhost:5173 — логин `admin` / `admin`.
Vite проксирует `/api`, `/login`, `/logout` на бэкенд, поэтому фронт всегда
ходит по относительным путям и порт нигде не зашит.
## Прод
```bash
cd admin/frontend && npm run build # → frontend/dist
cd admin/backend && npm start # раздаёт dist и API с одного порта
```
## Переменные окружения (backend)
| Переменная | По умолчанию | Смысл |
|---------------|---------------------------|--------------------------------------------------------------|
| `PORT` | `8080` | Порт бэкенда |
| `ADMIN_USER` | `admin` | Логин в панель |
| `ADMIN_PASS` | — (пусто ⇒ вход отключён) | Пароль; без него `/login` всегда отвечает 401 |
| `JWT_SECRET` | случайный при старте | Подпись токенов; без него сессии рвутся при рестарте процесса |
| `STATIC_DIR` | `../frontend/dist` | Каталог со сборкой фронта |
## Авторизация
`POST /login` (логин + пароль) → HS256 JWT на 24 часа, подписанный `JWT_SECRET`
через встроенный `crypto` — без сторонних JWT-библиотек. Токен хранится в
`localStorage` и уходит в заголовке `Authorization: Bearer`. `POST /logout`
добавляет подпись токена в in-memory список отозванных — токен умирает сразу.
Перебор пароля ограничен: 5 неудачных попыток с одного IP за 15 минут → 429.
Всё под `/api` закрыто мидлварой `requireAuth` — свои роуты добавляй ниже
строки `app.use("/api", requireAuth)` в `server.ts`, и они получают авторизацию
даром. `GET /api/session` уже есть: фронт зовёт его при загрузке, чтобы понять,
жив ли сохранённый токен.
## Как добавить свой раздел
1. Роут на бэкенде — в `server.ts` под `/api/…`.
2. Вкладку на фронте — папка в `src/tabs/`, реестр подхватит её сам.
Подробности и формат `metadata.json` — в [src/tabs/README.md](frontend/src/tabs/README.md).