'use strict'; 'require view'; 'require poll'; 'require dom'; 'require ui'; 'require ndpid/api as api'; 'require secubox-theme/theme as Theme'; 'require secubox-portal/header as SbHeader'; var lang = (typeof L !== 'undefined' && L.env && L.env.lang) || (document.documentElement && document.documentElement.getAttribute('lang')) || (navigator.language ? navigator.language.split('-')[0] : 'en'); Theme.init({ language: lang }); var NDPID_NAV = [ { id: 'dashboard', icon: '๐Ÿ“Š', label: 'Dashboard' }, { id: 'flows', icon: '๐Ÿ”', label: 'Flows' }, { id: 'settings', icon: 'โš™๏ธ', label: 'Settings' } ]; function renderNdpidNav(activeId) { return E('div', { 'class': 'sb-app-nav', 'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;' }, NDPID_NAV.map(function(item) { var isActive = activeId === item.id; return E('a', { 'href': L.url('admin', 'secubox', 'ndpid', item.id), 'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' + (isActive ? 'background:linear-gradient(135deg,#667eea,#764ba2);color:white;' : 'color:#a0a0b0;background:transparent;') }, [ E('span', {}, item.icon), E('span', {}, _(item.label)) ]); })); } return view.extend({ title: _('nDPId Flows'), pollInterval: 3, pollActive: true, load: function() { return Promise.all([ api.getRealtimeFlows().catch(function(e) { console.log('getRealtimeFlows error:', e); return {}; }), api.getDetailedFlows().catch(function(e) { console.log('getDetailedFlows error:', e); return { flows: [] }; }), api.getTopApplications().catch(function(e) { console.log('getTopApplications error:', e); return { applications: [] }; }), api.getCategories().catch(function(e) { console.log('getCategories error:', e); return { categories: [] }; }), api.getInterfaceStats().catch(function(e) { console.log('getInterfaceStats error:', e); return { interfaces: [] }; }), api.getTopProtocols().catch(function(e) { console.log('getTopProtocols error:', e); return { protocols: [] }; }) ]).then(function(results) { console.log('nDPId flows.js load results:', results); console.log('Detailed flows:', results[1]); console.log('Applications:', results[2]); console.log('Categories:', results[3]); return { status: results[0], flows: results[1], applications: results[2], categories: results[3], interfaces: results[4], protocols: results[5] }; }); }, getProtoName: function(proto) { var protos = { '6': 'TCP', '17': 'UDP', '1': 'ICMP', 'tcp': 'TCP', 'udp': 'UDP', 'icmp': 'ICMP' }; return protos[proto] || proto || '?'; }, getAppIcon: function(app, category) { var icons = { 'HTTP': '๐ŸŒ', 'HTTPS': '๐Ÿ”’', 'TLS': '๐Ÿ”’', 'SSL': '๐Ÿ”’', 'DNS': '๐Ÿ“ก', 'NTP': '๐Ÿ•', 'DHCP': '๐Ÿ“‹', 'SSH': '๐Ÿ–ฅ๏ธ', 'Telnet': '๐Ÿ’ป', 'YouTube': 'โ–ถ๏ธ', 'Netflix': '๐ŸŽฌ', 'Twitch': '๐ŸŽฎ', 'Facebook': '๐Ÿ‘ค', 'Twitter': '๐Ÿฆ', 'Instagram': '๐Ÿ“ท', 'TikTok': '๐ŸŽต', 'WhatsApp': '๐Ÿ’ฌ', 'Telegram': 'โœˆ๏ธ', 'Discord': '๐ŸŽง', 'BitTorrent': '๐Ÿ“ฅ', 'eDonkey': '๐Ÿ“ฅ', 'Spotify': '๐ŸŽต', 'AppleMusic': '๐ŸŽต', 'Dropbox': '๐Ÿ“ฆ', 'GoogleDrive': '๐Ÿ“ฆ', 'OneDrive': '๐Ÿ“ฆ', 'Zoom': '๐Ÿ“น', 'Teams': '๐Ÿ‘ฅ', 'Skype': '๐Ÿ“ž', 'VPN': '๐Ÿ›ก๏ธ', 'OpenVPN': '๐Ÿ›ก๏ธ', 'WireGuard': '๐Ÿ›ก๏ธ', 'QUIC': 'โšก', 'HTTP2': 'โšก', 'SMTP': '๐Ÿ“ง', 'IMAP': '๐Ÿ“ง', 'POP3': '๐Ÿ“ง', 'FTP': '๐Ÿ“', 'SFTP': '๐Ÿ“', 'SMB': '๐Ÿ“', 'ICMP': '๐Ÿ“ถ', 'IGMP': '๐Ÿ“ก', 'Unknown': 'โ“' }; return icons[app] || icons[category] || '๐Ÿ“ฆ'; }, getCategoryColor: function(category) { var colors = { 'Web': '#3b82f6', 'Video': '#ef4444', 'Streaming': '#f59e0b', 'SocialNetwork': '#ec4899', 'Chat': '#8b5cf6', 'VoIP': '#10b981', 'Game': '#06b6d4', 'Download': '#f97316', 'Cloud': '#6366f1', 'VPN': '#14b8a6', 'Mail': '#84cc16', 'FileTransfer': '#a855f7', 'Network': '#64748b', 'Unknown': '#94a3b8' }; return colors[category] || '#64748b'; }, updateData: function(data) { var self = this; // Update flow counts var status = data.status || {}; var activeEl = document.querySelector('.ndpi-flows-active'); var totalEl = document.querySelector('.ndpi-flows-total'); if (activeEl) { var newActive = api.formatNumber(status.flows_active || 0); if (activeEl.textContent !== newActive) { activeEl.textContent = newActive; activeEl.classList.add('ndpi-value-updated'); setTimeout(function() { activeEl.classList.remove('ndpi-value-updated'); }, 500); } } if (totalEl) { totalEl.textContent = api.formatNumber(status.flow_count || 0); } // Update flows table var flows = Array.isArray(data.flows) ? data.flows : (data.flows || {}).flows || []; var tbody = document.querySelector('.ndpi-flows-tbody'); if (tbody && flows.length > 0) { tbody.innerHTML = ''; flows.slice(0, 50).forEach(function(flow) { var tr = document.createElement('tr'); tr.className = 'ndpi-flow-row ' + (flow.state === 'ended' ? 'ndpi-flow-ended' : 'ndpi-flow-active'); tr.innerHTML = [ '', '' + self.getAppIcon(flow.app, flow.category) + '', '
', '' + (flow.app || 'Unknown') + '', '' + (flow.hostname || '') + '', '
', '', '' + flow.src_ip + ':' + flow.src_port + '', 'โ†’', '' + flow.dst_ip + ':' + flow.dst_port + '', '' + self.getProtoName(flow.proto) + '', '' + (flow.category || 'Unknown') + '', '' + api.formatBytes((flow.bytes_rx || 0) + (flow.bytes_tx || 0)) + '', '' + (flow.state === 'active' ? 'โ—' : 'โ—‹') + '' ].join(''); tbody.appendChild(tr); }); } else if (tbody) { tbody.innerHTML = 'No flows detected yet'; } // Update top applications var apps = Array.isArray(data.applications) ? data.applications : (data.applications || {}).applications || []; var appsContainer = document.querySelector('.ndpi-apps-list'); if (appsContainer && apps.length > 0) { var maxBytes = Math.max.apply(null, apps.map(function(a) { return a.bytes || 0; })) || 1; appsContainer.innerHTML = ''; apps.slice(0, 10).forEach(function(app) { var pct = Math.round(((app.bytes || 0) / maxBytes) * 100); var div = document.createElement('div'); div.className = 'ndpi-app-item'; div.innerHTML = [ '
', '' + self.getAppIcon(app.name, app.category) + '', '' + app.name + '', '' + api.formatBytes(app.bytes || 0) + '', '
', '
', '
' + (app.flows || 0) + ' flows ยท ' + (app.category || 'Unknown') + '
' ].join(''); appsContainer.appendChild(div); }); } }, startPolling: function() { var self = this; this.pollActive = true; poll.add(L.bind(function() { if (!this.pollActive) return Promise.resolve(); return Promise.all([ api.getRealtimeFlows().catch(function() { return {}; }), api.getDetailedFlows().catch(function() { return { flows: [] }; }), api.getTopApplications().catch(function() { return { applications: [] }; }) ]).then(L.bind(function(results) { this.updateData({ status: results[0], flows: results[1], applications: results[2] }); }, this)); }, this), this.pollInterval); }, stopPolling: function() { this.pollActive = false; poll.stop(); }, render: function(data) { var self = this; var status = data.status || {}; // Debug: log raw data console.log('RENDER - raw data.flows:', data.flows); console.log('RENDER - Array.isArray(data.flows):', Array.isArray(data.flows)); // Handle both array and object formats from API var flows = Array.isArray(data.flows) ? data.flows : (data.flows || {}).flows || []; var applications = Array.isArray(data.applications) ? data.applications : (data.applications || {}).applications || []; var categories = Array.isArray(data.categories) ? data.categories : (data.categories || {}).categories || []; var interfaces = Array.isArray(data.interfaces) ? data.interfaces : (data.interfaces || {}).interfaces || []; var protocols = Array.isArray(data.protocols) ? data.protocols : (data.protocols || {}).protocols || []; // Debug: log processed data console.log('RENDER - processed flows.length:', flows.length); console.log('RENDER - processed apps.length:', applications.length); var totalPackets = protocols.reduce(function(sum, p) { return sum + (p.count || 0); }, 0); var view = E('div', { 'class': 'ndpid-dashboard ndpid-flows-page' }, [ E('link', { 'rel': 'stylesheet', 'href': L.resource('ndpid/dashboard.css') }), // Header E('div', { 'class': 'ndpi-header' }, [ E('div', { 'class': 'ndpi-logo' }, [ E('div', { 'class': 'ndpi-logo-icon' }, '๐Ÿ”'), E('div', { 'class': 'ndpi-logo-text' }, ['Deep Packet ', E('span', {}, 'Inspection')]) ]) ]), // Quick Stats E('div', { 'class': 'ndpi-quick-stats' }, [ E('div', { 'class': 'ndpi-quick-stat', 'style': '--stat-gradient: linear-gradient(135deg, #10b981, #34d399)' }, [ E('div', { 'class': 'ndpi-quick-stat-header' }, [ E('span', { 'class': 'ndpi-quick-stat-icon' }, 'โœ…'), E('span', { 'class': 'ndpi-quick-stat-label' }, 'Active Flows') ]), E('div', { 'class': 'ndpi-quick-stat-value ndpi-flows-active' }, api.formatNumber(status.flows_active || 0)), E('div', { 'class': 'ndpi-quick-stat-sub' }, 'Real-time tracking') ]), E('div', { 'class': 'ndpi-quick-stat' }, [ E('div', { 'class': 'ndpi-quick-stat-header' }, [ E('span', { 'class': 'ndpi-quick-stat-icon' }, '๐Ÿ“Š'), E('span', { 'class': 'ndpi-quick-stat-label' }, 'Total Flows') ]), E('div', { 'class': 'ndpi-quick-stat-value ndpi-flows-total' }, api.formatNumber(status.flow_count || 0)), E('div', { 'class': 'ndpi-quick-stat-sub' }, 'Since start') ]), E('div', { 'class': 'ndpi-quick-stat' }, [ E('div', { 'class': 'ndpi-quick-stat-header' }, [ E('span', { 'class': 'ndpi-quick-stat-icon' }, '๐Ÿ“ฑ'), E('span', { 'class': 'ndpi-quick-stat-label' }, 'Applications') ]), E('div', { 'class': 'ndpi-quick-stat-value' }, api.formatNumber(applications.length)), E('div', { 'class': 'ndpi-quick-stat-sub' }, 'Detected') ]), E('div', { 'class': 'ndpi-quick-stat' }, [ E('div', { 'class': 'ndpi-quick-stat-header' }, [ E('span', { 'class': 'ndpi-quick-stat-icon' }, '๐Ÿท๏ธ'), E('span', { 'class': 'ndpi-quick-stat-label' }, 'Categories') ]), E('div', { 'class': 'ndpi-quick-stat-value' }, api.formatNumber(categories.length)), E('div', { 'class': 'ndpi-quick-stat-sub' }, 'Traffic types') ]) ]), // Main content grid E('div', { 'class': 'ndpi-grid-2' }, [ // Flows Table E('div', { 'class': 'ndpi-card ndpi-card-wide' }, [ E('div', { 'class': 'ndpi-card-header' }, [ E('div', { 'class': 'ndpi-card-title' }, [ E('span', { 'class': 'ndpi-card-title-icon' }, '๐Ÿ”'), 'Live Flow Detection' ]), E('div', { 'class': 'ndpi-card-badge' }, flows.length + ' flows') ]), E('div', { 'class': 'ndpi-card-body ndpi-flows-table-container' }, flows.length > 0 ? E('table', { 'class': 'ndpi-table ndpi-flows-table' }, [ E('thead', {}, [ E('tr', {}, [ E('th', {}, 'Application'), E('th', {}, 'Source'), E('th', {}, ''), E('th', {}, 'Destination'), E('th', {}, 'Proto'), E('th', {}, 'Category'), E('th', {}, 'Traffic'), E('th', {}, '') ]) ]), E('tbody', { 'class': 'ndpi-flows-tbody' }, flows.slice(0, 50).map(function(flow) { return E('tr', { 'class': 'ndpi-flow-row ' + (flow.state === 'ended' ? 'ndpi-flow-ended' : 'ndpi-flow-active') }, [ E('td', { 'class': 'ndpi-flow-app' }, [ E('span', { 'class': 'ndpi-app-icon' }, self.getAppIcon(flow.app, flow.category)), E('div', { 'class': 'ndpi-app-info' }, [ E('span', { 'class': 'ndpi-app-name' }, flow.app || 'Unknown'), E('span', { 'class': 'ndpi-app-host' }, flow.hostname || '') ]) ]), E('td', { 'class': 'ndpi-flow-src mono' }, flow.src_ip + ':' + flow.src_port), E('td', { 'class': 'ndpi-flow-arrow' }, 'โ†’'), E('td', { 'class': 'ndpi-flow-dst mono' }, flow.dst_ip + ':' + flow.dst_port), E('td', { 'class': 'ndpi-flow-proto' }, [ E('span', { 'class': 'ndpi-proto-badge ndpi-proto-' + self.getProtoName(flow.proto).toLowerCase() }, self.getProtoName(flow.proto)) ]), E('td', { 'class': 'ndpi-flow-category' }, [ E('span', { 'class': 'ndpi-category-badge', 'style': 'background:' + self.getCategoryColor(flow.category) }, flow.category || 'Unknown') ]), E('td', { 'class': 'ndpi-flow-bytes mono' }, api.formatBytes((flow.bytes_rx || 0) + (flow.bytes_tx || 0))), E('td', { 'class': 'ndpi-flow-state' }, [ E('span', { 'class': 'ndpi-state-' + flow.state }, flow.state === 'active' ? 'โ—' : 'โ—‹') ]) ]); }) ) ]) : E('div', { 'class': 'ndpi-empty' }, [ E('div', { 'class': 'ndpi-empty-icon' }, '๐Ÿ”'), E('div', { 'class': 'ndpi-empty-text' }, 'No flows detected yet'), E('div', { 'class': 'ndpi-empty-hint' }, 'Generate some network traffic to see detection') ]) ) ]), // Top Applications E('div', { 'class': 'ndpi-card' }, [ E('div', { 'class': 'ndpi-card-header' }, [ E('div', { 'class': 'ndpi-card-title' }, [ E('span', { 'class': 'ndpi-card-title-icon' }, '๐Ÿ“ฑ'), 'Top Applications' ]) ]), E('div', { 'class': 'ndpi-card-body' }, applications.length > 0 ? E('div', { 'class': 'ndpi-apps-list' }, (function() { var maxBytes = Math.max.apply(null, applications.map(function(a) { return a.bytes || 0; })) || 1; return applications.slice(0, 10).map(function(app) { var pct = Math.round(((app.bytes || 0) / maxBytes) * 100); return E('div', { 'class': 'ndpi-app-item' }, [ E('div', { 'class': 'ndpi-app-header' }, [ E('span', { 'class': 'ndpi-app-icon' }, self.getAppIcon(app.name, app.category)), E('span', { 'class': 'ndpi-app-name' }, app.name), E('span', { 'class': 'ndpi-app-bytes' }, api.formatBytes(app.bytes || 0)) ]), E('div', { 'class': 'ndpi-app-bar' }, [ E('div', { 'class': 'ndpi-app-bar-fill', 'style': 'width:' + pct + '%;background:' + self.getCategoryColor(app.category) }) ]), E('div', { 'class': 'ndpi-app-meta' }, (app.flows || 0) + ' flows ยท ' + (app.category || 'Unknown')) ]); }); })() ) : E('div', { 'class': 'ndpi-empty' }, [ E('div', { 'class': 'ndpi-empty-icon' }, '๐Ÿ“ฑ'), E('div', { 'class': 'ndpi-empty-text' }, 'No applications detected yet') ]) ) ]) ]), // Protocol & Category breakdown E('div', { 'class': 'ndpi-grid-2' }, [ // Protocol Distribution E('div', { 'class': 'ndpi-card' }, [ E('div', { 'class': 'ndpi-card-header' }, [ E('div', { 'class': 'ndpi-card-title' }, [ E('span', { 'class': 'ndpi-card-title-icon' }, '๐Ÿ“ก'), 'Protocol Distribution' ]) ]), E('div', { 'class': 'ndpi-card-body' }, protocols.length > 0 ? E('div', { 'class': 'ndpi-protocol-grid' }, protocols.map(function(proto) { var pct = totalPackets > 0 ? Math.round((proto.count / totalPackets) * 100) : 0; var color = proto.name === 'TCP' ? '#0ea5e9' : proto.name === 'UDP' ? '#10b981' : '#f59e0b'; return E('div', { 'class': 'ndpi-protocol-item' }, [ E('div', { 'class': 'ndpi-protocol-header' }, [ E('span', { 'class': 'ndpi-protocol-name' }, proto.name), E('span', { 'class': 'ndpi-protocol-count' }, api.formatNumber(proto.count)) ]), E('div', { 'class': 'ndpi-protocol-bar' }, [ E('div', { 'class': 'ndpi-protocol-bar-fill', 'style': 'width:' + pct + '%;background:' + color }) ]), E('div', { 'class': 'ndpi-protocol-pct' }, pct + '%') ]); }) ) : E('div', { 'class': 'ndpi-empty' }, [ E('div', { 'class': 'ndpi-empty-icon' }, '๐Ÿ“ก'), E('div', { 'class': 'ndpi-empty-text' }, 'No protocol data') ]) ) ]), // Categories E('div', { 'class': 'ndpi-card' }, [ E('div', { 'class': 'ndpi-card-header' }, [ E('div', { 'class': 'ndpi-card-title' }, [ E('span', { 'class': 'ndpi-card-title-icon' }, '๐Ÿท๏ธ'), 'Traffic Categories' ]) ]), E('div', { 'class': 'ndpi-card-body' }, categories.length > 0 ? E('div', { 'class': 'ndpi-categories-list' }, (function() { var maxBytes = Math.max.apply(null, categories.map(function(c) { return c.bytes || 0; })) || 1; return categories.slice(0, 8).map(function(cat) { var pct = Math.round(((cat.bytes || 0) / maxBytes) * 100); return E('div', { 'class': 'ndpi-category-item' }, [ E('div', { 'class': 'ndpi-category-header' }, [ E('span', { 'class': 'ndpi-category-name', 'style': 'color:' + self.getCategoryColor(cat.name) }, cat.name), E('span', { 'class': 'ndpi-category-bytes' }, api.formatBytes(cat.bytes || 0)) ]), E('div', { 'class': 'ndpi-category-bar' }, [ E('div', { 'class': 'ndpi-category-bar-fill', 'style': 'width:' + pct + '%;background:' + self.getCategoryColor(cat.name) }) ]), E('div', { 'class': 'ndpi-category-meta' }, (cat.apps || 0) + ' apps ยท ' + (cat.flows || 0) + ' flows') ]); }); })() ) : E('div', { 'class': 'ndpi-empty' }, [ E('div', { 'class': 'ndpi-empty-icon' }, '๐Ÿท๏ธ'), E('div', { 'class': 'ndpi-empty-text' }, 'No categories detected') ]) ) ]) ]) ]); // Start polling this.startPolling(); var wrapper = E('div', { 'class': 'secubox-page-wrapper' }); wrapper.appendChild(SbHeader.render()); wrapper.appendChild(renderNdpidNav('flows')); wrapper.appendChild(view); return wrapper; }, handleSaveApply: null, handleSave: null, handleReset: null });