v1.1: Add CIDR subnet filtering and Agent filter in Devices page
This commit is contained in:
commit
966058e2fe
422 files changed
+54656
No files matched your search
@@ -0,0 +1,200 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const mongoose = require('mongoose');
|
||||
const { AppStat, ProtocolStat, AppCategoryStat, LookupApp } = require('../../models/Schemas');
|
||||
const { getTimeFilter, getBaseFilter } = require('./helpers');
|
||||
|
||||
// GET /api/dashboard/apps
|
||||
router.get('/apps', async (req, res) => {
|
||||
try {
|
||||
const limit = parseInt(req.query.limit || 10);
|
||||
const timeFilter = getTimeFilter(req);
|
||||
const base = getBaseFilter(req, timeFilter);
|
||||
const { Flow } = require('../../models/Schemas');
|
||||
|
||||
// Aggregate directly from Flow for accurate delta values
|
||||
const flowPipeline = [
|
||||
{ $match: { ...base, app_label: { $ne: null, $ne: '' } } },
|
||||
{ $group: {
|
||||
_id: '$app_label',
|
||||
download: { $sum: '$download' },
|
||||
upload: { $sum: '$upload' },
|
||||
flows: { $sum: 1 },
|
||||
}},
|
||||
{ $addFields: { total_bytes: { $add: ['$download', '$upload'] } } },
|
||||
{ $sort: { total_bytes: -1 } },
|
||||
{ $limit: limit }
|
||||
];
|
||||
let result = await Flow.aggregate(flowPipeline);
|
||||
|
||||
// Fetch lookup metadata (category and favicon) to enrich apps list
|
||||
const labels = result.map(r => r._id);
|
||||
const lookups = await LookupApp.find({ label: { $in: labels } }).lean();
|
||||
const lookupMap = {};
|
||||
for (const app of lookups) {
|
||||
lookupMap[app.label] = {
|
||||
favicon: app.favicon || app.logo || null,
|
||||
category: app.application_category?.label || null
|
||||
};
|
||||
}
|
||||
|
||||
const formatted = result.map(r => ({
|
||||
app_label: r._id,
|
||||
download: r.download || 0,
|
||||
upload: r.upload || 0,
|
||||
total_bytes: r.total_bytes || 0,
|
||||
flows: r.flows || 0,
|
||||
category: lookupMap[r._id]?.category || null,
|
||||
favicon: lookupMap[r._id]?.favicon || null,
|
||||
}));
|
||||
|
||||
res.json({ ok: true, data: formatted });
|
||||
} catch (err) {
|
||||
res.status(500).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dashboard/protocols
|
||||
router.get('/protocols', async (req, res) => {
|
||||
try {
|
||||
const timeFilter = getTimeFilter(req);
|
||||
const base = getBaseFilter(req, timeFilter);
|
||||
const { Flow } = require('../../models/Schemas');
|
||||
|
||||
const flowPipeline = [
|
||||
{ $match: { ...base, protocol: { $ne: null, $ne: '' } } },
|
||||
{ $group: {
|
||||
_id: '$protocol',
|
||||
download: { $sum: '$download' },
|
||||
upload: { $sum: '$upload' },
|
||||
flows: { $sum: 1 },
|
||||
}},
|
||||
{ $sort: { download: -1 } }
|
||||
];
|
||||
let result = await Flow.aggregate(flowPipeline);
|
||||
const formatted = result.map(r => ({
|
||||
protocol_label: r._id,
|
||||
download: r.download || 0,
|
||||
upload: r.upload || 0,
|
||||
flows: r.flows || 0,
|
||||
}));
|
||||
|
||||
res.json({ ok: true, data: formatted });
|
||||
} catch (err) {
|
||||
res.status(500).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dashboard/app-categories
|
||||
router.get('/app-categories', async (req, res) => {
|
||||
try {
|
||||
const timeFilter = getTimeFilter(req);
|
||||
const base = getBaseFilter(req, timeFilter);
|
||||
const { Flow, LookupApp } = require('../../models/Schemas');
|
||||
|
||||
// Flow doesn't store category label, so we must join it from LookupApp or use app_label
|
||||
const flowPipeline = [
|
||||
{ $match: { ...base, app_label: { $ne: null, $ne: '' } } },
|
||||
{ $group: {
|
||||
_id: '$app_label',
|
||||
download: { $sum: '$download' },
|
||||
upload: { $sum: '$upload' },
|
||||
flows: { $sum: 1 },
|
||||
}},
|
||||
{ $sort: { download: -1 } }
|
||||
];
|
||||
const appResult = await Flow.aggregate(flowPipeline);
|
||||
|
||||
// Enrich with categories
|
||||
const labels = appResult.map(r => r._id);
|
||||
const lookups = await LookupApp.find({ label: { $in: labels } }).lean();
|
||||
const lookupMap = {};
|
||||
for (const app of lookups) {
|
||||
if (app.application_category?.label) {
|
||||
lookupMap[app.label] = app.application_category.label;
|
||||
}
|
||||
}
|
||||
|
||||
// Group by category
|
||||
const catMap = {};
|
||||
for (const r of appResult) {
|
||||
const cat = lookupMap[r._id] || 'Uncategorized';
|
||||
if (!catMap[cat]) catMap[cat] = { download: 0, upload: 0, flows: 0 };
|
||||
catMap[cat].download += r.download || 0;
|
||||
catMap[cat].upload += r.upload || 0;
|
||||
catMap[cat].flows += r.flows || 0;
|
||||
}
|
||||
|
||||
const formatted = Object.keys(catMap).map(k => ({
|
||||
category_label: k,
|
||||
download: catMap[k].download,
|
||||
upload: catMap[k].upload,
|
||||
flows: catMap[k].flows,
|
||||
})).sort((a, b) => b.download - a.download).slice(0, 50);
|
||||
|
||||
res.json({ ok: true, data: formatted });
|
||||
} catch (err) {
|
||||
res.status(500).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dashboard/lookup/applications
|
||||
router.get('/lookup/applications', async (req, res) => {
|
||||
try {
|
||||
const search = String(req.query.search || req.query.q || '').trim();
|
||||
const category = String(req.query.category || '').trim();
|
||||
const page = Math.max(1, parseInt(req.query.page) || 1);
|
||||
const limit = Math.max(1, parseInt(req.query.limit) || 25);
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
let filter = {};
|
||||
if (search) {
|
||||
filter.$or = [
|
||||
{ label: { $regex: search, $options: 'i' } },
|
||||
{ name: { $regex: search, $options: 'i' } },
|
||||
{ tag: { $regex: search, $options: 'i' } }
|
||||
];
|
||||
}
|
||||
|
||||
if (category) {
|
||||
filter['application_category.label'] = category;
|
||||
}
|
||||
|
||||
const [applications, total_records] = await Promise.all([
|
||||
LookupApp.find(filter).sort({ label: 1 }).skip(skip).limit(limit).lean(),
|
||||
LookupApp.countDocuments(filter)
|
||||
]);
|
||||
|
||||
const total_pages = Math.ceil(total_records / limit) || 1;
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
data: {
|
||||
applications,
|
||||
pagination: {
|
||||
total_records,
|
||||
total_pages,
|
||||
current_page: page,
|
||||
start: skip,
|
||||
length: applications.length,
|
||||
limit
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/dashboard/lookup/categories
|
||||
router.get('/lookup/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await LookupApp.distinct('application_category.label');
|
||||
const validCategories = categories.filter(c => c).sort();
|
||||
res.json({ ok: true, data: validCategories });
|
||||
} catch (err) {
|
||||
res.status(500).json({ ok: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in new issue
Block a user