220 lines
8.1 KiB
JavaScript
220 lines
8.1 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { Summary, DeviceStat, Flow, Threat, Event } = require('../../models/Schemas');
|
|
const { getTimeFilter, getBaseFilter, getCustomLabelsMap } = require('./helpers');
|
|
const { generateMacFromIp, resolveDeviceTypeFromIp, resolveOSFromIp, resolveVendorFromIp, generateAutoLabel } = require('../../deviceResolver');
|
|
|
|
// GET /api/dashboard/summary
|
|
router.get('/summary', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const base = getBaseFilter(req, timeFilter);
|
|
const baseWithoutTime = getBaseFilter(req, null);
|
|
|
|
let bandwidthDown = 0;
|
|
let bandwidthUp = 0;
|
|
let activeFlowsCount = 0;
|
|
let totalDevicesCount = 0;
|
|
let totalThreatsCount = 0;
|
|
let totalEventsCount = 0;
|
|
let downloadSpeed = 0;
|
|
let uploadSpeed = 0;
|
|
let latestTime = null;
|
|
|
|
if (base.agent_uuid) {
|
|
// ── Agent-Level Summary (View As Agent mode) ───────────────────────────
|
|
const latestAgentSummary = await Summary
|
|
.findOne(baseWithoutTime)
|
|
.sort({ timestamp: -1 })
|
|
.lean();
|
|
|
|
if (latestAgentSummary) {
|
|
activeFlowsCount = latestAgentSummary.active_flows || 0;
|
|
totalDevicesCount = latestAgentSummary.total_devices || 0;
|
|
totalThreatsCount = latestAgentSummary.total_threats || 0;
|
|
totalEventsCount = latestAgentSummary.total_events || 0;
|
|
downloadSpeed = latestAgentSummary.download_speed || 0;
|
|
uploadSpeed = latestAgentSummary.upload_speed || 0;
|
|
latestTime = latestAgentSummary.timestamp;
|
|
}
|
|
} else {
|
|
// ── Site-Level Summary (default) ─────────────────────────────────────────
|
|
const agentQuery = {
|
|
agent_uuid: { $ne: null }
|
|
};
|
|
if (baseWithoutTime.site_uuid) {
|
|
agentQuery.site_uuid = baseWithoutTime.site_uuid;
|
|
}
|
|
if (timeFilter) agentQuery.timestamp = timeFilter;
|
|
|
|
const allAgentSummaries = await Summary.find(agentQuery).lean();
|
|
|
|
// Real-time stats (devices, flows, threats) use the latest snapshot of each agent
|
|
const latestPerAgent = {};
|
|
for (const doc of allAgentSummaries) {
|
|
if (!latestPerAgent[doc.agent_uuid] || new Date(doc.timestamp) > new Date(latestPerAgent[doc.agent_uuid].timestamp)) {
|
|
latestPerAgent[doc.agent_uuid] = doc;
|
|
}
|
|
}
|
|
|
|
for (const agentUuid in latestPerAgent) {
|
|
const doc = latestPerAgent[agentUuid];
|
|
activeFlowsCount += doc.active_flows || 0;
|
|
totalDevicesCount += doc.total_devices || 0;
|
|
totalThreatsCount += doc.total_threats || 0;
|
|
totalEventsCount += doc.total_events || 0;
|
|
downloadSpeed += doc.download_speed || 0;
|
|
uploadSpeed += doc.upload_speed || 0;
|
|
|
|
if (!latestTime || new Date(doc.timestamp) > new Date(latestTime)) {
|
|
latestTime = doc.timestamp;
|
|
}
|
|
}
|
|
|
|
// Fallback: if no site-level summaries
|
|
if (activeFlowsCount === 0 && totalDevicesCount === 0) {
|
|
const latestAgentDoc = await Summary.findOne(baseWithoutTime).sort({ timestamp: -1 }).lean();
|
|
if (latestAgentDoc) {
|
|
latestTime = latestAgentDoc.timestamp;
|
|
const agentSummaries = await Summary.find({ ...baseWithoutTime, timestamp: latestAgentDoc.timestamp }).lean();
|
|
downloadSpeed = agentSummaries.reduce((s, r) => s + (r.download_speed ?? 0), 0);
|
|
uploadSpeed = agentSummaries.reduce((s, r) => s + (r.upload_speed ?? 0), 0);
|
|
activeFlowsCount = agentSummaries.reduce((s, r) => s + (r.active_flows ?? 0), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Always aggregate exact bandwidth from Flow to guarantee consistency
|
|
// with Top Apps & Categories, bypassing potentially corrupted proxy Summary totals.
|
|
const flows = await Flow.find(base).select('download upload').lean();
|
|
bandwidthDown = flows.reduce((s, x) => s + (x.download || 0), 0);
|
|
bandwidthUp = flows.reduce((s, x) => s + (x.upload || 0), 0);
|
|
|
|
// Device count, Threats, Events, Flows — always use the scoped base filter
|
|
// (already contains agent_uuid when in AGENT_VIEWER mode)
|
|
let [uniqueDevices, realThreatsCount, realEventsCount, realFlowsCount] = await Promise.all([
|
|
DeviceStat.distinct('ip_address', base).then(r => r.length),
|
|
Threat.countDocuments(base),
|
|
Event.countDocuments(base),
|
|
Flow.countDocuments(base),
|
|
]);
|
|
|
|
if (uniqueDevices === 0) {
|
|
uniqueDevices = await Flow.distinct('src_ip', base).then(r => r.length);
|
|
}
|
|
|
|
// Fall back to critical/cybersecurity events if no real threats exist (aligned with threats route)
|
|
if (realThreatsCount === 0) {
|
|
const baseEventFilter = {};
|
|
if (base.agent_uuid) baseEventFilter.agent_uuid = base.agent_uuid;
|
|
if (base.site_uuid) baseEventFilter.site_uuid = base.site_uuid;
|
|
if (timeFilter) {
|
|
baseEventFilter.$and = [
|
|
{ $or: [{ event_at: timeFilter }, { timestamp: timeFilter }] }
|
|
];
|
|
}
|
|
realThreatsCount = await Event.countDocuments({
|
|
...baseEventFilter,
|
|
$or: [
|
|
{ severity: { $in: ['Critical', 'High'] } },
|
|
{ category_label: 'Cybersecurity' }
|
|
]
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
ok: true,
|
|
data: {
|
|
total_devices: uniqueDevices,
|
|
total_threats: realThreatsCount,
|
|
total_events: realEventsCount,
|
|
last_fetch: latestTime || new Date(),
|
|
bandwidth_down: bandwidthDown,
|
|
bandwidth_up: bandwidthUp,
|
|
active_flows: realFlowsCount,
|
|
download_speed: downloadSpeed,
|
|
upload_speed: uploadSpeed,
|
|
flow_speed: 0,
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.error('[/summary]', err.message);
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
// GET /api/dashboard/timeline
|
|
router.get('/timeline', async (req, res) => {
|
|
try {
|
|
const points = parseInt(req.query.points ?? 60);
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
|
|
const data = await Summary
|
|
.find(query)
|
|
.sort({ timestamp: -1 })
|
|
.limit(points)
|
|
.lean();
|
|
|
|
const formatted = data.reverse().map(s => {
|
|
const activeFlows = s.active_flows || 0;
|
|
const totalBandwidth = (s.bandwidth_down || 0) + (s.bandwidth_up || 0);
|
|
|
|
const cpu_usage = s.cpu_usage !== undefined && s.cpu_usage !== null
|
|
? s.cpu_usage
|
|
: Math.min(98, Math.max(1.2, parseFloat((2.5 + (activeFlows * 0.04) + (totalBandwidth / 10000000)).toFixed(2))));
|
|
|
|
const memory_usage = s.memory_usage !== undefined && s.memory_usage !== null
|
|
? s.memory_usage
|
|
: Math.min(99, Math.max(10.5, parseFloat((15.4 + (activeFlows * 0.02) + (totalBandwidth / 25000000)).toFixed(2))));
|
|
|
|
const queue_depth = s.queue_depth !== undefined && s.queue_depth !== null
|
|
? s.queue_depth
|
|
: Math.max(0, Math.floor((activeFlows * 0.15) + (totalBandwidth / 5000000)));
|
|
|
|
return {
|
|
fetched_at: s.timestamp,
|
|
timestamp: s.timestamp,
|
|
total_download: s.bandwidth_down ?? 0,
|
|
total_upload: s.bandwidth_up ?? 0,
|
|
total_flows: s.active_flows ?? 0,
|
|
download_speed: s.download_speed ?? 0,
|
|
upload_speed: s.upload_speed ?? 0,
|
|
packet_drops: s.packet_drops ?? 0,
|
|
peak_flow_rate: s.peak_flow_rate ?? 0,
|
|
cpu_usage,
|
|
memory_usage,
|
|
queue_depth,
|
|
flow_speed: 0,
|
|
};
|
|
});
|
|
|
|
res.json({ ok: true, data: formatted });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/data-interval
|
|
router.get('/data-interval', (req, res) => {
|
|
res.json({ ok: true, data: [] });
|
|
});
|
|
|
|
// GET /api/dashboard/agent-details?uuid=xxx
|
|
router.get('/agent-details', (req, res) => {
|
|
require('../agentDetailsHandler')(req, res, {
|
|
getTimeFilter,
|
|
generateMacFromIp,
|
|
resolveDeviceTypeFromIp,
|
|
resolveOSFromIp,
|
|
resolveVendorFromIp,
|
|
generateAutoLabel,
|
|
getCustomLabelsMap
|
|
});
|
|
});
|
|
|
|
module.exports = router;
|