feat(tgu-ops-ui): add pass selector styles and update architecture docs
tgu-editor.css: add .map-pass-selector styles — floating panel with semi-transparent background, range slider track, fill, tick marks, draggable thumbs, and per-handle labels showing orbit number + UTC time. docs: update PCP_ARCHITECTURE.md to reflect pcp-tgu-ops-ui is now a real SPA (not a placeholder); add PCP_TGU_OPS_UI_CAPABILITY_MAP.md describing feature areas and data sources for the TGU ops frontend. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,493 @@
|
||||
# pcp-tgu-ops-ui — карта возможностей и источников данных
|
||||
|
||||
Дата анализа: 2026-05-31.
|
||||
|
||||
Документ фиксирует, что сейчас умеет `pcp-tgu-ops-ui`: что отображает, чем управляет, какие backend endpoints вызывает и откуда реально приходят данные. Анализ выполнен по исходному коду frontend и по execution path backend endpoints до service/repository/external source.
|
||||
|
||||
## Кратко
|
||||
|
||||
`pcp-tgu-ops-ui` — отдельная Vite/React SPA для Mission Ops/TGU. Приложение рендерит `TguPlanningPage` и имеет три вкладки:
|
||||
|
||||
- `Планы` — timeline persisted TGU-планов по КА.
|
||||
- `Карта 2D` — карта с полигонами заявок на съемку.
|
||||
- `Редактор` — локальный редактор включений КА с черновиком, проверками и заглушкой сохранения.
|
||||
|
||||
Реально изменять состояние backend UI умеет только через отправку решения по плану: `ACCEPTED` или `REJECTED`. Редакторские правки пока не сохраняются во внешний сервис.
|
||||
|
||||
## Общая схема
|
||||
|
||||
```text
|
||||
Browser
|
||||
|
|
||||
v
|
||||
pcp-tgu-ops-ui SPA
|
||||
|
|
||||
|-- /api/tgu-planning/*
|
||||
| |
|
||||
| v
|
||||
| pcp-ui-service :7008
|
||||
| |
|
||||
| v
|
||||
| pcp-tgu-service :7011
|
||||
| |
|
||||
| |-- pcp_tgu DB
|
||||
| |
|
||||
| `-- Ordinis NSI spacecraft classifier
|
||||
|
|
||||
|-- /api/current-plans/*
|
||||
| |
|
||||
| v
|
||||
| pcp-ui-service :7008
|
||||
| |
|
||||
| v
|
||||
| pcp-mission-planing-service :7010
|
||||
| |
|
||||
| `-- mission planning DB
|
||||
|
|
||||
|-- /api/pcp-request/v1/*
|
||||
| |
|
||||
| v
|
||||
| pcp-request-service :7005
|
||||
| |
|
||||
| `-- request DB
|
||||
|
|
||||
`-- https://*.basemaps.cartocdn.com
|
||||
|
|
||||
`-- CARTO basemap tiles
|
||||
```
|
||||
|
||||
Dev proxy:
|
||||
|
||||
- `/api/pcp-request` -> `http://localhost:7005`, prefix `/api/pcp-request` stripped.
|
||||
- `/api` -> `http://localhost:7008`.
|
||||
|
||||
Production nginx:
|
||||
|
||||
- `/api/pcp-request/` -> `http://host.docker.internal:7005/`.
|
||||
- `/api/` -> `http://host.docker.internal:7008/api/`.
|
||||
|
||||
## Endpoint inventory
|
||||
|
||||
| UI endpoint | Метод | Использование в UI | Backend execution path | Источник |
|
||||
|---|---:|---|---|---|
|
||||
| `/api/tgu-planning/platforms` | GET | список КА, подписи, поиск, редактор | `pcp-ui-service` `TguPlanningRestController.platforms()` -> `TguPlanningService.platforms()` -> `pcp-tgu-service /api/platforms` -> `PlatformService.loadPlatforms()` | in-memory cache, Ordinis NSI |
|
||||
| `/api/tgu-planning/plans?historyDays=N` | GET | загрузка всех TGU-планов для timeline/map/editor context | `pcp-ui-service` -> `pcp-tgu-service /api/plans` -> `PlanQueryService.getAllPlans()` -> `PlannedPlanRepository.findAllByOrderByStartTime()` | `planned_plan` |
|
||||
| `/api/tgu-planning/spacecraft/{spacecraftId}/plans?historyDays=N` | GET | есть в API-клиенте, текущая страница в основном грузит все планы | `pcp-ui-service` -> `pcp-tgu-service /api/plans/{spacecraftId}` -> `PlanQueryService.getPlansBySpacecraftId()` | `planned_plan` |
|
||||
| `/api/tgu-planning/plans/{planId}/decision?decision=...&reason=...` | POST | кнопки принять/отклонить для `WAITING_DECISION` | `pcp-ui-service` -> `pcp-tgu-service /test/tgu/plans/{planId}/decision` -> `TestPlanDecisionService.generateDecision()` -> `HandlePlanDecisionUseCase.handle()` | `planned_plan`, `plan_issue_attempt`, `spacecraft_planning_state`, `processed_plan_decision_event` |
|
||||
| `/api/pcp-request/v1/requests/map?...` | GET | полигоны заявок на карте 2D и в редакторе | напрямую в `pcp-request-service /v1/requests/map` -> `RequestController.listRequestsForMap()` -> `RequestService.listRequestsForMap()` -> `RequestRepository.findRequests()` | `requests` |
|
||||
| `/api/current-plans/missions/{missionId}/modes` | GET | контуры существующих включений в редакторе | `pcp-ui-service` -> `pcp-mission-planing-service /api/missions/{missionId}/modes` -> `MissionPlaningService.modesByMission()` | `missions`, `plans`, `modes`, `survey_modes`, `drop_modes`, `drop_mode_survey`, `mode_booking` |
|
||||
|
||||
## Вкладка "Планы"
|
||||
|
||||
### Что отображает
|
||||
|
||||
- Список КА: `name`, `NORAD`, `mission`, `status`.
|
||||
- Количество планов по КА в выбранном интервале.
|
||||
- Индикатор проблемных статусов: `REJECTED`, `EXPIRED`, `START_AMBIGUOUS`.
|
||||
- Timeline persisted TGU-планов по КА.
|
||||
- Стрелки между последовательными планами одного КА.
|
||||
- Линию текущего времени, если `now` попадает в выбранный диапазон.
|
||||
- Детали выбранного плана:
|
||||
- `planId`;
|
||||
- `spacecraftId`;
|
||||
- КА;
|
||||
- `startTime`;
|
||||
- `endTime`;
|
||||
- duration;
|
||||
- `kppId`;
|
||||
- `status`.
|
||||
|
||||
### Чем управляет
|
||||
|
||||
- Фильтр диапазона дат.
|
||||
- Быстрые диапазоны: 24 часа, 3 суток, 7 суток.
|
||||
- Refresh данных.
|
||||
- Поиск по КА.
|
||||
- Выбор конкретного КА.
|
||||
- Выбор плана.
|
||||
- Отправка решения `ACCEPTED`/`REJECTED`, но только для плана в статусе `WAITING_DECISION`.
|
||||
|
||||
### Execution path решения по плану
|
||||
|
||||
```text
|
||||
User
|
||||
|
|
||||
v
|
||||
TguPlanDetails
|
||||
|
|
||||
|-- Принять -> decision=ACCEPTED, reason=UI_TEST_ACCEPTED
|
||||
|
|
||||
`-- Отклонить -> decision=REJECTED, reason from prompt
|
||||
|
|
||||
v
|
||||
sendPlanDecision()
|
||||
|
|
||||
v
|
||||
POST /api/tgu-planning/plans/{planId}/decision
|
||||
|
|
||||
v
|
||||
pcp-ui-service
|
||||
|
|
||||
v
|
||||
TguPlanningService.sendDecision()
|
||||
|
|
||||
v
|
||||
POST pcp-tgu-service /test/tgu/plans/{planId}/decision
|
||||
|
|
||||
v
|
||||
TestPlanDecisionService.generateDecision()
|
||||
|
|
||||
|-- checks plan exists
|
||||
|-- checks spacecraft_planning_state exists
|
||||
|-- checks activePlanId == planId
|
||||
|-- checks activeAttemptId exists and belongs to plan
|
||||
|-- checks attempt status STARTING/STARTED
|
||||
|
|
||||
v
|
||||
HandlePlanDecisionUseCase.handle()
|
||||
|
|
||||
|-- insert processed_plan_decision_event, dedup by eventId
|
||||
|-- lock plan_issue_attempt by attemptId
|
||||
|-- lock planned_plan by planId
|
||||
|-- lock/create spacecraft_planning_state
|
||||
|
|
||||
|-- ACCEPTED:
|
||||
| |-- attempt=ACCEPTED
|
||||
| |-- plan=ACCEPTED
|
||||
| |-- currentBasePlanId=planId
|
||||
| |-- activePlanId/activeAttemptId cleared
|
||||
| |-- enqueue plan_recalculation_job
|
||||
| `-- enqueue accepted_plan_slots_update_job
|
||||
|
|
||||
`-- REJECTED:
|
||||
|-- attempt=REJECTED
|
||||
|-- activePlanId/activeAttemptId cleared
|
||||
|-- if enough lead time: plan=PLANNED, issueNotBefore=now
|
||||
`-- else: plan=EXPIRED, promote next plan or enqueue rebuild
|
||||
```
|
||||
|
||||
Важный caveat: UI отправляет решение через test-only controller `pcp-tgu-service /test/tgu/...`. Если `tgu.test-controller.enabled=false`, эта команда перестанет работать.
|
||||
|
||||
## Вкладка "Карта 2D"
|
||||
|
||||
### Что отображает
|
||||
|
||||
- CARTO dark basemap tiles.
|
||||
- Lat/lon grid поверх тайлов.
|
||||
- Полигоны заявок на съемку из `pcp-request-service`.
|
||||
- Цвета заявок по `surveyType`:
|
||||
- `OPTICS`;
|
||||
- `RSA`;
|
||||
- `COMBINED`.
|
||||
- Hover tooltip по полигону.
|
||||
- Выбор полигона заявки на карте.
|
||||
- Детальную панель с выбранным планом и выбранным объектом карты.
|
||||
|
||||
### Чем управляет
|
||||
|
||||
- Панорамирование карты drag-ом.
|
||||
- Zoom колесом и кнопками `+`/`-`.
|
||||
- Reset view.
|
||||
- Включение/выключение слоев.
|
||||
- Загрузка заявок за отдельный интервал.
|
||||
- Быстрые интервалы заявок: сегодня, 7 дней, 30 дней.
|
||||
- Скрытие/показ КА в sidebar.
|
||||
|
||||
### Что сейчас заявлено в UI, но не имеет данных
|
||||
|
||||
В главной карте 2D есть переключатели слоев:
|
||||
|
||||
- трасса КА;
|
||||
- полоса обзора;
|
||||
- работы плана;
|
||||
- станции;
|
||||
- маркеры КА;
|
||||
- заявки на съемку.
|
||||
|
||||
Но текущий `buildTgu2DMapScene()` наполняет только `request` polygons. Для остальных слоев рисовалки существуют, но данные в scene не передаются. UI показывает предупреждение: `Данные слоя недоступны в текущем API.`
|
||||
|
||||
### Execution path карты 2D
|
||||
|
||||
```text
|
||||
User
|
||||
|
|
||||
v
|
||||
Tgu2DMapTab
|
||||
|
|
||||
|-- selectedPlan / range / layers / hiddenSpacecraftIds
|
||||
|
|
||||
|-- load requests
|
||||
| |
|
||||
| v
|
||||
| fetchRequestsForMap()
|
||||
| |
|
||||
| |-- page size = 500
|
||||
| |-- max pages = 10
|
||||
| |-- max loaded requests = 5000
|
||||
| |-- sort = beginDateTime,asc
|
||||
| |
|
||||
| v
|
||||
| GET /api/pcp-request/v1/requests/map
|
||||
| |
|
||||
| v
|
||||
| pcp-request-service
|
||||
| |
|
||||
| v
|
||||
| RequestController.listRequestsForMap()
|
||||
| |
|
||||
| v
|
||||
| RequestService.listRequestsForMap()
|
||||
| |
|
||||
| v
|
||||
| RequestRepository.findRequests()
|
||||
| |
|
||||
| |-- status filter, optional
|
||||
| |-- surveyType filter, optional
|
||||
| |-- kpp filter, supported by backend but not passed by current UI
|
||||
| |-- highPriorityTransmit filter, supported by backend but not passed by current UI
|
||||
| |-- beginFrom/beginTo/endFrom/endTo
|
||||
| |-- exclude DELETED by default
|
||||
| |
|
||||
| v
|
||||
| requests table
|
||||
|
|
||||
v
|
||||
buildTgu2DMapScene()
|
||||
|
|
||||
|-- parse WKT from request.geometry
|
||||
|-- supports POLYGON and MULTIPOLYGON
|
||||
|-- build MapPolygon objects with layer=requests
|
||||
|
|
||||
v
|
||||
Tgu2DMapView
|
||||
|
|
||||
|-- drawTiles()
|
||||
| |
|
||||
| `-- https://{a|b|c|d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png
|
||||
|
|
||||
|-- drawBaseMap()
|
||||
| |
|
||||
| `-- 30 degree lat/lon grid
|
||||
|
|
||||
|-- drawRequests()
|
||||
| |
|
||||
| `-- request polygons on canvas
|
||||
|
|
||||
|-- createPolygonSpatialIndex()
|
||||
|-- hitTestPolygons()
|
||||
|
|
||||
v
|
||||
Canvas + hover tooltip + selected object details
|
||||
```
|
||||
|
||||
### Фильтр заявок на карте
|
||||
|
||||
Для выбранного интервала `[from, to]` UI передает:
|
||||
|
||||
```text
|
||||
endFrom = from
|
||||
beginTo = to
|
||||
```
|
||||
|
||||
То есть выбираются заявки, которые пересекают интервал карты.
|
||||
|
||||
## Вкладка "Редактор"
|
||||
|
||||
### Что отображает
|
||||
|
||||
- Выбранный КА и выбранный TGU-план.
|
||||
- Черновик включений:
|
||||
- `shoot` — съемка;
|
||||
- `downlink` — сброс.
|
||||
- Timeline черновика.
|
||||
- Окна видимости цели/станции.
|
||||
- Синтетическую трассу КА на карте редактора.
|
||||
- Полигоны заявок из `pcp-request-service`.
|
||||
- Контуры `SURVEY` из `pcp-mission-planing-service`, если endpoint `/current-plans/missions/{id}/modes` вернул режимы.
|
||||
- Счетчики:
|
||||
- всего включений;
|
||||
- съемок;
|
||||
- сбросов;
|
||||
- конфликтов.
|
||||
|
||||
### Что умеет делать локально
|
||||
|
||||
- Добавить съемку через клик точки на карте.
|
||||
- Добавить сброс по станции и окну связи.
|
||||
- Перетаскивать включения по timeline.
|
||||
- Менять начало/конец включения drag resize.
|
||||
- Менять время через stepper.
|
||||
- Дублировать включение.
|
||||
- Удалять включение.
|
||||
- Undo/redo.
|
||||
- Проверять пересечения включений по времени.
|
||||
- Проверять видимость цели/станции по синтетической орбитальной модели `passes.ts`.
|
||||
- Показывать контекст других КА в rail, но сейчас `contextLanes.works` пустой.
|
||||
|
||||
### Что не умеет сохранять
|
||||
|
||||
Кнопка `Применить` вызывает `saveEditedPlan()`, но функция является заглушкой:
|
||||
|
||||
```text
|
||||
Сохранение правок плана во внешний сервис еще не реализовано в backend.
|
||||
```
|
||||
|
||||
Backend endpoint для сохранения отредактированного набора включений отсутствует.
|
||||
|
||||
### Execution path редактора
|
||||
|
||||
```text
|
||||
User
|
||||
|
|
||||
v
|
||||
TguEditorTab
|
||||
|
|
||||
|-- selectedSpacecraftId / selectedPlan from TguPlanningPage
|
||||
|
|
||||
|-- initialize draft
|
||||
| |
|
||||
| v
|
||||
| seedDraft(selectedPlan)
|
||||
| |
|
||||
| |-- if selectedPlan has embedded works:
|
||||
| | `-- use embedded works
|
||||
| |
|
||||
| `-- else:
|
||||
| `-- create fallback downlink work:
|
||||
| id = {planId}:plan-window
|
||||
| kind = downlink
|
||||
| start/end = selected plan start/end
|
||||
| stationId = selectedPlan.kppId
|
||||
|
|
||||
|-- load request context
|
||||
| |
|
||||
| v
|
||||
| fetchRequestsForMap()
|
||||
| |
|
||||
| v
|
||||
| GET /api/pcp-request/v1/requests/map
|
||||
| |
|
||||
| v
|
||||
| pcp-request-service
|
||||
| |
|
||||
| v
|
||||
| requests table
|
||||
|
|
||||
|-- load plan modes
|
||||
| |
|
||||
| v
|
||||
| fetchPlanModes(selectedPlan.planId)
|
||||
| |
|
||||
| v
|
||||
| GET /api/current-plans/missions/{selectedPlan.planId}/modes
|
||||
| |
|
||||
| v
|
||||
| pcp-ui-service
|
||||
| |
|
||||
| v
|
||||
| pcp-mission-planing-service
|
||||
| |
|
||||
| v
|
||||
| MissionPlaningService.modesByMission()
|
||||
| |
|
||||
| |-- check mission exists
|
||||
| |-- find modes by mission id
|
||||
| |-- map SurveyModeEntity -> SurveyModeResponseDTO
|
||||
| |-- map DropModeEntity -> DropModeResponseDTO
|
||||
| |
|
||||
| v
|
||||
| mission planning DB
|
||||
|
|
||||
|-- editor map scene
|
||||
| |
|
||||
| |-- groundTrack(spacecraftId) from frontend synthetic orbit
|
||||
| |-- station markers only if station lat/lon exists
|
||||
| |-- plan mode SURVEY contourWkt -> planWorks polygons
|
||||
| `-- request.geometry WKT -> request polygons
|
||||
|
|
||||
|-- local draft operations
|
||||
| |
|
||||
| |-- addShoot()
|
||||
| |-- addDownlink()
|
||||
| |-- updateWork()
|
||||
| |-- deleteWork()
|
||||
| |-- duplicateWork()
|
||||
| |-- undo()
|
||||
| |-- redo()
|
||||
| |-- overlaps()
|
||||
| `-- checkVisibility()
|
||||
|
|
||||
`-- apply draft
|
||||
|
|
||||
v
|
||||
saveEditedPlan()
|
||||
|
|
||||
v
|
||||
stub result only, no backend mutation
|
||||
```
|
||||
|
||||
### Важные caveats редактора
|
||||
|
||||
- `fetchPlanModes()` использует `selectedPlan.planId` как `missionId` для `/api/current-plans/missions/{missionId}/modes`.
|
||||
- Если UUID TGU-плана не совпадает с UUID mission в `pcp-mission-planing-service`, endpoint вернет 404, а редактор просто останется без режимов плана.
|
||||
- Станции строятся из `selectedPlan.kppId`, но координаты станции из backend не загружаются. Поэтому проверка видимости downlink часто возвращает конфликт `Нет координат станции для проверки видимости`.
|
||||
- Орбитальная модель в редакторе frontend-only и синтетическая: параметры выводятся из hash `spacecraftId`, а не из реальной баллистики/TLE.
|
||||
|
||||
## Capability matrix
|
||||
|
||||
| Область | Сейчас умеет | Ограничение |
|
||||
|---|---|---|
|
||||
| Платформы КА | читать из TGU/NSI, показывать name/NORAD/mission/status | при падении NSI `pcp-tgu-service` может использовать stale cache, если есть |
|
||||
| TGU-планы | читать persisted plans, строить timeline | UI грузит все планы и фильтрует на клиенте |
|
||||
| Решение по плану | отправить `ACCEPTED`/`REJECTED` для `WAITING_DECISION` | через test-only controller; нужен active plan + active attempt |
|
||||
| Карта заявок | загрузить и показать WKT-полигоны заявок | до 5000 заявок, без server-side фильтров kpp/highPriority в UI |
|
||||
| Слои карты | UI-переключатели есть | кроме заявок, основные слои главной карты не получают данных |
|
||||
| Редактор включений | локальный draft, drag/resize, add/delete/duplicate, undo/redo, checks | сохранения в backend нет |
|
||||
| Режимы плана в редакторе | пытается читать `/current-plans/missions/{id}/modes` | возможен mismatch `planId` vs `missionId` |
|
||||
| Станции | может показывать station markers только при наличии lat/lon | lat/lon станций сейчас не загружаются |
|
||||
| Тайлы карты | CARTO CDN | внешний network dependency браузера |
|
||||
|
||||
## Проверенные исходники
|
||||
|
||||
Frontend:
|
||||
|
||||
- `services/pcp-tgu-ops-ui/src/App.tsx`
|
||||
- `services/pcp-tgu-ops-ui/src/api/tguApi.ts`
|
||||
- `services/pcp-tgu-ops-ui/src/api/requestApi.ts`
|
||||
- `services/pcp-tgu-ops-ui/src/features/tgu-planning/*`
|
||||
- `services/pcp-tgu-ops-ui/src/features/tgu-map-2d/*`
|
||||
- `services/pcp-tgu-ops-ui/src/features/tgu-editor/*`
|
||||
- `services/pcp-tgu-ops-ui/vite.config.ts`
|
||||
- `services/pcp-tgu-ops-ui/nginx.conf`
|
||||
|
||||
Backend/BFF:
|
||||
|
||||
- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/TguPlanningController.kt`
|
||||
- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/TguPlanningService.kt`
|
||||
- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CurrentPlansController.kt`
|
||||
- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/MissionService.kt`
|
||||
|
||||
TGU:
|
||||
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/PlatformController.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/PlanController.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/TestTguController.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/PlatformService.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/PlanQueryService.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/TestPlanDecisionService.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/HandlePlanDecisionUseCase.kt`
|
||||
- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/repository/*`
|
||||
|
||||
Requests:
|
||||
|
||||
- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/controller/RequestController.kt`
|
||||
- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/service/RequestService.kt`
|
||||
- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/repository/RequestRepository.kt`
|
||||
- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/dto/RequestApiDtos.kt`
|
||||
|
||||
Mission planning:
|
||||
|
||||
- `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/controller/MissionController.kt`
|
||||
- `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/MissionPlaningService.kt`
|
||||
- `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/repository/*`
|
||||
- `libs/pcp-types-lib/src/main/kotlin/space/nstart/pcp/pcp_types_lib/dto/satellite/mission/*`
|
||||
Reference in New Issue
Block a user