mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-17 05:53:15 +02:00
Implement unban events and API and also add it to the Recent stored events, as well some cleanups
This commit is contained in:
@@ -46,24 +46,29 @@ function showBanEventToast(event) {
|
||||
var container = document.getElementById('toast-container');
|
||||
if (!container || !event) return;
|
||||
|
||||
var isUnban = event.eventType === 'unban';
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'toast toast-ban-event';
|
||||
toast.className = isUnban ? 'toast toast-unban-event' : 'toast toast-ban-event';
|
||||
|
||||
var ip = event.ip || 'Unknown IP';
|
||||
var jail = event.jail || 'Unknown Jail';
|
||||
var server = event.serverName || event.serverId || 'Unknown Server';
|
||||
var country = event.country || 'UNKNOWN';
|
||||
|
||||
var title = isUnban ? t('toast.unban.title', 'IP unblocked') : t('toast.ban.title', 'New block occurred');
|
||||
var action = isUnban ? t('toast.unban.action', 'unblocked from') : t('toast.ban.action', 'banned in');
|
||||
var icon = isUnban ? 'fas fa-check-circle text-green-400' : 'fas fa-shield-alt text-red-500';
|
||||
|
||||
toast.innerHTML = ''
|
||||
+ '<div class="flex items-start gap-3">'
|
||||
+ ' <div class="flex-shrink-0 mt-1">'
|
||||
+ ' <i class="fas fa-shield-alt text-red-500"></i>'
|
||||
+ ' <i class="' + icon + '"></i>'
|
||||
+ ' </div>'
|
||||
+ ' <div class="flex-1 min-w-0">'
|
||||
+ ' <div class="font-semibold text-sm">New block occurred</div>'
|
||||
+ ' <div class="font-semibold text-sm">' + title + '</div>'
|
||||
+ ' <div class="text-sm mt-1">'
|
||||
+ ' <span class="font-mono font-semibold">' + escapeHtml(ip) + '</span>'
|
||||
+ ' <span> banned in </span>'
|
||||
+ ' <span> ' + action + ' </span>'
|
||||
+ ' <span class="font-semibold">' + escapeHtml(jail) + '</span>'
|
||||
+ ' </div>'
|
||||
+ ' <div class="text-xs text-gray-400 mt-1">'
|
||||
|
||||
@@ -85,7 +85,7 @@ function fetchBanEventsData() {
|
||||
});
|
||||
}
|
||||
|
||||
// Add new ban event from WebSocket
|
||||
// Add new ban or unban event from WebSocket
|
||||
function addBanEventFromWebSocket(event) {
|
||||
// Check if event already exists (prevent duplicates)
|
||||
// Only check by ID if both events have IDs
|
||||
@@ -95,16 +95,21 @@ function addBanEventFromWebSocket(event) {
|
||||
return e.id === event.id;
|
||||
});
|
||||
} else {
|
||||
// If no ID, check by IP, jail, and occurredAt timestamp
|
||||
// If no ID, check by IP, jail, eventType, and occurredAt timestamp
|
||||
exists = latestBanEvents.some(function(e) {
|
||||
return e.ip === event.ip &&
|
||||
e.jail === event.jail &&
|
||||
e.eventType === event.eventType &&
|
||||
e.occurredAt === event.occurredAt;
|
||||
});
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
console.log('Adding new ban event from WebSocket:', event);
|
||||
// Ensure eventType is set (default to 'ban' for backward compatibility)
|
||||
if (!event.eventType) {
|
||||
event.eventType = 'ban';
|
||||
}
|
||||
console.log('Adding new event from WebSocket:', event);
|
||||
|
||||
// Prepend to the beginning of the array
|
||||
latestBanEvents.unshift(event);
|
||||
@@ -121,7 +126,7 @@ function addBanEventFromWebSocket(event) {
|
||||
// Refresh dashboard data (summary, stats, insights) and re-render
|
||||
refreshDashboardData();
|
||||
} else {
|
||||
console.log('Skipping duplicate ban event:', event);
|
||||
console.log('Skipping duplicate event:', event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,9 +458,8 @@ function unbanIP(jail, ip) {
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
showToast("Error unbanning IP: " + data.error, 'error');
|
||||
} else {
|
||||
showToast(data.message || "IP unbanned successfully", 'success');
|
||||
}
|
||||
// Don't show success toast here - the WebSocket unban event will show a proper toast
|
||||
return refreshData({ silent: true });
|
||||
})
|
||||
.catch(function(err) {
|
||||
@@ -761,12 +765,19 @@ function renderLogOverviewContent() {
|
||||
if (event.ip && recurringMap[event.ip]) {
|
||||
ipCell += ' <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">' + t('logs.badge.recurring', 'Recurring') + '</span>';
|
||||
}
|
||||
var eventType = event.eventType || 'ban';
|
||||
var eventTypeBadge = '';
|
||||
if (eventType === 'unban') {
|
||||
eventTypeBadge = ' <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">Unban</span>';
|
||||
} else {
|
||||
eventTypeBadge = ' <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800">Ban</span>';
|
||||
}
|
||||
html += ''
|
||||
+ ' <tr class="hover:bg-gray-50">'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + escapeHtml(formatDateTime(event.occurredAt || event.createdAt)) + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + serverCell + '</td>'
|
||||
+ ' <td class="hidden sm:table-cell px-2 py-2 whitespace-nowrap">' + jailCell + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + ipCell + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + ipCell + eventTypeBadge + '</td>'
|
||||
+ ' <td class="hidden md:table-cell px-2 py-2 whitespace-nowrap">' + escapeHtml(event.country || '—') + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">'
|
||||
+ ' <div class="flex gap-2">'
|
||||
|
||||
@@ -260,7 +260,7 @@ function openManageJailsModal() {
|
||||
const jsEscapedJailName = jail.jailName.replace(/'/g, "\\'");
|
||||
return ''
|
||||
+ '<div class="flex items-center justify-between gap-3 p-3 bg-gray-50">'
|
||||
+ ' <span class="text-sm font-medium flex-1">' + escapedJailName + '</span>'
|
||||
+ ' <span class="text-sm font-medium flex-1 text-gray-900">' + escapedJailName + '</span>'
|
||||
+ ' <div class="flex items-center gap-3">'
|
||||
+ ' <button'
|
||||
+ ' type="button"'
|
||||
@@ -282,7 +282,7 @@ function openManageJailsModal() {
|
||||
+ ' class="w-11 h-6 bg-gray-200 rounded-full peer-focus:ring-4 peer-focus:ring-blue-300 peer-checked:bg-blue-600 transition-colors"'
|
||||
+ ' ></div>'
|
||||
+ ' <span'
|
||||
+ ' class="absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform peer-checked:translate-x-5"'
|
||||
+ ' class="absolute left-1 top-1/2 -translate-y-1/2 bg-white w-4 h-4 rounded-full transition-transform peer-checked:translate-x-5"'
|
||||
+ ' ></span>'
|
||||
+ ' </label>'
|
||||
+ ' </div>'
|
||||
|
||||
@@ -13,6 +13,32 @@ function onGeoIPProviderChange(provider) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update email fields state based on checkbox preferences
|
||||
function updateEmailFieldsState() {
|
||||
const emailAlertsForBans = document.getElementById('emailAlertsForBans').checked;
|
||||
const emailAlertsForUnbans = document.getElementById('emailAlertsForUnbans').checked;
|
||||
const emailEnabled = emailAlertsForBans || emailAlertsForUnbans;
|
||||
|
||||
// Get all email-related fields
|
||||
const emailFields = [
|
||||
document.getElementById('destEmail'),
|
||||
document.getElementById('smtpHost'),
|
||||
document.getElementById('smtpPort'),
|
||||
document.getElementById('smtpUsername'),
|
||||
document.getElementById('smtpPassword'),
|
||||
document.getElementById('smtpFrom'),
|
||||
document.getElementById('smtpUseTLS'),
|
||||
document.getElementById('sendTestEmailBtn')
|
||||
];
|
||||
|
||||
// Enable/disable all email fields
|
||||
emailFields.forEach(field => {
|
||||
if (field) {
|
||||
field.disabled = !emailEnabled;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
showLoading(true);
|
||||
fetch('/api/settings')
|
||||
@@ -78,6 +104,11 @@ function loadSettings() {
|
||||
|
||||
document.getElementById('destEmail').value = data.destemail || '';
|
||||
|
||||
// Load email alert preferences
|
||||
document.getElementById('emailAlertsForBans').checked = data.emailAlertsForBans !== undefined ? data.emailAlertsForBans : true;
|
||||
document.getElementById('emailAlertsForUnbans').checked = data.emailAlertsForUnbans !== undefined ? data.emailAlertsForUnbans : false;
|
||||
updateEmailFieldsState();
|
||||
|
||||
const select = document.getElementById('alertCountries');
|
||||
for (let i = 0; i < select.options.length; i++) {
|
||||
select.options[i].selected = false;
|
||||
@@ -174,6 +205,8 @@ function saveSettings(event) {
|
||||
callbackUrl: callbackUrl,
|
||||
callbackSecret: document.getElementById('callbackSecret').value.trim(),
|
||||
alertCountries: selectedCountries.length > 0 ? selectedCountries : ["ALL"],
|
||||
emailAlertsForBans: document.getElementById('emailAlertsForBans').checked,
|
||||
emailAlertsForUnbans: document.getElementById('emailAlertsForUnbans').checked,
|
||||
bantimeIncrement: document.getElementById('bantimeIncrement').checked,
|
||||
defaultJailEnable: document.getElementById('defaultJailEnable').checked,
|
||||
bantime: document.getElementById('banTime').value.trim(),
|
||||
|
||||
@@ -93,6 +93,9 @@ class WebSocketManager {
|
||||
case 'ban_event':
|
||||
this.handleBanEvent(message.data);
|
||||
break;
|
||||
case 'unban_event':
|
||||
this.handleBanEvent(message.data); // Use same handler for unban events
|
||||
break;
|
||||
case 'heartbeat':
|
||||
this.handleHeartbeat(message);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user