feat(security-threats): Add Firewall & Network stats to dashboard
- Add get_security_stats RPC to API - Add renderFirewallStats section with 6 stat cards: - WAN Dropped packets - Firewall Rejects - CrowdSec Bans - CrowdSec Alerts 24h - Invalid Connections - HAProxy Connections - Visual gradient cards with formatted numbers (K/M suffixes) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
a1bad31807
commit
1a4096fd2e
@ -64,6 +64,12 @@ var callRemoveWhitelist = rpc.declare({
|
|||||||
expect: { }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callGetSecurityStats = rpc.declare({
|
||||||
|
object: 'luci.secubox-security-threats',
|
||||||
|
method: 'get_security_stats',
|
||||||
|
expect: { }
|
||||||
|
});
|
||||||
|
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
// Utility Functions
|
// Utility Functions
|
||||||
// ==============================================================================
|
// ==============================================================================
|
||||||
@ -212,13 +218,15 @@ function getDashboardData() {
|
|||||||
callStatus(),
|
callStatus(),
|
||||||
callGetActiveThreats(),
|
callGetActiveThreats(),
|
||||||
callGetStatsByType(),
|
callGetStatsByType(),
|
||||||
callGetBlockedIPs()
|
callGetBlockedIPs(),
|
||||||
|
callGetSecurityStats()
|
||||||
]).then(function(results) {
|
]).then(function(results) {
|
||||||
return {
|
return {
|
||||||
status: results[0] || {},
|
status: results[0] || {},
|
||||||
threats: results[1].threats || [],
|
threats: results[1].threats || [],
|
||||||
stats: results[2] || {},
|
stats: results[2] || {},
|
||||||
blocked: results[3].blocked || []
|
blocked: results[3].blocked || [],
|
||||||
|
securityStats: results[4] || {}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -235,6 +243,7 @@ return baseclass.extend({
|
|||||||
getStatsByType: callGetStatsByType,
|
getStatsByType: callGetStatsByType,
|
||||||
getStatsByHost: callGetStatsByHost,
|
getStatsByHost: callGetStatsByHost,
|
||||||
getBlockedIPs: callGetBlockedIPs,
|
getBlockedIPs: callGetBlockedIPs,
|
||||||
|
getSecurityStats: callGetSecurityStats,
|
||||||
blockThreat: callBlockThreat,
|
blockThreat: callBlockThreat,
|
||||||
whitelistHost: callWhitelistHost,
|
whitelistHost: callWhitelistHost,
|
||||||
removeWhitelist: callRemoveWhitelist,
|
removeWhitelist: callRemoveWhitelist,
|
||||||
|
|||||||
@ -16,6 +16,7 @@ return L.view.extend({
|
|||||||
var status = data.status || {};
|
var status = data.status || {};
|
||||||
var stats = data.stats || {};
|
var stats = data.stats || {};
|
||||||
var blocked = data.blocked || [];
|
var blocked = data.blocked || [];
|
||||||
|
var securityStats = data.securityStats || {};
|
||||||
|
|
||||||
// Calculate statistics
|
// Calculate statistics
|
||||||
var threatStats = {
|
var threatStats = {
|
||||||
@ -30,6 +31,7 @@ return L.view.extend({
|
|||||||
|
|
||||||
// Build view elements
|
// Build view elements
|
||||||
var statusBanner = this.renderStatusBanner(status);
|
var statusBanner = this.renderStatusBanner(status);
|
||||||
|
var fwStatsGrid = this.renderFirewallStats(securityStats);
|
||||||
var statsGrid = this.renderStatsGrid(threatStats, blocked.length);
|
var statsGrid = this.renderStatsGrid(threatStats, blocked.length);
|
||||||
var threatDist = this.renderThreatDistribution(stats);
|
var threatDist = this.renderThreatDistribution(stats);
|
||||||
var riskGauge = this.renderRiskGauge(threatStats.avg_score);
|
var riskGauge = this.renderRiskGauge(threatStats.avg_score);
|
||||||
@ -46,7 +48,11 @@ return L.view.extend({
|
|||||||
E('div', { 'class': 'cbi-map-descr' }, _('Real-time threat detection integrating netifyd DPI and CrowdSec intelligence')),
|
E('div', { 'class': 'cbi-map-descr' }, _('Real-time threat detection integrating netifyd DPI and CrowdSec intelligence')),
|
||||||
statusBanner,
|
statusBanner,
|
||||||
E('div', { 'class': 'cbi-section' }, [
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
E('h3', {}, _('Overview')),
|
E('h3', {}, _('Firewall & Network Protection')),
|
||||||
|
fwStatsGrid
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cbi-section' }, [
|
||||||
|
E('h3', {}, _('Threat Overview')),
|
||||||
statsGrid
|
statsGrid
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'cbi-section', 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;' }, [
|
E('div', { 'class': 'cbi-section', 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;' }, [
|
||||||
@ -60,6 +66,61 @@ return L.view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderFirewallStats: function(stats) {
|
||||||
|
var formatNumber = function(n) {
|
||||||
|
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
|
||||||
|
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
|
||||||
|
return n.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return E('div', {
|
||||||
|
'style': 'display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 1rem;'
|
||||||
|
}, [
|
||||||
|
E('div', {
|
||||||
|
'style': 'background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%); padding: 1.2rem; border-radius: 12px; color: white; text-align: center;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'font-size: 2.5rem; font-weight: bold;' }, formatNumber(stats.wan_dropped || 0)),
|
||||||
|
E('div', { 'style': 'font-size: 0.9rem; opacity: 0.9; margin-top: 0.3rem;' }, _('WAN Dropped')),
|
||||||
|
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7; margin-top: 0.2rem;' }, _('Packets blocked at interface'))
|
||||||
|
]),
|
||||||
|
E('div', {
|
||||||
|
'style': 'background: linear-gradient(135deg, #c62828 0%, #e53935 100%); padding: 1.2rem; border-radius: 12px; color: white; text-align: center;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'font-size: 2.5rem; font-weight: bold;' }, formatNumber(stats.firewall_rejects || 0)),
|
||||||
|
E('div', { 'style': 'font-size: 0.9rem; opacity: 0.9; margin-top: 0.3rem;' }, _('FW Rejects')),
|
||||||
|
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7; margin-top: 0.2rem;' }, _('Firewall rule blocks'))
|
||||||
|
]),
|
||||||
|
E('div', {
|
||||||
|
'style': 'background: linear-gradient(135deg, #6a1b9a 0%, #8e24aa 100%); padding: 1.2rem; border-radius: 12px; color: white; text-align: center;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'font-size: 2.5rem; font-weight: bold;' }, formatNumber(stats.crowdsec_bans || 0)),
|
||||||
|
E('div', { 'style': 'font-size: 0.9rem; opacity: 0.9; margin-top: 0.3rem;' }, _('CrowdSec Bans')),
|
||||||
|
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7; margin-top: 0.2rem;' }, _('Active IP bans'))
|
||||||
|
]),
|
||||||
|
E('div', {
|
||||||
|
'style': 'background: linear-gradient(135deg, #ef6c00 0%, #ff9800 100%); padding: 1.2rem; border-radius: 12px; color: white; text-align: center;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'font-size: 2.5rem; font-weight: bold;' }, formatNumber(stats.crowdsec_alerts_24h || 0)),
|
||||||
|
E('div', { 'style': 'font-size: 0.9rem; opacity: 0.9; margin-top: 0.3rem;' }, _('Alerts 24h')),
|
||||||
|
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7; margin-top: 0.2rem;' }, _('CrowdSec detections'))
|
||||||
|
]),
|
||||||
|
E('div', {
|
||||||
|
'style': 'background: linear-gradient(135deg, #455a64 0%, #607d8b 100%); padding: 1.2rem; border-radius: 12px; color: white; text-align: center;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'font-size: 2.5rem; font-weight: bold;' }, formatNumber(stats.invalid_connections || 0)),
|
||||||
|
E('div', { 'style': 'font-size: 0.9rem; opacity: 0.9; margin-top: 0.3rem;' }, _('Invalid Conns')),
|
||||||
|
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7; margin-top: 0.2rem;' }, _('Conntrack anomalies'))
|
||||||
|
]),
|
||||||
|
E('div', {
|
||||||
|
'style': 'background: linear-gradient(135deg, #00695c 0%, #00897b 100%); padding: 1.2rem; border-radius: 12px; color: white; text-align: center;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'font-size: 2.5rem; font-weight: bold;' }, formatNumber(stats.haproxy_connections || 0)),
|
||||||
|
E('div', { 'style': 'font-size: 0.9rem; opacity: 0.9; margin-top: 0.3rem;' }, _('HAProxy Conns')),
|
||||||
|
E('div', { 'style': 'font-size: 0.75rem; opacity: 0.7; margin-top: 0.2rem;' }, _('Reverse proxy sessions'))
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
renderStatusBanner: function(status) {
|
renderStatusBanner: function(status) {
|
||||||
var services = [];
|
var services = [];
|
||||||
var hasIssue = false;
|
var hasIssue = false;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user