feat(topbar): «Что нового» button + 2026-05-12 state.md update

This commit is contained in:
Александр Зимин
2026-05-12 17:05:24 +00:00
parent 58dd871808
commit a34c6d4c82
21 changed files with 1853 additions and 8 deletions
+95
View File
@@ -0,0 +1,95 @@
# Журнал аудита
Каждое изменение в системе фиксируется в **журнале аудита** — таблице,
в которой записано: кто, что, когда, что было до и после.
Доступ — в разделе **«Аудит»** в навигации (для пользователей с правом
INTERNAL и выше).
## Что попадает в журнал
- Создание, обновление, закрытие записей справочников (`CREATE` / `UPDATE` / `CLOSE`)
- Создание, обновление, удаление справочников
- Schema drafts: создание, отправка на ревью, approve, reject, publish
- Изменения настроек webhook-подписок
Технические события (запуск pod'ов, выполнение SQL миграций) — НЕ
попадают в этот аудит. Они в системных логах.
## Структура записи аудита
| Поле | Что значит |
|---|---|
| **Время** | Когда произошло событие (точное время сервера) |
| **Пользователь** | Кто сделал изменение (берётся из JWT-токена) |
| **Справочник** | Какой справочник затронут |
| **Бизнес-ключ** | Какая конкретно запись (для record-операций) |
| **Действие** | `CREATE`, `UPDATE`, `CLOSE`, `DRAFT_APPROVE` и т.п. |
| **Было / Стало** | Diff: что изменилось (для UPDATE) |
| **Trace ID** | Технический идентификатор для корреляции с логами |
## Фильтрация
В UI — toolbar с фильтрами:
- **Справочник** — конкретный или все
- **Действие** — `CREATE` / `UPDATE` / `CLOSE` и т.д.
- **Пользователь** — кто сделал
- **Бизнес-ключ** — конкретная запись
- **Период** — диапазон дат (используй DateRangePicker)
Применяется автоматически при изменении любого фильтра. URL обновляется —
можно поделиться ссылкой «аудит за вчера на ground_station».
## Что искать в аудите
### «Кто закрыл запись X?»
Фильтр: справочник = `<имя>`, бизнес-ключ = `<код>`, действие = `CLOSE`.
Видишь оператора, время, причину (если была указана).
### «Что менялось вчера в spacecraft?»
Фильтр: справочник = `spacecraft`, период = вчера. Сортировка по времени.
### «Я подозреваю некорректное изменение»
Найди запись, посмотри **diff** колонки «Было / Стало». Если изменение
действительно неверное — раскрой запись, кликни **«Восстановить эту
версию»** (создаст новую активную версию из исторической).
### «Кто часто меняет конкретный справочник?»
Фильтр: справочник + период (последний месяц). В UI отсортируй по
пользователю — видишь топ-операторов.
## Экспорт
Кнопка **«Экспортировать»** в правом верхнем — выгрузка в CSV отфильтрованных
записей. Полезно для:
- Отчётов руководству («что изменилось за квартал»)
- Compliance-проверок
- Анализа в Excel / BI
CSV содержит те же колонки, что в UI, плюс полные diff'ы в JSON-формате.
## Что НЕЛЬЗЯ найти в аудите
- **Чтения** (`READ`) — никто их не записывает (объём огромен, информативная
ценность низкая)
- **Изменения настроек системы** на уровне kubernetes / Vault / SSL —
это в системных логах ops-команды
- **Изменения вне Ordinis** — если внешняя система сделала свою копию
справочника и поменяла её, в нашем аудите этого нет
## Срок хранения
По умолчанию — **бессрочно**. Журнал растёт, но не очищается автоматически.
Если требуется retention policy (например, удалять старше 5 лет по GDPR) —
свяжись с ответственным за систему, это настраивается на уровне БД.
## Trace ID — как использовать
Если в audit-записи есть trace ID — это можно сопоставить с логами
бэкенда. Полезно когда ошибка произошла на стороне сервера и нужно
понять глубинную причину. Скопируй trace ID, отдай в support / ops.
+85
View File
@@ -0,0 +1,85 @@
# Закрытие записи
В Ordinis нет «удалить» в привычном смысле — данные не пропадают. Вместо
этого запись **закрывается**: ей выставляется `valid_to = текущий момент`,
после чего она перестаёт считаться актуальной, но история остаётся.
## Зачем так
Bitemporal модель — два временных измерения:
- **valid_from / valid_to** — когда запись актуальна в реальном мире
- **created_at / updated_at** — когда запись была введена в систему
Это позволяет:
- Восстанавливать что было «на дату» (например, состав группировки
спутников на 2024-12-31)
- Видеть, кто и когда вносил изменения
- Не ломать downstream-системы, которые могут читать архивные данные
## Когда закрывать запись
- Спутник **выведен из эксплуатации**
- Наземная станция **демонтирована**
- Контракт **истёк**
- Тип записи **переименован** (закрыть старую, открыть новую с новым кодом)
- Дубликат **обнаружен** (оставить одну, закрыть остальные)
## Как закрыть
1. Открой запись в drawer'е
2. Кнопка **«Удалить»** в footer'е (она же — «закрыть»)
3. Опционально укажи `valid_to` — момент закрытия. По умолчанию: сейчас.
4. Подтверди
После этого:
- В UI запись по умолчанию **не отображается** (фильтр «только активные»)
- В API при `GET /records?at=YYYY-MM-DD` — отображается только если
на эту дату была активна
- История доступна через `GET /records/{id}/history`
## Как восстановить
Если запись закрыта ошибочно — открой панель **«История версий»** в
drawer'е, найди последнюю активную версию **до закрытия**, нажми
**«Восстановить как новую запись»**. Это создаст новую активную версию
с тем же бизнес-ключом, что и закрытая.
Альтернатива: создать запись с тем же бизнес-ключом заново — система
проверит, что нет пересекающихся периодов действия.
## Bulk-закрытие при cascade
Если запись X **используется** в других справочниках (через `x-references`),
система проверит ссылки:
- Если есть активные записи, ссылающиеся на X — будет предупреждение
- Можно: (а) сначала закрыть/обновить зависимые записи, (б) принять
риск orphan'ов (downstream consumers получат FK-misses)
См. [Cross-references (FK)](../integration/api/x-references.md) о том,
как зависимости валидируются на уровне API.
## Закрытие vs полное удаление
**Полное удаление записи (DELETE из БД) не поддерживается** через UI и
обычный API. Это намеренно — целостность истории важнее «чистоты».
Если ОЧЕНЬ нужно (например, по GDPR / по приказу) — администратор
выполняет это через специальную процедуру с подтверждением. Свяжись с
ответственным за систему.
## Что произойдёт при закрытии
- В БД у записи обновляется `valid_to`
- В Kafka-топик публикуется событие `RecordClosed` с указанием
`valid_to` и причины (если задана)
- В audit log пишется операция `CLOSE` с привязкой к оператору
- Downstream consumers (Альтум, BI) видят новую версию состояния
справочника на следующем чтении (или сразу через событие)
## Чек-лист перед закрытием
- [ ] Я уверен, что закрываю правильную запись (бизнес-ключ совпадает)
- [ ] Если у записи есть зависимости через FK — я их проверил
- [ ] `valid_to` соответствует реальному моменту вывода из эксплуатации
(не «сейчас» по умолчанию, если эксплуатация закончилась в прошлом)
- [ ] Я понимаю: история останется, downstream-консьюмеры узнают
+118
View File
@@ -0,0 +1,118 @@
# Создание справочника
Справочник — это таблица однородных записей с фиксированной схемой полей.
Прежде чем создавать его в системе, продумай: какие поля будут, какие
обязательны, какие ссылаются на другие справочники.
## Когда нужен новый справочник
- Нужны однородные данные (>5 записей со схожей структурой)
- Записи будут переиспользоваться: на них будут ссылаться другие
справочники или внешние системы
- Структура данных стабильна (поля меняются редко)
Если просто нужна пара значений «для себя» — лучше добавь поле-перечисление
(enum) в существующий справочник, чем заводи отдельный.
## Шаги
### 1. Открой каталог
В админ-панели — раздел «Справочники». Кнопка **«Создать справочник»** в
правом верхнем углу.
### 2. Заполни основные поля
- **Имя (name)** — машинное имя, латиница в snake_case (`spacecraft`,
`ground_station`). По этому имени справочник будет доступен в API
и других справочниках через FK.
- **Отображаемое имя** — человеческое, как видит оператор
(«Космические аппараты», «Наземные станции»).
- **Версия схемы** — `1.0.0`, повышается при изменении схемы (см.
[schema drafts](../reviewer/schema-drafts.md)).
- **Bundle** — к какому набору справочников относится (`cuod` по умолчанию).
- **Scope** — `PUBLIC` / `INTERNAL` / `RESTRICTED`. Кто сможет читать.
### 3. Опиши схему полей
Схема справочника — это **JSON Schema** (стандарт описания структуры
JSON-объектов). В редакторе (Monaco) задаёшь свойства записи.
Минимальный пример:
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-id-source": "code",
"required": ["code"],
"properties": {
"code": {
"type": "string",
"x-unique": true,
"description": "Уникальный код"
},
"name": {
"type": "object",
"x-localized": true
}
}
}
```
#### Полезные аннотации
| Аннотация | Что делает |
|---|---|
| `x-id-source: "code"` | Бизнес-ключ записи берётся из поля `code` (а не задаётся отдельно) |
| `x-unique: true` | Значение уникально по справочнику (например, нет двух записей с одинаковым `code`) |
| `x-localized: true` | Поле локализованное — хранит значения для каждого языка (`{ru: "Земля", en: "Earth"}`) |
| `x-references: "dict.field"` | Внешний ключ — ссылка на запись другого справочника |
| `description: "..."` | Подсказка для оператора, показывается в UI рядом с полем |
| `enum: ["A", "B"]` | Список допустимых значений |
| `format: "date"` / `"date-time"` | Календарный picker в форме |
#### Пример со ссылкой на другой справочник
```json
{
"type": "string",
"x-references": "satellite_type.code",
"description": "Тип спутника"
}
```
При вводе оператор увидит выпадающий список типов из справочника
`satellite_type`.
### 4. Опубликуй
После нажатия **«Создать»** справочник появляется в каталоге, схема
блокируется (запись валидируется по ней). Чтобы изменить схему позже —
создай [schema draft](../reviewer/schema-drafts.md) и пройди ревью.
## Что произойдёт автоматически
При создании справочника:
- В БД появляется таблица записей с этой схемой
- В REST API endpoint становится доступен: `/api/v1/dictionaries/<name>/records`
- Kafka-топик начинает получать события об изменениях записей
- Audit log начинает писать историю операций
## Чек-лист перед созданием
- [ ] Машинное имя в snake_case
- [ ] Все обязательные поля помечены в `required`
- [ ] Бизнес-ключ задан (`x-id-source` или поле в `required`)
- [ ] Локализованные поля имеют `x-localized: true`
- [ ] FK-поля указывают на существующие справочники
- [ ] Описания (`description`) даны для каждого поля — это твоя памятка
и помощь следующему оператору
## Что НЕ стоит делать
- ❌ Создавать справочник «на всякий случай» без планируемых записей
- ❌ Класть бизнес-логику в схему (валидации вида «если X то Y») — это
приведёт к жёсткой связке, тяжело менять
- ❌ Зашивать константы в `description` — описание читается оператором,
но валидацию по нему не построишь
+101
View File
@@ -0,0 +1,101 @@
# Работа с записями
Запись — одна строка в справочнике (например, конкретный космический
аппарат «ISS» в справочнике `spacecraft`).
## Добавить запись
1. Открой справочник в каталоге
2. Кнопка **«Создать запись»** в правом верхнем
3. Заполни поля по схеме — обязательные помечены звёздочкой
4. **Период действия**`valid_from` / `valid_to`:
- `valid_from` — с какой даты запись актуальна (по умолчанию: сейчас)
- `valid_to` — до какой даты (по умолчанию: открыто)
5. **«Сохранить»** — запись появляется в списке
### Бизнес-ключ
Если у справочника `x-id-source: "code"`, бизнес-ключ берётся из поля
`code`. Иначе — задаётся отдельно при создании. Бизнес-ключ должен быть
уникален: попытка создать запись с уже существующим ключом → ошибка 409.
### Локализованные поля
Поля с `x-localized: true` имеют отдельные значения для каждого языка:
```
Название [ru]: Международная космическая станция
Название [en]: International Space Station
```
Если оператор ввёл только одну локаль — остальные пусты, при чтении в
другой локали покажется fallback на язык по умолчанию словаря (обычно
`ru` для ЦУОД).
### FK-поля (ссылки)
Поля с `x-references` отображаются как выпадающий список. Например, у
`spacecraft` поле `type` ссылается на `satellite_type.code` — оператор
выбирает значение из готового списка типов. Если нужный тип отсутствует —
сначала добавь его в справочник `satellite_type`, потом возвращайся.
## Редактировать запись
1. Клик по записи в списке → открывается боковая панель (drawer)
2. Меняешь нужные поля, **«Сохранить»**
3. Новая версия записи создаётся; **предыдущая остаётся в истории**
(см. [bitemporal модель](../integration/api/bitemporal.md))
История версий доступна:
- В drawer'е — кнопка **«История версий»**
- В timeline-режиме (`TimeTravel`) — слайдер по датам
### Что нельзя редактировать
- **Бизнес-ключ** — фиксирован с момента создания. Если ошибся — нужно
закрыть запись (см. [Закрытие записи](close-record.md)) и создать новую.
- **Схему** — общая для всех записей справочника, меняется только через
[schema draft](../reviewer/schema-drafts.md).
## Поиск и фильтрация
В каталоге справочника — toolbar поиска и фильтров:
- **Поиск по тексту** — ищет по бизнес-ключу и локализованным полям
- **Только заполненные** — toggle, скрывает записи где конкретное поле пустое
- **Период** — фильтр по `valid_from` / `valid_to`
- **Scope** — фильтр по уровню доступа (для администраторов)
### Глобальный поиск
В шапке (TopBar) — `⌘K` / `Ctrl+K` — поиск через все справочники по
бизнес-ключу или фрагменту названия.
## Bulk-операции
Сейчас bulk import / export — **только через API** ([интеграционный гайд](../integration/api/index.md)).
В UI массовых операций пока нет — добавятся в будущих версиях по запросам
от операторов.
## Что произойдёт при сохранении
- В БД добавляется новая версия записи
- В Kafka-топик публикуется событие `RecordCreated` или `RecordUpdated`
(потребители — Альтум, геопортал, BI — узнают об изменении в течение
секунд)
- В audit log пишется кто изменил, когда, что было до и после
- Если справочник имеет включённую Redis-проекцию — кэш обновляется
автоматически
## Типичные ошибки
См. [Разбор ошибок](errors.md). Самые частые:
- **«Бизнес-ключ уже существует»** (409) — попробуй другой `code` или
проверь, не закрыта ли существующая запись (фильтр истории)
- **«Поле X обязательно»** (422) — посмотри `required` в схеме
- **«FK не найден»** (422) — значение `x-references` указывает на запись,
которой нет; добавь в referenced справочник
- **«Период действия пересекается»** (409) — для bitemporal: нельзя
иметь две активные версии одной записи в один момент. См.
[Закрытие записи](close-record.md).
+144
View File
@@ -0,0 +1,144 @@
# Разбор ошибок валидации
Ordinis старается давать понятные ошибки, но иногда нужна расшифровка.
Эта страница — справочник самых частых.
## Формат ошибок
В UI ошибки появляются:
- **Инлайн под полем формы** — для конкретных полей (red text)
- **Тост в правом верхнем** — для общих ошибок (например, сеть)
- **Полноэкранный экран** — для серьёзных проблем (500 / 503)
Каждая ошибка имеет:
- **Код** (`webhook_status_invalid`, `record_not_active`) — машинный идентификатор
- **Сообщение** — человеческое описание на русском
- **Trace ID** (для серверных ошибок) — для передачи в ops
## Самые частые ошибки
### 422: «Поле X обязательно»
Что значит: схема справочника требует это поле, но ты его не заполнил.
Что делать: заполни и сохрани. Если поле кажется ненужным — это вопрос
к схеме справочника, обсуди с ревьюером (см. [schema drafts](../reviewer/schema-drafts.md)).
### 422: «Тип значения не соответствует схеме»
Что значит: например, в поле `mass_kg` (number) введён текст.
Что делать: проверь тип в hint'е под полем. Числовые поля принимают только
цифры (точка как разделитель десятых для дробей: `1234.5`).
### 422: «FK не найден»
Что значит: значение в поле со ссылкой (`x-references`) указывает на
запись, которой нет в целевом справочнике.
Что делать:
1. Открой целевой справочник (имя есть в hint'е поля)
2. Проверь, есть ли там запись с этим бизнес-ключом
3. Если нет — сначала добавь запись туда, потом возвращайся
4. Если есть, но закрыта — нужно её **активировать** или создать новую
### 409: «Бизнес-ключ уже существует»
Что значит: ты пытаешься создать запись с `code`, который уже занят
активной записью того же справочника.
Что делать:
- Выбери другой `code`
- Или: найди существующую запись и обнови её, а не создавай новую
- Или: закрой существующую (если она устарела) и создай заново
### 409: «Период действия пересекается»
Что значит: bitemporal модель не разрешает двум версиям одной записи
быть активными одновременно.
Что делать:
1. Найди активную версию того же бизнес-ключа
2. **Закрой её** (`valid_to = момент новой версии`), потом создавай новую
3. Или: укажи `valid_from` новой версии так, чтобы периоды не пересекались
См. [Закрытие записи](close-record.md).
### 403: «Нет доступа»
Что значит: у твоего пользователя нет прав на эту операцию или scope.
Что делать:
- Если справочник `RESTRICTED` — нужны права администратора, спроси у
руководителя
- Если `INTERNAL` — нужен внутренний токен (не публичный)
- Если операция «approve schema draft» — это право ревьюера, не оператора
(см. [reviewer guide](../reviewer/index.md))
### 401: «Требуется вход»
Что значит: твоя сессия истекла или нет валидного токена.
Что делать: войди заново через Keycloak. После logout / refresh всё должно
работать.
### 503: «Сервер временно недоступен»
Что значит: backend временно не отвечает (rolling restart, плановое
обслуживание, или сетевой сбой).
Что делать:
1. Подожди 30-60 секунд, нажми **«Повторить»**
2. Если повторяется > 5 минут — свяжись с ops
3. Никакие данные не пропали — сервер вернётся, твои изменения сохранены
локально пока не отправил
### Network error / «Нет соединения»
Что значит: твой браузер не может достучаться до сервера. Чаще всего
проблема в твоей сети, не в Ordinis.
Что делать:
1. Проверь соединение (открой любой другой сайт)
2. Если корпоративная сеть — проверь VPN / прокси
3. Если изменения не сохранились — они **не пропадут**, попробуй
повторить когда соединение вернётся
### «AxiosError: Request failed with status code 503» (raw)
Что значит: техническая ошибка, которая «убежала» в UI без friendly
обёртки. Это **баг UI**, не баг данных.
Что делать: сделай скриншот, передай в support с trace ID (если есть).
## Schema validation на стороне сервера
Когда ты сохраняешь запись, сервер валидирует её по JSON Schema справочника.
Если ошибка — увидишь подробности:
```
Validation failed:
- /satellite_type: must be one of ['OPERATIONAL', 'TESTING', 'DECOMMISSIONED']
- /mass_kg: must be number, got string
- /name: required property missing
```
Каждая строка — отдельная проблема. Поправь все, сохрани заново.
## Если не помогает
1. **Скриншот** — снимок экрана со всеми видимыми деталями ошибки
2. **Trace ID** — если есть в сообщении, скопируй
3. **Время** — точное время, когда возникла
4. **Что ты делал** — последняя последовательность действий
5. Передай это команде поддержки. По этим данным они смогут восстановить
контекст через [журнал аудита](audit-log.md) и серверные логи.
## Что НЕ нужно делать
-**Очищать кэш браузера / cookies** — это сломает сессию, ошибку не уберёт
-**Открывать в инкогнито** — то же самое
-**Менять JSON вручную в DevTools** — данные не сохранятся всё равно,
но можешь напугать себя
-**Заводить дубликат записи «чтобы попробовать»** — если первая не
сохранилась, она и не сохранилась, дубликат не появится
+53
View File
@@ -0,0 +1,53 @@
# Руководство оператора Ordinis НСИ
Это руководство — для **операторов справочников**: тех, кто заводит и
поддерживает данные в Ordinis (космические аппараты, наземные станции,
типы антенн, операторов спутниковой связи и т.д.).
Если ты **разработчик-интегратор** (Альтум, BI, геопортал) — смотри
[интеграцию по API](../integration/api/index.md).
Если ты **ревьюер** schema drafts — смотри
[руководство ревьюера](../reviewer/index.md).
## Что внутри
- [Создание справочника](create-dictionary.md) — как добавить новый
справочник (например, «типы антенн») и описать его поля
- [Работа с записями](edit-record.md) — как добавлять, редактировать,
искать записи внутри справочника
- [Закрытие записи](close-record.md) — как корректно «удалить» запись,
сохранив историю (bitemporal модель)
- [Журнал аудита](audit-log.md) — как посмотреть, кто и когда что менял
- [Разбор ошибок](errors.md) — типичные ошибки валидации и как их понять
## Базовые понятия
**Справочник** — таблица с однородными записями (например, «спутники»,
«наземные станции», «частотные диапазоны»). У каждого справочника есть
**схема** — описание полей: какие поля, какого типа, обязательные или
нет, ссылаются ли на другие справочники.
**Запись** — одна строка в справочнике. У каждой записи есть **бизнес-ключ**
(уникальный код) и **данные** (значения полей по схеме). Также есть
**период действия**`valid_from` / `valid_to`, в течение которого
запись считается актуальной.
**Scope** — уровень доступа к данным:
- `PUBLIC` — открыто всем
- `INTERNAL` — для внутренних потребителей
- `RESTRICTED` — только для администраторов
**Schema draft** — предложение изменить схему справочника (добавить поле,
изменить тип). Не применяется автоматически — проходит ревью, затем
публикуется.
## Какую роль ты выполняешь
| Если ты... | Иди в раздел |
|---|---|
| Создаёшь новый справочник | [Создание справочника](create-dictionary.md) |
| Добавляешь запись в существующий справочник | [Работа с записями](edit-record.md) |
| Хочешь «удалить» устаревшую запись | [Закрытие записи](close-record.md) |
| Видишь ошибку валидации, не понимаешь | [Разбор ошибок](errors.md) |
| Проверяешь, кто менял запись | [Журнал аудита](audit-log.md) |