chore: сохранение текущих изменений (deploy local-bundle/local-jar, request-service, slots UI markedObjectIds)
Безопасный коммит незакоммиченной работы перед реализацией бронирования слотов. Вне коммита оставлены: package-lock.json (пустой, мусор), requests.json (scratch-данные).
This commit is contained in:
@@ -53,6 +53,7 @@ out/
|
|||||||
/pcp_zip.zip
|
/pcp_zip.zip
|
||||||
/pcp-chatgpt-context*.zip
|
/pcp-chatgpt-context*.zip
|
||||||
/deploy/local-bundle/build/
|
/deploy/local-bundle/build/
|
||||||
|
/deploy/local-bundle/.env
|
||||||
/deploy/local-bundle/pcp-local-bundle-*.tar.gz
|
/deploy/local-bundle/pcp-local-bundle-*.tar.gz
|
||||||
|
|
||||||
### Node / Vite ###
|
### Node / Vite ###
|
||||||
@@ -69,3 +70,4 @@ out/
|
|||||||
/services/pcp-tgu-ops-ui/yarn-debug.log*
|
/services/pcp-tgu-ops-ui/yarn-debug.log*
|
||||||
/services/pcp-tgu-ops-ui/yarn-error.log*
|
/services/pcp-tgu-ops-ui/yarn-error.log*
|
||||||
/services/pcp-tgu-ops-ui/pnpm-debug.log*
|
/services/pcp-tgu-ops-ui/pnpm-debug.log*
|
||||||
|
.gstack/
|
||||||
|
|||||||
Regular → Executable
-2
@@ -1,4 +1,3 @@
|
|||||||
cat > create-service-dbs.sh <<'BASH'
|
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -124,4 +123,3 @@ done
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "All requested databases processed."
|
echo "All requested databases processed."
|
||||||
BASH
|
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
COMPOSE_PROJECT_NAME=pcp-local
|
COMPOSE_PROJECT_NAME=pcp-local
|
||||||
|
|
||||||
|
USE_EXTERNAL_INFRA=false
|
||||||
|
PCP_DOCKER_NETWORK=pcp-local_default
|
||||||
|
PCP_DOCKER_NETWORK_EXTERNAL=false
|
||||||
|
|
||||||
|
# Existing allinone infra example:
|
||||||
|
# USE_EXTERNAL_INFRA=true
|
||||||
|
# PCP_DOCKER_NETWORK=allinone_camunda
|
||||||
|
# PCP_DOCKER_NETWORK_EXTERNAL=true
|
||||||
|
# EXTERNAL_POSTGRES_CONTAINER=allinone-postgres-1
|
||||||
|
# EXTERNAL_KAFKA_CONTAINER=allinone-kafka-1
|
||||||
|
# CAMUNDA_CLIENT_ENABLED=true
|
||||||
|
# CAMUNDA_GRPC_ADDRESS=http://orchestration:26500
|
||||||
|
|
||||||
PCP_IMAGE_TAG=local
|
PCP_IMAGE_TAG=local
|
||||||
|
|
||||||
POSTGRES_IMAGE=postgis/postgis:16-3.4
|
POSTGRES_IMAGE=postgis/postgis:16-3.4
|
||||||
@@ -8,14 +21,24 @@ KAFKA_IMAGE=apache/kafka:3.7.2
|
|||||||
POSTGRES_USER=postgres
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=password
|
POSTGRES_PASSWORD=password
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
|
PCP_POSTGRES_HOST=postgres
|
||||||
|
PCP_POSTGRES_PORT=5432
|
||||||
|
EXTERNAL_POSTGRES_CONTAINER=
|
||||||
|
|
||||||
KAFKA_PORT=29092
|
KAFKA_PORT=29092
|
||||||
|
PCP_KAFKA_HOST=kafka
|
||||||
|
PCP_KAFKA_PORT=9092
|
||||||
|
EXTERNAL_KAFKA_CONTAINER=
|
||||||
|
|
||||||
|
CAMUNDA_CLIENT_ENABLED=false
|
||||||
|
CAMUNDA_GRPC_ADDRESS=http://host.docker.internal:26500
|
||||||
|
|
||||||
CONFIG_SERVER_PORT=38888
|
CONFIG_SERVER_PORT=38888
|
||||||
|
|
||||||
PCP_ADMIN_PORT=7000
|
PCP_ADMIN_PORT=7000
|
||||||
PCP_TLE_MONITORING_PORT=7001
|
|
||||||
PCP_COMPLEX_MISSION_PORT=7002
|
PCP_COMPLEX_MISSION_PORT=7002
|
||||||
PCP_BALLISTICS_PORT=7003
|
PCP_BALLISTICS_PORT=7003
|
||||||
|
PCP_ROUTE_PROCESSING_PORT=7004
|
||||||
PCP_REQUEST_PORT=7005
|
PCP_REQUEST_PORT=7005
|
||||||
PCP_SLOTS_PORT=7006
|
PCP_SLOTS_PORT=7006
|
||||||
PCP_UI_PORT=7008
|
PCP_UI_PORT=7008
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
Локальный bundle предназначен для запуска PCP на машине с Docker без GitLab Config Repo, Kubernetes и Helm.
|
Локальный bundle предназначен для запуска PCP на машине с Docker без GitLab Config Repo, Kubernetes и Helm.
|
||||||
|
|
||||||
|
В bundle входят backend-сервисы PCP, кроме `tle-monitoring-service`; отдельный frontend
|
||||||
|
`pcp-tgu-ops-ui` также не включается.
|
||||||
|
|
||||||
## Состав
|
## Состав
|
||||||
|
|
||||||
- `docker-compose.yml` - локальная схема запуска.
|
- `docker-compose.yml` - локальная схема запуска.
|
||||||
@@ -15,6 +18,72 @@
|
|||||||
- `scripts/stop.sh` - остановка compose stack.
|
- `scripts/stop.sh` - остановка compose stack.
|
||||||
- `scripts/status.sh` - статус контейнеров.
|
- `scripts/status.sh` - статус контейнеров.
|
||||||
|
|
||||||
|
## Локальный запуск без архива
|
||||||
|
|
||||||
|
Для запуска на той же машине, где собираются images, архив и `docker save` не нужны.
|
||||||
|
Используйте один скрипт:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-bundle/run-local.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт:
|
||||||
|
|
||||||
|
- создаёт `.env` из `.env.example`, если его ещё нет;
|
||||||
|
- собирает `bootJar` в Gradle offline mode;
|
||||||
|
- собирает локальные Docker images `pcp/<service>:<PCP_IMAGE_TAG>`;
|
||||||
|
- запускает `docker compose`.
|
||||||
|
|
||||||
|
Для вашего allinone Postgres/Kafka/Camunda перед первым запуском настройте
|
||||||
|
`deploy/local-bundle/.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
USE_EXTERNAL_INFRA=true
|
||||||
|
PCP_DOCKER_NETWORK=allinone_camunda
|
||||||
|
PCP_DOCKER_NETWORK_EXTERNAL=true
|
||||||
|
PCP_POSTGRES_HOST=postgres
|
||||||
|
PCP_POSTGRES_PORT=5432
|
||||||
|
PCP_KAFKA_HOST=kafka
|
||||||
|
PCP_KAFKA_PORT=9092
|
||||||
|
EXTERNAL_POSTGRES_CONTAINER=allinone-postgres-1
|
||||||
|
EXTERNAL_KAFKA_CONTAINER=allinone-kafka-1
|
||||||
|
CAMUNDA_CLIENT_ENABLED=true
|
||||||
|
CAMUNDA_GRPC_ADDRESS=http://orchestration:26500
|
||||||
|
```
|
||||||
|
|
||||||
|
Команды:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-bundle/run-local.sh start
|
||||||
|
./deploy/local-bundle/run-local.sh status
|
||||||
|
./deploy/local-bundle/run-local.sh logs
|
||||||
|
./deploy/local-bundle/run-local.sh stop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Пересборка одного сервиса
|
||||||
|
|
||||||
|
После первого запуска всего стека можно пересобирать и перезапускать только один сервис:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-bundle/services/pcp-request-service.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Это выполнит:
|
||||||
|
|
||||||
|
- `bootJar` только для `pcp-request-service`;
|
||||||
|
- `docker build` только для `pcp/pcp-request-service:<PCP_IMAGE_TAG>`;
|
||||||
|
- `docker compose up -d --no-deps --force-recreate pcp-request-service`.
|
||||||
|
|
||||||
|
Доступны wrapper-скрипты в `deploy/local-bundle/services/` для каждого сервиса.
|
||||||
|
То же самое можно вызвать через общий скрипт:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-bundle/run-service.sh pcp-request-service
|
||||||
|
./deploy/local-bundle/run-service.sh pcp-request-service build
|
||||||
|
./deploy/local-bundle/run-service.sh pcp-request-service logs
|
||||||
|
./deploy/local-bundle/run-service.sh pcp-request-service stop
|
||||||
|
```
|
||||||
|
|
||||||
## Сборка архива
|
## Сборка архива
|
||||||
|
|
||||||
Из корня репозитория:
|
Из корня репозитория:
|
||||||
@@ -38,6 +107,7 @@ deploy/local-bundle/build/pcp-local-bundle-<version>.tar.gz
|
|||||||
Скрипт сборки:
|
Скрипт сборки:
|
||||||
|
|
||||||
- собирает `bootJar` для разворачиваемых сервисов;
|
- собирает `bootJar` для разворачиваемых сервисов;
|
||||||
|
- запускает Gradle в offline mode по умолчанию;
|
||||||
- собирает Docker images сервисов;
|
- собирает Docker images сервисов;
|
||||||
- сохраняет PCP service images в один `images/pcp-services.tar`;
|
- сохраняет PCP service images в один `images/pcp-services.tar`;
|
||||||
- по умолчанию не сохраняет публичные infra images, чтобы не раздувать архив;
|
- по умолчанию не сохраняет публичные infra images, чтобы не раздувать архив;
|
||||||
@@ -61,6 +131,41 @@ KOTLIN_DAEMON_JVMARGS="-Xmx3g -XX:MaxMetaspaceSize=768m" \
|
|||||||
./deploy/local-bundle/build-local-bundle.sh
|
./deploy/local-bundle/build-local-bundle.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Gradle offline mode включён по умолчанию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GRADLE_OFFLINE=true ./deploy/local-bundle/build-local-bundle.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Если нужно один раз прогреть локальный Gradle cache, временно отключите offline mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GRADLE_OFFLINE=false ./deploy/local-bundle/build-local-bundle.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Важно: `GRADLE_OFFLINE=true` отключает сетевой резолв зависимостей Gradle, но Docker всё равно
|
||||||
|
должен иметь локально доступный base image. Для local bundle сервисные Dockerfile собираются с
|
||||||
|
локальным override:
|
||||||
|
|
||||||
|
```text
|
||||||
|
LOCAL_BUNDLE_RUNTIME_IMAGE=bellsoft/liberica-openjre-alpine:21.0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Поэтому перед полностью offline Docker-сборкой подтяните или импортируйте:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull bellsoft/liberica-openjre-alpine:21.0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Для полностью переносимого архива с infra images заранее подтяните Postgres/Kafka и включите их
|
||||||
|
сохранение:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull postgis/postgis:16-3.4
|
||||||
|
docker pull apache/kafka:3.7.2
|
||||||
|
INCLUDE_INFRA_IMAGES=true ./deploy/local-bundle/build-local-bundle.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Запуск на локальной машине
|
## Запуск на локальной машине
|
||||||
|
|
||||||
Распаковать архив:
|
Распаковать архив:
|
||||||
@@ -72,6 +177,26 @@ cd pcp-local-bundle-<version>
|
|||||||
|
|
||||||
Проверить `.env` и при необходимости изменить порты.
|
Проверить `.env` и при необходимости изменить порты.
|
||||||
|
|
||||||
|
Если Postgres/Kafka/Camunda уже подняты внешним allinone compose, например в сети
|
||||||
|
`allinone_camunda`, включите external infra mode в `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
USE_EXTERNAL_INFRA=true
|
||||||
|
PCP_DOCKER_NETWORK=allinone_camunda
|
||||||
|
PCP_DOCKER_NETWORK_EXTERNAL=true
|
||||||
|
PCP_POSTGRES_HOST=postgres
|
||||||
|
PCP_POSTGRES_PORT=5432
|
||||||
|
PCP_KAFKA_HOST=kafka
|
||||||
|
PCP_KAFKA_PORT=9092
|
||||||
|
EXTERNAL_POSTGRES_CONTAINER=allinone-postgres-1
|
||||||
|
EXTERNAL_KAFKA_CONTAINER=allinone-kafka-1
|
||||||
|
CAMUNDA_CLIENT_ENABLED=true
|
||||||
|
CAMUNDA_GRPC_ADDRESS=http://orchestration:26500
|
||||||
|
```
|
||||||
|
|
||||||
|
В этом режиме `scripts/start.sh` не поднимает свои `postgres` и `kafka`, а сервисы PCP
|
||||||
|
подключаются к уже существующим контейнерам через общую Docker network.
|
||||||
|
|
||||||
Импортировать images:
|
Импортировать images:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -109,9 +234,9 @@ cd pcp-local-bundle-<version>
|
|||||||
|
|
||||||
- Config Server: `http://localhost:38888`
|
- Config Server: `http://localhost:38888`
|
||||||
- Spring Boot Admin: `http://localhost:7000`
|
- Spring Boot Admin: `http://localhost:7000`
|
||||||
- TLE Monitoring: `http://localhost:7001`
|
|
||||||
- Complex Mission: `http://localhost:7002`
|
- Complex Mission: `http://localhost:7002`
|
||||||
- Ballistics: `http://localhost:7003`
|
- Ballistics: `http://localhost:7003`
|
||||||
|
- Route Processing: `http://localhost:7004`
|
||||||
- Request Service: `http://localhost:7005`
|
- Request Service: `http://localhost:7005`
|
||||||
- Slots Service: `http://localhost:7006`
|
- Slots Service: `http://localhost:7006`
|
||||||
- UI: `http://localhost:7008`
|
- UI: `http://localhost:7008`
|
||||||
@@ -178,13 +303,13 @@ SPRING_CLOUD_CONFIG_LABEL=
|
|||||||
|
|
||||||
- `pcp_ballistics`
|
- `pcp_ballistics`
|
||||||
- `pcp_requests`
|
- `pcp_requests`
|
||||||
|
- `pcp_route_processing`
|
||||||
- `pcp_missions`
|
- `pcp_missions`
|
||||||
- `pcp_slots`
|
- `pcp_slots`
|
||||||
- `pcp_complex_plan`
|
- `pcp_complex_plan`
|
||||||
- `pcp_satellite_catalog`
|
- `pcp_satellite_catalog`
|
||||||
- `pcp_stations`
|
- `pcp_stations`
|
||||||
- `pcp_tgu`
|
- `pcp_tgu`
|
||||||
- `pcp_tle`
|
|
||||||
- `pcp_satellites`
|
- `pcp_satellites`
|
||||||
|
|
||||||
Таблицы, схемы и seed data вручную не создаются. Схемы создаются существующими Flyway migrations внутри сервисов при старте приложений.
|
Таблицы, схемы и seed data вручную не создаются. Схемы создаются существующими Flyway migrations внутри сервисов при старте приложений.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ service_names=(
|
|||||||
pcp-satellite-catalog-service
|
pcp-satellite-catalog-service
|
||||||
pcp-ballistics-service
|
pcp-ballistics-service
|
||||||
pcp-request-service
|
pcp-request-service
|
||||||
|
pcp-route-processing-service
|
||||||
slots-service
|
slots-service
|
||||||
pcp-complex-mission-service
|
pcp-complex-mission-service
|
||||||
pcp-mission-planing-service
|
pcp-mission-planing-service
|
||||||
@@ -33,13 +34,14 @@ service_names=(
|
|||||||
pcp-dynamic-plan-service
|
pcp-dynamic-plan-service
|
||||||
pcp-stations-service
|
pcp-stations-service
|
||||||
pcp-ui-service
|
pcp-ui-service
|
||||||
tle-monitoring-service
|
|
||||||
pcp-tgu-service
|
pcp-tgu-service
|
||||||
)
|
)
|
||||||
|
|
||||||
gradle_max_workers="${GRADLE_MAX_WORKERS:-1}"
|
gradle_max_workers="${GRADLE_MAX_WORKERS:-1}"
|
||||||
gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}"
|
gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}"
|
||||||
kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}"
|
kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}"
|
||||||
|
gradle_offline="${GRADLE_OFFLINE:-true}"
|
||||||
|
runtime_image="${LOCAL_BUNDLE_RUNTIME_IMAGE:-bellsoft/liberica-openjre-alpine:21.0.5}"
|
||||||
include_infra_images="${INCLUDE_INFRA_IMAGES:-false}"
|
include_infra_images="${INCLUDE_INFRA_IMAGES:-false}"
|
||||||
|
|
||||||
gradle_common_args=(
|
gradle_common_args=(
|
||||||
@@ -49,6 +51,10 @@ gradle_common_args=(
|
|||||||
"-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs"
|
"-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if [[ "$gradle_offline" == "true" ]]; then
|
||||||
|
gradle_common_args+=("--offline")
|
||||||
|
fi
|
||||||
|
|
||||||
infra_images=(
|
infra_images=(
|
||||||
"${POSTGRES_IMAGE:-postgis/postgis:16-3.4}"
|
"${POSTGRES_IMAGE:-postgis/postgis:16-3.4}"
|
||||||
"${KAFKA_IMAGE:-apache/kafka:3.7.2}"
|
"${KAFKA_IMAGE:-apache/kafka:3.7.2}"
|
||||||
@@ -117,9 +123,11 @@ echo "Building bootJar artifacts..."
|
|||||||
echo "Gradle max workers: $gradle_max_workers"
|
echo "Gradle max workers: $gradle_max_workers"
|
||||||
echo "Gradle JVM args: $gradle_jvmargs"
|
echo "Gradle JVM args: $gradle_jvmargs"
|
||||||
echo "Kotlin daemon JVM args: $kotlin_daemon_jvmargs"
|
echo "Kotlin daemon JVM args: $kotlin_daemon_jvmargs"
|
||||||
|
echo "Gradle offline mode: $gradle_offline"
|
||||||
|
echo "Local bundle runtime image: $runtime_image"
|
||||||
for service_name in "${service_names[@]}"; do
|
for service_name in "${service_names[@]}"; do
|
||||||
echo "Building bootJar for $service_name..."
|
echo "Building bootJar for $service_name..."
|
||||||
"$repo_root/gradlew" "${gradle_common_args[@]}" ":services:$service_name:bootJar"
|
"$repo_root/gradlew" --project-dir "$repo_root" "${gradle_common_args[@]}" ":services:$service_name:bootJar"
|
||||||
done
|
done
|
||||||
|
|
||||||
rm -rf "$dist_dir" "$docker_contexts_dir" "$archive_path"
|
rm -rf "$dist_dir" "$docker_contexts_dir" "$archive_path"
|
||||||
@@ -132,7 +140,11 @@ for service_name in "${service_names[@]}"; do
|
|||||||
context_dir="$docker_contexts_dir/$service_name"
|
context_dir="$docker_contexts_dir/$service_name"
|
||||||
|
|
||||||
copy_single_boot_jar "$service_name" "$context_dir"
|
copy_single_boot_jar "$service_name" "$context_dir"
|
||||||
docker build --file "$context_dir/Dockerfile" --tag "$image_name" "$context_dir"
|
docker build \
|
||||||
|
--build-arg "RUNTIME_IMAGE=$runtime_image" \
|
||||||
|
--file "$context_dir/Dockerfile" \
|
||||||
|
--tag "$image_name" \
|
||||||
|
"$context_dir"
|
||||||
service_images+=("$image_name")
|
service_images+=("$image_name")
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,17 @@ x-pcp-service-env: &pcp-service-env
|
|||||||
SPRING_CLOUD_CONFIG_LABEL: ""
|
SPRING_CLOUD_CONFIG_LABEL: ""
|
||||||
SPRING_KAFKA_ADMIN_AUTO_CREATE: "true"
|
SPRING_KAFKA_ADMIN_AUTO_CREATE: "true"
|
||||||
SPRING_KAFKA_LISTENER_AUTO_STARTUP: "true"
|
SPRING_KAFKA_LISTENER_AUTO_STARTUP: "true"
|
||||||
|
PCP_POSTGRES_HOST: ${PCP_POSTGRES_HOST:-postgres}
|
||||||
|
PCP_POSTGRES_PORT: ${PCP_POSTGRES_PORT:-5432}
|
||||||
|
PCP_KAFKA_HOST: ${PCP_KAFKA_HOST:-kafka}
|
||||||
|
PCP_KAFKA_PORT: ${PCP_KAFKA_PORT:-9092}
|
||||||
|
CAMUNDA_CLIENT_ENABLED: ${CAMUNDA_CLIENT_ENABLED:-false}
|
||||||
|
CAMUNDA_GRPC_ADDRESS: ${CAMUNDA_GRPC_ADDRESS:-http://host.docker.internal:26500}
|
||||||
|
|
||||||
x-pcp-service-common: &pcp-service-common
|
x-pcp-service-common: &pcp-service-common
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
depends_on:
|
depends_on:
|
||||||
spring-cloud-config-server:
|
spring-cloud-config-server:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
@@ -64,11 +72,8 @@ services:
|
|||||||
spring-cloud-config-server:
|
spring-cloud-config-server:
|
||||||
image: pcp/spring-cloud-config-server:${PCP_IMAGE_TAG:-local}
|
image: pcp/spring-cloud-config-server:${PCP_IMAGE_TAG:-local}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
extra_hosts:
|
||||||
postgres:
|
- "host.docker.internal:host-gateway"
|
||||||
condition: service_healthy
|
|
||||||
kafka:
|
|
||||||
condition: service_started
|
|
||||||
environment:
|
environment:
|
||||||
SPRING_PROFILES_ACTIVE: local
|
SPRING_PROFILES_ACTIVE: local
|
||||||
CONFIG_NATIVE_SEARCH_LOCATIONS: file:/config-repo
|
CONFIG_NATIVE_SEARCH_LOCATIONS: file:/config-repo
|
||||||
@@ -86,14 +91,6 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${PCP_ADMIN_PORT:-7000}:7000"
|
- "${PCP_ADMIN_PORT:-7000}:7000"
|
||||||
|
|
||||||
tle-monitoring-service:
|
|
||||||
<<: *pcp-service-common
|
|
||||||
image: pcp/tle-monitoring-service:${PCP_IMAGE_TAG:-local}
|
|
||||||
environment:
|
|
||||||
<<: *pcp-service-env
|
|
||||||
ports:
|
|
||||||
- "${PCP_TLE_MONITORING_PORT:-7001}:7001"
|
|
||||||
|
|
||||||
pcp-complex-mission-service:
|
pcp-complex-mission-service:
|
||||||
<<: *pcp-service-common
|
<<: *pcp-service-common
|
||||||
image: pcp/pcp-complex-mission-service:${PCP_IMAGE_TAG:-local}
|
image: pcp/pcp-complex-mission-service:${PCP_IMAGE_TAG:-local}
|
||||||
@@ -118,6 +115,14 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${PCP_REQUEST_PORT:-7005}:7005"
|
- "${PCP_REQUEST_PORT:-7005}:7005"
|
||||||
|
|
||||||
|
pcp-route-processing-service:
|
||||||
|
<<: *pcp-service-common
|
||||||
|
image: pcp/pcp-route-processing-service:${PCP_IMAGE_TAG:-local}
|
||||||
|
environment:
|
||||||
|
<<: *pcp-service-env
|
||||||
|
ports:
|
||||||
|
- "${PCP_ROUTE_PROCESSING_PORT:-7004}:7004"
|
||||||
|
|
||||||
slots-service:
|
slots-service:
|
||||||
<<: *pcp-service-common
|
<<: *pcp-service-common
|
||||||
image: pcp/slots-service:${PCP_IMAGE_TAG:-local}
|
image: pcp/slots-service:${PCP_IMAGE_TAG:-local}
|
||||||
@@ -193,3 +198,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
kafka-data:
|
kafka-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: ${PCP_DOCKER_NETWORK:-pcp-local_default}
|
||||||
|
external: ${PCP_DOCKER_NETWORK_EXTERNAL:-false}
|
||||||
|
|||||||
Executable
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
||||||
|
env_file="$script_dir/.env"
|
||||||
|
env_example_file="$script_dir/.env.example"
|
||||||
|
docker_contexts_dir="$script_dir/build/local-docker-contexts"
|
||||||
|
|
||||||
|
service_names=(
|
||||||
|
spring-cloud-config-server
|
||||||
|
pcp-srpring-boot-admin-server
|
||||||
|
pcp-satellite-catalog-service
|
||||||
|
pcp-ballistics-service
|
||||||
|
pcp-request-service
|
||||||
|
pcp-route-processing-service
|
||||||
|
slots-service
|
||||||
|
pcp-complex-mission-service
|
||||||
|
pcp-mission-planing-service
|
||||||
|
pcp-coverage-scheme-service
|
||||||
|
pcp-dynamic-plan-service
|
||||||
|
pcp-stations-service
|
||||||
|
pcp-ui-service
|
||||||
|
pcp-tgu-service
|
||||||
|
)
|
||||||
|
|
||||||
|
ensure_env_file() {
|
||||||
|
if [[ ! -f "$env_file" && -f "$env_example_file" ]]; then
|
||||||
|
cp "$env_example_file" "$env_file"
|
||||||
|
echo "Created $env_file from .env.example"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_source_config_repo_dir() {
|
||||||
|
if grep -q '^CONFIG_REPO_DIR=' "$env_file"; then
|
||||||
|
sed -i "s#^CONFIG_REPO_DIR=.*#CONFIG_REPO_DIR=$repo_root/config-repo#" "$env_file"
|
||||||
|
else
|
||||||
|
printf '\nCONFIG_REPO_DIR=%s/config-repo\n' "$repo_root" >> "$env_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
load_env() {
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$env_file"
|
||||||
|
set +a
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local command_name="$1"
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
echo "$command_name is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
docker compose --project-directory "$script_dir" -f "$script_dir/docker-compose.yml" "$@"
|
||||||
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
|
docker-compose --project-directory "$script_dir" -f "$script_dir/docker-compose.yml" "$@"
|
||||||
|
else
|
||||||
|
echo "docker compose or docker-compose is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_single_boot_jar() {
|
||||||
|
local service_name="$1"
|
||||||
|
local context_dir="$2"
|
||||||
|
local jar_count
|
||||||
|
local jar_file
|
||||||
|
|
||||||
|
jar_count="$(
|
||||||
|
find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | wc -l
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$jar_count" != "1" ]]; then
|
||||||
|
echo "Expected exactly one bootJar for $service_name, found $jar_count:" >&2
|
||||||
|
find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' -print >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
jar_file="$(
|
||||||
|
find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1
|
||||||
|
)"
|
||||||
|
|
||||||
|
mkdir -p "$context_dir/build/libs"
|
||||||
|
cp "$repo_root/services/$service_name/Dockerfile" "$context_dir/Dockerfile"
|
||||||
|
cp "$jar_file" "$context_dir/build/libs/app.jar"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_images() {
|
||||||
|
local gradle_max_workers="${GRADLE_MAX_WORKERS:-1}"
|
||||||
|
local gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}"
|
||||||
|
local kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}"
|
||||||
|
local gradle_offline="${GRADLE_OFFLINE:-true}"
|
||||||
|
local runtime_image="${LOCAL_BUNDLE_RUNTIME_IMAGE:-bellsoft/liberica-openjre-alpine:21.0.5}"
|
||||||
|
local image_tag="${PCP_IMAGE_TAG:-local}"
|
||||||
|
local service_name
|
||||||
|
local image_name
|
||||||
|
local context_dir
|
||||||
|
local gradle_common_args=(
|
||||||
|
"--no-parallel"
|
||||||
|
"--max-workers=$gradle_max_workers"
|
||||||
|
"-Dorg.gradle.jvmargs=$gradle_jvmargs"
|
||||||
|
"-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "$gradle_offline" == "true" ]]; then
|
||||||
|
gradle_common_args+=("--offline")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building local Docker images..."
|
||||||
|
echo "Gradle offline mode: $gradle_offline"
|
||||||
|
echo "Local image tag: $image_tag"
|
||||||
|
echo "Runtime image: $runtime_image"
|
||||||
|
|
||||||
|
for service_name in "${service_names[@]}"; do
|
||||||
|
echo "Building bootJar for $service_name..."
|
||||||
|
"$repo_root/gradlew" --project-dir "$repo_root" "${gradle_common_args[@]}" ":services:$service_name:bootJar"
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -rf "$docker_contexts_dir"
|
||||||
|
mkdir -p "$docker_contexts_dir"
|
||||||
|
|
||||||
|
for service_name in "${service_names[@]}"; do
|
||||||
|
image_name="pcp/$service_name:$image_tag"
|
||||||
|
context_dir="$docker_contexts_dir/$service_name"
|
||||||
|
|
||||||
|
copy_single_boot_jar "$service_name" "$context_dir"
|
||||||
|
echo "Building Docker image $image_name..."
|
||||||
|
docker build \
|
||||||
|
--build-arg "RUNTIME_IMAGE=$runtime_image" \
|
||||||
|
--file "$context_dir/Dockerfile" \
|
||||||
|
--tag "$image_name" \
|
||||||
|
"$context_dir"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./deploy/local-bundle/run-local.sh [command]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
start Build service images locally and start docker compose.
|
||||||
|
restart Stop compose stack, then build and start.
|
||||||
|
build Build service images locally, without starting compose.
|
||||||
|
stop Stop compose stack.
|
||||||
|
status Show compose status.
|
||||||
|
logs Follow compose logs.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
start_local() {
|
||||||
|
build_images
|
||||||
|
"$script_dir/scripts/start.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
command="${1:-start}"
|
||||||
|
case "$command" in
|
||||||
|
help|-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
ensure_env_file
|
||||||
|
ensure_source_config_repo_dir
|
||||||
|
load_env
|
||||||
|
require_command docker
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
start)
|
||||||
|
start_local
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
"$script_dir/scripts/stop.sh"
|
||||||
|
start_local
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
build_images
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
"$script_dir/scripts/stop.sh"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
"$script_dir/scripts/status.sh"
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
compose logs -f
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+242
@@ -0,0 +1,242 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
||||||
|
env_file="$script_dir/.env"
|
||||||
|
env_example_file="$script_dir/.env.example"
|
||||||
|
docker_contexts_dir="$script_dir/build/local-docker-contexts"
|
||||||
|
|
||||||
|
service_names=(
|
||||||
|
spring-cloud-config-server
|
||||||
|
pcp-srpring-boot-admin-server
|
||||||
|
pcp-satellite-catalog-service
|
||||||
|
pcp-ballistics-service
|
||||||
|
pcp-request-service
|
||||||
|
pcp-route-processing-service
|
||||||
|
slots-service
|
||||||
|
pcp-complex-mission-service
|
||||||
|
pcp-mission-planing-service
|
||||||
|
pcp-coverage-scheme-service
|
||||||
|
pcp-dynamic-plan-service
|
||||||
|
pcp-stations-service
|
||||||
|
pcp-ui-service
|
||||||
|
pcp-tgu-service
|
||||||
|
)
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./deploy/local-bundle/run-service.sh SERVICE [command]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
restart Build SERVICE bootJar, rebuild its Docker image and recreate only this compose service.
|
||||||
|
build Build SERVICE bootJar and Docker image, without restarting container.
|
||||||
|
stop Stop SERVICE container.
|
||||||
|
logs Follow SERVICE logs.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./deploy/local-bundle/run-service.sh pcp-request-service
|
||||||
|
./deploy/local-bundle/run-service.sh pcp-request-service build
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_env_file() {
|
||||||
|
if [[ ! -f "$env_file" && -f "$env_example_file" ]]; then
|
||||||
|
cp "$env_example_file" "$env_file"
|
||||||
|
echo "Created $env_file from .env.example"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_source_config_repo_dir() {
|
||||||
|
if grep -q '^CONFIG_REPO_DIR=' "$env_file"; then
|
||||||
|
sed -i "s#^CONFIG_REPO_DIR=.*#CONFIG_REPO_DIR=$repo_root/config-repo#" "$env_file"
|
||||||
|
else
|
||||||
|
printf '\nCONFIG_REPO_DIR=%s/config-repo\n' "$repo_root" >> "$env_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
load_env() {
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$env_file"
|
||||||
|
set +a
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local command_name="$1"
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
echo "$command_name is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
docker compose --project-directory "$script_dir" -f "$script_dir/docker-compose.yml" "$@"
|
||||||
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
|
docker-compose --project-directory "$script_dir" -f "$script_dir/docker-compose.yml" "$@"
|
||||||
|
else
|
||||||
|
echo "docker compose or docker-compose is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_known_service() {
|
||||||
|
local service_name="$1"
|
||||||
|
local known_service_name
|
||||||
|
|
||||||
|
for known_service_name in "${service_names[@]}"; do
|
||||||
|
if [[ "$known_service_name" == "$service_name" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_single_boot_jar() {
|
||||||
|
local service_name="$1"
|
||||||
|
local context_dir="$2"
|
||||||
|
local jar_count
|
||||||
|
local jar_file
|
||||||
|
|
||||||
|
jar_count="$(
|
||||||
|
find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | wc -l
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$jar_count" != "1" ]]; then
|
||||||
|
echo "Expected exactly one bootJar for $service_name, found $jar_count:" >&2
|
||||||
|
find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' -print >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
jar_file="$(
|
||||||
|
find "$repo_root/services/$service_name/build/libs" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1
|
||||||
|
)"
|
||||||
|
|
||||||
|
mkdir -p "$context_dir/build/libs"
|
||||||
|
cp "$repo_root/services/$service_name/Dockerfile" "$context_dir/Dockerfile"
|
||||||
|
cp "$jar_file" "$context_dir/build/libs/app.jar"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_service_image() {
|
||||||
|
local service_name="$1"
|
||||||
|
local gradle_max_workers="${GRADLE_MAX_WORKERS:-1}"
|
||||||
|
local gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}"
|
||||||
|
local kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}"
|
||||||
|
local gradle_offline="${GRADLE_OFFLINE:-true}"
|
||||||
|
local runtime_image="${LOCAL_BUNDLE_RUNTIME_IMAGE:-bellsoft/liberica-openjre-alpine:21.0.5}"
|
||||||
|
local image_tag="${PCP_IMAGE_TAG:-local}"
|
||||||
|
local image_name="pcp/$service_name:$image_tag"
|
||||||
|
local context_dir="$docker_contexts_dir/$service_name"
|
||||||
|
local gradle_common_args=(
|
||||||
|
"--no-parallel"
|
||||||
|
"--max-workers=$gradle_max_workers"
|
||||||
|
"-Dorg.gradle.jvmargs=$gradle_jvmargs"
|
||||||
|
"-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "$gradle_offline" == "true" ]]; then
|
||||||
|
gradle_common_args+=("--offline")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building bootJar for $service_name..."
|
||||||
|
"$repo_root/gradlew" --project-dir "$repo_root" "${gradle_common_args[@]}" ":services:$service_name:bootJar"
|
||||||
|
|
||||||
|
rm -rf "$context_dir"
|
||||||
|
copy_single_boot_jar "$service_name" "$context_dir"
|
||||||
|
|
||||||
|
echo "Building Docker image $image_name..."
|
||||||
|
docker build \
|
||||||
|
--build-arg "RUNTIME_IMAGE=$runtime_image" \
|
||||||
|
--file "$context_dir/Dockerfile" \
|
||||||
|
--tag "$image_name" \
|
||||||
|
"$context_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
http_get() {
|
||||||
|
local url="$1"
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -fsS "$url" >/dev/null
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -q -O /dev/null "$url"
|
||||||
|
else
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_config_server() {
|
||||||
|
local attempt
|
||||||
|
local url="http://localhost:${CONFIG_SERVER_PORT:-38888}/actuator/health"
|
||||||
|
|
||||||
|
for attempt in {1..90}; do
|
||||||
|
if http_get "$url"; then
|
||||||
|
echo "spring-cloud-config-server is ready: $url"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "spring-cloud-config-server is not ready after 180 seconds: $url" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_service() {
|
||||||
|
local service_name="$1"
|
||||||
|
|
||||||
|
build_service_image "$service_name"
|
||||||
|
|
||||||
|
if [[ "$service_name" == "spring-cloud-config-server" ]]; then
|
||||||
|
compose up -d --no-deps --force-recreate "$service_name"
|
||||||
|
wait_for_config_server
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
compose up -d spring-cloud-config-server
|
||||||
|
wait_for_config_server
|
||||||
|
compose up -d --no-deps --force-recreate "$service_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
service_name="${1:-}"
|
||||||
|
command="${2:-restart}"
|
||||||
|
|
||||||
|
if [[ -z "$service_name" || "$service_name" == "help" || "$service_name" == "-h" || "$service_name" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_known_service "$service_name"; then
|
||||||
|
echo "Unknown service: $service_name" >&2
|
||||||
|
echo "Known services:" >&2
|
||||||
|
printf ' %s\n' "${service_names[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_env_file
|
||||||
|
ensure_source_config_repo_dir
|
||||||
|
load_env
|
||||||
|
require_command docker
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
restart)
|
||||||
|
restart_service "$service_name"
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
build_service_image "$service_name"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
compose stop "$service_name"
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
compose logs -f "$service_name"
|
||||||
|
;;
|
||||||
|
help|-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -25,6 +25,10 @@ ensure_env_file() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure_env_file
|
ensure_env_file
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$bundle_dir/.env"
|
||||||
|
set +a
|
||||||
|
|
||||||
if [[ ! -d "$images_dir" ]]; then
|
if [[ ! -d "$images_dir" ]]; then
|
||||||
echo "Images directory not found: $images_dir" >&2
|
echo "Images directory not found: $images_dir" >&2
|
||||||
@@ -49,7 +53,12 @@ if [[ -f "$images_dir/public-images.txt" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$bundle_dir"
|
cd "$bundle_dir"
|
||||||
|
if [[ "${USE_EXTERNAL_INFRA:-false}" == "true" ]]; then
|
||||||
|
echo "Using external infrastructure. Starting spring-cloud-config-server only."
|
||||||
|
compose up -d spring-cloud-config-server
|
||||||
|
else
|
||||||
echo "Starting postgres, kafka and spring-cloud-config-server..."
|
echo "Starting postgres, kafka and spring-cloud-config-server..."
|
||||||
compose up -d postgres kafka spring-cloud-config-server
|
compose up -d postgres kafka spring-cloud-config-server
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Images imported and base services requested."
|
echo "Images imported and base services requested."
|
||||||
|
|||||||
@@ -43,6 +43,26 @@ wait_for_postgres() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_external_postgres() {
|
||||||
|
local container_name="${EXTERNAL_POSTGRES_CONTAINER:-}"
|
||||||
|
local attempt
|
||||||
|
|
||||||
|
if [[ -z "$container_name" ]]; then
|
||||||
|
echo "EXTERNAL_POSTGRES_CONTAINER is required when USE_EXTERNAL_INFRA=true and SKIP_DB_INIT is not true." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for attempt in {1..60}; do
|
||||||
|
if docker exec "$container_name" pg_isready -U "${POSTGRES_USER:-postgres}" -d postgres >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "External Postgres is not ready after 120 seconds: $container_name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
create_database_if_missing() {
|
create_database_if_missing() {
|
||||||
local db_name="$1"
|
local db_name="$1"
|
||||||
local exists
|
local exists
|
||||||
@@ -61,16 +81,35 @@ create_database_if_missing() {
|
|||||||
compose exec -T postgres createdb -U "${POSTGRES_USER:-postgres}" "$db_name"
|
compose exec -T postgres createdb -U "${POSTGRES_USER:-postgres}" "$db_name"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_external_database_if_missing() {
|
||||||
|
local db_name="$1"
|
||||||
|
local container_name="${EXTERNAL_POSTGRES_CONTAINER:-}"
|
||||||
|
local exists
|
||||||
|
|
||||||
|
exists="$(
|
||||||
|
docker exec "$container_name" psql -U "${POSTGRES_USER:-postgres}" -d postgres -tAc \
|
||||||
|
"SELECT 1 FROM pg_database WHERE datname = '$db_name'" | tr -d '[:space:]'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$exists" == "1" ]]; then
|
||||||
|
echo "Database already exists: $db_name"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating database: $db_name"
|
||||||
|
docker exec "$container_name" createdb -U "${POSTGRES_USER:-postgres}" "$db_name"
|
||||||
|
}
|
||||||
|
|
||||||
databases=(
|
databases=(
|
||||||
pcp_ballistics
|
pcp_ballistics
|
||||||
pcp_requests
|
pcp_requests
|
||||||
|
pcp_route_processing
|
||||||
pcp_missions
|
pcp_missions
|
||||||
pcp_slots
|
pcp_slots
|
||||||
pcp_complex_plan
|
pcp_complex_plan
|
||||||
pcp_satellite_catalog
|
pcp_satellite_catalog
|
||||||
pcp_stations
|
pcp_stations
|
||||||
pcp_tgu
|
pcp_tgu
|
||||||
pcp_tle
|
|
||||||
pcp_satellites
|
pcp_satellites
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,12 +117,20 @@ ensure_env_file
|
|||||||
load_env
|
load_env
|
||||||
|
|
||||||
cd "$bundle_dir"
|
cd "$bundle_dir"
|
||||||
|
if [[ "${USE_EXTERNAL_INFRA:-false}" == "true" ]]; then
|
||||||
|
wait_for_external_postgres
|
||||||
|
|
||||||
|
for db_name in "${databases[@]}"; do
|
||||||
|
create_external_database_if_missing "$db_name"
|
||||||
|
done
|
||||||
|
else
|
||||||
compose up -d postgres
|
compose up -d postgres
|
||||||
wait_for_postgres
|
wait_for_postgres
|
||||||
|
|
||||||
for db_name in "${databases[@]}"; do
|
for db_name in "${databases[@]}"; do
|
||||||
create_database_if_missing "$db_name"
|
create_database_if_missing "$db_name"
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Database initialization completed. Tables are left to service Flyway migrations."
|
echo "Database initialization completed. Tables are left to service Flyway migrations."
|
||||||
echo "No schema objects or seed data were created by this script."
|
echo "No schema objects or seed data were created by this script."
|
||||||
|
|||||||
@@ -72,11 +72,34 @@ wait_for_kafka() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_external_kafka() {
|
||||||
|
local container_name="${EXTERNAL_KAFKA_CONTAINER:-}"
|
||||||
|
local attempt
|
||||||
|
|
||||||
|
if [[ -z "$container_name" ]]; then
|
||||||
|
echo "External Kafka container is not configured; skipping Kafka readiness check."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for attempt in {1..60}; do
|
||||||
|
if docker exec "$container_name" kafka-topics --bootstrap-server "${PCP_KAFKA_HOST:-kafka}:${PCP_KAFKA_PORT:-9092}" --list >/dev/null 2>&1 ||
|
||||||
|
docker exec "$container_name" kafka-topics.sh --bootstrap-server "${PCP_KAFKA_HOST:-kafka}:${PCP_KAFKA_PORT:-9092}" --list >/dev/null 2>&1; then
|
||||||
|
echo "External Kafka is ready in container $container_name."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "External Kafka is not ready after 120 seconds: $container_name" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
app_services=(
|
app_services=(
|
||||||
pcp-srpring-boot-admin-server
|
pcp-srpring-boot-admin-server
|
||||||
pcp-satellite-catalog-service
|
pcp-satellite-catalog-service
|
||||||
pcp-ballistics-service
|
pcp-ballistics-service
|
||||||
pcp-request-service
|
pcp-request-service
|
||||||
|
pcp-route-processing-service
|
||||||
slots-service
|
slots-service
|
||||||
pcp-complex-mission-service
|
pcp-complex-mission-service
|
||||||
pcp-mission-planing-service
|
pcp-mission-planing-service
|
||||||
@@ -84,7 +107,6 @@ app_services=(
|
|||||||
pcp-dynamic-plan-service
|
pcp-dynamic-plan-service
|
||||||
pcp-stations-service
|
pcp-stations-service
|
||||||
pcp-ui-service
|
pcp-ui-service
|
||||||
tle-monitoring-service
|
|
||||||
pcp-tgu-service
|
pcp-tgu-service
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,9 +115,16 @@ load_env
|
|||||||
|
|
||||||
cd "$bundle_dir"
|
cd "$bundle_dir"
|
||||||
|
|
||||||
|
if [[ "${USE_EXTERNAL_INFRA:-false}" == "true" ]]; then
|
||||||
|
echo "Using external infrastructure. Skipping local postgres/kafka startup."
|
||||||
|
echo "Postgres endpoint for services: ${PCP_POSTGRES_HOST:-postgres}:${PCP_POSTGRES_PORT:-5432}"
|
||||||
|
echo "Kafka endpoint for services: ${PCP_KAFKA_HOST:-kafka}:${PCP_KAFKA_PORT:-9092}"
|
||||||
|
wait_for_external_kafka
|
||||||
|
else
|
||||||
echo "Starting infrastructure..."
|
echo "Starting infrastructure..."
|
||||||
compose up -d postgres kafka
|
compose up -d postgres kafka
|
||||||
wait_for_kafka
|
wait_for_kafka
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${SKIP_DB_INIT:-false}" != "true" ]]; then
|
if [[ "${SKIP_DB_INIT:-false}" != "true" ]]; then
|
||||||
"$script_dir/init-db.sh"
|
"$script_dir/init-db.sh"
|
||||||
@@ -106,7 +135,10 @@ compose up -d spring-cloud-config-server
|
|||||||
wait_for_http "spring-cloud-config-server" "http://localhost:${CONFIG_SERVER_PORT:-38888}/actuator/health"
|
wait_for_http "spring-cloud-config-server" "http://localhost:${CONFIG_SERVER_PORT:-38888}/actuator/health"
|
||||||
|
|
||||||
echo "Starting application services..."
|
echo "Starting application services..."
|
||||||
compose up -d "${app_services[@]}"
|
for service_name in "${app_services[@]}"; do
|
||||||
|
echo "Starting $service_name..."
|
||||||
|
compose up -d "$service_name"
|
||||||
|
done
|
||||||
|
|
||||||
echo "Application startup requested."
|
echo "Application startup requested."
|
||||||
compose ps
|
compose ps
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-ballistics-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-complex-mission-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-coverage-scheme-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-dynamic-plan-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-mission-planing-service "$@"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-request-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-route-processing-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-satellite-catalog-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-srpring-boot-admin-server "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-stations-service "$@"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-tgu-service "$@"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" pcp-ui-service "$@"
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" slots-service "$@"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
exec "$script_dir/run-service.sh" spring-cloud-config-server "$@"
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Local jar runner
|
||||||
|
|
||||||
|
Скрипт запускает backend-сервисы как Spring Boot jar'ы на локальной машине.
|
||||||
|
Spring Cloud Config Server стартует первым и читает конфигурацию из локального
|
||||||
|
`config-repo`.
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
Из корня репозитория:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-jar/run-services.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
Что делает `start`:
|
||||||
|
|
||||||
|
- собирает `bootJar` для `spring-cloud-config-server` и всех Spring Boot сервисов;
|
||||||
|
- поднимает локальные `postgres` и `kafka` через `deploy/local-bundle/docker-compose.yml`;
|
||||||
|
- создаёт сервисные базы данных, если их ещё нет;
|
||||||
|
- запускает `spring-cloud-config-server` на `http://localhost:38888`;
|
||||||
|
- проверяет lookup конфигурации из `config-repo`;
|
||||||
|
- запускает остальные jar'ы в заданной последовательности.
|
||||||
|
|
||||||
|
Runtime-файлы создаются в `build/local-jar-runtime`:
|
||||||
|
|
||||||
|
- `pids/` - PID процессов;
|
||||||
|
- `logs/` - stdout/stderr каждого сервиса.
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-jar/run-services.sh status
|
||||||
|
./deploy/local-jar/run-services.sh logs pcp-request-service
|
||||||
|
./deploy/local-jar/run-services.sh stop
|
||||||
|
./deploy/local-jar/run-services.sh restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Только сборка:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-jar/run-services.sh build
|
||||||
|
```
|
||||||
|
|
||||||
|
Только инфраструктура:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/local-jar/run-services.sh infra-up
|
||||||
|
./deploy/local-jar/run-services.sh infra-down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройки
|
||||||
|
|
||||||
|
По умолчанию config-server использует:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SPRING_PROFILES_ACTIVE=local
|
||||||
|
CONFIG_NATIVE_SEARCH_LOCATIONS=file:<repo>/config-repo
|
||||||
|
SERVER_PORT=38888
|
||||||
|
```
|
||||||
|
|
||||||
|
Сервисы используют:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SPRING_PROFILES_ACTIVE=local
|
||||||
|
SPRING_CLOUD_CONFIG_PROFILE=local
|
||||||
|
SPRING_CLOUD_CONFIG_LABEL=
|
||||||
|
CONFIG_SERVER_URI=http://localhost:38888
|
||||||
|
```
|
||||||
|
|
||||||
|
Полезные override'ы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
START_INFRA=false ./deploy/local-jar/run-services.sh start
|
||||||
|
INIT_DB=false ./deploy/local-jar/run-services.sh start
|
||||||
|
WAIT_FOR_APPS=false ./deploy/local-jar/run-services.sh start
|
||||||
|
JAVA_OPTS="-Xmx1g" ./deploy/local-jar/run-services.sh start
|
||||||
|
CONFIG_REPO_DIR=/path/to/config-repo ./deploy/local-jar/run-services.sh start
|
||||||
|
LOCAL_SERVICE_PROFILES=local,foo ./deploy/local-jar/run-services.sh start
|
||||||
|
```
|
||||||
Executable
+538
@@ -0,0 +1,538 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
||||||
|
local_bundle_dir="$repo_root/deploy/local-bundle"
|
||||||
|
compose_file="$local_bundle_dir/docker-compose.yml"
|
||||||
|
local_bundle_env_file="$local_bundle_dir/.env"
|
||||||
|
|
||||||
|
if [[ -f "$local_bundle_env_file" ]]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$local_bundle_env_file"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
config_repo_dir="${CONFIG_REPO_DIR:-$repo_root/config-repo}"
|
||||||
|
runtime_dir="${LOCAL_JAR_RUNTIME_DIR:-$repo_root/build/local-jar-runtime}"
|
||||||
|
pid_dir="$runtime_dir/pids"
|
||||||
|
log_dir="$runtime_dir/logs"
|
||||||
|
|
||||||
|
if [[ "$config_repo_dir" != /* ]]; then
|
||||||
|
config_repo_dir="$repo_root/$config_repo_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$runtime_dir" != /* ]]; then
|
||||||
|
runtime_dir="$repo_root/$runtime_dir"
|
||||||
|
pid_dir="$runtime_dir/pids"
|
||||||
|
log_dir="$runtime_dir/logs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
config_service="spring-cloud-config-server"
|
||||||
|
app_services=(
|
||||||
|
pcp-srpring-boot-admin-server
|
||||||
|
pcp-stations-service
|
||||||
|
pcp-satellite-catalog-service
|
||||||
|
pcp-request-service
|
||||||
|
pcp-route-processing-service
|
||||||
|
pcp-ballistics-service
|
||||||
|
slots-service
|
||||||
|
pcp-complex-mission-service
|
||||||
|
pcp-mission-planing-service
|
||||||
|
pcp-coverage-scheme-service
|
||||||
|
pcp-dynamic-plan-service
|
||||||
|
tle-monitoring-service
|
||||||
|
pcp-tgu-service
|
||||||
|
pcp-ui-service
|
||||||
|
)
|
||||||
|
all_services=("$config_service" "${app_services[@]}")
|
||||||
|
|
||||||
|
databases=(
|
||||||
|
pcp_ballistics
|
||||||
|
pcp_requests
|
||||||
|
pcp_route_processing
|
||||||
|
pcp_missions
|
||||||
|
pcp_slots
|
||||||
|
pcp_complex_plan
|
||||||
|
pcp_satellite_catalog
|
||||||
|
pcp_stations
|
||||||
|
pcp_tgu
|
||||||
|
pcp_tle
|
||||||
|
pcp_satellites
|
||||||
|
)
|
||||||
|
|
||||||
|
declare -A service_ports=(
|
||||||
|
[spring-cloud-config-server]="${CONFIG_SERVER_PORT:-38888}"
|
||||||
|
[pcp-srpring-boot-admin-server]="${PCP_ADMIN_PORT:-7000}"
|
||||||
|
[tle-monitoring-service]="${PCP_TLE_MONITORING_PORT:-7001}"
|
||||||
|
[pcp-complex-mission-service]="${PCP_COMPLEX_MISSION_PORT:-7002}"
|
||||||
|
[pcp-ballistics-service]="${PCP_BALLISTICS_PORT:-7003}"
|
||||||
|
[pcp-route-processing-service]="${PCP_ROUTE_PROCESSING_PORT:-7004}"
|
||||||
|
[pcp-request-service]="${PCP_REQUEST_PORT:-7005}"
|
||||||
|
[slots-service]="${PCP_SLOTS_PORT:-7006}"
|
||||||
|
[pcp-ui-service]="${PCP_UI_PORT:-7008}"
|
||||||
|
[pcp-stations-service]="${PCP_STATIONS_PORT:-7009}"
|
||||||
|
[pcp-mission-planing-service]="${PCP_MISSION_PORT:-7010}"
|
||||||
|
[pcp-tgu-service]="${PCP_TGU_PORT:-7011}"
|
||||||
|
[pcp-coverage-scheme-service]="${PCP_COVERAGE_SCHEME_PORT:-7012}"
|
||||||
|
[pcp-satellite-catalog-service]="${PCP_SATELLITE_CATALOG_PORT:-7013}"
|
||||||
|
[pcp-dynamic-plan-service]="${PCP_DYNAMIC_PLAN_PORT:-7014}"
|
||||||
|
)
|
||||||
|
|
||||||
|
gradle_max_workers="${GRADLE_MAX_WORKERS:-1}"
|
||||||
|
gradle_jvmargs="${GRADLE_JVMARGS:--Xmx2g -Dfile.encoding=UTF-8}"
|
||||||
|
kotlin_daemon_jvmargs="${KOTLIN_DAEMON_JVMARGS:--Xmx3g -XX:MaxMetaspaceSize=768m}"
|
||||||
|
config_server_port="${service_ports[$config_service]}"
|
||||||
|
config_server_uri="${LOCAL_CONFIG_SERVER_URI:-http://localhost:$config_server_port}"
|
||||||
|
service_profiles="${LOCAL_SERVICE_PROFILES:-local}"
|
||||||
|
config_profiles="${LOCAL_CONFIG_PROFILES:-$service_profiles}"
|
||||||
|
postgres_host="${PCP_POSTGRES_HOST:-localhost}"
|
||||||
|
postgres_port="${PCP_POSTGRES_PORT:-5432}"
|
||||||
|
kafka_host="${PCP_KAFKA_HOST:-localhost}"
|
||||||
|
kafka_port="${PCP_KAFKA_PORT:-29092}"
|
||||||
|
|
||||||
|
gradle_common_args=(
|
||||||
|
"--no-parallel"
|
||||||
|
"--max-workers=$gradle_max_workers"
|
||||||
|
"-Dorg.gradle.jvmargs=$gradle_jvmargs"
|
||||||
|
"-Pkotlin.daemon.jvmargs=$kotlin_daemon_jvmargs"
|
||||||
|
)
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
./deploy/local-jar/run-services.sh [command]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
start Build bootJar artifacts and start local infra, config-server and services.
|
||||||
|
restart Stop local jar processes, then run start.
|
||||||
|
stop Stop jar processes started by this script.
|
||||||
|
status Show pid and log status for local jar processes.
|
||||||
|
build Build bootJar artifacts only.
|
||||||
|
infra-up Start local Postgres/Kafka via deploy/local-bundle/docker-compose.yml.
|
||||||
|
infra-down Stop local Postgres/Kafka containers.
|
||||||
|
logs NAME Tail log for a service.
|
||||||
|
|
||||||
|
Useful environment:
|
||||||
|
START_INFRA=false Do not start Postgres/Kafka containers during start.
|
||||||
|
INIT_DB=false Do not create local Postgres databases during start.
|
||||||
|
WAIT_FOR_APPS=false Do not wait for each app /actuator/health.
|
||||||
|
CONFIG_REPO_DIR=... Config repository used by spring-cloud-config-server.
|
||||||
|
JAVA_OPTS="-Xmx1g" Extra JVM options for every jar process.
|
||||||
|
LOCAL_SERVICE_PROFILES=local,foo
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
local command_name="$1"
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
echo "$command_name is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
docker compose --project-directory "$local_bundle_dir" -f "$compose_file" "$@"
|
||||||
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
|
docker-compose --project-directory "$local_bundle_dir" -f "$compose_file" "$@"
|
||||||
|
else
|
||||||
|
echo "docker compose or docker-compose is required." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
http_get() {
|
||||||
|
local url="$1"
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -fsS "$url" >/dev/null
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -q -O /dev/null "$url"
|
||||||
|
else
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_http() {
|
||||||
|
local name="$1"
|
||||||
|
local url="$2"
|
||||||
|
local attempts="${3:-90}"
|
||||||
|
local attempt
|
||||||
|
|
||||||
|
for ((attempt = 1; attempt <= attempts; attempt++)); do
|
||||||
|
if http_get "$url"; then
|
||||||
|
echo "$name is ready: $url"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$name is not ready after $((attempts * 2)) seconds: $url" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_postgres() {
|
||||||
|
local attempt
|
||||||
|
local postgres_user="${POSTGRES_USER:-postgres}"
|
||||||
|
|
||||||
|
for attempt in {1..60}; do
|
||||||
|
if compose exec -T postgres pg_isready -U "$postgres_user" -d postgres >/dev/null 2>&1; then
|
||||||
|
echo "Postgres is ready."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Postgres is not ready after 120 seconds." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_kafka() {
|
||||||
|
local attempt
|
||||||
|
for attempt in {1..60}; do
|
||||||
|
if compose exec -T kafka kafka-topics.sh --bootstrap-server kafka:9092 --list >/dev/null 2>&1; then
|
||||||
|
echo "Kafka is ready."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Kafka is not ready after 120 seconds." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
create_database_if_missing() {
|
||||||
|
local db_name="$1"
|
||||||
|
local postgres_user="${POSTGRES_USER:-postgres}"
|
||||||
|
local exists
|
||||||
|
|
||||||
|
exists="$(
|
||||||
|
compose exec -T postgres psql -U "$postgres_user" -d postgres -tAc \
|
||||||
|
"SELECT 1 FROM pg_database WHERE datname = '$db_name'" | tr -d '[:space:]'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$exists" == "1" ]]; then
|
||||||
|
echo "Database already exists: $db_name"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating database: $db_name"
|
||||||
|
compose exec -T postgres createdb -U "$postgres_user" "$db_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_infra() {
|
||||||
|
require_command docker
|
||||||
|
echo "Starting local Postgres and Kafka..."
|
||||||
|
compose up -d postgres kafka
|
||||||
|
wait_for_postgres
|
||||||
|
wait_for_kafka
|
||||||
|
}
|
||||||
|
|
||||||
|
init_databases() {
|
||||||
|
echo "Initializing local service databases..."
|
||||||
|
for db_name in "${databases[@]}"; do
|
||||||
|
create_database_if_missing "$db_name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
build_services() {
|
||||||
|
echo "Building bootJar artifacts..."
|
||||||
|
echo "Gradle max workers: $gradle_max_workers"
|
||||||
|
echo "Gradle JVM args: $gradle_jvmargs"
|
||||||
|
echo "Kotlin daemon JVM args: $kotlin_daemon_jvmargs"
|
||||||
|
|
||||||
|
for service_name in "${all_services[@]}"; do
|
||||||
|
echo "Building bootJar for $service_name..."
|
||||||
|
"$repo_root/gradlew" "${gradle_common_args[@]}" ":services:$service_name:bootJar"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
service_pid_file() {
|
||||||
|
local service_name="$1"
|
||||||
|
printf '%s/%s.pid' "$pid_dir" "$service_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
service_log_file() {
|
||||||
|
local service_name="$1"
|
||||||
|
printf '%s/%s.log' "$log_dir" "$service_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_pid_running() {
|
||||||
|
local pid="$1"
|
||||||
|
[[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
read_service_pid() {
|
||||||
|
local service_name="$1"
|
||||||
|
local pid_file
|
||||||
|
|
||||||
|
pid_file="$(service_pid_file "$service_name")"
|
||||||
|
if [[ -f "$pid_file" ]]; then
|
||||||
|
tr -d '[:space:]' < "$pid_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
find_boot_jar() {
|
||||||
|
local service_name="$1"
|
||||||
|
local jar_dir="$repo_root/services/$service_name/build/libs"
|
||||||
|
local jar_count
|
||||||
|
local jar_file
|
||||||
|
|
||||||
|
if [[ ! -d "$jar_dir" ]]; then
|
||||||
|
echo "Missing build/libs for $service_name. Run build first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
jar_count="$(find "$jar_dir" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | wc -l)"
|
||||||
|
if [[ "$jar_count" != "1" ]]; then
|
||||||
|
echo "Expected exactly one bootJar for $service_name, found $jar_count:" >&2
|
||||||
|
find "$jar_dir" -maxdepth 1 -type f -name '*.jar' -print >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
jar_file="$(find "$jar_dir" -maxdepth 1 -type f -name '*.jar' ! -name '*-plain.jar' | head -n 1)"
|
||||||
|
printf '%s' "$jar_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
local service_name="$1"
|
||||||
|
local jar_file
|
||||||
|
local pid_file
|
||||||
|
local log_file
|
||||||
|
local existing_pid
|
||||||
|
local port="${service_ports[$service_name]}"
|
||||||
|
local java_cmd=(java)
|
||||||
|
local parsed_java_opts=()
|
||||||
|
|
||||||
|
pid_file="$(service_pid_file "$service_name")"
|
||||||
|
log_file="$(service_log_file "$service_name")"
|
||||||
|
existing_pid="$(read_service_pid "$service_name" || true)"
|
||||||
|
|
||||||
|
if is_pid_running "$existing_pid"; then
|
||||||
|
echo "$service_name is already running with pid $existing_pid"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$pid_dir" "$log_dir"
|
||||||
|
jar_file="$(find_boot_jar "$service_name")"
|
||||||
|
if [[ -n "${JAVA_OPTS:-}" ]]; then
|
||||||
|
read -r -a parsed_java_opts <<< "$JAVA_OPTS"
|
||||||
|
java_cmd+=("${parsed_java_opts[@]}")
|
||||||
|
fi
|
||||||
|
java_cmd+=(-jar "$jar_file")
|
||||||
|
|
||||||
|
echo "Starting $service_name on port $port..."
|
||||||
|
if [[ "$service_name" == "$config_service" ]]; then
|
||||||
|
(
|
||||||
|
cd "$repo_root"
|
||||||
|
env \
|
||||||
|
SPRING_PROFILES_ACTIVE=local \
|
||||||
|
CONFIG_NATIVE_SEARCH_LOCATIONS="file:$config_repo_dir" \
|
||||||
|
SERVER_PORT="$config_server_port" \
|
||||||
|
"${java_cmd[@]}" > "$log_file" 2>&1 &
|
||||||
|
echo "$!" > "$pid_file"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
(
|
||||||
|
cd "$repo_root"
|
||||||
|
env \
|
||||||
|
CONFIG_SERVER_URI="$config_server_uri" \
|
||||||
|
CONFIG_SERVER_FAIL_FAST=true \
|
||||||
|
SPRING_PROFILES_ACTIVE="$service_profiles" \
|
||||||
|
SPRING_CLOUD_CONFIG_PROFILE="$config_profiles" \
|
||||||
|
SPRING_CLOUD_CONFIG_LABEL= \
|
||||||
|
SPRING_KAFKA_ADMIN_AUTO_CREATE=true \
|
||||||
|
SPRING_KAFKA_LISTENER_MISSING_TOPICS_FATAL=false \
|
||||||
|
CAMUNDA_CLIENT_ENABLED=false \
|
||||||
|
PLANNING_EXTERNAL_UPDATE_ENABLED=false \
|
||||||
|
TLE_POLLING_ENABLED=false \
|
||||||
|
PCP_POSTGRES_HOST="$postgres_host" \
|
||||||
|
PCP_POSTGRES_PORT="$postgres_port" \
|
||||||
|
PCP_KAFKA_HOST="$kafka_host" \
|
||||||
|
PCP_KAFKA_PORT="$kafka_port" \
|
||||||
|
PCP_PORTS_CONFIG="$config_server_port" \
|
||||||
|
PCP_PORTS_ADMIN="${service_ports[pcp-srpring-boot-admin-server]}" \
|
||||||
|
PCP_PORTS_TLE_MONITORING="${service_ports[tle-monitoring-service]}" \
|
||||||
|
PCP_PORTS_COMPLEX_MISSION_SERVICE="${service_ports[pcp-complex-mission-service]}" \
|
||||||
|
PCP_PORTS_BALLISTICS="${service_ports[pcp-ballistics-service]}" \
|
||||||
|
PCP_PORTS_ROUTE_PROCESSING="${service_ports[pcp-route-processing-service]}" \
|
||||||
|
PCP_PORTS_REQUEST="${service_ports[pcp-request-service]}" \
|
||||||
|
PCP_PORTS_SLOTS="${service_ports[slots-service]}" \
|
||||||
|
PCP_PORTS_UI="${service_ports[pcp-ui-service]}" \
|
||||||
|
PCP_PORTS_STATIONS="${service_ports[pcp-stations-service]}" \
|
||||||
|
PCP_PORTS_MISSION="${service_ports[pcp-mission-planing-service]}" \
|
||||||
|
PCP_PORTS_TGU="${service_ports[pcp-tgu-service]}" \
|
||||||
|
PCP_PORTS_COVERAGE_SCHEME="${service_ports[pcp-coverage-scheme-service]}" \
|
||||||
|
PCP_PORTS_SATELLITE_CATALOG="${service_ports[pcp-satellite-catalog-service]}" \
|
||||||
|
PCP_PORTS_DYNAMIC_PLAN="${service_ports[pcp-dynamic-plan-service]}" \
|
||||||
|
"${java_cmd[@]}" > "$log_file" 2>&1 &
|
||||||
|
echo "$!" > "$pid_file"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$service_name log: $log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_config_server() {
|
||||||
|
if [[ ! -d "$config_repo_dir" ]]; then
|
||||||
|
echo "Config repo directory does not exist: $config_repo_dir" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_service "$config_service"
|
||||||
|
wait_for_http "$config_service" "$config_server_uri/actuator/health"
|
||||||
|
wait_for_http "$config_service config-repo lookup" "$config_server_uri/pcp-request-service/local"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_service_health() {
|
||||||
|
local service_name="$1"
|
||||||
|
local port="${service_ports[$service_name]}"
|
||||||
|
wait_for_http "$service_name" "http://localhost:$port/actuator/health" "${APP_HEALTH_ATTEMPTS:-90}"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_app_services() {
|
||||||
|
for service_name in "${app_services[@]}"; do
|
||||||
|
start_service "$service_name"
|
||||||
|
if [[ "${WAIT_FOR_APPS:-true}" == "true" ]]; then
|
||||||
|
wait_for_service_health "$service_name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
local service_name="$1"
|
||||||
|
local pid_file
|
||||||
|
local pid
|
||||||
|
local attempt
|
||||||
|
|
||||||
|
pid_file="$(service_pid_file "$service_name")"
|
||||||
|
pid="$(read_service_pid "$service_name" || true)"
|
||||||
|
if ! is_pid_running "$pid"; then
|
||||||
|
rm -f "$pid_file"
|
||||||
|
echo "$service_name is not running."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Stopping $service_name with pid $pid..."
|
||||||
|
kill "$pid" >/dev/null 2>&1 || true
|
||||||
|
for attempt in {1..30}; do
|
||||||
|
if ! is_pid_running "$pid"; then
|
||||||
|
rm -f "$pid_file"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Force stopping $service_name with pid $pid..."
|
||||||
|
kill -9 "$pid" >/dev/null 2>&1 || true
|
||||||
|
rm -f "$pid_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_services() {
|
||||||
|
local service_name
|
||||||
|
local idx
|
||||||
|
|
||||||
|
for ((idx = ${#app_services[@]} - 1; idx >= 0; idx--)); do
|
||||||
|
service_name="${app_services[$idx]}"
|
||||||
|
stop_service "$service_name"
|
||||||
|
done
|
||||||
|
stop_service "$config_service"
|
||||||
|
}
|
||||||
|
|
||||||
|
status_services() {
|
||||||
|
local service_name
|
||||||
|
local pid
|
||||||
|
local log_file
|
||||||
|
|
||||||
|
printf '%-36s %-10s %s\n' "service" "pid" "log"
|
||||||
|
for service_name in "${all_services[@]}"; do
|
||||||
|
pid="$(read_service_pid "$service_name" || true)"
|
||||||
|
log_file="$(service_log_file "$service_name")"
|
||||||
|
if is_pid_running "$pid"; then
|
||||||
|
printf '%-36s %-10s %s\n' "$service_name" "$pid" "$log_file"
|
||||||
|
else
|
||||||
|
printf '%-36s %-10s %s\n' "$service_name" "-" "$log_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
tail_logs() {
|
||||||
|
local service_name="${1:-}"
|
||||||
|
local log_file
|
||||||
|
|
||||||
|
if [[ -z "$service_name" ]]; then
|
||||||
|
echo "Service name is required for logs command." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_file="$(service_log_file "$service_name")"
|
||||||
|
if [[ ! -f "$log_file" ]]; then
|
||||||
|
echo "Log file does not exist: $log_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tail -f "$log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_all() {
|
||||||
|
require_command java
|
||||||
|
|
||||||
|
build_services
|
||||||
|
|
||||||
|
if [[ "${START_INFRA:-true}" == "true" ]]; then
|
||||||
|
start_infra
|
||||||
|
if [[ "${INIT_DB:-true}" == "true" ]]; then
|
||||||
|
init_databases
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Skipping local infra startup. Expecting Postgres at $postgres_host:$postgres_port and Kafka at $kafka_host:$kafka_port."
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_config_server
|
||||||
|
start_app_services
|
||||||
|
|
||||||
|
echo "Local jar startup requested."
|
||||||
|
status_services
|
||||||
|
}
|
||||||
|
|
||||||
|
command="${1:-start}"
|
||||||
|
case "$command" in
|
||||||
|
start)
|
||||||
|
start_all
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
stop_services
|
||||||
|
start_all
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop_services
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status_services
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
build_services
|
||||||
|
;;
|
||||||
|
infra-up)
|
||||||
|
start_infra
|
||||||
|
if [[ "${INIT_DB:-true}" == "true" ]]; then
|
||||||
|
init_databases
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
infra-down)
|
||||||
|
require_command docker
|
||||||
|
compose stop kafka postgres
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
tail_logs "${2:-}"
|
||||||
|
;;
|
||||||
|
help|-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# Аудит проекта observatio-terrae
|
||||||
|
|
||||||
|
Дата: 2026-06-06. Автор: Claude (Opus 4.8).
|
||||||
|
Охват: фронтенд `pcp-tgu-ops-ui` + бэкенд-сервисы. Сервис `tle-monitoring-service` по
|
||||||
|
указанию исключён из анализа.
|
||||||
|
|
||||||
|
Метод: статический разбор кода, прогон сборки/тестов фронта, выборочное чтение бэкенд-сервисов
|
||||||
|
(акцент на недавно дорабатываемых: tgu, mission-planing, complex-mission, request), проверка
|
||||||
|
конфигов, CORS, обработки ошибок и гигиены репозитория.
|
||||||
|
|
||||||
|
## Сводка по состоянию
|
||||||
|
|
||||||
|
Базовое здоровье — хорошее. Фронтенд проходит строгую типизацию (`tsc -b`, exit 0) и все
|
||||||
|
тесты (53 теста в 12 файлах, зелёные); в исходниках нет `console.*`, `any`, `@ts-ignore`.
|
||||||
|
Бэкенд использует общий `GlobalExceptionHandler` из `pcp-types-lib`, нет `printStackTrace`,
|
||||||
|
нет `@Disabled`/`@Ignore` тестов. Найденные проблемы — это в основном технический долг,
|
||||||
|
копипаст-артефакты и один реальный риск надёжности в Camunda-воркерах.
|
||||||
|
|
||||||
|
Приоритеты: **B1** (надёжность), **R1/C1** (гигиена/конфиги), затем рефакторинг-долг.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Бэкенд
|
||||||
|
|
||||||
|
### B1 (высокий). Camunda job-воркеры молча проглатывают исключения
|
||||||
|
Файл: [FlowService.kt](../services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/FlowService.kt)
|
||||||
|
|
||||||
|
Все воркеры объявлены с `autoComplete = false` и обёрнуты в:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
catch (ex : Exception) { logger.warn(ex.message) }
|
||||||
|
```
|
||||||
|
|
||||||
|
Если исключение возникает до `client.newCompleteCommand(...).send()`, задача **не завершается и
|
||||||
|
не падает**: воркер «съел» ошибку. Camunda не получает ни complete, ни fail/incident — BPMN-процесс
|
||||||
|
зависает молча до таймаута активации, без видимого инцидента и без явной диагностики. Плюс
|
||||||
|
`logger.warn(ex.message)` теряет stack trace, а `ex.message` может быть `null`.
|
||||||
|
|
||||||
|
Рекомендация: в catch завершать задачу через `client.newFailCommand(job.key)` (с retries) либо
|
||||||
|
бросать исключение наверх, чтобы Camunda создала incident; логировать как `logger.error(msg, ex)`
|
||||||
|
со стек-трейсом. Это касается `surveyPlan`, `surveyDrop`, `sendPlan`, `confirmPlan` и прочих
|
||||||
|
воркеров файла.
|
||||||
|
|
||||||
|
### B2 (средний). Симуляционная заглушка `Thread.sleep(5000)` в проде-пути
|
||||||
|
Файл: [FlowService.kt:97](../services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/FlowService.kt#L97)
|
||||||
|
|
||||||
|
В `sendPlan` стоит `Thread.sleep(5000)` с комментарием «Иммитация сообщения о получении
|
||||||
|
подтверждения» — синхронно блокирует поток job-воркера на 5 с и эмулирует приём подтверждения
|
||||||
|
плана. Для MVP-демо допустимо (см. практику осознанных заглушек), но это нужно снять при
|
||||||
|
интеграции с реальной шиной подтверждений; пока имитация маскирует отсутствие реального канала и
|
||||||
|
держит поток пула воркеров.
|
||||||
|
|
||||||
|
### B3 (средний). Несогласованные пакеты — копипаст-артефакты
|
||||||
|
Базовые пакеты ряда сервисов не соответствуют их имени (видимо, форки шаблона):
|
||||||
|
|
||||||
|
| Сервис | Пакет | Проблема |
|
||||||
|
|---|---|---|
|
||||||
|
| pcp-ballistics-service | `space.nstart.pcp.pcp_request_service` | пакет от request-сервиса |
|
||||||
|
| pcp-complex-mission-service | `space.nstart.pcp.pcp_satellites_service` | чужое имя |
|
||||||
|
| pcp-ui-service | `space.nstart.pcp.slots_service` | совпадает со slots-service |
|
||||||
|
| slots-service | `space.nstart.pcp.slots_service` | тот же пакет, что у ui-service |
|
||||||
|
| pcp-tgu-service | `space.nstart.pcp_tgu_service` | пропущен сегмент `.pcp.` |
|
||||||
|
| pcp-request-service | `org.nstart.dep265.requestservice` | другой корень (`org` вместо `space`) |
|
||||||
|
|
||||||
|
Последствия: grep/навигация по пакету выводит на «не тот» сервис, IDE-импорты обманчивы,
|
||||||
|
|
||||||
|
ui-service и slots-service неотличимы по пакету. Переименование пакетов затрагивает много файлов —
|
||||||
|
делать осознанно, отдельной задачей; новые сервисы заводить уже с корректным пакетом.
|
||||||
|
|
||||||
|
### B4 (низкий). Опечатка «srpring» растиражирована
|
||||||
|
`pcp-srpring-boot-admin-server` — опечатка в имени каталога и пакета, продублированная в
|
||||||
|
`settings.gradle.kts`, `build.gradle.kts`, `.gitlab-ci.yml`, `deploy/local-bundle/docker-compose.yml`,
|
||||||
|
`config-repo/application-docker-local.yaml`. Косметика, но фиксируется во всей инфраструктуре, потому
|
||||||
|
исправлять дорого — оставить как известный долг или чинить вместе с B3.
|
||||||
|
|
||||||
|
### B5 (низкий). Потеря stack trace при логировании
|
||||||
|
~9 мест вида `logger.warn(ex.message)` / `logger.error(ex.message)` логируют только текст без
|
||||||
|
исключения. Для диагностики предпочтительно `logger.error("...", ex)`.
|
||||||
|
|
||||||
|
### B6 (инфо). Опечатки в лог-сообщениях
|
||||||
|
Напр. «Утверждение поана» (план), «Иммитация» (имитация) в FlowService.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фронтенд (pcp-tgu-ops-ui)
|
||||||
|
|
||||||
|
### F1 (средний). Дублирование HTTP-/error-обвязки в API-клиентах
|
||||||
|
`extractErrorText` скопирован в 5 файлах: `tguApi.ts`, `ballisticsApi.ts`, `missionPlanningApi.ts`,
|
||||||
|
`complexPlanApi.ts`, `tguPlanApi.ts`. Шаблон `try { fetch } catch { TguApiError("network") }` +
|
||||||
|
разбор `!response.ok` повторяется ~22 раза (7 в missionPlanningApi, 5 в tguPlanApi и т.д.).
|
||||||
|
В `tguApi.ts` уже есть аккуратный `fetchJson<T>()` — остальные клиенты переписывают то же руками.
|
||||||
|
|
||||||
|
Рекомендация: вынести единый `src/api/http.ts` (обёртка `fetchJson` + `extractErrorText` +
|
||||||
|
маппинг ошибок) и переиспользовать. Сократит ~150 строк и устранит расхождения (см. F5).
|
||||||
|
|
||||||
|
### F2 (низкий). Островок нетипизированного JS во вкладке «Группировка»
|
||||||
|
`features/constellation/*` — это `.jsx`/`.js` (`ConstellationDashboard.jsx`, `sim.js`, `data.js`,
|
||||||
|
`MapView.jsx`, `Panels.jsx`, `Timeline.jsx`, `util.js`), включён через `allowJs`. Остальное
|
||||||
|
приложение — строгий TS. Прототипный код подключён в прод через `ConstellationTab.tsx` (обёртка с
|
||||||
|
`as ComponentType`). Типы в нём фактически `any` — провал строгой типизации в этой ветке UI.
|
||||||
|
Долг: постепенно перевести на `.tsx` или хотя бы добавить декларации типов на границе.
|
||||||
|
|
||||||
|
### F3 (низкий). Мёртвый barrel `constellation/index.js`
|
||||||
|
`features/constellation/index.js` (реэкспорт `SIM`, `SATELLITES`, `STATIONS`, ...) нигде не
|
||||||
|
импортируется — можно удалить.
|
||||||
|
|
||||||
|
### F4 (низкий). God-компонент `TguEditorTab.tsx` (1727 строк)
|
||||||
|
Самый крупный файл фронта; держит десятки `useState`/`useRef`, режимы правой панели, построение
|
||||||
|
маршрутов, валидацию, undo/redo. Хорошо закомментирован, но монолитен. Кандидат на декомпозицию
|
||||||
|
(вынос хуков `useDraftHistory`, `useRouteBuilder`, разнесение панелей) для снижения связности.
|
||||||
|
|
||||||
|
### F5 (инфо). Расхождение приоритета полей ошибки
|
||||||
|
В `tguApi.extractErrorText` порядок полей `reason, message, error, detail`, а в
|
||||||
|
`missionPlanningApi.extractErrorText` — `message, error, reason, detail`. При едином ответе бэка
|
||||||
|
пользователь увидит разный текст из разных клиентов. Устранится при выносе общей обвязки (F1).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Гигиена репозитория
|
||||||
|
|
||||||
|
### R1 (средний). Крупные дампы данных в корне репозитория (в git)
|
||||||
|
Отслеживаются git и лежат в корне:
|
||||||
|
- `pcp_requests_converted_create_requests.json` (~228 КБ)
|
||||||
|
- `response_заданияКубернетс.json` (~157 КБ)
|
||||||
|
- `response_заданияЕмельянов.json` (~66 КБ)
|
||||||
|
|
||||||
|
Это тестовые выгрузки, засоряющие корень и историю. Перенести в `docs/`/`*/src/test/resources`
|
||||||
|
или убрать из git (оставив в `.gitignore`).
|
||||||
|
|
||||||
|
### R2 (низкий). Посторонние артефакты в корне (не в git)
|
||||||
|
- `node_modules/` в корне + пустой `package-lock.json` (`"packages": {}`, 0 зависимостей) —
|
||||||
|
корневого Node-проекта нет, фронт живёт в `services/pcp-tgu-ops-ui`. Мусор от случайного
|
||||||
|
`npm install` в корне; удалить, при желании добавить `/package-lock.json` в `.gitignore`.
|
||||||
|
- `obj_terra.zip` (~1.5 МБ) в корне — не в git, но захламляет дерево.
|
||||||
|
|
||||||
|
### R3 (низкий). 26 устаревших каталогов `bin/` на диске
|
||||||
|
Скомпилированные `bin/main/...` (gitignored) задваивают исходники: любой `grep` по `services`
|
||||||
|
выдаёт каждый файл дважды (src + bin) — шум при навигации. Прогнать `./gradlew clean` /
|
||||||
|
удалить `bin/` каталоги.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Конфигурация и безопасность
|
||||||
|
|
||||||
|
### C1 (средний). Несогласованное задание паролей БД в config-repo
|
||||||
|
В большинстве сервисов пароль зашит как `password: password`, и только
|
||||||
|
[pcp-tgu-service.yaml](../config-repo/pcp-tgu-service.yaml) использует переопределяемый паттерн
|
||||||
|
`password: ${DB_PASSWORD:password}`. Для локалки риск низкий, но шаблон важен: привести все
|
||||||
|
сервисы к `${DB_PASSWORD:...}` (и прочие креды — к env-переменным), чтобы прод/стенды
|
||||||
|
переопределялись без правки config-repo.
|
||||||
|
|
||||||
|
### C2 (низкий). CORS: широкий в ui-service, отсутствует в остальных
|
||||||
|
[WebConfig.kt](../services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/configuration/WebConfig.kt)
|
||||||
|
задаёт `allowedOriginPatterns("*")` + `allowCredentials=true` — широко; для прода имеет смысл
|
||||||
|
сузить origin до конкретных хостов. Остальные сервисы, которые фронт зовёт напрямую (tgu,
|
||||||
|
mission, request, ballistics, complex, stations), CORS-конфига не имеют — сейчас работает только
|
||||||
|
потому, что обращение идёт через Vite-прокси/nginx (same-origin). Прямые кросс-доменные вызовы к
|
||||||
|
ним из браузера упадут — это ограничение стоит держать в курсе при изменении схемы доступа.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что в порядке (намеренно не правим)
|
||||||
|
- Заглушки/тест-контроллеры/пустой контракт валидации миссии — осознанные MVP-срезы, не дефекты.
|
||||||
|
- Прямые вызовы микросервисов из фронта через прокси (без BFF) — принятое решение этапа.
|
||||||
|
- Naive `LocalDateTime` как локальное настенное время — намеренная договорённость проекта.
|
||||||
|
|
||||||
|
## Рекомендованный порядок работ
|
||||||
|
1. **B1** — починить обработку ошибок в Camunda-воркерах (надёжность процессов).
|
||||||
|
2. **R1, C1** — убрать дампы из корня; унифицировать env-пароли.
|
||||||
|
3. **F1** — общий `http.ts`, заодно закрыть F5.
|
||||||
|
4. **B2** — снять `Thread.sleep`-имитацию при появлении реального канала подтверждений.
|
||||||
|
5. Фоном: **R2/R3** (чистка), **F2/F4** (типизация constellation, декомпозиция редактора),
|
||||||
|
**B3/B4** (пакеты/опечатки) — отдельными задачами из-за широкого охвата.
|
||||||
@@ -38,6 +38,11 @@ _Обновлено: 2026-06-11_
|
|||||||
|
|
||||||
### Бэкенд / инфраструктура
|
### Бэкенд / инфраструктура
|
||||||
- [ ] **#7** — Перевести сервис станций на ordinis (по примеру получения аппаратов в сервисе tgu).
|
- [ ] **#7** — Перевести сервис станций на ordinis (по примеру получения аппаратов в сервисе tgu).
|
||||||
|
- [ ] **#47** — Перевести сервисы с Jackson 2 на Jackson 3 и убрать двойной стек. Под Spring Boot 4.0.2 MVC уже десериализует входящий JSON через **Jackson 3** (`tools.jackson.*`), а весь код монорепы написан под **Jackson 2** (`com.fasterxml.jackson`, `versions.jackson_databind=2.18.2`); в `pcp-request-service` для внутреннего парсинга держится отдельный Jackson 2-бин (`JacksonConfig`). Из-за этой рассинхронизации словили баг: Jackson 3 по умолчанию включает `FAIL_ON_NULL_FOR_PRIMITIVES`, и явный `null` на примитивном `Double coverageRequiredPercent` ронял создание заявки (контракт-тесты не ловили — они на Jackson 2). Срочности/EOL-риска нет, это техдолг и консистентность.
|
||||||
|
- **Сейчас, дёшево (часы):** системно снять класс багов «null на примитиве» — выставить `spring.jackson.deserialization.fail-on-null-for-primitives=false` в config-repo request-service (проверить, что свойство применяется к Jackson 3-бину; если нет — через `Jackson3ObjectMapperBuilderCustomizer`). Не латать каждое поле по очереди.
|
||||||
|
- **Среднесрок, по сервису:** мигрировать по одному, пилот — `pcp-request-service` (~7 файлов main, ~0.5–1 день). Аннотации **не трогаем** — `jackson-annotations` в Jackson 3 остались в пакете `com.fasterxml.jackson.annotation` (у нас это основная масса: `@JsonIgnoreProperties` ×36, `@JsonProperty` ×16, `@JsonInclude` ×15…). Менять только databind-поверхность: `ObjectMapper` ×23, `TypeReference` ×8, `JsonNode` ×5, `jacksonObjectMapper()` ×12 → пакеты `tools.jackson.databind/core/module.kotlin`; в Jackson 3 `JsonProcessingException` стала unchecked. Дропнуть Jackson 2-зависимости и Jackson 2-бин `JacksonConfig`.
|
||||||
|
- **Главный риск — Kafka:** `KafkaConsumerConfig`/сериализаторы завязаны на `com.fasterxml` ObjectMapper. Перед стартом — спайк (~полдня): умеет ли `spring-kafka` в нашем Boot 4 Jackson 3-сериализаторы. Если нет — Kafka-слой мигрируем отдельно, HTTP/внутренний JSON переводим независимо.
|
||||||
|
- **Масштаб (src/main):** 67 файлов в 10 сервисах — pcp-dynamic-plan-service 22, pcp-ui-service 16 (выводится из эксплуатации, ср. #40 — можно не мигрировать), pcp-request-service 7, slots-service/pcp-tgu-service по 5, остальные 1–4. **Грубая оценка всей репы — ~6–10 человеко-дней**, основной риск — Kafka. Big-bang не нужен: шаг 1 закрывает причину бага, полную миграцию вести фоново сервис за сервисом.
|
||||||
- [ ] **#45** — Собрать native-образ сервиса (Kotlin/Native, `kompile`) и контейнер на его основе. Цель — быстрый старт и меньший footprint в сравнении с JVM-образом. Уточнить: какой сервис(ы) переводим первым, совместимость с рефлексией/AOT-конфигом (reachability metadata), сборку в CI и базовый образ контейнера.
|
- [ ] **#45** — Собрать native-образ сервиса (Kotlin/Native, `kompile`) и контейнер на его основе. Цель — быстрый старт и меньший footprint в сравнении с JVM-образом. Уточнить: какой сервис(ы) переводим первым, совместимость с рефлексией/AOT-конфигом (reachability metadata), сборку в CI и базовый образ контейнера.
|
||||||
- [ ] **#37** — Рассинхрон номеров витков между баллистическими источниками. Для одного и того же КА/времени `flight-line` (трасса, `/api/tgu-planning/spacecraft/{norad}/flight-line`) и `survey-route/build` (контур, pcp-mission-planing) отдают **разный номер витка**: на Emissio 1 (norad 2) 2026-06-06 12:50–12:55 трасса помечена витком 142, а контур/сохранённые режимы — витком 144 (по широте ~74° с.ш. согласованы, расходится только нумерация). В редакторе карта фильтрует трассу/полосу обзора по видимому витку (`buildEditorMapScene` → `visibleRevolutions`), поэтому при сужении селектора до «виток N» точки трассы с другим номером выпадают и пропадает орбитальный контекст. Свести нумерацию витков к единому источнику (или маппинг на границе), чтобы трасса и маршрут согласовывались по витку.
|
- [ ] **#37** — Рассинхрон номеров витков между баллистическими источниками. Для одного и того же КА/времени `flight-line` (трасса, `/api/tgu-planning/spacecraft/{norad}/flight-line`) и `survey-route/build` (контур, pcp-mission-planing) отдают **разный номер витка**: на Emissio 1 (norad 2) 2026-06-06 12:50–12:55 трасса помечена витком 142, а контур/сохранённые режимы — витком 144 (по широте ~74° с.ш. согласованы, расходится только нумерация). В редакторе карта фильтрует трассу/полосу обзора по видимому витку (`buildEditorMapScene` → `visibleRevolutions`), поэтому при сужении селектора до «виток N» точки трассы с другим номером выпадают и пропадает орбитальный контекст. Свести нумерацию витков к единому источнику (или маппинг на границе), чтобы трасса и маршрут согласовывались по витку.
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -67,7 +67,10 @@ data class CreateRequestDto(
|
|||||||
*/
|
*/
|
||||||
@field:DecimalMin(value = "0", inclusive = false)
|
@field:DecimalMin(value = "0", inclusive = false)
|
||||||
@field:DecimalMax("100")
|
@field:DecimalMax("100")
|
||||||
val coverageRequiredPercent: Double = 100.0,
|
// Nullable с дефолтом: под Jackson 3 (MVC) FAIL_ON_NULL_FOR_PRIMITIVES включён по умолчанию,
|
||||||
|
// и явный null от клиента падал бы на примитиве Double. Опциональное поле с дефолтом трактуем
|
||||||
|
// null как «взять дефолт» — при маппинге в сущность коалесим к 100.0.
|
||||||
|
val coverageRequiredPercent: Double? = 100.0,
|
||||||
|
|
||||||
/** Параметры оптической съемки. */
|
/** Параметры оптической съемки. */
|
||||||
@field:Valid
|
@field:Valid
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ class RequestService(
|
|||||||
remainingArea = geometryArea,
|
remainingArea = geometryArea,
|
||||||
// Порог завершения по покрытию приходит из API (по умолчанию 100%); это прогресс съёмки,
|
// Порог завершения по покрытию приходит из API (по умолчанию 100%); это прогресс съёмки,
|
||||||
// в отличие от importance — важности заявки для карты приоритетов ячеек.
|
// в отличие от importance — важности заявки для карты приоритетов ячеек.
|
||||||
coverageRequiredPercent = request.coverageRequiredPercent,
|
coverageRequiredPercent = request.coverageRequiredPercent ?: 100.0,
|
||||||
importance = importance,
|
importance = importance,
|
||||||
beginDateTime = request.beginDateTime,
|
beginDateTime = request.beginDateTime,
|
||||||
endDateTime = request.endDateTime,
|
endDateTime = request.endDateTime,
|
||||||
|
|||||||
+18
-1
@@ -112,6 +112,23 @@ class RequestServiceTest {
|
|||||||
assertEquals(80.0, persistedRequests.single().coverageRequiredPercent)
|
assertEquals(80.0, persistedRequests.single().coverageRequiredPercent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create request coalesces null coverage required percent to default`() {
|
||||||
|
val persistedRequests = mutableListOf<RequestEntity>()
|
||||||
|
doReturn(false)
|
||||||
|
.`when`(requestRepository)
|
||||||
|
.existsById(REQUEST_ID)
|
||||||
|
doAnswer { invocation ->
|
||||||
|
persistedRequests += invocation.getArgument<RequestEntity>(0)
|
||||||
|
null
|
||||||
|
}.`when`(entityManager).persist(any(RequestEntity::class.java))
|
||||||
|
|
||||||
|
// Явный null (клиент прислал coverageRequiredPercent: null) трактуем как «взять дефолт».
|
||||||
|
service.createRequest(createRequestDto(coverageRequiredPercent = null))
|
||||||
|
|
||||||
|
assertEquals(100.0, persistedRequests.single().coverageRequiredPercent)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `get request by id returns name and importance`() {
|
fun `get request by id returns name and importance`() {
|
||||||
val request = existingRequest()
|
val request = existingRequest()
|
||||||
@@ -296,7 +313,7 @@ class RequestServiceTest {
|
|||||||
verify(requestCellRepository).deleteByRequestId(REQUEST_ID)
|
verify(requestCellRepository).deleteByRequestId(REQUEST_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRequestDto(coverageRequiredPercent: Double = 100.0): CreateRequestDto {
|
private fun createRequestDto(coverageRequiredPercent: Double? = 100.0): CreateRequestDto {
|
||||||
return CreateRequestDto(
|
return CreateRequestDto(
|
||||||
id = REQUEST_ID,
|
id = REQUEST_ID,
|
||||||
name = REQUEST_NAME,
|
name = REQUEST_NAME,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
ARG RUNTIME_IMAGE=repo.nstart.cloud/bellsoft/liberica-openjre-alpine:21.0.5
|
||||||
|
FROM ${RUNTIME_IMAGE}
|
||||||
|
|
||||||
|
ENV JAVA_OPTS=""
|
||||||
|
|
||||||
|
ADD ./build/libs/*.jar /app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]
|
||||||
@@ -24,6 +24,8 @@ type Tgu2DMapViewProps = {
|
|||||||
selectedRequestPolygonId?: string;
|
selectedRequestPolygonId?: string;
|
||||||
/** Внешняя подсветка объекта (контур съёмки или линия сброса) по его id — «как будто кликнули на карте». */
|
/** Внешняя подсветка объекта (контур съёмки или линия сброса) по его id — «как будто кликнули на карте». */
|
||||||
selectedObjectId?: string;
|
selectedObjectId?: string;
|
||||||
|
/** Отмеченные объекты (постоянная подсветка независимо от выбора) — напр. слоты, отобранные под бронь. */
|
||||||
|
markedObjectIds?: ReadonlySet<string>;
|
||||||
/** Точка, на которую центрировать карту; перецентрирование срабатывает при смене focusToken. */
|
/** Точка, на которую центрировать карту; перецентрирование срабатывает при смене focusToken. */
|
||||||
focusPoint?: GeoPoint;
|
focusPoint?: GeoPoint;
|
||||||
focusToken?: number;
|
focusToken?: number;
|
||||||
@@ -84,6 +86,7 @@ export function Tgu2DMapView({
|
|||||||
hoveredRequestPolygonId,
|
hoveredRequestPolygonId,
|
||||||
selectedRequestPolygonId,
|
selectedRequestPolygonId,
|
||||||
selectedObjectId,
|
selectedObjectId,
|
||||||
|
markedObjectIds,
|
||||||
focusPoint,
|
focusPoint,
|
||||||
focusToken,
|
focusToken,
|
||||||
enableObjectChooser = false,
|
enableObjectChooser = false,
|
||||||
@@ -529,6 +532,14 @@ export function Tgu2DMapView({
|
|||||||
[hoveredRequestPolygonId, activePolygons]
|
[hoveredRequestPolygonId, activePolygons]
|
||||||
);
|
);
|
||||||
const overlayPolygons = uniquePolygons([externalSelectedPolygon, selectedPolygon, externalHoveredPolygon, hoverRef.current]);
|
const overlayPolygons = uniquePolygons([externalSelectedPolygon, selectedPolygon, externalHoveredPolygon, hoverRef.current]);
|
||||||
|
// Отмеченные объекты (напр. отобранные под бронь слоты) — постоянная подсветка под обычным выбором.
|
||||||
|
const markedPolygons = useMemo(
|
||||||
|
() =>
|
||||||
|
markedObjectIds && markedObjectIds.size > 0
|
||||||
|
? activePolygons.filter((p) => markedObjectIds.has(p.id))
|
||||||
|
: [],
|
||||||
|
[markedObjectIds, activePolygons]
|
||||||
|
);
|
||||||
const overlayMarkers = uniqueMarkers([selectedMarker, hoverMarkerRef.current]);
|
const overlayMarkers = uniqueMarkers([selectedMarker, hoverMarkerRef.current]);
|
||||||
|
|
||||||
// Накопленное расстояние (км) от первой вершины линейки до каждой — по дуге большого круга.
|
// Накопленное расстояние (км) от первой вершины линейки до каждой — по дуге большого круга.
|
||||||
@@ -563,6 +574,24 @@ export function Tgu2DMapView({
|
|||||||
>
|
>
|
||||||
<canvas className="tgu-2d-map__canvas" ref={canvasRef} />
|
<canvas className="tgu-2d-map__canvas" ref={canvasRef} />
|
||||||
<svg className="tgu-2d-map__overlay" width={size.width} height={size.height} aria-hidden="true">
|
<svg className="tgu-2d-map__overlay" width={size.width} height={size.height} aria-hidden="true">
|
||||||
|
{markedPolygons.flatMap((polygon) => {
|
||||||
|
const raw = polygon.points.map((point) => projection.project(point));
|
||||||
|
const pts = unwrapX(raw, projection.worldSize);
|
||||||
|
return ([0, projection.worldSize, -projection.worldSize] as const).flatMap((dx) => {
|
||||||
|
const visible = pts.some((p) => {
|
||||||
|
const x = p.x + dx;
|
||||||
|
return x >= -64 && x <= size.width + 64 && p.y >= -64 && p.y <= size.height + 64;
|
||||||
|
});
|
||||||
|
if (!visible) return [];
|
||||||
|
return (
|
||||||
|
<polygon
|
||||||
|
key={`marked:${polygon.id}:${dx}`}
|
||||||
|
points={pts.map((p) => `${p.x + dx},${p.y}`).join(" ")}
|
||||||
|
className="is-marked"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})}
|
||||||
{overlayPolygons.flatMap((polygon) => {
|
{overlayPolygons.flatMap((polygon) => {
|
||||||
const raw = polygon.points.map((point) => projection.project(point));
|
const raw = polygon.points.map((point) => projection.project(point));
|
||||||
const pts = unwrapX(raw, projection.worldSize);
|
const pts = unwrapX(raw, projection.worldSize);
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
// Ключи забронированных слотов — для бейджа «бронь» в таблице и отдельного слоя на карте.
|
// Ключи забронированных слотов — для бейджа «бронь» в таблице и отдельного слоя на карте.
|
||||||
const bookedKeys = useMemo(() => new Set(bookedSlots.map(slotKey)), [bookedSlots]);
|
const bookedKeys = useMemo(() => new Set(bookedSlots.map(slotKey)), [bookedSlots]);
|
||||||
|
|
||||||
|
// id полигонов слотов, отобранных галочками под бронь — постоянная подсветка на карте.
|
||||||
|
const markedSlotIds = useMemo(
|
||||||
|
() => new Set(Array.from(selectedKeys, (key) => `slot:${key}`)),
|
||||||
|
[selectedKeys]
|
||||||
|
);
|
||||||
|
|
||||||
// Слот по ключу — чтобы из выбора на карте (id "slot:key"/"booked:key") восстановить полные данные.
|
// Слот по ключу — чтобы из выбора на карте (id "slot:key"/"booked:key") восстановить полные данные.
|
||||||
const slotByKey = useMemo(() => {
|
const slotByKey = useMemo(() => {
|
||||||
const map = new Map<string, SlotDTO>();
|
const map = new Map<string, SlotDTO>();
|
||||||
@@ -758,6 +764,7 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) {
|
|||||||
onObjectsAtPoint={handleObjectsAtPoint}
|
onObjectsAtPoint={handleObjectsAtPoint}
|
||||||
onObjectSelect={handleObjectSelect}
|
onObjectSelect={handleObjectSelect}
|
||||||
selectedObjectId={hasRequest ? activeSlotId : activeHitId}
|
selectedObjectId={hasRequest ? activeSlotId : activeHitId}
|
||||||
|
markedObjectIds={hasRequest ? markedSlotIds : undefined}
|
||||||
focusPoint={focusPoint}
|
focusPoint={focusPoint}
|
||||||
focusToken={focusToken}
|
focusToken={focusToken}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1175,6 +1175,14 @@
|
|||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Отобранные под бронь слоты — постоянная зелёная подсветка (отличается от оранжевого выбора). */
|
||||||
|
.tgu-2d-map__overlay polygon.is-marked {
|
||||||
|
fill: rgba(58, 209, 122, 0.22);
|
||||||
|
stroke: #3ad17a;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-dasharray: 6 4;
|
||||||
|
}
|
||||||
|
|
||||||
.tgu-2d-map__overlay polyline.is-selected {
|
.tgu-2d-map__overlay polyline.is-selected {
|
||||||
stroke: #f0ad2e;
|
stroke: #f0ad2e;
|
||||||
stroke-width: 7;
|
stroke-width: 7;
|
||||||
|
|||||||
Reference in New Issue
Block a user