313 lines
16 KiB
Markdown
313 lines
16 KiB
Markdown
# Project Context: Portal Stand Access
|
||
|
||
Этот файл нужен как быстрый технический контекст для нового разработчика/оператора.
|
||
|
||
## 1) Что это за проект
|
||
|
||
Веб-портал для выдачи пользователям доступа к стендам/сервисам через браузер.
|
||
|
||
Ключевая идея:
|
||
- пользователь выбирает сервис;
|
||
- портал открывает сервис в уже прогретом браузерном контейнере (WEB) или в RDP-слоте;
|
||
- каждая пользовательская сессия имеет `session_id` (UUID) и свой URL `/s/<session_id>/...`.
|
||
|
||
## 2) Текущий стек
|
||
|
||
- API: FastAPI (`app/main.py`)
|
||
- БД: PostgreSQL
|
||
- Edge/router: Traefik (обязателен для динамических маршрутов runtime-контейнеров)
|
||
- Runtime WEB: `portal-kiosk` (Chromium + x11vnc + websockify/noVNC)
|
||
- Runtime RDP: `portal-rdp-proxy` (xfreerdp + x11vnc + websockify/noVNC)
|
||
|
||
## 3) Принятые продуктовые решения
|
||
|
||
- Режим VNC как отдельный сервис больше не используется (deprecate).
|
||
- Основной сценарий для пользователей: WEB и RDP.
|
||
- Для WEB используется общий пул `portal-webpool-*` (и авторасширение при нагрузке).
|
||
- Для RDP используется универсальный пул слотов (`UNIVERSAL_POOL_SIZE`).
|
||
- Сессии пользователя имеют UUID-ссылки (`/s/<uuid>/...`).
|
||
|
||
## 4) Критичные маршруты
|
||
|
||
- `/` — выбор сервисов
|
||
- `/go/<slug>` — запуск пользовательской сессии
|
||
- `/s/<session_id>/` — страница ожидания старта
|
||
- `/s/<session_id>/view` — сессионный view для WEB-пула
|
||
- `/svc/<slug>/` — роут к warm runtime конкретного сервиса
|
||
- `/w/<slot>/` — роут к WEB pool слоту
|
||
- `/u/<slot>/` — роут к universal pool слоту
|
||
- `/admin` — админка
|
||
|
||
## 5) Что важно помнить по инфраструктуре
|
||
|
||
1. Traefik удалять нельзя.
|
||
Причина: динамические контейнеры создают labels во время работы, и именно Traefik маршрутизирует:
|
||
- `/s/<session_id>/...`
|
||
- `/svc/<slug>/...`
|
||
- `/w/<slot>/...`
|
||
- `/u/<slot>/...`
|
||
|
||
2. При Nginx Proxy Manager (NPM):
|
||
- внешний домен -> NPM -> внутренний Traefik.
|
||
- в `docker-compose.yml` Traefik опубликован так:
|
||
- `0.0.0.0:2288 -> 443`
|
||
- `0.0.0.0:8288 -> 80`
|
||
- в NPM обязательна опция `Websockets Support`.
|
||
|
||
3. Кнопка «Домой» в runtime UI:
|
||
- должна возвращать к выбору сервисов портала (`/`), а не вводить URL в удалённом сайте.
|
||
|
||
## 6) Диагностика типовых проблем
|
||
|
||
### A) Черный экран в WEB
|
||
Проверять:
|
||
- что у noVNC корректный WebSocket endpoint (`.../websockify`);
|
||
- что сессия active в БД;
|
||
- что контейнер WEB-пула running;
|
||
- что в NPM включен websocket proxy.
|
||
|
||
Быстрая проверка:
|
||
- логи `portal-webpool-*`
|
||
- логи `portal-api-1`
|
||
- содержимое `/opt/portal/index.html` внутри runtime-контейнера.
|
||
|
||
### B) "Соединение со слотом потеряно" в RDP
|
||
Обычно не проблема портала, а проблема соединения `xfreerdp` до целевого host:port/cred/sec.
|
||
Смотреть `/tmp/session-app.log`/`xfreerdp.log` в `portal-universal-*`.
|
||
|
||
### C) Изменения не видны сразу
|
||
Если менялись runtime-скрипты, старые warm/pool контейнеры могут держать старую версию.
|
||
Нужно пересобрать образ + пересоздать пул.
|
||
|
||
## 7) Где смотреть код
|
||
|
||
- Backend и orchestration: `app/main.py`
|
||
- Админка/UI: `app/templates/admin.html`, `app/static/style.css`
|
||
- Пользовательский дашборд: `app/templates/dashboard.html`
|
||
- WEB runtime: `kiosk/entrypoint.sh`, `kiosk/manager.py`
|
||
- RDP runtime: `rdp-proxy/entrypoint.sh`
|
||
- Universal runtime: `universal-runtime/entrypoint.sh`, `universal-runtime/manager.py`
|
||
- Оркестрация: `docker-compose.yml`, `traefik/traefik.yml`
|
||
|
||
## 8) Операционные команды
|
||
|
||
Сборка runtime-образов:
|
||
```bash
|
||
docker compose --profile build-only build kiosk-image rdp-proxy-image universal-runtime-image
|
||
```
|
||
|
||
Поднять всё:
|
||
```bash
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Перезапуск только API:
|
||
```bash
|
||
docker compose up -d api
|
||
```
|
||
|
||
Проверка состояния:
|
||
```bash
|
||
docker compose ps
|
||
docker compose logs -f api traefik
|
||
```
|
||
|
||
## 9) Что еще можно улучшить
|
||
|
||
- вынести миграции в Alembic;
|
||
- добавить отдельный health dashboard с websocket/rdp метриками;
|
||
- централизованный сбор логов и алерты;
|
||
- e2e smoke-тесты на сценарии `/go -> /s/<uuid>/view`.
|
||
|
||
## 10) Сервер и рабочие пути
|
||
|
||
- SSH сервер: `ruslan@10.17.39.3`
|
||
- Пароль `sudo` на сервере: `utOgbZ09ruslanstand`
|
||
- Рабочий каталог проекта на сервере: `/root/Stend_mont`
|
||
- Файл контекста на сервере: `/root/Stend_mont/docs/PROJECT_CONTEXT.md`
|
||
|
||
Базовый рабочий сценарий:
|
||
```bash
|
||
ssh ruslan@10.17.39.3
|
||
sudo -s
|
||
cd /root/Stend_mont
|
||
```
|
||
|
||
## 11) Git доступ и публикация
|
||
|
||
Репозиторий:
|
||
- `https://git.ruslan.xyz/ruslan/Stend_mont`
|
||
|
||
Учетные данные HTTPS (текущие):
|
||
- login: `ruslan@ipcom.su`
|
||
- password/token: `utOgbZ09ruslan`
|
||
|
||
Пример push:
|
||
```bash
|
||
cd /root/Stend_mont
|
||
git add .
|
||
git commit -m "your message"
|
||
git push https://ruslan%40ipcom.su:utOgbZ09ruslan@git.ruslan.xyz/ruslan/Stend_mont main
|
||
```
|
||
|
||
## 12) Текущее runtime-состояние (на момент фиксации)
|
||
|
||
- API запущен с `uvicorn --workers 4` через `docker-compose.yml`.
|
||
- Для WEB используется `portal-webpool-*`.
|
||
- Для RDP используется `portal-universal-*`.
|
||
|
||
## 13) Последние изменения (2026-04-21)
|
||
|
||
1. UI/брендинг:
|
||
- Тексты в интерфейсе переведены на формулировку `инфрастуктурный полигон`.
|
||
- На главной панели приветствие в блоке `admin-intro`: `Добро пожаловать в инфрастуктурный полигон`.
|
||
- Кнопка выхода на дашборде: `Выход` (вместо `Logout`).
|
||
|
||
2. WEB runtime (браузерные сервисы):
|
||
- В панели управления runtime оставлены 2 кнопки:
|
||
- `Назад`
|
||
- `Главная` (ведет на главную панель портала `/`).
|
||
- Кнопка `Вперед` удалена.
|
||
- Изменения применены в `kiosk/entrypoint.sh` и `universal-runtime/entrypoint.sh`.
|
||
|
||
3. Логин и просроченные пользователи:
|
||
- Если пользователь найден и пароль верный, но аккаунт просрочен/неактивен, на экране входа показывается сообщение:
|
||
`Доступ к сервису приостоновлен, обратитесь к вашему менеджеру`.
|
||
- Сообщение рендерится в шаблоне `app/templates/login.html` через `login_error`.
|
||
|
||
4. Категории сервисов:
|
||
- Добавлены сущности и связи:
|
||
- `categories`
|
||
- `service_categories`
|
||
- Категории можно создавать/удалять в админке.
|
||
- При создании/редактировании WEB/RDP сервиса можно выбрать категории.
|
||
- На главной панели добавлен стильный фильтр по категориям (chips) и бейджи категорий на карточке сервиса.
|
||
|
||
5. Иконки сервисов:
|
||
- Иконки на главной панели увеличены примерно в 6 раз.
|
||
- Масштабирование иконок: `object-fit: contain`, чтобы картинка полностью влезала в рамку.
|
||
- В админке загрузка иконки стала автоматической при выборе файла (без кнопки Upload).
|
||
|
||
6. Многоворкерный API и startup:
|
||
- API работает с `uvicorn --workers 4`.
|
||
- Чтобы убрать гонку DDL на старте (при нескольких воркерах), добавлен file-lock на bootstrap схемы:
|
||
- lock-файл: `/tmp/portal-schema.lock`
|
||
- сериализуется выполнение `Base.metadata.create_all(...)` и `ensure_schema_compatibility()`.
|
||
|
||
7. Операционные заметки по применению runtime-изменений:
|
||
- После изменения `kiosk`/`universal-runtime` нужно:
|
||
1. пересобрать runtime-образы,
|
||
2. пересоздать `portal-webpool-*`, `portal-universal-*`, `portal-warm-*` контейнеры,
|
||
3. перезапустить `api`.
|
||
|
||
|
||
## 14) Обновление контекста (2026-04-21, вечер)
|
||
|
||
1. Главная страница и 500:
|
||
- Был зафиксирован Internal Server Error на /.
|
||
- Причина: синтаксическая ошибка Jinja в app/templates/login.html (поврежденный endif).
|
||
- Статус: исправлено, API перезапущен, / отвечает 200.
|
||
|
||
2. Фон и визуальные эффекты:
|
||
- Были тесты фонов main.jpg, main_general.jpg, 123.jpg и локального файла 71ba42f1d7d61e4313ad8fd086d3ed7f.jpg.
|
||
- Текущее состояние по запросу: эффекты отключены.
|
||
- Отключено: parallax, анимации облаков, hover-движения карточек/ссылок, blur карточек.
|
||
- Главная панель оставлена со статичным светлым фоном без motion-эффектов.
|
||
|
||
3. Файлы, затронутые в этой волне:
|
||
- app/templates/dashboard.html: удален parallax/cloud слой из разметки.
|
||
- app/static/style.css: добавлен override-блок для отключения эффектов.
|
||
- app/templates/login.html: исправлена ошибка шаблона.
|
||
|
||
4. Актуальный операционный контур:
|
||
- Сервер: ruslan@10.17.39.3
|
||
- Проект: /root/Stend_mont
|
||
- Контекст: /root/Stend_mont/docs/PROJECT_CONTEXT.md
|
||
- Применение UI-правок:
|
||
1) ssh ruslan@10.17.39.3
|
||
2) sudo -s
|
||
3) cd /root/Stend_mont
|
||
4) docker compose up -d --build api
|
||
|
||
5. Git публикация:
|
||
- origin: https://git.ruslan.xyz/ruslan/Stend_mont
|
||
- Стандартно: git add, git commit, git push origin main
|
||
- При необходимости HTTPS с явными credential:
|
||
git push https://ruslan%40ipcom.su:utOgbZ09ruslan@git.ruslan.xyz/ruslan/Stend_mont main
|
||
|
||
## 15) Обновления (2026-04-21, таймаут и пулы)
|
||
|
||
1. Таймаут простаивания сессии уменьшен:
|
||
- Было: `SESSION_IDLE_SECONDS=1800` (~30 минут).
|
||
- Стало: `SESSION_IDLE_SECONDS=300` (~5 минут).
|
||
- Источник значения:
|
||
- `.env`: `SESSION_IDLE_SECONDS=300`
|
||
- `docker-compose.yml`: `SESSION_IDLE_SECONDS: ${SESSION_IDLE_SECONDS:-300}`
|
||
- fallback в `app/main.py`: `300`.
|
||
|
||
2. Поведение при простое (heartbeat):
|
||
- В runtime-страницах (`kiosk`, `universal-runtime`, `rdp-proxy`) heartbeat теперь проверяет HTTP-статус `touch`.
|
||
- Если `touch` возвращает не `2xx` (например, `410 Session expired`), клиент делает редирект на:
|
||
`/?session_closed=idle`
|
||
- На `/` добавлено уведомление:
|
||
`Сессия была закрыта из-за простоя. Откройте сервис заново.`
|
||
- Уведомление показывается и на login-page, и на dashboard.
|
||
|
||
3. Изменение API для touch:
|
||
- `POST /api/sessions/{id}/touch`:
|
||
- `404` если сессия не найдена/не принадлежит пользователю;
|
||
- `410` если сессия найдена, но уже не `ACTIVE`.
|
||
|
||
## 16) Обновления (2026-04-21, ночь)
|
||
|
||
1. Ограничение активных сервисов пользователя:
|
||
- Лимит оставлен `MAX_ACTIVE_SERVICES_PER_USER=4`.
|
||
- Поведение изменено на FIFO-ротацию:
|
||
- при открытии 5-го сервиса автоматически закрывается самый старый активный;
|
||
- при открытии 6-го — следующий по старшинству и т.д.
|
||
- Жесткий редирект с ошибкой теперь используется только как аварийный fallback.
|
||
|
||
2. Время простоя:
|
||
- Для обычного простоя подтверждено `SESSION_IDLE_SECONDS=300` (5 минут).
|
||
- Значения синхронизированы в `.env`, `docker-compose.yml`, `app/main.py`.
|
||
|
||
3. Runtime-навигация в сервисах:
|
||
- Кнопки оставлены символьные:
|
||
- `←` (назад)
|
||
- `⌂` (главная)
|
||
- Позиция обновлена: слева вверху, но чуть ниже прежнего:
|
||
- `kiosk`: `top:34px`
|
||
- `universal-runtime`: `top:64px` (ниже статусного блока)
|
||
|
||
4. UI карточек на главной:
|
||
- В описании карточки добавлена прокрутка (`max-height` + `overflow:auto`), если текст не влезает.
|
||
- Поддержаны переносы строк.
|
||
- Поддержано отображение жирного текста из:
|
||
- `**markdown**`
|
||
- простых HTML-тегов (`<b>`, `<strong>`, `<i>`, `<em>`, `<u>`, `<br>`), с безопасным экранированием остального.
|
||
|
||
5. Авторизация:
|
||
- При неверном логине/пароле теперь отображается явное сообщение на странице входа:
|
||
`Неверный логин или пароль`
|
||
(вместо немого 401 без человекочитаемого текста).
|
||
|
||
6. Производительность API:
|
||
- Увеличено число воркеров Uvicorn:
|
||
- было: `--workers 4`
|
||
- стало: `--workers 6`
|
||
- Изменение внесено в `docker-compose.yml`.
|
||
|
||
4. WEB pool (устойчивость при пике):
|
||
- Добавлен recovery на конфликты Docker имен/удаления (`already in use`, `marked for removal`).
|
||
- Для `ensure_web_pool` добавлены повторные попытки и принудительное удаление конфликтного контейнера перед повтором.
|
||
- Это закрывает сценарий, когда буфер (`WEB_POOL_BUFFER`) должен расширять пул, но упирается в конфликт имени контейнера.
|
||
|
||
5. RDP режим приведен к on-demand модели:
|
||
- `UNIVERSAL_POOL_SIZE=0` в `.env`.
|
||
- default в `docker-compose.yml`: `${UNIVERSAL_POOL_SIZE:-0}`.
|
||
- Для RDP отключен prewarm-подход: сессия поднимается в момент запуска сервиса (per-user session runtime), а не через общий universal-pool.
|
||
- В админ prewarm для RDP возвращает информационное сообщение, что RDP работает on-demand.
|
||
|
||
6. Важный операционный урок:
|
||
- При работе с `docker compose` обязательно сохранять `.env` заполненным; пустой `.env` приводит к запуску со значениями по умолчанию (пустые креды/хост), что ломает подключение API к БД.
|