Add inline edit for custom routes (Pencil icon → inline form with all fields). Log route add/update/delete/import to the logs table (type: system) so operations appear in the Logbook. Fix loadCaddyRoutes() called without await after settings save, causing a race between the success message and route list.
171 lines
5.7 KiB
TypeScript
171 lines
5.7 KiB
TypeScript
import Database from 'better-sqlite3';
|
|
import path from 'path';
|
|
|
|
export const DB_FILE = path.join(process.cwd(), 'ghostgrid.db');
|
|
|
|
console.log(`[Database] Connecting to SQLite database at: ${DB_FILE}`);
|
|
const db = new Database(DB_FILE);
|
|
|
|
db.pragma('journal_mode = WAL');
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'User',
|
|
email TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS devices (
|
|
id TEXT PRIMARY KEY,
|
|
hostname TEXT NOT NULL,
|
|
ip TEXT NOT NULL,
|
|
location TEXT NOT NULL,
|
|
notes TEXT,
|
|
type TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
emergencySheet TEXT NOT NULL,
|
|
lastCheckedAt TEXT,
|
|
checkMkUrl TEXT NOT NULL DEFAULT '',
|
|
cmkHostname TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS labs (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
contactPerson TEXT NOT NULL,
|
|
location TEXT NOT NULL,
|
|
deviceIds TEXT NOT NULL,
|
|
topology TEXT NOT NULL,
|
|
semaphoreSetupTemplateId TEXT NOT NULL DEFAULT '',
|
|
semaphoreTeardownTemplateId TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS bookings (
|
|
id TEXT PRIMARY KEY,
|
|
labId TEXT NOT NULL,
|
|
userId TEXT NOT NULL,
|
|
startDateTime TEXT NOT NULL,
|
|
endDateTime TEXT NOT NULL,
|
|
notes TEXT,
|
|
status TEXT NOT NULL,
|
|
notified INTEGER NOT NULL DEFAULT 0,
|
|
emailSent INTEGER NOT NULL DEFAULT 0,
|
|
ansibleSetupTriggered INTEGER NOT NULL DEFAULT 0,
|
|
ansibleTeardownTriggered INTEGER NOT NULL DEFAULT 0,
|
|
ansibleSetupJobId TEXT NOT NULL DEFAULT '',
|
|
ansibleTeardownJobId TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS logs (
|
|
id TEXT PRIMARY KEY,
|
|
timestamp TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
deviceId TEXT,
|
|
userId TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS links (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
url TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
category TEXT NOT NULL DEFAULT '',
|
|
color TEXT NOT NULL DEFAULT 'emerald',
|
|
createdBy TEXT,
|
|
createdAt TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS caddy (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
hostname TEXT NOT NULL,
|
|
upstream TEXT NOT NULL,
|
|
tls INTEGER NOT NULL DEFAULT 1,
|
|
compress INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
`);
|
|
|
|
// Seed default settings — INSERT OR IGNORE writes a key only if it is absent.
|
|
const DEFAULT_SETTINGS: Record<string, string> = {
|
|
azure_enabled: 'false',
|
|
azure_client_id: '',
|
|
azure_tenant_id: '',
|
|
azure_client_secret: '',
|
|
azure_redirect_uri: '',
|
|
azure_allowed_group: '',
|
|
checkmk_enabled: 'false',
|
|
checkmk_api_url: '',
|
|
checkmk_api_user: 'automation',
|
|
checkmk_api_secret: '',
|
|
checkmk_sync_interval_ms: '60000',
|
|
semaphore_enabled: 'false',
|
|
semaphore_api_url: '',
|
|
semaphore_api_token: '',
|
|
semaphore_project_id: '',
|
|
caddy_enabled: 'false',
|
|
caddy_admin_url: 'http://localhost:2019',
|
|
};
|
|
|
|
const seedSetting = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
|
|
for (const [key, value] of Object.entries(DEFAULT_SETTINGS)) seedSetting.run(key, value);
|
|
|
|
export function getSetting(key: string): string {
|
|
const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
|
|
return row?.value ?? '';
|
|
}
|
|
|
|
export function setSetting(key: string, value: string): void {
|
|
db.prepare("INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now'))").run(key, value);
|
|
}
|
|
|
|
export function getAllSettings(): Record<string, string> {
|
|
const rows = db.prepare('SELECT key, value FROM settings').all() as { key: string; value: string }[];
|
|
return Object.fromEntries(rows.map(r => [r.key, r.value]));
|
|
}
|
|
|
|
export interface CaddyRoute {
|
|
id: number;
|
|
hostname: string;
|
|
upstream: string;
|
|
tls: number;
|
|
compress: number;
|
|
created_at: string;
|
|
}
|
|
|
|
export function getCaddyRoutes(): CaddyRoute[] {
|
|
return db.prepare('SELECT * FROM caddy ORDER BY id ASC').all() as CaddyRoute[];
|
|
}
|
|
|
|
export function addCaddyRoute(hostname: string, upstream: string, tls: boolean, compress: boolean): CaddyRoute {
|
|
const { lastInsertRowid } = db.prepare(
|
|
'INSERT INTO caddy (hostname, upstream, tls, compress) VALUES (?, ?, ?, ?)'
|
|
).run(hostname, upstream, tls ? 1 : 0, compress ? 1 : 0);
|
|
return db.prepare('SELECT * FROM caddy WHERE id = ?').get(lastInsertRowid) as CaddyRoute;
|
|
}
|
|
|
|
export function updateCaddyRoute(id: number, hostname: string, upstream: string, tls: boolean, compress: boolean): CaddyRoute {
|
|
db.prepare('UPDATE caddy SET hostname = ?, upstream = ?, tls = ?, compress = ? WHERE id = ?')
|
|
.run(hostname, upstream, tls ? 1 : 0, compress ? 1 : 0, id);
|
|
return db.prepare('SELECT * FROM caddy WHERE id = ?').get(id) as CaddyRoute;
|
|
}
|
|
|
|
export function deleteCaddyRoute(id: number): void {
|
|
db.prepare('DELETE FROM caddy WHERE id = ?').run(id);
|
|
}
|
|
|
|
export function getCaddyRouteById(id: number): CaddyRoute | undefined {
|
|
return db.prepare('SELECT * FROM caddy WHERE id = ?').get(id) as CaddyRoute | undefined;
|
|
}
|
|
|
|
export default db;
|