// Modal management for Fail2ban UI "use strict"; // ========================================================================= // Modal Lifecycle // ========================================================================= function updateBodyScrollLock() { if (openModalCount > 0) { document.body.classList.add('modal-open'); } else { document.body.classList.remove('modal-open'); } } function closeModal(modalId) { var modal = document.getElementById(modalId); if (!modal || modal.classList.contains('hidden')) { return; } modal.classList.add('hidden'); openModalCount = Math.max(0, openModalCount - 1); updateBodyScrollLock(); } function openModal(modalId) { var modal = document.getElementById(modalId); if (!modal || !modal.classList.contains('hidden')) { updateBodyScrollLock(); return; } modal.classList.remove('hidden'); openModalCount += 1; updateBodyScrollLock(); } // ========================================================================= // Whois and Logs Modal // ========================================================================= // Whois modal function openWhoisModal(eventIndex) { if (!latestBanEvents || !latestBanEvents[eventIndex]) { showToast("Event not found", 'error'); return; } var event = latestBanEvents[eventIndex]; if (!event.whois || !event.whois.trim()) { showToast("No whois data available for this event", 'info'); return; } document.getElementById('whoisModalIP').textContent = event.ip || 'N/A'; var contentEl = document.getElementById('whoisModalContent'); contentEl.textContent = event.whois; openModal('whoisModal'); } // Logs modal function openLogsModal(eventIndex) { if (!latestBanEvents || !latestBanEvents[eventIndex]) { showToast("Event not found", 'error'); return; } var event = latestBanEvents[eventIndex]; if (!event.logs || !event.logs.trim()) { showToast("No logs data available for this event", 'info'); return; } document.getElementById('logsModalIP').textContent = event.ip || 'N/A'; document.getElementById('logsModalJail').textContent = event.jail || 'N/A'; var logs = event.logs; var ip = event.ip || ''; var logLines = logs.split('\n'); var suspiciousIndices = []; for (var i = 0; i < logLines.length; i++) { if (isSuspiciousLogLine(logLines[i], ip)) { suspiciousIndices.push(i); } } var contentEl = document.getElementById('logsModalContent'); if (suspiciousIndices.length) { var highlightMap = {}; suspiciousIndices.forEach(function(idx) { highlightMap[idx] = true; }); var html = ''; for (var j = 0; j < logLines.length; j++) { var safeLine = escapeHtml(logLines[j] || ''); if (highlightMap[j]) { html += '' + safeLine + ''; } else { html += safeLine + '\n'; } } contentEl.innerHTML = html; } else { contentEl.textContent = logs; } openModal('logsModal'); } // ========================================================================= // Ban Insights Modal // ========================================================================= function openBanInsightsModal() { var countriesContainer = document.getElementById('countryStatsContainer'); var recurringContainer = document.getElementById('recurringIPsContainer'); var summaryContainer = document.getElementById('insightsSummary'); var totals = (latestBanInsights && latestBanInsights.totals) || { overall: 0, today: 0, week: 0 }; if (summaryContainer) { var summaryCards = [ { label: t('logs.overview.total_events', 'Total stored events'), value: formatNumber(totals.overall || 0), sub: t('logs.modal.total_overall_note', 'Lifetime bans recorded') }, { label: t('logs.overview.total_today', 'Today'), value: formatNumber(totals.today || 0), sub: t('logs.modal.total_today_note', 'Last 24 hours') }, { label: t('logs.overview.total_week', 'Last 7 days'), value: formatNumber(totals.week || 0), sub: t('logs.modal.total_week_note', 'Weekly activity') } ]; summaryContainer.innerHTML = summaryCards.map(function(card) { return '' + '
' + '

' + escapeHtml(card.label) + '

' + '

' + escapeHtml(card.value) + '

' + '

' + escapeHtml(card.sub) + '

' + '
'; }).join(''); } var countries = (latestBanInsights && latestBanInsights.countries) || []; if (!countries.length) { countriesContainer.innerHTML = '

No bans recorded for this period.

