mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Add serverID to all events to sort per fail2ban instance, update language
This commit is contained in:
@@ -22,17 +22,22 @@
|
||||
"dashboard.cards.total_banned": "Gesamt gesperrte IPs",
|
||||
"dashboard.cards.new_last_hour": "Neue in der letzten Stunde",
|
||||
"dashboard.cards.total_logged": "Gespeicherte Sperr-Ereignisse",
|
||||
"dashboard.cards.recurring_week": "Wiederkehrende IPs (7 Tage)",
|
||||
"dashboard.cards.recurring_hint": "Behalte wiederholte Angreifer der letzten 7 Tage im Auge.",
|
||||
"dashboard.table.jail_name": "Jail-Name",
|
||||
"dashboard.table.total_banned": "Insgesamt gesperrt",
|
||||
"dashboard.table.new_last_hour": "Neu in letzter Stunde",
|
||||
"dashboard.table.banned_ips": "Gesperrte IPs (Entsperren)",
|
||||
"dashboard.no_jails": "Keine Jails gefunden.",
|
||||
"dashboard.overview_detail": "Listen ein- oder ausklappen, um betroffene Dienste schneller zu sehen.",
|
||||
"dashboard.table.time": "Zeit",
|
||||
"dashboard.table.jail": "Jail",
|
||||
"dashboard.table.ip": "IP",
|
||||
"dashboard.table.log_line": "Logzeile",
|
||||
"dashboard.no_banned_ips": "Keine gesperrten IPs",
|
||||
"dashboard.unban": "Entsperren",
|
||||
"dashboard.banned.show_more": "Mehr anzeigen",
|
||||
"dashboard.banned.show_less": "Weniger anzeigen",
|
||||
"logs.overview.title": "Interne Log-Übersicht",
|
||||
"logs.overview.subtitle": "Von Fail2ban-UI gespeicherte Ereignisse über alle Connectoren.",
|
||||
"logs.overview.refresh": "Daten aktualisieren",
|
||||
|
||||
@@ -22,17 +22,22 @@
|
||||
"dashboard.cards.total_banned": "Total g'sperrti IPs",
|
||||
"dashboard.cards.new_last_hour": "Neu i dr letschte Stund",
|
||||
"dashboard.cards.total_logged": "Gspeichereti Sperr-Ereigniss",
|
||||
"dashboard.cards.recurring_week": "Widerkehrendi IPs (7 Täg)",
|
||||
"dashboard.cards.recurring_hint": "Beobachte wiederholti Angreifer us de letschte 7 Täg.",
|
||||
"dashboard.table.jail_name": "Jail-Name",
|
||||
"dashboard.table.total_banned": "Insgsamt g'sperrt",
|
||||
"dashboard.table.new_last_hour": "Neu in dr letschte Stund",
|
||||
"dashboard.table.banned_ips": "G'sperrti IPs (Entsperre)",
|
||||
"dashboard.no_jails": "Kei Jails gfunde.",
|
||||
"dashboard.overview_detail": "Listä zämme- oder usklappe, zum schnäll betroffene Dinst erkenne.",
|
||||
"dashboard.table.time": "Zyt",
|
||||
"dashboard.table.jail": "Jail",
|
||||
"dashboard.table.ip": "IP",
|
||||
"dashboard.table.log_line": "Log-Zile",
|
||||
"dashboard.no_banned_ips": "Kei g'sperrti IPs",
|
||||
"dashboard.unban": "Entsperre",
|
||||
"dashboard.banned.show_more": "Meh azeige",
|
||||
"dashboard.banned.show_less": "Weniger azeige",
|
||||
"logs.overview.title": "Interni Log-Übersicht",
|
||||
"logs.overview.subtitle": "Vo Fail2ban-UI gspeichereti Ereigniss über alli Connectorä.",
|
||||
"logs.overview.refresh": "Date aktualisiere",
|
||||
|
||||
@@ -22,17 +22,22 @@
|
||||
"dashboard.cards.total_banned": "Total Banned IPs",
|
||||
"dashboard.cards.new_last_hour": "New Last Hour",
|
||||
"dashboard.cards.total_logged": "Stored Ban Events",
|
||||
"dashboard.cards.recurring_week": "Recurring IPs (7 days)",
|
||||
"dashboard.cards.recurring_hint": "Watch repeated offenders detected in the last seven days.",
|
||||
"dashboard.table.jail_name": "Jail Name",
|
||||
"dashboard.table.total_banned": "Total Banned",
|
||||
"dashboard.table.new_last_hour": "New Last Hour",
|
||||
"dashboard.table.banned_ips": "Banned IPs (Unban)",
|
||||
"dashboard.no_jails": "No jails found.",
|
||||
"dashboard.overview_detail": "Collapse or expand long lists to quickly focus on impacted services.",
|
||||
"dashboard.table.time": "Time",
|
||||
"dashboard.table.jail": "Jail",
|
||||
"dashboard.table.ip": "IP",
|
||||
"dashboard.table.log_line": "Log Line",
|
||||
"dashboard.no_banned_ips": "No banned IPs",
|
||||
"dashboard.unban": "Unban",
|
||||
"dashboard.banned.show_more": "Show more",
|
||||
"dashboard.banned.show_less": "Hide extra",
|
||||
"logs.overview.title": "Internal Log Overview",
|
||||
"logs.overview.subtitle": "Events stored by Fail2ban-UI across all connectors.",
|
||||
"logs.overview.refresh": "Refresh data",
|
||||
|
||||
@@ -22,17 +22,22 @@
|
||||
"dashboard.cards.total_banned": "IPs bloqueadas totales",
|
||||
"dashboard.cards.new_last_hour": "Nuevas en la última hora",
|
||||
"dashboard.cards.total_logged": "Eventos de bloqueo almacenados",
|
||||
"dashboard.cards.recurring_week": "IPs recurrentes (7 días)",
|
||||
"dashboard.cards.recurring_hint": "Vigila a los infractores repetidos de los últimos 7 días.",
|
||||
"dashboard.table.jail_name": "Nombre del Jail",
|
||||
"dashboard.table.total_banned": "Total bloqueadas",
|
||||
"dashboard.table.new_last_hour": "Nuevas en la última hora",
|
||||
"dashboard.table.banned_ips": "IPs bloqueadas (Desbloquear)",
|
||||
"dashboard.no_jails": "No se encontraron jails.",
|
||||
"dashboard.overview_detail": "Colapsa o expande las listas largas para centrarte en los servicios afectados.",
|
||||
"dashboard.table.time": "Hora",
|
||||
"dashboard.table.jail": "Jail",
|
||||
"dashboard.table.ip": "IP",
|
||||
"dashboard.table.log_line": "Línea de log",
|
||||
"dashboard.no_banned_ips": "No hay IP bloqueadas",
|
||||
"dashboard.unban": "Desbloquear",
|
||||
"dashboard.banned.show_more": "Mostrar más",
|
||||
"dashboard.banned.show_less": "Mostrar menos",
|
||||
"logs.overview.title": "Resumen interno de registros",
|
||||
"logs.overview.subtitle": "Eventos almacenados por Fail2ban-UI a través de todos los conectores.",
|
||||
"logs.overview.refresh": "Actualizar datos",
|
||||
|
||||
@@ -22,17 +22,22 @@
|
||||
"dashboard.cards.total_banned": "Total d'IPs bloquées",
|
||||
"dashboard.cards.new_last_hour": "Nouvelles dans la dernière heure",
|
||||
"dashboard.cards.total_logged": "Événements de blocage enregistrés",
|
||||
"dashboard.cards.recurring_week": "IPs récurrentes (7 jours)",
|
||||
"dashboard.cards.recurring_hint": "Surveillez les récidivistes observés durant les 7 derniers jours.",
|
||||
"dashboard.table.jail_name": "Nom du Jail",
|
||||
"dashboard.table.total_banned": "Total bloqués",
|
||||
"dashboard.table.new_last_hour": "Nouveaux dans la dernière heure",
|
||||
"dashboard.table.banned_ips": "IPs bloquées (Débloquer)",
|
||||
"dashboard.no_jails": "Aucun jail trouvé.",
|
||||
"dashboard.overview_detail": "Réduisez ou développez les longues listes pour vous concentrer sur les services impactés.",
|
||||
"dashboard.table.time": "Heure",
|
||||
"dashboard.table.jail": "Jail",
|
||||
"dashboard.table.ip": "IP",
|
||||
"dashboard.table.log_line": "Ligne de log",
|
||||
"dashboard.no_banned_ips": "Aucune IP bloquée",
|
||||
"dashboard.unban": "Débloquer",
|
||||
"dashboard.banned.show_more": "Afficher plus",
|
||||
"dashboard.banned.show_less": "Afficher moins",
|
||||
"logs.overview.title": "Vue d'ensemble interne des journaux",
|
||||
"logs.overview.subtitle": "Événements enregistrés par Fail2ban-UI sur l'ensemble des connecteurs.",
|
||||
"logs.overview.refresh": "Actualiser les données",
|
||||
|
||||
@@ -22,17 +22,22 @@
|
||||
"dashboard.cards.total_banned": "IP bloccate totali",
|
||||
"dashboard.cards.new_last_hour": "Nuove nell'ultima ora",
|
||||
"dashboard.cards.total_logged": "Eventi di blocco memorizzati",
|
||||
"dashboard.cards.recurring_week": "IP ricorrenti (7 giorni)",
|
||||
"dashboard.cards.recurring_hint": "Tieni d'occhio gli indirizzi ricorrenti degli ultimi 7 giorni.",
|
||||
"dashboard.table.jail_name": "Nome del Jail",
|
||||
"dashboard.table.total_banned": "Totale bloccate",
|
||||
"dashboard.table.new_last_hour": "Nuove nell'ultima ora",
|
||||
"dashboard.table.banned_ips": "IP bloccate (Sblocca)",
|
||||
"dashboard.no_jails": "Nessun jail trovato.",
|
||||
"dashboard.overview_detail": "Comprimi o espandi gli elenchi lunghi per concentrarti sui servizi interessati.",
|
||||
"dashboard.table.time": "Ora",
|
||||
"dashboard.table.jail": "Jail",
|
||||
"dashboard.table.ip": "IP",
|
||||
"dashboard.table.log_line": "Riga di log",
|
||||
"dashboard.no_banned_ips": "Nessuna IP bloccata",
|
||||
"dashboard.unban": "Sblocca",
|
||||
"dashboard.banned.show_more": "Mostra di più",
|
||||
"dashboard.banned.show_less": "Mostra meno",
|
||||
"logs.overview.title": "Panoramica interna dei log",
|
||||
"logs.overview.subtitle": "Eventi memorizzati da Fail2ban-UI su tutti i connettori.",
|
||||
"logs.overview.refresh": "Aggiorna dati",
|
||||
|
||||
@@ -537,8 +537,8 @@ WHERE 1=1`
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// CountBanEvents returns total number of ban events optionally filtered by time.
|
||||
func CountBanEvents(ctx context.Context, since time.Time) (int64, error) {
|
||||
// CountBanEvents returns total number of ban events optionally filtered by time and server.
|
||||
func CountBanEvents(ctx context.Context, since time.Time, serverID string) (int64, error) {
|
||||
if db == nil {
|
||||
return 0, errors.New("storage not initialised")
|
||||
}
|
||||
@@ -549,6 +549,11 @@ FROM ban_events
|
||||
WHERE 1=1`
|
||||
args := []any{}
|
||||
|
||||
if serverID != "" {
|
||||
query += " AND server_id = ?"
|
||||
args = append(args, serverID)
|
||||
}
|
||||
|
||||
if !since.IsZero() {
|
||||
query += " AND occurred_at >= ?"
|
||||
args = append(args, since.UTC())
|
||||
@@ -561,8 +566,8 @@ WHERE 1=1`
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// CountBanEventsByCountry returns aggregation per country code.
|
||||
func CountBanEventsByCountry(ctx context.Context, since time.Time) (map[string]int64, error) {
|
||||
// CountBanEventsByCountry returns aggregation per country code, optionally filtered by server.
|
||||
func CountBanEventsByCountry(ctx context.Context, since time.Time, serverID string) (map[string]int64, error) {
|
||||
if db == nil {
|
||||
return nil, errors.New("storage not initialised")
|
||||
}
|
||||
@@ -573,6 +578,11 @@ FROM ban_events
|
||||
WHERE 1=1`
|
||||
args := []any{}
|
||||
|
||||
if serverID != "" {
|
||||
query += " AND server_id = ?"
|
||||
args = append(args, serverID)
|
||||
}
|
||||
|
||||
if !since.IsZero() {
|
||||
query += " AND occurred_at >= ?"
|
||||
args = append(args, since.UTC())
|
||||
@@ -599,8 +609,8 @@ WHERE 1=1`
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// ListRecurringIPStats returns IPs that have been banned at least minCount times.
|
||||
func ListRecurringIPStats(ctx context.Context, since time.Time, minCount, limit int) ([]RecurringIPStat, error) {
|
||||
// ListRecurringIPStats returns IPs that have been banned at least minCount times, optionally filtered by server.
|
||||
func ListRecurringIPStats(ctx context.Context, since time.Time, minCount, limit int, serverID string) ([]RecurringIPStat, error) {
|
||||
if db == nil {
|
||||
return nil, errors.New("storage not initialised")
|
||||
}
|
||||
@@ -618,6 +628,11 @@ FROM ban_events
|
||||
WHERE ip != ''`
|
||||
args := []any{}
|
||||
|
||||
if serverID != "" {
|
||||
query += " AND server_id = ?"
|
||||
args = append(args, serverID)
|
||||
}
|
||||
|
||||
if !since.IsZero() {
|
||||
query += " AND occurred_at >= ?"
|
||||
args = append(args, since.UTC())
|
||||
|
||||
@@ -248,6 +248,7 @@ func BanInsightsHandler(c *gin.Context) {
|
||||
since = parsed
|
||||
}
|
||||
}
|
||||
serverID := c.Query("serverId")
|
||||
|
||||
minCount := 3
|
||||
if minCountStr := c.DefaultQuery("minCount", "3"); minCountStr != "" {
|
||||
@@ -265,19 +266,19 @@ func BanInsightsHandler(c *gin.Context) {
|
||||
|
||||
ctx := c.Request.Context()
|
||||
|
||||
countriesMap, err := storage.CountBanEventsByCountry(ctx, since)
|
||||
countriesMap, err := storage.CountBanEventsByCountry(ctx, since, serverID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
recurring, err := storage.ListRecurringIPStats(ctx, since, minCount, limit)
|
||||
recurring, err := storage.ListRecurringIPStats(ctx, since, minCount, limit, serverID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
totalOverall, err := storage.CountBanEvents(ctx, time.Time{})
|
||||
totalOverall, err := storage.CountBanEvents(ctx, time.Time{}, serverID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -285,13 +286,13 @@ func BanInsightsHandler(c *gin.Context) {
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
totalToday, err := storage.CountBanEvents(ctx, now.Add(-24*time.Hour))
|
||||
totalToday, err := storage.CountBanEvents(ctx, now.Add(-24*time.Hour), serverID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
totalWeek, err := storage.CountBanEvents(ctx, now.Add(-7*24*time.Hour))
|
||||
totalWeek, err := storage.CountBanEvents(ctx, now.Add(-7*24*time.Hour), serverID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
/* Custom mark styling for search highlights */
|
||||
mark {
|
||||
background-color: #fef08a;
|
||||
padding: 0.1em 0.2em;
|
||||
padding: 0.1em 0em 0.1em 0.2em;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
|
||||
@@ -949,12 +949,31 @@
|
||||
countries: [],
|
||||
recurring: []
|
||||
};
|
||||
var latestServerInsights = null;
|
||||
var banEventsFilterText = '';
|
||||
var banEventsFilterCountry = 'all';
|
||||
var banEventsFilterDebounce = null;
|
||||
var translations = {};
|
||||
var sshKeysCache = null;
|
||||
|
||||
function normalizeInsights(data) {
|
||||
var normalized = data && typeof data === 'object' ? data : {};
|
||||
if (!normalized.totals || typeof normalized.totals !== 'object') {
|
||||
normalized.totals = { overall: 0, today: 0, week: 0 };
|
||||
} else {
|
||||
normalized.totals.overall = typeof normalized.totals.overall === 'number' ? normalized.totals.overall : 0;
|
||||
normalized.totals.today = typeof normalized.totals.today === 'number' ? normalized.totals.today : 0;
|
||||
normalized.totals.week = typeof normalized.totals.week === 'number' ? normalized.totals.week : 0;
|
||||
}
|
||||
if (!Array.isArray(normalized.countries)) {
|
||||
normalized.countries = [];
|
||||
}
|
||||
if (!Array.isArray(normalized.recurring)) {
|
||||
normalized.recurring = [];
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function t(key, fallback) {
|
||||
if (translations && Object.prototype.hasOwnProperty.call(translations, key) && translations[key]) {
|
||||
return translations[key];
|
||||
@@ -1354,20 +1373,37 @@
|
||||
}
|
||||
|
||||
function fetchBanInsightsData() {
|
||||
return fetch('/api/events/bans/insights')
|
||||
var sevenDaysAgo = new Date(Date.now() - (7 * 24 * 60 * 60 * 1000)).toISOString();
|
||||
var sinceQuery = '?since=' + encodeURIComponent(sevenDaysAgo);
|
||||
var globalPromise = fetch('/api/events/bans/insights' + sinceQuery)
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
latestBanInsights = data || {};
|
||||
latestBanInsights.totals = latestBanInsights.totals || { overall: 0, today: 0, week: 0 };
|
||||
latestBanInsights.countries = latestBanInsights.countries || [];
|
||||
latestBanInsights.recurring = latestBanInsights.recurring || [];
|
||||
latestBanInsights = normalizeInsights(data);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error fetching ban insights:', err);
|
||||
if (!latestBanInsights) {
|
||||
latestBanInsights = { totals: { overall: 0, today: 0, week: 0 }, countries: [], recurring: [] };
|
||||
latestBanInsights = normalizeInsights(null);
|
||||
}
|
||||
});
|
||||
|
||||
var serverPromise;
|
||||
if (currentServerId) {
|
||||
serverPromise = fetch(withServerParam('/api/events/bans/insights' + sinceQuery))
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
latestServerInsights = normalizeInsights(data);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('Error fetching server-specific ban insights:', err);
|
||||
latestServerInsights = null;
|
||||
});
|
||||
} else {
|
||||
latestServerInsights = null;
|
||||
serverPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.all([globalPromise, serverPromise]);
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
@@ -1411,25 +1447,11 @@
|
||||
}
|
||||
|
||||
function recurringIPsLastWeekCount() {
|
||||
if (!latestBanInsights || !Array.isArray(latestBanInsights.recurring)) {
|
||||
var source = latestServerInsights || latestBanInsights;
|
||||
if (!source || !Array.isArray(source.recurring)) {
|
||||
return 0;
|
||||
}
|
||||
var cutoff = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
||||
var seen = {};
|
||||
var count = 0;
|
||||
latestBanInsights.recurring.forEach(function(stat) {
|
||||
if (!stat || !stat.ip) {
|
||||
return;
|
||||
}
|
||||
var lastSeenTime = stat.lastSeen ? new Date(stat.lastSeen).getTime() : NaN;
|
||||
if (isNaN(lastSeenTime) || lastSeenTime >= cutoff) {
|
||||
if (!seen[stat.ip]) {
|
||||
seen[stat.ip] = true;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
return count;
|
||||
return source.recurring.length;
|
||||
}
|
||||
|
||||
function captureFocusState(container) {
|
||||
@@ -1460,7 +1482,13 @@
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
if (typeof next.focus === 'function') {
|
||||
try {
|
||||
next.focus({ preventScroll: true });
|
||||
} catch (err) {
|
||||
next.focus();
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (typeof state.selectionStart === 'number' && typeof state.selectionEnd === 'number' && typeof next.setSelectionRange === 'function') {
|
||||
next.setSelectionRange(state.selectionStart, state.selectionEnd);
|
||||
@@ -1522,19 +1550,24 @@
|
||||
});
|
||||
}
|
||||
|
||||
function updateBanEventsSearch(value) {
|
||||
banEventsFilterText = value || '';
|
||||
function scheduleLogOverviewRender() {
|
||||
if (banEventsFilterDebounce) {
|
||||
clearTimeout(banEventsFilterDebounce);
|
||||
}
|
||||
banEventsFilterDebounce = setTimeout(function() {
|
||||
renderDashboard();
|
||||
}, 200);
|
||||
renderLogOverviewSection();
|
||||
banEventsFilterDebounce = null;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function updateBanEventsSearch(value) {
|
||||
banEventsFilterText = value || '';
|
||||
scheduleLogOverviewRender();
|
||||
}
|
||||
|
||||
function updateBanEventsCountry(value) {
|
||||
banEventsFilterCountry = value || 'all';
|
||||
renderDashboard();
|
||||
scheduleLogOverviewRender();
|
||||
}
|
||||
|
||||
function getRecurringIPMap() {
|
||||
@@ -1562,6 +1595,7 @@
|
||||
+ ' <p class="text-sm mt-1" data-i18n="dashboard.no_servers_body">Add a server to start monitoring and controlling Fail2ban instances.</p>'
|
||||
+ '</div>';
|
||||
if (typeof updateTranslations === 'function') updateTranslations();
|
||||
restoreFocusState(focusState);
|
||||
return;
|
||||
}
|
||||
if (!enabledServers.length) {
|
||||
@@ -1571,6 +1605,7 @@
|
||||
+ ' <p class="text-sm mt-1" data-i18n="dashboard.no_enabled_servers_body">Enable the local connector or register a remote Fail2ban server to see live data.</p>'
|
||||
+ '</div>';
|
||||
if (typeof updateTranslations === 'function') updateTranslations();
|
||||
restoreFocusState(focusState);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1667,7 +1702,7 @@
|
||||
html += '</div>'; // close overview card
|
||||
}
|
||||
|
||||
html += renderLogOverview();
|
||||
html += '<div id="logOverview">' + renderLogOverviewContent() + '</div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
restoreFocusState(focusState);
|
||||
@@ -1693,7 +1728,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderLogOverview() {
|
||||
function renderLogOverviewSection() {
|
||||
var target = document.getElementById('logOverview');
|
||||
if (!target) return;
|
||||
var focusState = captureFocusState(target);
|
||||
target.innerHTML = renderLogOverviewContent();
|
||||
restoreFocusState(focusState);
|
||||
if (typeof updateTranslations === 'function') {
|
||||
updateTranslations();
|
||||
}
|
||||
}
|
||||
|
||||
function renderLogOverviewContent() {
|
||||
var html = ''
|
||||
+ '<div class="bg-white rounded-lg shadow p-6 mb-6">'
|
||||
+ ' <div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between mb-4">'
|
||||
@@ -1770,6 +1816,7 @@
|
||||
var countries = getBanEventCountries();
|
||||
var filteredEvents = getFilteredBanEvents();
|
||||
var recurringMap = getRecurringIPMap();
|
||||
var searchQuery = (banEventsFilterText || '').trim();
|
||||
|
||||
html += ''
|
||||
+ '<div class="flex flex-col sm:flex-row gap-3 mb-4">'
|
||||
@@ -1817,15 +1864,20 @@
|
||||
var index = latestBanEvents.indexOf(event);
|
||||
var hasWhois = event.whois && event.whois.trim().length > 0;
|
||||
var hasLogs = event.logs && event.logs.trim().length > 0;
|
||||
var ipCell = escapeHtml(event.ip || '');
|
||||
var serverValue = event.serverName || event.serverId || '';
|
||||
var jailValue = event.jail || '';
|
||||
var ipValue = event.ip || '';
|
||||
var serverCell = highlightQueryMatch(serverValue, searchQuery);
|
||||
var jailCell = highlightQueryMatch(jailValue, searchQuery);
|
||||
var ipCell = highlightQueryMatch(ipValue, searchQuery);
|
||||
if (event.ip && recurringMap[event.ip]) {
|
||||
ipCell += ' <span class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">' + t('logs.badge.recurring', 'Recurring') + '</span>';
|
||||
}
|
||||
html += ''
|
||||
+ ' <tr class="hover:bg-gray-50">'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + escapeHtml(formatDateTime(event.occurredAt || event.createdAt)) + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + escapeHtml(event.serverName || event.serverId || '') + '</td>'
|
||||
+ ' <td class="hidden sm:table-cell px-2 py-2 whitespace-nowrap">' + escapeHtml(event.jail || '') + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + serverCell + '</td>'
|
||||
+ ' <td class="hidden sm:table-cell px-2 py-2 whitespace-nowrap">' + jailCell + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">' + ipCell + '</td>'
|
||||
+ ' <td class="hidden md:table-cell px-2 py-2 whitespace-nowrap">' + escapeHtml(event.country || '—') + '</td>'
|
||||
+ ' <td class="px-2 py-2 whitespace-nowrap">'
|
||||
@@ -2251,6 +2303,24 @@
|
||||
});
|
||||
}
|
||||
|
||||
function highlightQueryMatch(value, query) {
|
||||
var text = value || '';
|
||||
if (!query) {
|
||||
return escapeHtml(text);
|
||||
}
|
||||
var escapedPattern = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
if (!escapedPattern) {
|
||||
return escapeHtml(text);
|
||||
}
|
||||
var regex = new RegExp(escapedPattern, "gi");
|
||||
var highlighted = text.replace(regex, function(match) {
|
||||
return "%%MARK_START%%" + match + "%%MARK_END%%";
|
||||
});
|
||||
return escapeHtml(highlighted)
|
||||
.replace(/%%MARK_START%%/g, "<mark>")
|
||||
.replace(/%%MARK_END%%/g, "</mark>");
|
||||
}
|
||||
|
||||
// Render banned IPs with "Unban" button
|
||||
function slugifyId(value, prefix) {
|
||||
var input = (value || '').toString();
|
||||
@@ -2377,15 +2447,7 @@
|
||||
} else if (originalIP.indexOf(query) !== -1) {
|
||||
// If the IP contains the query, show the item and highlight the matching text.
|
||||
item.style.display = "";
|
||||
const escapedPattern = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(escapedPattern, "gi");
|
||||
var highlighted = originalIP.replace(regex, function(match) {
|
||||
return "%%MARK_START%%" + match + "%%MARK_END%%";
|
||||
});
|
||||
var safeHighlighted = escapeHtml(highlighted)
|
||||
.replace(/%%MARK_START%%/g, "<mark>")
|
||||
.replace(/%%MARK_END%%/g, "</mark>");
|
||||
span.innerHTML = safeHighlighted;
|
||||
span.innerHTML = highlightQueryMatch(originalIP, query);
|
||||
rowHasMatch = true;
|
||||
} else {
|
||||
item.style.display = "none";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user