修复无法正常登录的问题
This commit is contained in:
@@ -362,43 +362,57 @@
|
|||||||
function initGlobe() {
|
function initGlobe() {
|
||||||
if (!dom.globeContainer) return;
|
if (!dom.globeContainer) return;
|
||||||
|
|
||||||
myGlobe = Globe()
|
if (typeof Globe !== 'function') {
|
||||||
(dom.globeContainer)
|
console.warn('[Globe] Globe.gl library not loaded. 3D visualization will be disabled.');
|
||||||
.globeImageUrl('//unpkg.com/three-globe/example/img/earth-dark.jpg')
|
dom.globeContainer.innerHTML = `
|
||||||
.bumpImageUrl('//unpkg.com/three-globe/example/img/earth-topology.png')
|
<div style="height: 100%; display: flex; align-items: center; justify-content: center; color: var(--text-muted); font-size: 0.8rem; text-align: center; padding: 20px;">
|
||||||
.backgroundColor('rgba(0,0,0,0)')
|
Globe.gl 库加载失败<br>请检查网络连接或刷新页面
|
||||||
.showAtmosphere(true)
|
|
||||||
.atmosphereColor('#6366f1')
|
|
||||||
.atmosphereDaylightAlpha(0.1)
|
|
||||||
.pointsData([])
|
|
||||||
.pointColor(() => '#06b6d4')
|
|
||||||
.pointAltitude(0.05)
|
|
||||||
.pointRadius(0.8)
|
|
||||||
.pointsMerge(true)
|
|
||||||
.pointLabel(d => `
|
|
||||||
<div style="background: rgba(10, 14, 26, 0.9); padding: 8px 12px; border: 1px solid var(--accent-indigo); border-radius: 8px; backdrop-filter: blur(8px);">
|
|
||||||
<div style="font-weight: 700; color: #fff; margin-bottom: 4px;">${escapeHtml(d.job)}</div>
|
|
||||||
<div style="font-size: 0.75rem; color: var(--text-secondary);">${escapeHtml(d.city || '')}, ${escapeHtml(d.countryName || d.country || '')}</div>
|
|
||||||
<div style="font-size: 0.75rem; margin-top: 4px;">
|
|
||||||
<span style="color: var(--accent-indigo);">BW:</span> ↓${formatBandwidth(d.netRx)} ↑${formatBandwidth(d.netTx)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`;
|
||||||
`);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Resizing
|
try {
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
myGlobe = Globe()
|
||||||
if (myGlobe) {
|
(dom.globeContainer)
|
||||||
const width = dom.globeContainer.clientWidth;
|
.globeImageUrl('//unpkg.com/three-globe/example/img/earth-dark.jpg')
|
||||||
const height = dom.globeContainer.clientHeight;
|
.bumpImageUrl('//unpkg.com/three-globe/example/img/earth-topology.png')
|
||||||
myGlobe.width(width).height(height);
|
.backgroundColor('rgba(0,0,0,0)')
|
||||||
}
|
.showAtmosphere(true)
|
||||||
});
|
.atmosphereColor('#6366f1')
|
||||||
resizeObserver.observe(dom.globeContainer);
|
.atmosphereDaylightAlpha(0.1)
|
||||||
|
.pointsData([])
|
||||||
|
.pointColor(() => '#06b6d4')
|
||||||
|
.pointAltitude(0.05)
|
||||||
|
.pointRadius(0.8)
|
||||||
|
.pointsMerge(true)
|
||||||
|
.pointLabel(d => `
|
||||||
|
<div style="background: rgba(10, 14, 26, 0.9); padding: 8px 12px; border: 1px solid var(--accent-indigo); border-radius: 8px; backdrop-filter: blur(8px);">
|
||||||
|
<div style="font-weight: 700; color: #fff; margin-bottom: 4px;">${escapeHtml(d.job)}</div>
|
||||||
|
<div style="font-size: 0.75rem; color: var(--text-secondary);">${escapeHtml(d.city || '')}, ${escapeHtml(d.countryName || d.country || '')}</div>
|
||||||
|
<div style="font-size: 0.75rem; margin-top: 4px;">
|
||||||
|
<span style="color: var(--accent-indigo);">BW:</span> ↓${formatBandwidth(d.netRx)} ↑${formatBandwidth(d.netTx)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
// Initial view
|
// Resizing
|
||||||
myGlobe.controls().autoRotate = true;
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
myGlobe.controls().autoRotateSpeed = 0.5;
|
if (myGlobe) {
|
||||||
myGlobe.controls().enableZoom = false; // Disable zoom to maintain dashboard feel
|
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) {
|
function updateGlobe(servers) {
|
||||||
|
|||||||
@@ -18,16 +18,9 @@ const REQUIRED_TABLES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
async function checkAndFixDatabase() {
|
async function checkAndFixDatabase() {
|
||||||
// Only run if .env is already configured
|
|
||||||
const envPath = path.join(__dirname, '..', '.env');
|
const envPath = path.join(__dirname, '..', '.env');
|
||||||
if (!fs.existsSync(envPath)) return;
|
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 {
|
try {
|
||||||
// Check tables
|
// Check tables
|
||||||
const [rows] = await db.query("SHOW TABLES");
|
const [rows] = await db.query("SHOW TABLES");
|
||||||
@@ -36,110 +29,88 @@ async function checkAndFixDatabase() {
|
|||||||
const missingTables = REQUIRED_TABLES.filter(t => !existingTables.includes(t));
|
const missingTables = REQUIRED_TABLES.filter(t => !existingTables.includes(t));
|
||||||
|
|
||||||
if (missingTables.length > 0) {
|
if (missingTables.length > 0) {
|
||||||
console.log(`[Database Integrity] ⚠️ Missing tables: ${missingTables.join(', ')}`);
|
console.log(`[Database Integrity] ⚠️ Missing tables: ${missingTables.join(', ')}. Creating them...`);
|
||||||
await recreateDatabase(dbHost, dbPort, dbUser, dbPass, dbName);
|
|
||||||
} else {
|
for (const table of missingTables) {
|
||||||
// console.log(`[Database Integrity] ✅ All tables accounted for.`);
|
await createTable(table);
|
||||||
|
}
|
||||||
|
console.log(`[Database Integrity] ✅ Missing tables created.`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ER_BAD_DB_ERROR') {
|
console.error('[Database Integrity] ❌ Error checking integrity:', err.message);
|
||||||
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) {
|
async function createTable(tableName) {
|
||||||
console.log(`[Database Integrity] 🔄 Re-initializing database "${dbName}"...`);
|
console.log(` - Creating table "${tableName}"...`);
|
||||||
|
switch (tableName) {
|
||||||
let connection;
|
case 'users':
|
||||||
try {
|
await db.query(`
|
||||||
connection = await mysql.createConnection({ host, port, user, password });
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
// Drop and create database
|
username VARCHAR(255) NOT NULL UNIQUE,
|
||||||
await connection.query(`DROP DATABASE IF EXISTS \`${dbName}\``);
|
password VARCHAR(255) NOT NULL,
|
||||||
await connection.query(`CREATE DATABASE \`${dbName}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
salt VARCHAR(255) NOT NULL,
|
||||||
await connection.query(`USE \`${dbName}\``);
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
// Recreate all tables
|
`);
|
||||||
console.log(' - Creating table "users"...');
|
break;
|
||||||
await connection.query(`
|
case 'prometheus_sources':
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
await db.query(`
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
CREATE TABLE IF NOT EXISTS prometheus_sources (
|
||||||
username VARCHAR(255) NOT NULL UNIQUE,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
password VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
salt VARCHAR(255) NOT NULL,
|
url VARCHAR(500) NOT NULL,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
description TEXT,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
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 "prometheus_sources"...');
|
`);
|
||||||
await connection.query(`
|
break;
|
||||||
CREATE TABLE IF NOT EXISTS prometheus_sources (
|
case 'site_settings':
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
await db.query(`
|
||||||
name VARCHAR(255) NOT NULL,
|
CREATE TABLE IF NOT EXISTS site_settings (
|
||||||
url VARCHAR(500) NOT NULL,
|
id INT PRIMARY KEY DEFAULT 1,
|
||||||
description TEXT,
|
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
logo_url TEXT,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
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
|
||||||
console.log(' - Creating table "site_settings"...');
|
`);
|
||||||
await connection.query(`
|
await db.query(`
|
||||||
CREATE TABLE IF NOT EXISTS site_settings (
|
INSERT IGNORE INTO site_settings (id, page_name, title, default_theme)
|
||||||
id INT PRIMARY KEY DEFAULT 1,
|
VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark')
|
||||||
page_name VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
`);
|
||||||
title VARCHAR(255) DEFAULT '数据可视化展示大屏',
|
break;
|
||||||
logo_url TEXT,
|
case 'traffic_stats':
|
||||||
default_theme VARCHAR(20) DEFAULT 'dark',
|
await db.query(`
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
CREATE TABLE IF NOT EXISTS traffic_stats (
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
`);
|
rx_bytes BIGINT UNSIGNED DEFAULT 0,
|
||||||
await connection.query(`
|
tx_bytes BIGINT UNSIGNED DEFAULT 0,
|
||||||
INSERT INTO site_settings (id, page_name, title, default_theme)
|
rx_bandwidth DOUBLE DEFAULT 0,
|
||||||
VALUES (1, '数据可视化展示大屏', '数据可视化展示大屏', 'dark')
|
tx_bandwidth DOUBLE DEFAULT 0,
|
||||||
`);
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE INDEX (timestamp)
|
||||||
console.log(' - Creating table "traffic_stats"...');
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
await connection.query(`
|
`);
|
||||||
CREATE TABLE IF NOT EXISTS traffic_stats (
|
break;
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
case 'server_locations':
|
||||||
rx_bytes BIGINT UNSIGNED DEFAULT 0,
|
await db.query(`
|
||||||
tx_bytes BIGINT UNSIGNED DEFAULT 0,
|
CREATE TABLE IF NOT EXISTS server_locations (
|
||||||
rx_bandwidth DOUBLE DEFAULT 0,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
tx_bandwidth DOUBLE DEFAULT 0,
|
ip VARCHAR(255) NOT NULL UNIQUE,
|
||||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
country CHAR(2),
|
||||||
UNIQUE INDEX (timestamp)
|
country_name VARCHAR(100),
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
region VARCHAR(100),
|
||||||
`);
|
city VARCHAR(100),
|
||||||
|
latitude DOUBLE,
|
||||||
console.log(' - Creating table "server_locations"...');
|
longitude DOUBLE,
|
||||||
await connection.query(`
|
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
CREATE TABLE IF NOT EXISTS server_locations (
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
`);
|
||||||
ip VARCHAR(255) NOT NULL UNIQUE,
|
break;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -582,25 +582,28 @@ app.get('/api/metrics/overview', async (req, res) => {
|
|||||||
|
|
||||||
// --- Add Geo Information to Servers ---
|
// --- Add Geo Information to Servers ---
|
||||||
const geoServers = await Promise.all(overview.servers.map(async (server) => {
|
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 realInstance = prometheusService.resolveToken(server.instance);
|
||||||
const cleanIp = realInstance.split(':')[0];
|
const cleanIp = realInstance.split(':')[0];
|
||||||
|
|
||||||
// Attempt to get location
|
// Try to get from DB cache only (fast)
|
||||||
const location = await geoService.getLocation(cleanIp);
|
try {
|
||||||
if (location) {
|
const [rows] = await db.query('SELECT * FROM server_locations WHERE ip = ?', [cleanIp]);
|
||||||
return {
|
if (rows.length > 0) {
|
||||||
...server,
|
const location = rows[0];
|
||||||
country: location.country,
|
return {
|
||||||
countryName: location.country_name,
|
...server,
|
||||||
lat: location.latitude,
|
country: location.country,
|
||||||
lng: location.longitude
|
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;
|
return server;
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user