'; } else { var totalCountries = countries.reduce(function(sum, stat) { return sum + (stat.count || 0); }, 0) || 1; var countryHTML = countries.map(function(stat) { var label = stat.country || t('logs.overview.country_unknown', 'Unknown'); var percent = Math.round(((stat.count || 0) / totalCountries) * 100); percent = Math.min(Math.max(percent, 3), 100); return '' + '
' + '
' + ' ' + escapeHtml(label) + '' + ' ' + formatNumber(stat.count || 0) + '' + '
' + '
' + '
' + '
' + '
'; }).join(''); countriesContainer.innerHTML = countryHTML; } var recurring = (latestBanInsights && latestBanInsights.recurring) || []; if (!recurring.length) { recurringContainer.innerHTML = '

No recurring IPs detected.

'; } else { var recurringHTML = recurring.map(function(stat) { var countryLabel = stat.country || t('logs.overview.country_unknown', 'Unknown'); var lastSeenLabel = stat.lastSeen ? formatDateTime(stat.lastSeen) : '—'; return '' + '
' + '
' + '
' + '

' + escapeHtml(stat.ip || '—') + '

' + '

' + escapeHtml(countryLabel) + '

' + '
' + ' ' + formatNumber(stat.count || 0) + '×' + '
' + '
' + ' ' + t('logs.overview.last_seen', 'Last seen') + '' + ' ' + escapeHtml(lastSeenLabel) + '' + '
' + '
'; }).join(''); recurringContainer.innerHTML = recurringHTML; } if (typeof updateTranslations === 'function') { updateTranslations(); } openModal('banInsightsModal'); } // ========================================================================= // Server Manager Modal // ========================================================================= function openServerManager(serverId) { showLoading(true); loadServers() .then(function() { if (serverId) { editServer(serverId); } else { resetServerForm(); } renderServerManagerList(); openModal('serverManagerModal'); }) .finally(function() { showLoading(false); }); } // ========================================================================= // Manage Jails Modal // ========================================================================= 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); const jsEscapedJailName = jail.jailName.replace(/'/g, "\\'"); return '' + '
' + ' ' + escapedJailName + '' + '
' + ' ' + escapeHtml(t('modal.filter_config_edit', 'Edit Filter / Jail')) + ' ' + ' ' + ' ' + ' ' + '
' + ' ' + ' ' + '
' + ''; }).join(''); document.getElementById('jailsList').innerHTML = html; let saveTimeout; document.querySelectorAll('#jailsList input[type="checkbox"]').forEach(function(checkbox) { checkbox.addEventListener('change', function() { if (saveTimeout) { clearTimeout(saveTimeout); } saveTimeout = setTimeout(function() { saveManageJailsSingle(checkbox); }, 300); }); }); openModal('manageJailsModal'); }) .catch(err => showToast("Error fetching jails: " + err, 'error')) .finally(() => showLoading(false)); } // ========================================================================= // Create Jail Modal // ========================================================================= function openCreateJailModal() { document.getElementById('newJailName').value = ''; document.getElementById('newJailContent').value = ''; const filterSelect = document.getElementById('newJailFilter'); if (filterSelect) { filterSelect.value = ''; } showLoading(true); fetch(withServerParam('/api/filters'), { headers: serverHeaders() }) .then(res => res.json()) .then(data => { if (filterSelect) { filterSelect.innerHTML = ''; 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)); } // ========================================================================= // Create Filter Modal // ========================================================================= function openCreateFilterModal() { document.getElementById('newFilterName').value = ''; document.getElementById('newFilterContent').value = ''; openModal('createFilterModal'); } // ========================================================================= // Jail / Filter Config Editor Modal // ========================================================================= 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; 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 || ''; 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'; } // Update logpath button visibility 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'); } jailTextArea.addEventListener('input', updateLogpathButtonVisibility); preventExtensionInterference(filterTextArea); preventExtensionInterference(jailTextArea); openModal('jailConfigModal'); setTimeout(function() { preventExtensionInterference(filterTextArea); preventExtensionInterference(jailTextArea); }, 200); }) .catch(function(err) { showToast("Error: " + err, 'error'); }) .finally(function() { showLoading(false); }); }