Песочница отчётов для ИИ: recharts в custom pages + внешние БД с read-only шлюзом
- JsComponentRenderer: ctx.libs.charts (recharts) — графики в кастомных страницах и JS-вкладках без redeploy; справка get_js_coding_reference обновлена - Внешние БД (миграция 0088): вкладка «Настройки → Внешние БД» — реестр подключений (пароль AES-256-GCM, env EXTERNAL_DB_SECRET, по API не отдаётся), тест подключения, allowlist таблиц на подключение - Read-only шлюз POST /api/external-db/:id/query (finance.manage): только SELECT, без мульти-выражений, обёртка с LIMIT 5000, statement_timeout 10с, проверка таблиц по allowlist, аудит в external_db_query_log (кто/SQL/строки/мс) - Назначение: ИИ-агенты строят отчёты и виджеты поверх витрин (напр. НДС 1С) без новых серверных эндпоинтов и пересборки
This commit is contained in:
51
migrations/0088_external_db_connections.sql
Normal file
51
migrations/0088_external_db_connections.sql
Normal file
@@ -0,0 +1,51 @@
|
||||
-- Внешние подключения к БД (read-only шлюз для отчётов/виджетов, в т.ч. ИИ-агентами).
|
||||
-- Пароль хранится в AES-256-GCM (server/routes/external-db.routes.ts), по API не отдаётся.
|
||||
-- allowed_tables — jsonb-массив имён таблиц (lowercase), доступных через шлюз;
|
||||
-- пустой массив = шлюз закрыт (админ обязан явно указать таблицы).
|
||||
|
||||
CREATE TABLE IF NOT EXISTS external_db_connections (
|
||||
id SERIAL PRIMARY KEY,
|
||||
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
host TEXT NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5432,
|
||||
database TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password_encrypted TEXT NOT NULL,
|
||||
ssl BOOLEAN NOT NULL DEFAULT false,
|
||||
allowed_tables JSONB NOT NULL DEFAULT '[]',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS external_db_connections_org_idx ON external_db_connections (organization_id);
|
||||
|
||||
-- Журнал запросов шлюза: кто, какой SQL, сколько строк, сколько мс
|
||||
CREATE TABLE IF NOT EXISTS external_db_query_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
connection_id INTEGER NOT NULL REFERENCES external_db_connections(id) ON DELETE CASCADE,
|
||||
user_id INTEGER,
|
||||
bot_id INTEGER,
|
||||
sql_text TEXT NOT NULL,
|
||||
row_count INTEGER NOT NULL DEFAULT 0,
|
||||
duration_ms INTEGER NOT NULL DEFAULT 0,
|
||||
error TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS external_db_query_log_org_idx ON external_db_query_log (organization_id, created_at DESC);
|
||||
|
||||
-- Tenant isolation
|
||||
ALTER TABLE external_db_connections ENABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_iso ON external_db_connections;
|
||||
CREATE POLICY tenant_iso ON external_db_connections USING (
|
||||
current_setting('app.is_superadmin', true) = 'true'
|
||||
OR organization_id = NULLIF(current_setting('app.current_org_id', true), '')::int
|
||||
);
|
||||
|
||||
ALTER TABLE external_db_query_log ENABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_iso ON external_db_query_log;
|
||||
CREATE POLICY tenant_iso ON external_db_query_log USING (
|
||||
current_setting('app.is_superadmin', true) = 'true'
|
||||
OR organization_id = NULLIF(current_setting('app.current_org_id', true), '')::int
|
||||
);
|
||||
Reference in New Issue
Block a user