添加流量地图
This commit is contained in:
@@ -13,7 +13,8 @@ const REQUIRED_TABLES = [
|
||||
'users',
|
||||
'prometheus_sources',
|
||||
'site_settings',
|
||||
'traffic_stats'
|
||||
'traffic_stats',
|
||||
'server_locations'
|
||||
];
|
||||
|
||||
async function checkAndFixDatabase() {
|
||||
@@ -115,6 +116,21 @@ async function recreateDatabase(host, port, user, password, dbName) {
|
||||
) 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
|
||||
|
||||
94
server/geo-service.js
Normal file
94
server/geo-service.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const axios = require('axios');
|
||||
const db = require('./db');
|
||||
|
||||
/**
|
||||
* Geo Location Service
|
||||
* Resolves IP addresses to geographical coordinates and country info.
|
||||
* Caches results in the database to minimize API calls.
|
||||
*/
|
||||
|
||||
const ipInfoToken = process.env.IPINFO_TOKEN;
|
||||
|
||||
async function getLocation(ip) {
|
||||
// Normalize IP (strip port if present)
|
||||
const cleanIp = ip.split(':')[0];
|
||||
|
||||
// Skip local/reserved IPs
|
||||
if (isLocalIp(cleanIp)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check database first
|
||||
try {
|
||||
const [rows] = await db.query('SELECT * FROM server_locations WHERE ip = ?', [cleanIp]);
|
||||
if (rows.length > 0) {
|
||||
// Check if data is stale (e.g., older than 30 days)
|
||||
const data = rows[0];
|
||||
const age = Date.now() - new Date(data.last_updated).getTime();
|
||||
if (age < 30 * 24 * 60 * 60 * 1000) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve via ipinfo.io
|
||||
console.log(`[Geo Service] Resolving location for IP: ${cleanIp}`);
|
||||
const url = `https://ipinfo.io/${cleanIp}/json${ipInfoToken ? `?token=${ipInfoToken}` : ''}`;
|
||||
const response = await axios.get(url, { timeout: 5000 });
|
||||
const geo = response.data;
|
||||
|
||||
if (geo && geo.loc) {
|
||||
const [lat, lon] = geo.loc.split(',').map(Number);
|
||||
const locationData = {
|
||||
ip: cleanIp,
|
||||
country: geo.country,
|
||||
country_name: geo.country_name || geo.country, // ipinfo might not have country_name in basic response
|
||||
region: geo.region,
|
||||
city: geo.city,
|
||||
latitude: lat,
|
||||
longitude: lon
|
||||
};
|
||||
|
||||
// Save to DB
|
||||
await db.query(`
|
||||
INSERT INTO server_locations (ip, country, country_name, region, city, latitude, longitude)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
country = VALUES(country),
|
||||
country_name = VALUES(country_name),
|
||||
region = VALUES(region),
|
||||
city = VALUES(city),
|
||||
latitude = VALUES(latitude),
|
||||
longitude = VALUES(longitude)
|
||||
`, [
|
||||
locationData.ip,
|
||||
locationData.country,
|
||||
locationData.country_name,
|
||||
locationData.region,
|
||||
locationData.city,
|
||||
locationData.latitude,
|
||||
locationData.longitude
|
||||
]);
|
||||
|
||||
return locationData;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Geo Service] Error resolving IP ${cleanIp}:`, err.message);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isLocalIp(ip) {
|
||||
if (ip === 'localhost' || ip === '127.0.0.1' || ip === '::1') return true;
|
||||
|
||||
// RFC1918 private addresses
|
||||
const p1 = /^10\./;
|
||||
const p2 = /^172\.(1[6-9]|2[0-9]|3[0-1])\./;
|
||||
const p3 = /^192\.168\./;
|
||||
|
||||
return p1.test(ip) || p2.test(ip) || p3.test(ip);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getLocation
|
||||
};
|
||||
@@ -5,6 +5,7 @@ const path = require('path');
|
||||
const db = require('./db');
|
||||
const prometheusService = require('./prometheus-service');
|
||||
const cache = require('./cache');
|
||||
const geoService = require('./geo-service');
|
||||
const checkAndFixDatabase = require('./db-integrity-check');
|
||||
|
||||
const app = express();
|
||||
@@ -548,7 +549,7 @@ app.get('/api/metrics/overview', async (req, res) => {
|
||||
console.error('Error calculating 24h traffic from DB integration:', err);
|
||||
}
|
||||
|
||||
res.json({
|
||||
const overview = {
|
||||
totalServers,
|
||||
activeServers,
|
||||
cpu: {
|
||||
@@ -577,7 +578,35 @@ app.get('/api/metrics/overview', async (req, res) => {
|
||||
total: traffic24hRx + traffic24hTx
|
||||
},
|
||||
servers: allServers
|
||||
});
|
||||
};
|
||||
|
||||
// --- 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
|
||||
};
|
||||
}
|
||||
return server;
|
||||
}));
|
||||
|
||||
overview.servers = geoServers;
|
||||
res.json(overview);
|
||||
} catch (err) {
|
||||
console.error('Error fetching overview metrics:', err);
|
||||
res.status(500).json({ error: 'Failed to fetch metrics' });
|
||||
|
||||
@@ -67,6 +67,22 @@ async function initDatabase() {
|
||||
`);
|
||||
console.log(' ✅ Table "site_settings" ready');
|
||||
|
||||
// Create server_locations table
|
||||
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(' ✅ Table "server_locations" ready');
|
||||
|
||||
console.log('\n🎉 Database initialization complete!\n');
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
@@ -650,5 +650,6 @@ module.exports = {
|
||||
getCpuHistory,
|
||||
mergeCpuHistories,
|
||||
getServerDetails,
|
||||
getServerHistory
|
||||
getServerHistory,
|
||||
resolveToken
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user