go-git, чистий Go без cgo. Голий репозиторій на тенанта, гілка на пристрій за ідентифікатором (ім'я змінюють, історія не має від цього розсипатись), файл за іменем. Однаковий вміст нового коміту не створює. netpulse-gitsync переливає накопичену історію й відтворює втрачений репозиторій із бази — тіла конфігів там і так лежать зашифрованими. У вебі з'явився вибір версії, з якою порівнювати: сервер це вмів (?from=), інтерфейс — ні. Дорогою виправлено чотири тести, які CI запустив уперше з базою. Серед них справжня помилка: машинний токен не міг читати мапи — ACL мап отримував порожній рядок замість uuid і давав 500. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
22 KiB
NetPulse — план робіт
Документ доповнює 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, контракт — 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
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/<device_id> на пристрій, файл <пристрій>/<тип>.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, контракт — 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 окремо.
Порядок і чому саме такий
Етап 5 (користувачі) — без входу продукт не можна віддати нікому.✅Етап 9 (алерти) — без сповіщень це не моніторинг.✅- Етап 6 (шаблони) — знімає потребу дописувати Go під кожен вендор; що раніше, то менше захардкодженого коду доведеться викидати.
Етап 8 (керування зондом) — дешевий і робить онбординг можливим.✅- Етап 7 (NCM) — головний аргумент Enterprise-тарифу.
- Етап 10 (мобільний) — після того, як є що показувати.
Далі за ТЗ лишаються дашборди з NOC TV-режимом, білінг зі Stripe і ліцензійні ключі — усі три мають готову схему й жодного коду.
Дрібний борг
- Повторне малювання зв'язку між тією ж парою вузлів дає загальне «такий запис уже існує» замість зрозумілого пояснення.
- Перетягування вузла через UI не покрите автотестом: синтетичні події не запускають d3-drag у React Flow. Шлях перевірений тестами сервера.
- Підкладки-плани приміщень віддаються API, але полотно їх не малює — потрібен прийом і роздача файлів (S3/MinIO або локальний диск).
- Модулі
http/sslіmodbusоголошені в сіді плагінів, але не написані.