Закрывает эту тему окончательно. После merge в main CI анализирует
conventional commits с последнего tag'а, бампит package.json + CHANGELOG.md
и пушит git tag vX.Y.Z. Tag pipeline далее собирает images и ждёт
manual gate на trigger-deploy-prod.
Что добавил:
- ordinis-admin-ui/.releaserc.json — config (commit-analyzer, release-notes,
changelog, npm[no-publish], git, gitlab). branches: ['main']. Маппинг
conventional-commit types к bump levels.
- ordinis-admin-ui/package.json — devDeps: semantic-release@24.2 + 6 plugins.
- .gitlab-ci.yml — new 'release' stage + release job:
- needs: trigger-deploy-staging (release ТОЛЬКО после зелёного staging)
- image: node:22-alpine + pnpm@9.15.4
- GL_TOKEN из CI variable GITLAB_TOKEN (semantic-release/gitlab требует
именно GL_TOKEN env name)
- allow_failure: true (нет релизных commits = OK, не блокирует pipeline)
- RELEASE.md — operator docs:
- One-time setup: создать Project Access Token + GITLAB_TOKEN CI var
- Commit conventions table (feat→minor, fix→patch, breaking→major)
- Skip release (use chore: type или [skip ci])
- Dry-run command для local validation
Prod safety: trigger-deploy-prod остаётся manual (when: manual). Автоматизация
ТОЛЬКО tag-создания, не deploy. Tag → CI runs → human кликает Deploy.
ACTION REQUIRED от user (one-time):
1. GitLab → Settings → Access Tokens → создать Project Access Token
scopes: write_repository + api, role: Maintainer
2. GitLab → Settings → CI/CD → Variables → GITLAB_TOKEN (Masked, Protected)
Без этого release job будет fail'ить (но pipeline не покраснеет — allow_failure).
leaflet-draw 1.0.4 (последний release 2017) сломан с Leaflet 1.8+:
mousedown сразу триггерит mouseup, прямоугольник схлопывается в
точку при первом нажатии. На пользовательском скрине quadrate ~5px
вместо нарисованной области.
Заменил полностью на свою draw-implementation (~80 строк):
- Toolbar UI: 3 кнопки (Прямоугольник / Полигон / Очистить)
- Active mode highlights ultramarain background
- Rectangle: mousedown → preview → mouseup finalize. Map.dragging
отключается в rectangle mode чтобы drag не панил карту.
- Polygon: click добавляет вершину + dashed mouse-line preview к
курсору. Double-click closes (min 3 vertices). doubleClickZoom
disabled на map чтобы не конфликтовало.
- DrawMode read через ref чтобы listeners не пересоздавались на
каждом state change.
Удалены deps:
- leaflet-draw 1.0.4 (broken)
- @types/leaflet-draw
Bundle: -68KB minified / -23KB gzip (less broken legacy code).
Tests: 89 passed. i18n: RU/EN ключи для новой toolbar.
Admin кликает 'AOI фильтр' на странице словаря → открывается
dialog с картой OSM. Рисует rectangle (→ bbox CSV) или
polygon → onApply применяется к recordsQuery как ?bbox= или
?polygon=GeoJSON.
Server-side parsing уже есть (BoundingBox.parse, GeoJsonPolygon.parse
в ordinis-rest-api) — подключили UI поверх.
Активный AOI отображается inline над таблицей с кнопкой
'Сбросить'. Edit shape возможен через leaflet-draw edit/remove
controls.
Зависимости: leaflet 1.9.4 + react-leaflet 5 + leaflet-draw 1.0.4
+ types. Bundle +231KB gzip 62KB — приемлемо для AOI-only feature
(map работает без MapLibre WASM, OSM tiles — публичные).
Picker self-contained: монтируется/уничтожается с диалогом,
invalidateSize 100ms после open чтобы Leaflet корректно посчитал
container size после modal animation.
RequireAuth: гейтит весь UI по аутентификации. Anonymous юзер вместо
красного "AxiosError 401" получает immediate signinRedirect на Keycloak;
authenticated юзер с истёкшим токеном получает silent renew → fallback на
full redirect. Guards против infinite loop:
- auth.isLoading / activeNavigator — bootstrap или signin/signout уже идёт
- ?error= в URL — вернулись с провалом из Keycloak, показываем сообщение
- auth.error без isAuthenticated — Keycloak недоступен, показываем сообщение
main.tsx: RequireAuth обёрнут вокруг Router/Query, чтобы tan-stack-router и
React Query не делали запросов до auth (иначе 401-каскад на старте).
TokenSync: 401 interceptor теперь редиректит и anonymous (раньше срабатывал
только для authenticated с истёкшим токеном), с теми же guards.
RTL component тесты — 22 теста на 3 ключевых компонента:
- SchemaBuilder (7): empty state, add/remove/reorder properties
- PropertyEditor (10): kind change, required/unique toggle, kind-specific
поля (string→pattern, integer→min/max, enum→values, array_string→uniqueItems),
move up/down disabled на boundaries
- SchemaDrivenForm (5): identity tab с required, переключение на extra,
isPending → save disabled, cancel callback, serverError → Alert
Setup:
- vite.config.ts: test block (jsdom env, setup file, css off)
- src/test/setup.ts: jest-dom matchers, cleanup, HTMLCanvasElement.getContext
stub (lottie-web из @nstart/ui крашит в jsdom без canvas)
- src/test/render.tsx: renderWithI18n helper, форсит ru-RU
(LanguageDetector в node по умолчанию даёт en-US)
Deps: +@testing-library/react +@testing-library/jest-dom
+@testing-library/user-event +jsdom
react-oidc-context + oidc-client-ts. Confidential client тоже работает —
SPA просто не использует client_secret.
- src/auth/oidcConfig.ts — UserManager settings (authority, redirect_uri,
PKCE, automaticSilentRenew). VITE_KEYCLOAK_{URL,REALM,CLIENT_ID} с дефолтами
на nstart realm + ordinis client.
- src/auth/TokenSync.tsx — синкает access_token в localStorage и axios default
header после login/refresh. Existing API код не трогали — interceptor уже
читает localStorage.
Bonus: 401 interceptor триггерит signinSilent → signinRedirect fallback.
- src/auth/AuthBadge.tsx — header'ный бейдж: "Войти" если anonymous, имя +
logout если залогинен.
- main.tsx обёрнут в <AuthProvider>.
- __root.tsx показывает AuthBadge рядом с language switch.
- .env.example с конфигом Keycloak.
- i18n auth.login / auth.logout (RU + EN).
Keycloak client `ordinis` — настроить:
Valid redirect URIs: https://ordinis.k8s.265.nstart.cloud/*,
https://ordinis.k8s.264.nstart.cloud/*,
http://localhost:5173/*
Web Origins: те же + (или "+"), PKCE Code Challenge: S256
Tests (Vitest):
- src/lib/dates.ts (вынесено из SchemaDrivenForm + dictionaries.$name): parseFormDate,
formatIsoDate, formatIsoDateTime, extractTime, combineDateTime, localTzOffset, nowIsoLocal
- src/lib/dates.test.ts (20 tests): TZ-safety, pure-date local midnight, far-future filter,
round-trip parse/format, time extraction в local TZ, clear-button (null Date → '')
- src/components/schema/types.test.ts (31 tests): buildSchemaJson всех 9 kinds + x-unique +
x-id-source, parseSchemaJson roundtrip, diffSchemas (8 breaking detection cases),
suggestVersionBump (major/minor/patch + breaking-wins-mix)
CI:
- Откатил dual resolve-tag (BACKEND/FRONTEND) на single resolve-tag.
GitLab variable interpolation в trigger jobs не подхватывает dotenv от needs-job
→ admin-ui-only push не доезжал до infra. Now backwards compat path работает
как раньше с UPSTREAM_IMAGE_TAG.
- Backend docker jobs (maven-package + 4 docker-*) теперь без rules:changes —
catch ImagePullBackOff если frontend push шёл без backend rebuild. BuildKit cache
делает повторный билд идентичных layers ~30 sec.
- Path-filter оставлен на maven-test (тесты не запускаются на frontend-only PR
— они бэкендовые) и на docker-admin-ui (frontend-only).
Run: pnpm test → 51 passed.
Свои Modal/Button/inputs выкинуты — берём nstart-ui v0.1.3 как в других corp
React приложениях. Установка через .npmrc → repo.nstart.cloud/repository/npm-hosted/.
Что сделано:
- .npmrc: scope @nstart на corp Nexus + min-release-age=7
- deps: @nstart/ui ^0.1.3, @phosphor-icons/react ^2.1.10
- bumped peer deps: i18next ^26 + react-i18next ^17 (требование nstart-ui)
- styles.css: импорт @nstart/ui/styles/{fonts,theme}.css + critical @source
директива (без неё Tailwind v4 tree-shaking дропнул бы все классы из dist либы)
- main.tsx: <NStartUiProvider> на корне
- DROP src/components/ui/Modal.tsx (свой Modal больше не нужен)
Routes:
- __root: LanguageSwitch вместо двух кнопок RU/EN, токены ultramarain/regolith/carbon
- dictionaries.index: PageHeader + Badge для scope chip + Alert/EmptyState/LoadingBlock
- dictionaries.$name: PageHeader с actions, Modal/Panel/Table/TableRow/Cell,
IconButton с Phosphor (PencilSimple, XCircle), Button variant=primary/danger/ghost
Form (SchemaDrivenForm):
- TextInput/TextArea/SingleSelect/Checkbox + FieldLabel/Hint/Error из @nstart/ui
- FormSection + FormGrid columns=1|2 для группировки
- FormActions align=end с Button loading state
- Alert variant=error для server-side ошибок (422 IdempotencyConflict / 409 InProgress)
- ajv валидация по schema_json остаётся такой же (Draft-07, как у backend)
Bundle: 192KB → 659KB gzip — тянет echarts/lottie/brand assets из nstart-ui,
для internal admin tool приемлемо (cached after first load).