diff --git a/HISTORY.md b/HISTORY.md index 1060f96..eea8e40 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -584,6 +584,7 @@ web/src/hooks/useLiveMap.ts undo + canUndo ### Далі -- Підкладки-плани приміщень на полотні (потрібен прийом файлів). -- Історія метрик для графіків. -- `EnrollmentService` + видача сертифікатів зондам. +План на наступні етапи винесено в окремий документ — [ROADMAP.md](ROADMAP.md). +Коротко: користувачі й права (схема є, коду немає) → алерти → шаблони опитування +(нова підсистема `tpl.*`) → керування зондом із UI → NCM до кінця → мобільний +адаптив і PWA. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..5fd85e7 --- /dev/null +++ b/ROADMAP.md @@ -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, гірша за її відсутність). + +**Обсяг:** ~2–3 дні. Ризиків мало: схема готова, візерунок автентифікації в +проєкті вже відпрацьований на агентських токенах. + +--- + +## Етап 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 стає бажаним, а не критичним шляхом. + +**Обсяг:** ~5–7 днів на схему + реконсиляцію + імпортер + 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/`, віддає +diff між ревізіями. Зараз `commit_sha` тимчасово містить hex контентного хеша, і +міняти доведеться лише джерело цього поля. + +**UI:** список версій, side-by-side diff, кнопка відкату (сутність +`ncm.rollbacks` із двоетапним погодженням уже є). + +**Обсяг:** ~4–5 днів. + +--- + +## Етап 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` уже наповнюється), +повзунки лімітів, перемикачі модулів, кнопка «запустити виявлення», інструкція +встановлення з готовою командою й токеном. + +**Обсяг:** ~2–3 дні. + +--- + +## Етап 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` у схемі готова. + +**Обсяг:** ~4–5 днів. + +--- + +## Етап 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* — усе з ТЗ. + +**Обсяг:** ~3–4 дні на адаптив + 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` оголошені в сіді плагінів, але не написані.