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;