Add hardware ingest flow and UI upload

This commit is contained in:
2026-02-11 21:57:34 +03:00
parent 38ebe8cd2a
commit 66a3166276
14 changed files with 2222 additions and 111 deletions

114
docs/plan/Plan #1.md Normal file
View File

@@ -0,0 +1,114 @@
# Реализация JSON hardware-ingest по INTEGRATION_GUIDE.md
## Summary
Добавляем новый инструмент импорта hardware snapshot: `POST /ingest/hardware` + форму в ingest UI, без ломки текущего `POST /ingest/logbundle`.
Импорт должен:
1. находить/создавать `asset` по `hardware.board.serial_number`,
2. автоматически назначать новые asset в служебный customer/project (`Без заказчика` / `Без проекта`),
3. создавать/обновлять компоненты + LOT,
4. писать `observations` с `details JSON`,
5. синхронизировать `installations`,
6. генерировать timeline events (`LOG_COLLECTED`, `INSTALLED`, `REMOVED`, `FIRMWARE_CHANGED`, `COMPONENT_WARNING`, `COMPONENT_FAILED`),
7. создавать `failure_events` при `Critical`.
## Подтвержденные решения
1. Scope: `API + UI`.
2. Endpoint: основной `POST /ingest/hardware`.
3. Новые asset по умолчанию идут в служебный customer/project:
- customer: `Без заказчика`
- project: `Без проекта`
4. Статусы:
- `Critical` -> `failure_event` + `COMPONENT_FAILED`
- `Warning` -> `COMPONENT_WARNING`
5. Расширенные observation-поля храним в `observations.details JSON`.
6. Ответ endpoint: как в guide (`status`, `bundle_id`, `asset_id`, `collected_at`, `duplicate`, `summary|message`).
## Публичные API / контракты
1. Новый маршрут в `internal/api/ingest.go`:
- `POST /ingest/hardware`
2. Request JSON:
- `collected_at` (required, RFC3339)
- `target_host` (required)
- `hardware` (required) с `board.serial_number` (required)
- опционально: `filename`, `source_type`, `protocol`, секции `cpus/memory/storage/pcie_devices/power_supplies/firmware`
3. Response success (201):
- `status: "success"`
- `bundle_id`, `asset_id`, `collected_at`, `duplicate: false`
- `summary` с детализированными счетчиками
4. Response duplicate (200):
- те же поля + `duplicate: true`, `message`
5. Validation error (400):
- `status: "error"`, `error: "validation_failed"`, `details.field`, `details.message`
## Изменения по файлам (implementation design)
1. `migrations/0007_hardware_ingest/up.sql` и `down.sql`
- `ALTER TABLE observations ADD COLUMN details JSON NULL;`
2. `internal/api/ingest.go`
- новый handler `handleHardware`
- строгая decode/validate
- вызов `IngestHardware(...)`
3. `internal/ingest/service.go`
- `HardwareInput`, `HardwareResult`, summary counters
- transactional pipeline
- `ensureServiceProject()`:
- customer `Без заказчика` (create if missing)
- project `Без проекта` (create if missing)
- `ensureAssetByBoardSerial()`:
- lookup по `assets.vendor_serial = board.serial_number`
- create если нет (`name=target_host`, `project_id=service_project_id`, `vendor/model` по board)
- update vendor/model при валидных значениях
4. `internal/ingest/parser_hardware.go`
- parse + normalize + flatten snapshot
- генерация serial:
- CPU: `{board_serial}-CPU-{socket}`
- PCIe: `{board_serial}-PCIE-{slot}` если пусто/N/A
- фильтрация `present=false`, `status=Empty`
5. `internal/ingest/lot_classifier.go`
- LOT rules для CPU/DIMM/Storage/PSU/PCIe
- `ensureLotByCode()`
6. `internal/ingest/service.go` (status/failure/events)
- `observations.details` хранит: `component_type`, `slot`, `status`, `present`, `telemetry`, raw attrs
- `Critical`:
- `COMPONENT_FAILED` в timeline
- `failure_events` (`source=hardware_ingest`, deterministic `external_id`)
- `Warning`:
- `COMPONENT_WARNING` в timeline
- system firmware (`hardware.firmware`):
- сравнение с последним значением и `FIRMWARE_CHANGED` для asset
7. UI:
- `internal/api/ui_ingest.tmpl`
- `internal/api/ui.go`
- добавить секцию “Hardware Ingest” с примером payload на основе `docs/import-example-full.json`
## Идемпотентность
1. Хеш считается от канонически marshaled hardware request.
2. Если hash уже есть:
- без side effects
- `200 duplicate=true`
3. Если hash новый:
- полный pipeline
- `201`
## Тесты и acceptance
1. `TestIngestHardwareIdempotent`: 201 -> 200 duplicate, без дублей в `log_bundles/observations/installations`.
2. Тест авто-создания служебного customer/project и auto-create asset.
3. Тест замены компонента: `REMOVED` + `INSTALLED`.
4. Тест firmware change: `FIRMWARE_CHANGED`.
5. Тест статусов:
- Warning -> `COMPONENT_WARNING`
- Critical -> `COMPONENT_FAILED` + `failure_events`
6. Validation tests:
- missing required fields
- invalid RFC3339
- unknown field -> 400
7. UI smoke: `/ui/ingest` содержит форму hardware ingest.
## Assumptions and defaults
1. Служебные записи:
- customer.name = `Без заказчика`
- project.name = `Без проекта`
2. При дублях по имени используется запись с минимальным `id`.
3. `manufacturer/product_name == "NULL"` -> `NULL` в БД.
4. Новые `event_type` (`COMPONENT_WARNING`, `COMPONENT_FAILED`) хранятся как строки без enum-миграции.
5. `/ingest/logbundle` остается полностью совместимым.