feat(portal): Add CrowdSec blocked IPs statistics to dashboard

- Add RPC call to fetch CrowdSec nftables statistics
- Replace Security Modules widget with IPs Blocked widget
- Show active/inactive status based on firewall bouncer health
- Add detailed breakdown in System Overview (IPv4/IPv6, CAPI/local)
- Gracefully handle missing CrowdSec dashboard package

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-22 06:00:40 +01:00
parent 7bb437b29f
commit a602462915
2 changed files with 36 additions and 11 deletions

View File

@ -11,7 +11,7 @@ LUCI_DESCRIPTION:=Unified entry point for all SecuBox applications with tabbed n
LUCI_DEPENDS:=+luci-base +luci-theme-secubox
LUCI_PKGARCH:=all
PKG_VERSION:=0.6.0
PKG_RELEASE:=7
PKG_RELEASE:=8
PKG_LICENSE:=GPL-3.0-or-later
PKG_MAINTAINER:=SecuBox Team <secubox@example.com>

View File

@ -18,6 +18,11 @@ var callSystemInfo = rpc.declare({
method: 'info'
});
var callCrowdSecStats = rpc.declare({
object: 'luci.crowdsec-dashboard',
method: 'nftables_stats'
});
return view.extend({
currentSection: 'dashboard',
appStatuses: {},
@ -26,7 +31,8 @@ return view.extend({
return Promise.all([
callSystemBoard(),
callSystemInfo(),
this.loadAppStatuses()
this.loadAppStatuses(),
callCrowdSecStats().catch(function() { return null; })
]);
},
@ -80,6 +86,7 @@ return view.extend({
render: function(data) {
var boardInfo = data[0] || {};
var sysInfo = data[1] || {};
var crowdSecStats = data[3] || {};
var self = this;
// Set portal app context and hide LuCI navigation
@ -118,7 +125,7 @@ return view.extend({
this.renderHeader(),
// Content
E('div', { 'class': 'sb-portal-content' }, [
this.renderDashboardSection(boardInfo, sysInfo),
this.renderDashboardSection(boardInfo, sysInfo, crowdSecStats),
this.renderSecuritySection(),
this.renderNetworkSection(),
this.renderMonitoringSection(),
@ -204,7 +211,7 @@ return view.extend({
});
},
renderDashboardSection: function(boardInfo, sysInfo) {
renderDashboardSection: function(boardInfo, sysInfo, crowdSecStats) {
var self = this;
var securityApps = portal.getAppsBySection('security');
var networkApps = portal.getAppsBySection('network');
@ -214,6 +221,13 @@ return view.extend({
var runningCount = Object.values(this.appStatuses).filter(function(s) { return s === 'running'; }).length;
var totalServices = Object.keys(this.appStatuses).length;
// CrowdSec blocked IPs count
var blockedIPv4 = (crowdSecStats.ipv4_total_count || 0);
var blockedIPv6 = (crowdSecStats.ipv6_total_count || 0);
var totalBlocked = blockedIPv4 + blockedIPv6;
var crowdSecHealth = crowdSecStats.firewall_health || {};
var crowdSecActive = crowdSecHealth.bouncer_running && crowdSecHealth.decisions_synced;
return E('div', { 'class': 'sb-portal-section active', 'data-section': 'dashboard' }, [
E('div', { 'class': 'sb-section-header' }, [
E('h2', { 'class': 'sb-section-title' }, 'SecuBox Dashboard'),
@ -244,14 +258,15 @@ return view.extend({
E('div', { 'class': 'sb-quick-stat-label' }, 'Services Running')
]),
// Security Apps
// CrowdSec Blocked IPs
E('div', { 'class': 'sb-quick-stat' }, [
E('div', { 'class': 'sb-quick-stat-header' }, [
E('div', { 'class': 'sb-quick-stat-icon security' }, '\ud83d\udee1\ufe0f'),
E('span', { 'class': 'sb-quick-stat-status running' }, 'Protected')
E('div', { 'class': 'sb-quick-stat-icon security' }, '\ud83d\udeab'),
E('span', { 'class': 'sb-quick-stat-status ' + (crowdSecActive ? 'running' : 'warning') },
crowdSecActive ? 'Active' : 'Inactive')
]),
E('div', { 'class': 'sb-quick-stat-value' }, securityApps.length),
E('div', { 'class': 'sb-quick-stat-label' }, 'Security Modules')
E('div', { 'class': 'sb-quick-stat-value' }, totalBlocked.toLocaleString()),
E('div', { 'class': 'sb-quick-stat-label' }, 'IPs Blocked')
]),
// Network Apps
@ -300,8 +315,18 @@ return view.extend({
' / ' + this.formatBytes(sysInfo.memory ? sysInfo.memory.total : 0) + ' used'),
E('span', { 'class': 'sb-events-meta' }, 'Resource Usage')
])
])
])
]),
totalBlocked > 0 ? E('div', { 'class': 'sb-events-item' }, [
E('div', { 'class': 'sb-events-icon security' }, '\ud83d\udee1\ufe0f'),
E('div', { 'class': 'sb-events-content' }, [
E('p', { 'class': 'sb-events-message' },
'IPv4: ' + blockedIPv4.toLocaleString() + ' (' +
(crowdSecStats.ipv4_capi_count || 0) + ' CAPI + ' +
(crowdSecStats.ipv4_cscli_count || 0) + ' local) | IPv6: ' + blockedIPv6.toLocaleString()),
E('span', { 'class': 'sb-events-meta' }, 'CrowdSec Firewall Protection')
])
]) : null
].filter(Boolean))
]);
},