Optimize error-reporting of failure when reloading fail2ban

This commit is contained in:
2026-02-11 17:02:18 +01:00
parent bdfd96b65d
commit 08112ff9b9
10 changed files with 58 additions and 24 deletions

View File

@@ -1458,12 +1458,10 @@ func SetJailFilterConfigHandler(c *gin.Context) {
// Reload fail2ban
config.DebugLog("Reloading fail2ban")
if err := conn.Reload(c.Request.Context()); err != nil {
config.DebugLog("Failed to reload fail2ban: %v", err)
// Still return success but warn about reload failure
// The config was saved successfully, user can manually reload
log.Printf("⚠️ Config saved but fail2ban reload failed: %v", err)
c.JSON(http.StatusOK, gin.H{
"message": "Config saved successfully, but fail2ban reload failed",
"warning": "Please check the fail2ban configuration and reload manually: " + err.Error(),
"warning": err.Error(),
})
return
}
@@ -1908,13 +1906,9 @@ func UpdateJailManagementHandler(c *gin.Context) {
}
}
// Update errMsg with detailed error output when debug mode is enabled
settings := config.GetSettings()
if settings.Debug && detailedErrorOutput != "" {
errMsg = fmt.Sprintf("%s\n\nDetailed error output:\n%s", errMsg, detailedErrorOutput)
} else if detailedErrorOutput != "" {
// Even without debug mode, include basic error info
errMsg = fmt.Sprintf("%s (check debug mode for details)", errMsg)
if detailedErrorOutput != "" {
// We use only the extracted error output
errMsg = strings.TrimSpace(detailedErrorOutput)
}
// If any jails were enabled in this request and reload failed, disable them all

View File

@@ -179,6 +179,7 @@ mark {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
color: #fff;
pointer-events: auto;
font-weight: 500;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
opacity: 0;

View File

@@ -28,12 +28,37 @@ function showToast(message, type, duration) {
var toast = document.createElement('div');
var variant = type || 'info';
toast.className = 'toast toast-' + variant;
toast.textContent = message;
// Build inner layout with close button
var wrapper = document.createElement('div');
wrapper.className = 'flex items-start';
var textSpan = document.createElement('span');
textSpan.className = 'flex-1';
textSpan.textContent = message;
var closeBtn = document.createElement('button');
closeBtn.className = 'flex-shrink-0 ml-2 mt-0.5 opacity-60 hover:opacity-100 focus:outline-none';
closeBtn.setAttribute('aria-label', 'Close');
closeBtn.innerHTML = '<i class="fas fa-times text-sm"></i>';
wrapper.appendChild(textSpan);
wrapper.appendChild(closeBtn);
toast.appendChild(wrapper);
// Close button handler
closeBtn.addEventListener('click', function(e) {
e.stopPropagation();
clearTimeout(autoRemoveTimer);
toast.classList.remove('show');
setTimeout(function() { toast.remove(); }, 300);
});
container.appendChild(toast);
requestAnimationFrame(function() {
toast.classList.add('show');
});
setTimeout(function() {
var autoRemoveTimer = setTimeout(function() {
toast.classList.remove('show');
setTimeout(function() {
toast.remove();

View File

@@ -155,7 +155,11 @@ function saveJailConfig() {
return;
}
closeModal('jailConfigModal');
showToast(t('filter_debug.save_success', 'Filter and jail config saved and reloaded'), 'success');
if (data.warning) {
showToast(t('filter_debug.save_reload_warning', 'Config saved, but fail2ban reload failed') + ': ' + data.warning, 'warning', 12000);
} else {
showToast(t('filter_debug.save_success', 'Filter and jail config saved and reloaded'), 'success');
}
return refreshData({ silent: true });
})
.catch(function(err) {
@@ -468,17 +472,17 @@ function saveManageJailsSingle(checkbox) {
console.error('Could not find jail name span');
return;
}
const jailName = nameSpan.textContent.trim();
if (!jailName) {
console.error('Jail name is empty');
return;
}
const isEnabled = checkbox.checked;
const updatedJails = {};
updatedJails[jailName] = isEnabled;
console.log('Saving jail state:', jailName, 'enabled:', isEnabled, 'payload:', updatedJails);
// Send updated state to the API endpoint /api/jails/manage.
@@ -500,22 +504,20 @@ function saveManageJailsSingle(checkbox) {
if (data.error) {
var errorMsg = data.error;
var toastType = 'error';
// If jails were auto-disabled, check if this jail was one of them
var wasAutoDisabled = data.autoDisabled && data.enabledJails && Array.isArray(data.enabledJails) && data.enabledJails.indexOf(jailName) !== -1;
if (wasAutoDisabled) {
checkbox.checked = false;
toastType = 'warning';
// Use the message if available, otherwise use the error
errorMsg = data.message || errorMsg;
} else {
// Revert checkbox state on error
checkbox.checked = !isEnabled;
}
showToast(errorMsg, toastType);
showToast(errorMsg, toastType, wasAutoDisabled ? 15000 : undefined);
// Still reload the jail list to reflect the actual state
return fetch(withServerParam('/api/jails/manage'), {
headers: serverHeaders()