mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-17 05:53:15 +02:00
Implement filtering for ban event-history, simple aggregation and insights
This commit is contained in:
@@ -39,8 +39,16 @@
|
||||
"logs.overview.total_events": "Gespeicherte Ereignisse gesamt",
|
||||
"logs.overview.per_server": "Ereignisse pro Server",
|
||||
"logs.overview.recent_events_title": "Letzte gespeicherte Ereignisse",
|
||||
"logs.overview.recent_empty": "Für den ausgewählten Server wurden keine gespeicherten Ereignisse gefunden.",
|
||||
"logs.overview.recent_empty": "Keine gespeicherten Ereignisse gefunden.",
|
||||
"logs.overview.empty": "Es wurden noch keine Sperr-Ereignisse protokolliert.",
|
||||
"logs.overview.open_insights": "Insights öffnen",
|
||||
"logs.overview.total_today": "Heute",
|
||||
"logs.overview.total_week": "Letzte 7 Tage",
|
||||
"logs.overview.per_server_empty": "Noch keine Serverdaten verfügbar.",
|
||||
"logs.overview.recent_filtered_empty": "Keine gespeicherten Ereignisse passen zu den Filtern.",
|
||||
"logs.overview.recent_count_label": "Angezeigte Ereignisse",
|
||||
"logs.overview.country_unknown": "Unbekannt",
|
||||
"logs.overview.last_seen": "Zuletzt gesehen",
|
||||
"logs.table.server": "Server",
|
||||
"logs.table.count": "Anzahl",
|
||||
"logs.table.jail": "Jail",
|
||||
@@ -50,9 +58,21 @@
|
||||
"logs.table.actions": "Aktionen",
|
||||
"logs.actions.whois": "Whois",
|
||||
"logs.actions.logs": "Logs",
|
||||
"logs.search.label": "Ereignisse suchen",
|
||||
"logs.search.placeholder": "Nach IP, Jail oder Server suchen",
|
||||
"logs.search.country_label": "Land",
|
||||
"logs.search.country_all": "Alle Länder",
|
||||
"logs.search.country_unknown": "Unbekannt",
|
||||
"logs.badge.recurring": "Wiederkehrend",
|
||||
"logs.modal.whois_title": "Whois-Informationen",
|
||||
"logs.modal.logs_title": "Logs",
|
||||
"logs.modal.jail": "Jail",
|
||||
"logs.modal.insights_title": "Ban-Insights",
|
||||
"logs.modal.insights_description": "Verteilung nach Ländern und wiederholte Angreifer.",
|
||||
"logs.modal.insights_countries": "Sperren nach Land",
|
||||
"logs.modal.insights_countries_empty": "Für diesen Zeitraum wurden keine Sperren erfasst.",
|
||||
"logs.modal.insights_recurring": "Wiederkehrende IPs",
|
||||
"logs.modal.insights_recurring_empty": "Keine wiederkehrenden IPs erkannt.",
|
||||
"filter_debug.title": "Filter-Debug",
|
||||
"filter_debug.select_filter": "Wählen Sie einen Filter",
|
||||
"filter_debug.log_lines": "Logzeilen",
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
"logs.overview.total_events": "Total gspeichereti Ereigniss",
|
||||
"logs.overview.per_server": "Ereigniss pro Server",
|
||||
"logs.overview.recent_events_title": "Letschti gspeichereti Ereigniss",
|
||||
"logs.overview.recent_empty": "Kei gspeichereti Ereigniss für dä gwählte Server gfunde.",
|
||||
"logs.overview.recent_empty": "Kei gspeichereti Ereigniss gfunde.",
|
||||
"logs.overview.empty": "No kei Sperr-Ereigniss protokolliert.",
|
||||
"logs.overview.open_insights": "Insights azeige",
|
||||
"logs.overview.total_today": "Hüt",
|
||||
"logs.overview.total_week": "Letschti 7 Täg",
|
||||
"logs.overview.per_server_empty": "No kei Serverdate verfügbar.",
|
||||
"logs.overview.recent_filtered_empty": "Kei Ereigniss erfülle d Filter.",
|
||||
"logs.overview.recent_count_label": "Aazeigti Ereigniss",
|
||||
"logs.overview.country_unknown": "Unbekannt",
|
||||
"logs.overview.last_seen": "Zletscht gseh",
|
||||
"logs.table.server": "Server",
|
||||
"logs.table.count": "Aazahl",
|
||||
"logs.table.jail": "Jail",
|
||||
@@ -50,9 +58,21 @@
|
||||
"logs.table.actions": "Aktione",
|
||||
"logs.actions.whois": "Whois",
|
||||
"logs.actions.logs": "Logs",
|
||||
"logs.search.label": "Ereigniss sueche",
|
||||
"logs.search.placeholder": "IP, Jail oder Server sueche",
|
||||
"logs.search.country_label": "Land",
|
||||
"logs.search.country_all": "Alli Länder",
|
||||
"logs.search.country_unknown": "Unbekannt",
|
||||
"logs.badge.recurring": "Widerkehrend",
|
||||
"logs.modal.whois_title": "Whois-Informatione",
|
||||
"logs.modal.logs_title": "Logs",
|
||||
"logs.modal.jail": "Jail",
|
||||
"logs.modal.insights_title": "Ban-Insights",
|
||||
"logs.modal.insights_description": "Verteilig nach Länder und wiederholti Angreifer.",
|
||||
"logs.modal.insights_countries": "Sperre nach Land",
|
||||
"logs.modal.insights_countries_empty": "Kei Sperre i däm Zeitraum.",
|
||||
"logs.modal.insights_recurring": "Wiederkehrendi IPs",
|
||||
"logs.modal.insights_recurring_empty": "Kei wiederkehrendi IPs erkannt.",
|
||||
"filter_debug.title": "Filter Debug",
|
||||
"filter_debug.select_filter": "Wähl en Filter us",
|
||||
"filter_debug.log_lines": "Log-Zile",
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
"logs.overview.total_events": "Total stored events",
|
||||
"logs.overview.per_server": "Events per server",
|
||||
"logs.overview.recent_events_title": "Recent stored events",
|
||||
"logs.overview.recent_empty": "No stored events found for the selected server.",
|
||||
"logs.overview.recent_empty": "No stored events found.",
|
||||
"logs.overview.empty": "No ban events recorded yet.",
|
||||
"logs.overview.open_insights": "Open insights",
|
||||
"logs.overview.total_today": "Today",
|
||||
"logs.overview.total_week": "Last 7 days",
|
||||
"logs.overview.per_server_empty": "No per-server data available yet.",
|
||||
"logs.overview.recent_filtered_empty": "No stored events match the current filters.",
|
||||
"logs.overview.recent_count_label": "Events shown",
|
||||
"logs.overview.country_unknown": "Unknown",
|
||||
"logs.overview.last_seen": "Last seen",
|
||||
"logs.table.server": "Server",
|
||||
"logs.table.count": "Count",
|
||||
"logs.table.jail": "Jail",
|
||||
@@ -50,9 +58,21 @@
|
||||
"logs.table.actions": "Actions",
|
||||
"logs.actions.whois": "Whois",
|
||||
"logs.actions.logs": "Logs",
|
||||
"logs.search.label": "Search events",
|
||||
"logs.search.placeholder": "Search IP, jail or server",
|
||||
"logs.search.country_label": "Country",
|
||||
"logs.search.country_all": "All countries",
|
||||
"logs.search.country_unknown": "Unknown",
|
||||
"logs.badge.recurring": "Recurring",
|
||||
"logs.modal.whois_title": "Whois Information",
|
||||
"logs.modal.logs_title": "Logs",
|
||||
"logs.modal.jail": "Jail",
|
||||
"logs.modal.insights_title": "Ban Insights",
|
||||
"logs.modal.insights_description": "Country distribution and recurring offenders.",
|
||||
"logs.modal.insights_countries": "Bans by country",
|
||||
"logs.modal.insights_countries_empty": "No bans recorded for this period.",
|
||||
"logs.modal.insights_recurring": "Recurring IPs",
|
||||
"logs.modal.insights_recurring_empty": "No recurring IPs detected.",
|
||||
"filter_debug.title": "Filter Debug",
|
||||
"filter_debug.select_filter": "Select a Filter",
|
||||
"filter_debug.log_lines": "Log Lines",
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
"logs.overview.total_events": "Eventos almacenados totales",
|
||||
"logs.overview.per_server": "Eventos por servidor",
|
||||
"logs.overview.recent_events_title": "Eventos almacenados recientes",
|
||||
"logs.overview.recent_empty": "No se encontraron eventos almacenados para el servidor seleccionado.",
|
||||
"logs.overview.recent_empty": "No se encontraron eventos almacenados.",
|
||||
"logs.overview.empty": "Aún no se han registrado eventos de bloqueo.",
|
||||
"logs.overview.open_insights": "Abrir estadísticas",
|
||||
"logs.overview.total_today": "Hoy",
|
||||
"logs.overview.total_week": "Últimos 7 días",
|
||||
"logs.overview.per_server_empty": "Aún no hay datos por servidor.",
|
||||
"logs.overview.recent_filtered_empty": "No hay eventos que coincidan con los filtros.",
|
||||
"logs.overview.recent_count_label": "Eventos mostrados",
|
||||
"logs.overview.country_unknown": "Desconocido",
|
||||
"logs.overview.last_seen": "Última vez",
|
||||
"logs.table.server": "Servidor",
|
||||
"logs.table.count": "Cantidad",
|
||||
"logs.table.jail": "Jail",
|
||||
@@ -50,9 +58,21 @@
|
||||
"logs.table.actions": "Acciones",
|
||||
"logs.actions.whois": "Whois",
|
||||
"logs.actions.logs": "Registros",
|
||||
"logs.search.label": "Buscar eventos",
|
||||
"logs.search.placeholder": "Busca IP, jail o servidor",
|
||||
"logs.search.country_label": "País",
|
||||
"logs.search.country_all": "Todos los países",
|
||||
"logs.search.country_unknown": "Desconocido",
|
||||
"logs.badge.recurring": "Recurrente",
|
||||
"logs.modal.whois_title": "Información Whois",
|
||||
"logs.modal.logs_title": "Registros",
|
||||
"logs.modal.jail": "Jail",
|
||||
"logs.modal.insights_title": "Información de bloqueos",
|
||||
"logs.modal.insights_description": "Distribución por país y atacantes recurrentes.",
|
||||
"logs.modal.insights_countries": "Bloqueos por país",
|
||||
"logs.modal.insights_countries_empty": "No se registraron bloqueos en este periodo.",
|
||||
"logs.modal.insights_recurring": "IPs recurrentes",
|
||||
"logs.modal.insights_recurring_empty": "No se detectaron IPs recurrentes.",
|
||||
"filter_debug.title": "Depuración de filtros",
|
||||
"filter_debug.select_filter": "Selecciona un filtro",
|
||||
"filter_debug.log_lines": "Líneas de log",
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
"logs.overview.total_events": "Total d'événements enregistrés",
|
||||
"logs.overview.per_server": "Événements par serveur",
|
||||
"logs.overview.recent_events_title": "Événements enregistrés récents",
|
||||
"logs.overview.recent_empty": "Aucun événement enregistré trouvé pour le serveur sélectionné.",
|
||||
"logs.overview.recent_empty": "Aucun événement stocké trouvé.",
|
||||
"logs.overview.empty": "Aucun événement de blocage n'a encore été enregistré.",
|
||||
"logs.overview.open_insights": "Ouvrir les insights",
|
||||
"logs.overview.total_today": "Aujourd'hui",
|
||||
"logs.overview.total_week": "7 derniers jours",
|
||||
"logs.overview.per_server_empty": "Aucune donnée par serveur pour le moment.",
|
||||
"logs.overview.recent_filtered_empty": "Aucun événement ne correspond aux filtres.",
|
||||
"logs.overview.recent_count_label": "Événements affichés",
|
||||
"logs.overview.country_unknown": "Inconnu",
|
||||
"logs.overview.last_seen": "Dernière apparition",
|
||||
"logs.table.server": "Serveur",
|
||||
"logs.table.count": "Nombre",
|
||||
"logs.table.jail": "Jail",
|
||||
@@ -50,9 +58,21 @@
|
||||
"logs.table.actions": "Actions",
|
||||
"logs.actions.whois": "Whois",
|
||||
"logs.actions.logs": "Journaux",
|
||||
"logs.search.label": "Rechercher des événements",
|
||||
"logs.search.placeholder": "Rechercher IP, jail ou serveur",
|
||||
"logs.search.country_label": "Pays",
|
||||
"logs.search.country_all": "Tous les pays",
|
||||
"logs.search.country_unknown": "Inconnu",
|
||||
"logs.badge.recurring": "Récurrent",
|
||||
"logs.modal.whois_title": "Informations Whois",
|
||||
"logs.modal.logs_title": "Journaux",
|
||||
"logs.modal.jail": "Jail",
|
||||
"logs.modal.insights_title": "Aperçu des blocages",
|
||||
"logs.modal.insights_description": "Répartition par pays et IP récurrentes.",
|
||||
"logs.modal.insights_countries": "Blocages par pays",
|
||||
"logs.modal.insights_countries_empty": "Aucun blocage enregistré pour cette période.",
|
||||
"logs.modal.insights_recurring": "IPs récurrentes",
|
||||
"logs.modal.insights_recurring_empty": "Aucune IP récurrente détectée.",
|
||||
"filter_debug.title": "Débogage des filtres",
|
||||
"filter_debug.select_filter": "Sélectionnez un filtre",
|
||||
"filter_debug.log_lines": "Lignes de log",
|
||||
|
||||
@@ -39,8 +39,16 @@
|
||||
"logs.overview.total_events": "Eventi memorizzati totali",
|
||||
"logs.overview.per_server": "Eventi per server",
|
||||
"logs.overview.recent_events_title": "Eventi memorizzati recenti",
|
||||
"logs.overview.recent_empty": "Nessun evento memorizzato trovato per il server selezionato.",
|
||||
"logs.overview.recent_empty": "Nessun evento memorizzato trovato.",
|
||||
"logs.overview.empty": "Nessun evento di blocco è stato ancora registrato.",
|
||||
"logs.overview.open_insights": "Apri insights",
|
||||
"logs.overview.total_today": "Oggi",
|
||||
"logs.overview.total_week": "Ultimi 7 giorni",
|
||||
"logs.overview.per_server_empty": "Ancora nessun dato per server.",
|
||||
"logs.overview.recent_filtered_empty": "Nessun evento corrisponde ai filtri.",
|
||||
"logs.overview.recent_count_label": "Eventi mostrati",
|
||||
"logs.overview.country_unknown": "Sconosciuto",
|
||||
"logs.overview.last_seen": "Ultima visualizzazione",
|
||||
"logs.table.server": "Server",
|
||||
"logs.table.count": "Conteggio",
|
||||
"logs.table.jail": "Jail",
|
||||
@@ -50,9 +58,21 @@
|
||||
"logs.table.actions": "Azioni",
|
||||
"logs.actions.whois": "Whois",
|
||||
"logs.actions.logs": "Log",
|
||||
"logs.search.label": "Cerca eventi",
|
||||
"logs.search.placeholder": "Cerca IP, jail o server",
|
||||
"logs.search.country_label": "Paese",
|
||||
"logs.search.country_all": "Tutti i paesi",
|
||||
"logs.search.country_unknown": "Sconosciuto",
|
||||
"logs.badge.recurring": "Ricorrente",
|
||||
"logs.modal.whois_title": "Informazioni Whois",
|
||||
"logs.modal.logs_title": "Log",
|
||||
"logs.modal.jail": "Jail",
|
||||
"logs.modal.insights_title": "Statistiche blocchi",
|
||||
"logs.modal.insights_description": "Distribuzione per paese e IP ricorrenti.",
|
||||
"logs.modal.insights_countries": "Blocchi per paese",
|
||||
"logs.modal.insights_countries_empty": "Nessun blocco registrato per questo periodo.",
|
||||
"logs.modal.insights_recurring": "IP ricorrenti",
|
||||
"logs.modal.insights_recurring_empty": "Nessun IP ricorrente rilevato.",
|
||||
"filter_debug.title": "Debug Filtro",
|
||||
"filter_debug.select_filter": "Seleziona un filtro",
|
||||
"filter_debug.log_lines": "Righe di log",
|
||||
|
||||
@@ -104,6 +104,14 @@ type BanEventRecord struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// RecurringIPStat represents aggregation info for repeatedly banned IPs.
|
||||
type RecurringIPStat struct {
|
||||
IP string `json:"ip"`
|
||||
Country string `json:"country"`
|
||||
Count int64 `json:"count"`
|
||||
LastSeen time.Time `json:"lastSeen"`
|
||||
}
|
||||
|
||||
// Init initializes the internal storage. Safe to call multiple times.
|
||||
func Init(dbPath string) error {
|
||||
initOnce.Do(func() {
|
||||
@@ -529,6 +537,124 @@ 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) {
|
||||
if db == nil {
|
||||
return 0, errors.New("storage not initialised")
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT COUNT(*)
|
||||
FROM ban_events
|
||||
WHERE 1=1`
|
||||
args := []any{}
|
||||
|
||||
if !since.IsZero() {
|
||||
query += " AND occurred_at >= ?"
|
||||
args = append(args, since.UTC())
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := db.QueryRowContext(ctx, query, args...).Scan(&total); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// CountBanEventsByCountry returns aggregation per country code.
|
||||
func CountBanEventsByCountry(ctx context.Context, since time.Time) (map[string]int64, error) {
|
||||
if db == nil {
|
||||
return nil, errors.New("storage not initialised")
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT COALESCE(country, '') AS country, COUNT(*)
|
||||
FROM ban_events
|
||||
WHERE 1=1`
|
||||
args := []any{}
|
||||
|
||||
if !since.IsZero() {
|
||||
query += " AND occurred_at >= ?"
|
||||
args = append(args, since.UTC())
|
||||
}
|
||||
|
||||
query += " GROUP BY COALESCE(country, '')"
|
||||
|
||||
rows, err := db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make(map[string]int64)
|
||||
for rows.Next() {
|
||||
var country sql.NullString
|
||||
var count int64
|
||||
if err := rows.Scan(&country, &count); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[stringFromNull(country)] = count
|
||||
}
|
||||
|
||||
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) {
|
||||
if db == nil {
|
||||
return nil, errors.New("storage not initialised")
|
||||
}
|
||||
|
||||
if minCount < 2 {
|
||||
minCount = 2
|
||||
}
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT ip, COALESCE(country, '') AS country, COUNT(*) AS cnt, MAX(occurred_at) AS last_seen
|
||||
FROM ban_events
|
||||
WHERE ip != ''`
|
||||
args := []any{}
|
||||
|
||||
if !since.IsZero() {
|
||||
query += " AND occurred_at >= ?"
|
||||
args = append(args, since.UTC())
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY ip, COALESCE(country, '')
|
||||
HAVING cnt >= ?
|
||||
ORDER BY cnt DESC, last_seen DESC
|
||||
LIMIT ?`
|
||||
|
||||
args = append(args, minCount, limit)
|
||||
|
||||
rows, err := db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []RecurringIPStat
|
||||
for rows.Next() {
|
||||
var stat RecurringIPStat
|
||||
var lastSeen sql.NullString
|
||||
if err := rows.Scan(&stat.IP, &stat.Country, &stat.Count, &lastSeen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lastSeen.Valid {
|
||||
if parsed, err := time.Parse(time.RFC3339Nano, lastSeen.String); err == nil {
|
||||
stat.LastSeen = parsed
|
||||
}
|
||||
}
|
||||
results = append(results, stat)
|
||||
}
|
||||
|
||||
return results, rows.Err()
|
||||
}
|
||||
|
||||
func ensureSchema(ctx context.Context) error {
|
||||
if db == nil {
|
||||
return errors.New("storage not initialised")
|
||||
|
||||
Reference in New Issue
Block a user