Netpulse_SasS/server/migrations/0010_seed.sql
byrsapty 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

205 lines
14 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- 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;