secubox-openwrt/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/bouncers.js
CyberMind-FR cf39eb6e1d fix: resolve validation issues across all modules
- Fixed minified RPC declaration in secubox/modules.js that caused false positive in validation
- Added 30 missing menu entries across 10 modules:
  * bandwidth-manager: clients, schedules
  * client-guardian: zones, portal, logs, alerts, parental
  * crowdsec-dashboard: metrics
  * netdata-dashboard: system, processes, realtime, network
  * netifyd-dashboard: talkers, risks, devices
  * network-modes: router, accesspoint, relay, sniffer
  * secubox: settings
  * system-hub: components, diagnostics, health, remote, settings
  * vhost-manager: internal, ssl, redirects
  * wireguard-dashboard: traffic, config
- All modules now pass comprehensive validation (0 errors, 0 warnings)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 09:01:06 +01:00

222 lines
7.0 KiB
JavaScript

'use strict';
'require view';
'require dom';
'require poll';
'require ui';
'require crowdsec-dashboard/api as API';
return view.extend({
load: function() {
return Promise.all([
API.getBouncers(),
API.getStatus()
]);
},
render: function(data) {
var bouncers = data[0] || [];
var status = data[1] || {};
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('CrowdSec Bouncers')),
E('div', { 'class': 'cbi-map-descr' },
_('Bouncers are components that enforce CrowdSec decisions by blocking malicious IPs at various points (firewall, web server, etc.).')),
// Status Card
E('div', { 'class': 'cbi-section', 'style': 'background: ' + (status.crowdsec === 'running' ? '#d4edda' : '#f8d7da') + '; border-left: 4px solid ' + (status.crowdsec === 'running' ? '#28a745' : '#dc3545') + '; padding: 1em; margin-bottom: 1.5em;' }, [
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center;' }, [
E('div', {}, [
E('strong', {}, _('CrowdSec Status:')),
' ',
E('span', { 'class': 'badge', 'style': 'background: ' + (status.crowdsec === 'running' ? '#28a745' : '#dc3545') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px; margin-left: 0.5em;' },
status.crowdsec === 'running' ? _('RUNNING') : _('STOPPED'))
]),
E('div', {}, [
E('strong', {}, _('Active Bouncers:')),
' ',
E('span', { 'style': 'font-size: 1.3em; color: #0088cc; font-weight: bold; margin-left: 0.5em;' },
bouncers.length.toString())
])
])
]),
// Bouncers Table
E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 1em;' }, [
E('h3', { 'style': 'margin: 0;' }, _('Registered Bouncers')),
E('button', {
'class': 'cbi-button cbi-button-action',
'click': L.bind(this.handleRefresh, this)
}, _('Refresh'))
]),
E('div', { 'class': 'table-wrapper' }, [
E('table', { 'class': 'table', 'id': 'bouncers-table' }, [
E('thead', {}, [
E('tr', {}, [
E('th', {}, _('Name')),
E('th', {}, _('IP Address')),
E('th', {}, _('Type')),
E('th', {}, _('Version')),
E('th', {}, _('Last Pull')),
E('th', {}, _('Status')),
E('th', {}, _('Authentication'))
])
]),
E('tbody', { 'id': 'bouncers-tbody' },
this.renderBouncerRows(bouncers)
)
])
])
]),
// Help Section
E('div', { 'class': 'cbi-section', 'style': 'background: #e8f4f8; padding: 1em; margin-top: 2em;' }, [
E('h3', {}, _('About Bouncers')),
E('p', {}, _('Bouncers are remediation components that connect to the CrowdSec Local API to fetch decisions and apply them on your infrastructure.')),
E('div', { 'style': 'margin-top: 1em;' }, [
E('strong', {}, _('Common Bouncer Types:')),
E('ul', { 'style': 'margin-top: 0.5em;' }, [
E('li', {}, [
E('strong', {}, 'cs-firewall-bouncer:'),
' ',
_('Manages iptables/nftables rules to block IPs at the firewall level')
]),
E('li', {}, [
E('strong', {}, 'cs-nginx-bouncer:'),
' ',
_('Blocks IPs directly in Nginx web server')
]),
E('li', {}, [
E('strong', {}, 'cs-haproxy-bouncer:'),
' ',
_('Integrates with HAProxy load balancer')
]),
E('li', {}, [
E('strong', {}, 'cs-cloudflare-bouncer:'),
' ',
_('Pushes decisions to Cloudflare firewall')
])
])
]),
E('p', { 'style': 'margin-top: 1em; padding: 0.75em; background: #fff3cd; border-radius: 4px;' }, [
E('strong', {}, _('Note:')),
' ',
_('To register a new bouncer, use the command: '),
E('code', {}, 'cscli bouncers add <bouncer-name>')
])
])
]);
// Setup auto-refresh
poll.add(L.bind(function() {
return API.getBouncers().then(L.bind(function(refreshData) {
var tbody = document.getElementById('bouncers-tbody');
if (tbody) {
dom.content(tbody, this.renderBouncerRows(refreshData || []));
}
}, this));
}, this), 10);
return view;
},
renderBouncerRows: function(bouncers) {
if (!bouncers || bouncers.length === 0) {
return E('tr', {}, [
E('td', { 'colspan': 7, 'style': 'text-align: center; padding: 2em; color: #999;' },
_('No bouncers registered. Use "cscli bouncers add <name>" to register a bouncer.'))
]);
}
return bouncers.map(L.bind(function(bouncer) {
var lastPull = bouncer.last_pull || bouncer.lastPull || 'Never';
var isRecent = this.isRecentPull(lastPull);
return E('tr', {
'style': isRecent ? '' : 'opacity: 0.6;'
}, [
E('td', {}, [
E('strong', {}, bouncer.name || 'Unknown')
]),
E('td', {}, [
E('code', { 'style': 'font-size: 0.9em;' }, bouncer.ip_address || bouncer.ipAddress || 'N/A')
]),
E('td', {}, bouncer.type || 'Unknown'),
E('td', {}, bouncer.version || 'N/A'),
E('td', {}, this.formatLastPull(lastPull)),
E('td', {}, [
E('span', {
'class': 'badge',
'style': 'background: ' + (isRecent ? '#28a745' : '#6c757d') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px;'
}, isRecent ? _('Active') : _('Inactive'))
]),
E('td', {}, [
E('span', {
'class': 'badge',
'style': 'background: ' + (bouncer.revoked ? '#dc3545' : '#28a745') + '; color: white; padding: 0.25em 0.6em; border-radius: 3px;'
}, bouncer.revoked ? _('Revoked') : _('Valid'))
])
]);
}, this));
},
formatLastPull: function(lastPull) {
if (!lastPull || lastPull === 'Never' || lastPull === 'never') {
return E('span', { 'style': 'color: #999;' }, _('Never'));
}
try {
var pullDate = new Date(lastPull);
var now = new Date();
var diffMinutes = Math.floor((now - pullDate) / 60000);
if (diffMinutes < 1) return _('Just now');
if (diffMinutes < 60) return diffMinutes + 'm ago';
if (diffMinutes < 1440) return Math.floor(diffMinutes / 60) + 'h ago';
return Math.floor(diffMinutes / 1440) + 'd ago';
} catch(e) {
return lastPull;
}
},
isRecentPull: function(lastPull) {
if (!lastPull || lastPull === 'Never' || lastPull === 'never') {
return false;
}
try {
var pullDate = new Date(lastPull);
var now = new Date();
var diffMinutes = Math.floor((now - pullDate) / 60000);
// Consider active if pulled within last 5 minutes
return diffMinutes < 5;
} catch(e) {
return false;
}
},
handleRefresh: function() {
poll.start();
return Promise.all([
API.getBouncers(),
API.getStatus()
]).then(L.bind(function(data) {
var tbody = document.getElementById('bouncers-tbody');
if (tbody) {
var bouncers = data[0] || [];
dom.content(tbody, this.renderBouncerRows(bouncers));
}
ui.addNotification(null, E('p', _('Bouncer list refreshed')), 'info');
}, this)).catch(function(err) {
ui.addNotification(null, E('p', _('Failed to refresh: %s').format(err.message || err)), 'error');
});
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});