Init commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import Database from "better-sqlite3"
|
||||
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,
|
||||
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,
|
||||
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);
|
||||
`)
|
||||
|
||||
// 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 ''`)
|
||||
}
|
||||
|
||||
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);
|
||||
`)
|
||||
|
||||
export const db = drizzle(sqlite, { schema })
|
||||
@@ -0,0 +1,200 @@
|
||||
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),
|
||||
|
||||
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'))`),
|
||||
})
|
||||
|
||||
// ── 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(),
|
||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||
intervalSec: integer("interval_sec").notNull().default(15),
|
||||
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),
|
||||
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(""),
|
||||
})
|
||||
|
||||
// ── 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 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
|
||||
Reference in New Issue
Block a user