From 0f4d3a298625f1dd44c01e5b23d32548b90e311e Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sun, 5 Apr 2026 01:22:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E7=99=BB=E5=BD=95=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/js/app.js | 82 +++++++++------- server/db-integrity-check.js | 183 +++++++++++++++-------------------- server/index.js | 35 ++++--- 3 files changed, 144 insertions(+), 156 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index d9acdf7..8e9c457 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -362,43 +362,57 @@ function initGlobe() { if (!dom.globeContainer) return; - myGlobe = Globe() - (dom.globeContainer) - .globeImageUrl('//unpkg.com/three-globe/example/img/earth-dark.jpg') - .bumpImageUrl('//unpkg.com/three-globe/example/img/earth-topology.png') - .backgroundColor('rgba(0,0,0,0)') - .showAtmosphere(true) - .atmosphereColor('#6366f1') - .atmosphereDaylightAlpha(0.1) - .pointsData([]) - .pointColor(() => '#06b6d4') - .pointAltitude(0.05) - .pointRadius(0.8) - .pointsMerge(true) - .pointLabel(d => ` -
-
${escapeHtml(d.job)}
-
${escapeHtml(d.city || '')}, ${escapeHtml(d.countryName || d.country || '')}
-
- BW: ↓${formatBandwidth(d.netRx)} ↑${formatBandwidth(d.netTx)} + if (typeof Globe !== 'function') { + console.warn('[Globe] Globe.gl library not loaded. 3D visualization will be disabled.'); + dom.globeContainer.innerHTML = ` +
+ Globe.gl 库加载失败
请检查网络连接或刷新页面
-
- `); + `; + return; + } - // Resizing - const resizeObserver = new ResizeObserver(() => { - if (myGlobe) { - const width = dom.globeContainer.clientWidth; - const height = dom.globeContainer.clientHeight; - myGlobe.width(width).height(height); - } - }); - resizeObserver.observe(dom.globeContainer); + try { + myGlobe = Globe() + (dom.globeContainer) + .globeImageUrl('//unpkg.com/three-globe/example/img/earth-dark.jpg') + .bumpImageUrl('//unpkg.com/three-globe/example/img/earth-topology.png') + .backgroundColor('rgba(0,0,0,0)') + .showAtmosphere(true) + .atmosphereColor('#6366f1') + .atmosphereDaylightAlpha(0.1) + .pointsData([]) + .pointColor(() => '#06b6d4') + .pointAltitude(0.05) + .pointRadius(0.8) + .pointsMerge(true) + .pointLabel(d => ` +
+
${escapeHtml(d.job)}
+
${escapeHtml(d.city || '')}, ${escapeHtml(d.countryName || d.country || '')}
+
+ BW: ↓${formatBandwidth(d.netRx)} ↑${formatBandwidth(d.netTx)} +
+
+ `); - // Initial view - myGlobe.controls().autoRotate = true; - myGlobe.controls().autoRotateSpeed = 0.5; - myGlobe.controls().enableZoom = false; // Disable zoom to maintain dashboard feel + // Resizing + const resizeObserver = new ResizeObserver(() => { + if (myGlobe) { + const width = dom.globeContainer.clientWidth; + const height = dom.globeContainer.clientHeight; + myGlobe.width(width).height(height); + } + }); + resizeObserver.observe(dom.globeContainer); + + // Initial view + myGlobe.controls().autoRotate = true; + myGlobe.controls().autoRotateSpeed = 0.5; + myGlobe.controls().enableZoom = false; // Disable zoom to maintain dashboard feel + } catch (err) { + console.error('[Globe] Initialization failed:', err); + } } function updateGlobe(servers) { diff --git a/server/db-integrity-check.js b/server/db-integrity-check.js index 6d021d1..a30f0c8 100644 --- a/server/db-integrity-check.js +++ b/server/db-integrity-check.js @@ -18,16 +18,9 @@ const REQUIRED_TABLES = [ ]; 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"); @@ -36,110 +29,88 @@ async function checkAndFixDatabase() { 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.`); + 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.`); } } 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); - } + 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(' - Creating table "server_locations"...'); - await connection.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 - `); - - 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(); +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 '数据可视化展示大屏', + 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 db.query(` + INSERT IGNORE INTO site_settings (id, page_name, title, default_theme) + VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark') + `); + 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 '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; } } diff --git a/server/index.js b/server/index.js index 50745f2..d744c25 100644 --- a/server/index.js +++ b/server/index.js @@ -582,25 +582,28 @@ app.get('/api/metrics/overview', async (req, res) => { // --- Add Geo Information to Servers --- const geoServers = await Promise.all(overview.servers.map(async (server) => { - // The original IP is masked in the response from prometheusService.getOverviewMetrics - // But we can get it back from serverIdMap in prometheusService if we are on the same process - // Actually, prometheusService needs to provide a way to get the real IP back. - // Or we can just modify getOverviewMetrics to return the real IP for internal use. - - // Let's look at prometheusService.js's getServerIdMap logic const realInstance = prometheusService.resolveToken(server.instance); const cleanIp = realInstance.split(':')[0]; - // Attempt to get location - const location = await geoService.getLocation(cleanIp); - if (location) { - return { - ...server, - country: location.country, - countryName: location.country_name, - lat: location.latitude, - lng: location.longitude - }; + // Try to get from DB cache only (fast) + try { + const [rows] = await db.query('SELECT * FROM server_locations WHERE ip = ?', [cleanIp]); + if (rows.length > 0) { + const location = rows[0]; + return { + ...server, + country: location.country, + countryName: location.country_name, + city: location.city, + lat: location.latitude, + lng: location.longitude + }; + } else { + // Trigger background resolution for future requests + geoService.getLocation(cleanIp).catch(() => {}); + } + } catch (e) { + // DB error, skip geo for now } return server; }));