// Core utility functions for Fail2ban UI
// Toggle the loading overlay (with !important)
function showLoading(show) {
var overlay = document.getElementById('loading-overlay');
if (overlay) {
if (show) {
overlay.style.setProperty('display', 'flex', 'important');
setTimeout(() => overlay.classList.add('show'), 10);
} else {
overlay.classList.remove('show'); // Start fade-out
setTimeout(() => overlay.style.setProperty('display', 'none', 'important'), 400);
}
}
}
// Show toast notification
function showToast(message, type) {
var container = document.getElementById('toast-container');
if (!container || !message) return;
// Handle ban event objects
if (typeof message === 'object' && message.type === 'ban_event') {
showBanEventToast(message.data || message);
return;
}
var toast = document.createElement('div');
var variant = type || 'info';
toast.className = 'toast toast-' + variant;
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(function() {
toast.classList.add('show');
});
setTimeout(function() {
toast.classList.remove('show');
setTimeout(function() {
toast.remove();
}, 300);
}, 5000);
}
// Show toast for ban event
function showBanEventToast(event) {
var container = document.getElementById('toast-container');
if (!container || !event) return;
var toast = document.createElement('div');
toast.className = 'toast toast-ban-event';
var ip = event.ip || 'Unknown IP';
var jail = event.jail || 'Unknown Jail';
var server = event.serverName || event.serverId || 'Unknown Server';
var country = event.country || 'UNKNOWN';
toast.innerHTML = ''
+ '
'
+ '
'
+ ' '
+ '
'
+ '
'
+ '
New block occurred
'
+ '
'
+ ' ' + escapeHtml(ip) + ''
+ ' banned in '
+ ' ' + escapeHtml(jail) + ''
+ '
'
+ '
'
+ ' ' + escapeHtml(server) + ' • ' + escapeHtml(country)
+ '
'
+ '
'
+ '
';
// Add click handler to scroll to ban events table
toast.addEventListener('click', function() {
var logSection = document.getElementById('logOverviewSection');
if (logSection) {
logSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
toast.style.cursor = 'pointer';
container.appendChild(toast);
requestAnimationFrame(function() {
toast.classList.add('show');
});
setTimeout(function() {
toast.classList.remove('show');
setTimeout(function() {
toast.remove();
}, 300);
}, 5000);
}
// Escape HTML to prevent XSS
function escapeHtml(value) {
if (value === undefined || value === null) return '';
return String(value).replace(/[&<>"']/g, function(match) {
switch (match) {
case '&': return '&';
case '<': return '<';
case '>': return '>';
case '"': return '"';
default: return ''';
}
});
}
// Format number with locale
function formatNumber(value) {
var num = Number(value);
if (!isFinite(num)) {
return '0';
}
try {
return num.toLocaleString();
} catch (e) {
return String(num);
}
}
// Format date/time (custom format for dashboard)
function formatDateTime(value) {
if (!value) return '';
var date = new Date(value);
if (isNaN(date.getTime())) {
return value;
}
// Format as "2025.11.12, 21:21:52"
var year = date.getFullYear();
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
var hours = String(date.getHours()).padStart(2, '0');
var minutes = String(date.getMinutes()).padStart(2, '0');
var seconds = String(date.getSeconds()).padStart(2, '0');
return year + '.' + month + '.' + day + ', ' + hours + ':' + minutes + ':' + seconds;
}
// Fetch and display own external IP for webUI
function displayExternalIP() {
const target = document.getElementById('external-ip');
if (!target) return;
const providers = [
{ url: 'https://api.ipify.org?format=json', extract: data => data.ip },
{ url: 'https://ipapi.co/json/', extract: data => data && (data.ip || data.ip_address) },
{ url: 'https://ipv4.jsonip.com/', extract: data => data.ip }
];
const tryProvider = (index) => {
if (index >= providers.length) {
target.textContent = 'Unavailable';
return;
}
const provider = providers[index];
fetch(provider.url, { headers: { 'Accept': 'application/json' } })
.then(res => {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(data => {
const ip = provider.extract(data);
if (ip) {
target.textContent = ip;
} else {
throw new Error('Missing IP');
}
})
.catch(() => {
tryProvider(index + 1);
});
};
tryProvider(0);
}
// Function to initialize tooltips
function initializeTooltips() {
const tooltips = document.querySelectorAll('[data-tooltip]');
tooltips.forEach(el => {
el.addEventListener('mouseenter', () => {
const tooltip = document.createElement('div');
tooltip.className = 'absolute z-10 bg-gray-800 text-white text-xs rounded py-1 px-2 whitespace-nowrap';
tooltip.textContent = el.getAttribute('data-tooltip');
tooltip.style.top = (el.offsetTop - 30) + 'px';
tooltip.style.left = (el.offsetLeft + (el.offsetWidth / 2) - (tooltip.offsetWidth / 2)) + 'px';
tooltip.id = 'tooltip-' + Date.now();
document.body.appendChild(tooltip);
el.setAttribute('data-tooltip-id', tooltip.id);
});
el.addEventListener('mouseleave', () => {
const tooltipId = el.getAttribute('data-tooltip-id');
if (tooltipId) {
const tooltip = document.getElementById(tooltipId);
if (tooltip) tooltip.remove();
el.removeAttribute('data-tooltip-id');
}
});
});
}
// Function to initialize the IP search
function initializeSearch() {
const ipSearch = document.getElementById("ipSearch");
if (ipSearch) {
ipSearch.addEventListener("keypress", function(event) {
const char = String.fromCharCode(event.which);
if (!/[0-9.]/.test(char)) {
event.preventDefault();
}
});
}
}
// Update restart banner visibility
function updateRestartBanner() {
var banner = document.getElementById('restartBanner');
if (!banner) return;
if (currentServer && currentServer.restartNeeded) {
banner.style.display = 'block';
} else {
banner.style.display = 'none';
}
}
// Load dynamically the other pages when navigating in nav
function showSection(sectionId) {
// hide all sections
document.getElementById('dashboardSection').classList.add('hidden');
document.getElementById('filterSection').classList.add('hidden');
document.getElementById('settingsSection').classList.add('hidden');
// show the requested section
document.getElementById(sectionId).classList.remove('hidden');
// If it's filterSection, load filters
if (sectionId === 'filterSection') {
if (typeof showFilterSection === 'function') {
showFilterSection();
}
}
// If it's settingsSection, load settings
if (sectionId === 'settingsSection') {
if (typeof loadSettings === 'function') {
loadSettings();
}
}
// Close navbar on mobile when clicking a menu item
document.getElementById('mobileMenu').classList.add('hidden');
}
// Toggle mobile menu
function toggleMobileMenu() {
const menu = document.getElementById('mobileMenu');
menu.classList.toggle('hidden');
}