mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-19 06:53:14 +02:00
Fix javascript files to include sections and cleanup the initial movment from one js to splited up ones
This commit is contained in:
@@ -1,133 +1,56 @@
|
||||
// Jail management functions for Fail2ban UI
|
||||
"use strict";
|
||||
|
||||
function preventExtensionInterference(element) {
|
||||
if (!element) return;
|
||||
try {
|
||||
// Ensure control property exists to prevent "Cannot read properties of undefined" errors
|
||||
if (!element.control) {
|
||||
Object.defineProperty(element, 'control', {
|
||||
value: {
|
||||
type: element.type || 'textarea',
|
||||
name: element.name || 'filter-config-editor',
|
||||
form: null,
|
||||
autocomplete: 'off'
|
||||
},
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
// Prevent extensions from adding their own properties
|
||||
Object.seal(element.control);
|
||||
} catch (e) {
|
||||
// Silently ignore errors
|
||||
// =========================================================================
|
||||
// Jail creation
|
||||
// =========================================================================
|
||||
|
||||
function createJail() {
|
||||
const jailName = document.getElementById('newJailName').value.trim();
|
||||
const content = document.getElementById('newJailContent').value.trim();
|
||||
|
||||
if (!jailName) {
|
||||
showToast('Jail name is required', 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function openJailConfigModal(jailName) {
|
||||
currentJailForConfig = jailName;
|
||||
var filterTextArea = document.getElementById('filterConfigTextarea');
|
||||
var jailTextArea = document.getElementById('jailConfigTextarea');
|
||||
filterTextArea.value = '';
|
||||
jailTextArea.value = '';
|
||||
|
||||
// Prevent browser extensions from interfering
|
||||
preventExtensionInterference(filterTextArea);
|
||||
preventExtensionInterference(jailTextArea);
|
||||
|
||||
document.getElementById('modalJailName').textContent = jailName;
|
||||
|
||||
// Hide test logpath section initially
|
||||
document.getElementById('testLogpathSection').classList.add('hidden');
|
||||
document.getElementById('logpathResults').classList.add('hidden');
|
||||
|
||||
showLoading(true);
|
||||
var url = '/api/jails/' + encodeURIComponent(jailName) + '/config';
|
||||
fetch(withServerParam(url), {
|
||||
headers: serverHeaders()
|
||||
fetch(withServerParam('/api/jails'), {
|
||||
method: 'POST',
|
||||
headers: serverHeaders({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify({
|
||||
jailName: jailName,
|
||||
content: content
|
||||
})
|
||||
})
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(res) {
|
||||
if (!res.ok) {
|
||||
return res.json().then(function(data) {
|
||||
throw new Error(data.error || 'Server returned ' + res.status);
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
showToast("Error loading config: " + data.error, 'error');
|
||||
showToast('Error creating jail: ' + data.error, 'error');
|
||||
return;
|
||||
}
|
||||
filterTextArea.value = data.filter || '';
|
||||
jailTextArea.value = data.jailConfig || '';
|
||||
|
||||
// Display file paths if available
|
||||
var filterFilePathEl = document.getElementById('filterFilePath');
|
||||
var jailFilePathEl = document.getElementById('jailFilePath');
|
||||
if (filterFilePathEl && data.filterFilePath) {
|
||||
filterFilePathEl.textContent = data.filterFilePath;
|
||||
filterFilePathEl.style.display = 'block';
|
||||
} else if (filterFilePathEl) {
|
||||
filterFilePathEl.style.display = 'none';
|
||||
}
|
||||
if (jailFilePathEl && data.jailFilePath) {
|
||||
jailFilePathEl.textContent = data.jailFilePath;
|
||||
jailFilePathEl.style.display = 'block';
|
||||
} else if (jailFilePathEl) {
|
||||
jailFilePathEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// Check if logpath is set in jail config and show test button
|
||||
updateLogpathButtonVisibility();
|
||||
|
||||
// Show hint for local servers
|
||||
var localServerHint = document.getElementById('localServerLogpathHint');
|
||||
if (localServerHint && currentServer && currentServer.type === 'local') {
|
||||
localServerHint.classList.remove('hidden');
|
||||
} else if (localServerHint) {
|
||||
localServerHint.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Add listener to update button visibility when jail config changes
|
||||
jailTextArea.addEventListener('input', updateLogpathButtonVisibility);
|
||||
|
||||
// Prevent extension interference before opening modal
|
||||
preventExtensionInterference(filterTextArea);
|
||||
preventExtensionInterference(jailTextArea);
|
||||
openModal('jailConfigModal');
|
||||
|
||||
// Setup syntax highlighting for both textareas after modal is visible
|
||||
setTimeout(function() {
|
||||
preventExtensionInterference(filterTextArea);
|
||||
preventExtensionInterference(jailTextArea);
|
||||
}, 200);
|
||||
closeModal('createJailModal');
|
||||
showToast(data.message || 'Jail created successfully', 'success');
|
||||
openManageJailsModal();
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast("Error: " + err, 'error');
|
||||
console.error('Error creating jail:', err);
|
||||
showToast('Error creating jail: ' + (err.message || err), 'error');
|
||||
})
|
||||
.finally(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function updateLogpathButtonVisibility() {
|
||||
var jailTextArea = document.getElementById('jailConfigTextarea');
|
||||
var jailConfig = jailTextArea ? jailTextArea.value : '';
|
||||
var hasLogpath = /logpath\s*=/i.test(jailConfig);
|
||||
var testSection = document.getElementById('testLogpathSection');
|
||||
var localServerHint = document.getElementById('localServerLogpathHint');
|
||||
|
||||
if (hasLogpath && testSection) {
|
||||
testSection.classList.remove('hidden');
|
||||
// Show hint for local servers
|
||||
if (localServerHint && currentServer && currentServer.type === 'local') {
|
||||
localServerHint.classList.remove('hidden');
|
||||
} else if (localServerHint) {
|
||||
localServerHint.classList.add('hidden');
|
||||
}
|
||||
} else if (testSection) {
|
||||
testSection.classList.add('hidden');
|
||||
document.getElementById('logpathResults').classList.add('hidden');
|
||||
if (localServerHint) {
|
||||
localServerHint.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
// =========================================================================
|
||||
// Jail configuration saving
|
||||
// =========================================================================
|
||||
|
||||
function saveJailConfig() {
|
||||
if (!currentJailForConfig) return;
|
||||
@@ -178,28 +101,195 @@ function saveJailConfig() {
|
||||
});
|
||||
}
|
||||
|
||||
// Extract logpath from jail config text
|
||||
// Supports multiple logpaths in a single line (space-separated) or multiple lines
|
||||
// Fail2ban supports both formats:
|
||||
// logpath = /var/log/file1.log /var/log/file2.log
|
||||
// logpath = /var/log/file1.log
|
||||
// /var/log/file2.log
|
||||
function updateJailConfigFromFilter() {
|
||||
const filterSelect = document.getElementById('newJailFilter');
|
||||
const jailNameInput = document.getElementById('newJailName');
|
||||
const contentTextarea = document.getElementById('newJailContent');
|
||||
|
||||
if (!filterSelect || !contentTextarea) return;
|
||||
const selectedFilter = filterSelect.value;
|
||||
|
||||
if (!selectedFilter) {
|
||||
return;
|
||||
}
|
||||
if (jailNameInput && !jailNameInput.value.trim()) {
|
||||
jailNameInput.value = selectedFilter;
|
||||
}
|
||||
|
||||
const jailName = (jailNameInput && jailNameInput.value.trim()) || selectedFilter;
|
||||
const config = `[${jailName}]
|
||||
enabled = false
|
||||
filter = ${selectedFilter}
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = 5
|
||||
bantime = 3600
|
||||
findtime = 600`;
|
||||
|
||||
contentTextarea.value = config;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Jail toggle enable/disable state of single jails
|
||||
// =========================================================================
|
||||
|
||||
function saveManageJailsSingle(checkbox) {
|
||||
const item = checkbox.closest('div.flex.items-center.justify-between');
|
||||
if (!item) {
|
||||
console.error('Could not find parent container for checkbox');
|
||||
return;
|
||||
}
|
||||
|
||||
const nameSpan = item.querySelector('span.text-sm.font-medium');
|
||||
if (!nameSpan) {
|
||||
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);
|
||||
|
||||
fetch(withServerParam('/api/jails/manage'), {
|
||||
method: 'POST',
|
||||
headers: serverHeaders({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify(updatedJails),
|
||||
})
|
||||
.then(function(res) {
|
||||
if (!res.ok) {
|
||||
return res.json().then(function(data) {
|
||||
throw new Error(data.error || 'Server returned ' + res.status);
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
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';
|
||||
} else {
|
||||
// Revert checkbox state if error occurs
|
||||
checkbox.checked = !isEnabled;
|
||||
}
|
||||
showToast(errorMsg, toastType, wasAutoDisabled ? 15000 : undefined);
|
||||
|
||||
// Reload the jail list to reflect the actual state
|
||||
return fetch(withServerParam('/api/jails/manage'), {
|
||||
headers: serverHeaders()
|
||||
}).then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.jails && data.jails.length) {
|
||||
const jail = data.jails.find(function(j) { return j.jailName === jailName; });
|
||||
if (jail) {
|
||||
checkbox.checked = jail.enabled;
|
||||
}
|
||||
}
|
||||
loadServers().then(function() {
|
||||
updateRestartBanner();
|
||||
return refreshData({ silent: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (data.warning) {
|
||||
showToast(data.warning, 'warning');
|
||||
}
|
||||
|
||||
console.log('Jail state saved successfully:', data);
|
||||
showToast(data.message || ('Jail ' + jailName + ' ' + (isEnabled ? 'enabled' : 'disabled') + ' successfully'), 'success');
|
||||
return fetch(withServerParam('/api/jails/manage'), {
|
||||
headers: serverHeaders()
|
||||
}).then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.jails && data.jails.length) {
|
||||
const jail = data.jails.find(function(j) { return j.jailName === jailName; });
|
||||
if (jail) {
|
||||
checkbox.checked = jail.enabled;
|
||||
}
|
||||
}
|
||||
loadServers().then(function() {
|
||||
updateRestartBanner();
|
||||
return refreshData({ silent: true });
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error saving jail settings:', err);
|
||||
showToast("Error saving jail settings: " + (err.message || err), 'error');
|
||||
checkbox.checked = !isEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Jail deletion
|
||||
// =========================================================================
|
||||
|
||||
function deleteJail(jailName) {
|
||||
if (!confirm('Are you sure you want to delete the jail "' + escapeHtml(jailName) + '"? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
showLoading(true);
|
||||
fetch(withServerParam('/api/jails/' + encodeURIComponent(jailName)), {
|
||||
method: 'DELETE',
|
||||
headers: serverHeaders()
|
||||
})
|
||||
.then(function(res) {
|
||||
if (!res.ok) {
|
||||
return res.json().then(function(data) {
|
||||
throw new Error(data.error || 'Server returned ' + res.status);
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
showToast('Error deleting jail: ' + data.error, 'error');
|
||||
return;
|
||||
}
|
||||
showToast(data.message || 'Jail deleted successfully', 'success');
|
||||
openManageJailsModal();
|
||||
refreshData({ silent: true });
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error deleting jail:', err);
|
||||
showToast('Error deleting jail: ' + (err.message || err), 'error');
|
||||
})
|
||||
.finally(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Logpath Helpers
|
||||
// =========================================================================
|
||||
|
||||
// Supported fail2ban logpath formats: space-separated / multi-line
|
||||
function extractLogpathFromConfig(configText) {
|
||||
if (!configText) return '';
|
||||
|
||||
var logpaths = [];
|
||||
var lines = configText.split('\n');
|
||||
var inLogpathLine = false;
|
||||
var currentLogpath = '';
|
||||
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
// Skip comments
|
||||
if (line.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this line starts with logpath =
|
||||
// Check if the line starts with logpath =
|
||||
var logpathMatch = line.match(/^logpath\s*=\s*(.+)$/i);
|
||||
if (logpathMatch && logpathMatch[1]) {
|
||||
// Trim whitespace and remove quotes if present
|
||||
@@ -207,15 +297,11 @@ function extractLogpathFromConfig(configText) {
|
||||
currentLogpath = currentLogpath.replace(/^["']|["']$/g, '');
|
||||
inLogpathLine = true;
|
||||
} else if (inLogpathLine) {
|
||||
// Continuation line (indented or starting with space)
|
||||
// Fail2ban allows continuation lines for logpath
|
||||
|
||||
if (line !== '' && !line.includes('=')) {
|
||||
// This is a continuation line, append to current logpath
|
||||
currentLogpath += ' ' + line.trim();
|
||||
} else {
|
||||
// End of logpath block, process current logpath
|
||||
if (currentLogpath) {
|
||||
// Split by spaces to handle multiple logpaths in one line
|
||||
var paths = currentLogpath.split(/\s+/).filter(function(p) { return p.length > 0; });
|
||||
logpaths = logpaths.concat(paths);
|
||||
currentLogpath = '';
|
||||
@@ -223,7 +309,6 @@ function extractLogpathFromConfig(configText) {
|
||||
inLogpathLine = false;
|
||||
}
|
||||
} else if (inLogpathLine && line === '') {
|
||||
// Empty line might end the logpath block
|
||||
if (currentLogpath) {
|
||||
var paths = currentLogpath.split(/\s+/).filter(function(p) { return p.length > 0; });
|
||||
logpaths = logpaths.concat(paths);
|
||||
@@ -232,35 +317,52 @@ function extractLogpathFromConfig(configText) {
|
||||
inLogpathLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Process any remaining logpath
|
||||
|
||||
if (currentLogpath) {
|
||||
var paths = currentLogpath.split(/\s+/).filter(function(p) { return p.length > 0; });
|
||||
logpaths = logpaths.concat(paths);
|
||||
}
|
||||
|
||||
// Join multiple logpaths with newlines
|
||||
return logpaths.join('\n');
|
||||
}
|
||||
|
||||
function updateLogpathButtonVisibility() {
|
||||
var jailTextArea = document.getElementById('jailConfigTextarea');
|
||||
var jailConfig = jailTextArea ? jailTextArea.value : '';
|
||||
var hasLogpath = /logpath\s*=/i.test(jailConfig);
|
||||
var testSection = document.getElementById('testLogpathSection');
|
||||
var localServerHint = document.getElementById('localServerLogpathHint');
|
||||
|
||||
if (hasLogpath && testSection) {
|
||||
testSection.classList.remove('hidden');
|
||||
if (localServerHint && currentServer && currentServer.type === 'local') {
|
||||
localServerHint.classList.remove('hidden');
|
||||
} else if (localServerHint) {
|
||||
localServerHint.classList.add('hidden');
|
||||
}
|
||||
} else if (testSection) {
|
||||
testSection.classList.add('hidden');
|
||||
document.getElementById('logpathResults').classList.add('hidden');
|
||||
if (localServerHint) {
|
||||
localServerHint.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testLogpath() {
|
||||
if (!currentJailForConfig) return;
|
||||
|
||||
// Extract logpath from the textarea
|
||||
|
||||
var jailTextArea = document.getElementById('jailConfigTextarea');
|
||||
var jailConfig = jailTextArea ? jailTextArea.value : '';
|
||||
var logpath = extractLogpathFromConfig(jailConfig);
|
||||
|
||||
|
||||
if (!logpath) {
|
||||
showToast('No logpath found in jail configuration. Please add a logpath line (e.g., logpath = /var/log/example.log)', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
var resultsDiv = document.getElementById('logpathResults');
|
||||
resultsDiv.textContent = 'Testing logpath...';
|
||||
resultsDiv.classList.remove('hidden');
|
||||
resultsDiv.classList.remove('text-red-600', 'text-yellow-600');
|
||||
|
||||
showLoading(true);
|
||||
var url = '/api/jails/' + encodeURIComponent(currentJailForConfig) + '/logpath/test';
|
||||
fetch(withServerParam(url), {
|
||||
@@ -274,49 +376,42 @@ function testLogpath() {
|
||||
if (data.error) {
|
||||
resultsDiv.textContent = 'Error: ' + data.error;
|
||||
resultsDiv.classList.add('text-red-600');
|
||||
// Auto-scroll to results
|
||||
setTimeout(function() {
|
||||
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
var originalLogpath = data.original_logpath || '';
|
||||
var results = data.results || [];
|
||||
var isLocalServer = data.is_local_server || false;
|
||||
|
||||
// Build HTML output with visual indicators
|
||||
var output = '';
|
||||
|
||||
|
||||
if (results.length === 0) {
|
||||
output = '<div class="text-yellow-600">No logpath entries found.</div>';
|
||||
resultsDiv.innerHTML = output;
|
||||
resultsDiv.classList.add('text-yellow-600');
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each logpath result
|
||||
|
||||
results.forEach(function(result, idx) {
|
||||
var logpath = result.logpath || '';
|
||||
var resolvedPath = result.resolved_path || '';
|
||||
var found = result.found || false;
|
||||
var files = result.files || [];
|
||||
var error = result.error || '';
|
||||
|
||||
|
||||
if (idx > 0) {
|
||||
output += '<div class="my-4 border-t border-gray-300 pt-4"></div>';
|
||||
}
|
||||
|
||||
|
||||
output += '<div class="mb-3">';
|
||||
output += '<div class="font-semibold text-gray-800 mb-1">Logpath ' + (idx + 1) + ':</div>';
|
||||
output += '<div class="ml-4 text-sm text-gray-600 font-mono">' + escapeHtml(logpath) + '</div>';
|
||||
|
||||
|
||||
if (resolvedPath && resolvedPath !== logpath) {
|
||||
output += '<div class="ml-4 text-xs text-gray-500 mt-1">Resolved: <span class="font-mono">' + escapeHtml(resolvedPath) + '</span></div>';
|
||||
}
|
||||
output += '</div>';
|
||||
|
||||
// Test results
|
||||
output += '<div class="ml-4 mb-2">';
|
||||
output += '<div class="flex items-center gap-2">';
|
||||
if (isLocalServer) {
|
||||
@@ -348,11 +443,10 @@ function testLogpath() {
|
||||
}
|
||||
output += '</div>';
|
||||
});
|
||||
|
||||
// Set overall status color
|
||||
|
||||
var allFound = results.every(function(r) { return r.found; });
|
||||
var anyFound = results.some(function(r) { return r.found; });
|
||||
|
||||
|
||||
if (allFound) {
|
||||
resultsDiv.classList.remove('text-red-600', 'text-yellow-600');
|
||||
} else if (anyFound) {
|
||||
@@ -362,10 +456,9 @@ function testLogpath() {
|
||||
resultsDiv.classList.remove('text-yellow-600');
|
||||
resultsDiv.classList.add('text-red-600');
|
||||
}
|
||||
|
||||
|
||||
resultsDiv.innerHTML = output;
|
||||
|
||||
// Auto-scroll to results
|
||||
|
||||
setTimeout(function() {
|
||||
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}, 100);
|
||||
@@ -374,356 +467,33 @@ function testLogpath() {
|
||||
showLoading(false);
|
||||
resultsDiv.textContent = 'Error: ' + err;
|
||||
resultsDiv.classList.add('text-red-600');
|
||||
// Auto-scroll to results
|
||||
setTimeout(function() {
|
||||
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function openManageJailsModal() {
|
||||
if (!currentServerId) {
|
||||
showToast(t('servers.selector.none', 'Please add and select a Fail2ban server first.'), 'info');
|
||||
return;
|
||||
}
|
||||
showLoading(true);
|
||||
fetch(withServerParam('/api/jails/manage'), {
|
||||
headers: serverHeaders()
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!data.jails || !data.jails.length) {
|
||||
showToast("No jails found for this server.", 'info');
|
||||
return;
|
||||
}
|
||||
// =========================================================================
|
||||
// Extension interference workaround
|
||||
// =========================================================================
|
||||
|
||||
const html = data.jails.map(jail => {
|
||||
const isEnabled = jail.enabled ? 'checked' : '';
|
||||
const escapedJailName = escapeHtml(jail.jailName);
|
||||
// Escape single quotes for JavaScript string
|
||||
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 text-gray-900">' + escapedJailName + '</span>'
|
||||
+ ' <div class="flex items-center gap-3">'
|
||||
+ ' <button'
|
||||
+ ' type="button"'
|
||||
+ ' onclick="openJailConfigModal(\'' + jsEscapedJailName + '\')"'
|
||||
+ ' class="text-xs px-3 py-1.5 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors whitespace-nowrap"'
|
||||
+ ' data-i18n="modal.filter_config_edit"'
|
||||
+ ' title="' + escapeHtml(t('modal.filter_config_edit', 'Edit Filter / Jail')) + '"'
|
||||
+ ' >'
|
||||
+ escapeHtml(t('modal.filter_config_edit', 'Edit Filter / Jail'))
|
||||
+ ' </button>'
|
||||
+ ' <button'
|
||||
+ ' type="button"'
|
||||
+ ' onclick="deleteJail(\'' + jsEscapedJailName + '\')"'
|
||||
+ ' class="text-xs px-3 py-1.5 bg-red-500 text-white rounded hover:bg-red-600 transition-colors whitespace-nowrap"'
|
||||
+ ' title="' + escapeHtml(t('modal.delete_jail', 'Delete Jail')) + '"'
|
||||
+ ' >'
|
||||
+ ' <i class="fas fa-trash"></i>'
|
||||
+ ' </button>'
|
||||
+ ' <label class="inline-flex relative items-center cursor-pointer">'
|
||||
+ ' <input'
|
||||
+ ' type="checkbox"'
|
||||
+ ' id="toggle-' + jail.jailName.replace(/[^a-zA-Z0-9]/g, '_') + '"'
|
||||
+ ' class="sr-only peer"'
|
||||
+ isEnabled
|
||||
+ ' />'
|
||||
+ ' <div'
|
||||
+ ' 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/2 -translate-y-1/2 bg-white w-4 h-4 rounded-full transition-transform peer-checked:translate-x-5"'
|
||||
+ ' ></span>'
|
||||
+ ' </label>'
|
||||
+ ' </div>'
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
|
||||
document.getElementById('jailsList').innerHTML = html;
|
||||
|
||||
// Add auto-save on checkbox change with debouncing
|
||||
let saveTimeout;
|
||||
document.querySelectorAll('#jailsList input[type="checkbox"]').forEach(function(checkbox) {
|
||||
checkbox.addEventListener('change', function() {
|
||||
// Clear any pending save
|
||||
if (saveTimeout) {
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
|
||||
// Debounce save by 300ms
|
||||
saveTimeout = setTimeout(function() {
|
||||
saveManageJailsSingle(checkbox);
|
||||
}, 300);
|
||||
});
|
||||
function preventExtensionInterference(element) {
|
||||
if (!element) return;
|
||||
try {
|
||||
// Ensure control property exists to prevent "Cannot read properties of undefined" errors
|
||||
if (!element.control) {
|
||||
Object.defineProperty(element, 'control', {
|
||||
value: {
|
||||
type: element.type || 'textarea',
|
||||
name: element.name || 'filter-config-editor',
|
||||
form: null,
|
||||
autocomplete: 'off'
|
||||
},
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
openModal('manageJailsModal');
|
||||
})
|
||||
.catch(err => showToast("Error fetching jails: " + err, 'error'))
|
||||
.finally(() => showLoading(false));
|
||||
}
|
||||
Object.seal(element.control);
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
function saveManageJailsSingle(checkbox) {
|
||||
// Find the parent container div
|
||||
const item = checkbox.closest('div.flex.items-center.justify-between');
|
||||
if (!item) {
|
||||
console.error('Could not find parent container for checkbox');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get jail name from the span - it's the first span with text-sm font-medium class
|
||||
const nameSpan = item.querySelector('span.text-sm.font-medium');
|
||||
if (!nameSpan) {
|
||||
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.
|
||||
fetch(withServerParam('/api/jails/manage'), {
|
||||
method: 'POST',
|
||||
headers: serverHeaders({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify(updatedJails),
|
||||
})
|
||||
.then(function(res) {
|
||||
if (!res.ok) {
|
||||
return res.json().then(function(data) {
|
||||
throw new Error(data.error || 'Server returned ' + res.status);
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
// Check if there was an error (including auto-disabled jails)
|
||||
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';
|
||||
} else {
|
||||
// Revert checkbox state on error
|
||||
checkbox.checked = !isEnabled;
|
||||
}
|
||||
|
||||
showToast(errorMsg, toastType, wasAutoDisabled ? 15000 : undefined);
|
||||
|
||||
// Still reload the jail list to reflect the actual state
|
||||
return fetch(withServerParam('/api/jails/manage'), {
|
||||
headers: serverHeaders()
|
||||
}).then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.jails && data.jails.length) {
|
||||
// Update the checkbox state based on server response
|
||||
const jail = data.jails.find(function(j) { return j.jailName === jailName; });
|
||||
if (jail) {
|
||||
checkbox.checked = jail.enabled;
|
||||
}
|
||||
}
|
||||
loadServers().then(function() {
|
||||
updateRestartBanner();
|
||||
return refreshData({ silent: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Check for warning (legacy support)
|
||||
if (data.warning) {
|
||||
showToast(data.warning, 'warning');
|
||||
}
|
||||
|
||||
console.log('Jail state saved successfully:', data);
|
||||
// Show success toast
|
||||
showToast(data.message || ('Jail ' + jailName + ' ' + (isEnabled ? 'enabled' : 'disabled') + ' successfully'), 'success');
|
||||
// Reload the jail list to reflect the actual state
|
||||
return fetch(withServerParam('/api/jails/manage'), {
|
||||
headers: serverHeaders()
|
||||
}).then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.jails && data.jails.length) {
|
||||
// Update the checkbox state based on server response
|
||||
const jail = data.jails.find(function(j) { return j.jailName === jailName; });
|
||||
if (jail) {
|
||||
checkbox.checked = jail.enabled;
|
||||
}
|
||||
}
|
||||
loadServers().then(function() {
|
||||
updateRestartBanner();
|
||||
return refreshData({ silent: true });
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error saving jail settings:', err);
|
||||
showToast("Error saving jail settings: " + (err.message || err), 'error');
|
||||
// Revert checkbox state on error
|
||||
checkbox.checked = !isEnabled;
|
||||
});
|
||||
}
|
||||
|
||||
function openCreateJailModal() {
|
||||
document.getElementById('newJailName').value = '';
|
||||
document.getElementById('newJailContent').value = '';
|
||||
const filterSelect = document.getElementById('newJailFilter');
|
||||
if (filterSelect) {
|
||||
filterSelect.value = '';
|
||||
}
|
||||
|
||||
// Load filters into dropdown
|
||||
showLoading(true);
|
||||
fetch(withServerParam('/api/filters'), {
|
||||
headers: serverHeaders()
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (filterSelect) {
|
||||
filterSelect.innerHTML = '<option value="">-- Select a filter --</option>';
|
||||
if (data.filters && data.filters.length > 0) {
|
||||
data.filters.forEach(filter => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = filter;
|
||||
opt.textContent = filter;
|
||||
filterSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
}
|
||||
openModal('createJailModal');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error loading filters:', err);
|
||||
openModal('createJailModal');
|
||||
})
|
||||
.finally(() => showLoading(false));
|
||||
}
|
||||
|
||||
function updateJailConfigFromFilter() {
|
||||
const filterSelect = document.getElementById('newJailFilter');
|
||||
const jailNameInput = document.getElementById('newJailName');
|
||||
const contentTextarea = document.getElementById('newJailContent');
|
||||
|
||||
if (!filterSelect || !contentTextarea) return;
|
||||
|
||||
const selectedFilter = filterSelect.value;
|
||||
|
||||
if (!selectedFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-fill jail name if empty
|
||||
if (jailNameInput && !jailNameInput.value.trim()) {
|
||||
jailNameInput.value = selectedFilter;
|
||||
}
|
||||
|
||||
// Auto-populate jail config
|
||||
const jailName = (jailNameInput && jailNameInput.value.trim()) || selectedFilter;
|
||||
const config = `[${jailName}]
|
||||
enabled = false
|
||||
filter = ${selectedFilter}
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = 5
|
||||
bantime = 3600
|
||||
findtime = 600`;
|
||||
|
||||
contentTextarea.value = config;
|
||||
}
|
||||
|
||||
function createJail() {
|
||||
const jailName = document.getElementById('newJailName').value.trim();
|
||||
const content = document.getElementById('newJailContent').value.trim();
|
||||
|
||||
if (!jailName) {
|
||||
showToast('Jail name is required', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading(true);
|
||||
fetch(withServerParam('/api/jails'), {
|
||||
method: 'POST',
|
||||
headers: serverHeaders({ 'Content-Type': 'application/json' }),
|
||||
body: JSON.stringify({
|
||||
jailName: jailName,
|
||||
content: content
|
||||
})
|
||||
})
|
||||
.then(function(res) {
|
||||
if (!res.ok) {
|
||||
return res.json().then(function(data) {
|
||||
throw new Error(data.error || 'Server returned ' + res.status);
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
showToast('Error creating jail: ' + data.error, 'error');
|
||||
return;
|
||||
}
|
||||
closeModal('createJailModal');
|
||||
showToast(data.message || 'Jail created successfully', 'success');
|
||||
// Reload the manage jails modal
|
||||
openManageJailsModal();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error creating jail:', err);
|
||||
showToast('Error creating jail: ' + (err.message || err), 'error');
|
||||
})
|
||||
.finally(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteJail(jailName) {
|
||||
if (!confirm('Are you sure you want to delete the jail "' + escapeHtml(jailName) + '"? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading(true);
|
||||
fetch(withServerParam('/api/jails/' + encodeURIComponent(jailName)), {
|
||||
method: 'DELETE',
|
||||
headers: serverHeaders()
|
||||
})
|
||||
.then(function(res) {
|
||||
if (!res.ok) {
|
||||
return res.json().then(function(data) {
|
||||
throw new Error(data.error || 'Server returned ' + res.status);
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
showToast('Error deleting jail: ' + data.error, 'error');
|
||||
return;
|
||||
}
|
||||
showToast(data.message || 'Jail deleted successfully', 'success');
|
||||
// Reload the manage jails modal
|
||||
openManageJailsModal();
|
||||
// Refresh dashboard
|
||||
refreshData({ silent: true });
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error deleting jail:', err);
|
||||
showToast('Error deleting jail: ' + (err.message || err), 'error');
|
||||
})
|
||||
.finally(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user