secubox-openwrt/package/secubox/luci-app-secubox-security-threats/htdocs/luci-static/resources/secubox-security-threats/api.js
CyberMind-FR 5a40e8a61e feat: Major updates - CDN cache with Squid, network modes UI rework, bugfixes
CDN Cache:
- Migrate from nginx to Squid proxy for better caching
- Add aggressive caching rules for Windows Update, Linux repos, Steam, Apple
- Proper firewall integration via UCI (transparent proxy)
- Real-time stats from Squid access logs

Network Modes:
- Complete UI rework with MirrorBox dark theme
- 9 network modes with emojis and descriptions
- Dynamic CSS animations and modern styling

Fixes:
- Fix jshn boolean handling in secubox-recovery (1/0 vs true/false)
- Fix nDPId RPCD to use netifyd as fallback DPI provider
- Update media-flow and security-threats dashboards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:03:02 +01:00

467 lines
14 KiB
JavaScript

'use strict';
'require baseclass';
'require rpc';
// ==============================================================================
// RPC Method Declarations
// ==============================================================================
var callStatus = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'status',
expect: { }
});
var callGetActiveThreats = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'get_active_threats',
expect: { threats: [] }
});
var callGetThreatHistory = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'get_threat_history',
params: ['hours'],
expect: { threats: [] }
});
var callGetStatsByType = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'get_stats_by_type',
expect: { }
});
var callGetStatsByHost = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'get_stats_by_host',
expect: { hosts: [] }
});
var callGetBlockedIPs = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'get_blocked_ips',
expect: { blocked: [] }
});
var callBlockThreat = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'block_threat',
params: ['ip', 'duration', 'reason'],
expect: { }
});
var callWhitelistHost = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'whitelist_host',
params: ['ip', 'reason'],
expect: { }
});
var callRemoveWhitelist = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'remove_whitelist',
params: ['ip'],
expect: { }
});
var callGetSecurityStats = rpc.declare({
object: 'luci.secubox-security-threats',
method: 'get_security_stats',
expect: { }
});
// ==============================================================================
// nDPId Integration for Device Detection
// ==============================================================================
var callNdpidStatus = rpc.declare({
object: 'luci.ndpid',
method: 'get_service_status',
expect: { }
});
var callNdpidFlows = rpc.declare({
object: 'luci.ndpid',
method: 'get_detailed_flows',
expect: { flows: [] }
});
var callNdpidTopApps = rpc.declare({
object: 'luci.ndpid',
method: 'get_top_applications',
expect: { applications: [] }
});
var callNdpidCategories = rpc.declare({
object: 'luci.ndpid',
method: 'get_categories',
expect: { categories: [] }
});
// ==============================================================================
// Utility Functions
// ==============================================================================
/**
* Get color for severity level
* @param {string} severity - Severity level (critical, high, medium, low)
* @returns {string} Hex color code
*/
function getSeverityColor(severity) {
var colors = {
'critical': '#d32f2f', // Red
'high': '#ff5722', // Deep Orange
'medium': '#ff9800', // Orange
'low': '#ffc107' // Amber
};
return colors[severity] || '#666';
}
/**
* Get icon for threat category
* @param {string} category - Threat category
* @returns {string} Unicode emoji icon
*/
function getThreatIcon(category) {
var icons = {
'malware': '🦠',
'web_attack': '⚔️',
'anomaly': '⚠️',
'protocol': '🚫',
'tls_issue': '🔒',
'other': '❓'
};
return icons[category] || '❓';
}
/**
* Format risk flags for display
* @param {Array} risks - Array of risk flag names
* @returns {string} Formatted risk flags
*/
function formatRiskFlags(risks) {
if (!risks || !Array.isArray(risks)) return 'N/A';
return risks.map(function(risk) {
// Convert MALICIOUS_JA3 to "Malicious JA3"
return risk.toString().split('_').map(function(word) {
return word.charAt(0) + word.slice(1).toLowerCase();
}).join(' ');
}).join(', ');
}
/**
* Get human-readable category label
* @param {string} category - Category code
* @returns {string} Display label
*/
function getCategoryLabel(category) {
var labels = {
'malware': 'Malware',
'web_attack': 'Web Attack',
'anomaly': 'Network Anomaly',
'protocol': 'Protocol Threat',
'tls_issue': 'TLS/Certificate',
'other': 'Other'
};
return labels[category] || 'Unknown';
}
/**
* Format duration string (4h, 24h, etc.)
* @param {string} duration - Duration string
* @returns {string} Formatted duration
*/
function formatDuration(duration) {
if (!duration) return 'N/A';
return duration;
}
/**
* Format timestamp to localized string
* @param {string} timestamp - ISO 8601 timestamp
* @returns {string} Formatted timestamp
*/
function formatTimestamp(timestamp) {
if (!timestamp) return 'N/A';
try {
var date = new Date(timestamp);
return date.toLocaleString();
} catch(e) {
return timestamp;
}
}
/**
* Format relative time (e.g., "5 minutes ago")
* @param {string} timestamp - ISO 8601 timestamp
* @returns {string} Relative time string
*/
function formatRelativeTime(timestamp) {
if (!timestamp) return 'N/A';
try {
var date = new Date(timestamp);
var now = new Date();
var seconds = Math.floor((now - date) / 1000);
if (seconds < 60) return seconds + 's ago';
if (seconds < 3600) return Math.floor(seconds / 60) + 'm ago';
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h ago';
return Math.floor(seconds / 86400) + 'd ago';
} catch(e) {
return timestamp;
}
}
/**
* Format bytes to human-readable size
* @param {number} bytes - Byte count
* @returns {string} Formatted size (e.g., "1.5 MB")
*/
function formatBytes(bytes) {
if (!bytes || bytes === 0) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
}
/**
* Get badge HTML for severity
* @param {string} severity - Severity level
* @returns {string} HTML string
*/
function getSeverityBadge(severity) {
var color = getSeverityColor(severity);
var label = severity.charAt(0).toUpperCase() + severity.slice(1);
return '<span style="display: inline-block; padding: 2px 8px; border-radius: 3px; background: ' + color + '; color: white; font-size: 0.85em; font-weight: bold;">' + label + '</span>';
}
/**
* Device type classification based on applications/protocols
*/
var deviceTypes = {
'streaming': { icon: '📺', zone: 'media', apps: ['Netflix', 'YouTube', 'Twitch', 'Spotify', 'AppleTV', 'Disney'] },
'gaming': { icon: '🎮', zone: 'gaming', apps: ['Steam', 'PlayStation', 'Xbox', 'Nintendo', 'Discord'] },
'iot': { icon: '🏠', zone: 'iot', apps: ['MQTT', 'CoAP', 'Zigbee', 'HomeKit', 'Alexa', 'GoogleHome'] },
'work': { icon: '💼', zone: 'trusted', apps: ['Teams', 'Zoom', 'Slack', 'Office365', 'Webex'] },
'mobile': { icon: '📱', zone: 'mobile', apps: ['WhatsApp', 'Telegram', 'Instagram', 'TikTok', 'Facebook'] },
'security': { icon: '🔒', zone: 'secure', apps: ['VPN', 'WireGuard', 'OpenVPN', 'SSH', 'HTTPS'] },
'unknown': { icon: '❓', zone: 'guest', apps: [] }
};
/**
* Zone definitions with firewall suggestions
*/
var networkZones = {
'trusted': { icon: '🏠', color: '#2ecc71', access: 'full', desc: 'Full network access' },
'media': { icon: '📺', color: '#9b59b6', access: 'streaming', desc: 'Streaming services only' },
'gaming': { icon: '🎮', color: '#3498db', access: 'gaming', desc: 'Gaming ports & services' },
'iot': { icon: '🤖', color: '#e67e22', access: 'limited', desc: 'Local network only, no WAN' },
'mobile': { icon: '📱', color: '#1abc9c', access: 'filtered', desc: 'Web access with filtering' },
'guest': { icon: '👤', color: '#95a5a6', access: 'isolated', desc: 'Internet only, no LAN' },
'secure': { icon: '🔐', color: '#e74c3c', access: 'vpn', desc: 'VPN/encrypted traffic only' },
'quarantine': { icon: '⛔', color: '#c0392b', access: 'blocked', desc: 'No network access' }
};
/**
* Classify device based on detected applications
* @param {Array} apps - List of detected applications
* @returns {Object} Device classification
*/
function classifyDevice(apps) {
if (!apps || !Array.isArray(apps)) return { type: 'unknown', ...deviceTypes.unknown };
for (var type in deviceTypes) {
var typeApps = deviceTypes[type].apps;
for (var i = 0; i < apps.length; i++) {
for (var j = 0; j < typeApps.length; j++) {
if (apps[i].toLowerCase().indexOf(typeApps[j].toLowerCase()) !== -1) {
return { type: type, ...deviceTypes[type] };
}
}
}
}
return { type: 'unknown', ...deviceTypes.unknown };
}
/**
* Get suggested firewall rules for a device
* @param {Object} device - Device info with classification
* @returns {Array} Suggested firewall rules
*/
function getSuggestedRules(device) {
var zone = device.zone || 'guest';
var rules = [];
switch (zone) {
case 'trusted':
rules.push({ action: 'ACCEPT', desc: 'Allow all traffic' });
break;
case 'media':
rules.push({ action: 'ACCEPT', ports: '443,80,8080', proto: 'tcp', desc: 'HTTPS/HTTP streaming' });
rules.push({ action: 'ACCEPT', ports: '1935', proto: 'tcp', desc: 'RTMP streaming' });
rules.push({ action: 'DROP', desc: 'Block other traffic' });
break;
case 'gaming':
rules.push({ action: 'ACCEPT', ports: '443,80', proto: 'tcp', desc: 'Web services' });
rules.push({ action: 'ACCEPT', ports: '3478-3480,27000-27050', proto: 'udp', desc: 'Gaming ports' });
rules.push({ action: 'DROP', desc: 'Block other traffic' });
break;
case 'iot':
rules.push({ action: 'ACCEPT', dest: 'lan', desc: 'Local network only' });
rules.push({ action: 'DROP', dest: 'wan', desc: 'Block internet access' });
break;
case 'guest':
rules.push({ action: 'ACCEPT', dest: 'wan', ports: '443,80,53', desc: 'Web + DNS only' });
rules.push({ action: 'DROP', dest: 'lan', desc: 'Block local network' });
break;
case 'quarantine':
rules.push({ action: 'DROP', desc: 'Block all traffic' });
break;
default:
rules.push({ action: 'ACCEPT', ports: '443,80,53', desc: 'Basic web access' });
}
return rules;
}
/**
* Get device icon based on MAC vendor or app detection
* @param {Object} device - Device information
* @returns {string} Emoji icon
*/
function getDeviceIcon(device) {
if (device.classification) return device.classification.icon;
if (device.vendor) {
var vendor = device.vendor.toLowerCase();
if (vendor.indexOf('apple') !== -1) return '🍎';
if (vendor.indexOf('samsung') !== -1) return '📱';
if (vendor.indexOf('amazon') !== -1) return '📦';
if (vendor.indexOf('google') !== -1) return '🔍';
if (vendor.indexOf('microsoft') !== -1) return '🪟';
if (vendor.indexOf('sony') !== -1 || vendor.indexOf('playstation') !== -1) return '🎮';
if (vendor.indexOf('intel') !== -1 || vendor.indexOf('dell') !== -1 || vendor.indexOf('hp') !== -1) return '💻';
}
return '📟';
}
/**
* Composite data fetcher for dashboard (with ndpid)
* @returns {Promise} Promise resolving to dashboard data
*/
function getDashboardData() {
return Promise.all([
callStatus(),
callGetActiveThreats(),
callGetStatsByType(),
callGetBlockedIPs(),
callGetSecurityStats(),
callNdpidStatus().catch(function() { return { running: false }; }),
callNdpidFlows().catch(function() { return { flows: [] }; }),
callNdpidTopApps().catch(function() { return { applications: [] }; })
]).then(function(results) {
var ndpidFlows = results[6].flows || [];
var ndpidApps = results[7].applications || [];
// Build device list from ndpid flows
var devicesMap = {};
ndpidFlows.forEach(function(flow) {
var ip = flow.src_ip || flow.local_ip;
if (!ip || ip.indexOf('192.168') === -1) return; // Only local devices
if (!devicesMap[ip]) {
devicesMap[ip] = {
ip: ip,
mac: flow.src_mac || flow.local_mac || '',
hostname: flow.hostname || '',
apps: [],
protocols: [],
bytes_rx: 0,
bytes_tx: 0,
flows: 0,
last_seen: flow.timestamp
};
}
var dev = devicesMap[ip];
if (flow.application && dev.apps.indexOf(flow.application) === -1) {
dev.apps.push(flow.application);
}
if (flow.protocol && dev.protocols.indexOf(flow.protocol) === -1) {
dev.protocols.push(flow.protocol);
}
dev.bytes_rx += flow.bytes_rx || 0;
dev.bytes_tx += flow.bytes_tx || 0;
dev.flows++;
});
// Classify devices and suggest zones
var devices = Object.values(devicesMap).map(function(dev) {
dev.classification = classifyDevice(dev.apps);
dev.suggestedZone = dev.classification.zone;
dev.suggestedRules = getSuggestedRules(dev.classification);
dev.icon = getDeviceIcon(dev);
return dev;
});
return {
status: results[0] || {},
threats: results[1].threats || [],
stats: results[2] || {},
blocked: results[3].blocked || [],
securityStats: results[4] || {},
ndpid: {
running: results[5].running || false,
uptime: results[5].uptime || 0
},
devices: devices,
topApps: ndpidApps,
zones: networkZones
};
});
}
// ==============================================================================
// Exports
// ==============================================================================
return baseclass.extend({
// RPC Methods
getStatus: callStatus,
getActiveThreats: callGetActiveThreats,
getThreatHistory: callGetThreatHistory,
getStatsByType: callGetStatsByType,
getStatsByHost: callGetStatsByHost,
getBlockedIPs: callGetBlockedIPs,
getSecurityStats: callGetSecurityStats,
blockThreat: callBlockThreat,
whitelistHost: callWhitelistHost,
removeWhitelist: callRemoveWhitelist,
// nDPId Methods
getNdpidStatus: callNdpidStatus,
getNdpidFlows: callNdpidFlows,
getNdpidTopApps: callNdpidTopApps,
getNdpidCategories: callNdpidCategories,
// Utility Functions
getSeverityColor: getSeverityColor,
getThreatIcon: getThreatIcon,
formatRiskFlags: formatRiskFlags,
getCategoryLabel: getCategoryLabel,
formatDuration: formatDuration,
formatTimestamp: formatTimestamp,
formatRelativeTime: formatRelativeTime,
formatBytes: formatBytes,
getSeverityBadge: getSeverityBadge,
// Device Classification
classifyDevice: classifyDevice,
getSuggestedRules: getSuggestedRules,
getDeviceIcon: getDeviceIcon,
deviceTypes: deviceTypes,
networkZones: networkZones,
// Composite Fetchers
getDashboardData: getDashboardData
});