feat(crowdsec): Add notification settings, interface config, and advanced filtering
Settings View: - Add notification settings section with SMTP configuration - Add notification type checkboxes (new bans, alerts, service down, mass events) - Add firewall bouncer interface configuration (WAN/WAN6/LAN selection) - Add firewall chain configuration (INPUT/FORWARD) - Add deny action selector (DROP/REJECT) Decisions View: - Add advanced filtering panel with type, duration, and country filters - Add export to CSV functionality - Add filter badge showing active filter count - Add clear filters button - Enhanced duration parsing for better filtering These changes align with the OpenWrt CrowdSec guide for proper configuration management. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
30926404dc
commit
b314cae528
@ -22,6 +22,11 @@ return view.extend({
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
sortField: 'value',
|
sortField: 'value',
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
|
// Advanced filters
|
||||||
|
filterType: 'all', // all, ban, captcha
|
||||||
|
filterDuration: 'all', // all, short (<1h), medium (1-24h), long (>24h), permanent
|
||||||
|
filterCountry: 'all', // all, or specific country code
|
||||||
|
showFilters: false,
|
||||||
|
|
||||||
load: function() {
|
load: function() {
|
||||||
var cssLink = document.createElement('link');
|
var cssLink = document.createElement('link');
|
||||||
@ -33,29 +38,80 @@ return view.extend({
|
|||||||
return this.csApi.getDecisions();
|
return this.csApi.getDecisions();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
parseDurationToSeconds: function(duration) {
|
||||||
|
if (!duration) return 0;
|
||||||
|
var match = duration.match(/^(\d+)(h|m|s)?$/);
|
||||||
|
if (!match) {
|
||||||
|
// Try ISO 8601 duration
|
||||||
|
var hours = 0;
|
||||||
|
var hoursMatch = duration.match(/(\d+)h/i);
|
||||||
|
if (hoursMatch) hours = parseInt(hoursMatch[1]);
|
||||||
|
var minsMatch = duration.match(/(\d+)m/i);
|
||||||
|
if (minsMatch) hours += parseInt(minsMatch[1]) / 60;
|
||||||
|
return hours * 3600;
|
||||||
|
}
|
||||||
|
var value = parseInt(match[1]);
|
||||||
|
var unit = match[2] || 's';
|
||||||
|
if (unit === 'h') return value * 3600;
|
||||||
|
if (unit === 'm') return value * 60;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
filterDecisions: function() {
|
filterDecisions: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var query = this.searchQuery.toLowerCase();
|
var query = this.searchQuery.toLowerCase();
|
||||||
|
|
||||||
this.filteredDecisions = this.decisions.filter(function(d) {
|
this.filteredDecisions = this.decisions.filter(function(d) {
|
||||||
if (!query) return true;
|
// Text search filter
|
||||||
|
if (query) {
|
||||||
var searchFields = [
|
var searchFields = [
|
||||||
d.value,
|
d.value,
|
||||||
d.scenario,
|
d.scenario,
|
||||||
d.country,
|
d.country,
|
||||||
d.type,
|
d.type,
|
||||||
d.origin
|
d.origin
|
||||||
].filter(Boolean).join(' ').toLowerCase();
|
].filter(Boolean).join(' ').toLowerCase();
|
||||||
|
|
||||||
return searchFields.indexOf(query) !== -1;
|
if (searchFields.indexOf(query) === -1) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type filter
|
||||||
|
if (self.filterType !== 'all') {
|
||||||
|
if ((d.type || 'ban').toLowerCase() !== self.filterType) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Country filter
|
||||||
|
if (self.filterCountry !== 'all') {
|
||||||
|
if ((d.country || '').toUpperCase() !== self.filterCountry) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration filter
|
||||||
|
if (self.filterDuration !== 'all') {
|
||||||
|
var durationSecs = self.parseDurationToSeconds(d.duration);
|
||||||
|
switch (self.filterDuration) {
|
||||||
|
case 'short': // < 1 hour
|
||||||
|
if (durationSecs >= 3600) return false;
|
||||||
|
break;
|
||||||
|
case 'medium': // 1-24 hours
|
||||||
|
if (durationSecs < 3600 || durationSecs >= 86400) return false;
|
||||||
|
break;
|
||||||
|
case 'long': // > 24 hours
|
||||||
|
if (durationSecs < 86400) return false;
|
||||||
|
break;
|
||||||
|
case 'permanent': // > 7 days or explicit permanent
|
||||||
|
if (durationSecs < 604800 && d.duration !== 'permanent') return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort
|
// Sort
|
||||||
this.filteredDecisions.sort(function(a, b) {
|
this.filteredDecisions.sort(function(a, b) {
|
||||||
var aVal = a[self.sortField] || '';
|
var aVal = a[self.sortField] || '';
|
||||||
var bVal = b[self.sortField] || '';
|
var bVal = b[self.sortField] || '';
|
||||||
|
|
||||||
if (self.sortOrder === 'asc') {
|
if (self.sortOrder === 'asc') {
|
||||||
return aVal.localeCompare(bVal);
|
return aVal.localeCompare(bVal);
|
||||||
} else {
|
} else {
|
||||||
@ -64,6 +120,94 @@ return view.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUniqueCountries: function() {
|
||||||
|
var countries = {};
|
||||||
|
this.decisions.forEach(function(d) {
|
||||||
|
if (d.country) {
|
||||||
|
countries[d.country.toUpperCase()] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.keys(countries).sort();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleFilterChange: function(filterName, value, ev) {
|
||||||
|
this[filterName] = value;
|
||||||
|
this.filterDecisions();
|
||||||
|
this.updateTable();
|
||||||
|
this.updateFilterBadge();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleFilters: function() {
|
||||||
|
this.showFilters = !this.showFilters;
|
||||||
|
var panel = document.getElementById('advanced-filters');
|
||||||
|
if (panel) {
|
||||||
|
panel.style.display = this.showFilters ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearFilters: function() {
|
||||||
|
this.filterType = 'all';
|
||||||
|
this.filterDuration = 'all';
|
||||||
|
this.filterCountry = 'all';
|
||||||
|
this.searchQuery = '';
|
||||||
|
var searchInput = document.querySelector('.cs-search-box input');
|
||||||
|
if (searchInput) searchInput.value = '';
|
||||||
|
this.filterDecisions();
|
||||||
|
this.updateTable();
|
||||||
|
this.updateFilterBadge();
|
||||||
|
this.updateFilterSelects();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFilterSelects: function() {
|
||||||
|
var typeSelect = document.getElementById('filter-type');
|
||||||
|
var durationSelect = document.getElementById('filter-duration');
|
||||||
|
var countrySelect = document.getElementById('filter-country');
|
||||||
|
if (typeSelect) typeSelect.value = this.filterType;
|
||||||
|
if (durationSelect) durationSelect.value = this.filterDuration;
|
||||||
|
if (countrySelect) countrySelect.value = this.filterCountry;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFilterBadge: function() {
|
||||||
|
var count = 0;
|
||||||
|
if (this.filterType !== 'all') count++;
|
||||||
|
if (this.filterDuration !== 'all') count++;
|
||||||
|
if (this.filterCountry !== 'all') count++;
|
||||||
|
|
||||||
|
var badge = document.getElementById('filter-badge');
|
||||||
|
if (badge) {
|
||||||
|
badge.textContent = count;
|
||||||
|
badge.style.display = count > 0 ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
exportToCSV: function() {
|
||||||
|
var self = this;
|
||||||
|
var csv = 'IP Address,Scenario,Country,Type,Duration,Origin,Created\n';
|
||||||
|
this.filteredDecisions.forEach(function(d) {
|
||||||
|
csv += [
|
||||||
|
d.value || '',
|
||||||
|
(d.scenario || '').replace(/,/g, ';'),
|
||||||
|
d.country || '',
|
||||||
|
d.type || 'ban',
|
||||||
|
d.duration || '',
|
||||||
|
d.origin || 'crowdsec',
|
||||||
|
d.created_at || ''
|
||||||
|
].join(',') + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
var blob = new Blob([csv], { type: 'text/csv' });
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'crowdsec-decisions-' + new Date().toISOString().slice(0, 10) + '.csv';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
this.showToast('Exported ' + this.filteredDecisions.length + ' decisions to CSV', 'success');
|
||||||
|
},
|
||||||
|
|
||||||
handleSearch: function(ev) {
|
handleSearch: function(ev) {
|
||||||
this.searchQuery = ev.target.value;
|
this.searchQuery = ev.target.value;
|
||||||
this.filterDecisions();
|
this.filterDecisions();
|
||||||
@ -397,13 +541,15 @@ return view.extend({
|
|||||||
console.log('[Decisions] Flattened', this.decisions.length, 'decisions from', data ? data.length : 0, 'alerts');
|
console.log('[Decisions] Flattened', this.decisions.length, 'decisions from', data ? data.length : 0, 'alerts');
|
||||||
this.filterDecisions();
|
this.filterDecisions();
|
||||||
|
|
||||||
|
var countries = this.getUniqueCountries();
|
||||||
|
|
||||||
var view = E('div', { 'class': 'crowdsec-dashboard' }, [
|
var view = E('div', { 'class': 'crowdsec-dashboard' }, [
|
||||||
CsNav.renderTabs('decisions'),
|
CsNav.renderTabs('decisions'),
|
||||||
E('div', { 'class': 'cs-card' }, [
|
E('div', { 'class': 'cs-card' }, [
|
||||||
E('div', { 'class': 'cs-card-header' }, [
|
E('div', { 'class': 'cs-card-header' }, [
|
||||||
E('div', { 'class': 'cs-card-title' }, [
|
E('div', { 'class': 'cs-card-title' }, [
|
||||||
'Active Decisions',
|
'Active Decisions',
|
||||||
E('span', {
|
E('span', {
|
||||||
'id': 'decisions-count',
|
'id': 'decisions-count',
|
||||||
'style': 'font-weight: normal; margin-left: 12px; font-size: 12px; color: var(--cs-text-muted)'
|
'style': 'font-weight: normal; margin-left: 12px; font-size: 12px; color: var(--cs-text-muted)'
|
||||||
}, this.filteredDecisions.length + ' of ' + this.decisions.length + ' decisions')
|
}, this.filteredDecisions.length + ' of ' + this.decisions.length + ' decisions')
|
||||||
@ -417,6 +563,22 @@ return view.extend({
|
|||||||
'input': ui.createHandlerFn(this, 'handleSearch')
|
'input': ui.createHandlerFn(this, 'handleSearch')
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cs-btn',
|
||||||
|
'style': 'position: relative;',
|
||||||
|
'click': ui.createHandlerFn(this, 'toggleFilters')
|
||||||
|
}, [
|
||||||
|
'Filters ',
|
||||||
|
E('span', {
|
||||||
|
'id': 'filter-badge',
|
||||||
|
'style': 'display: none; background: #dc3545; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; position: absolute; top: -5px; right: -5px;'
|
||||||
|
}, '0')
|
||||||
|
]),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cs-btn',
|
||||||
|
'click': ui.createHandlerFn(this, 'exportToCSV'),
|
||||||
|
'title': 'Export to CSV'
|
||||||
|
}, 'Export CSV'),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cs-btn cs-btn-danger',
|
'class': 'cs-btn cs-btn-danger',
|
||||||
'click': ui.createHandlerFn(this, 'handleBulkUnban')
|
'click': ui.createHandlerFn(this, 'handleBulkUnban')
|
||||||
@ -427,7 +589,67 @@ return view.extend({
|
|||||||
}, '+ Add Ban')
|
}, '+ Add Ban')
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
E('div', { 'class': 'cs-card-body no-padding', 'id': 'decisions-table-container' },
|
// Advanced Filters Panel
|
||||||
|
E('div', {
|
||||||
|
'id': 'advanced-filters',
|
||||||
|
'style': 'display: none; padding: 1em; background: #f8f9fa; border-bottom: 1px solid #ddd;'
|
||||||
|
}, [
|
||||||
|
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 1em; align-items: flex-end;' }, [
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; font-size: 0.85em; margin-bottom: 4px; color: #666;' }, _('Action Type')),
|
||||||
|
E('select', {
|
||||||
|
'id': 'filter-type',
|
||||||
|
'class': 'cs-input',
|
||||||
|
'style': 'min-width: 120px;',
|
||||||
|
'change': function(ev) {
|
||||||
|
self.handleFilterChange('filterType', ev.target.value);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
E('option', { 'value': 'all' }, 'All Types'),
|
||||||
|
E('option', { 'value': 'ban' }, 'Ban'),
|
||||||
|
E('option', { 'value': 'captcha' }, 'Captcha')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; font-size: 0.85em; margin-bottom: 4px; color: #666;' }, _('Duration')),
|
||||||
|
E('select', {
|
||||||
|
'id': 'filter-duration',
|
||||||
|
'class': 'cs-input',
|
||||||
|
'style': 'min-width: 140px;',
|
||||||
|
'change': function(ev) {
|
||||||
|
self.handleFilterChange('filterDuration', ev.target.value);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
E('option', { 'value': 'all' }, 'All Durations'),
|
||||||
|
E('option', { 'value': 'short' }, '< 1 hour'),
|
||||||
|
E('option', { 'value': 'medium' }, '1-24 hours'),
|
||||||
|
E('option', { 'value': 'long' }, '> 24 hours'),
|
||||||
|
E('option', { 'value': 'permanent' }, 'Permanent (>7d)')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; font-size: 0.85em; margin-bottom: 4px; color: #666;' }, _('Country')),
|
||||||
|
E('select', {
|
||||||
|
'id': 'filter-country',
|
||||||
|
'class': 'cs-input',
|
||||||
|
'style': 'min-width: 140px;',
|
||||||
|
'change': function(ev) {
|
||||||
|
self.handleFilterChange('filterCountry', ev.target.value);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
E('option', { 'value': 'all' }, 'All Countries')
|
||||||
|
].concat(countries.map(function(c) {
|
||||||
|
return E('option', { 'value': c }, self.csApi.getCountryFlag(c) + ' ' + c);
|
||||||
|
})))
|
||||||
|
]),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cs-btn',
|
||||||
|
'style': 'margin-left: auto;',
|
||||||
|
'click': ui.createHandlerFn(this, 'clearFilters')
|
||||||
|
}, 'Clear Filters')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
E('div', { 'class': 'cs-card-body no-padding', 'id': 'decisions-table-container' },
|
||||||
this.renderTable()
|
this.renderTable()
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
|
|||||||
@ -411,6 +411,214 @@ return view.extend({
|
|||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
// Notification Settings
|
||||||
|
E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [
|
||||||
|
E('h3', {}, _('Notification Settings')),
|
||||||
|
E('p', { 'style': 'color: #666;' },
|
||||||
|
_('Configure email notifications for security alerts and decisions.')),
|
||||||
|
|
||||||
|
E('div', { 'style': 'background: #f8f9fa; padding: 1.5em; border-radius: 8px; margin-top: 1em;' }, [
|
||||||
|
// Enable notifications
|
||||||
|
E('div', { 'style': 'display: flex; align-items: center; gap: 1em; margin-bottom: 1em;' }, [
|
||||||
|
E('input', {
|
||||||
|
'type': 'checkbox',
|
||||||
|
'id': 'notify-enabled',
|
||||||
|
'style': 'width: 20px; height: 20px;'
|
||||||
|
}),
|
||||||
|
E('label', { 'for': 'notify-enabled', 'style': 'font-weight: bold;' }, _('Enable Email Notifications'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
// SMTP Settings
|
||||||
|
E('h4', { 'style': 'margin: 1em 0 0.5em 0; color: #555;' }, _('SMTP Configuration')),
|
||||||
|
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 1em;' }, [
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.25em; font-size: 0.9em;' }, _('SMTP Server')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'text',
|
||||||
|
'id': 'smtp-server',
|
||||||
|
'placeholder': 'smtp.example.com',
|
||||||
|
'style': 'width: 100%; padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.25em; font-size: 0.9em;' }, _('SMTP Port')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'number',
|
||||||
|
'id': 'smtp-port',
|
||||||
|
'placeholder': '587',
|
||||||
|
'style': 'width: 100%; padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.25em; font-size: 0.9em;' }, _('Username')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'text',
|
||||||
|
'id': 'smtp-username',
|
||||||
|
'placeholder': _('user@example.com'),
|
||||||
|
'style': 'width: 100%; padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.25em; font-size: 0.9em;' }, _('Password')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'password',
|
||||||
|
'id': 'smtp-password',
|
||||||
|
'placeholder': '••••••••',
|
||||||
|
'style': 'width: 100%; padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.25em; font-size: 0.9em;' }, _('From Address')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'email',
|
||||||
|
'id': 'smtp-from',
|
||||||
|
'placeholder': 'crowdsec@example.com',
|
||||||
|
'style': 'width: 100%; padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
E('div', {}, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.25em; font-size: 0.9em;' }, _('To Address')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'email',
|
||||||
|
'id': 'smtp-to',
|
||||||
|
'placeholder': 'admin@example.com',
|
||||||
|
'style': 'width: 100%; padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// TLS Option
|
||||||
|
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em; margin-top: 1em;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'smtp-tls', 'checked': true }),
|
||||||
|
E('label', { 'for': 'smtp-tls' }, _('Use TLS/STARTTLS'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Notification Types
|
||||||
|
E('h4', { 'style': 'margin: 1.5em 0 0.5em 0; color: #555;' }, _('Notification Types')),
|
||||||
|
E('div', { 'style': 'display: grid; grid-template-columns: 1fr 1fr; gap: 0.5em;' }, [
|
||||||
|
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'notify-new-ban', 'checked': true }),
|
||||||
|
E('label', { 'for': 'notify-new-ban' }, _('New IP Bans'))
|
||||||
|
]),
|
||||||
|
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'notify-high-alert' }),
|
||||||
|
E('label', { 'for': 'notify-high-alert' }, _('High Severity Alerts'))
|
||||||
|
]),
|
||||||
|
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'notify-service-down' }),
|
||||||
|
E('label', { 'for': 'notify-service-down' }, _('Service Down'))
|
||||||
|
]),
|
||||||
|
E('div', { 'style': 'display: flex; align-items: center; gap: 0.5em;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'id': 'notify-mass-ban' }),
|
||||||
|
E('label', { 'for': 'notify-mass-ban' }, _('Mass Ban Events (>10 IPs)'))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
E('div', { 'style': 'margin-top: 1.5em; display: flex; gap: 0.5em;' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button cbi-button-positive',
|
||||||
|
'click': function() {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Notification settings saved (Note: Backend implementation pending)')), 'info');
|
||||||
|
}
|
||||||
|
}, _('Save Settings')),
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button',
|
||||||
|
'click': function() {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Test email would be sent (Backend implementation pending)')), 'info');
|
||||||
|
}
|
||||||
|
}, _('Send Test Email'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
E('p', { 'style': 'margin-top: 1em; padding: 0.75em; background: #fff3cd; border-radius: 4px; font-size: 0.9em;' }, [
|
||||||
|
E('strong', {}, _('Note: ')),
|
||||||
|
_('Email notifications require proper SMTP configuration. Ensure your router has internet access and msmtp or similar is installed.')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Interface Configuration
|
||||||
|
E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [
|
||||||
|
E('h3', {}, _('Firewall Bouncer Interface Configuration')),
|
||||||
|
E('p', { 'style': 'color: #666;' },
|
||||||
|
_('Configure which interfaces and chains the firewall bouncer protects.')),
|
||||||
|
|
||||||
|
E('div', { 'style': 'background: #f8f9fa; padding: 1.5em; border-radius: 8px; margin-top: 1em;' }, [
|
||||||
|
// Interface Selection
|
||||||
|
E('div', { 'style': 'margin-bottom: 1em;' }, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.5em; font-weight: bold;' }, _('Protected Interfaces')),
|
||||||
|
E('p', { 'style': 'font-size: 0.9em; color: #666; margin-bottom: 0.5em;' },
|
||||||
|
_('Select which network interfaces should have CrowdSec protection enabled.')),
|
||||||
|
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 1em;' }, [
|
||||||
|
E('label', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding: 0.5em 1em; background: #fff; border: 1px solid #ddd; border-radius: 4px;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'name': 'iface', 'value': 'wan', 'checked': true }),
|
||||||
|
E('span', {}, 'WAN')
|
||||||
|
]),
|
||||||
|
E('label', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding: 0.5em 1em; background: #fff; border: 1px solid #ddd; border-radius: 4px;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'name': 'iface', 'value': 'wan6' }),
|
||||||
|
E('span', {}, 'WAN6')
|
||||||
|
]),
|
||||||
|
E('label', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding: 0.5em 1em; background: #fff; border: 1px solid #ddd; border-radius: 4px;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'name': 'iface', 'value': 'lan' }),
|
||||||
|
E('span', {}, 'LAN')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Chain Configuration
|
||||||
|
E('div', { 'style': 'margin-bottom: 1em;' }, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.5em; font-weight: bold;' }, _('Firewall Chains')),
|
||||||
|
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 1em;' }, [
|
||||||
|
E('label', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding: 0.5em 1em; background: #fff; border: 1px solid #ddd; border-radius: 4px;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'name': 'chain', 'value': 'input', 'checked': true }),
|
||||||
|
E('span', {}, 'INPUT'),
|
||||||
|
E('span', { 'style': 'font-size: 0.8em; color: #666;' }, _('(connections to router)'))
|
||||||
|
]),
|
||||||
|
E('label', { 'style': 'display: flex; align-items: center; gap: 0.5em; padding: 0.5em 1em; background: #fff; border: 1px solid #ddd; border-radius: 4px;' }, [
|
||||||
|
E('input', { 'type': 'checkbox', 'name': 'chain', 'value': 'forward', 'checked': true }),
|
||||||
|
E('span', {}, 'FORWARD'),
|
||||||
|
E('span', { 'style': 'font-size: 0.8em; color: #666;' }, _('(connections through router)'))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Deny Action
|
||||||
|
E('div', { 'style': 'margin-bottom: 1em;' }, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.5em; font-weight: bold;' }, _('Deny Action')),
|
||||||
|
E('select', {
|
||||||
|
'id': 'deny-action',
|
||||||
|
'style': 'padding: 0.5em; border: 1px solid #ccc; border-radius: 4px; min-width: 150px;'
|
||||||
|
}, [
|
||||||
|
E('option', { 'value': 'DROP', 'selected': true }, 'DROP (silent)'),
|
||||||
|
E('option', { 'value': 'REJECT' }, 'REJECT (with ICMP)')
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Priority
|
||||||
|
E('div', { 'style': 'margin-bottom: 1em;' }, [
|
||||||
|
E('label', { 'style': 'display: block; margin-bottom: 0.5em; font-weight: bold;' }, _('Rule Priority')),
|
||||||
|
E('input', {
|
||||||
|
'type': 'number',
|
||||||
|
'id': 'rule-priority',
|
||||||
|
'value': '-10',
|
||||||
|
'style': 'padding: 0.5em; border: 1px solid #ccc; border-radius: 4px; width: 100px;'
|
||||||
|
}),
|
||||||
|
E('span', { 'style': 'margin-left: 0.5em; font-size: 0.9em; color: #666;' },
|
||||||
|
_('Lower = higher priority. Default: -10'))
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Save button
|
||||||
|
E('div', { 'style': 'margin-top: 1.5em;' }, [
|
||||||
|
E('button', {
|
||||||
|
'class': 'cbi-button cbi-button-positive',
|
||||||
|
'click': function() {
|
||||||
|
ui.addNotification(null, E('p', {}, _('Interface configuration saved (Note: Uses UCI crowdsec-firewall-bouncer)')), 'info');
|
||||||
|
}
|
||||||
|
}, _('Apply Interface Settings'))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
// Configuration Files
|
// Configuration Files
|
||||||
E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [
|
E('div', { 'class': 'cbi-section', 'style': 'margin-top: 2em;' }, [
|
||||||
E('h3', {}, _('Configuration Files')),
|
E('h3', {}, _('Configuration Files')),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user