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>
79 lines
4.5 KiB
SQL
79 lines
4.5 KiB
SQL
-- =====================================================================
|
||
-- NetPulse :: 0022_template_graphs.sql
|
||
-- Графіки описуються в шаблоні разом із метриками.
|
||
--
|
||
-- Причина та сама, що для самих метрик: «завантаження каналу — це
|
||
-- if.in_bps і if.out_bps на одній осі» — це властивість класу
|
||
-- пристроїв, а не окремого хоста. Малювати той самий графік руками на
|
||
-- кожному хості означає повторювати одне рішення стільки разів,
|
||
-- скільки в мережі заліза.
|
||
-- =====================================================================
|
||
|
||
CREATE TYPE tpl.graph_kind AS ENUM (
|
||
'line', -- звичайні лінії
|
||
'area', -- лінії із заливкою
|
||
'stacked', -- заливка з накопиченням: сума має сенс (трафік по портах)
|
||
'bar', -- стовпчики: рідкі виміри, де лінія бреше про проміжок
|
||
'gauge', -- одне значення зі шкалою
|
||
'stat' -- одне значення числом
|
||
);
|
||
|
||
CREATE TABLE tpl.graphs (
|
||
id uuid PRIMARY KEY DEFAULT core.new_id(),
|
||
template_id uuid NOT NULL REFERENCES tpl.templates(id) ON DELETE CASCADE,
|
||
key core.slug NOT NULL,
|
||
name text NOT NULL,
|
||
kind tpl.graph_kind NOT NULL DEFAULT 'line',
|
||
-- Метрики на графіку: ["cpu.util_pct","mem.available_bytes"].
|
||
--
|
||
-- Ключі метрик, а не посилання на tpl.items: графік має право
|
||
-- показувати й те, що прийшло з іншого шаблону, — саме так і
|
||
-- виглядає «трафік поруч із помилками на тому ж порту».
|
||
metric_keys jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||
-- Осі, кольори, межі, пороги: {"min":0,"max":100,"unit":"%"}
|
||
options jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||
position int NOT NULL DEFAULT 0,
|
||
created_at timestamptz NOT NULL DEFAULT now(),
|
||
UNIQUE (template_id, key)
|
||
);
|
||
CREATE INDEX tpl_graphs_template_idx ON tpl.graphs (template_id);
|
||
|
||
ALTER TABLE tpl.graphs ENABLE ROW LEVEL SECURITY;
|
||
ALTER TABLE tpl.graphs FORCE ROW LEVEL SECURITY;
|
||
|
||
-- Видимість успадковується від шаблону — власного tenant_id у графіка
|
||
-- немає з тієї ж причини, що і в елементів: він завжди дорівнював би
|
||
-- шаблоновому, а дублювання ключа ізоляції розходиться.
|
||
CREATE POLICY graphs_follow_template ON tpl.graphs
|
||
USING (EXISTS (
|
||
SELECT 1 FROM tpl.templates t
|
||
WHERE t.id = tpl.graphs.template_id
|
||
AND (t.tenant_id IS NULL OR t.tenant_id = core.current_tenant())
|
||
))
|
||
WITH CHECK (EXISTS (
|
||
SELECT 1 FROM tpl.templates t
|
||
WHERE t.id = tpl.graphs.template_id AND t.tenant_id = core.current_tenant()
|
||
));
|
||
|
||
GRANT SELECT, INSERT, UPDATE, DELETE ON tpl.graphs TO netpulse_app, netpulse_worker;
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Графіки вбудованих шаблонів
|
||
-- ---------------------------------------------------------------------
|
||
|
||
INSERT INTO tpl.graphs (template_id, key, name, kind, metric_keys, options) VALUES
|
||
('00000000-0000-0000-0000-0000000000c3', 'memory', 'Пам''ять', 'area',
|
||
'["mem.available_bytes","mem.total_bytes"]'::jsonb, '{"unit":"B"}'::jsonb),
|
||
('00000000-0000-0000-0000-0000000000c3', 'load', 'Середнє навантаження', 'line',
|
||
'["cpu.load1","cpu.load5"]'::jsonb, '{"min":0}'::jsonb),
|
||
('00000000-0000-0000-0000-0000000000c4', 'cpu', 'Завантаження CPU', 'gauge',
|
||
'["cpu.util_pct"]'::jsonb, '{"min":0,"max":100,"unit":"%"}'::jsonb),
|
||
('00000000-0000-0000-0000-0000000000c4', 'temp', 'Температура плати', 'line',
|
||
'["sensor.temp_c"]'::jsonb, '{"unit":"°C"}'::jsonb),
|
||
('00000000-0000-0000-0000-0000000000c5', 'rtt', 'Час відгуку', 'line',
|
||
'["icmp.rtt_avg","icmp.jitter"]'::jsonb, '{"min":0,"unit":"ms"}'::jsonb),
|
||
('00000000-0000-0000-0000-0000000000c5', 'loss', 'Втрати пакетів', 'area',
|
||
'["icmp.loss_pct"]'::jsonb, '{"min":0,"max":100,"unit":"%"}'::jsonb),
|
||
('00000000-0000-0000-0000-0000000000c1', 'uptime', 'Час роботи', 'stat',
|
||
'["sys.uptime_sec"]'::jsonb, '{"unit":"s"}'::jsonb)
|
||
ON CONFLICT DO NOTHING;
|