import { sql } from "drizzle-orm" import { bigint, boolean, date, doublePrecision, index, inet, integer, jsonb, pgTable, primaryKey, smallint, text, timestamp, uniqueIndex, } from "drizzle-orm/pg-core" function ts(name: string) { return timestamp(name, { withTimezone: true, mode: "string" as const }) } function idIdentity() { return bigint("id", { mode: "number" }).generatedByDefaultAsIdentity() } function idSingleton() { return bigint("id", { mode: "number" }).primaryKey() } function intPkRef() { return bigint("server_id", { mode: "number" }).notNull() } // ── servers ──────────────────────────────────────────────────────────────────── export const servers = pgTable("servers", { id: idIdentity().primaryKey(), 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: boolean("use_ssl").notNull().default(true), verifySsl: boolean("verify_ssl").notNull().default(false), 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: boolean("enabled").notNull().default(true), lanSubnet: text("lan_subnet").notNull().default(""), wanUplinks: jsonb("wan_uplinks").notNull().default(sql`'[]'::jsonb`), mgmtTunnelIp: text("mgmt_tunnel_ip").notNull().default(""), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const serverSnapshots = pgTable("server_snapshots", { id: idIdentity(), serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), polledAt: ts("polled_at").notNull(), status: text("status", { enum: ["online", "offline"] }).notNull(), latencyMs: doublePrecision("latency_ms"), rosVersion: text("ros_version"), boardName: text("board_name"), uptime: text("uptime"), cpuLoad: integer("cpu_load"), freeMemory: bigint("free_memory", { mode: "number" }), totalMemory: bigint("total_memory", { mode: "number" }), identityName: text("identity_name"), rawInterfaces: jsonb("raw_interfaces"), rawIpAddresses: jsonb("raw_ip_addresses"), }, (t) => [ primaryKey({ columns: [t.id, t.polledAt] }), index("idx_server_snapshots_server_time").on(t.serverId, t.polledAt), ]) export const filterRules = pgTable("filter_rules", { id: idIdentity().primaryKey(), serverId: intPkRef().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: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }, (t) => [ index("idx_filter_rules_server_sort").on(t.serverId, t.sortOrder), ]) export const configRevisions = pgTable("config_revisions", { id: text("id").primaryKey(), serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), section: text("section", { enum: ["filters", "recursive-routes", "firewall", "wireguard", "gre"] }).notNull(), source: text("source", { enum: ["apply", "rollback", "observed", "copy"] }).notNull(), fingerprint: text("fingerprint").notNull(), payload: jsonb("payload").$type().notNull().default(sql`'[]'::jsonb`), note: text("note"), createdAt: ts("created_at").notNull().defaultNow(), }, (t) => [ index("idx_config_revisions_server_section_created").on(t.serverId, t.section, t.createdAt), ]) export const recursiveRoutes = pgTable("recursive_routes", { id: idIdentity().primaryKey(), serverId: intPkRef().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: boolean("disabled").notNull().default(false), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }, (t) => [ index("idx_recursive_routes_server_sort").on(t.serverId, t.sortOrder), ]) export const trafficSettings = pgTable("traffic_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(true), intervalSec: integer("interval_sec").notNull().default(30), retentionDays: integer("retention_days").notNull().default(14), lastCollectedAt: ts("last_collected_at"), lastDurationMs: integer("last_duration_ms"), lastError: text("last_error"), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const serversApiPingSettings = pgTable("servers_api_ping_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(false), intervalSec: integer("interval_sec").notNull().default(120), lastCollectedAt: ts("last_collected_at"), lastDurationMs: integer("last_duration_ms"), lastError: text("last_error"), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const serversRestPingSamples = pgTable("servers_rest_ping_samples", { serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), sampledAt: ts("sampled_at").notNull(), ok: boolean("ok").notNull(), latencyMs: integer("latency_ms"), error: text("error"), }, (t) => [ primaryKey({ columns: [t.serverId, t.sampledAt] }), ]) export const trafficFlowSettings = pgTable("traffic_flow_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(false), collectorIp: text("collector_ip").notNull().default("10.255.254.1"), flowListenPort: integer("flow_listen_port").notNull().default(4739), wgListenPort: integer("wg_listen_port").notNull().default(51821), prefix: text("prefix").notNull().default("10.255.254.0/24"), publicEndpoint: text("public_endpoint").notNull().default(""), hostPublicKey: text("host_public_key").notNull().default(""), hostPrivateKey: text("host_private_key").notNull().default(""), hubServerId: bigint("hub_server_id", { mode: "number" }), retentionHours: integer("retention_hours").notNull().default(24), topN: integer("top_n").notNull().default(200), mapServiceMinSharePct: doublePrecision("map_service_min_share_pct").notNull().default(5), lastDatagramAt: ts("last_datagram_at"), lastExporterIp: text("last_exporter_ip"), lastError: text("last_error"), packetsReceived: bigint("packets_received", { mode: "number" }).notNull().default(0), peersJson: jsonb("peers_json").notNull().default(sql`'[]'::jsonb`), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const flowMinuteStats = pgTable("flow_minute_stats", { serverId: bigint("server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), bucketAt: ts("bucket_at").notNull(), bytes: bigint("bytes", { mode: "number" }).notNull().default(0), packets: bigint("packets", { mode: "number" }).notNull().default(0), uniqueSrc: integer("unique_src").notNull().default(0), uniqueDst: integer("unique_dst").notNull().default(0), conversations: integer("conversations").notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.bucketAt] }), ]) export const flowMinuteDims = pgTable("flow_minute_dims", { serverId: bigint("server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), bucketAt: ts("bucket_at").notNull(), dim: text("dim").notNull(), key: text("key").notNull(), bytes: bigint("bytes", { mode: "number" }).notNull().default(0), packets: bigint("packets", { mode: "number" }).notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.bucketAt, t.dim, t.key] }), index("idx_flow_minute_dims_time").on(t.bucketAt, t.dim), ]) export const flowDailyDims = pgTable("flow_daily_dims", { serverId: bigint("server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), day: date("day", { mode: "string" }).notNull(), dim: text("dim").notNull(), key: text("key").notNull(), bytes: bigint("bytes", { mode: "number" }).notNull().default(0), packets: bigint("packets", { mode: "number" }).notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.day, t.dim, t.key] }), index("idx_flow_daily_dims_day").on(t.day, t.dim), ]) /** Hour-grain traffic cube for statistics (≤48h). No secondary indexes. */ export const flowHourFacts = pgTable("flow_hour_facts", { serverId: bigint("server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), bucketAt: ts("bucket_at").notNull(), iface: text("iface").notNull(), country: text("country").notNull(), service: text("service").notNull(), asn: integer("asn").notNull(), bytes: bigint("bytes", { mode: "number" }).notNull().default(0), packets: bigint("packets", { mode: "number" }).notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.bucketAt, t.iface, t.country, t.service, t.asn] }), ]) /** Daily-grain traffic cube for statistics (long window). No secondary indexes. */ export const flowDailyFacts = pgTable("flow_daily_facts", { serverId: bigint("server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), day: date("day", { mode: "string" }).notNull(), iface: text("iface").notNull(), country: text("country").notNull(), service: text("service").notNull(), asn: integer("asn").notNull(), bytes: bigint("bytes", { mode: "number" }).notNull().default(0), packets: bigint("packets", { mode: "number" }).notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.day, t.iface, t.country, t.service, t.asn] }), ]) export const flowBuckets = pgTable("flow_buckets", { serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), bucketAt: ts("bucket_at").notNull(), src: inet("src").notNull(), dst: inet("dst").notNull(), proto: smallint("proto").notNull().default(0), srcPort: integer("src_port").notNull().default(0), dstPort: integer("dst_port").notNull().default(0), bytes: bigint("bytes", { mode: "number" }).notNull().default(0), packets: bigint("packets", { mode: "number" }).notNull().default(0), inIface: text("in_iface").notNull().default(""), outIface: text("out_iface").notNull().default(""), nextHop: inet("next_hop"), flowStartMs: bigint("flow_start_ms", { mode: "number" }).notNull().default(0), flowEndMs: bigint("flow_end_ms", { mode: "number" }).notNull().default(0), }, (t) => [ primaryKey({ name: "flow_buckets_pkey", columns: [t.serverId, t.bucketAt, t.src, t.dst, t.proto, t.srcPort, t.dstPort, t.inIface], }), index("idx_flow_buckets_server_time").on(t.serverId, t.bucketAt), ]) export const geoipSettings = pgTable("geoip_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(true), updateIntervalSec: integer("update_interval_sec").notNull().default(604800), lastCheckAt: ts("last_check_at"), lastSuccessAt: ts("last_success_at"), lastError: text("last_error"), countryBuildAt: ts("country_build_at"), asnBuildAt: ts("asn_build_at"), etagsJson: jsonb("etags_json").notNull().default(sql`'{}'::jsonb`), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const flowIpMeta = pgTable("flow_ip_meta", { prefix: text("prefix").primaryKey(), asn: integer("asn").notNull().default(0), country: text("country").notNull().default(""), lat: doublePrecision("lat"), lng: doublePrecision("lng"), holder: text("holder").notNull().default(""), ok: integer("ok").notNull().default(1), fetchedAt: ts("fetched_at").notNull(), }) export const flowAsnMeta = pgTable("flow_asn_meta", { asn: integer("asn").primaryKey(), holder: text("holder").notNull().default(""), fetchedAt: ts("fetched_at").notNull(), }) export const trafficSamples = pgTable("traffic_samples", { serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), interfaceName: text("interface_name").notNull(), peerPublicKey: text("peer_public_key").notNull().default(""), sampledAt: ts("sampled_at").notNull(), rxBytes: bigint("rx_bytes", { mode: "number" }).notNull().default(0), txBytes: bigint("tx_bytes", { mode: "number" }).notNull().default(0), rxBps: bigint("rx_bps", { mode: "number" }).notNull().default(0), txBps: bigint("tx_bps", { mode: "number" }).notNull().default(0), flags: smallint("flags").notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.sampledAt, t.interfaceName, t.peerPublicKey] }), index("idx_traffic_samples_server_iface_time").on(t.serverId, t.interfaceName, t.sampledAt), ]) export const uptimeSettings = pgTable("uptime_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(true), resourcesEnabled: boolean("resources_enabled").notNull().default(true), pingEnabled: boolean("ping_enabled").notNull().default(true), speedEnabled: boolean("speed_enabled").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: ts("last_collected_at"), lastDurationMs: integer("last_duration_ms"), lastError: text("last_error"), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const uptimeProbes = pgTable("uptime_probes", { id: text("id").primaryKey(), srcServerId: bigint("src_server_id", { mode: "number" }).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: boolean("enabled").notNull().default(true), intervalSec: integer("interval_sec").notNull().default(0), showOnDashboard: boolean("show_on_dashboard").notNull().default(false), sortOrder: integer("sort_order").notNull().default(0), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }, (t) => [ index("idx_uptime_probes_server_sort").on(t.srcServerId, t.sortOrder), ]) export const uptimeProbeSamples = pgTable("uptime_probe_samples", { probeId: text("probe_id").notNull().references(() => uptimeProbes.id, { onDelete: "cascade" }), sampledAt: ts("sampled_at").notNull(), rttMs: integer("rtt_ms"), lossPct: integer("loss_pct").notNull().default(0), status: text("status", { enum: ["up", "warn", "down"] }).notNull().default("down"), }, (t) => [ primaryKey({ columns: [t.probeId, t.sampledAt] }), ]) export const uptimeResourceSamples = pgTable("uptime_resource_samples", { serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), sampledAt: ts("sampled_at").notNull(), status: text("status", { enum: ["online", "offline"] }).notNull().default("offline"), cpuLoad: integer("cpu_load").notNull().default(0), freeMemory: bigint("free_memory", { mode: "number" }).notNull().default(0), totalMemory: bigint("total_memory", { mode: "number" }).notNull().default(0), freeHddSpace: bigint("free_hdd_space", { mode: "number" }).notNull().default(0), totalHddSpace: bigint("total_hdd_space", { mode: "number" }).notNull().default(0), uptimeSeconds: bigint("uptime_seconds", { mode: "number" }).notNull().default(0), }, (t) => [ primaryKey({ columns: [t.serverId, t.sampledAt] }), ]) export const uptimeSpeedProbes = pgTable("uptime_speed_probes", { id: text("id").primaryKey(), srcServerId: bigint("src_server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), dstServerId: bigint("dst_server_id", { mode: "number" }).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: boolean("enabled").notNull().default(true), lastRunAt: ts("last_run_at"), lastTxAvgMbps: doublePrecision("last_tx_avg_mbps"), lastRxAvgMbps: doublePrecision("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: ts("last_ping_at"), lastPingError: text("last_ping_error"), sortOrder: integer("sort_order").notNull().default(0), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }, (t) => [ index("idx_uptime_speed_probes_src_sort").on(t.srcServerId, t.sortOrder), ]) export const evobgpSettings = pgTable("evobgp_settings", { id: idSingleton(), baseUrl: text("base_url").notNull().default(""), apiKey: text("api_key").notNull().default(""), enabled: boolean("enabled").notNull().default(false), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const alertTelegramSettings = pgTable("alert_telegram_settings", { id: idSingleton(), botToken: text("bot_token").notNull().default(""), chatId: text("chat_id").notNull().default(""), messageThreadId: integer("message_thread_id"), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const acmeSettings = pgTable("acme_settings", { id: idSingleton(), 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: ts("updated_at").notNull().defaultNow(), }) export const certificateIssueJobs = pgTable("certificate_issue_jobs", { id: text("id").primaryKey(), status: text("status", { enum: ["queued", "running", "done", "failed"] }).notNull().default("queued"), step: text("step").notNull().default("queued"), source: text("source", { enum: ["manual", "scheduler"] }).notNull().default("manual"), serverId: bigint("server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), certName: text("cert_name").notNull(), domainNames: jsonb("domain_names").notNull(), keyType: text("key_type").notNull().default("rsa2048"), trustStore: text("trust_store").notNull().default("www,api"), requestedAt: ts("requested_at").notNull().defaultNow(), startedAt: ts("started_at"), finishedAt: ts("finished_at"), error: text("error"), }) export const certificateRenewSettings = pgTable("certificate_renew_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(true), intervalSec: integer("interval_sec").notNull().default(21600), renewBeforeDays: integer("renew_before_days").notNull().default(30), lastCollectedAt: ts("last_collected_at"), lastDurationMs: integer("last_duration_ms"), lastError: text("last_error"), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const backupScheduleSettings = pgTable("backup_schedule_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(true), frequency: text("frequency", { enum: ["daily", "weekly", "monthly"] }).notNull().default("daily"), hour: integer("hour").notNull().default(3), minute: integer("minute").notNull().default(0), weekDay: integer("week_day").notNull().default(0), monthDay: integer("month_day").notNull().default(1), keepCount: integer("keep_count").notNull().default(7), format: text("format", { enum: ["rsc", "backup"] }).notNull().default("rsc"), serverIdsJson: jsonb("server_ids_json").notNull().default(sql`'[]'::jsonb`), lastRunAt: ts("last_run_at"), lastDurationMs: integer("last_duration_ms"), lastError: text("last_error"), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const backupStorageSettings = pgTable("backup_storage_settings", { id: idSingleton(), provider: text("provider", { enum: ["local", "s3"] }).notNull().default("local"), s3Endpoint: text("s3_endpoint").notNull().default(""), s3Region: text("s3_region").notNull().default("us-east-1"), s3Bucket: text("s3_bucket").notNull().default(""), s3Prefix: text("s3_prefix").notNull().default("mikrotik"), s3AccessKeyId: text("s3_access_key_id").notNull().default(""), s3SecretAccessKey: text("s3_secret_access_key").notNull().default(""), s3ForcePathStyle: boolean("s3_force_path_style").notNull().default(true), keepLocalCopy: boolean("keep_local_copy").notNull().default(true), lastTestAt: ts("last_test_at"), lastTestError: text("last_test_error"), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const backupEntries = pgTable("backup_entries", { id: text("id").primaryKey(), serverId: bigint("server_id", { mode: "number" }) .references(() => servers.id, { onDelete: "set null" }), serverName: text("server_name").notNull(), filename: text("filename").notNull(), sizeBytes: bigint("size_bytes", { mode: "number" }).notNull(), kind: text("kind", { enum: ["manual", "auto"] }).notNull().default("manual"), notes: text("notes"), storage: text("storage", { enum: ["local", "s3", "both"] }).notNull().default("local"), s3Key: text("s3_key"), s3Etag: text("s3_etag"), uploadError: text("upload_error"), createdAt: ts("created_at").notNull(), }, (t) => [ uniqueIndex("idx_backup_entries_filename").on(t.filename), index("idx_backup_entries_server_created").on(t.serverId, t.createdAt), ]) export const alertGroups = pgTable("alert_groups", { id: text("id").primaryKey(), name: text("name").notNull(), combineMode: text("combine_mode", { enum: ["any", "all"] }).notNull().default("any"), enabled: boolean("enabled").notNull().default(true), cooldownOverride: text("cooldown_override", { enum: ["1м", "5м", "15м", "1ч", "4ч", "24ч"], }), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const alertRules = pgTable("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: text("target").notNull(), condition: text("condition").notNull(), severity: text("severity", { enum: ["critical", "warning", "info"] }).notNull(), enabled: boolean("enabled").notNull().default(true), cooldown: text("cooldown", { enum: ["1м", "5м", "15м", "1ч", "4ч", "24ч"], }).notNull().default("5м"), chatId: text("rule_chat_id").notNull().default(""), confirmStabilitySec: integer("confirm_stability_sec"), recoveryMode: text("recovery_mode", { enum: ["always", "never", "conditional"], }).notNull().default("always"), recoveryStabilitySec: integer("recovery_stability_sec"), groupId: text("group_id").references(() => alertGroups.id, { onDelete: "set null" }), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const alertRuleTargets = pgTable("alert_rule_targets", { id: text("id").primaryKey(), ruleId: text("rule_id").notNull().references(() => alertRules.id, { onDelete: "cascade" }), target: text("target").notNull(), sortIndex: integer("sort_index").notNull().default(0), }, (t) => [ index("idx_alert_rule_targets_rule").on(t.ruleId), ]) export const alertRuleConditions = pgTable("alert_rule_conditions", { id: text("id").primaryKey(), ruleId: text("rule_id").notNull().references(() => alertRules.id, { onDelete: "cascade" }), conditionLine: text("condition_line").notNull(), sortIndex: integer("sort_index").notNull().default(0), }, (t) => [ index("idx_alert_rule_conditions_rule").on(t.ruleId), ]) export const alertEngineState = pgTable("alert_engine_state", { scopeKey: text("scope_key").primaryKey(), lastFiredAt: text("last_fired_at").notNull().default(""), lastPayloadHash: text("last_payload_hash"), }) export const alertEnginePrevLive = pgTable("alert_engine_prev_live", { kind: text("kind", { enum: ["gre", "bgp"] }).primaryKey(), payloadJson: jsonb("payload_json").notNull().default(sql`'{}'::jsonb`), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const alertEngineConfirmPending = pgTable("alert_engine_confirm_pending", { ruleId: text("rule_id").primaryKey(), payloadHash: text("payload_hash").notNull(), sinceAt: ts("since_at").notNull(), }) export const alertHistory = pgTable("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: boolean("sent_ok").notNull().default(true), firedAt: ts("fired_at").notNull(), }, (t) => [ index("idx_alert_history_fired_at").on(t.firedAt), index("idx_alert_history_rule_id").on(t.ruleId), ]) export const alertOutbox = pgTable("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: ts("next_attempt_at").notNull(), payloadJson: jsonb("payload_json").notNull(), createdAt: ts("created_at").notNull().defaultNow(), sentAt: ts("sent_at"), lastError: text("last_error"), }, (t) => [ uniqueIndex("idx_alert_outbox_dedupe").on(t.dedupeKey), index("idx_alert_outbox_status_next_attempt").on(t.status, t.nextAttemptAt), ]) export const alertEngineCursor = pgTable("alert_engine_cursor", { id: idSingleton(), lastSourceFinishedAt: ts("last_source_finished_at"), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const schedulerRuns = pgTable("scheduler_runs", { id: text("id").primaryKey(), jobKey: text("job_key").notNull(), startedAt: ts("started_at").notNull(), finishedAt: ts("finished_at").notNull(), status: text("status", { enum: ["ok", "error"] }).notNull(), error: text("error"), durationMs: integer("duration_ms").notNull().default(0), resultJson: jsonb("result_json"), }, (t) => [ index("idx_scheduler_runs_job_time").on(t.jobKey, t.startedAt), ]) export const events = pgTable("events", { id: text("id").primaryKey(), createdAt: ts("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: jsonb("payload_json"), }, (t) => [ index("idx_events_created_at").on(t.createdAt), index("idx_events_level_created_at").on(t.level, t.createdAt), index("idx_events_source_created_at").on(t.sourceModule, t.createdAt), index("idx_events_event_type_created_at").on(t.eventType, t.createdAt), ]) export const uptimeSpeedTestRuns = pgTable("uptime_speed_test_runs", { id: text("id").primaryKey(), probeId: text("probe_id"), srcServerId: bigint("src_server_id", { mode: "number" }).notNull() .references(() => servers.id, { onDelete: "cascade" }), dstServerId: bigint("dst_server_id", { mode: "number" }).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: doublePrecision("tx_avg_mbps"), rxAvgMbps: doublePrecision("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: ts("created_at").notNull().defaultNow(), }, (t) => [ index("idx_uptime_speed_test_runs_created_at").on(t.createdAt), ]) export const internetPathSettings = pgTable("internet_path_settings", { id: idSingleton(), enabled: boolean("enabled").notNull().default(true), intervalSec: integer("interval_sec").notNull().default(300), retentionDays: integer("retention_days").notNull().default(14), lastCollectedAt: ts("last_collected_at"), lastDurationMs: integer("last_duration_ms"), lastError: text("last_error"), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const appUsers = pgTable("app_users", { id: text("id").primaryKey(), name: text("name").notNull().default(""), login: text("login").notNull().unique(), email: text("email").notNull().default(""), role: text("role", { enum: ["admin", "operator", "viewer"] }).notNull().default("viewer"), active: boolean("active").notNull().default(true), avatar: text("avatar").notNull().default(""), lastSeen: ts("last_seen"), sectionsJson: jsonb("sections_json").notNull().default(sql`'[]'::jsonb`), serversJson: jsonb("servers_json").notNull().default(sql`'[]'::jsonb`), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }) export const userInterfaceBindings = pgTable("user_interface_bindings", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => appUsers.id, { onDelete: "cascade" }), serverId: intPkRef().references(() => servers.id, { onDelete: "cascade" }), interfaceName: text("interface_name").notNull(), interfaceType: text("interface_type", { enum: ["ether", "gre", "wg", "other"] }) .notNull().default("other"), peerPublicKey: text("peer_public_key").notNull().default(""), peerName: text("peer_name").notNull().default(""), comment: text("comment").notNull().default(""), createdAt: ts("created_at").notNull().defaultNow(), updatedAt: ts("updated_at").notNull().defaultNow(), }, (t) => [ uniqueIndex("idx_user_iface_bind_server_name_peer").on(t.serverId, t.interfaceName, t.peerPublicKey), index("idx_user_iface_bind_user").on(t.userId), ]) export const internetPathSnapshots = pgTable("internet_path_snapshots", { id: idIdentity(), sampledAt: ts("sampled_at").notNull(), payloadJson: jsonb("payload_json").notNull(), }, (t) => [ primaryKey({ columns: [t.id, t.sampledAt] }), index("idx_internet_path_snapshots_sampled").on(t.sampledAt), ]) export const dataMigration = pgTable("data_migration", { id: integer("id").primaryKey(), sqliteImportedAt: ts("sqlite_imported_at"), sqlitePath: text("sqlite_path"), sqliteSha256: text("sqlite_sha256"), reportJson: jsonb("report_json"), }) 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 ConfigRevisionRow = typeof configRevisions.$inferSelect export type RecursiveRouteRow = typeof recursiveRoutes.$inferSelect export type TrafficSettingsRow = typeof trafficSettings.$inferSelect export type TrafficFlowSettingsRow = typeof trafficFlowSettings.$inferSelect export type FlowBucketRow = typeof flowBuckets.$inferSelect export type FlowIpMetaRow = typeof flowIpMeta.$inferSelect export type FlowAsnMetaRow = typeof flowAsnMeta.$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 CertificateRenewSettingsRow = typeof certificateRenewSettings.$inferSelect export type BackupScheduleSettingsRow = typeof backupScheduleSettings.$inferSelect export type BackupEntryRow = typeof backupEntries.$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 AlertHistoryRow = typeof alertHistory.$inferSelect export type AlertOutboxRow = typeof alertOutbox.$inferSelect export type AlertEngineCursorRow = typeof alertEngineCursor.$inferSelect export type AppUserRow = typeof appUsers.$inferSelect export type UserInterfaceBindingRow = typeof userInterfaceBindings.$inferSelect