secubox-openwrt/package/secubox/luci-app-dpi-dual/htdocs/luci-static/resources/view/dpi-dual/timeline.js
CyberMind-FR 7ff9ee3805 feat(dpi): Phase 3 - Correlation engine + timeline view
Correlation Library (correlation-lib.sh):
- IP reputation tracking with configurable decay
- Full context gathering from MITM, DPI, WAF streams
- CrowdSec decision checking and notification
- Correlation entry builder with rich context

Enhanced Correlator (dpi-correlator v2):
- Watches WAF alerts, CrowdSec decisions, DPI flows
- Auto-ban for high-reputation IPs (threshold: 80)
- Notification queue for high-severity threats
- CLI: correlate, reputation, context, search, stats

LuCI Timeline View:
- Correlation timeline with colored event cards
- IP context modal showing MITM requests + WAF alerts
- Quick ban button with CrowdSec integration
- Search by IP functionality
- Stats: total, high-threat, banned, unique IPs

RPCD Methods (8 new):
- get_correlation_stats, get_ip_context, get_ip_reputation
- get_timeline, search_correlations, ban_ip, set_auto_ban

UCI Config: auto_ban, auto_ban_threshold, notifications

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

333 lines
13 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require poll';
'require rpc';
'require ui';
var callGetTimeline = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_timeline',
params: ['limit'],
expect: {}
});
var callGetCorrelationStats = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_correlation_stats',
expect: {}
});
var callGetIPContext = rpc.declare({
object: 'luci.dpi-dual',
method: 'get_ip_context',
params: ['ip'],
expect: {}
});
var callSearchCorrelations = rpc.declare({
object: 'luci.dpi-dual',
method: 'search_correlations',
params: ['ip', 'limit'],
expect: {}
});
var callBanIP = rpc.declare({
object: 'luci.dpi-dual',
method: 'ban_ip',
params: ['ip', 'duration'],
expect: {}
});
function formatTime(ts) {
if (!ts) return '-';
var d = new Date(ts);
return d.toLocaleString();
}
function getEventColor(event_type) {
switch (event_type) {
case 'waf_block': return '#ff4d4d';
case 'waf_alert': return '#ffa500';
case 'crowdsec_ban': return '#ff6b6b';
case 'dpi_threat': return '#00a0ff';
case 'scanner': return '#ff00ff';
default: return '#808090';
}
}
function getScoreColor(score) {
if (score >= 70) return '#ff4d4d';
if (score >= 40) return '#ffa500';
return '#00d4aa';
}
function createStatCard(label, value, color) {
return E('div', {
'style': 'background:#1a1a24;padding:1rem;border-radius:8px;text-align:center;min-width:120px;'
}, [
E('div', {
'style': 'font-size:2rem;font-weight:700;color:' + (color || '#00d4aa') + ';font-family:monospace;'
}, String(value)),
E('div', {
'style': 'font-size:0.8rem;color:#808090;text-transform:uppercase;margin-top:4px;'
}, label)
]);
}
function createTimelineEntry(entry, self) {
var color = getEventColor(entry.event_type);
var scoreColor = getScoreColor(entry.threat_score || 0);
return E('div', {
'class': 'timeline-entry',
'style': 'background:#12121a;border-radius:8px;padding:1rem;margin:0.5rem 0;' +
'border-left:4px solid ' + color + ';position:relative;'
}, [
// Timeline dot
E('div', {
'style': 'position:absolute;left:-8px;top:50%;transform:translateY(-50%);' +
'width:12px;height:12px;border-radius:50%;background:' + color + ';' +
'box-shadow:0 0 8px ' + color + ';'
}),
// Header row
E('div', {
'style': 'display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem;'
}, [
E('div', { 'style': 'display:flex;align-items:center;gap:10px;' }, [
E('span', {
'style': 'font-family:monospace;font-size:1.1rem;color:#00a0ff;cursor:pointer;',
'click': function() {
self.showIPContext(entry.ip);
}
}, entry.ip || '-'),
E('span', {
'style': 'background:' + color + '22;color:' + color +
';padding:2px 8px;border-radius:10px;font-size:0.75rem;font-weight:600;'
}, entry.event_type || 'unknown')
]),
E('span', { 'style': 'color:#808090;font-size:0.8rem;' }, formatTime(entry.timestamp))
]),
// Details row
E('div', { 'style': 'display:flex;gap:1rem;flex-wrap:wrap;' }, [
E('div', {}, [
E('span', { 'style': 'color:#606070;font-size:0.75rem;' }, 'Reason: '),
E('span', { 'style': 'color:#fff;' }, entry.reason || '-')
]),
E('div', {}, [
E('span', { 'style': 'color:#606070;font-size:0.75rem;' }, 'Threat: '),
E('span', {
'style': 'color:' + scoreColor + ';font-weight:600;'
}, String(entry.threat_score || 0))
]),
E('div', {}, [
E('span', { 'style': 'color:#606070;font-size:0.75rem;' }, 'Reputation: '),
E('span', { 'style': 'color:#ffa500;font-weight:600;' }, String(entry.reputation_score || 0))
]),
entry.crowdsec_status === 'banned' ?
E('span', {
'style': 'background:#ff4d4d22;color:#ff4d4d;padding:2px 8px;border-radius:10px;font-size:0.75rem;'
}, '🚫 BANNED') : null
].filter(Boolean)),
// Action buttons
E('div', { 'style': 'margin-top:0.5rem;display:flex;gap:8px;' }, [
E('button', {
'class': 'btn cbi-button',
'style': 'font-size:0.75rem;padding:4px 10px;',
'click': function() {
self.showIPContext(entry.ip);
}
}, '🔍 Context'),
entry.crowdsec_status !== 'banned' ?
E('button', {
'class': 'btn cbi-button cbi-button-negative',
'style': 'font-size:0.75rem;padding:4px 10px;',
'click': function() {
self.banIP(entry.ip);
}
}, '🚫 Ban') : null
].filter(Boolean))
]);
}
return view.extend({
selectedIP: null,
load: function() {
return Promise.all([
callGetTimeline(50).catch(function() { return { entries: [] }; }),
callGetCorrelationStats().catch(function() { return {}; })
]);
},
showIPContext: function(ip) {
var self = this;
ui.showModal('IP Context: ' + ip, [
E('div', { 'style': 'min-width:500px;' }, [
E('div', { 'id': 'ip-context-loading', 'style': 'text-align:center;padding:2rem;' },
E('em', {}, 'Loading context...'))
]),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn',
'click': ui.hideModal
}, 'Close')
])
]);
callGetIPContext(ip).then(function(ctx) {
var content = E('div', {}, [
E('div', { 'style': 'margin-bottom:1rem;' }, [
E('strong', {}, 'IP: '),
E('span', { 'style': 'font-family:monospace;color:#00a0ff;' }, ctx.ip || ip),
E('span', { 'style': 'margin-left:1rem;' }, [
E('strong', {}, 'Reputation: '),
E('span', {
'style': 'color:' + getScoreColor(ctx.reputation_score || 0) + ';font-weight:600;'
}, String(ctx.reputation_score || 0))
]),
E('span', { 'style': 'margin-left:1rem;' }, [
E('strong', {}, 'CrowdSec: '),
E('span', {
'style': 'color:' + (ctx.crowdsec_status === 'banned' ? '#ff4d4d' : '#00d4aa') + ';'
}, ctx.crowdsec_status || 'clean')
])
]),
ctx.context && ctx.context.mitm_requests && ctx.context.mitm_requests.length > 0 ?
E('div', { 'style': 'margin-bottom:1rem;' }, [
E('h4', { 'style': 'color:#00d4aa;margin-bottom:0.5rem;' }, 'MITM Requests'),
E('div', { 'style': 'max-height:150px;overflow-y:auto;background:#1a1a24;padding:8px;border-radius:6px;' },
ctx.context.mitm_requests.map(function(req) {
return E('div', { 'style': 'font-family:monospace;font-size:0.8rem;margin:2px 0;' },
(req.method || 'GET') + ' ' + (req.host || '') + (req.path || '/'));
})
)
]) : null,
ctx.context && ctx.context.waf_alerts && ctx.context.waf_alerts.length > 0 ?
E('div', { 'style': 'margin-bottom:1rem;' }, [
E('h4', { 'style': 'color:#ffa500;margin-bottom:0.5rem;' }, 'WAF Alerts'),
E('div', { 'style': 'max-height:150px;overflow-y:auto;background:#1a1a24;padding:8px;border-radius:6px;' },
ctx.context.waf_alerts.map(function(alert) {
return E('div', { 'style': 'font-family:monospace;font-size:0.8rem;margin:2px 0;' },
'[Score: ' + (alert.threat_score || 0) + '] ' + (alert.path || '/'));
})
)
]) : null
].filter(Boolean));
var loadingEl = document.getElementById('ip-context-loading');
if (loadingEl) {
loadingEl.parentNode.replaceChild(content, loadingEl);
}
});
},
banIP: function(ip) {
var self = this;
if (!confirm('Ban IP ' + ip + ' for 4 hours?')) {
return;
}
callBanIP(ip, '4h').then(function(res) {
if (res.success) {
ui.addNotification(null, E('p', res.message), 'info');
window.location.reload();
} else {
ui.addNotification(null, E('p', 'Error: ' + (res.error || 'Unknown')), 'error');
}
});
},
render: function(data) {
var timeline = data[0] || {};
var stats = data[1] || {};
var self = this;
var entries = timeline.entries || [];
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,#ffa500,#ff6b6b);' +
'-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin:0;'
}, 'Correlation Timeline'),
E('div', { 'style': 'color:#606070;margin-top:4px;' },
'Threat correlation across MITM + TAP streams')
]),
// Stats row
E('div', {
'style': 'display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;margin-bottom:1.5rem;'
}, [
createStatCard('Total', stats.total_correlations || 0, '#00d4aa'),
createStatCard('High Threat', stats.high_threat_count || 0, '#ff4d4d'),
createStatCard('Banned IPs', stats.banned_ips || 0, '#ff6b6b'),
createStatCard('Unique IPs', stats.unique_ips || 0, '#00a0ff')
]),
// Search bar
E('div', { 'style': 'display:flex;gap:8px;justify-content:center;margin-bottom:1rem;' }, [
E('input', {
'type': 'text',
'id': 'search-ip',
'placeholder': 'Search by IP...',
'style': 'background:#1a1a24;border:1px solid #2a2a3a;border-radius:6px;' +
'padding:8px 12px;color:#fff;width:200px;'
}),
E('button', {
'class': 'btn cbi-button cbi-button-apply',
'click': function() {
var ip = document.getElementById('search-ip').value;
if (ip) {
callSearchCorrelations(ip, 50).then(function(res) {
ui.addNotification(null, E('p', 'Found ' + (res.results || []).length + ' correlations'), 'info');
});
}
}
}, '🔍 Search')
]),
// Timeline
E('div', { 'style': 'max-width:800px;margin:0 auto;padding:0 1rem;position:relative;' }, [
// Vertical line
E('div', {
'style': 'position:absolute;left:calc(1rem - 2px);top:0;bottom:0;width:4px;' +
'background:linear-gradient(to bottom,#2a2a3a,transparent);border-radius:2px;'
}),
// Timeline entries
E('div', { 'style': 'padding-left:1.5rem;' },
entries.length > 0 ?
entries.reverse().map(function(entry) {
return createTimelineEntry(entry, self);
}) :
E('div', { 'style': 'text-align:center;color:#606070;padding:2rem;' },
'No correlation events yet')
)
])
]);
// Auto-refresh
poll.add(L.bind(function() {
return callGetTimeline(50).then(function() {
// Refresh handled by poll
});
}, this), 30);
return view;
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});