feat(source2): lock dark mode, sans-serif typography, and all recent updates

This commit is contained in:
rafif committed 2026-08-20 23:02:00 +07:00
1 parent dd4c8f6876
commit 9f24b56e97
259 files changed
+15596 -8966

No files matched your search

+109 -212
View File
@@ -1,40 +1,121 @@
const express = require('express');
const router = express.Router();
const { Flow } = require('../../models/Schemas');
const { getTimeFilter, getBaseFilter, topFlowField } = require('./helpers');
const { Flow, DeviceStat } = require('../../models/Schemas');
const { getTimeFilter, getBaseFilter, topFlowField, getCustomLabelsMap } = require('./helpers');
// GET /api/dashboard/flows-options
router.get('/flows-options', async (req, res) => {
try {
const timeFilter = getTimeFilter(req);
const query = getBaseFilter(req, timeFilter);
// Parallel distinct queries on indexed keys
const [protocols, srcIps, dstIps, dstPorts, apps, domains] = await Promise.all([
Flow.distinct('protocol', query),
Flow.distinct('src_ip', query),
Flow.distinct('dst_ip', query),
Flow.distinct('dst_port', query),
Flow.distinct('app_label', query),
Flow.distinct('domain', query)
]);
res.json({
ok: true,
data: {
protocols: protocols.filter(Boolean).sort(),
srcIps: srcIps.filter(Boolean).sort(),
dstIps: dstIps.filter(Boolean).sort(),
dstPorts: dstPorts.filter(Boolean).sort().map(String),
apps: apps.filter(Boolean).sort(),
domains: domains.filter(Boolean).sort()
}
});
} catch (err) {
res.status(500).json({ ok: false, error: err.message });
}
});
// 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 limit = rawLimit <= 0 ? 0 : Math.min(rawLimit, 1000000);
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 });
// Apply query filters on MongoDB
if (req.query.protocol && req.query.protocol !== 'All') {
query.protocol = req.query.protocol;
}
if (req.query.src_ip && req.query.src_ip !== 'All') {
query.src_ip = req.query.src_ip;
}
if (req.query.dst_ip && req.query.dst_ip !== 'All') {
query.dst_ip = req.query.dst_ip;
}
if (req.query.dst_port && req.query.dst_port !== 'All') {
query.dst_port = parseInt(req.query.dst_port);
}
if (req.query.app && req.query.app !== 'All') {
query.app_label = req.query.app;
}
if (req.query.domain && req.query.domain !== 'All') {
query.domain = req.query.domain;
}
// 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;
if (req.query.search) {
const q = req.query.search.trim();
if (q) {
query.$or = [
{ src_ip: { $regex: q, $options: 'i' } },
{ dst_ip: { $regex: q, $options: 'i' } }
];
}
}
const raw = await Flow
.find(query)
.sort({ timestamp: sortOrder })
.skip(skip)
.limit(limit)
.lean();
if (limit === 0) {
const total = await Flow.countDocuments(query);
console.log('[BACKEND /flows] countOnly total:', total);
return res.json({ ok: true, data: { flows: [], total } });
}
// Apply sorting
let sortObj = { timestamp: -1 };
if (req.query.sort_download === 'Descending') {
sortObj = { download: -1 };
} else if (req.query.sort_download === 'Ascending') {
sortObj = { download: 1 };
} else if (req.query.sort_upload === 'Descending') {
sortObj = { upload: -1 };
} else if (req.query.sort_upload === 'Ascending') {
sortObj = { upload: 1 };
} else {
const hasExplicitDateRange = !!(req.query.date_from || req.query.date_to);
sortObj = { timestamp: hasExplicitDateRange ? 1 : -1 };
}
console.log('[BACKEND /flows] Constructed MongoDB query:', JSON.stringify(query));
const deviceFilter = {};
if (query.site_uuid) deviceFilter.site_uuid = query.site_uuid;
const [raw, customLabelsMap, devicesList] = await Promise.all([
Flow.find(query).sort(sortObj).skip(skip).limit(limit).lean(),
getCustomLabelsMap(),
DeviceStat.find(deviceFilter, { ip_address: 1, mac_address: 1 }).lean()
]);
const total = await Flow.countDocuments(query);
console.log(`[BACKEND /flows] Found total: ${total}, returning slice length: ${raw.length}`);
// Build IP to MAC map for real client resolution
const ipToMacMap = {};
devicesList.forEach(d => {
if (d.ip_address && d.mac_address && d.mac_address !== '-') {
ipToMacMap[d.ip_address] = d.mac_address.toLowerCase();
}
});
const data = raw.map(f => {
const port = f.dst_port ?? 0;
@@ -55,12 +136,18 @@ router.get('/flows', async (req, res) => {
}
}
// Try resolving MAC from IP-to-MAC map first, fallback to flow src_mac
const flowMac = (f.src_mac || '').toLowerCase();
const realMac = ipToMacMap[f.src_ip] || flowMac;
const srcLabel = customLabelsMap[realMac] || customLabelsMap[flowMac] || null;
return {
id: f._id?.toString(),
fetched_at: f.timestamp,
flow_id: f.flow_id,
src_ip: f.src_ip,
src_mac: f.src_mac,
src_label: srcLabel,
dst_ip: f.dst_ip,
dst_port: port,
protocol: proto,
@@ -76,197 +163,7 @@ router.get('/flows', async (req, res) => {
};
});
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 });
res.json({ ok: true, data: { flows: data, total } });
} catch (err) {
res.status(500).json({ ok: false, error: err.message });
}