Профілі були даними без виконавця. Тепер ланцюг замкнено: черга в БД → диспетчер у AgentService → зонд по SSH/Telnet → вивантаження стрімом → звірка sha256 і дедуплікація. Агент (internal/ncmx): - усе побудовано навколо пошуку промпту: у консолі немає ані коду завершення, ані довжини відповіді - промпт шукається лише в хвості 512 байтів, інакше "banner motd #" обривав би збір на середині конфігу - дедлайн на паузу між байтами, а не на всю операцію: збір із шасі триває хвилини, а тиша означає завислий пристрій або хибний regex - ключі SSH обладнання не звіряються свідомо: залізо міняє їх з кожною прошивкою, і known_hosts на сотні пристроїв означав би не збирати конфіги зовсім Сервер: черга ncm.jobs із FOR UPDATE SKIP LOCKED (два екземпляри не надішлють одне завдання двічі), диспетчер, прибирання завислих. Знайдено живим прогоном: - сервер ігнорував поле encoding: агент стискав gzip і рахував sha256 від оригіналу, сервер рахував від стиснених байтів і відхиляв кожен бекап. Поле в контракті з Етапу 2, реалізації не було — інтеграційний тест користувався "none" і повз дірку проходив - промпт обрізався не по рядку: шаблон [>#]\s*$ ловить лише символ, і в конфізі лишалось ім'я пристрою окремим рядком - у конфіг потрапляли escape-послідовності bash і подвоєні \r\r\n від PTY: 295 байтів / 12 рядків замість 286 / 10. Після виправлення — 285 / 10, різниця лише у фінальному переводі рядка Живий прогін проти стенду: success, конфіг 285 байтів, повторний збір — unchanged без другої версії. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
20 KiB
NetPulse — план робіт
Документ доповнює HISTORY.md: той описує зроблене, цей — що лишилось і чому саме в такому порядку.
Де ми зараз
Працює наскрізний ланцюг кабель → браузер: зонд знаходить сусідів по
LLDP/CDP/ARP, сервер зводить їх у topo.links, сам заводить чеки на трафік,
API віддає готове полотно з живими статусами, редактор пише назад із контролем
конфліктів і відкатом.
Перевірено на живому стенді проти справжнього snmpd. Деталі — в README кожного
компонента.
Чого немає: чесний зріз
Схема БД з Етапу 1 покриває майже все з технічного завдання, але схема ≠ реалізація. Нижче — розрив між ними.
| Підсистема | Схема | Реалізація |
|---|---|---|
| Мапа, топологія, телеметрія | ✅ | ✅ |
| Автовиявлення LLDP/CDP/ARP/FDB | ✅ | ✅ |
| Користувачі, ролі, вхід | ✅ | ✅ |
| Шаблони опитування | ❌ | ❌ |
| NCM (збір конфігів) | ✅ | ⚠️ збір працює, Git і розклад — ні |
| Керування зондом із 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-подібні)
Найбільша нова підсистема. Зараз 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-16
Зроблено: агентський модуль SSH/Telnet, черга завдань, диспетчер, вивантаження зі стисненням і дедуплікацією. Перевірено наскрізно.
Лишилось: планувальник за cron, тригер за Syslog, Git-двигун, візуальний diff у вебі.
Етап 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 плюс тригер за
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 із двоетапним погодженням уже є).
Обсяг: ~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. Алерти й сповіщення — ✅ зроблено 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оголошені в сіді плагінів, але не написані.