// 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 } } 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() }) .then(function(res) { return res.json(); }) .then(function(data) { if (data.error) { showToast("Error loading config: " + 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(); // 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); }) .catch(function(err) { showToast("Error: " + 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'); if (hasLogpath && testSection) { testSection.classList.remove('hidden'); } else if (testSection) { testSection.classList.add('hidden'); document.getElementById('logpathResults').classList.add('hidden'); } } function saveJailConfig() { if (!currentJailForConfig) return; showLoading(true); var filterConfig = document.getElementById('filterConfigTextarea').value; var jailConfig = document.getElementById('jailConfigTextarea').value; var url = '/api/jails/' + encodeURIComponent(currentJailForConfig) + '/config'; fetch(withServerParam(url), { method: 'POST', headers: serverHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ filter: filterConfig, jail: jailConfig }), }) .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 saving config: " + data.error, 'error'); return; } closeModal('jailConfigModal'); showToast(t('filter_debug.save_success', 'Filter and jail config saved and reloaded'), 'success'); return refreshData({ silent: true }); }) .catch(function(err) { console.error("Error saving config:", err); showToast("Error saving config: " + err.message, 'error'); }) .finally(function() { showLoading(false); }); } // Extract logpath from jail config text function extractLogpathFromConfig(configText) { if (!configText) return ''; // Match logpath = value (handles various formats) var logpathMatch = configText.match(/^logpath\s*=\s*(.+)$/im); if (logpathMatch && logpathMatch[1]) { // Trim whitespace and remove quotes if present var logpath = logpathMatch[1].trim(); // Remove surrounding quotes logpath = logpath.replace(/^["']|["']$/g, ''); return logpath; } return ''; } 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), { method: 'POST', headers: serverHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify({ logpath: logpath }) }) .then(function(res) { return res.json(); }) .then(function(data) { showLoading(false); 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 resolvedLogpath = data.resolved_logpath || ''; var files = data.files || []; // Build output message with better formatting var output = ''; // Show original logpath if (originalLogpath) { output += 'Logpath:\n ' + originalLogpath + '\n\n'; } // Show resolved logpath if different from original if (resolvedLogpath && resolvedLogpath !== originalLogpath) { output += 'Resolved logpath:\n ' + resolvedLogpath + '\n\n'; } else if (resolvedLogpath) { //output += 'Logpath:\n ' + resolvedLogpath + '\n\n'; } // Show files found with better formatting if (files.length === 0) { output += 'No files found matching the logpath pattern.'; resultsDiv.classList.remove('text-red-600'); resultsDiv.classList.add('text-yellow-600'); } else { output += 'Found ' + files.length + ' file(s):\n\n'; files.forEach(function(file, index) { output += ' ' + (index + 1) + '. ' + file + '\n'; }); resultsDiv.classList.remove('text-red-600', 'text-yellow-600'); } // Use textContent for plain text, but we could also use innerHTML for better formatting resultsDiv.textContent = output; // Auto-scroll to results setTimeout(function() { resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 100); }) .catch(function(err) { 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; } 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 '' + '