# NetPulse — план робіт Документ доповнює [HISTORY.md](HISTORY.md): той описує зроблене, цей — що лишилось і чому саме в такому порядку. ## Де ми зараз Працює наскрізний ланцюг **кабель → браузер**: зонд знаходить сусідів по LLDP/CDP/ARP, сервер зводить їх у `topo.links`, сам заводить чеки на трафік, API віддає готове полотно з живими статусами, редактор пише назад із контролем конфліктів і відкатом. Перевірено на живому стенді проти справжнього `snmpd`. Деталі — в README кожного компонента. ## Чого немає: чесний зріз Схема БД з Етапу 1 покриває майже все з технічного завдання, але **схема ≠ реалізація**. Нижче — розрив між ними. | Підсистема | Схема | Реалізація | |------------|-------|------------| | Мапа, топологія, телеметрія | ✅ | ✅ | | Автовиявлення LLDP/CDP/ARP/FDB | ✅ | ✅ | | **Користувачі, ролі, вхід** | ✅ | ✅ | | **Шаблони опитування** | ❌ | ❌ | | **NCM (збір конфігів)** | ✅ | ⚠️ збір, розклад і Git є; лишився тригер за Syslog | | **Керування зондом із UI** | ✅ | ⚠️ транспорт є | | **Алерти й сповіщення** | ✅ | ✅ | | **Мобільна адаптивність, PWA** | — | ⚠️ адаптив є, PWA немає | | Веб-інтерфейс (навігація, сторінки) | — | ✅ | | Дашборди, NOC TV | ✅ | ❌ | | Білінг, ліцензії | ✅ | ❌ | --- ## Етап 5. Веб із користувачами і правами — ✅ зроблено 2026-08-15 > Реалізовано: вхід, JWT + ротація refresh-сесій, RBAC на всіх ендпоїнтах, > `netpulse-user` для першого власника, сторінка входу, приховування дій без > права, мобільний адаптив. Подробиці — [HISTORY.md](HISTORY.md), > контракт — [server/API.md](server/API.md). > > **Доповнено 2026-08-15:** повноцінний веб — навігація й вісім сторінок > (мапа, пристрої, алерти, правила, канали, зонди, команда, профіль), > примітиви UI з мобільними картками замість таблиць, захист маршрутів > правами. > > **Відкладено з цього етапу:** TOTP (`totp_secret_enc` є в схемі, коду немає), > запрошення поштою (`core.invitations` порожня — користувача заводять із > паролем одразу), кастомні ролі (`POST /api/v1/roles`), фільтр за > `memberships.scope_group_ids`, редагування умови правила (лише створення > й вимкнення), маршрути сповіщень і вікна обслуговування в UI. **Навіщо перше.** Зараз 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-подібні) — ⚠️ наполовину, 2026-08-24 > **Зроблено:** схема `tpl.*`, реконсиляція шаблонів у `core.checks`, > звірка планів зонда, REST і редактор у вебі, чотири вбудовані шаблони. > Перевірено наскрізно на живому net-snmp. > > **Додано 2026-08-24:** шаблон описує перевірки будь-якого типу (не лише > OID), імпорт/експорт глобальний і поштучний, ручні інтервали. > > **Додано 2026-08-24:** графіки описуються в шаблоні (шість видів, > жорсткі межі осей), картка хоста на вкладках без інтервалів. > > **Лишилось:** автопризначення за `sysObjectID`, прототипи шаблонів, > `snmp.walk` як тип елемента (таблиці з динамічним індексом), тригери > всередині шаблону. ## Етап 6 (початковий план) **Найбільша нова підсистема.** Зараз `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` тим самим механізмом, що й паролі. ### Готова база шаблонів Формат `tpl.*` варто спроєктувати так, щоб він приймав **Zabbix-експорт** (YAML/JSON з items, discovery rules, prototypes, macros): його структура майже один-до-одного лягає на запропоновану схему, а публічних шаблонів там сотні. Це знімає потребу набивати базу вручну. Команди збору конфігу вже є власним каталогом — `db/profiles/catalog.json`, 147 платформ. Шаблони опитування SNMP — окрема задача, і базу для них варто починати саме з імпортера, а не з ручного наповнення. **Обсяг:** ~5–7 днів на схему + реконсиляцію + імпортер + UI редактора шаблонів. --- ## Етап 7. NCM — збір конфігів до кінця — ⚠️ майже, 2026-08-25 > **Зроблено:** агентський модуль SSH/Telnet, черга завдань, диспетчер, > вивантаження зі стисненням і дедуплікацією, візуальний diff у вебі, > планувальник за cron, Git-двигун на go-git, порівняння з довільною > версією в UI, переливання історії командою `netpulse-gitsync`. > Перевірено наскрізно. > > **Лишилось:** тригер за Syslog. Він упирається не в тригер: приймача > syslog немає взагалі — `ts.syslog` і `WriteLogs` у сховищі є, але на > агенті немає модуля, який слухав би 514/udp, і транспорт до сервера > не під'єднано. Це окрема робота розміром із власний етап, а не > дописування умови до наявного коду. ## Етап 7 (початковий план) Половина шляху вже є: `ncm.*` у схемі, `ConfigJob`/`ConfigUpload` у контракті, сервер приймає чанки, звіряє sha256, дедуплікує за `content_hash` і шифрує тіло. Бракує трьох частин. **Модуль `ncm` на агенті.** SSH через `golang.org/x/crypto/ssh`, Telnet своїм кодом (протокол тривіальний). Виконує команди з профілю, ловить prompt за регуляркою, віддає сирий текст чанками. Для `bdcom-olt` і `mikrotik-routeros` профілі вже в сіді — вони й стануть першими підопічними. **Планувальник на сервері.** ✅ Cron із `ncm.device_policies.cron` — власний парсер `internal/cronx`, тік раз на хвилину під advisory-блокуванням. Лишився тригер за Syslog-подією (`on_syslog`, `%SYS-5-CONFIG_I`) — поле в схемі є, читати його нікому. **Git-двигун.** ✅ `go-git`, чистий Go без cgo. Голий репозиторій на тенанта, гілка `refs/heads/device/` на пристрій, файл `<пристрій>/<тип>.cfg`. Однаковий вміст нового коміту не створює. `netpulse-gitsync` переливає вже накопичену історію й відтворює втрачений репозиторій із бази. **UI:** ✅ список версій і порівняння з довільною попередньою. Кнопка відкату лишилась (сутність `ncm.rollbacks` із двоетапним погодженням уже є). **Обсяг:** ~4–5 днів. --- ## Етап 8. Керування зондом із UI — ✅ зроблено 2026-08-25 > **Зроблено:** `EnrollmentService` із одноразовими запрошеннями, файл > посвідчення на агенті, сторінка зондів із командою встановлення, > керування модулями й лімітами, видалення зонда. Перевірено наскрізно. > > **Лишилось:** справжній CA з підписом CSR — контракт це передбачає, > але власний центр сертифікації з ротацією є окремою системою. ## Етап 8 (початковий план) Транспорт готовий повністю: сервер уже вміє штовхати `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. Алерти й сповіщення — ✅ зроблено 2026-08-15 > Реалізовано: движок правил (icmp/interface/metric/no_data), антифлап > вікном, кореляція за топологією, вікна обслуговування й ручне > заглушення, маршрути з тихими годинами, доставка в Telegram/webhook/ > SMTP, панель алертів у UI. Подробиці — [HISTORY.md](HISTORY.md), > контракт — [server/API.md](server/API.md). > > **Відкладено з цього етапу:** ескалації й повторні сповіщення, > приймач кнопок Telegram (`callback_data`), Web Push (іде з Етапом 10), > правила з джерел `syslog`/`trap`/`ncm`/`compliance`. **Те, без чого це не моніторинг.** Система малює мапу, але мовчить, коли щось падає. Схема готова з Етапу 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 (алерти)** — без сповіщень це не моніторинг.~~ ✅ 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` оголошені в сіді плагінів, але не написані.