216 lines
8.0 KiB
JavaScript
216 lines
8.0 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { CountryStat } = require('../../models/SchemasAux');
|
|
const { Flow } = require('../../models/Schemas');
|
|
const { getTimeFilter, getBaseFilter, topFlowField } = require('./helpers');
|
|
const { resolveIPContinent, resolveIPGeography } = require('./geoResolver');
|
|
|
|
|
|
// GET /api/dashboard/countries
|
|
router.get('/countries', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const matchBase = getBaseFilter(req, timeFilter);
|
|
|
|
// Aggregate from CountryStat collection (real country data from BackOne API)
|
|
const pipeline = [
|
|
{ $match: matchBase },
|
|
{ $group: {
|
|
_id: '$country_name', // country_name actually stores country code (e.g., "US", "ID")
|
|
download: { $sum: '$download' },
|
|
upload: { $sum: '$upload' },
|
|
flow_count: { $sum: { $ifNull: ['$flows', 1] } },
|
|
country_code: { $first: '$country_name' } // same field (data stored inverted)
|
|
}},
|
|
{ $sort: { download: -1 } },
|
|
{ $limit: 200 }
|
|
];
|
|
|
|
const raw = await CountryStat.aggregate(pipeline);
|
|
|
|
// Country code -> name mapping
|
|
const codeToName = {
|
|
'ID': 'Indonesia', 'US': 'United States', 'SG': 'Singapore', 'JP': 'Japan',
|
|
'AU': 'Australia', 'GB': 'United Kingdom', 'DE': 'Germany', 'CN': 'China',
|
|
'MY': 'Malaysia', 'TH': 'Thailand', 'VN': 'Vietnam', 'PH': 'Philippines',
|
|
'IN': 'India', 'KR': 'South Korea', 'NL': 'Netherlands', 'FR': 'France',
|
|
'CA': 'Canada', 'RU': 'Russia', 'BR': 'Brazil', 'IT': 'Italy',
|
|
'HK': 'Hong Kong', 'TW': 'Taiwan', 'TR': 'Turkey', 'SA': 'Saudi Arabia',
|
|
'AE': 'United Arab Emirates', 'ES': 'Spain', 'SE': 'Sweden', 'CH': 'Switzerland',
|
|
'AT': 'Austria', 'BE': 'Belgium', 'PL': 'Poland', 'CZ': 'Czech Republic',
|
|
'UA': 'Ukraine', 'GR': 'Greece', 'PT': 'Portugal', 'RO': 'Romania',
|
|
'HU': 'Hungary', 'NZ': 'New Zealand', 'ZA': 'South Africa', 'EG': 'Egypt',
|
|
'NG': 'Nigeria', 'KE': 'Kenya', 'AR': 'Argentina', 'MX': 'Mexico',
|
|
'CL': 'Chile', 'CO': 'Colombia', 'VE': 'Venezuela', 'PE': 'Peru',
|
|
'DK': 'Denmark', 'FI': 'Finland', 'NO': 'Norway', 'LU': 'Luxembourg',
|
|
'SC': 'Seychelles', 'BD': 'Bangladesh', 'PK': 'Pakistan', 'LK': 'Sri Lanka',
|
|
'MM': 'Myanmar', 'KH': 'Cambodia', 'LA': 'Laos', 'BN': 'Brunei',
|
|
};
|
|
|
|
const data = raw
|
|
.filter(r => r.country_code && r.country_code !== 'Unknown' && r.country_code.length === 2)
|
|
.map(r => ({
|
|
country_code: r.country_code,
|
|
country_name: codeToName[r.country_code] || r.country_code,
|
|
download: r.download || 0,
|
|
upload: r.upload || 0,
|
|
flow_count: r.flow_count || 0
|
|
}))
|
|
.sort((a, b) => b.download - a.download);
|
|
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/continents
|
|
router.get('/continents', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 10);
|
|
const timeFilter = getTimeFilter(req);
|
|
const matchBase = getBaseFilter(req, timeFilter);
|
|
|
|
const raw = await CountryStat.aggregate([
|
|
{ $match: { ...matchBase, country_name: { $ne: null, $ne: 'Unknown' } } },
|
|
{ $group: { _id: '$country_name', download: { $sum: '$download' }, upload: { $sum: '$upload' } } },
|
|
]);
|
|
|
|
const countryToContinent = {
|
|
'ID': 'Asia', 'SG': 'Asia', 'MY': 'Asia', 'TH': 'Asia', 'VN': 'Asia',
|
|
'PH': 'Asia', 'KH': 'Asia', 'LA': 'Asia', 'MM': 'Asia', 'BN': 'Asia',
|
|
'JP': 'Asia', 'KR': 'Asia', 'CN': 'Asia', 'TW': 'Asia', 'HK': 'Asia',
|
|
'IN': 'Asia', 'BD': 'Asia', 'PK': 'Asia', 'LK': 'Asia',
|
|
'SA': 'Asia', 'AE': 'Asia', 'TR': 'Asia',
|
|
'AU': 'Oceania', 'NZ': 'Oceania',
|
|
'US': 'North America', 'CA': 'North America', 'MX': 'North America',
|
|
'BR': 'South America', 'AR': 'South America', 'CL': 'South America',
|
|
'CO': 'South America', 'VE': 'South America', 'PE': 'South America',
|
|
'GB': 'Europe', 'DE': 'Europe', 'FR': 'Europe', 'NL': 'Europe',
|
|
'IT': 'Europe', 'ES': 'Europe', 'SE': 'Europe', 'DK': 'Europe',
|
|
'NO': 'Europe', 'FI': 'Europe', 'CH': 'Europe', 'AT': 'Europe',
|
|
'BE': 'Europe', 'PL': 'Europe', 'CZ': 'Europe', 'HU': 'Europe',
|
|
'RO': 'Europe', 'GR': 'Europe', 'PT': 'Europe', 'UA': 'Europe',
|
|
'RU': 'Europe', 'LU': 'Europe', 'IM': 'Europe',
|
|
'ZA': 'Africa', 'NG': 'Africa', 'KE': 'Africa', 'EG': 'Africa',
|
|
'BI': 'Africa', 'SC': 'Africa',
|
|
};
|
|
|
|
const map = {};
|
|
for (const r of raw) {
|
|
const cc = r._id; // country code
|
|
const name = countryToContinent[cc] || 'Other';
|
|
if (!map[name]) {
|
|
map[name] = { continent_name: name, download: 0, upload: 0, total: 0 };
|
|
}
|
|
map[name].download += r.download;
|
|
map[name].upload += r.upload;
|
|
map[name].total += (r.download + r.upload);
|
|
|
|
}
|
|
const data = Object.values(map).sort((a, b) => b.download - a.download).slice(0, limit);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/regions
|
|
router.get('/regions', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 20);
|
|
const raw = await topFlowField('dst_ip', req, limit);
|
|
const map = {};
|
|
for (const r of raw) {
|
|
const geo = resolveIPGeography(r.label);
|
|
const key = `${geo.region_name}:${geo.country_name}`;
|
|
if (!map[key]) {
|
|
map[key] = {
|
|
region_name: geo.region_name,
|
|
country_name: geo.country_name,
|
|
download: 0,
|
|
upload: 0,
|
|
count: 0
|
|
};
|
|
}
|
|
map[key].download += r.download;
|
|
map[key].upload += r.upload;
|
|
map[key].count += r.count;
|
|
}
|
|
const data = Object.values(map).sort((a, b) => b.download - a.download).slice(0, limit);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/cities
|
|
router.get('/cities', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 20);
|
|
const raw = await topFlowField('dst_ip', req, limit);
|
|
const map = {};
|
|
for (const r of raw) {
|
|
const geo = resolveIPGeography(r.label);
|
|
const key = `${geo.city_name}:${geo.region_name}:${geo.country_name}`;
|
|
if (!map[key]) {
|
|
map[key] = {
|
|
city_name: geo.city_name,
|
|
region_name: geo.region_name,
|
|
country_name: geo.country_name,
|
|
download: 0,
|
|
upload: 0,
|
|
count: 0
|
|
};
|
|
}
|
|
map[key].download += r.download;
|
|
map[key].upload += r.upload;
|
|
map[key].count += r.count;
|
|
}
|
|
const data = Object.values(map).sort((a, b) => b.download - a.download).slice(0, limit);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/dns
|
|
router.get('/dns', async (req, res) => {
|
|
try {
|
|
const limit = req.query.limit !== undefined ? parseInt(req.query.limit) : 0;
|
|
const timeFilter = getTimeFilter(req);
|
|
const matchBase = getBaseFilter(req, timeFilter);
|
|
const { SniHostnameStat } = require('../../models/SchemasTelemetry');
|
|
|
|
const pipeline = [
|
|
{ $match: { ...matchBase, sni_hostname: { $ne: null } } },
|
|
{ $group: {
|
|
_id: '$sni_hostname',
|
|
download: { $sum: '$download' },
|
|
upload: { $sum: '$upload' },
|
|
count: { $sum: '$flows' }
|
|
}},
|
|
{ $project: {
|
|
domain: '$_id',
|
|
query_count: '$count',
|
|
download: 1,
|
|
upload: 1,
|
|
app_label: { $literal: null },
|
|
category: { $literal: null },
|
|
_id: 0
|
|
}},
|
|
{ $sort: { query_count: -1 } },
|
|
];
|
|
if (limit > 0) {
|
|
pipeline.push({ $limit: limit });
|
|
}
|
|
|
|
const data = await SniHostnameStat.aggregate(pipeline);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|