542 lines
29 KiB
TypeScript
542 lines
29 KiB
TypeScript
import { sql } from "drizzle-orm"
|
||
import {
|
||
integer,
|
||
real,
|
||
sqliteTable,
|
||
text,
|
||
} from "drizzle-orm/sqlite-core"
|
||
|
||
// ── servers ────────────────────────────────────────────────────────────────────
|
||
|
||
export const servers = sqliteTable("servers", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
name: text("name").notNull().default(""),
|
||
host: text("host").notNull(),
|
||
port: integer("port").notNull().default(443),
|
||
username: text("username").notNull().default("admin"),
|
||
password: text("password").notNull().default(""),
|
||
useSsl: integer("use_ssl", { mode: "boolean" }).notNull().default(true),
|
||
verifySsl: integer("verify_ssl", { mode: "boolean" }).notNull().default(false),
|
||
|
||
// metadata set manually by user
|
||
type: text("type", { enum: ["jump-host", "exit-node", "home-router"] })
|
||
.notNull().default("home-router"),
|
||
site: text("site").notNull().default(""),
|
||
country: text("country").notNull().default(""),
|
||
asn: text("asn").notNull().default(""),
|
||
comment: text("comment").notNull().default(""),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
|
||
/** Подсеть LAN (home-router), текст из формы */
|
||
lanSubnet: text("lan_subnet").notNull().default(""),
|
||
/** JSON-массив WAN-аплинков [{ id, name, isp, iface, ip, maxDl, maxUl }, …] */
|
||
wanUplinks: text("wan_uplinks").notNull().default("[]"),
|
||
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
// ── server_snapshots ───────────────────────────────────────────────────────────
|
||
|
||
export const serverSnapshots = sqliteTable("server_snapshots", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
serverId: integer("server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
polledAt: text("polled_at").notNull(),
|
||
status: text("status", { enum: ["online", "offline"] }).notNull(),
|
||
latencyMs: real("latency_ms"),
|
||
|
||
// data from RouterOS
|
||
rosVersion: text("ros_version"),
|
||
boardName: text("board_name"),
|
||
uptime: text("uptime"),
|
||
cpuLoad: integer("cpu_load"),
|
||
freeMemory: integer("free_memory"),
|
||
totalMemory: integer("total_memory"),
|
||
identityName: text("identity_name"),
|
||
|
||
// raw JSON payloads for future use
|
||
rawInterfaces: text("raw_interfaces"),
|
||
rawIpAddresses: text("raw_ip_addresses"),
|
||
})
|
||
|
||
// ── filter_rules ───────────────────────────────────────────────────────────────
|
||
|
||
export const filterRules = sqliteTable("filter_rules", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
serverId: integer("server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
sortOrder: integer("sort_order").notNull().default(0),
|
||
community: text("community").notNull(),
|
||
communityName: text("community_name"),
|
||
action: text("action", { enum: ["route", "blackhole"] }).notNull().default("route"),
|
||
gateway: text("gateway").notNull().default(""),
|
||
gatewayTunnelId: text("gateway_tunnel_id").notNull().default(""),
|
||
description: text("description").notNull().default(""),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
// ── recursive_routes ───────────────────────────────────────────────────────────
|
||
|
||
export const recursiveRoutes = sqliteTable("recursive_routes", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
serverId: integer("server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
sortOrder: integer("sort_order").notNull().default(0),
|
||
dstAddress: text("dst_address").notNull(),
|
||
gateway: text("gateway").notNull(),
|
||
distance: integer("distance").notNull().default(1),
|
||
scope: integer("scope"),
|
||
targetScope: integer("target_scope"),
|
||
routingTable: text("routing_table").notNull().default("main"),
|
||
checkGateway: text("check_gateway").notNull().default(""),
|
||
country: text("country").notNull().default(""),
|
||
comment: text("comment").notNull().default(""),
|
||
disabled: integer("disabled", { mode: "boolean" }).notNull().default(false),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
// ── traffic collection settings ────────────────────────────────────────────────
|
||
|
||
export const trafficSettings = sqliteTable("traffic_settings", {
|
||
id: integer("id").primaryKey(),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
intervalSec: integer("interval_sec").notNull().default(30),
|
||
retentionDays: integer("retention_days").notNull().default(14),
|
||
lastCollectedAt: text("last_collected_at"),
|
||
lastDurationMs: integer("last_duration_ms"),
|
||
lastError: text("last_error"),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Настройки фоновой проверки доступности RouterOS REST API по серверам каталога. */
|
||
export const serversApiPingSettings = sqliteTable("servers_api_ping_settings", {
|
||
id: integer("id").primaryKey(),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
|
||
intervalSec: integer("interval_sec").notNull().default(120),
|
||
lastCollectedAt: text("last_collected_at"),
|
||
lastDurationMs: integer("last_duration_ms"),
|
||
lastError: text("last_error"),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Сырые пробы REST `/system/identity` по серверам — для UI сбора и для `buildSignalSnapshot` (алерты). */
|
||
export const serversRestPingSamples = sqliteTable("servers_rest_ping_samples", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
serverId: integer("server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
ok: integer("ok", { mode: "boolean" }).notNull(),
|
||
latencyMs: integer("latency_ms"),
|
||
error: text("error"),
|
||
})
|
||
|
||
/** Сырые снимки GRE для алертов — пишет джоба `gre_bgp`. */
|
||
export const alertGreTunnelSamples = sqliteTable("alert_gre_tunnel_samples", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
targetLabel: text("target_label").notNull(),
|
||
status: text("status").notNull(),
|
||
})
|
||
|
||
/** Сырые снимки BGP-сессий для алертов — пишет джоба `gre_bgp`. */
|
||
export const alertBgpPeerSamples = sqliteTable("alert_bgp_peer_samples", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
peerKey: text("peer_key").notNull(),
|
||
state: text("state").notNull(),
|
||
})
|
||
|
||
// ── raw traffic samples (per server/interface/timepoint) ──────────────────────
|
||
|
||
export const trafficSamples = sqliteTable("traffic_samples", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
serverId: integer("server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
interfaceName: text("interface_name").notNull(),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
rxBytes: integer("rx_bytes").notNull().default(0),
|
||
txBytes: integer("tx_bytes").notNull().default(0),
|
||
rxBps: integer("rx_bps").notNull().default(0),
|
||
txBps: integer("tx_bps").notNull().default(0),
|
||
running: integer("running", { mode: "boolean" }).notNull().default(false),
|
||
disabled: integer("disabled", { mode: "boolean" }).notNull().default(false),
|
||
})
|
||
|
||
// ── uptime monitor settings ────────────────────────────────────────────────────
|
||
|
||
export const uptimeSettings = sqliteTable("uptime_settings", {
|
||
id: integer("id").primaryKey(),
|
||
/** Устаревший агрегат: синхронизируется как resources ∨ ping ∨ speed (для совместимости). */
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
resourcesEnabled: integer("resources_enabled", { mode: "boolean" }).notNull().default(true),
|
||
pingEnabled: integer("ping_enabled", { mode: "boolean" }).notNull().default(true),
|
||
speedEnabled: integer("speed_enabled", { mode: "boolean" }).notNull().default(true),
|
||
intervalSec: integer("interval_sec").notNull().default(15),
|
||
probeIntervalSec: integer("probe_interval_sec").notNull().default(15),
|
||
speedIntervalSec: integer("speed_interval_sec").notNull().default(60),
|
||
retentionDays: integer("retention_days").notNull().default(14),
|
||
lastCollectedAt: text("last_collected_at"),
|
||
lastDurationMs: integer("last_duration_ms"),
|
||
lastError: text("last_error"),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
export const uptimeProbes = sqliteTable("uptime_probes", {
|
||
id: text("id").primaryKey(),
|
||
srcServerId: integer("src_server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
srcInterface: text("src_interface").notNull().default(""),
|
||
name: text("name").notNull(),
|
||
target: text("target").notNull(),
|
||
probeFilter: text("probe_filter").notNull().default("—"),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
/** 0 — брать глобальный probe_interval_sec из uptime_settings */
|
||
intervalSec: integer("interval_sec").notNull().default(0),
|
||
/** Выводить пробу в блоке «Активные пробы» на дашборде */
|
||
showOnDashboard: integer("show_on_dashboard", { mode: "boolean" }).notNull().default(false),
|
||
sortOrder: integer("sort_order").notNull().default(0),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
export const uptimeProbeSamples = sqliteTable("uptime_probe_samples", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
probeId: text("probe_id")
|
||
.notNull()
|
||
.references(() => uptimeProbes.id, { onDelete: "cascade" }),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
rttMs: integer("rtt_ms"),
|
||
lossPct: integer("loss_pct").notNull().default(0),
|
||
status: text("status", { enum: ["up", "warn", "down"] }).notNull().default("down"),
|
||
})
|
||
|
||
export const uptimeResourceSamples = sqliteTable("uptime_resource_samples", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
serverId: integer("server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
status: text("status", { enum: ["online", "offline"] }).notNull().default("offline"),
|
||
cpuLoad: integer("cpu_load").notNull().default(0),
|
||
freeMemory: integer("free_memory").notNull().default(0),
|
||
totalMemory: integer("total_memory").notNull().default(0),
|
||
freeHddSpace: integer("free_hdd_space").notNull().default(0),
|
||
totalHddSpace: integer("total_hdd_space").notNull().default(0),
|
||
uptimeSeconds: integer("uptime_seconds").notNull().default(0),
|
||
boardName: text("board_name").notNull().default(""),
|
||
rosVersion: text("ros_version").notNull().default(""),
|
||
})
|
||
|
||
export const uptimeSpeedProbes = sqliteTable("uptime_speed_probes", {
|
||
id: text("id").primaryKey(),
|
||
srcServerId: integer("src_server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
dstServerId: integer("dst_server_id")
|
||
.notNull()
|
||
.references(() => servers.id, { onDelete: "cascade" }),
|
||
srcInterface: text("src_interface").notNull().default(""),
|
||
dstInterface: text("dst_interface").notNull().default(""),
|
||
protocol: text("protocol", { enum: ["tcp", "udp"] }).notNull().default("tcp"),
|
||
direction: text("direction", { enum: ["transmit", "receive", "both"] }).notNull().default("both"),
|
||
durationSec: integer("duration_sec").notNull().default(10),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
lastRunAt: text("last_run_at"),
|
||
lastTxAvgMbps: real("last_tx_avg_mbps"),
|
||
lastRxAvgMbps: real("last_rx_avg_mbps"),
|
||
lastStatus: text("last_status", { enum: ["done", "error"] }),
|
||
lastError: text("last_error"),
|
||
lastPingRttMs: integer("last_ping_rtt_ms"),
|
||
lastPingLossPct: integer("last_ping_loss_pct"),
|
||
lastPingAt: text("last_ping_at"),
|
||
lastPingError: text("last_ping_error"),
|
||
sortOrder: integer("sort_order").notNull().default(0),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
// ── EvoBGP integration (URL + API key на сервере) ─────────────────────────────
|
||
|
||
export const evobgpSettings = sqliteTable("evobgp_settings", {
|
||
id: integer("id").primaryKey(),
|
||
baseUrl: text("base_url").notNull().default(""),
|
||
apiKey: text("api_key").notNull().default(""),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Единственная строка id=1: токен и чат Telegram для оповещений. */
|
||
export const alertTelegramSettings = sqliteTable("alert_telegram_settings", {
|
||
id: integer("id").primaryKey(),
|
||
botToken: text("bot_token").notNull().default(""),
|
||
chatId: text("chat_id").notNull().default(""),
|
||
/** Тема супергруппы (forum): `message_thread_id` в Bot API; NULL — общий чат */
|
||
messageThreadId: integer("message_thread_id"),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
export const acmeSettings = sqliteTable("acme_settings", {
|
||
id: integer("id").primaryKey(),
|
||
directoryUrl: text("directory_url").notNull().default("https://acme-v02.api.letsencrypt.org/directory"),
|
||
cloudflareApiToken: text("cloudflare_api_token").notNull().default(""),
|
||
defaultZoneId: text("default_zone_id").notNull().default(""),
|
||
accountPrivateKey: text("account_private_key").notNull().default(""),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
export const certificateIssueJobs = sqliteTable("certificate_issue_jobs", {
|
||
id: text("id").primaryKey(),
|
||
status: text("status", { enum: ["queued", "running", "done", "failed"] }).notNull().default("queued"),
|
||
step: text("step").notNull().default("queued"),
|
||
serverId: text("server_id").notNull(),
|
||
certName: text("cert_name").notNull(),
|
||
domainNames: text("domain_names").notNull(),
|
||
keyType: text("key_type").notNull().default("rsa2048"),
|
||
trustStore: text("trust_store").notNull().default("www,api"),
|
||
requestedAt: text("requested_at").notNull().default(sql`(datetime('now'))`),
|
||
startedAt: text("started_at"),
|
||
finishedAt: text("finished_at"),
|
||
error: text("error"),
|
||
})
|
||
|
||
/** Группы правил: ANY = хотя бы одно; ALL = все одновременно в окне тика. */
|
||
export const alertGroups = sqliteTable("alert_groups", {
|
||
id: text("id").primaryKey(),
|
||
name: text("name").notNull(),
|
||
combineMode: text("combine_mode", { enum: ["any", "all"] }).notNull().default("any"),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
/** Переопределение cooldown для агрегата группы; NULL — по умолчанию 5м */
|
||
cooldownOverride: text("cooldown_override", {
|
||
enum: ["1м", "5м", "15м", "1ч", "4ч", "24ч"],
|
||
}),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Правила оповещений (UI /alerts). */
|
||
export const alertRules = sqliteTable("alert_rules", {
|
||
id: text("id").primaryKey(),
|
||
name: text("name").notNull(),
|
||
type: text("type", {
|
||
enum: ["gre-tunnel", "bgp-peer", "bgp-prefix", "gre-client", "server", "rtt", "loss", "traffic"],
|
||
}).notNull(),
|
||
/** Сводная подпись (первый target или join); для совместимости и поиска */
|
||
target: text("target").notNull(),
|
||
condition: text("condition").notNull(),
|
||
severity: text("severity", { enum: ["critical", "warning", "info"] }).notNull(),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
cooldown: text("cooldown", {
|
||
enum: ["1м", "5м", "15м", "1ч", "4ч", "24ч"],
|
||
}).notNull().default("5м"),
|
||
chatId: text("rule_chat_id").notNull().default(""),
|
||
/** NULL / 0 — выкл. Секунды: уведомление только если условие держится столько времени без «отмены» (восстановление сигнала). */
|
||
confirmStabilitySec: integer("confirm_stability_sec"),
|
||
/** Политика recovery-уведомления для правила. */
|
||
recoveryMode: text("recovery_mode", {
|
||
enum: ["always", "never", "conditional"],
|
||
}).notNull().default("always"),
|
||
/** Доп. задержка подтверждения для recovery при `conditional`; NULL — без доп. ожидания. */
|
||
recoveryStabilitySec: integer("recovery_stability_sec"),
|
||
/** NULL — правило вне группы (отдельные отправки по cooldown правила) */
|
||
groupId: text("group_id"),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Несколько объектов на правило; срабатывание по OR (хотя бы один). */
|
||
export const alertRuleTargets = sqliteTable("alert_rule_targets", {
|
||
id: text("id").primaryKey(),
|
||
ruleId: text("rule_id").notNull(),
|
||
target: text("target").notNull(),
|
||
sortIndex: integer("sort_index").notNull().default(0),
|
||
})
|
||
|
||
/** Несколько условий на правило; срабатывание по OR (любое из выбранных). */
|
||
export const alertRuleConditions = sqliteTable("alert_rule_conditions", {
|
||
id: text("id").primaryKey(),
|
||
ruleId: text("rule_id").notNull(),
|
||
conditionLine: text("condition_line").notNull(),
|
||
sortIndex: integer("sort_index").notNull().default(0),
|
||
})
|
||
|
||
/** Состояние движка: cooldown / дедуп по ключу rule:id или group:id */
|
||
export const alertEngineState = sqliteTable("alert_engine_state", {
|
||
scopeKey: text("scope_key").primaryKey(),
|
||
lastFiredAt: text("last_fired_at").notNull().default(""),
|
||
lastPayloadHash: text("last_payload_hash"),
|
||
})
|
||
|
||
/** Снимок прошлого тика для live-сигналов (GRE/BGP): переходы «offline» / «восстановился». */
|
||
export const alertEnginePrevLive = sqliteTable("alert_engine_prev_live", {
|
||
kind: text("kind", { enum: ["gre", "bgp"] }).primaryKey(),
|
||
payloadJson: text("payload_json").notNull().default("{}"),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Ожидание стабильности срабатывания по правилу (антидребезг). */
|
||
export const alertEngineConfirmPending = sqliteTable("alert_engine_confirm_pending", {
|
||
ruleId: text("rule_id").primaryKey(),
|
||
payloadHash: text("payload_hash").notNull(),
|
||
sinceAt: text("since_at").notNull(),
|
||
})
|
||
|
||
/** Задел: несколько получателей Telegram (пока UI не подключён) */
|
||
export const alertDestinations = sqliteTable("alert_destinations", {
|
||
id: text("id").primaryKey(),
|
||
kind: text("kind", { enum: ["telegram"] }).notNull().default("telegram"),
|
||
label: text("label").notNull().default(""),
|
||
telegramChatId: text("telegram_chat_id").notNull().default(""),
|
||
messageThreadId: integer("message_thread_id"),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Журнал отправок / срабатываний (персист + при необходимости пополняется воркером). */
|
||
export const alertHistory = sqliteTable("alert_history", {
|
||
id: text("id").primaryKey(),
|
||
ruleId: text("rule_id"),
|
||
groupId: text("group_id"),
|
||
ruleName: text("rule_name").notNull(),
|
||
severity: text("severity", { enum: ["critical", "warning", "info"] }).notNull(),
|
||
message: text("message").notNull(),
|
||
sentOk: integer("sent_ok", { mode: "boolean" }).notNull().default(true),
|
||
firedAt: text("fired_at").notNull(),
|
||
})
|
||
|
||
/** Outbox доставки уведомлений: ретраи и идемпотентность отправки каналов. */
|
||
export const alertOutbox = sqliteTable("alert_outbox", {
|
||
id: text("id").primaryKey(),
|
||
dedupeKey: text("dedupe_key").notNull(),
|
||
channel: text("channel", { enum: ["telegram"] }).notNull().default("telegram"),
|
||
status: text("status", { enum: ["pending", "sent", "failed"] }).notNull().default("pending"),
|
||
retryCount: integer("retry_count").notNull().default(0),
|
||
maxRetries: integer("max_retries").notNull().default(3),
|
||
nextAttemptAt:text("next_attempt_at").notNull(),
|
||
payloadJson: text("payload_json").notNull(),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
sentAt: text("sent_at"),
|
||
lastError: text("last_error"),
|
||
})
|
||
|
||
/** Cursor движка: watermark последней обработанной точки snapshot-источников. */
|
||
export const alertEngineCursor = sqliteTable("alert_engine_cursor", {
|
||
id: integer("id").primaryKey(),
|
||
lastSourceFinishedAt: text("last_source_finished_at"),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
/** Журнал прогонов планировщика (аудит). */
|
||
export const schedulerRuns = sqliteTable("scheduler_runs", {
|
||
id: text("id").primaryKey(),
|
||
jobKey: text("job_key").notNull(),
|
||
startedAt: text("started_at").notNull(),
|
||
finishedAt: text("finished_at").notNull(),
|
||
status: text("status", { enum: ["ok", "error"] }).notNull(),
|
||
error: text("error"),
|
||
durationMs: integer("duration_ms").notNull().default(0),
|
||
/** JSON: см. `SchedulerRunSnapshot` в types/scheduler-run-snapshot.ts */
|
||
resultJson: text("result_json"),
|
||
})
|
||
|
||
/** Централизованный append-only журнал событий системы. */
|
||
export const events = sqliteTable("events", {
|
||
id: text("id").primaryKey(),
|
||
createdAt: text("created_at").notNull(),
|
||
level: text("level", { enum: ["critical", "warning", "info"] }).notNull(),
|
||
eventType: text("event_type").notNull(),
|
||
sourceModule: text("source_module").notNull(),
|
||
title: text("title").notNull(),
|
||
message: text("message").notNull(),
|
||
entityType: text("entity_type"),
|
||
entityId: text("entity_id"),
|
||
payloadJson: text("payload_json"),
|
||
})
|
||
|
||
export const uptimeSpeedTestRuns = sqliteTable("uptime_speed_test_runs", {
|
||
id: text("id").primaryKey(),
|
||
probeId: text("probe_id"),
|
||
srcServerId: integer("src_server_id").notNull().references(() => servers.id, { onDelete: "cascade" }),
|
||
dstServerId: integer("dst_server_id").notNull().references(() => servers.id, { onDelete: "cascade" }),
|
||
srcInterface: text("src_interface").notNull().default(""),
|
||
dstInterface: text("dst_interface").notNull().default(""),
|
||
srcAddress: text("src_address"),
|
||
dstAddress: text("dst_address"),
|
||
srcInterfaceAddress:text("src_interface_address"),
|
||
dstInterfaceAddress:text("dst_interface_address"),
|
||
protocol: text("protocol", { enum: ["tcp", "udp"] }).notNull().default("tcp"),
|
||
direction: text("direction", { enum: ["transmit", "receive", "both"] }).notNull().default("both"),
|
||
durationSec: integer("duration_sec").notNull().default(10),
|
||
txAvgMbps: real("tx_avg_mbps"),
|
||
rxAvgMbps: real("rx_avg_mbps"),
|
||
pingRttMs: integer("ping_rtt_ms"),
|
||
pingLossPct: integer("ping_loss_pct"),
|
||
pingError: text("ping_error"),
|
||
status: text("status", { enum: ["done", "error"] }).notNull().default("done"),
|
||
error: text("error"),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
export const internetPathSettings = sqliteTable("internet_path_settings", {
|
||
id: integer("id").primaryKey(),
|
||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||
intervalSec: integer("interval_sec").notNull().default(300),
|
||
retentionDays: integer("retention_days").notNull().default(14),
|
||
lastCollectedAt: text("last_collected_at"),
|
||
lastDurationMs: integer("last_duration_ms"),
|
||
lastError: text("last_error"),
|
||
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
|
||
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
|
||
})
|
||
|
||
export const internetPathSnapshots = sqliteTable("internet_path_snapshots", {
|
||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||
sampledAt: text("sampled_at").notNull(),
|
||
payloadJson: text("payload_json").notNull(),
|
||
})
|
||
|
||
// ── inferred types ─────────────────────────────────────────────────────────────
|
||
|
||
export type Server = typeof servers.$inferSelect
|
||
export type ServerInsert = typeof servers.$inferInsert
|
||
export type Snapshot = typeof serverSnapshots.$inferSelect
|
||
export type SnapshotInsert = typeof serverSnapshots.$inferInsert
|
||
export type FilterRuleRow = typeof filterRules.$inferSelect
|
||
export type RecursiveRouteRow = typeof recursiveRoutes.$inferSelect
|
||
export type TrafficSettingsRow = typeof trafficSettings.$inferSelect
|
||
export type ServersApiPingSettingsRow = typeof serversApiPingSettings.$inferSelect
|
||
export type TrafficSampleRow = typeof trafficSamples.$inferSelect
|
||
export type UptimeSettingsRow = typeof uptimeSettings.$inferSelect
|
||
export type UptimeProbeRow = typeof uptimeProbes.$inferSelect
|
||
export type UptimeProbeSampleRow = typeof uptimeProbeSamples.$inferSelect
|
||
export type UptimeResourceSampleRow = typeof uptimeResourceSamples.$inferSelect
|
||
export type UptimeSpeedProbeRow = typeof uptimeSpeedProbes.$inferSelect
|
||
export type UptimeSpeedTestRunRow = typeof uptimeSpeedTestRuns.$inferSelect
|
||
export type InternetPathSettingsRow = typeof internetPathSettings.$inferSelect
|
||
export type InternetPathSnapshotRow = typeof internetPathSnapshots.$inferSelect
|
||
export type SchedulerRunRow = typeof schedulerRuns.$inferSelect
|
||
export type EventRow = typeof events.$inferSelect
|
||
export type EvobgpSettingsRow = typeof evobgpSettings.$inferSelect
|
||
export type AlertTelegramSettingsRow = typeof alertTelegramSettings.$inferSelect
|
||
export type AcmeSettingsRow = typeof acmeSettings.$inferSelect
|
||
export type CertificateIssueJobRow = typeof certificateIssueJobs.$inferSelect
|
||
export type AlertGroupRow = typeof alertGroups.$inferSelect
|
||
export type AlertRuleRow = typeof alertRules.$inferSelect
|
||
export type AlertRuleTargetRow = typeof alertRuleTargets.$inferSelect
|
||
export type AlertEngineStateRow = typeof alertEngineState.$inferSelect
|
||
export type AlertDestinationRow = typeof alertDestinations.$inferSelect
|
||
export type AlertHistoryRow = typeof alertHistory.$inferSelect
|
||
export type AlertOutboxRow = typeof alertOutbox.$inferSelect
|
||
export type AlertEngineCursorRow = typeof alertEngineCursor.$inferSelect
|