feat(crowdsec): Add organization column to alerts table
- Add batch IP lookup via ip-api.com for org/ISP info - Display organization column between Source and Country - Cache org lookups to avoid repeated requests - Include organization in search filter - Skip private IP ranges (192.168.x, 10.x, 127.x) fix(mitmproxy): Fix null text appearing in status table - Use concat([]) pattern instead of ternary null returns - Prevents "null" text from rendering in DOM Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ce9c42bc37
commit
ee9afc0ccf
@ -9,6 +9,7 @@
|
|||||||
return view.extend({
|
return view.extend({
|
||||||
alerts: [],
|
alerts: [],
|
||||||
bannedIPs: new Set(),
|
bannedIPs: new Set(),
|
||||||
|
ipOrgCache: {},
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -35,7 +36,46 @@ return view.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return results[0];
|
var alerts = results[0];
|
||||||
|
// Extract unique IPs for org lookup
|
||||||
|
var ips = [];
|
||||||
|
(Array.isArray(alerts) ? alerts : []).forEach(function(a) {
|
||||||
|
var ip = a.source && a.source.ip;
|
||||||
|
if (ip && !self.ipOrgCache[ip] && ips.indexOf(ip) === -1 && !ip.startsWith('192.168.') && !ip.startsWith('10.') && !ip.startsWith('127.')) {
|
||||||
|
ips.push(ip);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Batch lookup orgs (max 100 per request)
|
||||||
|
if (ips.length > 0) {
|
||||||
|
return self.lookupOrgs(ips.slice(0, 100)).then(function() { return alerts; });
|
||||||
|
}
|
||||||
|
return alerts;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
lookupOrgs: function(ips) {
|
||||||
|
var self = this;
|
||||||
|
// Use ip-api.com batch endpoint
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', 'http://ip-api.com/batch?fields=query,org,isp,as', true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
xhr.timeout = 5000;
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
var results = JSON.parse(xhr.responseText);
|
||||||
|
results.forEach(function(r) {
|
||||||
|
if (r.query) {
|
||||||
|
self.ipOrgCache[r.query] = r.org || r.isp || r.as || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
xhr.onerror = xhr.ontimeout = function() { resolve(); };
|
||||||
|
xhr.send(JSON.stringify(ips));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -127,6 +167,7 @@ return view.extend({
|
|||||||
E('thead', {}, E('tr', {}, [
|
E('thead', {}, E('tr', {}, [
|
||||||
E('th', {}, 'Time'),
|
E('th', {}, 'Time'),
|
||||||
E('th', {}, 'Source'),
|
E('th', {}, 'Source'),
|
||||||
|
E('th', {}, 'Organization'),
|
||||||
E('th', {}, 'Country'),
|
E('th', {}, 'Country'),
|
||||||
E('th', {}, 'Scenario'),
|
E('th', {}, 'Scenario'),
|
||||||
E('th', {}, 'Events'),
|
E('th', {}, 'Events'),
|
||||||
@ -136,11 +177,14 @@ return view.extend({
|
|||||||
var src = a.source || {};
|
var src = a.source || {};
|
||||||
var ip = src.ip || '';
|
var ip = src.ip || '';
|
||||||
var country = src.cn || src.country || '';
|
var country = src.cn || src.country || '';
|
||||||
|
var org = self.ipOrgCache[ip] || '';
|
||||||
|
var orgDisplay = org.length > 25 ? org.substring(0, 22) + '...' : org;
|
||||||
var isBanned = self.bannedIPs.has(ip);
|
var isBanned = self.bannedIPs.has(ip);
|
||||||
|
|
||||||
return E('tr', {}, [
|
return E('tr', {}, [
|
||||||
E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, api.formatRelativeTime(a.created_at)),
|
E('td', { 'style': 'font-family: monospace; font-size: 12px; color: var(--kiss-muted);' }, api.formatRelativeTime(a.created_at)),
|
||||||
E('td', {}, E('span', { 'style': 'font-family: monospace; color: var(--kiss-cyan);' }, ip || '-')),
|
E('td', {}, E('span', { 'style': 'font-family: monospace; color: var(--kiss-cyan);' }, ip || '-')),
|
||||||
|
E('td', { 'title': org }, E('span', { 'style': 'font-size: 11px; color: var(--kiss-muted);' }, orgDisplay || '-')),
|
||||||
E('td', {}, [
|
E('td', {}, [
|
||||||
E('span', { 'style': 'font-size: 16px; margin-right: 4px;' }, api.getCountryFlag(country)),
|
E('span', { 'style': 'font-size: 16px; margin-right: 4px;' }, api.getCountryFlag(country)),
|
||||||
E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, country)
|
E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, country)
|
||||||
@ -219,11 +263,13 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
filterAlerts: function() {
|
filterAlerts: function() {
|
||||||
|
var self = this;
|
||||||
var query = (document.getElementById('alert-search').value || '').toLowerCase();
|
var query = (document.getElementById('alert-search').value || '').toLowerCase();
|
||||||
var filtered = this.alerts.filter(function(a) {
|
var filtered = this.alerts.filter(function(a) {
|
||||||
if (!query) return true;
|
if (!query) return true;
|
||||||
var src = a.source || {};
|
var src = a.source || {};
|
||||||
var fields = [src.ip, a.scenario, src.country, src.cn].join(' ').toLowerCase();
|
var org = self.ipOrgCache[src.ip] || '';
|
||||||
|
var fields = [src.ip, a.scenario, src.country, src.cn, org].join(' ').toLowerCase();
|
||||||
return fields.includes(query);
|
return fields.includes(query);
|
||||||
});
|
});
|
||||||
var el = document.getElementById('alerts-list');
|
var el = document.getElementById('alerts-list');
|
||||||
|
|||||||
@ -131,34 +131,32 @@ return view.extend({
|
|||||||
'target': '_blank'
|
'target': '_blank'
|
||||||
}, 'http://' + window.location.hostname + ':' + (status.web_port || 8081) + (status.token ? '/?token=***' : '')) :
|
}, 'http://' + window.location.hostname + ':' + (status.web_port || 8081) + (status.token ? '/?token=***' : '')) :
|
||||||
_('Not available'))
|
_('Not available'))
|
||||||
]),
|
])
|
||||||
status.token ? E('tr', { 'class': 'tr' }, [
|
].concat(status.token ? [E('tr', { 'class': 'tr' }, [
|
||||||
E('td', { 'class': 'td' }, E('strong', {}, _('Auth Token'))),
|
E('td', { 'class': 'td' }, E('strong', {}, _('Auth Token'))),
|
||||||
E('td', { 'class': 'td' }, [
|
E('td', { 'class': 'td' }, [
|
||||||
E('code', { 'style': 'font-size: 11px; background: #f0f0f0; padding: 2px 6px; border-radius: 3px;' },
|
E('code', { 'style': 'font-size: 11px; background: #f0f0f0; padding: 2px 6px; border-radius: 3px;' },
|
||||||
status.token.substring(0, 12) + '...'),
|
status.token.substring(0, 12) + '...'),
|
||||||
' ',
|
' ',
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'btn cbi-button cbi-button-action',
|
'class': 'btn cbi-button cbi-button-action',
|
||||||
'style': 'font-size: 11px; padding: 2px 8px;',
|
'style': 'font-size: 11px; padding: 2px 8px;',
|
||||||
'click': function() {
|
'click': function() {
|
||||||
navigator.clipboard.writeText(status.token);
|
navigator.clipboard.writeText(status.token);
|
||||||
this.textContent = _('Copied!');
|
this.textContent = _('Copied!');
|
||||||
setTimeout(function() { this.textContent = _('Copy'); }.bind(this), 1500);
|
setTimeout(function() { this.textContent = _('Copy'); }.bind(this), 1500);
|
||||||
}
|
}
|
||||||
}, _('Copy'))
|
}, _('Copy'))
|
||||||
])
|
])
|
||||||
]) : null,
|
])] : []).concat(status.haproxy_router_enabled ? [E('tr', { 'class': 'tr' }, [
|
||||||
status.haproxy_router_enabled ? E('tr', { 'class': 'tr' }, [
|
E('td', { 'class': 'td' }, E('strong', {}, _('HAProxy Router'))),
|
||||||
E('td', { 'class': 'td' }, E('strong', {}, _('HAProxy Router'))),
|
E('td', { 'class': 'td' }, [
|
||||||
E('td', { 'class': 'td' }, [
|
E('span', {
|
||||||
E('span', {
|
'style': 'display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; background: #27ae60;'
|
||||||
'style': 'display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; background: #27ae60;'
|
}),
|
||||||
}),
|
_('Enabled (port ') + (status.haproxy_listen_port || 8889) + ')'
|
||||||
_('Enabled (port ') + (status.haproxy_listen_port || 8889) + ')'
|
])
|
||||||
])
|
])] : []))
|
||||||
]) : null
|
|
||||||
])
|
|
||||||
]),
|
]),
|
||||||
E('div', { 'style': 'margin-top: 16px;' }, [
|
E('div', { 'style': 'margin-top: 16px;' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user