Fix devices bandwidth and MAC descriptors

This commit is contained in:
vanne committed 2026-07-01 14:53:33 +07:00
1 parent 4eee901d76
commit 448736bc03
4 files changed
+171 -117

No files matched your search

+81 -107
View File
@@ -883,125 +883,99 @@ function getLatestDevices(limit = 100, siteUuid = null, agentUuid = null) {
const intelList = d.prepare("SELECT * FROM intel_device_discovery").all(); const intelList = d.prepare("SELECT * FROM intel_device_discovery").all();
const intelMap = new Map(intelList.map(i => [i.ip_address, i])); const intelMap = new Map(intelList.map(i => [i.ip_address, i]));
if (agentUuid && AGENT_MAC_MAP[agentUuid]) { // Get all devices in latest snapshot from devices table
const macs = AGENT_MAC_MAP[agentUuid]; const devices = d.prepare(`SELECT * FROM devices WHERE fetched_at = ?`).all(latest.t);
const placeholders = macs.map(() => '?').join(',');
// Find latest fetched_at in flows
const latestFlowFetch = d.prepare(`SELECT MAX(fetched_at) as t FROM flows`).get();
// Get unique flow client IPs with bandwidth aggregation
let flowClients = [];
if (latestFlowFetch?.t) {
flowClients = d.prepare(`
SELECT src_ip, src_mac, SUM(bytes_download) as flow_download, SUM(bytes_upload) as flow_upload
FROM flows
WHERE src_mac IN (${placeholders}) AND fetched_at = ?
GROUP BY src_ip
ORDER BY flow_download DESC
`).all(...macs, latestFlowFetch.t);
}
// Get all devices in latest snapshot // Get active flow bandwidth in latest flows snapshot
const devices = d.prepare(`SELECT * FROM devices WHERE fetched_at = ?`).all(latest.t); const latestFlowFetch = d.prepare(`SELECT MAX(fetched_at) as t FROM flows`).get();
const devicesMap = new Map(devices.map(dev => [dev.ip_address, dev])); let flowsBandwidth = [];
if (latestFlowFetch?.t) {
const resolved = []; flowsBandwidth = d.prepare(`
const seenIps = new Set(); SELECT src_ip, src_mac, SUM(bytes_download) as flow_download, SUM(bytes_upload) as flow_upload
FROM flows
// First, add all devices that are explicitly in flows (active) WHERE fetched_at = ?
for (const fc of flowClients) { GROUP BY src_ip
if (!fc.src_ip) continue; `).all(latestFlowFetch.t);
seenIps.add(fc.src_ip);
const devInfo = devicesMap.get(fc.src_ip);
const intelInfo = intelMap.get(fc.src_ip);
const dbLabel = devInfo ? devInfo.device_label : (intelInfo ? intelInfo.device_label : null);
const dbMan = devInfo ? devInfo.manufacturer : (intelInfo ? intelInfo.manufacturer : null);
const dbType = intelInfo ? intelInfo.device_type : null;
const meta = resolveDeviceMetadata(fc.src_ip, fc.src_mac, dbLabel, dbMan, dbType);
const isRouted = fc.src_mac === '04:f4:1c:ce:c2:e6' && fc.src_ip !== '10.6.50.25' && fc.src_ip !== '10.6.12.242';
resolved.push({
id: devInfo ? devInfo.id : null,
site_uuid: devInfo ? devInfo.site_uuid : siteUuid,
fetched_at: latest.t,
mac_address: fc.src_mac,
ip_address: fc.src_ip,
device_label: meta.label,
device_type: meta.type,
os_label: meta.os,
manufacturer: meta.manufacturer,
download: fc.flow_download,
upload: fc.flow_upload,
total: fc.flow_download + fc.flow_upload,
is_gateway_routed: isRouted ? 1 : 0
});
}
// Next, add other devices in devices table belonging to the agent's subnet (e.g. inactive/idle)
for (const dev of devices) {
if (seenIps.has(dev.ip_address)) continue;
// Check if this device belongs to the agent
let isAgentDevice = macs.includes(dev.mac_address);
if (!isAgentDevice && dev.ip_address) {
const devOctets = dev.ip_address.split('.');
if (devOctets.length === 4) {
const prefix3 = devOctets.slice(0, 3).join('.');
isAgentDevice = flowClients.some(fc => fc.src_ip && fc.src_ip.startsWith(prefix3));
}
}
if (isAgentDevice) {
const intelInfo = intelMap.get(dev.ip_address);
const meta = resolveDeviceMetadata(dev.ip_address, dev.mac_address, dev.device_label, dev.manufacturer, intelInfo ? intelInfo.device_type : null);
const isRouted = dev.mac_address === '04:f4:1c:ce:c2:e6' && dev.ip_address !== '10.6.50.25' && dev.ip_address !== '10.6.12.242';
resolved.push({
...dev,
device_label: meta.label,
device_type: meta.type,
os_label: meta.os,
manufacturer: meta.manufacturer,
download: dev.download || 0,
upload: dev.upload || 0,
total: dev.total || 0,
is_gateway_routed: isRouted ? 1 : 0
});
}
}
// Sort by download desc
resolved.sort((a, b) => b.download - a.download);
return resolved.slice(0, limit);
} }
const flowMap = new Map(flowsBandwidth.map(f => [f.src_ip, f]));
// Admin view: apply metadata parsing and gateway routed check for all devices const resolved = [];
const rows = d.prepare(` const seenIps = new Set();
SELECT * FROM devices WHERE (@siteUuid IS NULL OR site_uuid = @siteUuid) AND fetched_at = @fetched_at ORDER BY download DESC LIMIT @limit
`).all({ fetched_at: latest.t, limit, siteUuid });
return rows.map(dev => { function processDevice(ip, mac, dbDev, flowInfo) {
const intelInfo = intelMap.get(dev.ip_address); const intelInfo = intelMap.get(ip);
const meta = resolveDeviceMetadata(dev.ip_address, dev.mac_address, dev.device_label, dev.manufacturer, intelInfo ? intelInfo.device_type : null); const dbLabel = dbDev ? dbDev.device_label : (intelInfo ? intelInfo.device_label : null);
const dbMan = dbDev ? dbDev.manufacturer : (intelInfo ? intelInfo.manufacturer : null);
const isRouted = dev.mac_address === '04:f4:1c:ce:c2:e6' && dev.ip_address !== '10.6.50.25' && dev.ip_address !== '10.6.12.242'; const dbType = intelInfo ? intelInfo.device_type : null;
const meta = resolveDeviceMetadata(ip, mac, dbLabel, dbMan, dbType);
const isRouted = mac === '04:f4:1c:ce:c2:e6' && ip !== '10.6.50.25' && ip !== '10.6.12.242';
const download = flowInfo ? flowInfo.flow_download : (dbDev ? dbDev.download : 0);
const upload = flowInfo ? flowInfo.flow_upload : (dbDev ? dbDev.upload : 0);
return { return {
...dev, id: dbDev ? dbDev.id : null,
mac_address: dev.mac_address, site_uuid: dbDev ? dbDev.site_uuid : siteUuid,
fetched_at: latest.t,
mac_address: mac,
ip_address: ip,
device_label: meta.label, device_label: meta.label,
device_type: meta.type, device_type: meta.type,
os_label: meta.os, os_label: meta.os,
manufacturer: meta.manufacturer, manufacturer: meta.manufacturer,
download: download || 0,
upload: upload || 0,
total: (download || 0) + (upload || 0),
is_gateway_routed: isRouted ? 1 : 0 is_gateway_routed: isRouted ? 1 : 0
}; };
}); }
// 1. Process all devices in the devices table
for (const dev of devices) {
seenIps.add(dev.ip_address);
const flowInfo = flowMap.get(dev.ip_address);
resolved.push(processDevice(dev.ip_address, dev.mac_address, dev, flowInfo));
}
// 2. Process any active flow IPs that are NOT present in the devices table (e.g. dynamic client IPs)
for (const flow of flowsBandwidth) {
if (!flow.src_ip || seenIps.has(flow.src_ip)) continue;
seenIps.add(flow.src_ip);
resolved.push(processDevice(flow.src_ip, flow.src_mac, null, flow));
}
// 3. Filter list based on role (Admin vs Agent)
let filtered = resolved;
if (agentUuid && AGENT_MAC_MAP[agentUuid]) {
const macs = AGENT_MAC_MAP[agentUuid];
filtered = resolved.filter(dev => {
// Direct MAC check
if (macs.includes(dev.mac_address)) {
// But if it is the routed gateway MAC, only keep if it belongs to the agent's subnet/IP range
if (dev.mac_address === '04:f4:1c:ce:c2:e6') {
return dev.ip_address.startsWith('10.6.');
}
return true;
}
// Subnet check for client IPs
if (dev.ip_address) {
const octets = dev.ip_address.split('.');
if (octets.length === 4) {
if (agentUuid === '8A-V3-PB-85') return octets[0] === '10' && octets[1] === '6';
if (agentUuid === '2F-TF-1D-GK') return octets[0] === '10' && octets[1] === '6';
if (agentUuid === 'F6-2V-DT-8A') return octets[0] === '10' && octets[1] === '6';
}
}
return false;
});
} else if (siteUuid) {
filtered = resolved.filter(dev => dev.site_uuid === siteUuid);
}
// 4. Sort by download DESC
filtered.sort((a, b) => b.download - a.download);
return filtered.slice(0, limit);
} }
function getLatestFlows(limit = 100, siteUuid = null, agentUuid = null) { function getLatestFlows(limit = 100, siteUuid = null, agentUuid = null) {
+2 -2
View File
@@ -1756,8 +1756,8 @@ async function fetchDeviceDetails(ip) {
return { return {
ip, ip,
mac_address : mac, mac_address : mac,
total_download : sumRow?.total_download ?? 0, total_download : deviceRow?.download || sumRow?.total_download || 0,
total_upload : sumRow?.total_upload ?? 0, total_upload : deviceRow?.upload || sumRow?.total_upload || 0,
flow_count : sumRow?.flow_count ?? 0, flow_count : sumRow?.flow_count ?? 0,
device_info, device_info,
top_apps, top_apps,
+74
View File
@@ -0,0 +1,74 @@
const { getLatestDevices } = require('../database');
function runTest() {
console.log('=== STARTING TDD TEST FOR DEVICES BANDWIDTH ===');
// Test 1: Admin View
console.log('\nRunning Test 1: Admin View...');
const adminDevices = getLatestDevices(100, null, null);
if (!Array.isArray(adminDevices)) {
throw new Error('Admin devices should be an array');
}
console.log(`- Retrieved ${adminDevices.length} devices for Admin`);
// Verify descending sort
for (let i = 1; i < adminDevices.length; i++) {
if (adminDevices[i].download > adminDevices[i-1].download) {
throw new Error(`Admin devices not sorted correctly at index ${i}: ${adminDevices[i].download} > ${adminDevices[i-1].download}`);
}
}
console.log('- Verified sort order is descending');
// Find any active reconstructed flow client (which has null id because it is not in the devices table)
const activeReconstructed = adminDevices.find(d => d.id === null && d.download > 0 && d.ip_address.startsWith('10.6.'));
if (!activeReconstructed) {
throw new Error('Should find at least one active reconstructed flow client (IP starting with 10.6. and id = null) in Admin view');
}
console.log(`- Active reconstructed device ${activeReconstructed.ip_address} has download: ${activeReconstructed.download} bytes (OK)`);
// Verify specific historical device (e.g., 10.250.192.202) has non-zero fallback bandwidth
const historicalDevice = adminDevices.find(d => d.ip_address === '10.250.192.202');
if (!historicalDevice) {
throw new Error('Historical device 10.250.192.202 should be present in Admin view');
}
if (historicalDevice.download === 0) {
throw new Error(`Historical device 10.250.192.202 should have non-zero fallback download, got ${historicalDevice.download}`);
}
console.log(`- Historical device 10.250.192.202 has download: ${historicalDevice.download} bytes (OK)`);
// Verify no duplicate IP addresses
const seenIps = new Set();
for (const d of adminDevices) {
if (seenIps.has(d.ip_address)) {
throw new Error(`Duplicate IP address found: ${d.ip_address}`);
}
seenIps.add(d.ip_address);
}
console.log('- Verified no duplicate IP addresses exist in the output');
// Test 2: Agent View
console.log('\nRunning Test 2: Agent View...');
const agentUuid = '8A-V3-PB-85'; // IFG LT.18
const agentDevices = getLatestDevices(100, null, agentUuid);
if (!Array.isArray(agentDevices)) {
throw new Error('Agent devices should be an array');
}
console.log(`- Retrieved ${agentDevices.length} devices for Agent ${agentUuid}`);
// Verify only agent devices are returned (IP starts with 10.6.)
for (const d of agentDevices) {
if (d.ip_address && !d.ip_address.startsWith('10.6.')) {
throw new Error(`Non-agent device IP ${d.ip_address} found in Agent view`);
}
}
console.log('- Verified all returned devices belong to the agent\'s subnet (10.6.x.x)');
console.log('\n=== ALL TESTS PASSED SUCCESSFULLY! ===');
}
try {
runTest();
} catch (error) {
console.error('\n❌ TEST FAILED:', error.message);
process.exit(1);
}
+14 -8
View File
@@ -38,21 +38,27 @@ export default function DevicesPage() {
}, },
{ {
header: "MAC Address", header: "MAC Address",
accessor: (row) => accessor: (row) => {
row.mac_address ? ( const isRouted = row.is_gateway_routed === 1;
<div className="flex flex-col gap-1"> const hasMan = row.manufacturer && row.manufacturer !== "-" && row.manufacturer !== "Unknown";
<code className="text-xs bg-secondary/50 px-1 py-0.5 rounded text-muted-foreground w-fit"> const info = hasMan ? row.manufacturer : (row.device_type && row.device_type !== "-" && row.device_type !== "Generic Client" ? row.device_type : "");
return row.mac_address ? (
<div className="flex flex-col gap-0.5">
<code className="text-xs bg-secondary/50 px-1 py-0.5 rounded text-muted-foreground w-fit font-mono">
{row.mac_address} {row.mac_address}
</code> </code>
{row.is_gateway_routed === 1 && ( {(isRouted || info) && (
<span className="text-[10px] text-amber-500/80 font-sans italic"> <span className={`text-[10px] font-sans font-medium ${isRouted ? "text-amber-500/90 italic" : "text-slate-400/80"}`}>
Via Routed Gateway {isRouted ? "Via Routed Gateway" : ""}
{isRouted && info ? ` • ${info}` : (!isRouted ? info : "")}
</span> </span>
)} )}
</div> </div>
) : ( ) : (
"-" "-"
), );
},
}, },
{ header: "Type", accessor: (row) => row.device_type || "-" }, { header: "Type", accessor: (row) => row.device_type || "-" },
{ header: "OS", accessor: (row) => row.os_label || "-" }, { header: "OS", accessor: (row) => row.os_label || "-" },