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>
205 lines
14 KiB
SQL
205 lines
14 KiB
SQL
-- =====================================================================
|
||
-- NetPulse :: 0010_seed.sql
|
||
-- Довідники: права, системні ролі, core-плагіни, типи чеків, тарифи, фічі.
|
||
-- Ідемпотентно (ON CONFLICT DO NOTHING/UPDATE).
|
||
-- ВАЖЛИВО: виконується ДО 0011_rls.sql — після вмикання FORCE RLS
|
||
-- навіть власник схеми не зможе вставити рядки з tenant_id IS NULL.
|
||
-- =====================================================================
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Права доступу
|
||
-- ---------------------------------------------------------------------
|
||
INSERT INTO core.permissions (key, description) VALUES
|
||
('devices:read', 'Перегляд пристроїв та метрик'),
|
||
('devices:write', 'Створення/редагування/видалення пристроїв'),
|
||
('devices:control', 'Керування опитуванням, ручний запуск чеків'),
|
||
('maps:read', 'Перегляд мап топології'),
|
||
('maps:write', 'Редагування мап, вузлів та зв''язків'),
|
||
('maps:publish', 'Публічні посилання на мапи'),
|
||
('alerts:read', 'Перегляд алертів'),
|
||
('alerts:ack', 'Підтвердження та mute алертів'),
|
||
('alerts:write', 'Редагування правил, каналів, ескалацій'),
|
||
('ncm:read', 'Перегляд конфігів та diff'),
|
||
('ncm:write', 'Редагування політик бекапу'),
|
||
('ncm:rollback', 'Відкат конфігурації на пристрій'),
|
||
('agents:read', 'Перегляд зондів'),
|
||
('agents:write', 'Реєстрація та налаштування зондів'),
|
||
('users:read', 'Перегляд користувачів'),
|
||
('users:write', 'Запрошення та керування ролями'),
|
||
('billing:read', 'Перегляд тарифу та інвойсів'),
|
||
('billing:manage', 'Зміна тарифу, платіжні дані'),
|
||
('audit:read', 'Перегляд журналу аудиту'),
|
||
('settings:write', 'Налаштування тенанта, білий лейбл')
|
||
ON CONFLICT (key) DO NOTHING;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Системні ролі (tenant_id IS NULL)
|
||
-- ---------------------------------------------------------------------
|
||
INSERT INTO core.roles (id, tenant_id, key, name, description, is_system) VALUES
|
||
('00000000-0000-0000-0000-0000000000a1', NULL, 'owner', 'Власник', 'Повний доступ, включно з білінгом', true),
|
||
('00000000-0000-0000-0000-0000000000a2', NULL, 'admin', 'Адмін', 'Усе, крім білінгу', true),
|
||
('00000000-0000-0000-0000-0000000000a3', NULL, 'engineer', 'Інженер', 'Пристрої, мапи, алерти, NCM без rollback', true),
|
||
('00000000-0000-0000-0000-0000000000a4', NULL, 'operator', 'Оператор', 'Перегляд + ack алертів', true),
|
||
('00000000-0000-0000-0000-0000000000a5', NULL, 'viewer', 'Глядач', 'Тільки перегляд', true)
|
||
ON CONFLICT (id) DO NOTHING;
|
||
|
||
-- owner: усі права
|
||
INSERT INTO core.role_permissions (role_id, permission_key)
|
||
SELECT '00000000-0000-0000-0000-0000000000a1', key FROM core.permissions
|
||
ON CONFLICT DO NOTHING;
|
||
|
||
-- admin: усе, крім billing:manage
|
||
INSERT INTO core.role_permissions (role_id, permission_key)
|
||
SELECT '00000000-0000-0000-0000-0000000000a2', key FROM core.permissions
|
||
WHERE key <> 'billing:manage'
|
||
ON CONFLICT DO NOTHING;
|
||
|
||
INSERT INTO core.role_permissions (role_id, permission_key) VALUES
|
||
('00000000-0000-0000-0000-0000000000a3','devices:read'),
|
||
('00000000-0000-0000-0000-0000000000a3','devices:write'),
|
||
('00000000-0000-0000-0000-0000000000a3','devices:control'),
|
||
('00000000-0000-0000-0000-0000000000a3','maps:read'),
|
||
('00000000-0000-0000-0000-0000000000a3','maps:write'),
|
||
('00000000-0000-0000-0000-0000000000a3','alerts:read'),
|
||
('00000000-0000-0000-0000-0000000000a3','alerts:ack'),
|
||
('00000000-0000-0000-0000-0000000000a3','alerts:write'),
|
||
('00000000-0000-0000-0000-0000000000a3','ncm:read'),
|
||
('00000000-0000-0000-0000-0000000000a3','ncm:write'),
|
||
('00000000-0000-0000-0000-0000000000a3','agents:read'),
|
||
('00000000-0000-0000-0000-0000000000a4','devices:read'),
|
||
('00000000-0000-0000-0000-0000000000a4','maps:read'),
|
||
('00000000-0000-0000-0000-0000000000a4','alerts:read'),
|
||
('00000000-0000-0000-0000-0000000000a4','alerts:ack'),
|
||
('00000000-0000-0000-0000-0000000000a4','ncm:read'),
|
||
('00000000-0000-0000-0000-0000000000a5','devices:read'),
|
||
('00000000-0000-0000-0000-0000000000a5','maps:read'),
|
||
('00000000-0000-0000-0000-0000000000a5','alerts:read')
|
||
ON CONFLICT DO NOTHING;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Плагіни ядра
|
||
-- ---------------------------------------------------------------------
|
||
INSERT INTO core.plugins (key, name, version, scope, description, manifest, min_plan_key, is_core) VALUES
|
||
('icmp', 'ICMP / Ping', '1.0.0', 'agent', 'Доступність, RTT, jitter, втрати',
|
||
'{"metrics":["icmp.rtt","icmp.loss","icmp.jitter"],"checks":["icmp.ping"]}', 'free', true),
|
||
('snmp', 'SNMP v2c/v3', '1.0.0', 'agent', 'Опитування OID, інтерфейси, CPU/RAM/сенсори',
|
||
'{"metrics":["if.*","cpu.util","mem.used","sensor.*"],"checks":["snmp.get","snmp.walk","snmp.if"]}', 'pro', true),
|
||
('http', 'HTTP/HTTPS/SSL', '1.0.0', 'agent', 'Код відповіді, час, строк дії сертифіката',
|
||
'{"metrics":["http.status","http.latency","ssl.days_left"],"checks":["http.status","http.ssl_expiry"]}', 'free', true),
|
||
('topology', 'Topology Discovery', '1.0.0', 'both', 'LLDP/CDP/ARP/FDB автовиявлення зв''язків',
|
||
'{"protocols":["lldp","cdp","arp","fdb"],"checks":["topology.discover"]}', 'pro', true),
|
||
('ncm', 'Config Backup (NCM)', '1.0.0', 'both', 'SSH/Telnet бекап конфігів + Git diff',
|
||
'{"transports":["ssh","telnet","api"],"checks":["ncm.backup"]}', 'enterprise', true),
|
||
('modbus', 'Modbus-TCP', '1.0.0', 'agent', 'Інвертори, UPS, BMS',
|
||
'{"metrics":["modbus.*"],"checks":["modbus.read"]}', 'pro', false),
|
||
('netflow', 'NetFlow / sFlow', '0.9.0', 'server', 'Аналіз потоків трафіку',
|
||
'{"metrics":["flow.*"],"collectors":["netflow5","netflow9","ipfix","sflow"]}', 'enterprise', false),
|
||
('syslog', 'Syslog Collector', '1.0.0', 'server', 'Прийом та парсинг syslog, тригер NCM',
|
||
'{"ports":[514],"triggers":["ncm.backup"]}', 'pro', true)
|
||
ON CONFLICT (key) DO UPDATE SET version = EXCLUDED.version, manifest = EXCLUDED.manifest;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Типи чеків
|
||
-- ---------------------------------------------------------------------
|
||
INSERT INTO core.check_types (key, plugin_key, name, params_schema, metrics) VALUES
|
||
('icmp.ping', 'icmp', 'ICMP Ping',
|
||
'{"type":"object","properties":{"count":{"type":"integer","default":3},"packet_size":{"type":"integer","default":56}}}',
|
||
'["icmp.rtt_avg","icmp.rtt_min","icmp.rtt_max","icmp.loss_pct","icmp.jitter"]'),
|
||
('snmp.get', 'snmp', 'SNMP Get',
|
||
'{"type":"object","required":["oids"],"properties":{"oids":{"type":"array","items":{"type":"string"}}}}',
|
||
'[]'),
|
||
('snmp.walk', 'snmp', 'SNMP Walk',
|
||
'{"type":"object","required":["oid"],"properties":{"oid":{"type":"string"},"max_rows":{"type":"integer","default":500}}}',
|
||
'[]'),
|
||
('snmp.if', 'snmp', 'SNMP Interfaces',
|
||
'{"type":"object","properties":{"use_hc_counters":{"type":"boolean","default":true}}}',
|
||
'["if.in_bps","if.out_bps","if.util_in_pct","if.util_out_pct","if.errors"]'),
|
||
('http.status', 'http', 'HTTP Status',
|
||
'{"type":"object","required":["url"],"properties":{"url":{"type":"string"},"expect_status":{"type":"integer","default":200},"keyword":{"type":"string"}}}',
|
||
'["http.status","http.latency_ms"]'),
|
||
('http.ssl_expiry','http','SSL Certificate Expiry',
|
||
'{"type":"object","required":["host"],"properties":{"host":{"type":"string"},"port":{"type":"integer","default":443}}}',
|
||
'["ssl.days_left"]'),
|
||
('topology.discover','topology','Neighbor Discovery',
|
||
'{"type":"object","properties":{"protos":{"type":"array","default":["lldp","cdp","arp"]}}}',
|
||
'[]'),
|
||
('ncm.backup', 'ncm', 'Config Backup',
|
||
'{"type":"object","properties":{"config_type":{"type":"string","default":"running"}}}',
|
||
'[]'),
|
||
('modbus.read', 'modbus','Modbus Read Registers',
|
||
'{"type":"object","required":["registers"],"properties":{"unit_id":{"type":"integer","default":1},"registers":{"type":"array"}}}',
|
||
'[]')
|
||
ON CONFLICT (key) DO NOTHING;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Фічі та тарифи
|
||
-- ---------------------------------------------------------------------
|
||
INSERT INTO bill.features (key, name, description, plugin_key) VALUES
|
||
('icmp', 'ICMP-моніторинг', 'Ping, RTT, втрати', 'icmp'),
|
||
('snmp', 'SNMP v2c/v3', 'Опитування метрик пристроїв', 'snmp'),
|
||
('http_checks', 'HTTP/SSL-чеки', 'Веб-сервіси та сертифікати', 'http'),
|
||
('maps_basic', 'Базова мапа', 'Одна мапа, до 15 вузлів', NULL),
|
||
('maps_unlimited', 'Мапи без обмежень', 'Необмежена кількість мап і вузлів', NULL),
|
||
('auto_discovery', 'Автовиявлення LLDP/CDP', 'Автопобудова топології', 'topology'),
|
||
('floor_plans', 'Плани приміщень', 'Підкладки PNG/SVG/OSM, схеми стійок', NULL),
|
||
('traffic_animation','Анімація трафіку', 'Анімовані лінії за завантаженням каналу', NULL),
|
||
('telegram', 'Telegram-сповіщення', 'Бот + Mini App', NULL),
|
||
('web_push', 'PWA Push', 'Web Push-сповіщення', NULL),
|
||
('ncm_git', 'NCM + Git diff', 'Бекап конфігів і контроль версій', 'ncm'),
|
||
('ncm_rollback', 'Відкат конфігурації', 'Push конфігу назад на пристрій', 'ncm'),
|
||
('compliance', 'Compliance-правила', 'Перевірка конфігів на відповідність', 'ncm'),
|
||
('netflow', 'NetFlow/sFlow', 'Аналіз потоків', 'netflow'),
|
||
('white_label', 'White-label', 'Власний бренд і домен', NULL),
|
||
('custom_roles', 'Кастомні ролі', 'Власна матриця прав', NULL),
|
||
('api_access', 'Публічний API', 'REST/gRPC + вебхуки', NULL),
|
||
('sla_reports', 'SLA-звіти', 'Uptime-звіти та PDF-експорт', NULL),
|
||
('sso', 'SSO / SAML', 'Корпоративна автентифікація', NULL)
|
||
ON CONFLICT (key) DO NOTHING;
|
||
|
||
INSERT INTO bill.plans
|
||
(key, name, description, base_price_cents, per_device_cents, max_devices, max_maps,
|
||
max_map_nodes, max_agents, max_users, metric_retention_days, features, sort_order)
|
||
VALUES
|
||
('free', 'Free / Community', 'До 15 пристроїв, 1 мапа, базовий ICMP-моніторинг',
|
||
0, 0, 15, 1, 15, 1, 3, 7,
|
||
ARRAY['icmp','http_checks','maps_basic','web_push'], 10),
|
||
|
||
('pro', 'Pro', 'Per-device білінг: SNMP, мапи без обмежень, автовиявлення, Telegram',
|
||
0, 100, NULL, NULL, NULL, 10, 25, 90,
|
||
ARRAY['icmp','snmp','http_checks','maps_basic','maps_unlimited','auto_discovery',
|
||
'traffic_animation','telegram','web_push','api_access','sla_reports'], 20),
|
||
|
||
('enterprise', 'Enterprise / NCM Suite', 'Pro + конфіги з Git diff, плани приміщень, white-label',
|
||
0, 150, NULL, NULL, NULL, NULL, NULL, 400,
|
||
ARRAY['icmp','snmp','http_checks','maps_basic','maps_unlimited','auto_discovery',
|
||
'traffic_animation','telegram','web_push','api_access','sla_reports',
|
||
'floor_plans','ncm_git','ncm_rollback','compliance','netflow',
|
||
'white_label','custom_roles','sso'], 30)
|
||
ON CONFLICT (key) DO UPDATE SET
|
||
features = EXCLUDED.features,
|
||
max_devices = EXCLUDED.max_devices,
|
||
max_map_nodes = EXCLUDED.max_map_nodes,
|
||
metric_retention_days = EXCLUDED.metric_retention_days;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Вбудовані NCM-профілі (tenant_id IS NULL)
|
||
-- ---------------------------------------------------------------------
|
||
INSERT INTO ncm.profiles (key, name, vendor, transport, commands, prompt_regex, enable_required,
|
||
scrub_patterns, redact_patterns, is_builtin) VALUES
|
||
('cisco-ios', 'Cisco IOS', 'cisco', 'ssh',
|
||
'["terminal length 0","show running-config"]', '[>#]\s*$', true,
|
||
'["^Building configuration","^Current configuration : \\d+ bytes","^ntp clock-period"]',
|
||
'["(password|secret) \\d? ?\\S+","snmp-server community \\S+"]', true),
|
||
('mikrotik-routeros', 'MikroTik RouterOS', 'mikrotik', 'ssh',
|
||
'["/export terse"]', '\] > $', false,
|
||
'["^# \\w{3}/\\d{2}/\\d{4}"]', '["password=\\S+"]', true),
|
||
('juniper-junos', 'Juniper JunOS', 'juniper', 'ssh',
|
||
'["set cli screen-length 0","show configuration | display set"]', '[%>#]\s*$', false,
|
||
'["^## Last commit:"]', '["encrypted-password \\S+"]', true),
|
||
('huawei-vrp', 'Huawei VRP', 'huawei', 'ssh',
|
||
'["screen-length 0 temporary","display current-configuration"]', '[<\\[].*[>\\]]', false,
|
||
'[]', '["cipher \\S+"]', true),
|
||
('bdcom-olt', 'BDCOM OLT', 'bdcom', 'telnet',
|
||
'["terminal length 0","show running-config"]', '[>#]\s*$', true,
|
||
'[]', '["(password|secret) \\S+"]', true)
|
||
ON CONFLICT DO NOTHING;
|