Added internet path settings and snapshot management to the application. This includes new database tables for internet path settings and snapshots, API routes for fetching and managing internet path data, and integration into the dashboard and data collection pages. Enhanced the scheduler to support internet path jobs, ensuring regular data collection and updates. Updated relevant types and interfaces to accommodate the new functionality.
561 lines
21 KiB
TypeScript
561 lines
21 KiB
TypeScript
import Database from "better-sqlite3"
|
|
|
|
type SqliteHandle = InstanceType<typeof Database>
|
|
import { drizzle } from "drizzle-orm/better-sqlite3"
|
|
import { env } from "../config.js"
|
|
import * as schema from "./schema.js"
|
|
|
|
const sqlite = new Database(env.DATABASE_PATH)
|
|
|
|
// WAL mode for better concurrent read performance
|
|
sqlite.pragma("journal_mode = WAL")
|
|
sqlite.pragma("foreign_keys = ON")
|
|
sqlite.exec(`
|
|
CREATE TABLE IF NOT EXISTS filter_rules (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
server_id INTEGER NOT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
community TEXT NOT NULL,
|
|
community_name TEXT,
|
|
action TEXT NOT NULL DEFAULT 'route',
|
|
gateway TEXT NOT NULL DEFAULT '',
|
|
gateway_tunnel_id TEXT NOT NULL DEFAULT '',
|
|
description TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_filter_rules_server_sort
|
|
ON filter_rules(server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS recursive_routes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
server_id INTEGER NOT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
dst_address TEXT NOT NULL,
|
|
gateway TEXT NOT NULL,
|
|
distance INTEGER NOT NULL DEFAULT 1,
|
|
scope INTEGER,
|
|
target_scope INTEGER,
|
|
routing_table TEXT NOT NULL DEFAULT 'main',
|
|
check_gateway TEXT NOT NULL DEFAULT '',
|
|
country TEXT NOT NULL DEFAULT '',
|
|
comment TEXT NOT NULL DEFAULT '',
|
|
disabled INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_recursive_routes_server_sort
|
|
ON recursive_routes(server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS traffic_settings (
|
|
id INTEGER PRIMARY KEY,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
interval_sec INTEGER NOT NULL DEFAULT 30,
|
|
retention_days INTEGER NOT NULL DEFAULT 14,
|
|
last_collected_at TEXT,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS traffic_samples (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
server_id INTEGER NOT NULL,
|
|
interface_name TEXT NOT NULL,
|
|
sampled_at TEXT NOT NULL,
|
|
rx_bytes INTEGER NOT NULL DEFAULT 0,
|
|
tx_bytes INTEGER NOT NULL DEFAULT 0,
|
|
rx_bps INTEGER NOT NULL DEFAULT 0,
|
|
tx_bps INTEGER NOT NULL DEFAULT 0,
|
|
running INTEGER NOT NULL DEFAULT 0,
|
|
disabled INTEGER NOT NULL DEFAULT 0,
|
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_traffic_samples_server_time
|
|
ON traffic_samples(server_id, sampled_at);
|
|
CREATE INDEX IF NOT EXISTS idx_traffic_samples_server_iface_time
|
|
ON traffic_samples(server_id, interface_name, sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_settings (
|
|
id INTEGER PRIMARY KEY,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
interval_sec INTEGER NOT NULL DEFAULT 15,
|
|
probe_interval_sec INTEGER NOT NULL DEFAULT 15,
|
|
speed_interval_sec INTEGER NOT NULL DEFAULT 60,
|
|
retention_days INTEGER NOT NULL DEFAULT 14,
|
|
last_collected_at TEXT,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_probes (
|
|
id TEXT PRIMARY KEY,
|
|
src_server_id INTEGER NOT NULL,
|
|
src_interface TEXT NOT NULL DEFAULT '',
|
|
name TEXT NOT NULL,
|
|
target TEXT NOT NULL,
|
|
probe_filter TEXT NOT NULL DEFAULT '—',
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
show_on_dashboard INTEGER NOT NULL DEFAULT 0,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (src_server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_probes_server_sort
|
|
ON uptime_probes(src_server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_probe_samples (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
probe_id TEXT NOT NULL,
|
|
sampled_at TEXT NOT NULL,
|
|
rtt_ms INTEGER,
|
|
loss_pct INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'down',
|
|
FOREIGN KEY (probe_id) REFERENCES uptime_probes(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_probe_samples_probe_time
|
|
ON uptime_probe_samples(probe_id, sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_resource_samples (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
server_id INTEGER NOT NULL,
|
|
sampled_at TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'offline',
|
|
cpu_load INTEGER NOT NULL DEFAULT 0,
|
|
free_memory INTEGER NOT NULL DEFAULT 0,
|
|
total_memory INTEGER NOT NULL DEFAULT 0,
|
|
free_hdd_space INTEGER NOT NULL DEFAULT 0,
|
|
total_hdd_space INTEGER NOT NULL DEFAULT 0,
|
|
uptime_seconds INTEGER NOT NULL DEFAULT 0,
|
|
board_name TEXT NOT NULL DEFAULT '',
|
|
ros_version TEXT NOT NULL DEFAULT '',
|
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_resource_samples_server_time
|
|
ON uptime_resource_samples(server_id, sampled_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_speed_probes (
|
|
id TEXT PRIMARY KEY,
|
|
src_server_id INTEGER NOT NULL,
|
|
dst_server_id INTEGER NOT NULL,
|
|
src_interface TEXT NOT NULL DEFAULT '',
|
|
dst_interface TEXT NOT NULL DEFAULT '',
|
|
protocol TEXT NOT NULL DEFAULT 'tcp',
|
|
direction TEXT NOT NULL DEFAULT 'both',
|
|
duration_sec INTEGER NOT NULL DEFAULT 10,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
last_run_at TEXT,
|
|
last_tx_avg_mbps REAL,
|
|
last_rx_avg_mbps REAL,
|
|
last_status TEXT,
|
|
last_error TEXT,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (src_server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (dst_server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_speed_probes_src_sort
|
|
ON uptime_speed_probes(src_server_id, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS uptime_speed_test_runs (
|
|
id TEXT PRIMARY KEY,
|
|
probe_id TEXT,
|
|
src_server_id INTEGER NOT NULL,
|
|
dst_server_id INTEGER NOT NULL,
|
|
src_interface TEXT NOT NULL DEFAULT '',
|
|
dst_interface TEXT NOT NULL DEFAULT '',
|
|
src_address TEXT,
|
|
dst_address TEXT,
|
|
src_interface_address TEXT,
|
|
dst_interface_address TEXT,
|
|
protocol TEXT NOT NULL DEFAULT 'tcp',
|
|
direction TEXT NOT NULL DEFAULT 'both',
|
|
duration_sec INTEGER NOT NULL DEFAULT 10,
|
|
tx_avg_mbps REAL,
|
|
rx_avg_mbps REAL,
|
|
ping_rtt_ms INTEGER,
|
|
ping_loss_pct INTEGER,
|
|
ping_error TEXT,
|
|
status TEXT NOT NULL DEFAULT 'done',
|
|
error TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (src_server_id) REFERENCES servers(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (dst_server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_uptime_speed_test_runs_created_at
|
|
ON uptime_speed_test_runs(created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS scheduler_runs (
|
|
id TEXT PRIMARY KEY,
|
|
job_key TEXT NOT NULL,
|
|
started_at TEXT NOT NULL,
|
|
finished_at TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
error TEXT,
|
|
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
result_json TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_scheduler_runs_job_time
|
|
ON scheduler_runs(job_key, finished_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS internet_path_settings (
|
|
id INTEGER PRIMARY KEY,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
interval_sec INTEGER NOT NULL DEFAULT 300,
|
|
retention_days INTEGER NOT NULL DEFAULT 14,
|
|
last_collected_at TEXT,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS internet_path_snapshots (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
sampled_at TEXT NOT NULL,
|
|
payload_json TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_internet_path_snapshots_sampled
|
|
ON internet_path_snapshots(sampled_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id TEXT PRIMARY KEY,
|
|
created_at TEXT NOT NULL,
|
|
level TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
source_module TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
entity_type TEXT,
|
|
entity_id TEXT,
|
|
payload_json TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_events_level_created_at ON events(level, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_events_source_created_at ON events(source_module, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_events_event_type_created_at ON events(event_type, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS servers_api_ping_settings (
|
|
id INTEGER PRIMARY KEY,
|
|
enabled INTEGER NOT NULL DEFAULT 0,
|
|
interval_sec INTEGER NOT NULL DEFAULT 120,
|
|
last_collected_at TEXT,
|
|
last_duration_ms INTEGER,
|
|
last_error TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS servers_rest_ping_samples (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
server_id INTEGER NOT NULL,
|
|
sampled_at TEXT NOT NULL,
|
|
ok INTEGER NOT NULL DEFAULT 0,
|
|
latency_ms INTEGER,
|
|
error TEXT,
|
|
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_servers_rest_ping_samples_server_id
|
|
ON servers_rest_ping_samples(server_id, id DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_gre_tunnel_samples (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
sampled_at TEXT NOT NULL,
|
|
target_label TEXT NOT NULL,
|
|
status TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_gre_tunnel_samples_label_id
|
|
ON alert_gre_tunnel_samples(target_label, id DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_bgp_peer_samples (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
sampled_at TEXT NOT NULL,
|
|
peer_key TEXT NOT NULL,
|
|
state TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_bgp_peer_samples_key_id
|
|
ON alert_bgp_peer_samples(peer_key, id DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS evobgp_settings (
|
|
id INTEGER PRIMARY KEY,
|
|
base_url TEXT NOT NULL DEFAULT '',
|
|
api_key TEXT NOT NULL DEFAULT '',
|
|
enabled INTEGER NOT NULL DEFAULT 0,
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_telegram_settings (
|
|
id INTEGER PRIMARY KEY,
|
|
bot_token TEXT NOT NULL DEFAULT '',
|
|
chat_id TEXT NOT NULL DEFAULT '',
|
|
message_thread_id INTEGER,
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
target TEXT NOT NULL,
|
|
condition TEXT NOT NULL,
|
|
severity TEXT NOT NULL,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
cooldown TEXT NOT NULL DEFAULT '5м',
|
|
rule_chat_id TEXT NOT NULL DEFAULT '',
|
|
recovery_mode TEXT NOT NULL DEFAULT 'always',
|
|
recovery_stability_sec INTEGER,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_history (
|
|
id TEXT PRIMARY KEY,
|
|
rule_id TEXT,
|
|
rule_name TEXT NOT NULL,
|
|
severity TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
sent_ok INTEGER NOT NULL DEFAULT 1,
|
|
fired_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_history_fired_at ON alert_history(fired_at);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_history_rule_id ON alert_history(rule_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_groups (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
combine_mode TEXT NOT NULL DEFAULT 'any',
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
cooldown_override TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_rule_targets (
|
|
id TEXT PRIMARY KEY,
|
|
rule_id TEXT NOT NULL,
|
|
target TEXT NOT NULL,
|
|
sort_index INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_rule_targets_rule ON alert_rule_targets(rule_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_rule_conditions (
|
|
id TEXT PRIMARY KEY,
|
|
rule_id TEXT NOT NULL,
|
|
condition_line TEXT NOT NULL,
|
|
sort_index INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_rule_conditions_rule ON alert_rule_conditions(rule_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_state (
|
|
scope_key TEXT PRIMARY KEY,
|
|
last_fired_at TEXT NOT NULL DEFAULT '',
|
|
last_payload_hash TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_prev_live (
|
|
kind TEXT PRIMARY KEY,
|
|
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_confirm_pending (
|
|
rule_id TEXT PRIMARY KEY,
|
|
payload_hash TEXT NOT NULL,
|
|
since_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_destinations (
|
|
id TEXT PRIMARY KEY,
|
|
kind TEXT NOT NULL DEFAULT 'telegram',
|
|
label TEXT NOT NULL DEFAULT '',
|
|
telegram_chat_id TEXT NOT NULL DEFAULT '',
|
|
message_thread_id INTEGER,
|
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_outbox (
|
|
id TEXT PRIMARY KEY,
|
|
dedupe_key TEXT NOT NULL,
|
|
channel TEXT NOT NULL DEFAULT 'telegram',
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
max_retries INTEGER NOT NULL DEFAULT 3,
|
|
next_attempt_at TEXT NOT NULL,
|
|
payload_json TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
sent_at TEXT,
|
|
last_error TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_outbox_status_next_attempt
|
|
ON alert_outbox(status, next_attempt_at);
|
|
CREATE INDEX IF NOT EXISTS idx_alert_outbox_dedupe
|
|
ON alert_outbox(dedupe_key);
|
|
|
|
CREATE TABLE IF NOT EXISTS alert_engine_cursor (
|
|
id INTEGER PRIMARY KEY,
|
|
last_source_finished_at TEXT,
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
`)
|
|
|
|
// Lightweight schema evolution for existing databases without migrations
|
|
const recursiveCols = sqlite.prepare(`PRAGMA table_info('recursive_routes')`).all() as Array<{ name?: string }>
|
|
const hasCountryColumn = recursiveCols.some((c) => c.name === "country")
|
|
if (!hasCountryColumn) {
|
|
sqlite.exec(`ALTER TABLE recursive_routes ADD COLUMN country TEXT NOT NULL DEFAULT ''`)
|
|
}
|
|
|
|
const uptimeProbeCols = sqlite.prepare(`PRAGMA table_info('uptime_probes')`).all() as Array<{ name?: string }>
|
|
const hasSrcInterfaceColumn = uptimeProbeCols.some((c) => c.name === "src_interface")
|
|
if (!hasSrcInterfaceColumn) {
|
|
sqlite.exec(`ALTER TABLE uptime_probes ADD COLUMN src_interface TEXT NOT NULL DEFAULT ''`)
|
|
}
|
|
const hasShowOnDashboardColumn = uptimeProbeCols.some((c) => c.name === "show_on_dashboard")
|
|
if (!hasShowOnDashboardColumn) {
|
|
sqlite.exec(`ALTER TABLE uptime_probes ADD COLUMN show_on_dashboard INTEGER NOT NULL DEFAULT 0`)
|
|
}
|
|
const hasProbeIntervalSecColumn = uptimeProbeCols.some((c) => c.name === "interval_sec")
|
|
if (!hasProbeIntervalSecColumn) {
|
|
sqlite.exec(`ALTER TABLE uptime_probes ADD COLUMN interval_sec INTEGER NOT NULL DEFAULT 0`)
|
|
}
|
|
|
|
const uptimeSettingsCols = sqlite.prepare(`PRAGMA table_info('uptime_settings')`).all() as Array<{ name?: string }>
|
|
const hasProbeIntervalColumn = uptimeSettingsCols.some((c) => c.name === "probe_interval_sec")
|
|
if (!hasProbeIntervalColumn) {
|
|
sqlite.exec(`ALTER TABLE uptime_settings ADD COLUMN probe_interval_sec INTEGER NOT NULL DEFAULT 15`)
|
|
}
|
|
const hasSpeedIntervalColumn = uptimeSettingsCols.some((c) => c.name === "speed_interval_sec")
|
|
if (!hasSpeedIntervalColumn) {
|
|
sqlite.exec(`ALTER TABLE uptime_settings ADD COLUMN speed_interval_sec INTEGER NOT NULL DEFAULT 60`)
|
|
}
|
|
|
|
const hasUptimeJobFlags = uptimeSettingsCols.some((c) => c.name === "resources_enabled")
|
|
if (!hasUptimeJobFlags) {
|
|
sqlite.exec(`ALTER TABLE uptime_settings ADD COLUMN resources_enabled INTEGER NOT NULL DEFAULT 1`)
|
|
sqlite.exec(`ALTER TABLE uptime_settings ADD COLUMN ping_enabled INTEGER NOT NULL DEFAULT 1`)
|
|
sqlite.exec(`ALTER TABLE uptime_settings ADD COLUMN speed_enabled INTEGER NOT NULL DEFAULT 1`)
|
|
sqlite.exec(
|
|
`UPDATE uptime_settings SET resources_enabled = enabled, ping_enabled = enabled, speed_enabled = enabled WHERE id = 1`,
|
|
)
|
|
}
|
|
|
|
const uptimeSpeedProbeCols = sqlite.prepare(`PRAGMA table_info('uptime_speed_probes')`).all() as Array<{ name?: string }>
|
|
const ensureSpeedProbeCol = (name: string, ddl: string) => {
|
|
if (!uptimeSpeedProbeCols.some((c) => c.name === name)) sqlite.exec(ddl)
|
|
}
|
|
ensureSpeedProbeCol("last_run_at", `ALTER TABLE uptime_speed_probes ADD COLUMN last_run_at TEXT`)
|
|
ensureSpeedProbeCol("last_tx_avg_mbps", `ALTER TABLE uptime_speed_probes ADD COLUMN last_tx_avg_mbps REAL`)
|
|
ensureSpeedProbeCol("last_rx_avg_mbps", `ALTER TABLE uptime_speed_probes ADD COLUMN last_rx_avg_mbps REAL`)
|
|
ensureSpeedProbeCol("last_status", `ALTER TABLE uptime_speed_probes ADD COLUMN last_status TEXT`)
|
|
ensureSpeedProbeCol("last_error", `ALTER TABLE uptime_speed_probes ADD COLUMN last_error TEXT`)
|
|
ensureSpeedProbeCol("last_ping_rtt_ms", `ALTER TABLE uptime_speed_probes ADD COLUMN last_ping_rtt_ms INTEGER`)
|
|
ensureSpeedProbeCol("last_ping_loss_pct", `ALTER TABLE uptime_speed_probes ADD COLUMN last_ping_loss_pct INTEGER`)
|
|
ensureSpeedProbeCol("last_ping_at", `ALTER TABLE uptime_speed_probes ADD COLUMN last_ping_at TEXT`)
|
|
ensureSpeedProbeCol("last_ping_error", `ALTER TABLE uptime_speed_probes ADD COLUMN last_ping_error TEXT`)
|
|
|
|
const schedulerRunCols = sqlite.prepare(`PRAGMA table_info('scheduler_runs')`).all() as Array<{ name?: string }>
|
|
if (!schedulerRunCols.some((c) => c.name === "result_json")) {
|
|
sqlite.exec(`ALTER TABLE scheduler_runs ADD COLUMN result_json TEXT`)
|
|
}
|
|
|
|
const serverCols = sqlite.prepare(`PRAGMA table_info('servers')`).all() as Array<{ name?: string }>
|
|
if (!serverCols.some((c) => c.name === "lan_subnet")) {
|
|
sqlite.exec(`ALTER TABLE servers ADD COLUMN lan_subnet TEXT NOT NULL DEFAULT ''`)
|
|
}
|
|
if (!serverCols.some((c) => c.name === "wan_uplinks")) {
|
|
sqlite.exec(`ALTER TABLE servers ADD COLUMN wan_uplinks TEXT NOT NULL DEFAULT '[]'`)
|
|
}
|
|
|
|
const alertTgCols = sqlite.prepare(`PRAGMA table_info('alert_telegram_settings')`).all() as Array<{ name?: string }>
|
|
if (!alertTgCols.some((c) => c.name === "message_thread_id")) {
|
|
sqlite.exec(`ALTER TABLE alert_telegram_settings ADD COLUMN message_thread_id INTEGER`)
|
|
}
|
|
|
|
const alertRulesCols = sqlite.prepare(`PRAGMA table_info('alert_rules')`).all() as Array<{ name?: string }>
|
|
if (!alertRulesCols.some((c) => c.name === "group_id")) {
|
|
sqlite.exec(`ALTER TABLE alert_rules ADD COLUMN group_id TEXT`)
|
|
}
|
|
if (!alertRulesCols.some((c) => c.name === "confirm_stability_sec")) {
|
|
sqlite.exec(`ALTER TABLE alert_rules ADD COLUMN confirm_stability_sec INTEGER`)
|
|
}
|
|
if (!alertRulesCols.some((c) => c.name === "recovery_mode")) {
|
|
sqlite.exec(`ALTER TABLE alert_rules ADD COLUMN recovery_mode TEXT NOT NULL DEFAULT 'always'`)
|
|
}
|
|
if (!alertRulesCols.some((c) => c.name === "recovery_stability_sec")) {
|
|
sqlite.exec(`ALTER TABLE alert_rules ADD COLUMN recovery_stability_sec INTEGER`)
|
|
}
|
|
|
|
const alertHistoryCols = sqlite.prepare(`PRAGMA table_info('alert_history')`).all() as Array<{ name?: string }>
|
|
if (!alertHistoryCols.some((c) => c.name === "group_id")) {
|
|
sqlite.exec(`ALTER TABLE alert_history ADD COLUMN group_id TEXT`)
|
|
}
|
|
|
|
/** Одна строка target на правило из legacy-колонки `alert_rules.target` */
|
|
sqlite.exec(`
|
|
INSERT OR IGNORE INTO alert_rule_targets (id, rule_id, target, sort_index)
|
|
SELECT 'rt-' || id || '-0', id, target, 0 FROM alert_rules
|
|
WHERE id NOT IN (SELECT rule_id FROM alert_rule_targets)
|
|
`)
|
|
|
|
/** Одна строка условия из legacy `alert_rules.condition` */
|
|
sqlite.exec(`
|
|
INSERT OR IGNORE INTO alert_rule_conditions (id, rule_id, condition_line, sort_index)
|
|
SELECT 'rc-' || id || '-0', id, condition, 0 FROM alert_rules
|
|
WHERE id NOT IN (SELECT rule_id FROM alert_rule_conditions)
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO traffic_settings (id, enabled, interval_sec, retention_days)
|
|
SELECT 1, 1, 30, 14
|
|
WHERE NOT EXISTS (SELECT 1 FROM traffic_settings WHERE id = 1);
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO uptime_settings (id, enabled, interval_sec, retention_days)
|
|
SELECT 1, 1, 15, 14
|
|
WHERE NOT EXISTS (SELECT 1 FROM uptime_settings WHERE id = 1);
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO evobgp_settings (id, base_url, api_key, enabled)
|
|
SELECT 1, '', '', 0
|
|
WHERE NOT EXISTS (SELECT 1 FROM evobgp_settings WHERE id = 1);
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO servers_api_ping_settings (id, enabled, interval_sec)
|
|
SELECT 1, 0, 120
|
|
WHERE NOT EXISTS (SELECT 1 FROM servers_api_ping_settings WHERE id = 1);
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO internet_path_settings (id, enabled, interval_sec, retention_days)
|
|
SELECT 1, 1, 300, 14
|
|
WHERE NOT EXISTS (SELECT 1 FROM internet_path_settings WHERE id = 1);
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO alert_telegram_settings (id, bot_token, chat_id)
|
|
SELECT 1, '', ''
|
|
WHERE NOT EXISTS (SELECT 1 FROM alert_telegram_settings WHERE id = 1);
|
|
`)
|
|
|
|
sqlite.exec(`
|
|
INSERT INTO alert_engine_cursor (id, last_source_finished_at)
|
|
SELECT 1, NULL
|
|
WHERE NOT EXISTS (SELECT 1 FROM alert_engine_cursor WHERE id = 1);
|
|
`)
|
|
|
|
export const db = drizzle(sqlite, { schema })
|
|
|
|
/** Прямой доступ к better-sqlite3 для сложных read-only запросов (напр. /api/alerts). */
|
|
export const sqliteDatabase: SqliteHandle = sqlite
|