feat(crowdsec-dashboard): Add Firewall Blocks section with nftables visualization
- Scan ALL nftables sets (CAPI, cscli, etc.) instead of just base set - Display blocked IPs count by origin (Community vs Local) - Show sample of blocked IPs with Unban button - Add ipv4_capi_count, ipv4_cscli_count, ipv4_total_count to API response - Support for 14,000+ community blocklist IPs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d1bc9a9b63
commit
ddae65d0fc
@ -37,7 +37,8 @@ return view.extend({
|
||||
this.csApi.getSecuboxLogs(),
|
||||
this.csApi.getHealthCheck().catch(function() { return {}; }),
|
||||
this.csApi.getCapiMetrics().catch(function() { return {}; }),
|
||||
this.csApi.getCollections().catch(function() { return { collections: [] }; })
|
||||
this.csApi.getCollections().catch(function() { return { collections: [] }; }),
|
||||
this.csApi.getNftablesStats().catch(function() { return {}; })
|
||||
]);
|
||||
},
|
||||
|
||||
@ -451,6 +452,10 @@ return view.extend({
|
||||
this.renderCapiBlocklist(),
|
||||
this.renderCollectionsCard()
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cs-charts-row' }, [
|
||||
this.renderFirewallBlocks()
|
||||
]),
|
||||
|
||||
E('div', { 'class': 'cs-charts-row' }, [
|
||||
E('div', { 'class': 'cs-card', 'style': 'flex: 2' }, [
|
||||
@ -514,6 +519,7 @@ return view.extend({
|
||||
this.healthCheck = payload[2] || {};
|
||||
this.capiMetrics = payload[3] || {};
|
||||
this.collections = (payload[4] && payload[4].collections) || [];
|
||||
this.nftablesStats = payload[5] || {};
|
||||
|
||||
// Main wrapper with SecuBox header
|
||||
var wrapper = E('div', { 'class': 'secubox-page-wrapper' });
|
||||
@ -541,13 +547,15 @@ refreshDashboard: function() {
|
||||
self.csApi.getSecuboxLogs(),
|
||||
self.csApi.getHealthCheck().catch(function() { return {}; }),
|
||||
self.csApi.getCapiMetrics().catch(function() { return {}; }),
|
||||
self.csApi.getCollections().catch(function() { return { collections: [] }; })
|
||||
self.csApi.getCollections().catch(function() { return { collections: [] }; }),
|
||||
self.csApi.getNftablesStats().catch(function() { return {}; })
|
||||
]).then(function(results) {
|
||||
self.data = results[0];
|
||||
self.logs = (results[1] && results[1].entries) || [];
|
||||
self.healthCheck = results[2] || {};
|
||||
self.capiMetrics = results[3] || {};
|
||||
self.collections = (results[4] && results[4].collections) || [];
|
||||
self.nftablesStats = results[5] || {};
|
||||
self.updateView();
|
||||
});
|
||||
},
|
||||
@ -731,6 +739,112 @@ refreshDashboard: function() {
|
||||
});
|
||||
},
|
||||
|
||||
// Firewall Blocks - Shows IPs blocked in nftables
|
||||
renderFirewallBlocks: function() {
|
||||
var self = this;
|
||||
var stats = this.nftablesStats || {};
|
||||
|
||||
// Check if nftables available
|
||||
if (stats.error) {
|
||||
return E('div', { 'class': 'cs-card' }, [
|
||||
E('div', { 'class': 'cs-card-header' }, [
|
||||
E('div', { 'class': 'cs-card-title' }, _('Firewall Blocks'))
|
||||
]),
|
||||
E('div', { 'class': 'cs-card-body' }, [
|
||||
E('div', { 'class': 'cs-empty' }, [
|
||||
E('div', { 'class': 'cs-empty-icon' }, '⚠️'),
|
||||
E('p', {}, stats.error)
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
var ipv4Active = stats.ipv4_table_exists;
|
||||
var ipv6Active = stats.ipv6_table_exists;
|
||||
var ipv4List = stats.ipv4_blocked_ips || [];
|
||||
var ipv6List = stats.ipv6_blocked_ips || [];
|
||||
var ipv4Rules = stats.ipv4_rules_count || 0;
|
||||
var ipv6Rules = stats.ipv6_rules_count || 0;
|
||||
// Use total counts from API (includes all IPs, not just sample)
|
||||
var ipv4Total = stats.ipv4_total_count || ipv4List.length;
|
||||
var ipv6Total = stats.ipv6_total_count || ipv6List.length;
|
||||
var ipv4Capi = stats.ipv4_capi_count || 0;
|
||||
var ipv4Cscli = stats.ipv4_cscli_count || 0;
|
||||
var ipv6Capi = stats.ipv6_capi_count || 0;
|
||||
var ipv6Cscli = stats.ipv6_cscli_count || 0;
|
||||
var totalBlocked = ipv4Total + ipv6Total;
|
||||
|
||||
// Build IP list (combine IPv4 and IPv6, limit to 20)
|
||||
var allIps = [];
|
||||
ipv4List.forEach(function(ip) { allIps.push({ ip: ip, type: 'IPv4' }); });
|
||||
ipv6List.forEach(function(ip) { allIps.push({ ip: ip, type: 'IPv6' }); });
|
||||
var displayIps = allIps.slice(0, 20);
|
||||
|
||||
var ipRows = displayIps.map(function(item) {
|
||||
return E('div', {
|
||||
'style': 'display: flex; align-items: center; justify-content: space-between; padding: 0.5em 0; border-bottom: 1px solid rgba(255,255,255,0.1);'
|
||||
}, [
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 0.75em;' }, [
|
||||
E('span', { 'style': 'font-size: 1.1em;' }, '🚫'),
|
||||
E('code', { 'style': 'font-size: 0.85em; background: rgba(0,0,0,0.2); padding: 0.2em 0.5em; border-radius: 4px;' }, item.ip),
|
||||
E('span', {
|
||||
'style': 'font-size: 0.7em; padding: 0.15em 0.4em; background: ' + (item.type === 'IPv4' ? '#667eea' : '#764ba2') + '; border-radius: 3px;'
|
||||
}, item.type)
|
||||
]),
|
||||
E('button', {
|
||||
'class': 'cs-btn cs-btn-danger cs-btn-sm',
|
||||
'style': 'font-size: 0.75em; padding: 0.25em 0.5em;',
|
||||
'click': ui.createHandlerFn(self, 'handleUnban', item.ip)
|
||||
}, _('Unban'))
|
||||
]);
|
||||
});
|
||||
|
||||
// Status indicators with breakdown by origin
|
||||
var statusRow = E('div', { 'style': 'display: flex; gap: 1.5em; margin-bottom: 1em; flex-wrap: wrap;' }, [
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||
E('span', { 'style': 'font-size: 1.2em;' }, ipv4Active ? '✅' : '❌'),
|
||||
E('span', { 'style': 'font-size: 0.85em;' }, 'IPv4'),
|
||||
E('span', { 'style': 'font-size: 0.75em; color: #888;' }, ipv4Total.toLocaleString() + ' IPs')
|
||||
]),
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||
E('span', { 'style': 'font-size: 1.2em;' }, ipv6Active ? '✅' : '❌'),
|
||||
E('span', { 'style': 'font-size: 0.85em;' }, 'IPv6'),
|
||||
E('span', { 'style': 'font-size: 0.75em; color: #888;' }, ipv6Total.toLocaleString() + ' IPs')
|
||||
]),
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding-left: 1em; border-left: 1px solid rgba(255,255,255,0.2);' }, [
|
||||
E('span', { 'style': 'font-size: 0.8em; padding: 0.2em 0.5em; background: #667eea; border-radius: 4px;' }, 'CAPI'),
|
||||
E('span', { 'style': 'font-size: 0.85em; color: #667eea;' }, (ipv4Capi + ipv6Capi).toLocaleString())
|
||||
]),
|
||||
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||
E('span', { 'style': 'font-size: 0.8em; padding: 0.2em 0.5em; background: #00d4aa; border-radius: 4px;' }, 'Local'),
|
||||
E('span', { 'style': 'font-size: 0.85em; color: #00d4aa;' }, (ipv4Cscli + ipv6Cscli).toLocaleString())
|
||||
])
|
||||
]);
|
||||
|
||||
return E('div', { 'class': 'cs-card', 'style': 'flex: 2;' }, [
|
||||
E('div', { 'class': 'cs-card-header' }, [
|
||||
E('div', { 'class': 'cs-card-title' }, [
|
||||
_('Firewall Blocks'),
|
||||
E('span', {
|
||||
'style': 'margin-left: 0.75em; font-size: 0.8em; padding: 0.2em 0.6em; background: linear-gradient(90deg, #ff4757, #ff6b81); border-radius: 12px;'
|
||||
}, totalBlocked + ' blocked')
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'cs-card-body' }, [
|
||||
statusRow,
|
||||
ipRows.length > 0 ?
|
||||
E('div', { 'style': 'max-height: 300px; overflow-y: auto;' }, ipRows) :
|
||||
E('div', { 'class': 'cs-empty', 'style': 'padding: 1em;' }, [
|
||||
E('div', { 'class': 'cs-empty-icon' }, '✅'),
|
||||
E('p', {}, _('No IPs currently blocked in firewall'))
|
||||
]),
|
||||
allIps.length > 20 ? E('div', { 'style': 'text-align: center; padding: 0.5em; font-size: 0.8em; color: #888;' },
|
||||
_('Showing 20 of ') + allIps.length + _(' blocked IPs')
|
||||
) : E('span')
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderLogCard: function(entries) {
|
||||
return E('div', { 'class': 'cs-card cs-log-card' }, [
|
||||
E('div', { 'class': 'cs-card-header' }, [
|
||||
|
||||
@ -845,31 +845,65 @@ get_nftables_stats() {
|
||||
fi
|
||||
json_add_boolean "ipv6_table_exists" "$ipv6_exists"
|
||||
|
||||
# Get blocked IPs from IPv4 set
|
||||
# Get blocked IPs from ALL IPv4 sets (CAPI, cscli, etc.)
|
||||
local ipv4_total=0
|
||||
local ipv4_capi=0
|
||||
local ipv4_cscli=0
|
||||
local ipv4_other=0
|
||||
|
||||
json_add_array "ipv4_blocked_ips"
|
||||
if [ "$ipv4_exists" = "1" ]; then
|
||||
local ips
|
||||
ips=$(nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' || echo "")
|
||||
if [ -n "$ips" ]; then
|
||||
local ip
|
||||
for ip in $ips; do
|
||||
json_add_string "" "$ip"
|
||||
done
|
||||
fi
|
||||
# Get all set names
|
||||
local sets
|
||||
sets=$(nft list sets ip 2>/dev/null | grep "set crowdsec-blacklists" | sed 's/.*set //' | sed 's/ {//')
|
||||
|
||||
local setname ips ip
|
||||
for setname in $sets; do
|
||||
ips=$(nft list set ip crowdsec "$setname" 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' | head -50)
|
||||
if [ -n "$ips" ]; then
|
||||
for ip in $ips; do
|
||||
json_add_string "" "$ip"
|
||||
ipv4_total=$((ipv4_total + 1))
|
||||
done
|
||||
fi
|
||||
# Count by origin
|
||||
local count
|
||||
count=$(nft list set ip crowdsec "$setname" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | wc -l)
|
||||
case "$setname" in
|
||||
*-CAPI) ipv4_capi=$((ipv4_capi + count)) ;;
|
||||
*-cscli) ipv4_cscli=$((ipv4_cscli + count)) ;;
|
||||
*) ipv4_other=$((ipv4_other + count)) ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
json_close_array
|
||||
|
||||
# Get blocked IPs from IPv6 set
|
||||
# Get blocked IPs from ALL IPv6 sets
|
||||
local ipv6_total=0
|
||||
local ipv6_capi=0
|
||||
local ipv6_cscli=0
|
||||
|
||||
json_add_array "ipv6_blocked_ips"
|
||||
if [ "$ipv6_exists" = "1" ]; then
|
||||
local ips
|
||||
ips=$(nft list set ip6 crowdsec6 crowdsec6-blacklists 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}' || echo "")
|
||||
if [ -n "$ips" ]; then
|
||||
local ip
|
||||
for ip in $ips; do
|
||||
json_add_string "" "$ip"
|
||||
done
|
||||
fi
|
||||
local sets
|
||||
sets=$(nft list sets ip6 2>/dev/null | grep "set crowdsec6-blacklists" | sed 's/.*set //' | sed 's/ {//')
|
||||
|
||||
local setname ips ip
|
||||
for setname in $sets; do
|
||||
ips=$(nft list set ip6 crowdsec6 "$setname" 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '([0-9a-fA-F:]+:+)+[0-9a-fA-F]+' | head -50)
|
||||
if [ -n "$ips" ]; then
|
||||
for ip in $ips; do
|
||||
json_add_string "" "$ip"
|
||||
ipv6_total=$((ipv6_total + 1))
|
||||
done
|
||||
fi
|
||||
local count
|
||||
count=$(nft list set ip6 crowdsec6 "$setname" 2>/dev/null | grep -oE '([0-9a-fA-F:]+:+)+[0-9a-fA-F]+' | wc -l)
|
||||
case "$setname" in
|
||||
*-CAPI) ipv6_capi=$((ipv6_capi + count)) ;;
|
||||
*-cscli) ipv6_cscli=$((ipv6_cscli + count)) ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
json_close_array
|
||||
|
||||
@ -885,6 +919,14 @@ get_nftables_stats() {
|
||||
json_add_int "ipv4_rules_count" "$ipv4_rules"
|
||||
json_add_int "ipv6_rules_count" "$ipv6_rules"
|
||||
|
||||
# Add counts by origin
|
||||
json_add_int "ipv4_capi_count" "$ipv4_capi"
|
||||
json_add_int "ipv4_cscli_count" "$ipv4_cscli"
|
||||
json_add_int "ipv4_total_count" "$((ipv4_capi + ipv4_cscli + ipv4_other))"
|
||||
json_add_int "ipv6_capi_count" "$ipv6_capi"
|
||||
json_add_int "ipv6_cscli_count" "$ipv6_cscli"
|
||||
json_add_int "ipv6_total_count" "$((ipv6_capi + ipv6_cscli))"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user