Netpulse_SasS/ROADMAP.md
byrsapty ed8fc831bf Дві сесії роботи: 0058–0068, розгортання однією командою, тести
Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (store.go, docker-compose.yml, deploy/README.md), і
розділити їх можна було б лише індексуванням шматків. Коміти, які не
збираються, гірші за один великий — тим паче що це рівно той стан, який
перевірявся разом.

ЩО ПРАЦЮЄ НА СТЕНДІ Й ПЕРЕВІРЕНО ТАМ

  0058  подієві алерти: syslog, ncm, compliance спрацьовують у мить
        події; правило з нереалізованим джерелом більше не зберігається
        мовчки
  0059  snmp.walk і прототипи шаблонів — таблиці з динамічним індексом
        описуються шаблоном, а не Go
  0060  відкат конфігу: план як різниця, маскування паролів із підписом
        плану, обов'язковий контрольний збір, verifying при обриві
  0061  кнопки Telegram: довге опитування, авторизація не з callback_data
  0062  аудит і архів хостів; тест на AST, що падає на ключі без назви
  0063  RLS: три ролі, окремий пул для фонових тактів
  0064  строки зберігання даних і сторінка сховища
  0065  приймач SNMP-трапів; перевірено справжніми пакетами по дроту,
        переклад v1→v2 за RFC 3584 дає правильний OID
  0066  ескалації сповіщень
  0067  алерт про вичерпання диска
  0068  поля заливки конфігу переїхали в каталог профілів

Плюс: 137 тестів вебу з нуля (їх не було взагалі), одинадцять справжніх
вад, знайдених ними й виправлених, і виправлення двох інтеграційних
тестів grpcapi, які мовчки пропускались півтора року.

ЩО ЩЕ НЕ ЗАПУСКАЛОСЬ

  netpulse            установник: одна команда замість 18 змінних і
                      593 рядків інструкції
  RLS з першого запуску  нова інсталяція під політиками одразу;
                      RLS-EXISTING-INSTALL.md лишається тільки для
                      старих інсталяцій
  .forgejo + CI       раннер не зареєстрований

Ці три перевірені компіляцією й міркуванням, але не виконанням.

ГОЛОВНИЙ ВИСНОВОК ДВОХ СЕСІЙ

Зелена перевірка доводить рівно те, що вона перевіряє. Тест ізоляції RLS
був правильний і зелений — і пропустив зламаний вхід, бо перевіряв «чи
не видно чужого», коли зламалось «чи видно своє». Інтеграційні тести
grpcapi були зелені, бо не виконувались. Схема, довідник і протокол
описували те, чого в коді не існувало, і виглядало це як готове.

Тому в кожному завданні цих сесій стояла вимога назвати НЕПОКРИТЕ, а
чотири задачі закінчились не можливістю, а відмовою: правило з
нереалізованим джерелом не зберігається, профіль без команд заливки
каже про це замість мовчазної кнопки, міграція RLS валить сама себе на
таблиці без політики, тест словника аудиту падає на ключі без назви.

Подробиці — HISTORY.md, розділи за 26 і 27 серпня.
2026-08-27 17:32:49 +03:00

36 KiB
Raw Blame History

NetPulse — план робіт

Документ доповнює 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, контракт — 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, гірша за її відсутність).

Обсяг: ~23 дні. Ризиків мало: схема готова, візерунок автентифікації в проєкті вже відпрацьований на агентських токенах.


Етап 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

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 — окрема задача, і базу для них варто починати саме з імпортера, а не з ручного наповнення.

Обсяг: ~57 днів на схему + реконсиляцію + імпортер + 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 із двоетапним погодженням уже є).

Обсяг: ~45 днів.


Етап 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 уже наповнюється), повзунки лімітів, перемикачі модулів, кнопка «запустити виявлення», інструкція встановлення з готовою командою й токеном.

Обсяг: ~23 дні.


Етап 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 у схемі готова.

Обсяг: ~45 днів.


Етап 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усе з ТЗ.

Обсяг: ~34 дні на адаптив + PWA, Mini App окремо.


Етап 11. Сім задач одним заходом — 2026-08-27

Міграції 00580064, зроблено паралельно. Розбір спільного знаменника — в 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, розділ «Перехід на роль без 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

Міграції 00650068 плюс роботи без міграцій. Розбір спільного — у 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-трапи не перевіряються — розбираються й зберігаються, підпис і шифрування не звіряються.

Порядок і чому саме такий

  1. Етап 5 (користувачі) — без входу продукт не можна віддати нікому.
  2. Етап 9 (алерти) — без сповіщень це не моніторинг.
  3. Етап 6 (шаблони) — знімає потребу дописувати Go під кожен вендор; що раніше, то менше захардкодженого коду доведеться викидати.
  4. Етап 8 (керування зондом) — дешевий і робить онбординг можливим.
  5. Етап 7 (NCM) — головний аргумент Enterprise-тарифу.
  6. Етап 10 (мобільний) — після того, як є що показувати.

Дашборди з NOC TV-режимом зроблено 2026-08-25. Що лишається — нижче, окремим переліком із причинами.

Чого немає: повний перелік

Стан на 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% зусиль.