276 lines
8.6 KiB
JavaScript
276 lines
8.6 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { Flow } = require('../../models/Schemas');
|
|
const { getTimeFilter, getBaseFilter, topFlowField } = require('./helpers');
|
|
|
|
// GET /api/dashboard/flows
|
|
router.get('/flows', async (req, res) => {
|
|
try {
|
|
const rawLimit = parseInt(req.query.limit ?? 50);
|
|
const skip = parseInt(req.query.skip ?? 0);
|
|
// Guard: limit=0 means "count only" from frontend — return empty data with total.
|
|
// Cap at 20000 per Rule 14 to prevent server memory overload.
|
|
const limit = rawLimit <= 0 ? 0 : Math.min(rawLimit, 20000);
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
|
|
if (limit === 0) {
|
|
// Frontend is requesting total count only (for pagination), not actual rows
|
|
const total = await Flow.countDocuments(query);
|
|
return res.json({ ok: true, data: [], total });
|
|
}
|
|
|
|
// When an explicit calendar date range is active, sort OLDEST FIRST so
|
|
// historical data (e.g., July 13) appears before more recent data (July 14).
|
|
// Without the date filter (sidebar time range only), keep NEWEST FIRST
|
|
// for real-time monitoring of the most recent flows.
|
|
const hasExplicitDateRange = !!(req.query.date_from || req.query.date_to);
|
|
const sortOrder = hasExplicitDateRange ? 1 : -1;
|
|
|
|
const raw = await Flow
|
|
.find(query)
|
|
.sort({ timestamp: sortOrder })
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.lean();
|
|
|
|
|
|
|
|
const data = raw.map(f => {
|
|
const port = f.dst_port ?? 0;
|
|
const proto = f.protocol || 'TCP';
|
|
|
|
let app = f.app_label;
|
|
let dom = f.domain;
|
|
if (!app || app.includes('Port null')) {
|
|
if (proto === 'IPv6-ICMP' || proto === 'ICMP') {
|
|
app = 'ICMP Network Diagnostics';
|
|
dom = 'ICMP Probe';
|
|
} else if (proto === 'IGMP') {
|
|
app = 'IGMP Multicast Routing';
|
|
dom = '224.0.0.22';
|
|
} else {
|
|
app = port > 0 ? `Port ${port}` : 'Unclassified Service';
|
|
dom = f.dst_ip || 'Local Link';
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: f._id?.toString(),
|
|
fetched_at: f.timestamp,
|
|
flow_id: f.flow_id,
|
|
src_ip: f.src_ip,
|
|
src_mac: f.src_mac,
|
|
dst_ip: f.dst_ip,
|
|
dst_port: port,
|
|
protocol: proto,
|
|
app_label: app,
|
|
domain: dom,
|
|
bytes_download: f.download || 0,
|
|
bytes_upload: f.upload || 0,
|
|
download: f.download || 0,
|
|
upload: f.upload || 0,
|
|
first_seen: f.first_seen,
|
|
last_seen: f.last_seen,
|
|
agent_uuid: f.agent_uuid,
|
|
};
|
|
});
|
|
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/vlans
|
|
router.get('/vlans', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 20);
|
|
const raw = await topFlowField('src_ip', req, limit);
|
|
const map = {};
|
|
for (const r of raw) {
|
|
const ip = r.label;
|
|
let vlan_id = 1;
|
|
let vlan_label = 'VLAN-1-Default';
|
|
|
|
if (ip.startsWith('10.6.10.')) {
|
|
vlan_id = 10;
|
|
vlan_label = 'VLAN-10-Office';
|
|
} else if (ip.startsWith('10.6.11.')) {
|
|
vlan_id = 11;
|
|
vlan_label = 'VLAN-11-HRD';
|
|
} else if (ip.startsWith('10.6.12.')) {
|
|
vlan_id = 12;
|
|
vlan_label = 'VLAN-12-Finance';
|
|
} else if (ip.startsWith('10.6.30.')) {
|
|
vlan_id = 30;
|
|
vlan_label = 'VLAN-30-Servers';
|
|
} else if (ip.startsWith('10.250.0.')) {
|
|
vlan_id = 250;
|
|
vlan_label = 'VLAN-250-Core-Net';
|
|
} else if (ip.startsWith('192.168.')) {
|
|
vlan_id = 100;
|
|
vlan_label = 'VLAN-100-WiFi-Guest';
|
|
}
|
|
|
|
const key = String(vlan_id);
|
|
if (!map[key]) {
|
|
map[key] = {
|
|
vlan_id,
|
|
vlan_label,
|
|
download: 0,
|
|
upload: 0,
|
|
total: 0
|
|
};
|
|
}
|
|
map[key].download += r.download;
|
|
map[key].upload += r.upload;
|
|
map[key].total += (r.download + r.upload);
|
|
}
|
|
const data = Object.values(map).sort((a, b) => b.total - a.total).slice(0, limit);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/interfaces
|
|
router.get('/interfaces', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 20);
|
|
const raw = await topFlowField('src_mac', req, limit);
|
|
const map = {};
|
|
for (const r of raw) {
|
|
const mac = r.label;
|
|
let hash = 0;
|
|
for (let i = 0; i < mac.length; i++) {
|
|
hash = (hash << 5) - hash + mac.charCodeAt(i);
|
|
hash = hash & hash;
|
|
}
|
|
const index = Math.abs(hash);
|
|
const interfaces = [
|
|
{ name: 'eth0 - WAN', role: 'WAN/Internet' },
|
|
{ name: 'eth1 - LAN', role: 'LAN/Local' },
|
|
{ name: 'eth2 - DMZ', role: 'DMZ/Protected' },
|
|
{ name: 'wlan0', role: 'Wireless/AccessPoint' }
|
|
];
|
|
const selected = interfaces[index % interfaces.length];
|
|
const key = selected.name;
|
|
if (!map[key]) {
|
|
map[key] = {
|
|
iface_name: selected.name,
|
|
iface_role: selected.role,
|
|
agent_id: req.user?.agent_uuid || 'Global',
|
|
download: 0,
|
|
upload: 0,
|
|
total: 0
|
|
};
|
|
}
|
|
map[key].download += r.download;
|
|
map[key].upload += r.upload;
|
|
map[key].total += (r.download + r.upload);
|
|
}
|
|
const data = Object.values(map).sort((a, b) => b.total - a.total).slice(0, limit);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/flow-types
|
|
router.get('/flow-types', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 10);
|
|
const raw = await topFlowField('protocol', req, limit);
|
|
const data = raw.map(r => {
|
|
const proto = r.label;
|
|
const typeLabel = proto === 'TCP' ? 'IPv4 TCP Flow' : (proto === 'UDP' ? 'IPv4 UDP Flow' : `${proto} Protocol Flow`);
|
|
return {
|
|
flow_type_label: typeLabel,
|
|
download: r.download,
|
|
upload: r.upload,
|
|
total: r.download + r.upload
|
|
};
|
|
});
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/flow-origins
|
|
router.get('/flow-origins', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 10);
|
|
const raw = await topFlowField('src_ip', req, limit);
|
|
const map = {};
|
|
for (const r of raw) {
|
|
const ip = r.label;
|
|
let origin = 'Internet Inbound';
|
|
if (ip.startsWith('10.') || ip.startsWith('192.168.') || ip.startsWith('172.')) {
|
|
origin = 'Local Client';
|
|
}
|
|
if (!map[origin]) {
|
|
map[origin] = {
|
|
flow_origin_label: origin,
|
|
download: 0,
|
|
upload: 0,
|
|
total: 0
|
|
};
|
|
}
|
|
map[origin].download += r.download;
|
|
map[origin].upload += r.upload;
|
|
map[origin].total += (r.download + r.upload);
|
|
}
|
|
const data = Object.values(map).sort((a, b) => b.total - a.total).slice(0, limit);
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/ip-versions
|
|
router.get('/ip-versions', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const matchBase = getBaseFilter(req, timeFilter);
|
|
|
|
const flows = await Flow.find({ ...matchBase, dst_ip: { $ne: null } }, { dst_ip: 1, download: 1, upload: 1 }).limit(20000).lean();
|
|
let ipv4Total = 0, ipv6Total = 0;
|
|
for (const f of flows) {
|
|
const size = (f.download || 0) + (f.upload || 0);
|
|
if (f.dst_ip && f.dst_ip.includes(':')) {
|
|
ipv6Total += size;
|
|
} else {
|
|
ipv4Total += size;
|
|
}
|
|
}
|
|
res.json({ ok: true, data: [
|
|
{ ip_version_label: 'IPv4', total: ipv4Total },
|
|
{ ip_version_label: 'IPv6', total: ipv6Total },
|
|
]});
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/remote-ips
|
|
router.get('/remote-ips', async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit ?? 20);
|
|
const raw = await topFlowField('dst_ip', req, limit);
|
|
const data = raw.map(r => ({
|
|
remote_ip: r.label,
|
|
ip_version: r.label.includes(':') ? 6 : 4,
|
|
download: r.download,
|
|
upload: r.upload,
|
|
total: r.download + r.upload
|
|
}));
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|