';
return content;
}
function filterIPs() {
const input = document.getElementById("ipSearch");
if (!input) {
return;
}
const query = input.value.trim();
const rows = document.querySelectorAll("#jailsTable .jail-row");
rows.forEach(row => {
const hiddenSections = row.querySelectorAll(".banned-ip-hidden");
const toggleButtons = row.querySelectorAll(".banned-ip-toggle");
if (query === "") {
hiddenSections.forEach(section => {
if (section.getAttribute("data-initially-hidden") === "true") {
section.classList.add("hidden");
}
});
toggleButtons.forEach(button => {
const moreLabel = button.getAttribute("data-more-label");
if (moreLabel) {
button.textContent = moreLabel;
}
button.setAttribute("data-expanded", "false");
});
} else {
hiddenSections.forEach(section => section.classList.remove("hidden"));
toggleButtons.forEach(button => {
const lessLabel = button.getAttribute("data-less-label");
if (lessLabel) {
button.textContent = lessLabel;
}
button.setAttribute("data-expanded", "true");
});
}
const ipItems = row.querySelectorAll(".banned-ip-item");
let rowHasMatch = false;
ipItems.forEach(item => {
const span = item.querySelector("span.text-sm");
if (!span) return;
const storedValue = span.getAttribute("data-ip-value");
const originalIP = storedValue ? decodeURIComponent(storedValue) : span.textContent.trim();
if (query === "") {
item.style.display = "";
span.textContent = originalIP;
rowHasMatch = true;
} else if (originalIP.indexOf(query) !== -1) {
item.style.display = "";
span.innerHTML = highlightQueryMatch(originalIP, query);
rowHasMatch = true;
} else {
item.style.display = "none";
}
});
row.style.display = rowHasMatch ? "" : "none";
});
}
function toggleBannedList(hiddenId, buttonId) {
var hidden = document.getElementById(hiddenId);
var button = document.getElementById(buttonId);
if (!hidden || !button) {
return;
}
var isHidden = hidden.classList.contains("hidden");
if (isHidden) {
hidden.classList.remove("hidden");
button.textContent = button.getAttribute("data-less-label") || button.textContent;
button.setAttribute("data-expanded", "true");
} else {
hidden.classList.add("hidden");
button.textContent = button.getAttribute("data-more-label") || button.textContent;
button.setAttribute("data-expanded", "false");
}
}
function toggleManualBlockSection() {
var container = document.getElementById('manualBlockFormContainer');
var icon = document.getElementById('manualBlockToggleIcon');
if (!container || !icon) {
return;
}
var isHidden = container.classList.contains("hidden");
if (isHidden) {
container.classList.remove("hidden");
icon.classList.remove("fa-chevron-down");
icon.classList.add("fa-chevron-up");
} else {
container.classList.add("hidden");
icon.classList.remove("fa-chevron-up");
icon.classList.add("fa-chevron-down");
}
}
function unbanIP(jail, ip) {
const confirmMsg = isLOTRModeActive
? 'Restore ' + ip + ' to the realm from ' + jail + '?'
: 'Unban IP ' + ip + ' from jail ' + jail + '?';
if (!confirm(confirmMsg)) {
return;
}
showLoading(true);
var url = '/api/jails/' + encodeURIComponent(jail) + '/unban/' + encodeURIComponent(ip);
fetch(withServerParam(url), {
method: 'POST',
headers: serverHeaders()
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
showToast("Error unbanning IP: " + data.error, 'error');
}
// Don't show success toast here - the WebSocket unban event will show a proper toast
return refreshData({ silent: true });
})
.catch(function(err) {
showToast("Error: " + err, 'error');
})
.finally(function() {
showLoading(false);
});
}
function banIP(jail, ip) {
const confirmMsg = isLOTRModeActive
? 'Banish ' + ip + ' from the realm in ' + jail + '?'
: 'Block IP ' + ip + ' in jail ' + jail + '?';
if (!confirm(confirmMsg)) {
return;
}
showLoading(true);
var url = '/api/jails/' + encodeURIComponent(jail) + '/ban/' + encodeURIComponent(ip);
fetch(withServerParam(url), {
method: 'POST',
headers: serverHeaders()
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
showToast("Error blocking IP: " + data.error, 'error');
} else {
showToast(t('dashboard.manual_block.success', 'IP blocked successfully'), 'success');
return refreshData({ silent: true });
}
})
.catch(function(err) {
showToast("Error: " + err, 'error');
})
.finally(function() {
showLoading(false);
});
}
function handleManualBlock() {
var jailSelect = document.getElementById('blockJailSelect');
var ipInput = document.getElementById('blockIPInput');
if (!jailSelect || !ipInput) {
return;
}
var jail = jailSelect.value;
var ip = ipInput.value.trim();
if (!jail) {
showToast(t('dashboard.manual_block.jail_required', 'Please select a jail'), 'error');
jailSelect.focus();
return;
}
if (!ip) {
showToast(t('dashboard.manual_block.ip_required', 'Please enter an IP address'), 'error');
ipInput.focus();
return;
}
// Basic IP validation
var ipv4Pattern = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
var ipv6Pattern = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
if (!ipv4Pattern.test(ip) && !ipv6Pattern.test(ip)) {
showToast(t('dashboard.manual_block.invalid_ip', 'Please enter a valid IP address'), 'error');
ipInput.focus();
return;
}
banIP(jail, ip);
// Clear form after submission
ipInput.value = '';
jailSelect.value = '';
}
function renderDashboard() {
var container = document.getElementById('dashboard');
if (!container) return;
var focusState = captureFocusState(container);
var enabledServers = serversCache.filter(function(s) { return s.enabled; });
if (!serversCache.length) {
container.innerHTML = ''
+ '
'
+ '
No Fail2ban servers configured
'
+ '
Add a server to start monitoring and controlling Fail2ban instances.
'
+ '
';
if (typeof updateTranslations === 'function') updateTranslations();
restoreFocusState(focusState);
return;
}
if (!enabledServers.length) {
container.innerHTML = ''
+ '
'
+ '
No active connectors
'
+ '
Enable the local connector or register a remote Fail2ban server to see live data.
'
+ '
';
if (typeof updateTranslations === 'function') updateTranslations();
restoreFocusState(focusState);
return;
}
var summary = latestSummary;
var html = '';
if (latestSummaryError) {
html += ''
+ '
'
+ escapeHtml(latestSummaryError)
+ '
';
}
if (!summary) {
html += ''
+ '
'
+ '
Loading summary data…
'
+ '
';
} else {
var totalBanned = summary.jails ? summary.jails.reduce(function(sum, j) { return sum + (j.totalBanned || 0); }, 0) : 0;
var newLastHour = summary.jails ? summary.jails.reduce(function(sum, j) { return sum + (j.newInLastHour || 0); }, 0) : 0;
var recurringWeekCount = recurringIPsLastWeekCount();
html += ''
+ '
';
} else {
var countries = getBanEventCountries();
var filteredEvents = getFilteredBanEvents();
var recurringMap = getRecurringIPMap();
var searchQuery = (banEventsFilterText || '').trim();
html += ''
+ '