Commit graph

10 commits

Author SHA1 Message Date
ae07bd2a79 Прив'язка Telegram: сторінка була, дороги до неї не було
All checks were successful
CI / hygiene (push) Successful in 8s
CI / web (push) Successful in 1m21s
CI / server (push) Successful in 1m48s
CI / agent (push) Successful in 2m59s
Кнопка під сповіщенням відповідала «ваш Telegram не прив'язано» і не
давала виходу. У базі нуль прив'язок і нуль кодів за весь час.

Сторінка профілю існує, але пункту меню не мала, а єдиний вхід — ім'я
користувача в шапці — малювався за умовою «є ім'я або пошта», тоді як
/me віддавало лише пошту. В облікового запису власника, який заводить
установник і який входить ІМЕНЕМ, вона порожня. Тобто в типовій
інсталяції входу в профіль не було взагалі.

* /me віддає username (тип Me на фронтенді його вже вимагав);
* вхід у профіль малюється завжди для людини;
* пункт меню «Обліковий запис → Мій профіль», perm став необов'язковим;
* текст бота називає те, що видно на екрані;
* сторінка каналів показує стан прив'язки біля telegram-каналу.

Плюс 0073: оренда сходинки ескалації отримала lease_token. Партія
переростає 2-хвилинну оренду, і другий інстанс доставляв ту саму
сходинку паралельно з першим. Тепер запис проходить лише за збігу
токена; при розбіжності не відбувається нічого, сходинка лишається
належною.

65 міграцій, усе зелене проти справжньої бази.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 19:57:09 +03:00
ec4b2cd54b Тиха година й драбина: вада окремо, вибір окремо
All checks were successful
CI / hygiene (push) Successful in 10s
CI / web (push) Successful in 1m18s
CI / server (push) Successful in 1m54s
CI / agent (push) Successful in 1m1s
Асиметрія: аварія о 21:59 ескалювала всю ніч, о 22:01 не ескалювала
ніколи. Дві хвилини різниці — протилежні наслідки, причому гірший
(повна тиша) виглядав як тиша справна.

ВАДА. targets() повертав порожньо в тиху годину, а взведення читало це
як «немає куди слати». Взводять лише новий алерт, тож драбина не
з'являлась уже ніколи: тиха година вимикала механізм саме тоді, коли
перше сповіщення не спрацювало. Тепер targets() розрізняє «каналів
немає» і «канали є, просто зараз ніч».

ВИБІР. 0072 додає respect_quiet_hours на драбину:
  false (типово, як діяло) — драбина пробивається;
  true  — сходинка відкладається до ранку і НЕ витрачається.
Залежить від того, чи є в кабінету нічна зміна — це вирішує кабінет.
disaster пробивається за будь-якого значення, як і в targets().

Відлік драбини — від першого сповіщення, а не від started_at: інакше
для розглушеного алерту вона протухла б ще у вікні.

І сам прогін проти бази брехав: dbtest.sh котив схему готовим образом
(старі міграції), а тести брав із нового дерева. Тепер міграції з того
ж дерева. 64 міграції, усе зелене.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 18:08:27 +03:00
ca143a616b Білінг, SLA, вбудовані правила, пісочниця установника, тести сторінок
Some checks failed
CI / hygiene (push) Successful in 8s
CI / web (push) Successful in 59s
CI / server (push) Failing after 3m27s
CI / agent (push) Successful in 3m3s
П'ять паралельних задач. Найцінніше в них — не можливості, а знайдене.

0069 БІЛІНГ. Аудит 0009 показав, що перевірка ліміту не спрацювала б
жодного разу: isPlanLimit шукала слово «ліміт», а тригер писав
"device limit reached" англійською. Перше ж досягнення стелі дало б
клієнту 500 замість пояснення. Плюс три діри: тригер лише на INSERT
(стеля в 15 обходилась за чотири дії через архів), max_maps/max_agents/
max_users не перевіряло ніщо — тобто рівно те, чим відрізняються плани,
і license_keys була закрита політикою tenant_isolation з 0011, хоча
tenant_id там NULLABLE навмисно: головний сценарій self-hosted був
недосяжний.

