feat: device detail drill-down modal + smart domain/app display
- Add DeviceDetailModal with 8 tabs (Info, Flows, Apps, Encryption, Servers, Password Leaks, IP Reputation, VPN & Events) - Make device cards in AgentDetailModal clickable (nested modal drill-down) - Expand fetchDeviceDetails backend to query all 10 correlated tables (devices, flows, intel_device_discovery, intel_encryption_audit, intel_server_discovery, intel_unencrypted_passwords, intel_ip_reputation, intel_vpn_detection, events, mac_bandwidth) - Smart combined app/domain display: prioritize actual domain names over port-only labels (Port 443 -> scontent.fcgk42-1.fna.fbcdn.net) - Update Flows tab: domain shown in teal monospace, protocols in purple, port-only entries muted - Add 10 new TypeScript interfaces for device detail data types - Add MAC address fallback queries (by MAC when IP yields no results) - Fix fmtBytes for very large values
This commit is contained in:
1 parent
bba75c7dc6
commit
26179a0270
8 files changed
+2171
-400
No files matched your search
@@ -0,0 +1,38 @@
|
||||
const db = require('../backend/database').getDB();
|
||||
|
||||
// Check all flows - how many have domain vs just port
|
||||
const stats = db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN domain IS NOT NULL THEN 1 ELSE 0 END) as with_domain,
|
||||
SUM(CASE WHEN app_label IS NOT NULL AND app_label NOT LIKE 'Port %' THEN 1 ELSE 0 END) as named_app,
|
||||
SUM(CASE WHEN app_label LIKE 'Port %' THEN 1 ELSE 0 END) as port_only,
|
||||
SUM(CASE WHEN app_label IS NULL AND domain IS NULL THEN 1 ELSE 0 END) as both_null
|
||||
FROM flows
|
||||
`).get();
|
||||
console.log('Flow stats:', stats);
|
||||
|
||||
// What are the unique domain values?
|
||||
const uniqueDomains = db.prepare(`SELECT DISTINCT domain FROM flows WHERE domain IS NOT NULL LIMIT 20`).all();
|
||||
console.log('\nUnique domains in DB:', uniqueDomains.length);
|
||||
uniqueDomains.forEach(r => console.log(' ', r.domain));
|
||||
|
||||
// What devices have domain data, and how many?
|
||||
console.log('\n\nDevices with domain+app data:');
|
||||
const devDomains = db.prepare(`
|
||||
SELECT src_ip, src_mac,
|
||||
COUNT(*) total_flows,
|
||||
SUM(CASE WHEN domain IS NOT NULL THEN 1 ELSE 0 END) domain_flows,
|
||||
SUM(CASE WHEN app_label IS NOT NULL AND app_label NOT LIKE 'Port %' THEN 1 ELSE 0 END) named_flows,
|
||||
GROUP_CONCAT(DISTINCT domain) sample_domains
|
||||
FROM flows
|
||||
WHERE domain IS NOT NULL OR (app_label IS NOT NULL AND app_label NOT LIKE 'Port %')
|
||||
GROUP BY src_ip
|
||||
ORDER BY domain_flows DESC
|
||||
LIMIT 10
|
||||
`).all();
|
||||
devDomains.forEach(r => {
|
||||
const {sample_domains, ...rest} = r;
|
||||
console.log(JSON.stringify(rest));
|
||||
if (sample_domains) console.log(' domains:', sample_domains.split(',').slice(0,3).join(', '));
|
||||
});
|
||||
@@ -0,0 +1,325 @@
|
||||
// patch_device_details.js — replaces fetchDeviceDetails in netify.js
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filePath = path.join(__dirname, '..', 'backend', 'netify.js');
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
const startMarker = '// Fetch data for a specific device IP — from local DB flows + intel tables\r\nasync function fetchDeviceDetails(ip) {';
|
||||
const endMarker = '}\r\n\r\n// Fetch data for a specific application';
|
||||
|
||||
const startIdx = content.indexOf(startMarker);
|
||||
const endIdx = content.indexOf('// Fetch data for a specific application');
|
||||
|
||||
if (startIdx === -1) { console.error('START not found'); process.exit(1); }
|
||||
if (endIdx === -1) { console.error('END not found'); process.exit(1); }
|
||||
|
||||
console.log(`Found fetchDeviceDetails: char ${startIdx} → ${endIdx}`);
|
||||
|
||||
const replacement = `// Fetch data for a specific device IP — from local DB (all 10 correlated tables)
|
||||
async function fetchDeviceDetails(ip) {
|
||||
const db = require('./database');
|
||||
const d = db.getDB();
|
||||
|
||||
// ── 0. Resolve MAC from flows (most recent) ──────────────────────────────
|
||||
const macRow = d.prepare(\`SELECT src_mac FROM flows WHERE src_ip = ? AND src_mac IS NOT NULL ORDER BY last_seen DESC LIMIT 1\`).get(ip);
|
||||
const mac = macRow?.src_mac || null;
|
||||
|
||||
// ── 1. Device info from devices table ────────────────────────────────────
|
||||
const deviceRow = d.prepare(\`
|
||||
SELECT device_label, device_type, os_label, manufacturer, download, upload, last_seen
|
||||
FROM devices
|
||||
WHERE ip_address = ?
|
||||
ORDER BY fetched_at DESC
|
||||
LIMIT 1
|
||||
\`).get(ip);
|
||||
|
||||
// ── 2. Discovery info (may differ from devices table) ────────────────────
|
||||
const discRow = d.prepare(\`
|
||||
SELECT device_type, os_label, manufacturer, device_label, is_new
|
||||
FROM intel_device_discovery
|
||||
WHERE ip_address = ?
|
||||
ORDER BY fetched_at DESC
|
||||
LIMIT 1
|
||||
\`).get(ip);
|
||||
|
||||
const device_info = {
|
||||
device_label : deviceRow?.device_label || discRow?.device_label || null,
|
||||
device_type : deviceRow?.device_type || discRow?.device_type || null,
|
||||
os_label : deviceRow?.os_label || discRow?.os_label || null,
|
||||
manufacturer : deviceRow?.manufacturer || discRow?.manufacturer || null,
|
||||
mac_address : mac,
|
||||
is_new : discRow?.is_new ?? null,
|
||||
};
|
||||
|
||||
// ── 3. Top apps (flows GROUP BY app_label) ─────────────────────────────
|
||||
const appRows = d.prepare(\`
|
||||
SELECT app_label,
|
||||
SUM(bytes_download) AS download,
|
||||
SUM(bytes_upload) AS upload,
|
||||
COUNT(*) AS flow_count
|
||||
FROM flows
|
||||
WHERE src_ip = ?
|
||||
AND app_label IS NOT NULL
|
||||
GROUP BY app_label
|
||||
ORDER BY download DESC
|
||||
LIMIT 20
|
||||
\`).all(ip);
|
||||
|
||||
const top_apps = appRows.map(r => ({
|
||||
app_label : r.app_label,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
flow_count : r.flow_count,
|
||||
}));
|
||||
|
||||
// ── 4. Top domains (flows GROUP BY domain) ─────────────────────────────
|
||||
const domainRows = d.prepare(\`
|
||||
SELECT domain,
|
||||
SUM(bytes_download) AS download,
|
||||
SUM(bytes_upload) AS upload,
|
||||
COUNT(*) AS flow_count
|
||||
FROM flows
|
||||
WHERE src_ip = ?
|
||||
AND domain IS NOT NULL
|
||||
GROUP BY domain
|
||||
ORDER BY download DESC
|
||||
LIMIT 20
|
||||
\`).all(ip);
|
||||
|
||||
// ── 5. Recent flows ────────────────────────────────────────────────────
|
||||
const flowRows = d.prepare(\`
|
||||
SELECT dst_ip, dst_port, protocol, app_label, domain,
|
||||
bytes_download AS download, bytes_upload AS upload, last_seen
|
||||
FROM flows
|
||||
WHERE src_ip = ?
|
||||
ORDER BY last_seen DESC
|
||||
LIMIT 100
|
||||
\`).all(ip);
|
||||
|
||||
const flows = flowRows.map(r => ({
|
||||
dst_ip : r.dst_ip,
|
||||
dst_port : r.dst_port,
|
||||
protocol : r.protocol,
|
||||
app_label : r.app_label,
|
||||
domain : r.domain,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
last_seen : r.last_seen,
|
||||
}));
|
||||
|
||||
// ── 6. Totals ─────────────────────────────────────────────────────────
|
||||
const sumRow = d.prepare(\`
|
||||
SELECT SUM(bytes_download) AS total_download,
|
||||
SUM(bytes_upload) AS total_upload,
|
||||
COUNT(*) AS flow_count
|
||||
FROM flows
|
||||
WHERE src_ip = ?
|
||||
\`).get(ip);
|
||||
|
||||
// ── 7. Encryption audit (latest snapshot) ─────────────────────────────
|
||||
const latestAudit = d.prepare(\`SELECT MAX(fetched_at) AS t FROM intel_encryption_audit\`).get()?.t;
|
||||
const encRow = latestAudit
|
||||
? d.prepare(\`
|
||||
SELECT encrypted_pct, encrypted, unencrypted, total, risk_level, mac_address
|
||||
FROM intel_encryption_audit
|
||||
WHERE fetched_at = ? AND ip_address = ?
|
||||
LIMIT 1
|
||||
\`).get(latestAudit, ip)
|
||||
: null;
|
||||
|
||||
// Also try fallback by MAC if not found by IP
|
||||
const encRowMac = (!encRow && mac && latestAudit)
|
||||
? d.prepare(\`
|
||||
SELECT encrypted_pct, encrypted, unencrypted, total, risk_level, ip_address
|
||||
FROM intel_encryption_audit
|
||||
WHERE fetched_at = ? AND mac_address = ?
|
||||
ORDER BY detected_at DESC
|
||||
LIMIT 1
|
||||
\`).get(latestAudit, mac)
|
||||
: null;
|
||||
|
||||
const encFinal = encRow || encRowMac;
|
||||
|
||||
const encryption = encFinal ? {
|
||||
encrypted_pct : encFinal.encrypted_pct ?? null,
|
||||
encrypted_bytes : encFinal.encrypted ?? null,
|
||||
unencrypted_bytes: encFinal.unencrypted ?? null,
|
||||
total_bytes : encFinal.total ?? null,
|
||||
risk_level : encFinal.risk_level ?? null,
|
||||
} : null;
|
||||
|
||||
// ── 8. Server discovery (servers this device accessed) ─────────────────
|
||||
const serverRows = d.prepare(\`
|
||||
SELECT DISTINCT server_type, hostname, port, protocol, os_label,
|
||||
MAX(download) AS download, MAX(upload) AS upload, MAX(detected_at) AS detected_at
|
||||
FROM intel_server_discovery
|
||||
WHERE ip_address = ?
|
||||
GROUP BY server_type, port, protocol
|
||||
ORDER BY download DESC
|
||||
LIMIT 50
|
||||
\`).all(ip);
|
||||
|
||||
// fallback by MAC if no rows by IP
|
||||
const serverRowsMac = (serverRows.length === 0 && mac)
|
||||
? d.prepare(\`
|
||||
SELECT DISTINCT server_type, hostname, port, protocol, os_label,
|
||||
MAX(download) AS download, MAX(upload) AS upload, MAX(detected_at) AS detected_at
|
||||
FROM intel_server_discovery
|
||||
WHERE mac_address = ?
|
||||
GROUP BY server_type, port, protocol
|
||||
ORDER BY download DESC
|
||||
LIMIT 50
|
||||
\`).all(mac)
|
||||
: [];
|
||||
|
||||
const server_discovery = (serverRows.length > 0 ? serverRows : serverRowsMac).map(r => ({
|
||||
server_type : r.server_type,
|
||||
hostname : r.hostname || null,
|
||||
port : r.port,
|
||||
protocol : r.protocol,
|
||||
os_label : r.os_label || null,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
detected_at : r.detected_at,
|
||||
}));
|
||||
|
||||
// ── 9. Unencrypted passwords ───────────────────────────────────────────
|
||||
let pwdRows = d.prepare(\`
|
||||
SELECT dst_ip, dst_port, protocol, username, severity, download, upload, detected_at
|
||||
FROM intel_unencrypted_passwords
|
||||
WHERE ip_address = ?
|
||||
ORDER BY detected_at DESC
|
||||
LIMIT 50
|
||||
\`).all(ip);
|
||||
|
||||
if (pwdRows.length === 0 && mac) {
|
||||
pwdRows = d.prepare(\`
|
||||
SELECT dst_ip, dst_port, protocol, username, severity, download, upload, detected_at
|
||||
FROM intel_unencrypted_passwords
|
||||
WHERE mac_address = ?
|
||||
ORDER BY detected_at DESC
|
||||
LIMIT 50
|
||||
\`).all(mac);
|
||||
}
|
||||
|
||||
const unencrypted_passwords = pwdRows.map(r => ({
|
||||
dst_ip : r.dst_ip,
|
||||
dst_port : r.dst_port,
|
||||
protocol : r.protocol,
|
||||
username : r.username,
|
||||
severity : r.severity,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
detected_at : r.detected_at,
|
||||
}));
|
||||
|
||||
// ── 10. IP Reputation (latest snapshot, this device's local_ip) ─────────
|
||||
const latestRepSnap = d.prepare(\`SELECT MAX(fetched_at) AS t FROM intel_ip_reputation\`).get()?.t;
|
||||
const repRows = latestRepSnap
|
||||
? d.prepare(\`
|
||||
SELECT ip_address, local_ip, reputation, score, country, app_label, blacklisted, download, upload
|
||||
FROM intel_ip_reputation
|
||||
WHERE fetched_at = ? AND (local_ip = ? OR ip_address = ?)
|
||||
ORDER BY score DESC NULLS LAST
|
||||
LIMIT 30
|
||||
\`).all(latestRepSnap, ip, ip)
|
||||
: [];
|
||||
|
||||
const ip_reputation = repRows.map(r => ({
|
||||
remote_ip : r.ip_address,
|
||||
local_ip : r.local_ip,
|
||||
reputation : r.reputation,
|
||||
score : r.score,
|
||||
country : r.country,
|
||||
app_label : r.app_label,
|
||||
blacklisted : !!r.blacklisted,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
}));
|
||||
|
||||
// ── 11. VPN detection ─────────────────────────────────────────────────
|
||||
let vpnRows = d.prepare(\`
|
||||
SELECT vpn_type, remote_ip, protocol, country, confidence, download, upload, detected_at
|
||||
FROM intel_vpn_detection
|
||||
WHERE ip_address = ?
|
||||
ORDER BY detected_at DESC
|
||||
LIMIT 20
|
||||
\`).all(ip);
|
||||
|
||||
if (vpnRows.length === 0 && mac) {
|
||||
vpnRows = d.prepare(\`
|
||||
SELECT vpn_type, remote_ip, protocol, country, confidence, download, upload, detected_at
|
||||
FROM intel_vpn_detection
|
||||
WHERE mac_address = ?
|
||||
ORDER BY detected_at DESC
|
||||
LIMIT 20
|
||||
\`).all(mac);
|
||||
}
|
||||
|
||||
const vpn_detections = vpnRows.map(r => ({
|
||||
vpn_type : r.vpn_type,
|
||||
remote_ip : r.remote_ip,
|
||||
protocol : r.protocol,
|
||||
country : r.country,
|
||||
confidence : r.confidence,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
detected_at : r.detected_at,
|
||||
}));
|
||||
|
||||
// ── 12. Events ────────────────────────────────────────────────────────
|
||||
const evtByIP = d.prepare(\`SELECT event_type, severity, ip_address, mac_address, description, event_at FROM events WHERE ip_address = ? ORDER BY event_at DESC LIMIT 50\`).all(ip);
|
||||
const evtByMAC = mac ? d.prepare(\`SELECT event_type, severity, ip_address, mac_address, description, event_at FROM events WHERE mac_address = ? ORDER BY event_at DESC LIMIT 50\`).all(mac) : [];
|
||||
|
||||
const seenEvt = new Set();
|
||||
const evtMerged = [];
|
||||
for (const r of [...evtByIP, ...evtByMAC]) {
|
||||
const key = \`\${r.event_type}:\${r.event_at}\`;
|
||||
if (!seenEvt.has(key)) {
|
||||
seenEvt.add(key);
|
||||
evtMerged.push({ event_type: r.event_type, severity: r.severity, ip_address: r.ip_address, mac_address: r.mac_address, description: r.description, event_at: r.event_at });
|
||||
}
|
||||
}
|
||||
evtMerged.sort((a, b) => (b.event_at || '').localeCompare(a.event_at || ''));
|
||||
const events = evtMerged.slice(0, 100);
|
||||
|
||||
// ── 13. MAC bandwidth (latest snapshot) ───────────────────────────────
|
||||
const latestMacSnap = d.prepare(\`SELECT MAX(fetched_at) AS t FROM mac_bandwidth\`).get()?.t;
|
||||
const macBwRow = (latestMacSnap && mac)
|
||||
? d.prepare(\`SELECT manufacturer, download, upload, total FROM mac_bandwidth WHERE fetched_at = ? AND mac_address = ? LIMIT 1\`).get(latestMacSnap, mac)
|
||||
: null;
|
||||
|
||||
const mac_bandwidth = macBwRow ? {
|
||||
mac_address : mac,
|
||||
manufacturer : macBwRow.manufacturer,
|
||||
download : macBwRow.download ?? 0,
|
||||
upload : macBwRow.upload ?? 0,
|
||||
total : macBwRow.total ?? 0,
|
||||
} : null;
|
||||
|
||||
return {
|
||||
ip,
|
||||
mac_address : mac,
|
||||
total_download : sumRow?.total_download ?? 0,
|
||||
total_upload : sumRow?.total_upload ?? 0,
|
||||
flow_count : sumRow?.flow_count ?? 0,
|
||||
device_info,
|
||||
top_apps,
|
||||
top_domains : domainRows.map(r => ({ domain: r.domain, download: r.download ?? 0, upload: r.upload ?? 0, flow_count: r.flow_count })),
|
||||
flows,
|
||||
encryption,
|
||||
server_discovery,
|
||||
unencrypted_passwords,
|
||||
ip_reputation,
|
||||
vpn_detections,
|
||||
events,
|
||||
mac_bandwidth,
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch data for a specific application`;
|
||||
|
||||
const newContent = content.slice(0, startIdx) + replacement + content.slice(endIdx);
|
||||
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||
console.log('SUCCESS: Patched fetchDeviceDetails, new file length:', newContent.length);
|
||||
@@ -0,0 +1,196 @@
|
||||
// patch_netify.js — patches the fetchAgentDetails function in netify.js
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filePath = path.join(__dirname, '..', 'backend', 'netify.js');
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Find the start marker (after the top_apps mapping block)
|
||||
const startMarker = ' // ── 2. Distinct devices for this agent ─────────────────────────────────────\n const devRows = d.prepare(`\n WHERE src_mac IN (${ph})\n ORDER BY last_seen DESC\n LIMIT 50\n `).all(...macs);';
|
||||
|
||||
const endMarker = 'return { agent_uuid: agentUuid, agent_label: label, summary, devices, flows, top_apps };\r\n}';
|
||||
|
||||
const startIdx = content.indexOf(' // ── 2. Distinct devices for this agent ─────────────────────────────────────');
|
||||
const endIdx = content.indexOf('return { agent_uuid: agentUuid, agent_label: label, summary, devices, flows, top_apps };\r\n}');
|
||||
|
||||
if (startIdx === -1) { console.error('START MARKER NOT FOUND'); process.exit(1); }
|
||||
if (endIdx === -1) { console.error('END MARKER NOT FOUND'); process.exit(1); }
|
||||
|
||||
console.log(`Found start at char ${startIdx}, end at char ${endIdx}`);
|
||||
|
||||
const endOffset = endIdx + endMarker.length;
|
||||
|
||||
const replacement = ` // ── 2. Distinct devices for this agent ─────────────────────────────────────
|
||||
const devRows = d.prepare(\`
|
||||
SELECT f.src_ip AS ip_address,
|
||||
f.src_mac AS mac_address,
|
||||
d.device_label,
|
||||
d.device_type,
|
||||
d.os_label,
|
||||
d.manufacturer,
|
||||
SUM(f.bytes_download) AS dl,
|
||||
SUM(f.bytes_upload) AS ul,
|
||||
MAX(f.last_seen) AS last_seen
|
||||
FROM flows f
|
||||
LEFT JOIN (
|
||||
SELECT ip_address, device_label, device_type, os_label, manufacturer
|
||||
FROM devices
|
||||
GROUP BY ip_address
|
||||
) d ON d.ip_address = f.src_ip
|
||||
WHERE f.src_mac IN (\${ph})
|
||||
GROUP BY f.src_ip
|
||||
ORDER BY dl DESC
|
||||
LIMIT 100
|
||||
\`).all(...macs);
|
||||
|
||||
const agentIPs = [...new Set(devRows.map(r => r.ip_address).filter(Boolean))];
|
||||
const phIPs = agentIPs.length > 0 ? agentIPs.map(() => '?').join(',') : null;
|
||||
|
||||
const latestEncAudit = d.prepare(\`SELECT MAX(fetched_at) AS t FROM intel_encryption_audit\`).get()?.t;
|
||||
const riskMap = {};
|
||||
if (latestEncAudit) {
|
||||
const riskRows = d.prepare(\`SELECT ip_address, encrypted_pct, risk_level FROM intel_encryption_audit WHERE fetched_at = ?\`).all(latestEncAudit);
|
||||
for (const r of riskRows) {
|
||||
if (r.ip_address) riskMap[r.ip_address] = { encrypted_pct: r.encrypted_pct, risk_level: r.risk_level };
|
||||
}
|
||||
}
|
||||
|
||||
const insecureIPs = new Set(
|
||||
phIPs ? d.prepare(\`SELECT DISTINCT ip_address FROM intel_insecure_protocols WHERE ip_address IN (\${phIPs})\`).all(...agentIPs).map(r => r.ip_address) : []
|
||||
);
|
||||
|
||||
const devices = devRows.map(r => ({
|
||||
ip_address : r.ip_address,
|
||||
mac_address : r.mac_address,
|
||||
device_label : r.device_label || r.ip_address || 'Unknown',
|
||||
device_type : r.device_type || null,
|
||||
os_label : r.os_label || null,
|
||||
manufacturer : r.manufacturer || null,
|
||||
last_seen : r.last_seen || null,
|
||||
download : r.dl ?? 0,
|
||||
upload : r.ul ?? 0,
|
||||
encrypted_pct: riskMap[r.ip_address]?.encrypted_pct ?? null,
|
||||
risk_level : riskMap[r.ip_address]?.risk_level ?? null,
|
||||
has_insecure : insecureIPs.has(r.ip_address),
|
||||
}));
|
||||
|
||||
// ── 3. Recent flows for this agent ─────────────────────────────────────────
|
||||
const flowRows = d.prepare(\`
|
||||
SELECT src_ip, dst_ip, dst_port, protocol, app_label, domain,
|
||||
bytes_download AS download, bytes_upload AS upload, last_seen
|
||||
FROM flows
|
||||
WHERE src_mac IN (\${ph})
|
||||
ORDER BY last_seen DESC
|
||||
LIMIT 100
|
||||
\`).all(...macs);
|
||||
|
||||
const flows = flowRows.map(r => ({
|
||||
src_ip : r.src_ip,
|
||||
dst_ip : r.dst_ip,
|
||||
dst_port : r.dst_port,
|
||||
protocol : r.protocol,
|
||||
app_label : r.app_label,
|
||||
domain : r.domain,
|
||||
download : r.download ?? 0,
|
||||
upload : r.upload ?? 0,
|
||||
last_seen : r.last_seen,
|
||||
}));
|
||||
|
||||
// ── 4. Summary stats ────────────────────────────────────────────────────────
|
||||
const sumRow = d.prepare(\`
|
||||
SELECT COUNT(DISTINCT src_ip) AS device_count,
|
||||
COUNT(*) AS flow_count,
|
||||
SUM(bytes_download) AS total_download,
|
||||
SUM(bytes_upload) AS total_upload
|
||||
FROM flows
|
||||
WHERE src_mac IN (\${ph})
|
||||
\`).get(...macs);
|
||||
|
||||
const summary = sumRow ? {
|
||||
total_devices : sumRow.device_count ?? 0,
|
||||
active_flows : sumRow.flow_count ?? 0,
|
||||
bandwidth_down : sumRow.total_download ?? 0,
|
||||
bandwidth_up : sumRow.total_upload ?? 0,
|
||||
} : null;
|
||||
|
||||
// ── 5. Security Intel filtered by agent IPs & MACs ─────────────────────────
|
||||
const encryptionRows = (latestEncAudit && phIPs)
|
||||
? d.prepare(\`SELECT ip_address, mac_address, device_label, encrypted_pct, unencrypted, encrypted, total, risk_level, detected_at FROM intel_encryption_audit WHERE fetched_at = ? AND ip_address IN (\${phIPs}) ORDER BY CASE risk_level WHEN 'Rawan' THEN 1 WHEN 'Sedang' THEN 2 ELSE 3 END\`).all(latestEncAudit, ...agentIPs)
|
||||
: [];
|
||||
|
||||
const insecureProtoRows = phIPs
|
||||
? d.prepare(\`SELECT ip_address, mac_address, protocol, risk, app_label, dst_ip, dst_port, download, upload, detected_at FROM intel_insecure_protocols WHERE ip_address IN (\${phIPs}) ORDER BY detected_at DESC LIMIT 50\`).all(...agentIPs)
|
||||
: [];
|
||||
|
||||
let unencPwdRows = d.prepare(\`SELECT ip_address, mac_address, dst_ip, dst_port, protocol, username, severity, download, upload, detected_at FROM intel_unencrypted_passwords WHERE mac_address IN (\${ph}) ORDER BY detected_at DESC LIMIT 50\`).all(...macs);
|
||||
if (unencPwdRows.length === 0 && phIPs) {
|
||||
unencPwdRows = d.prepare(\`SELECT ip_address, mac_address, dst_ip, dst_port, protocol, username, severity, download, upload, detected_at FROM intel_unencrypted_passwords WHERE ip_address IN (\${phIPs}) ORDER BY detected_at DESC LIMIT 50\`).all(...agentIPs);
|
||||
}
|
||||
|
||||
const latestRepSnap = d.prepare(\`SELECT MAX(fetched_at) AS t FROM intel_ip_reputation\`).get()?.t;
|
||||
const ipReputRows = (latestRepSnap && phIPs)
|
||||
? d.prepare(\`SELECT ip_address, local_ip, mac_address, reputation, score, country, app_label, blacklisted, download, upload, detected_at FROM intel_ip_reputation WHERE fetched_at = ? AND (local_ip IN (\${phIPs}) OR ip_address IN (\${phIPs})) ORDER BY score DESC LIMIT 50\`).all(latestRepSnap, ...agentIPs, ...agentIPs)
|
||||
: [];
|
||||
|
||||
let torRows = d.prepare(\`SELECT ip_address, mac_address, exit_node, circuit_id, country, download, upload, detected_at FROM intel_tor_detection WHERE mac_address IN (\${ph}) ORDER BY detected_at DESC LIMIT 20\`).all(...macs);
|
||||
if (torRows.length === 0 && phIPs) {
|
||||
torRows = d.prepare(\`SELECT ip_address, mac_address, exit_node, circuit_id, country, download, upload, detected_at FROM intel_tor_detection WHERE ip_address IN (\${phIPs}) ORDER BY detected_at DESC LIMIT 20\`).all(...agentIPs);
|
||||
}
|
||||
|
||||
let vpnRows = d.prepare(\`SELECT ip_address, mac_address, vpn_type, remote_ip, protocol, country, confidence, download, upload, detected_at FROM intel_vpn_detection WHERE mac_address IN (\${ph}) ORDER BY detected_at DESC LIMIT 20\`).all(...macs);
|
||||
if (vpnRows.length === 0 && phIPs) {
|
||||
vpnRows = d.prepare(\`SELECT ip_address, mac_address, vpn_type, remote_ip, protocol, country, confidence, download, upload, detected_at FROM intel_vpn_detection WHERE ip_address IN (\${phIPs}) ORDER BY detected_at DESC LIMIT 20\`).all(...agentIPs);
|
||||
}
|
||||
|
||||
const serverDiscRows = phIPs
|
||||
? d.prepare(\`SELECT ip_address, mac_address, server_type, hostname, port, protocol, os_label, download, upload, detected_at FROM intel_server_discovery WHERE ip_address IN (\${phIPs}) ORDER BY detected_at DESC LIMIT 50\`).all(...agentIPs)
|
||||
: [];
|
||||
|
||||
const security = {
|
||||
encryption_audit : encryptionRows,
|
||||
insecure_protocols : insecureProtoRows,
|
||||
unencrypted_passwords: unencPwdRows,
|
||||
ip_reputation : ipReputRows,
|
||||
tor_detections : torRows,
|
||||
vpn_detections : vpnRows,
|
||||
};
|
||||
|
||||
// ── 6. Events filtered by agent IPs & MACs ─────────────────────────────────
|
||||
const eventsByIP = phIPs ? d.prepare(\`SELECT event_id, event_type, severity, ip_address, mac_address, description, event_at FROM events WHERE ip_address IN (\${phIPs}) ORDER BY event_at DESC LIMIT 100\`).all(...agentIPs) : [];
|
||||
const eventsByMAC = d.prepare(\`SELECT event_id, event_type, severity, ip_address, mac_address, description, event_at FROM events WHERE mac_address IN (\${ph}) ORDER BY event_at DESC LIMIT 100\`).all(...macs);
|
||||
|
||||
const seenEvt = new Set();
|
||||
const allEvents = [];
|
||||
for (const r of [...eventsByIP, ...eventsByMAC]) {
|
||||
const key = r.event_id || \`\${r.ip_address}:\${r.event_at}\`;
|
||||
if (!seenEvt.has(key)) {
|
||||
seenEvt.add(key);
|
||||
allEvents.push({ event_id: r.event_id, event_type: r.event_type, severity: r.severity, ip_address: r.ip_address, mac_address: r.mac_address, description: r.description, event_at: r.event_at });
|
||||
}
|
||||
}
|
||||
allEvents.sort((a, b) => (b.event_at || '').localeCompare(a.event_at || ''));
|
||||
const events = allEvents.slice(0, 100);
|
||||
|
||||
// ── 7. MAC bandwidth for this agent's MACs ──────────────────────────────────
|
||||
const latestMacSnap = d.prepare(\`SELECT MAX(fetched_at) AS t FROM mac_bandwidth\`).get()?.t;
|
||||
const mac_bandwidth = latestMacSnap
|
||||
? d.prepare(\`SELECT mac_address, manufacturer, download, upload, total FROM mac_bandwidth WHERE fetched_at = ? AND mac_address IN (\${ph}) ORDER BY download DESC\`).all(latestMacSnap, ...macs)
|
||||
: [];
|
||||
|
||||
return {
|
||||
agent_uuid : agentUuid,
|
||||
agent_label : label,
|
||||
summary,
|
||||
devices,
|
||||
flows,
|
||||
top_apps,
|
||||
security,
|
||||
events,
|
||||
mac_bandwidth,
|
||||
server_discovery: serverDiscRows,
|
||||
};
|
||||
}`;
|
||||
|
||||
const newContent = content.slice(0, startIdx) + replacement + content.slice(endOffset);
|
||||
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||
console.log('SUCCESS: Patched netify.js, new length:', newContent.length);
|
||||
Reference in new issue
Block a user