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:
parent
7bb437b29f
commit
a602462915
@ -11,7 +11,7 @@ LUCI_DESCRIPTION:=Unified entry point for all SecuBox applications with tabbed n
|
|||||||
LUCI_DEPENDS:=+luci-base +luci-theme-secubox
|
LUCI_DEPENDS:=+luci-base +luci-theme-secubox
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
PKG_VERSION:=0.6.0
|
PKG_VERSION:=0.6.0
|
||||||
PKG_RELEASE:=7
|
PKG_RELEASE:=8
|
||||||
PKG_LICENSE:=GPL-3.0-or-later
|
PKG_LICENSE:=GPL-3.0-or-later
|
||||||
PKG_MAINTAINER:=SecuBox Team <secubox@example.com>
|
PKG_MAINTAINER:=SecuBox Team <secubox@example.com>
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,11 @@ var callSystemInfo = rpc.declare({
|
|||||||
method: 'info'
|
method: 'info'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callCrowdSecStats = rpc.declare({
|
||||||
|
object: 'luci.crowdsec-dashboard',
|
||||||
|
method: 'nftables_stats'
|
||||||
|
});
|
||||||
|
|
||||||
return view.extend({
|
return view.extend({
|
||||||
currentSection: 'dashboard',
|
currentSection: 'dashboard',
|
||||||
appStatuses: {},
|
appStatuses: {},
|
||||||
@ -26,7 +31,8 @@ return view.extend({
|
|||||||
return Promise.all([
|
return Promise.all([
|
||||||
callSystemBoard(),
|
callSystemBoard(),
|
||||||
callSystemInfo(),
|
callSystemInfo(),
|
||||||
this.loadAppStatuses()
|
this.loadAppStatuses(),
|
||||||
|
callCrowdSecStats().catch(function() { return null; })
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -80,6 +86,7 @@ return view.extend({
|
|||||||
render: function(data) {
|
render: function(data) {
|
||||||
var boardInfo = data[0] || {};
|
var boardInfo = data[0] || {};
|
||||||
var sysInfo = data[1] || {};
|
var sysInfo = data[1] || {};
|
||||||
|
var crowdSecStats = data[3] || {};
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Set portal app context and hide LuCI navigation
|
// Set portal app context and hide LuCI navigation
|
||||||
@ -118,7 +125,7 @@ return view.extend({
|
|||||||
this.renderHeader(),
|
this.renderHeader(),
|
||||||
// Content
|
// Content
|
||||||
E('div', { 'class': 'sb-portal-content' }, [
|
E('div', { 'class': 'sb-portal-content' }, [
|
||||||
this.renderDashboardSection(boardInfo, sysInfo),
|
this.renderDashboardSection(boardInfo, sysInfo, crowdSecStats),
|
||||||
this.renderSecuritySection(),
|
this.renderSecuritySection(),
|
||||||
this.renderNetworkSection(),
|
this.renderNetworkSection(),
|
||||||
this.renderMonitoringSection(),
|
this.renderMonitoringSection(),
|
||||||
@ -204,7 +211,7 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renderDashboardSection: function(boardInfo, sysInfo) {
|
renderDashboardSection: function(boardInfo, sysInfo, crowdSecStats) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var securityApps = portal.getAppsBySection('security');
|
var securityApps = portal.getAppsBySection('security');
|
||||||
var networkApps = portal.getAppsBySection('network');
|
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 runningCount = Object.values(this.appStatuses).filter(function(s) { return s === 'running'; }).length;
|
||||||
var totalServices = Object.keys(this.appStatuses).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' }, [
|
return E('div', { 'class': 'sb-portal-section active', 'data-section': 'dashboard' }, [
|
||||||
E('div', { 'class': 'sb-section-header' }, [
|
E('div', { 'class': 'sb-section-header' }, [
|
||||||
E('h2', { 'class': 'sb-section-title' }, 'SecuBox Dashboard'),
|
E('h2', { 'class': 'sb-section-title' }, 'SecuBox Dashboard'),
|
||||||
@ -244,14 +258,15 @@ return view.extend({
|
|||||||
E('div', { 'class': 'sb-quick-stat-label' }, 'Services Running')
|
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' }, [
|
||||||
E('div', { 'class': 'sb-quick-stat-header' }, [
|
E('div', { 'class': 'sb-quick-stat-header' }, [
|
||||||
E('div', { 'class': 'sb-quick-stat-icon security' }, '\ud83d\udee1\ufe0f'),
|
E('div', { 'class': 'sb-quick-stat-icon security' }, '\ud83d\udeab'),
|
||||||
E('span', { 'class': 'sb-quick-stat-status running' }, 'Protected')
|
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-value' }, totalBlocked.toLocaleString()),
|
||||||
E('div', { 'class': 'sb-quick-stat-label' }, 'Security Modules')
|
E('div', { 'class': 'sb-quick-stat-label' }, 'IPs Blocked')
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Network Apps
|
// Network Apps
|
||||||
@ -300,8 +315,18 @@ return view.extend({
|
|||||||
' / ' + this.formatBytes(sysInfo.memory ? sysInfo.memory.total : 0) + ' used'),
|
' / ' + this.formatBytes(sysInfo.memory ? sysInfo.memory.total : 0) + ' used'),
|
||||||
E('span', { 'class': 'sb-events-meta' }, 'Resource Usage')
|
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))
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user