secubox-openwrt/package/secubox/luci-app-dpi-dual/htdocs/luci-static/resources/view/dpi-dual/overview.js
CyberMind-FR e1ee84b3eb fix(dashboards): WAF bans cache and DPI LAN flow display
WAF Dashboard:
- Use cached bans from cron (waf-stats-update) instead of slow cscli
- Fixes "Failed to load bans" timeout issue

DPI Dual-Stream:
- Add LAN Flow Analysis card showing active clients, destinations, protocols
- LAN passive flow analysis was working but not displayed

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

327 lines
14 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require poll';
'require rpc';
'require ui';
var callStatus = rpc.declare({
object: 'luci.dpi-dual',
method: 'status',
expect: {}
});
var callGetFlows = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_flows',
expect: {}
});
var callGetThreats = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_threats',
params: ['limit'],
expect: {}
});
var callGetCorrelation = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_correlation',
params: ['limit'],
expect: {}
});
var callStart = rpc.declare({
object: 'luci.dpi-dual',
method: 'start',
expect: {}
});
var callStop = rpc.declare({
object: 'luci.dpi-dual',
method: 'stop',
expect: {}
});
var callRestart = rpc.declare({
object: 'luci.dpi-dual',
method: 'restart',
expect: {}
});
var callCorrelateIP = rpc.declare({
object: 'luci.dpi-dual',
method: 'correlate_ip',
params: ['ip'],
expect: {}
});
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function formatTimestamp(ts) {
if (!ts) return '-';
var d = new Date(ts);
return d.toLocaleTimeString();
}
function createStatusLED(running) {
var color = running ? '#00d4aa' : '#ff4d4d';
var label = running ? 'RUNNING' : 'STOPPED';
return E('span', {
'style': 'display:inline-flex;align-items:center;gap:6px;'
}, [
E('span', {
'style': 'width:12px;height:12px;border-radius:50%;background:' + color +
';box-shadow:0 0 8px ' + color + ';'
}),
E('span', { 'style': 'font-weight:600;color:' + color + ';' }, label)
]);
}
function createCard(title, icon, content, borderColor) {
return E('div', {
'class': 'cbi-section',
'style': 'background:#12121a;border-radius:12px;padding:1rem;margin:0.5rem 0;' +
'border-left:4px solid ' + (borderColor || '#2a2a3a') + ';'
}, [
E('div', {
'style': 'display:flex;align-items:center;gap:8px;margin-bottom:0.8rem;'
}, [
E('span', { 'style': 'font-size:1.3rem;' }, icon),
E('span', { 'style': 'font-size:1.1rem;font-weight:600;color:#fff;' }, title)
]),
E('div', {}, content)
]);
}
function createMetric(label, value, color) {
return E('div', {
'style': 'background:#1a1a24;padding:0.6rem 1rem;border-radius:8px;text-align:center;min-width:80px;'
}, [
E('div', {
'style': 'font-size:1.5rem;font-weight:700;color:' + (color || '#00d4aa') + ';font-family:monospace;'
}, String(value)),
E('div', {
'style': 'font-size:0.7rem;color:#808090;text-transform:uppercase;margin-top:2px;'
}, label)
]);
}
function createThreatRow(threat) {
var scoreColor = threat.threat_score > 70 ? '#ff4d4d' :
threat.threat_score > 40 ? '#ffa500' : '#00d4aa';
return E('tr', {}, [
E('td', { 'style': 'padding:8px;color:#808090;' }, formatTimestamp(threat.timestamp)),
E('td', { 'style': 'padding:8px;font-family:monospace;color:#00a0ff;' }, threat.client_ip || '-'),
E('td', { 'style': 'padding:8px;' }, threat.host || '-'),
E('td', { 'style': 'padding:8px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;' },
threat.path || '-'),
E('td', { 'style': 'padding:8px;' }, (threat.categories || []).join(', ') || '-'),
E('td', { 'style': 'padding:8px;text-align:center;' },
E('span', {
'style': 'background:' + scoreColor + '22;color:' + scoreColor +
';padding:2px 8px;border-radius:10px;font-weight:600;'
}, String(threat.threat_score || 0))
),
E('td', { 'style': 'padding:8px;text-align:center;' },
threat.blocked ?
E('span', { 'style': 'color:#ff4d4d;' }, '🚫') :
E('span', { 'style': 'color:#808090;' }, '-')
)
]);
}
return view.extend({
load: function() {
return Promise.all([
callStatus().catch(function() { return {}; }),
callGetFlows().catch(function() { return {}; }),
callGetThreats(20).catch(function() { return { alerts: [] }; }),
callGetCorrelation(10).catch(function() { return { correlated: [] }; })
]);
},
render: function(data) {
var status = data[0] || {};
var flows = data[1] || {};
var threats = data[2] || {};
var correlation = data[3] || {};
var mitm = status.mitm_stream || {};
var tap = status.tap_stream || {};
var corr = status.correlation || {};
var lan = status.lan_passive || {};
var view = E('div', { 'class': 'cbi-map', 'style': 'background:#0a0a12;min-height:100vh;' }, [
// Header
E('div', { 'style': 'text-align:center;padding:1rem 0;' }, [
E('h1', {
'style': 'font-size:1.8rem;font-weight:700;background:linear-gradient(90deg,#00d4aa,#00a0ff);' +
'-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin:0;'
}, 'DPI Dual-Stream'),
E('div', { 'style': 'color:#606070;margin-top:4px;' },
'Mode: ' + (status.mode || 'dual').toUpperCase())
]),
// Action buttons
E('div', { 'style': 'display:flex;gap:8px;justify-content:center;margin-bottom:1rem;' }, [
E('button', {
'class': 'btn cbi-button cbi-button-apply',
'click': ui.createHandlerFn(this, function() {
return callStart().then(function() {
ui.addNotification(null, E('p', 'DPI started'), 'info');
window.location.reload();
});
})
}, '▶ Start'),
E('button', {
'class': 'btn cbi-button cbi-button-reset',
'click': ui.createHandlerFn(this, function() {
return callStop().then(function() {
ui.addNotification(null, E('p', 'DPI stopped'), 'info');
window.location.reload();
});
})
}, '⏹ Stop'),
E('button', {
'class': 'btn cbi-button',
'click': ui.createHandlerFn(this, function() {
return callRestart().then(function() {
ui.addNotification(null, E('p', 'DPI restarted'), 'info');
window.location.reload();
});
})
}, '🔄 Restart')
]),
// Stream status cards
E('div', { 'style': 'display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1rem;' }, [
// MITM Stream Card
createCard('MITM Stream', '🔍', E('div', {}, [
E('div', { 'style': 'margin-bottom:0.8rem;' }, createStatusLED(mitm.running)),
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:8px;' }, [
createMetric('Buffer', mitm.buffer_entries || 0, '#00d4aa'),
createMetric('Threats', mitm.threats_detected || 0, '#ffa500'),
createMetric('Blocked', mitm.blocked_count || 0, '#ff4d4d')
])
]), mitm.running ? '#00d4aa' : '#ff4d4d'),
// TAP Stream Card
createCard('TAP Stream', '📡', E('div', {}, [
E('div', { 'style': 'margin-bottom:0.8rem;' }, createStatusLED(tap.running)),
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:8px;' }, [
createMetric('Interface', tap.interface || 'tap0', tap.interface_up ? '#00d4aa' : '#808090'),
createMetric('RX', formatBytes(tap.rx_bytes || 0), '#00a0ff'),
createMetric('TX', formatBytes(tap.tx_bytes || 0), '#00a0ff'),
createMetric('Flows/min', tap.flows_1min || 0, '#00d4aa')
])
]), tap.running ? '#00d4aa' : '#ff4d4d'),
// Correlation Card
createCard('Correlation Engine', '🔗', E('div', {}, [
E('div', { 'style': 'margin-bottom:0.8rem;' }, createStatusLED(corr.running)),
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:8px;' }, [
createMetric('Correlated', corr.threats_correlated || 0, '#ffa500')
]),
E('div', { 'style': 'margin-top:0.8rem;' }, [
E('input', {
'type': 'text',
'id': 'correlate-ip',
'placeholder': 'IP to correlate...',
'style': 'background:#1a1a24;border:1px solid #2a2a3a;border-radius:6px;' +
'padding:6px 10px;color:#fff;width:140px;margin-right:8px;'
}),
E('button', {
'class': 'btn cbi-button',
'click': ui.createHandlerFn(this, function() {
var ip = document.getElementById('correlate-ip').value;
if (ip) {
return callCorrelateIP(ip).then(function(res) {
ui.addNotification(null, E('p', res.message || 'Correlation triggered'), 'info');
});
}
})
}, 'Correlate')
])
]), corr.running ? '#00d4aa' : '#808090'),
// LAN Passive Flow Card
lan.enabled ? createCard('LAN Flow Analysis', '🌐', E('div', {}, [
E('div', { 'style': 'margin-bottom:0.8rem;' }, createStatusLED(lan.running)),
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:8px;' }, [
createMetric('Clients', lan.active_clients || 0, '#00d4aa'),
createMetric('Destinations', lan.unique_destinations || 0, '#00a0ff'),
createMetric('Protocols', lan.detected_protocols || 0, '#ffa500')
]),
E('div', { 'style': 'margin-top:8px;color:#808090;font-size:0.8rem;' },
'Interface: ' + (lan.interface || 'br-lan'))
]), lan.running ? '#00d4aa' : '#808090') : E('div')
]),
// Threats Table
createCard('Recent Threats', '⚠️', E('div', {}, [
E('div', { 'style': 'color:#808090;margin-bottom:8px;' },
'Total alerts: ' + (threats.total || 0)),
E('div', { 'style': 'overflow-x:auto;' }, [
E('table', {
'style': 'width:100%;border-collapse:collapse;font-size:0.85rem;'
}, [
E('thead', {}, [
E('tr', { 'style': 'border-bottom:1px solid #2a2a3a;' }, [
E('th', { 'style': 'padding:8px;text-align:left;color:#808090;' }, 'Time'),
E('th', { 'style': 'padding:8px;text-align:left;color:#808090;' }, 'Client IP'),
E('th', { 'style': 'padding:8px;text-align:left;color:#808090;' }, 'Host'),
E('th', { 'style': 'padding:8px;text-align:left;color:#808090;' }, 'Path'),
E('th', { 'style': 'padding:8px;text-align:left;color:#808090;' }, 'Categories'),
E('th', { 'style': 'padding:8px;text-align:center;color:#808090;' }, 'Score'),
E('th', { 'style': 'padding:8px;text-align:center;color:#808090;' }, 'Blocked')
])
]),
E('tbody', {},
((threats.alerts || []).slice(-15).reverse()).map(createThreatRow)
)
])
])
]), '#ffa500'),
// Flow Protocols
flows.protocols ? createCard('Protocol Distribution', '📊', E('div', {}, [
E('div', { 'style': 'display:flex;flex-wrap:wrap;gap:8px;' },
Object.entries(flows.protocols || {}).slice(0, 10).map(function(entry) {
return E('div', {
'style': 'background:#1a1a24;padding:6px 12px;border-radius:6px;'
}, [
E('span', { 'style': 'color:#00d4aa;font-weight:600;' }, entry[0]),
E('span', { 'style': 'color:#808090;margin-left:6px;' }, '(' + entry[1] + ')')
]);
})
)
]), '#00a0ff') : E('div')
]);
// Auto-refresh every 10 seconds
poll.add(L.bind(function() {
return Promise.all([
callStatus().catch(function() { return {}; }),
callGetThreats(20).catch(function() { return { alerts: [] }; })
]).then(L.bind(function(data) {
// Update would require DOM manipulation - for now just log
// Full implementation would update metrics in place
}, this));
}, this), 10);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});