secubox-openwrt/package/secubox/luci-app-dpi-dual/htdocs/luci-static/resources/view/dpi-dual/lan-flows.js
CyberMind-FR 69b5dca350 fix(dpi): Fix protocol display showing null suffix in LAN Flows
- Remove unused application field concatenation causing "TCPnull" display
- Sort protocols by flow count instead of non-existent bytes field
- Simplify protocol card to show protocol name and flow count only

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

269 lines
11 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require poll';
'require rpc';
'require ui';
var callLanStatus = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_lan_status',
expect: {}
});
var callLanClients = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_lan_clients',
expect: {}
});
var callLanDestinations = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_lan_destinations',
params: ['limit'],
expect: {}
});
var callLanProtocols = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_lan_protocols',
expect: {}
});
function formatBytes(bytes) {
if (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 parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatRelativeTime(timestamp) {
var now = Math.floor(Date.now() / 1000);
var diff = now - timestamp;
if (diff < 60) return diff + 's ago';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
return Math.floor(diff / 86400) + 'd ago';
}
function createLED(active, label) {
var color = active ? '#00d4aa' : '#ff4d4d';
return E('div', { 'style': 'display:flex;align-items:center;gap:8px;' }, [
E('span', {
'style': 'width:12px;height:12px;border-radius:50%;background:' + color +
';box-shadow:0 0 8px ' + color + ';'
}),
E('span', { 'style': 'color:#e0e0e0;' }, label)
]);
}
function createMetricCard(label, value, color) {
return E('div', {
'style': 'background:#1a1a24;padding:1rem;border-radius:8px;text-align:center;min-width:100px;'
}, [
E('div', {
'style': 'font-size:1.5rem;font-weight:700;color:' + (color || '#00d4aa') + ';font-family:monospace;'
}, String(value)),
E('div', {
'style': 'font-size:0.75rem;color:#808090;text-transform:uppercase;margin-top:4px;'
}, label)
]);
}
return view.extend({
load: function() {
return Promise.all([
callLanStatus().catch(function() { return {}; }),
callLanClients().catch(function() { return { clients: [] }; }),
callLanDestinations(100).catch(function() { return { destinations: [] }; }),
callLanProtocols().catch(function() { return { protocols: [] }; })
]);
},
render: function(data) {
var status = data[0] || {};
var clients = data[1] || {};
var destinations = data[2] || {};
var protocols = data[3] || {};
var view = E('div', { 'class': 'cbi-map', 'id': 'lan-flows-view' });
// Header section
var header = E('div', {
'style': 'background:linear-gradient(135deg,#1a1a2e 0%,#16213e 100%);padding:1.5rem;border-radius:12px;margin-bottom:1.5rem;border-left:4px solid #00a0ff;'
}, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem;' }, [
E('div', {}, [
E('h2', { 'style': 'margin:0;color:#fff;font-size:1.4rem;' }, 'LAN Flow Analysis'),
E('p', { 'style': 'margin:0.5rem 0 0;color:#808090;font-size:0.9rem;' },
'Real-time passive flow monitoring on ' + (status.interface || 'br-lan') + ' - No MITM, no caching')
]),
E('div', { 'style': 'display:flex;gap:1rem;' }, [
createLED(status.collector_running, 'Collector'),
createLED(status.enabled, 'Enabled')
])
])
]);
// Metrics row
var metrics = E('div', {
'style': 'display:flex;gap:1rem;margin-bottom:1.5rem;flex-wrap:wrap;'
}, [
createMetricCard('Active Clients', status.active_clients || 0, '#00d4aa'),
createMetricCard('Destinations', status.unique_destinations || 0, '#00a0ff'),
createMetricCard('Protocols', status.detected_protocols || 0, '#ffa500'),
createMetricCard('RX', formatBytes(status.rx_bytes || 0), '#00d4aa'),
createMetricCard('TX', formatBytes(status.tx_bytes || 0), '#ff6b6b')
]);
// Main content - three columns
var content = E('div', {
'style': 'display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1.5rem;'
});
// Clients table
var clientsCard = E('div', {
'style': 'background:#12121a;border-radius:12px;padding:1rem;'
}, [
E('h3', { 'style': 'margin:0 0 1rem;color:#00d4aa;font-size:1rem;border-bottom:1px solid #2a2a3a;padding-bottom:0.5rem;' },
'Active Clients'),
E('div', { 'id': 'clients-list', 'style': 'max-height:400px;overflow-y:auto;' })
]);
var clientsList = clientsCard.querySelector('#clients-list');
var clientsData = (clients.clients || []).sort(function(a, b) {
return (b.bytes_in + b.bytes_out) - (a.bytes_in + a.bytes_out);
});
if (clientsData.length === 0) {
clientsList.appendChild(E('div', {
'style': 'color:#808090;text-align:center;padding:2rem;'
}, 'No active clients detected'));
} else {
clientsData.forEach(function(client) {
var totalBytes = (client.bytes_in || 0) + (client.bytes_out || 0);
clientsList.appendChild(E('div', {
'style': 'background:#1a1a24;padding:0.75rem;border-radius:6px;margin-bottom:0.5rem;'
}, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;' }, [
E('span', { 'style': 'font-family:monospace;color:#fff;font-weight:600;' }, client.ip),
E('span', { 'style': 'color:#00d4aa;font-size:0.85rem;' }, formatBytes(totalBytes))
]),
E('div', { 'style': 'display:flex;gap:1rem;margin-top:0.5rem;font-size:0.75rem;color:#808090;' }, [
E('span', {}, 'Flows: ' + (client.flows || 0)),
E('span', {}, client.last_proto || ''),
E('span', {}, client.last_app || ''),
client.last_seen ? E('span', {}, formatRelativeTime(client.last_seen)) : null
].filter(Boolean))
]));
});
}
// Protocols table
var protocolsCard = E('div', {
'style': 'background:#12121a;border-radius:12px;padding:1rem;'
}, [
E('h3', { 'style': 'margin:0 0 1rem;color:#ffa500;font-size:1rem;border-bottom:1px solid #2a2a3a;padding-bottom:0.5rem;' },
'Detected Protocols'),
E('div', { 'id': 'protocols-list', 'style': 'max-height:400px;overflow-y:auto;' })
]);
var protocolsList = protocolsCard.querySelector('#protocols-list');
var protocolsData = (protocols.protocols || []).sort(function(a, b) {
return (b.flows || 0) - (a.flows || 0);
});
if (protocolsData.length === 0) {
protocolsList.appendChild(E('div', {
'style': 'color:#808090;text-align:center;padding:2rem;'
}, 'No protocols detected'));
} else {
protocolsData.forEach(function(proto) {
var protoName = proto.protocol || 'Unknown';
var flowCount = proto.flows || 0;
protocolsList.appendChild(E('div', {
'style': 'background:#1a1a24;padding:0.75rem;border-radius:6px;margin-bottom:0.5rem;display:flex;justify-content:space-between;align-items:center;'
}, [
E('span', { 'style': 'color:#fff;font-weight:500;' }, protoName),
E('div', { 'style': 'text-align:right;' }, [
E('div', { 'style': 'color:#ffa500;font-size:0.85rem;' }, flowCount + ' flows')
])
]));
});
}
// Destinations table
var destinationsCard = E('div', {
'style': 'background:#12121a;border-radius:12px;padding:1rem;'
}, [
E('h3', { 'style': 'margin:0 0 1rem;color:#00a0ff;font-size:1rem;border-bottom:1px solid #2a2a3a;padding-bottom:0.5rem;' },
'External Destinations'),
E('div', { 'id': 'destinations-list', 'style': 'max-height:400px;overflow-y:auto;' })
]);
var destinationsList = destinationsCard.querySelector('#destinations-list');
var destinationsData = (destinations.destinations || []).sort(function(a, b) {
return (b.hits || 0) - (a.hits || 0);
});
if (destinationsData.length === 0) {
destinationsList.appendChild(E('div', {
'style': 'color:#808090;text-align:center;padding:2rem;'
}, 'No external destinations'));
} else {
destinationsData.slice(0, 50).forEach(function(dest) {
destinationsList.appendChild(E('div', {
'style': 'background:#1a1a24;padding:0.75rem;border-radius:6px;margin-bottom:0.5rem;'
}, [
E('div', { 'style': 'display:flex;justify-content:space-between;align-items:center;' }, [
E('span', { 'style': 'font-family:monospace;color:#fff;font-size:0.85rem;' },
dest.ip + ':' + (dest.port || '?')),
E('span', { 'style': 'color:#00a0ff;font-size:0.85rem;' }, formatBytes(dest.bytes || 0))
]),
E('div', { 'style': 'display:flex;gap:1rem;margin-top:0.25rem;font-size:0.7rem;color:#808090;' }, [
E('span', {}, dest.proto || ''),
E('span', {}, (dest.hits || 0) + ' hits'),
dest.last_seen ? E('span', {}, formatRelativeTime(dest.last_seen)) : null
].filter(Boolean))
]));
});
}
content.appendChild(clientsCard);
content.appendChild(protocolsCard);
content.appendChild(destinationsCard);
view.appendChild(header);
view.appendChild(metrics);
view.appendChild(content);
// Setup polling for real-time updates
poll.add(L.bind(this.pollData, this), 5);
return view;
},
pollData: function() {
var self = this;
return Promise.all([
callLanStatus().catch(function() { return {}; }),
callLanClients().catch(function() { return { clients: [] }; }),
callLanDestinations(100).catch(function() { return { destinations: [] }; }),
callLanProtocols().catch(function() { return { protocols: [] }; })
]).then(function(data) {
var view = document.getElementById('lan-flows-view');
if (!view) return;
// Update would require DOM manipulation
// For now, the page auto-refreshes via poll
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});