Netpulse_SasS/server/migrations/0008_dashboards.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

102 lines
5 KiB
SQL
Raw 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 :: 0008_dashboards.sql
-- Дашборди (в т.ч. NOC/TV-режим), віджети, збережені фільтри, SLA-звіти.
-- =====================================================================
CREATE TYPE core.dashboard_kind AS ENUM ('standard','noc_tv','mobile','embed');
CREATE TABLE core.dashboards (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
name text NOT NULL,
slug core.slug NOT NULL,
kind core.dashboard_kind NOT NULL DEFAULT 'standard',
-- Сітка: {"cols":24,"rowHeight":40,"compact":"vertical"}
layout jsonb NOT NULL DEFAULT '{"cols":24,"rowHeight":40}'::jsonb,
-- TV-режим: автопрокрутка сторінок, темна тема, приховані меню
tv_options jsonb NOT NULL DEFAULT '{"rotate_sec":0,"hide_chrome":true,"theme":"dark"}'::jsonb,
refresh_sec int NOT NULL DEFAULT 30,
time_range jsonb NOT NULL DEFAULT '{"from":"now-6h","to":"now"}'::jsonb,
variables jsonb NOT NULL DEFAULT '[]'::jsonb, -- $site, $group для фільтрів
is_default boolean NOT NULL DEFAULT false,
public_token text UNIQUE, -- read-only лінк для телевізора в NOC
created_by uuid REFERENCES core.users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, slug)
);
CREATE TYPE core.widget_kind AS ENUM (
'map', -- повноекранна мапа топології
'timeseries','gauge','stat','table','heatmap','topn','pie',
'alert_list','device_grid','status_matrix','uptime_bar',
'syslog_stream','ncm_changes','text','iframe','weather_map'
);
CREATE TABLE core.dashboard_widgets (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
dashboard_id uuid NOT NULL REFERENCES core.dashboards(id) ON DELETE CASCADE,
kind core.widget_kind NOT NULL,
title text,
-- Позиція в сітці
grid_x int NOT NULL DEFAULT 0,
grid_y int NOT NULL DEFAULT 0,
grid_w int NOT NULL DEFAULT 6,
grid_h int NOT NULL DEFAULT 4,
-- Для kind='map'
map_id uuid REFERENCES topo.maps(id) ON DELETE SET NULL,
-- Джерела даних: [{"metric_key":"cpu.util","selector":{...},"agg":"avg"}]
queries jsonb NOT NULL DEFAULT '[]'::jsonb,
options jsonb NOT NULL DEFAULT '{}'::jsonb, -- осі, кольори, пороги, легенда
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX widgets_dashboard_idx ON core.dashboard_widgets (dashboard_id);
-- Збережені фільтри / представлення інвентарю
CREATE TABLE core.saved_views (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
user_id uuid REFERENCES core.users(id) ON DELETE CASCADE, -- NULL = спільний
scope text NOT NULL, -- devices | alerts | maps | ncm
name text NOT NULL,
filter jsonb NOT NULL DEFAULT '{}'::jsonb,
columns jsonb NOT NULL DEFAULT '[]'::jsonb,
sort jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
-- ---------------------------------------------------------------------
-- SLA / uptime-звіти
-- ---------------------------------------------------------------------
CREATE TABLE core.sla_targets (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
name text NOT NULL,
selector jsonb NOT NULL DEFAULT '{}'::jsonb,
target_pct numeric(5,3) NOT NULL DEFAULT 99.900,
-- 'window' — зарезервоване слово в PostgreSQL, тому period_kind
period_kind text NOT NULL DEFAULT 'monthly', -- daily | weekly | monthly | quarterly
-- Робочі години, поза якими простій не рахується
business_hours jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (tenant_id, name)
);
CREATE TABLE core.sla_periods (
id uuid PRIMARY KEY DEFAULT core.new_id(),
tenant_id uuid NOT NULL REFERENCES core.tenants(id) ON DELETE CASCADE,
sla_target_id uuid NOT NULL REFERENCES core.sla_targets(id) ON DELETE CASCADE,
device_id uuid REFERENCES inv.devices(id) ON DELETE CASCADE,
period daterange NOT NULL,
uptime_pct numeric(6,3) NOT NULL,
downtime_sec bigint NOT NULL DEFAULT 0,
maintenance_sec bigint NOT NULL DEFAULT 0,
incidents int NOT NULL DEFAULT 0,
breached boolean NOT NULL DEFAULT false,
computed_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (sla_target_id, device_id, period)
);
CREATE INDEX sla_periods_tenant_idx ON core.sla_periods (tenant_id, period);