План робіт: користувачі, шаблони опитування, NCM, керування зондом, мобільний

Зріз розриву між схемою й реалізацією: схема з Етапу 1 покриває майже все
ТЗ, але користувачі/ролі, NCM на агенті й керування зондом із UI лишились
без коду, а шаблонів опитування немає навіть у схемі.

Ключове рішення для шаблонів: шаблон — декларація, core.checks —
матеріалізація. Агент не знає про шаблони взагалі й далі отримує плаский
план, тож їх можна міняти без оновлення зондів у полі. LLD — той самий
snmp.walk, результат якого йде в реконсиляцію, а не в метрики.

Препроцесинг ділиться: дельта лічильника лишається на агенті (лише він
знає фактичний інтервал), решта — на сервері при записі.

Формат tpl.* проєктується під імпорт Zabbix-експорту, щоб база з NOC
project не була критичним шляхом: там профілі — Python-класи, а не
декларативні описи.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
zotac 2026-08-15 00:54:02 +03:00
parent bee55be3d1
commit ccaa9afcc4
2 changed files with 271 additions and 3 deletions

View file

@ -584,6 +584,7 @@ web/src/hooks/useLiveMap.ts undo + canUndo
### Далі
- Підкладки-плани приміщень на полотні (потрібен прийом файлів).
- Історія метрик для графіків.
- `EnrollmentService` + видача сертифікатів зондам.
План на наступні етапи винесено в окремий документ — [ROADMAP.md](ROADMAP.md).
Коротко: користувачі й права (схема є, коду немає) → алерти → шаблони опитування
(нова підсистема `tpl.*`) → керування зондом із UI → NCM до кінця → мобільний
адаптив і PWA.

267
ROADMAP.md Normal file
View file

