Files
fail2ban-ui/pkg/web/static/js/modals.js

190 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 += '<span style="display: block; background-color: #d97706; color: #fef3c7; padding: 0.25rem 0.5rem; margin: 0.125rem 0; border-radius: 0.25rem;">' + safeLine + '</span>';
} 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 ''
+ '<div class="border border-gray-200 rounded-lg p-4 bg-gray-50">'
+ ' <p class="text-xs uppercase tracking-wide text-gray-500">' + escapeHtml(card.label) + '</p>'
+ ' <p class="text-3xl font-semibold text-gray-900 mt-1">' + escapeHtml(card.value) + '</p>'
+ ' <p class="text-xs text-gray-500 mt-1">' + escapeHtml(card.sub) + '</p>'
+ '</div>';
}).join('');
}
var countries = (latestBanInsights && latestBanInsights.countries) || [];
if (!countries.length) {
countriesContainer.innerHTML = '<p class="text-sm text-gray-500" data-i18n="logs.modal.insights_countries_empty">No bans recorded for this period.</p>';
} 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 ''
+ '<div class="space-y-2">'
+ ' <div class="flex items-center justify-between text-sm font-medium text-gray-800">'
+ ' <span>' + escapeHtml(label) + '</span>'
+ ' <span>' + formatNumber(stat.count || 0) + '</span>'
+ ' </div>'
+ ' <div class="w-full bg-gray-200 rounded-full h-2">'
+ ' <div class="h-2 rounded-full bg-gradient-to-r from-blue-500 to-indigo-600" style="width:' + percent + '%;"></div>'
+ ' </div>'
+ '</div>';
}).join('');
countriesContainer.innerHTML = countryHTML;
}
var recurring = (latestBanInsights && latestBanInsights.recurring) || [];
if (!recurring.length) {
recurringContainer.innerHTML = '<p class="text-sm text-gray-500" data-i18n="logs.modal.insights_recurring_empty">No recurring IPs detected.</p>';
} 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 ''
+ '<div class="rounded-lg bg-white border border-gray-200 shadow-sm p-4">'
+ ' <div class="flex items-center justify-between">'
+ ' <div>'
+ ' <p class="font-mono text-base text-gray-900">' + escapeHtml(stat.ip || '—') + '</p>'
+ ' <p class="text-xs text-gray-500 mt-1">' + escapeHtml(countryLabel) + '</p>'
+ ' </div>'
+ ' <span class="inline-flex items-center rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">' + formatNumber(stat.count || 0) + '×</span>'
+ ' </div>'
+ ' <div class="mt-3 flex justify-between text-xs text-gray-500">'
+ ' <span>' + t('logs.overview.last_seen', 'Last seen') + '</span>'
+ ' <span>' + escapeHtml(lastSeenLabel) + '</span>'
+ ' </div>'
+ '</div>';
}).join('');
recurringContainer.innerHTML = recurringHTML;
}
if (typeof updateTranslations === 'function') {
updateTranslations();
}
openModal('banInsightsModal');
}