Files
MikrotikManager/backend/src/db/schema.ts
T

542 lines
29 KiB
TypeScript
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.
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