@ -0,0 +1,267 @@
# NetPulse — план робіт
Документ доповнює [HISTORY.md](HISTORY.md): той описує зроблене, цей — що лишилось
і чому саме в такому порядку.
## Де ми зараз
Працює наскрізний ланцюг **кабель → браузер**: зонд знаходить сусідів по
LLDP/CDP/ARP, сервер зводить їх у `topo.links`, сам заводить чеки на трафік,
API віддає готове полотно з живими статусами, редактор пише назад із контролем
конфліктів і відкатом.
Перевірено на живому стенді проти справжнього `snmpd`. Деталі — в README кожного
компонента.
## Чого немає: чесний зріз
Схема БД з Етапу 1 покриває майже все з технічного завдання, але **схема ≠
реалізація**. Нижче — розрив між ними.
| Підсистема | Схема | Реалізація |
|------------|-------|------------|
| Мапа, топологія, телеметрія | ✅ | ✅ |
| Автовиявлення LLDP/CDP/ARP/FDB | ✅ | ✅ |
| **Користувачі, ролі, вхід** | ✅ | ❌ |
| **Шаблони опитування** | ❌ | ❌ |
| **NCM (збір конфігів)** | ✅ | ⚠️ half |
| **Керування зондом із UI** | ✅ | ⚠️ транспорт є |
| **Алерти й сповіщення** | ✅ | ❌ |
| **Мобільна адаптивність, PWA** | — | ❌ |
| Дашборди, NOC TV | ✅ | ❌ |
| Білінг, ліцензії | ✅ | ❌ |
---
## Етап 5. Веб із користувачами і правами
**Навіщо перше.** Зараз API автентифікує лише машинні токени
(`core.api_tokens`). Людина увійти не може, а `core.roles` / `permissions` /
`memberships` лежать порожні. Без цього не можна ані впустити клієнта, ані
розмежувати доступ між інженером і глядачем — тобто продукт неможливо продати
навіть одній команді.
### Сервер
```
POST /api/v1/auth/login email + пароль → access (JWT, 15 хв) + refresh
POST /api/v1/auth/refresh обмін refresh-токена
POST /api/v1/auth/logout відкликання сесії
GET /api/v1/me профіль + ефективні права
POST /api/v1/auth/totp/enroll двофакторка (поле totp_secret_enc уже є)
GET /api/v1/users керування командою
POST /api/v1/users/invite запрошення (core.invitations уже є)
PATCH /api/v1/users/{id} зміна ролі, скоупу груп
GET /api/v1/roles системні + кастомні (Enterprise)
POST /api/v1/roles кастомна роль із набором прав
```
**Паролі — argon2id**, не bcrypt: він стійкіший до GPU-перебору, а `password_hash`
у схемі вже text і формат не диктує.
**Refresh-токен зберігається як `sha256` у `core.sessions`** — так само, як
агентські. Витік дампа БД не дає жодної живої сесії.
**RBAC замінює перевірку scopes.** Зараз обробники питають `tok.Can("maps:write")`.
Стане: посередник резолвить `memberships → roles → role_permissions` у набір прав
і кладе в контекст; машинні токени лишаються, але їхні scopes перетинаються з
правами власника. Скоуп груп (`memberships.scope_group_ids`) фільтрує вибірки
пристроїв — це вже не middleware, а предикат у запитах `store`.
### Фронтенд
Сторінка входу, зберігання refresh у httpOnly-cookie, сторінки «Команда» й «Ролі»,
приховування дій без права (кнопка, яка завжди дає 403, гірша за її відсутність).
**Обсяг:** ~23 дні. Ризиків мало: схема готова, візерунок автентифікації в
проєкті вже відпрацьований на агентських токенах.
---
## Етап 6. Шаблони опитування (Zabbix-подібні)
**Найбільша нова підсистема.** Зараз `snmp.if`-чеки народжує захардкоджений
`autochecks.go`. Це працює рівно для одного випадку — інтерфейсів. Щойно
знадобиться CPU Cisco, температура MikroTik чи ємність UPS, доведеться дописувати
Go-код під кожен вендор. Шаблони роблять це даними.
### Нова схема: `tpl`
```sql
tpl.templates -- key, name, vendor, is_builtin, tenant_id NULL = вбудований
tpl.template_links -- успадкування шаблон → шаблон
tpl.macros -- {$SNMP_COMMUNITY}, три рівні: глобальний → шаблон → пристрій
tpl.items -- один OID → одна метрика: key, check_type, params, interval,
-- units, value_type, preprocessing, metric_key
tpl.discovery_rules -- LLD: walk по таблиці (ifTable, entPhysicalTable, dot1dBase)
tpl.item_prototypes -- прототипи з {#IFNAME}, {#SNMPINDEX}
tpl.trigger_prototypes -- пороги, що народжують alr.rules
inv.device_templates -- прив'язка шаблон → пристрій
inv.group_templates -- прив'язка шаблон → динамічна група
```
### Рішення, які треба зафіксувати одразу
**Шаблон — декларація, `core.checks` — матеріалізація.** Агент не має знати про
шаблони взагалі: він і далі отримує плаский план задач. Сервер реконсилює
`шаблони × пристрої × LLD → core.checks` при кожній зміні шаблону, складу групи
або результату виявлення. Це зберігає контракт агента незмінним і дозволяє
міняти шаблони без оновлення зондів у полі.
**Low-level discovery — це той самий `snmp.walk`,** результат якого йде не в
метрики, а в реконсиляцію. Тобто `autochecks.go` узагальнюється: замість
«знайшов інтерфейси → створив snmp.if» стане «правило виявлення повернуло
рядки → застосував прототипи → створив items».
**Препроцесинг ділиться між агентом і сервером.** Дельта лічильника (`change per
second`) лишається на агенті: **лише він знає фактичний інтервал** між двома
опитуваннями — це вже реалізовано й перевірено. Решта (множник, регулярка,
JSONPath, `discard unchanged`) — на сервері при записі: інакше кожна зміна
правила вимагала б оновлення агентів.
**Макроси розшифровуються на сервері** й доїжджають до агента вже підставленими,
разом із креденшелами. Секретні макроси (`{$SNMP_COMMUNITY}`) лягають у
`core.secrets` тим самим механізмом, що й паролі.
### Готова база шаблонів
Ти згадав, що візьмеш базу з **NOC project**. Це реалістично, але не безкоштовно:
у NOC профілі — це Python-класи з методами розбору, а не декларативні YAML.
Витягти з них набори OID і командні послідовності можна, але це окрема задача
з непередбаченим обсягом.
**Пропоную страхуватись:** формат `tpl.*` спроєктувати так, щоб він приймав і
**Zabbix-експорт** (YAML/JSON з items, discovery rules, prototypes, macros) —
його структура майже один-до-одного лягає на запропоновану схему, а публічних
шаблонів там сотні. Тоді імпортер із NOC стає бажаним, а не критичним шляхом.
**Обсяг:** ~57 днів на схему + реконсиляцію + імпортер + UI редактора шаблонів.
Найбільший ризик — саме імпорт із NOC; його варто оцінити окремо, побачивши
формат, який ти маєш.
---
## Етап 7. NCM — збір конфігів до кінця
Половина шляху вже є: `ncm.*` у схемі, `ConfigJob`/`ConfigUpload` у контракті,
сервер приймає чанки, звіряє sha256, дедуплікує за `content_hash` і шифрує тіло.
Бракує трьох частин.
**Модуль `ncm` на агенті.** SSH через `golang.org/x/crypto/ssh`, Telnet своїм
кодом (протокол тривіальний). Виконує команди з профілю, ловить prompt за
регуляркою, віддає сирий текст чанками. Для `bdcom-olt` і `mikrotik-routeros`
профілі вже в сіді — вони й стануть першими підопічними.
**Планувальник на сервері.** Cron із `ncm.device_policies.cron` плюс тригер за
Syslog-подією (`on_syslog`, `%SYS-5-CONFIG_I`) — обидва поля в схемі є, читати їх
нікому. Видає `ConfigJob` у живу сесію зонда.
**Git-двигун.** `go-git` замість libgit2 — **чистий Go, без cgo**, що зберігає
статичний бінарник без залежностей. Комітить у `refs/heads/device/<id>`, віддає
diff між ревізіями. Зараз `commit_sha` тимчасово містить hex контентного хеша, і
міняти доведеться лише джерело цього поля.
**UI:** список версій, side-by-side diff, кнопка відкату (сутність
`ncm.rollbacks` із двоетапним погодженням уже є).
**Обсяг:** ~45 днів.
---
## Етап 8. Керування зондом із UI
Транспорт готовий повністю: сервер уже вміє штовхати `ModuleControl` (які модулі
вмикати), `TaskDelta` (що опитувати), ліміти в `Welcome` (паралельність, розмір
батчу, темп ICMP) і `Directive` (пауза, перезавантаження конфігу, оновлення).
Бракує того, що це все вмикає.
```
POST /api/v1/agents створити зонд + одноразовий enrollment-токен
PATCH /api/v1/agents/{id} ліміти, дозволені модулі, сайт
POST /api/v1/agents/{id}/discover запустити автовиявлення зараз (TriggerNow уже є)
DELETE /api/v1/agents/{id}
```
**`EnrollmentService`** — реалізувати серверну сторону: зонд приходить із
одноразовим токеном і CSR, іде з підписаним сертифікатом. Контракт написано ще на
Етапі 2, реалізації немає. Без цього зонди заводяться `INSERT`ом, що прийнятно на
стенді й неприйнятно у клієнта.
**UI:** сторінка зонда з самометриками (`ts.agent_health` уже наповнюється),
повзунки лімітів, перемикачі модулів, кнопка «запустити виявлення», інструкція
встановлення з готовою командою й токеном.
**Обсяг:** ~23 дні.
---
## Етап 9. Алерти й сповіщення
**Те, без чого це не моніторинг.** Система малює мапу, але мовчить, коли щось
падає. Схема готова з Етапу 1 (`alr.rules`, `alerts`, `channels`, `routes`,
`escalation_policies`, `maintenance_windows`, `mutes`) і повністю порожня.
Дані вже течуть: зміни статусу йдуть через `core.event_outbox`, метрики лежать у
TSDB, пороги описані в `map_edges.thresholds`.
**Движок правил** читає `ts.icmp_samples`, `ts.if_counters` і `ts.samples_5m`,
застосовує `for_seconds` (антифлап) і пише в `alr.alerts`. Дедуплікацію вже
гарантує унікальний індекс `alerts_active_dedup_uniq`.
**Кореляція за топологією** — та, заради якої будувалась `topo.links`: коли падає
маршрутизатор, 40 пристроїв за ним не мають дати 40 сповіщень. Поля
`root_alert_id` і `depends_on_topology` для цього вже є.
**Канали:** Telegram (бот + Mini App із кнопками *Ack*/*Mute*), Web Push, email,
webhook. `alr.push_subscriptions` у схемі готова.
**Обсяг:** ~45 днів.
---
## Етап 10. Мобільна адаптивність і PWA
Зараз UI розрахований лише на десктоп: фіксований сайдбар 240 px, шапка з десятком
елементів в один рядок, **жодного брейкпойнта**, полотно без тач-жестів.
**Адаптив:** сайдбар у висувну панель під `md`, шапка в дві смуги, цілі дотику не
менше 44 px, інспектор вузла — нижнім аркушем замість бічної колонки.
**Полотно на дотик:** React Flow вміє pinch-zoom і pan, але перетягування вузла
пальцем конфліктує з панорамуванням — потрібен явний режим «редагування», інакше
кожна спроба посунути карту рухатиме вузол.
**PWA:** manifest, service worker (кеш оболонки, не даних — застаріла мапа гірша
за її відсутність), Web Push через `alr.push_subscriptions`.
**Telegram Mini App:** перегляд мапи й алертів, кнопки *Ack* / *Mute* / *View
Diff* — усе з ТЗ.
**Обсяг:** ~34 дні на адаптив + PWA, Mini App окремо.
---
## Порядок і чому саме такий
1. **Етап 5 (користувачі)** — без входу продукт не можна віддати нікому.
2. **Етап 9 (алерти)** — без сповіщень це не моніторинг. Можна поміняти місцями
з п.1, якщо перший клієнт — ти сам.
3. **Етап 6 (шаблони)** — знімає потребу дописувати Go під кожен вендор; що
раніше, то менше захардкодженого коду доведеться викидати.
4. **Етап 8 (керування зондом)** — дешевий і робить онбординг можливим.
5. **Етап 7 (NCM)** — головний аргумент Enterprise-тарифу.
6. **Етап 10 (мобільний)** — після того, як є що показувати.
Далі за ТЗ лишаються дашборди з NOC TV-режимом, білінг зі Stripe і ліцензійні
ключі — усі три мають готову схему й жодного коду.
## Дрібний борг
- Повторне малювання зв'язку між тією ж парою вузлів дає загальне «такий запис
уже існує» замість зрозумілого пояснення.
- Перетягування вузла через UI не покрите автотестом: синтетичні події не
запускають d3-drag у React Flow. Шлях перевірений тестами сервера.
- Підкладки-плани приміщень віддаються API, але полотно їх не малює — потрібен
прийом і роздача файлів (S3/MinIO або локальний диск).
- Модулі `http`/`ssl` і `modbus` оголошені в сіді плагінів, але не написані.