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:
CyberMind-FR 2026-02-12 06:26:26 +01:00
parent ce9c42bc37
commit ee9afc0ccf
2 changed files with 74 additions and 30 deletions

View File

@ -9,6 +9,7 @@
return view.extend({
alerts: [],
bannedIPs: new Set(),
ipOrgCache: {},
load: function() {
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('th', {}, 'Time'),
E('th', {}, 'Source'),
E('th', {}, 'Organization'),
E('th', {}, 'Country'),
E('th', {}, 'Scenario'),
E('th', {}, 'Events'),
@ -136,11 +177,14 @@ return view.extend({
var src = a.source || {};
var ip = src.ip || '';
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);
return E('tr', {}, [
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', { 'title': org }, E('span', { 'style': 'font-size: 11px; color: var(--kiss-muted);' }, orgDisplay || '-')),
E('td', {}, [
E('span', { 'style': 'font-size: 16px; margin-right: 4px;' }, api.getCountryFlag(country)),
E('span', { 'style': 'font-size: 12px; color: var(--kiss-muted);' }, country)
@ -219,11 +263,13 @@ return view.extend({
},
filterAlerts: function() {
var self = this;
var query = (document.getElementById('alert-search').value || '').toLowerCase();
var filtered = this.alerts.filter(function(a) {
if (!query) return true;
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);
});
var el = document.getElementById('alerts-list');

View File

@ -131,34 +131,32 @@ return view.extend({
'target': '_blank'
}, 'http://' + window.location.hostname + ':' + (status.web_port || 8081) + (status.token ? '/?token=***' : '')) :
_('Not available'))
]),
status.token ? E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, E('strong', {}, _('Auth Token'))),
E('td', { 'class': 'td' }, [
E('code', { 'style': 'font-size: 11px; background: #f0f0f0; padding: 2px 6px; border-radius: 3px;' },
status.token.substring(0, 12) + '...'),
' ',
E('button', {
'class': 'btn cbi-button cbi-button-action',
'style': 'font-size: 11px; padding: 2px 8px;',
'click': function() {
navigator.clipboard.writeText(status.token);
this.textContent = _('Copied!');
setTimeout(function() { this.textContent = _('Copy'); }.bind(this), 1500);
}
}, _('Copy'))
])
]) : null,
status.haproxy_router_enabled ? E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, E('strong', {}, _('HAProxy Router'))),
E('td', { 'class': 'td' }, [
E('span', {
'style': 'display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; background: #27ae60;'
}),
_('Enabled (port ') + (status.haproxy_listen_port || 8889) + ')'
])
]) : null
])
])
].concat(status.token ? [E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, E('strong', {}, _('Auth Token'))),
E('td', { 'class': 'td' }, [
E('code', { 'style': 'font-size: 11px; background: #f0f0f0; padding: 2px 6px; border-radius: 3px;' },
status.token.substring(0, 12) + '...'),
' ',
E('button', {
'class': 'btn cbi-button cbi-button-action',
'style': 'font-size: 11px; padding: 2px 8px;',
'click': function() {
navigator.clipboard.writeText(status.token);
this.textContent = _('Copied!');
setTimeout(function() { this.textContent = _('Copy'); }.bind(this), 1500);
}
}, _('Copy'))
])
])] : []).concat(status.haproxy_router_enabled ? [E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, E('strong', {}, _('HAProxy Router'))),
E('td', { 'class': 'td' }, [
E('span', {
'style': 'display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; background: #27ae60;'
}),
_('Enabled (port ') + (status.haproxy_listen_port || 8889) + ')'
])
])] : []))
]),
E('div', { 'style': 'margin-top: 16px;' }, [
E('button', {