Старий розділ лишено як опис причин, але половина його вже неправда: CI-раннер стоїть, build.py --check у конвеєрі, тести вебу виросли до 340, приймач кнопок Telegram працює. Новий розділ — те, що відкрите сьогодні, у порядку ціни помилки. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
638 lines
44 KiB
Markdown
638 lines
44 KiB
Markdown
# NetPulse — план робіт
|
||
|
||
Документ доповнює [HISTORY.md](HISTORY.md): той описує зроблене, цей — що лишилось
|
||
і чому саме в такому порядку.
|
||
|
||
## Де ми зараз
|
||
|
||
Працює наскрізний ланцюг **кабель → браузер**: зонд знаходить сусідів по
|
||
LLDP/CDP/ARP, сервер зводить їх у `topo.links`, сам заводить чеки на трафік,
|
||
API віддає готове полотно з живими статусами, редактор пише назад із контролем
|
||
конфліктів і відкатом.
|
||
|
||
Перевірено на живому стенді проти справжнього `snmpd`. Деталі — в README кожного
|
||
компонента.
|
||
|
||
## Чого немає: чесний зріз
|
||
|
||
Схема БД з Етапу 1 покриває майже все з технічного завдання, але **схема ≠
|
||
реалізація**. Нижче — розрив між ними великими мазками; поіменний перелік
|
||
із причинами — у розділі «Чого немає: повний перелік» наприкінці.
|
||
|
||
| Підсистема | Схема | Реалізація |
|
||
|------------|-------|------------|
|
||
| Мапа, топологія, телеметрія | ✅ | ✅ |
|
||
| Автовиявлення LLDP/CDP/ARP/FDB | ✅ | ✅ |
|
||
| **Користувачі, ролі, вхід** | ✅ | ✅ |
|
||
| **Шаблони опитування** | ✅ | ✅ з тригерами, автопризначенням, `snmp.walk` і прототипами (0059) |
|
||
| **NCM (збір конфігів)** | ✅ | ✅ збір, розклад, Git, syslog, compliance і відкат (0060) |
|
||
| **Керування зондом із 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:** графіки описуються в шаблоні (шість видів,
|
||
> жорсткі межі осей), картка хоста на вкладках без інтервалів.
|
||
>
|
||
> **Додано 2026-08-25:** тригери описуються в шаблоні й розгортаються в
|
||
> правила сповіщень; автопризначення за `sysObjectID` — пристрій сам
|
||
> каже, що він таке, і шаблон чіпляється без жодного натискання.
|
||
> За тим самим `sysObjectID` тепер підбирається й профіль збору конфігів
|
||
> (`ncm.profile_auto_assign`, уточнення за `sysDescr` для випадків, коли
|
||
> один OID покриває різні типи заліза). Щоб було з чого підбирати, хост
|
||
> зі SNMP-доступом сам отримує чек розпізнавання: полегшений
|
||
> `topology.discover` — три OID, без сусідів і без обходу `ifTable`.
|
||
>
|
||
> **Лишилось:** прототипи шаблонів і `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`.
|
||
> Перевірено наскрізно.
|
||
>
|
||
> **Додано 2026-08-25:** приймач syslog на зонді (RFC3164/5424, черга,
|
||
> ліміт частоти на джерело), транспорт `StreamLogs`, зіставлення хоста
|
||
> за адресою джерела й тригер позачергового бекапу за зразком у
|
||
> `ncm.device_policies.syslog_match`.
|
||
|
||
## Етап 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](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 окремо.
|
||
|
||
---
|
||
|
||
## Етап 11. Сім задач одним заходом — 2026-08-27
|
||
|
||
> Міграції 0058–0064, зроблено паралельно. Розбір спільного знаменника —
|
||
> в [HISTORY.md](HISTORY.md), розділ «Сім задач одним заходом».
|
||
>
|
||
> - **0058 подієві алерти** — `syslog`, `ncm`, `compliance` спрацьовують
|
||
> у мить надходження події; правило з нереалізованим джерелом більше
|
||
> не зберігається мовчки.
|
||
> - **0059 `snmp.walk` і прототипи** — таблиці з динамічним індексом
|
||
> описуються шаблоном, а не Go.
|
||
> - **0060 відкат конфігу** — план як різниця, маскування паролів із
|
||
> підписом плану, обов'язковий контрольний збір, `verifying` при
|
||
> обриві. MikroTik і Juniper відмовлені з поясненням.
|
||
> - **0061 кнопки Telegram** — довге опитування (домену немає й не
|
||
> передбачається), авторизація не з `callback_data`, прив'язка
|
||
> акаунта одноразовим кодом.
|
||
> - **0062 аудит і архів хостів** — вісім відсутніх назв дій; тест на
|
||
> AST, що падає на ключі без назви; «відновити» повертає хост робочим,
|
||
> а не мовчазним.
|
||
> - **0063 RLS** — три ролі, окремий пул для фонових тактів. Інертна до
|
||
> перемикання DSN.
|
||
> - **0064 строки зберігання** — три гіпертаблиці й дві звичайні
|
||
> таблиці, що росли назавжди; сторінка сховища з прогнозом.
|
||
|
||
### Лишилось із цього етапу
|
||
|
||
- ~~**Тест ізоляції RLS не прогнано.**~~ ✅ 2026-08-27: прогнано на
|
||
бойовій базі, перемикання зроблено. Ізоляція діє, вхідні шляхи
|
||
переведено на воркерний пул. Подробиці — [HISTORY.md](HISTORY.md),
|
||
розділ «Перехід на роль без BYPASSRLS». **Лишилось:** телеметрія,
|
||
аудит та історія алертів під RLS не підпадають і не підпадуть —
|
||
TimescaleDB не поєднує стиснення з row level security. Їхню ізоляцію
|
||
далі тримає предикат у запиті.
|
||
- **0064 не прогнано на живій БД.** Перевірити першими:
|
||
`chunks_detailed_size` над матеріалізованою гіпертаблицею,
|
||
`hypertable_compression_stats` на нестиснутій, `add_retention_policy`
|
||
всередині транзакції під `SECURITY DEFINER`.
|
||
- **Алерт про вичерпання диска** — найдешевший шлях без правок движка:
|
||
писати `db.size.bytes` і `db.days_left` звичайними метриками на хості
|
||
машини зонда, тоді наявне метричне правило працює як є.
|
||
- **`apply_*` у генераторі профілів.** Поля заливки задані міграцією
|
||
через `UPDATE`; `db/profiles/catalog.json` про них не знає.
|
||
- **`plural()` повертає рядок разом із числом**, а частина місць виклику
|
||
додає число ще раз — на екрані «5 5 хостів». Стара вада, не з цього
|
||
етапу.
|
||
|
||
## Етап 12. Друга сімка — 2026-08-27
|
||
|
||
> Міграції 0065–0068 плюс роботи без міграцій. Розбір спільного — у
|
||
> [HISTORY.md](HISTORY.md), розділ «Друга сімка».
|
||
>
|
||
> - **0065 трапи** — приймач 162/udp на зонді, словник із шести
|
||
> протокольних трапів плюс словник кабінету, джерело алертів `trap`.
|
||
> - **0066 ескалації** — драбина сходинок, стан у базі, зупинка при
|
||
> підтвердженні, заглушення відкладає сходинку, а не витрачає.
|
||
> - **0067 алерт про диск** — пороги за часом (21 доба / 4 доби), а не
|
||
> за відсотками; вільне місце міряється `statfs` по `Bavail`.
|
||
> - **0068 каталог профілів** — поля заливки переїхали з разової
|
||
> міграції в `catalog.json`.
|
||
> - **`plural`** — 69 місць виклику, 13 друкували число двічі; підпис
|
||
> змінено так, щоб помилка стала неможливою.
|
||
> - **Тести вебу** — з нуля до 137; знайшли 11 справжніх вад,
|
||
> усі виправлені.
|
||
|
||
### Увімкнути трапи — рішення власника
|
||
|
||
Код розгорнуто, модуль **не увімкнено**. Щоб запрацював, потрібні три
|
||
речі, і третя виходить за межі технічної:
|
||
|
||
1. `traps` у `-modules` зонда;
|
||
2. `NET_BIND_SERVICE` — процес не root, а 162 привілейований;
|
||
3. **публікація 162/udp на хост** — порт без автентифікації приймає
|
||
будь-кого, хто знає адресу.
|
||
|
||
Обмеження в модулі є (20 трапів/с з адреси, стеля черги, окремий облік
|
||
невідомих джерел), але вони зменшують шкоду, а не прибирають рішення.
|
||
|
||
### Лишилось із цього етапу
|
||
|
||
- **`db/profiles/build.py --check` не в CI.** Один рядок у кроці «Схема»
|
||
ловив би розходження каталогу зі згенерованим — саме те, що цього разу
|
||
знайшлось випадково.
|
||
- **CI без раннера.** `scripts/check.sh` робить те саме однією командою
|
||
вже сьогодні; сам workflow чекає на раннера.
|
||
- **Перетягування вузлів на мапі не покрите й не буде** — d3-drag не
|
||
запускається синтетичними подіями. Наслідок: вузол візуально стає на
|
||
місце, запит не йде, розкладка «сама відкочується» після
|
||
перезавантаження, і всі тести при цьому зелені.
|
||
- **Тригери шаблонів не можуть отримати драбину ескалації** — поля в
|
||
тригері шаблону немає, а драбина ще й тенант-специфічна.
|
||
- **SNMPv3-трапи не перевіряються** — розбираються й зберігаються, підпис
|
||
і шифрування не звіряються.
|
||
|
||
## Етап 13. Випуск: реєстр образів, релізи, пакети — НЕ ЗРОБЛЕНО
|
||
|
||
Поставлена задача, дослівно: «ми ж зможемо його встановлювати з пакетів?
|
||
А то зараз не дуже».
|
||
|
||
Зауваження справедливе. Сьогоднішній шлях клієнта — клонувати
|
||
репозиторій, зібрати образи в себе (кілька хвилин і гігабайти) і
|
||
запустити установник. Це шлях розробника: клієнт бачить вихідний код,
|
||
якого не мав би бачити, і платить за збірку часом свого сервера.
|
||
|
||
### Чому «пакет» неможливий просто зараз
|
||
|
||
Не тому, що ліньки написати `.deb`. **Немає що в нього класти.** Образи
|
||
збираються на машині клієнта з джерел, тобто продукт як артефакт не
|
||
існує — існує лише рецепт. Пакет у такому стані ставив би те саме
|
||
«зберіть самі», лише з гарнішою обгорткою.
|
||
|
||
Тому порядок жорсткий: спершу реєстр, потім релізи, і лише потім пакети.
|
||
|
||
### 13.1 Реєстр образів
|
||
|
||
**Інфраструктура вже є:** Forgejo має вбудований реєстр контейнерів, а
|
||
CI-раннер ми запустили 2026-08-27. Бракує лише того, щоб почати ним
|
||
користуватись.
|
||
|
||
Образи `netpulse/server` і `netpulse/agent` мають публікуватися туди з
|
||
тегом версії. Після цього `docker compose` на машині клієнта тягне
|
||
готове, а не збирає.
|
||
|
||
### 13.2 Версіонування, якого немає
|
||
|
||
`NETPULSE_VERSION=v0.1.0` — зараз просто рядок у `.env`. Тега в git
|
||
немає, образу з таким тегом ніде, крім бойового стенду, немає теж.
|
||
Зв'язку «версія → коміт → образ» не існує, тобто на питання «що саме у
|
||
вас працює» відповіді немає.
|
||
|
||
Потрібен тег у git як єдина точка істини, і збірка, що бере версію
|
||
звідти, а не з рядка в конфігурації.
|
||
|
||
### 13.3 Випускальний конвеєр
|
||
|
||
git tag v0.2.0 → CI збирає → штовхає образи в реєстр
|
||
→ публікує netpulse-v0.2.0.tar.gz
|
||
|
||
В архіві — `netpulse`, compose-файли, `netpulse.conf.example`. **Без
|
||
джерел.** Плюс `install.sh`, який цей архів завантажує:
|
||
|
||
curl -fsSL https://git.zotac.keenetic.link/.../install.sh | sh
|
||
|
||
І `netpulse upgrade` починає ходити в реєстр замість перезбірки.
|
||
|
||
### 13.4 Пакети .deb / .rpm — кроком пізніше
|
||
|
||
`netpulse` у `/usr/bin`, compose у `/opt/netpulse`, `netpulse.conf` у
|
||
`/etc/netpulse`, systemd-юніт. Тоді працює те, чого й очікують:
|
||
|
||
apt install netpulse && netpulse install
|
||
|
||
Робити це до 13.1 безглуздо з причини, названої вище.
|
||
|
||
### Чого треба досягти ДО першого тегу
|
||
|
||
**Оновлення з версії на версію ніхто не перевіряв.** Пісочниця
|
||
установника (Етап 12) перевіряє установку з нуля — і саме вона знайшла
|
||
дві вади, які чекали на першого клієнта. Але шляху «стояла 0.1, стала
|
||
0.2» не перевіряє ніщо, а ламається найчастіше саме він: міграції
|
||
поверх наявних даних, зміна конфігурації, несумісність зонда з новим
|
||
колектором.
|
||
|
||
Потрібен другий режим пісочниці: підняти попередню версію, налити в неї
|
||
даних, оновити до нової, і прогнати ту саму самоперевірку. Без цього
|
||
перший же реліз перевіряє себе на клієнтові.
|
||
|
||
**Сумісність зонда й сервера.** Зонди стоять у мережах клієнтів і
|
||
оновлюються не одночасно з сервером. Правило «який зонд працює з яким
|
||
сервером» ніде не записане й не перевіряється.
|
||
|
||
## Порядок і чому саме такий
|
||
|
||
1. ~~**Етап 5 (користувачі)** — без входу продукт не можна віддати нікому.~~ ✅
|
||
2. ~~**Етап 9 (алерти)** — без сповіщень це не моніторинг.~~ ✅
|
||
3. **Етап 6 (шаблони)** — знімає потребу дописувати Go під кожен вендор; що
|
||
раніше, то менше захардкодженого коду доведеться викидати.
|
||
4. ~~**Етап 8 (керування зондом)** — дешевий і робить онбординг можливим.~~ ✅
|
||
5. **Етап 7 (NCM)** — головний аргумент Enterprise-тарифу.
|
||
6. **Етап 10 (мобільний)** — після того, як є що показувати.
|
||
|
||
Дашборди з NOC TV-режимом зроблено 2026-08-25. Що лишається — нижче,
|
||
окремим переліком із причинами.
|
||
|
||
## Що лишається — стан на 2026-08-28
|
||
|
||
Перелік нижче (розділ «Чого немає») складено 2026-08-25 і частина його
|
||
вже неправдива. Тут — те, що справді відкрите СЬОГОДНІ, у порядку ціни
|
||
помилки. Нижчий розділ лишено як довший опис причин.
|
||
|
||
### Перше: до першого тегу (Етап 13)
|
||
|
||
Це єдина річ, яка блокує продаж, і вона не про код.
|
||
|
||
1. **Оновлення з версії на версію не перевіряв ніхто.** Пісочниця
|
||
перевіряє установку з нуля. Шлях «стояла 0.1 — стала 0.2» ламається
|
||
частіше, а перевіряє його зараз перший клієнт.
|
||
2. **Немає версіонування.** Образ завжди `v0.1.0`, реєстру немає, тега
|
||
немає. Поки цього немає, «оновитись» означає «залити дерево й
|
||
зібрати», тобто те, що роблю я вручну.
|
||
3. **Сумісність зонда й сервера ніде не записана.** Зонди стоять у
|
||
чужих мережах і оновлюються не разом із сервером.
|
||
|
||
### Друге: перевірки, які є, але не бігають самі
|
||
|
||
4. **`scripts/dbtest.sh` не в CI.** Раннер уже стоїть (контейнер
|
||
`netpulse-ci-runner`), `check.sh` у конвеєрі є, `build.py --check`
|
||
теж. Бракує саме тестів проти бази — тобто тих, що 28 серпня знайшли
|
||
чотири справжні вади за один прогін. Зараз їх запускаю руками я.
|
||
5. **Токен у `origin` замість ключа розгортання.**
|
||
|
||
### Третє: діри, названі рецензіями й не закриті
|
||
|
||
6. **`LoadChannels` падає цілком через один нерозшифровний секрет** —
|
||
кабінет лишається без ескалацій до стелі життя драбини.
|
||
7. **Перетягування вузла на мапі не покрите тестом і не буде** (d3-drag
|
||
не запускається синтетичними подіями). Наслідок відомий: вузол стає
|
||
на місце, запит не йде, тести зелені.
|
||
8. **SNMPv3-трапи не перевіряються** — розбираються й зберігаються,
|
||
підпис і шифрування не звіряються.
|
||
9. **Тригери шаблонів не можуть отримати драбину ескалації.**
|
||
10. **`plural()` повертає число разом із рядком**, а частина викликів
|
||
додає його ще раз: «5 5 хостів».
|
||
|
||
### Четверте: ніколи не працювало на живому залізі
|
||
|
||
11. **Відкат конфігурації** — код є, на справжньому обладнанні не
|
||
виконувався жодного разу.
|
||
12. **Джерело тригера `trap`** — свідомо не зроблено без словника MIB.
|
||
13. **Прототипи шаблонів** (0059) увімкнені, але `snmp.if` через них не
|
||
виражається.
|
||
|
||
### П'яте: велике й відоме
|
||
|
||
14. **Білінг** — схема з 0009 і 0069 є, продавати без нього не можна.
|
||
15. **Web Push**, **збережені подання**, **прокрутка дашбордів на TV**,
|
||
**підкладки-плани приміщень**, **мобільний режим мапи**.
|
||
16. **Справжній TLS** — потрібен домен; на IP Let's Encrypt не видає.
|
||
|
||
---
|
||
|
||
## Чого немає: повний перелік
|
||
|
||
Стан на 2026-08-25, після розгортання на бойовому сервері. Перелік
|
||
складений перевіркою коду, а не з пам'яті: для кожного пункту звірено,
|
||
чи є під нього щось у `server/internal`, `agent/internal` і `web/src`.
|
||
|
||
Порядок — за тим, наскільки відсутність помітна тому, хто користується
|
||
системою.
|
||
|
||
### Схема є, коду немає зовсім
|
||
|
||
Ці таблиці створені міграціями й порожні. Небезпека тут не в самій
|
||
відсутності, а в тому, що схема виглядає як обіцянка.
|
||
|
||
| Що | Де схема | Чого бракує |
|
||
|---|---|---|
|
||
| **Білінг і ліцензії** | `0009_billing_licensing.sql` | усього: тарифи, ліміти, Stripe, ключі. Для Micro-SaaS це те, через що продають |
|
||
| **Web Push** | `alr.push_subscriptions` | підписки й доставка. Потрібне для PWA |
|
||
| **Звіти SLA** | `core.sla_targets`, `core.sla_periods` | розрахунок доступності за період і вивантаження |
|
||
| **Збережені подання** | `core.saved_views` | фільтри інвентарю, які можна назвати й повернутись |
|
||
| **Прокрутка дашбордів на TV** | `dashboards.tv_options.rotate_sec` | сам режим NOC TV працює, але сторінки не гортаються по колу |
|
||
|
||
### Оголошено в переліку можливостей, не написано
|
||
|
||
| Що | Стан | Чому не зроблено |
|
||
|---|---|---|
|
||
| **Modbus-TCP** | плагін у сіді, `is_core = false` | немає жодного інвертора чи UPS під рукою. Неперевірений промисловий протокол у мережі з живим обладнанням — гірше, ніж його відсутність |
|
||
| **NetFlow / sFlow** | плагін у сіді, `is_core = false` | окремий приймач потоків, за обсягом — власний етап |
|
||
|
||
Обидва плагіни позначені `is_core = false`, тож у переліку перевірок
|
||
система показує їх недоступними — обіцянки користувачу немає.
|
||
|
||
### Зроблено наполовину
|
||
|
||
**Сповіщення за подіями — лишився `trap`.** `syslog`, `ncm` і
|
||
`compliance` зроблено подієво (0058): правило перевіряється в мить
|
||
надходження події. `trap` свідомо не реалізовано — без словника MIB
|
||
умова звелась би до порівняння сирих OID, тобто до другої мовчазної
|
||
обіцянки замість першої. Джерела `link` і `agent` не подієві за
|
||
природою. Правило з нереалізованим джерелом тепер не зберігається, а не
|
||
мовчить.
|
||
|
||
**Підкладки-плани приміщень.** `topo.map_backgrounds` віддається в
|
||
`GET /maps/{id}`, полотно їх не малює. Потрібен прийом і роздача файлів
|
||
— іконки мап уже зберігаються в базі, тож можна тим самим шляхом, без
|
||
S3. Приблизно пів дня.
|
||
|
||
**Мобільний режим мапи.** Адаптив сторінок є, але на полотні
|
||
перетягування вузла пальцем не розведене з панорамуванням: потрібен
|
||
явний режим редагування, інакше кожна спроба посунути карту рухатиме
|
||
вузол.
|
||
|
||
**Інтерфейси досі захардкоджені.** Прототипи шаблонів зроблено (0059),
|
||
але `snmp.if` через них не виражається: зонд тримає попередній замір,
|
||
рахує швидкість за фактичним інтервалом і ловить перевертання
|
||
лічильника, а лічильники лягають у `ts.if_counters` за `interface_id`, а
|
||
не в `ts.samples` за міткою. На цьому `interface_id` тримаються анімація
|
||
трафіку на мапі, інспектор лінка й тригери з джерелом `interface`.
|
||
Виграш — мінус ~200 рядків Go; ризик — обірвані графіки на живих хостах.
|
||
Свідомо відкладено.
|
||
|
||
### Перевірки, яких немає
|
||
|
||
**Тести вебу з'явились 2026-08-27** — vitest, 137 перевірок, і
|
||
`scripts/check.sh` проганяє обидва світи однією командою. Що покрито і,
|
||
головне, що НІ — у `web/TESTING.md`. Найбільша діра лишається та сама:
|
||
перетягування вузла на мапі не покрите, бо d3-drag не запускається
|
||
синтетичними подіями; серверний бік цієї дії тестами покритий.
|
||
|
||
**CI жодного разу не запускався.** `.forgejo/workflows/ci.yml` написано,
|
||
але раннера немає. Прогони робились руками; те, що CI мав би ловити
|
||
(`gofmt`, `go vet`, тести з базою, крос-збірка зонда), проганялось
|
||
окремо перед кожним комітом.
|
||
|
||
### Чого не буде без зовнішніх умов
|
||
|
||
- **Справжній TLS** на бойовому сервері — потрібен домен. Зараз
|
||
самопідписаний: Let's Encrypt не видає сертифікатів на IP-адреси.
|
||
- **Telegram Mini App** — окрема робота, і почати варто з приймача
|
||
кнопок бота: він дає 90% користі за 10% зусиль.
|