196 lines
8.4 KiB
JavaScript
196 lines
8.4 KiB
JavaScript
/**
|
|
* Database Integrity Check
|
|
* Runs at startup to ensure all required tables exist.
|
|
* Recreates the database if any tables are missing.
|
|
*/
|
|
require('dotenv').config();
|
|
const mysql = require('mysql2/promise');
|
|
const db = require('./db');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const REQUIRED_TABLES = [
|
|
'users',
|
|
'prometheus_sources',
|
|
'site_settings',
|
|
'traffic_stats',
|
|
'server_locations',
|
|
'latency_routes'
|
|
];
|
|
|
|
async function checkAndFixDatabase() {
|
|
const envPath = path.join(__dirname, '..', '.env');
|
|
if (!fs.existsSync(envPath)) return;
|
|
|
|
try {
|
|
// Check tables
|
|
const [rows] = await db.query("SHOW TABLES");
|
|
const existingTables = rows.map(r => Object.values(r)[0]);
|
|
|
|
const missingTables = REQUIRED_TABLES.filter(t => !existingTables.includes(t));
|
|
|
|
if (missingTables.length > 0) {
|
|
console.log(`[Database Integrity] ⚠️ Missing tables: ${missingTables.join(', ')}. Creating them...`);
|
|
|
|
for (const table of missingTables) {
|
|
await createTable(table);
|
|
}
|
|
console.log(`[Database Integrity] ✅ Missing tables created.`);
|
|
}
|
|
|
|
// Check for is_server_source and type in prometheus_sources
|
|
const [promColumns] = await db.query("SHOW COLUMNS FROM prometheus_sources");
|
|
const promColumnNames = promColumns.map(c => c.Field);
|
|
|
|
if (!promColumnNames.includes('is_server_source')) {
|
|
console.log(`[Database Integrity] ⚠️ Missing column 'is_server_source' in 'prometheus_sources'. Adding it...`);
|
|
await db.query("ALTER TABLE prometheus_sources ADD COLUMN is_server_source TINYINT(1) DEFAULT 1 AFTER description");
|
|
console.log(`[Database Integrity] ✅ Column 'is_server_source' added.`);
|
|
}
|
|
|
|
if (!promColumnNames.includes('type')) {
|
|
console.log(`[Database Integrity] ⚠️ Missing column 'type' in 'prometheus_sources'. Adding it...`);
|
|
await db.query("ALTER TABLE prometheus_sources ADD COLUMN type VARCHAR(50) DEFAULT 'prometheus' AFTER is_server_source");
|
|
console.log(`[Database Integrity] ✅ Column 'type' added.`);
|
|
}
|
|
|
|
// Check for new columns in site_settings
|
|
const [columns] = await db.query("SHOW COLUMNS FROM site_settings");
|
|
const columnNames = columns.map(c => c.Field);
|
|
const addColumn = async (columnName, sql) => {
|
|
if (!columnNames.includes(columnName)) {
|
|
try {
|
|
console.log(`[Database Integrity] ⚠️ Missing column '${columnName}' in 'site_settings'. Adding it...`);
|
|
await db.query(sql);
|
|
console.log(`[Database Integrity] ✅ Column '${columnName}' added.`);
|
|
} catch (err) {
|
|
console.error(`[Database Integrity] ❌ Failed to add column '${columnName}':`, err.message);
|
|
// Try without AFTER if it exists
|
|
if (sql.includes('AFTER')) {
|
|
try {
|
|
const fallback = sql.split(' AFTER')[0];
|
|
console.log(`[Database Integrity] 🔄 Retrying column '${columnName}' WITHOUT 'AFTER'...`);
|
|
await db.query(fallback);
|
|
console.log(`[Database Integrity] ✅ Column '${columnName}' added via fallback.`);
|
|
} catch (err2) {
|
|
console.error(`[Database Integrity] ❌ Fallback also failed:`, err2.message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
await addColumn('show_page_name', "ALTER TABLE site_settings ADD COLUMN show_page_name TINYINT(1) DEFAULT 1 AFTER page_name");
|
|
await addColumn('show_95_bandwidth', "ALTER TABLE site_settings ADD COLUMN show_95_bandwidth TINYINT(1) DEFAULT 0 AFTER default_theme");
|
|
await addColumn('p95_type', "ALTER TABLE site_settings ADD COLUMN p95_type VARCHAR(20) DEFAULT 'tx' AFTER show_95_bandwidth");
|
|
await addColumn('blackbox_source_id', "ALTER TABLE site_settings ADD COLUMN blackbox_source_id INT AFTER p95_type");
|
|
await addColumn('latency_source', "ALTER TABLE site_settings ADD COLUMN latency_source VARCHAR(100) AFTER blackbox_source_id");
|
|
await addColumn('latency_dest', "ALTER TABLE site_settings ADD COLUMN latency_dest VARCHAR(100) AFTER latency_source");
|
|
await addColumn('latency_target', "ALTER TABLE site_settings ADD COLUMN latency_target VARCHAR(255) AFTER latency_dest");
|
|
await addColumn('icp_filing', "ALTER TABLE site_settings ADD COLUMN icp_filing VARCHAR(255) AFTER latency_target");
|
|
await addColumn('ps_filing', "ALTER TABLE site_settings ADD COLUMN ps_filing VARCHAR(255) AFTER icp_filing");
|
|
await addColumn('logo_url_dark', "ALTER TABLE site_settings ADD COLUMN logo_url_dark TEXT AFTER logo_url");
|
|
await addColumn('favicon_url', "ALTER TABLE site_settings ADD COLUMN favicon_url TEXT AFTER logo_url_dark");
|
|
} catch (err) {
|
|
console.error('[Database Integrity] ❌ Overall site_settings check error:', err.message);
|
|
}
|
|
}
|
|
|
|
async function createTable(tableName) {
|
|
console.log(` - Creating table "${tableName}"...`);
|
|
switch (tableName) {
|
|
case 'users':
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
username VARCHAR(255) NOT NULL UNIQUE,
|
|
password VARCHAR(255) NOT NULL,
|
|
salt VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
break;
|
|
case 'prometheus_sources':
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS prometheus_sources (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
url VARCHAR(500) NOT NULL,
|
|
description TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
break;
|
|
case 'site_settings':
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS site_settings (
|
|
id INT PRIMARY KEY DEFAULT 1,
|
|
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
|
show_page_name TINYINT(1) DEFAULT 1,
|
|
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
|
logo_url TEXT,
|
|
logo_url_dark TEXT,
|
|
favicon_url TEXT,
|
|
default_theme VARCHAR(20) DEFAULT 'dark',
|
|
show_95_bandwidth TINYINT(1) DEFAULT 0,
|
|
p95_type VARCHAR(20) DEFAULT 'tx',
|
|
blackbox_source_id INT,
|
|
latency_source VARCHAR(100),
|
|
latency_dest VARCHAR(100),
|
|
latency_target VARCHAR(255),
|
|
icp_filing VARCHAR(255),
|
|
ps_filing VARCHAR(255),
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
await db.query(`
|
|
INSERT IGNORE INTO site_settings (id, page_name, title, default_theme, show_95_bandwidth)
|
|
VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark', 0)
|
|
`);
|
|
break;
|
|
case 'traffic_stats':
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS traffic_stats (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
rx_bytes BIGINT UNSIGNED DEFAULT 0,
|
|
tx_bytes BIGINT UNSIGNED DEFAULT 0,
|
|
rx_bandwidth DOUBLE DEFAULT 0,
|
|
tx_bandwidth DOUBLE DEFAULT 0,
|
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE INDEX (timestamp)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
break;
|
|
case 'latency_routes':
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS latency_routes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
source_id INT NOT NULL,
|
|
latency_source VARCHAR(100) NOT NULL,
|
|
latency_dest VARCHAR(100) NOT NULL,
|
|
latency_target VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
break;
|
|
case 'server_locations':
|
|
await db.query(`
|
|
CREATE TABLE IF NOT EXISTS server_locations (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
ip VARCHAR(255) NOT NULL UNIQUE,
|
|
country CHAR(2),
|
|
country_name VARCHAR(100),
|
|
region VARCHAR(100),
|
|
city VARCHAR(100),
|
|
latitude DOUBLE,
|
|
longitude DOUBLE,
|
|
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
module.exports = checkAndFixDatabase;
|