Після закінчення ліцензії не вимикається нічого — замерзає лише ріст.
Моніторинг, що перестав моніторити через несплачений рахунок, це
аварія в мережі клієнта, спричинена нами.

0070 SLA. Джерелом обрано ts.icmp_1h, а не device_status_history:
остання не вміє сказати «ми не знали» — перехід пишеться лише при
зміні стану, тож доба мовчання зонда виглядає як доба роботи. Час
розкладено на чотири частини, і «немає даних» не додається ні до чого;
замість вибору між двома брехнями звіт каже, яку частку періоду він
бачив. Закритий період тримає тригер, а не домовленість у Go.

0071 ВІДПОВІДНІСТЬ. 20 правил, кожне прив'язане до родини: об'єднаний
вираз, що покриває Cisco й не покриває MikroTik, дав би «0 порушень» і
сховав сліпу пляму. Вендор не входить у перелік, доки для нього немає
зразка конфігу в тесті. TestBuiltinRulesAreNotAlwaysGreen вимагає, щоб
у кожного правила був конфіг, де воно спрацювало, І де ні.

ПІСОЧНИЦЯ УСТАНОВНИКА — та сама установка в ізольованому проєкті
compose. Знайшла дві справжні вади з трьох спроб:
  * healthcheck бази ходив unix-сокетом, а споживачі по TCP. При
    первинній ініціалізації Postgres слухає лише сокет — compose
    вважав базу здоровою, migrate отримував connection refused. На
    створеній базі цієї фази немає, тож вада чекала на першого клієнта;
  * у білому переліку модулів API не було traps і filecfg — зонд із
    приймачем трапів неможливо було зареєструвати взагалі.

ТЕСТИ СТОРІНОК: 137 → 252. Мережевий шар, права доступу, незворотні
дії, фільтри з адресного рядка. Підмінюється лише fetch і WebSocket —
api/client.ts працює справжній.
2026-08-27 21:17:23 +03:00
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
fe4ed4e7d9 Дедлайн сокета від входу через telnet обривав збір конфігу
Some checks failed
CI / web (push) Has been cancelled
CI / server (push) Has been cancelled
CI / agent (push) Has been cancelled
Знайдено на живому ZTE C320. Вхід чекає «Username:» з дедлайном читання
у дві секунди, а дедлайн сокета липкий: він лишався на всі наступні
читання.

Наслідок подвійний. Довгий конфіг обривався на середині помилкою
«read tcp: i/o timeout» — мережевою там, де мережа ні до чого. А коли
ще й запрошення не збігалось зі зразком, той самий дедлайн спрацьовував
раніше за власний таймер, і замість «не дочекались запрошення» людина
бачила ту саму мережеву помилку.

Щоб це побачити, довелося спершу полагодити діагностику: стенограма
починалась після входу, тобто після того місця, де все зупинялось.
Тепер розмова входу пишеться теж — без пароля, лише те, що надіслав
пристрій.

Плюс вбудований профіль zte-zxan: наявні профілі ZTE чекають «>» або
«]», а OLT показує «ZXAN#». Окремий профіль, бо в Comware трапляються
рядки з самої решітки, і зразок із «#» обірвав би їхній конфіг.

Результат на живому C320: 26 555 рядків.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 19:04:36 +03:00
29f440d436 Автопризначення шаблонів за sysObjectID
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
Пристрій сам каже, що він таке, і шаблон чіпляється без натискань.
Системна група знімається тією ж SNMP-сесією, що й обхід топології:
три зайві PDU дешевші за окремий чек із власним розкладом.

Збіг за префіксом на межі компонента: моделей у виробника тисячі, і
повний збіг означав би рядок на кожну коробку. Довший префікс
перемагає. Дванадцять вбудованих правил на основних виробників.

Шаблони тільки додаються, ніколи не знімаються: автоматика знає модель
пристрою, але не знає, чому цьому хосту дали ще один шаблон руками.

DiscoveredDevice отримав device_id: зіставляти за адресою не можна —
за одним NAT кілька хостів мають ту саму адресу опитування.

Заразом дубль перевірки перестав давати «внутрішню помилку».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 16:29:39 +03:00
4d1fd40e91 Режим NOC TV: дашборд на екрані в диспетчерській без входу
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
Телевізор нікуди не залогиниш — зранку на стіні висітиме форма входу
замість карти мережі. Тому /tv/<токен> розгалужується до перевірки
сесії, а доступ дає токен у посиланні.

