From a602462915700564a618993fe74f031909d28eef Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Thu, 22 Jan 2026 06:00:40 +0100 Subject: [PATCH] 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 --- .../secubox/luci-app-secubox-portal/Makefile | 2 +- .../resources/view/secubox-portal/index.js | 45 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/package/secubox/luci-app-secubox-portal/Makefile b/package/secubox/luci-app-secubox-portal/Makefile index 67ddc14c..86ceba0f 100644 --- a/package/secubox/luci-app-secubox-portal/Makefile +++ b/package/secubox/luci-app-secubox-portal/Makefile @@ -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 diff --git a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js index 97f2d8e2..9080bc20 100644 --- a/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js +++ b/package/secubox/luci-app-secubox-portal/htdocs/luci-static/resources/view/secubox-portal/index.js @@ -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)) ]); },