添加数据库修复脚本
This commit is contained in:
130
server/db-integrity-check.js
Normal file
130
server/db-integrity-check.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 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'
|
||||
];
|
||||
|
||||
async function checkAndFixDatabase() {
|
||||
// Only run if .env is already configured
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
if (!fs.existsSync(envPath)) return;
|
||||
|
||||
const dbHost = process.env.MYSQL_HOST || 'localhost';
|
||||
const dbUser = process.env.MYSQL_USER || 'root';
|
||||
const dbPass = process.env.MYSQL_PASSWORD || '';
|
||||
const dbPort = parseInt(process.env.MYSQL_PORT) || 3306;
|
||||
const dbName = process.env.MYSQL_DATABASE || 'display_wall';
|
||||
|
||||
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(', ')}`);
|
||||
await recreateDatabase(dbHost, dbPort, dbUser, dbPass, dbName);
|
||||
} else {
|
||||
// console.log(`[Database Integrity] ✅ All tables accounted for.`);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'ER_BAD_DB_ERROR') {
|
||||
console.log(`[Database Integrity] ⚠️ Database "${dbName}" does not exist.`);
|
||||
await recreateDatabase(dbHost, dbPort, dbUser, dbPass, dbName);
|
||||
} else {
|
||||
console.error('[Database Integrity] ❌ Error checking integrity:', err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function recreateDatabase(host, port, user, password, dbName) {
|
||||
console.log(`[Database Integrity] 🔄 Re-initializing database "${dbName}"...`);
|
||||
|
||||
let connection;
|
||||
try {
|
||||
connection = await mysql.createConnection({ host, port, user, password });
|
||||
|
||||
// Drop and create database
|
||||
await connection.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
|
||||
await connection.query(`CREATE DATABASE \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
||||
await connection.query(`USE \`${dbName}\``);
|
||||
|
||||
// Recreate all tables
|
||||
console.log(' - Creating table "users"...');
|
||||
await connection.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
|
||||
`);
|
||||
|
||||
console.log(' - Creating table "prometheus_sources"...');
|
||||
await connection.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
|
||||
`);
|
||||
|
||||
console.log(' - Creating table "site_settings"...');
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
id INT PRIMARY KEY DEFAULT 1,
|
||||
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||
logo_url TEXT,
|
||||
default_theme VARCHAR(20) DEFAULT 'dark',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
await connection.query(`
|
||||
INSERT INTO site_settings (id, page_name, title, default_theme)
|
||||
VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark')
|
||||
`);
|
||||
|
||||
console.log(' - Creating table "traffic_stats"...');
|
||||
await connection.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
|
||||
`);
|
||||
|
||||
console.log(`[Database Integrity] ✅ Re-initialization complete.`);
|
||||
|
||||
// Refresh db pool in the main app context
|
||||
db.initPool();
|
||||
|
||||
} catch (err) {
|
||||
console.error('[Database Integrity] ❌ Critical failure during re-initialization:', err.message);
|
||||
} finally {
|
||||
if (connection) await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = checkAndFixDatabase;
|
||||
@@ -4,6 +4,7 @@ const cors = require('cors');
|
||||
const path = require('path');
|
||||
const db = require('./db');
|
||||
const prometheusService = require('./prometheus-service');
|
||||
const checkAndFixDatabase = require('./db-integrity-check');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@@ -157,6 +158,21 @@ app.post('/api/setup/init', async (req, res) => {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
id INT PRIMARY KEY DEFAULT 1,
|
||||
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||
logo_url TEXT,
|
||||
default_theme VARCHAR(20) DEFAULT 'dark',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
await connection.query(`
|
||||
INSERT IGNORE INTO site_settings (id, page_name, title, default_theme)
|
||||
VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark')
|
||||
`);
|
||||
|
||||
await connection.end();
|
||||
|
||||
// Save to .env
|
||||
@@ -348,6 +364,47 @@ app.post('/api/sources/test', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== Site Settings ====================
|
||||
|
||||
// Get site settings
|
||||
app.get('/api/settings', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.query('SELECT * FROM site_settings WHERE id = 1');
|
||||
if (rows.length === 0) {
|
||||
return res.json({
|
||||
page_name: '数据可视化展示大屏',
|
||||
title: '数据可视化展示大屏',
|
||||
logo_url: null
|
||||
});
|
||||
}
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
console.error('Error fetching settings:', err);
|
||||
res.status(500).json({ error: 'Failed to fetch settings' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update site settings
|
||||
app.post('/api/settings', requireAuth, async (req, res) => {
|
||||
const { page_name, title, logo_url, default_theme } = req.body;
|
||||
try {
|
||||
await db.query(
|
||||
`INSERT INTO site_settings (id, page_name, title, logo_url, default_theme)
|
||||
VALUES (1, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
page_name = VALUES(page_name),
|
||||
title = VALUES(title),
|
||||
logo_url = VALUES(logo_url),
|
||||
default_theme = VALUES(default_theme)`,
|
||||
[page_name, title, logo_url, default_theme]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error('Error updating settings:', err);
|
||||
res.status(500).json({ error: 'Failed to update settings' });
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== Metrics Aggregation ====================
|
||||
|
||||
// Get all aggregated metrics from all Prometheus sources
|
||||
@@ -597,29 +654,8 @@ async function recordTrafficStats() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if need to create traffic_stats table if db already initialized
|
||||
async function ensureTrafficTable() {
|
||||
if (!isDbInitialized) return;
|
||||
try {
|
||||
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
|
||||
`);
|
||||
// Add columns if missing for existing tables
|
||||
try { await db.query('ALTER TABLE traffic_stats ADD COLUMN rx_bandwidth DOUBLE DEFAULT 0'); } catch(e) {}
|
||||
try { await db.query('ALTER TABLE traffic_stats ADD COLUMN tx_bandwidth DOUBLE DEFAULT 0'); } catch(e) {}
|
||||
try { await db.query('ALTER TABLE traffic_stats ADD UNIQUE INDEX (timestamp)'); } catch(e) {}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
ensureTrafficTable().then(() => {
|
||||
// Check and fix database integrity on startup
|
||||
checkAndFixDatabase().then(() => {
|
||||
initialPreload();
|
||||
});
|
||||
|
||||
|
||||
@@ -49,6 +49,24 @@ async function initDatabase() {
|
||||
`);
|
||||
console.log(' ✅ Table "prometheus_sources" ready');
|
||||
|
||||
// Create site_settings table
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
id INT PRIMARY KEY DEFAULT 1,
|
||||
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||
logo_url TEXT,
|
||||
default_theme VARCHAR(20) DEFAULT 'dark',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
// Insert default settings if not exists
|
||||
await connection.query(`
|
||||
INSERT IGNORE INTO site_settings (id, page_name, title, default_theme)
|
||||
VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark')
|
||||
`);
|
||||
console.log(' ✅ Table "site_settings" ready');
|
||||
|
||||
console.log('\n🎉 Database initialization complete!\n');
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user