Публічний зріз навмисно вузький: розкладка, алерти, хости й метрики
ЛИШЕ тих хостів, які показані на цьому дашборді. Ширший доступ був би
простішим у коді й перетворив би забутий лінк на ключ до кабінету.

Плитки ті самі, що в кабінеті: джерело даних підмінюється контекстом.

Дорогою знайдено дві помилки. Вбудовані icmp-тригери мали умову у
формі, якої движок не розуміє, і мовчали, засипаючи журнал помилками —
виправлено міграцією 0026 і в редакторі. База стенда виявилась у
SQL_ASCII: тепер міграція відмовляється накочуватись на не-UTF8, а
compose задає кодування явно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 15:41:22 +03:00
d1c9d450f2 Інтерфейс: правила у вкладці, кнопки не виділяються, панель згортається
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
«Правила» пішли з бічної панелі у вкладку поруч з «Алертами»: тригери
переїхали в шаблони, а окремий пункт меню й далі пропонував заводити
правила там, звідки їх прибрали.

Кнопки перестали бути виділюваним текстом — у них типово
user-select: auto, і в підпис можна було поставити блимаючий курсор.

Бічна панель згортається у значки, стан у localStorage.

Дорогою закрито дірку: вбудований шаблон нікого не сповіщав. Тригери
стають правилами лише при збереженні шаблону, а вбудований нікому не
належить і не зберігається — хост із icmp-basic збирав пінг і мовчав
про недоступність. Плюс індекс правил з 0024 не враховував тенанта,
через що другий кабінет із тим самим вбудованим шаблоном отримав би
помилку унікальності.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:52:09 +03:00
2fe562936d Тригери описуються в шаблоні; форма шаблону — на вкладках
Some checks are pending
CI / web (push) Waiting to run
CI / server (push) Waiting to run
CI / agent (push) Waiting to run
Правило «процесор вище 85% — це проблема» описує клас пристроїв, а не
окремий хост. Тепер воно живе поруч із перевірками, які дають йому
дані, а не окремою сторінкою, де його доводилось повторювати руками
для кожного комутатора.

Тригер розгортається в ОДНЕ правило alr.rules із селектором за
шаблоном, а не в правило на кожен хост: призначили шаблон новому
пристрою — він одразу під правилом, без перегенерації.

Форма шаблону розкладена на вкладки Загальне/Перевірки/Графіки/Тригери
з лічильниками; смуга вкладок винесена у спільний компонент. Умова
тригера редагується полями, JSON лишився запасним виходом.

Правила з шаблону в списку правил помічені й не редагуються там.
Клон копіює тригери вимкненими, щоб копія не подвоїла сповіщення.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 13:45:49 +03:00
205dd5e079 Пакування: runner міграцій і вшитий у бінарник фронтенд
netpulse-migrate замість PowerShell-скрипта: у контейнері немає ані
psql, ані PowerShell, а тягнути клієнт Postgres в образ заради одного
запуску — це половина дистрибутива на порожньому місці.

Міграції вшиті через embed і переїхали в server/migrations: embed не
бачить нічого за межами кореня свого модуля, а міграції поруч із
бінарником, який їх накочує, не можуть розійтися версіями.

Накочування під advisory-блокуванням: два інстанси при rolling update
інакше застосували б ту саму міграцію двічі. Кожен файл в одній
транзакції разом із записом у schema_migrations; виняток — continuous
aggregates, які TimescaleDB забороняє в транзакції. Змінена вже
застосована міграція зупиняє запуск: у різних інсталяціях інакше
опиниться різна схема під одним номером.

Перевірено на чистій базі: 23 міграції, 101 таблиця, повторний запуск
каже «схема актуальна».

Веб віддає сам API через embed: на self-hosted це прибирає з інструкції
встановлення цілий компонент. Три політики кешування — назавжди для
assets із хешем у імені, ніколи для index.html, коротко для решти.

Знайдено живим прогоном: невідомий шлях під /api/ віддавав 200 з
index.html, і клієнт падав на розборі HTML як JSON замість чесного 404.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 01:01:55 +03:00