feat: complete platform upgrade - user guide overhaul, unified export dropdowns, 24h agent tracking, HelpTrigger implementation on all pages
This commit is contained in:
1 parent
040ef4d45d
commit
64b5e87341
215 files changed
+7886
-4129
No files matched your search
+16
-227
@@ -3,157 +3,11 @@
|
||||
// DPI API wrapper for the BackOne Proxy Server targeting original Netify API.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const { netifyFetch, BASE_URL, agentMap } = require('./netifyClientCore');
|
||||
const { netifyFetch, BASE_URL } = require('./netifyClientCore');
|
||||
const telemetry = require('./netifyTelemetry');
|
||||
const stats = require('./netifyClientStats');
|
||||
|
||||
const PORT_SERVICE_MAP = {
|
||||
80: 'HTTP', 443: 'HTTPS / TLS', 8080: 'HTTP Alt', 8443: 'HTTPS Alt',
|
||||
53: 'DNS', 5353: 'mDNS', 853: 'DNS-over-TLS',
|
||||
25: 'SMTP', 587: 'SMTP TLS', 465: 'SMTPS', 110: 'POP3', 143: 'IMAP',
|
||||
22: 'SSH', 23: 'Telnet', 3389: 'RDP', 5900: 'VNC',
|
||||
21: 'FTP', 20: 'FTP Data', 989: 'FTPS', 990: 'FTPS Control',
|
||||
3306: 'MySQL', 5432: 'PostgreSQL', 6379: 'Redis', 27017: 'MongoDB',
|
||||
1194: 'OpenVPN', 51820: 'WireGuard', 500: 'IPSec IKE', 4500: 'IPSec NAT-T',
|
||||
67: 'DHCP', 68: 'DHCP Client', 123: 'NTP',
|
||||
6881: 'BitTorrent', 6882: 'BitTorrent', 6883: 'BitTorrent',
|
||||
9993: 'ZeroTier VPN',
|
||||
};
|
||||
|
||||
async function fetchAgents(siteUuid = null) {
|
||||
const data = await netifyFetch('/data/stats/top/agent/download', { filter_interval: 43200, settings_limit: 100 }, null, siteUuid);
|
||||
if (!data || !Array.isArray(data)) return [];
|
||||
const list = data.map(r => ({
|
||||
id: r.agent?.id,
|
||||
uuid: r.agent?.uuid,
|
||||
label: r.agent?.label,
|
||||
})).filter(a => a.uuid);
|
||||
|
||||
for (const a of list) {
|
||||
if (a.uuid && a.id) agentMap[a.uuid] = a.id;
|
||||
}
|
||||
|
||||
// Secondary validation: if this site already has data in MongoDB, only return agents
|
||||
// that have at least one summary record for THIS site_uuid. This prevents the
|
||||
// org-level stats endpoint from cross-contaminating agents across sites.
|
||||
if (siteUuid) {
|
||||
try {
|
||||
const mongoose = require('mongoose');
|
||||
if (mongoose.connection.readyState === 1) {
|
||||
const db = mongoose.connection.db;
|
||||
const knownAgents = await db.collection('summaries').distinct('agent_uuid', {
|
||||
site_uuid: siteUuid,
|
||||
agent_uuid: { $ne: null },
|
||||
});
|
||||
|
||||
if (knownAgents.length > 0) {
|
||||
const knownSet = new Set(knownAgents);
|
||||
const validated = list.filter(a => knownSet.has(a.uuid));
|
||||
// If MongoDB cross-check yields results, use the validated list.
|
||||
// On first boot (no DB data yet), fall through and use the full API list.
|
||||
if (validated.length > 0) return validated;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[Collector] fetchAgents DB cross-check failed:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
async function fetchBandwidthSummary(interval = 1440, agentUuid = null, siteUuid = null) {
|
||||
const [dlData, ulData, flowsData] = await Promise.all([
|
||||
netifyFetch('/data/stats/top/local_ip/download', { filter_interval: interval, settings_limit: 500 }, agentUuid, siteUuid),
|
||||
netifyFetch('/data/stats/top/local_ip/upload', { filter_interval: interval, settings_limit: 500 }, agentUuid, siteUuid),
|
||||
netifyFetch('/data/stats/top/local_ip/flow_count', { filter_interval: interval, settings_limit: 500 }, agentUuid, siteUuid),
|
||||
]);
|
||||
const bandwidth_down = dlData?.reduce((s, r) => s + (r.download ?? 0), 0) ?? 0;
|
||||
const bandwidth_up = ulData?.reduce((s, r) => s + (r.upload ?? 0), 0) ?? 0;
|
||||
const total_devices = dlData?.length ?? 0;
|
||||
const active_flows = flowsData?.reduce((s, r) => s + (r.flows ?? r.flow_count ?? 0), 0) ?? 0;
|
||||
return { bandwidth_down, bandwidth_up, total_devices, active_flows, total_threats: 0 };
|
||||
}
|
||||
|
||||
async function fetchTopApps(interval = 1440, limit = 200, agentUuid = null, siteUuid = null) {
|
||||
const [dlData, ulData] = await Promise.all([
|
||||
netifyFetch('/data/stats/top/application/download', { filter_interval: interval, settings_limit: limit }, agentUuid, siteUuid),
|
||||
netifyFetch('/data/stats/top/application/upload', { filter_interval: interval, settings_limit: limit }, agentUuid, siteUuid),
|
||||
]);
|
||||
if (!dlData) return null;
|
||||
const ulMap = {};
|
||||
if (ulData) {
|
||||
for (const r of ulData) {
|
||||
const id = r.application?.id;
|
||||
if (id) ulMap[id] = r.upload ?? 0;
|
||||
}
|
||||
}
|
||||
return dlData.map(r => ({
|
||||
application: {
|
||||
id: r.application?.id ?? null,
|
||||
label: r.application?.label ?? 'Unknown',
|
||||
tag: r.application?.tag ?? null,
|
||||
},
|
||||
download: r.download ?? 0,
|
||||
upload: ulMap[r.application?.id] ?? 0,
|
||||
flows: r.flows ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
async function fetchDiscoveredDevices(interval = 1440, limit = 500, agentUuid = null, siteUuid = null) {
|
||||
const [dlData, ulData] = await Promise.all([
|
||||
netifyFetch('/data/stats/top/local_ip/download', { filter_interval: interval, settings_limit: limit }, agentUuid, siteUuid),
|
||||
netifyFetch('/data/stats/top/local_ip/upload', { filter_interval: interval, settings_limit: limit }, agentUuid, siteUuid),
|
||||
]);
|
||||
if (!dlData) return null;
|
||||
const ulMap = {};
|
||||
if (ulData) {
|
||||
for (const r of ulData) {
|
||||
const ip = r.local_ip?.address ?? String(r.local_ip);
|
||||
ulMap[ip] = r.upload ?? 0;
|
||||
}
|
||||
}
|
||||
return dlData.map(r => {
|
||||
const ip = r.local_ip?.address ?? String(r.local_ip ?? '');
|
||||
return {
|
||||
ip_address: ip,
|
||||
mac_address: r.local_mac ?? null,
|
||||
device_label: r.device_label ?? ip,
|
||||
device_type: r.device_type ?? null,
|
||||
os_label: r.os_label ?? null,
|
||||
manufacturer: r.manufacturer ?? null,
|
||||
download: r.download ?? 0,
|
||||
upload: ulMap[ip] ?? 0,
|
||||
flows: r.flows ?? 0,
|
||||
last_seen: r.last_seen_at?.date ?? null,
|
||||
};
|
||||
}).filter(d => d.ip_address);
|
||||
}
|
||||
|
||||
async function fetchDeviceApps(ipAddress, interval = 1440, limit = 50, agentUuid = null, siteUuid = null) {
|
||||
const ipFilter = JSON.stringify([ipAddress]);
|
||||
const [dlData, ulData] = await Promise.all([
|
||||
netifyFetch('/data/stats/top/application/download', { filter_interval: interval, settings_limit: limit, filter_local_ips: ipFilter }, agentUuid, siteUuid),
|
||||
netifyFetch('/data/stats/top/application/upload', { filter_interval: interval, settings_limit: limit, filter_local_ips: ipFilter }, agentUuid, siteUuid),
|
||||
]);
|
||||
if (!dlData || !Array.isArray(dlData)) return [];
|
||||
const ulMap = {};
|
||||
if (ulData && Array.isArray(ulData)) {
|
||||
for (const r of ulData) {
|
||||
const id = r.application?.id;
|
||||
if (id) ulMap[id] = r.upload ?? 0;
|
||||
}
|
||||
}
|
||||
return dlData.map(r => ({
|
||||
app_label: r.application?.label ?? 'Unknown',
|
||||
app_id: r.application?.id ?? null,
|
||||
download: r.download ?? 0,
|
||||
upload: ulMap[r.application?.id] ?? 0,
|
||||
flows: r.flows ?? 0,
|
||||
})).filter(a => a.download > 0 || a.upload > 0);
|
||||
}
|
||||
|
||||
async function fetchFlows(limit = 10000, agentUuid = null, siteUuid = null) {
|
||||
async function fetchFlows(limit = 1000000, agentUuid = null, siteUuid = null) {
|
||||
const [raw, sniRaw] = await Promise.all([
|
||||
netifyFetch('/data/flows', { settings_limit: limit }, agentUuid, siteUuid),
|
||||
netifyFetch('/data/stats/top/tls_sni/download', { filter_interval: 1440, settings_limit: 50 }, agentUuid, siteUuid),
|
||||
@@ -163,12 +17,14 @@ async function fetchFlows(limit = 10000, agentUuid = null, siteUuid = null) {
|
||||
if (sniRaw && Array.isArray(sniRaw)) {
|
||||
for (const r of sniRaw) {
|
||||
const sni = typeof r.tls_sni === 'object' ? r.tls_sni?.label : r.tls_sni;
|
||||
if (sni && typeof sni === 'string' && sni.trim() !== '') sniList.push(sni.replace(/^\*\./, '').trim());
|
||||
if (sni && typeof sni === 'string' && sni.trim() !== '') {
|
||||
sniList.push(sni.replace(/^\*\./, '').trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
return raw.map(r => {
|
||||
const port = r.remote_port ?? null;
|
||||
const portService = port ? (PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
|
||||
const portService = port ? (stats.PORT_SERVICE_MAP[port] ?? `Port ${port}`) : null;
|
||||
const appLabel = r.application?.label || portService;
|
||||
const domain = r.tls_sni || r.dns_hostname || r.hostname || null;
|
||||
return {
|
||||
@@ -188,84 +44,17 @@ async function fetchFlows(limit = 10000, agentUuid = null, siteUuid = null) {
|
||||
}).filter(f => f.src_ip);
|
||||
}
|
||||
|
||||
async function fetchCyberThreats(agentUuid = null, siteUuid = null) {
|
||||
const ipRepData = await netifyFetch('/data/stats/top/remote_ip/download', { filter_interval: 1440, settings_limit: 50 }, agentUuid, siteUuid);
|
||||
if (!ipRepData || !Array.isArray(ipRepData)) return [];
|
||||
const SUSPICIOUS_PORTS = new Set([23, 4444, 1337, 6667, 31337, 12345, 54321, 4899, 5554, 9999]);
|
||||
const threats = [];
|
||||
for (const r of ipRepData) {
|
||||
const ip = r.remote_ip?.address ?? null;
|
||||
const port = r.remote_port ?? 0;
|
||||
if (ip && SUSPICIOUS_PORTS.has(port)) {
|
||||
threats.push({
|
||||
threat_type: `Suspicious Port ${port}`,
|
||||
severity: 'High',
|
||||
src_ip: null,
|
||||
dst_ip: ip,
|
||||
dst_port: port,
|
||||
protocol: r.ip_protocol?.label ?? null,
|
||||
description: `Suspicious outbound connection to ${ip}:${port}`,
|
||||
event_at: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
return threats;
|
||||
}
|
||||
|
||||
async function fetchEvents(limit = 100, agentUuid = null, siteUuid = null) {
|
||||
const raw = await netifyFetch('/event/events', { settings_limit: limit }, agentUuid, siteUuid);
|
||||
if (!raw || !Array.isArray(raw)) return [];
|
||||
return raw.map(r => {
|
||||
let msg = r.label || '';
|
||||
if (r.description) {
|
||||
try {
|
||||
const descObj = JSON.parse(r.description);
|
||||
msg = descObj.default || r.label || '';
|
||||
if (descObj.tags) {
|
||||
for (const k in descObj.tags) {
|
||||
const tagVal = descObj.tags[k];
|
||||
const val = Array.isArray(tagVal) ? (tagVal[0] === 'Unknown' && tagVal[1] ? tagVal[1] : tagVal[0]) : tagVal;
|
||||
msg = msg.replace(`{{ ${k} }}`, val).replace(`{{${k}}}`, val);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
msg = r.description;
|
||||
}
|
||||
}
|
||||
let sevLabel = 'Info';
|
||||
if (r.severity >= 30) sevLabel = 'Critical';
|
||||
else if (r.severity >= 20) sevLabel = 'High';
|
||||
else if (r.severity >= 10) sevLabel = 'Warning';
|
||||
let srcIp = null;
|
||||
if (r.description) {
|
||||
try {
|
||||
const descObj = JSON.parse(r.description);
|
||||
srcIp = descObj.tags?.device_ip || descObj.tags?.ip || null;
|
||||
} catch {}
|
||||
}
|
||||
return {
|
||||
event_id: r.id || null,
|
||||
event_type: r.basename || 'unknown',
|
||||
severity: sevLabel,
|
||||
description: msg,
|
||||
category_label: r.category?.label || 'Intelligence',
|
||||
ip_address: srcIp,
|
||||
mac_address: r.additional?.device?.mac?.address || null,
|
||||
event_at: r.created_at?.date ? new Date(r.created_at.date) : new Date()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchAgents,
|
||||
fetchBandwidthSummary,
|
||||
fetchTopApps,
|
||||
fetchDiscoveredDevices,
|
||||
fetchDeviceApps,
|
||||
fetchFlows,
|
||||
fetchCyberThreats,
|
||||
fetchEvents,
|
||||
fetchAgents: stats.fetchAgents,
|
||||
fetchBandwidthSummary: stats.fetchBandwidthSummary,
|
||||
fetchTopApps: stats.fetchTopApps,
|
||||
fetchDiscoveredDevices: stats.fetchDiscoveredDevices,
|
||||
fetchDeviceApps: stats.fetchDeviceApps,
|
||||
fetchCyberThreats: stats.fetchCyberThreats,
|
||||
fetchEvents: stats.fetchEvents,
|
||||
syncApplicationDictionary: stats.syncApplicationDictionary,
|
||||
BASE_URL,
|
||||
PORT_SERVICE_MAP,
|
||||
PORT_SERVICE_MAP: stats.PORT_SERVICE_MAP,
|
||||
...telemetry,
|
||||
};
|
||||
Reference in new issue
Block a user