Merge pull request #5 from swissmakers/dev

Dev
This commit is contained in:
Swissmakers GmbH
2025-02-19 13:34:58 +01:00
committed by GitHub
9 changed files with 1427 additions and 963 deletions

View File

@@ -12,36 +12,43 @@ import (
)
func main() {
// Get application settings from the config package.
settings := config.GetSettings()
// Set Gin mode based on settings
// Set Gin mode based on the debug flag in settings.
if settings.Debug {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
// Create a new Gin router.
router := gin.Default()
// To detect if running inside a container or not
// Load HTML templates depending on whether the application is running inside a container.
_, container := os.LookupEnv("CONTAINER")
if container {
router.LoadHTMLGlob("/app/templates/*") // Load HTML templates
// In container, templates are assumed to be in /app/templates
router.LoadHTMLGlob("/app/templates/*")
} else {
router.LoadHTMLGlob("pkg/web/templates/*") // Load HTML templates
// When running locally, load templates from pkg/web/templates
router.LoadHTMLGlob("pkg/web/templates/*")
}
// Register all application routes, including the static file serving route for locales.
web.RegisterRoutes(router)
printWelcomeBanner()
log.Println("--- Fail2Ban-UI started in", gin.Mode(), "mode ---")
log.Println("Server listening on port :8080.")
// Start the server on port 8080.
if err := router.Run(":8080"); err != nil {
log.Fatalf("Server crashed: %v", err)
}
}
// printWelcomeBanner prints a cool Tux banner with startup info
// printWelcomeBanner prints a cool Tux banner with startup info.
func printWelcomeBanner() {
greeting := getGreeting()
const tuxBanner = `
@@ -64,7 +71,7 @@ Listening on: http://0.0.0.0:8080
fmt.Printf(tuxBanner, greeting, gin.Mode())
}
// getGreeting returns a friendly greeting based on the time of day
// getGreeting returns a friendly greeting based on the time of day.
func getGreeting() string {
hour := time.Now().Hour()
switch {

69
internal/locales/de.json Normal file
View File

@@ -0,0 +1,69 @@
{
"page.title": "Fail2ban UI Dashboard",
"nav.dashboard": "Dashboard",
"nav.filter_debug": "Filter-Debug",
"nav.settings": "Einstellungen",
"reload_banner.message": "Konfiguration geändert!",
"reload_banner.button": "Fail2ban neu laden",
"dashboard.title": "Dashboard",
"dashboard.overview": "Aktive Jails und Blocks Übersicht",
"dashboard.search_label": "Suche gesperrte IPs",
"dashboard.search_placeholder": "Geben Sie eine IP-Adresse zum Suchen ein",
"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.last_bans": "Letzte 5 Sperrvorgänge",
"dashboard.table.time": "Zeit",
"dashboard.table.jail": "Jail",
"dashboard.table.ip": "IP",
"dashboard.table.log_line": "Logzeile",
"dashboard.no_recent_bans": "Keine aktuellen Sperrvorgänge gefunden.",
"dashboard.no_banned_ips": "Keine gesperrten IPs",
"dashboard.unban": "Entsperren",
"filter_debug.title": "Filter-Debug",
"filter_debug.select_filter": "Wählen Sie einen Filter",
"filter_debug.log_lines": "Logzeilen",
"filter_debug.log_lines_placeholder": "Geben Sie die Logzeilen hier ein...",
"filter_debug.test_filter": "Filter testen",
"filter_debug.test_results_title": "Testergebnisse",
"filter_debug.no_matches": "Keine Übereinstimmungen gefunden.",
"settings.title": "Einstellungen",
"settings.general": "Allgemeine Einstellungen",
"settings.language": "Sprache",
"settings.enable_debug": "Debug-Protokoll aktivieren",
"settings.alert": "Alarm-Einstellungen",
"settings.destination_email": "Ziel-E-Mail (Alarmempfänger)",
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
"settings.alert_countries": "Alarm-Länder",
"settings.alert_countries_description": "Wählen Sie die Länder aus, für die E-Mail-Alarme ausgelöst werden sollen, wenn eine Sperrung erfolgt.",
"settings.smtp": "SMTP-Konfiguration",
"settings.smtp_host": "SMTP-Host",
"settings.smtp_host_placeholder": "z.B. smtp.gmail.com",
"settings.smtp_port": "SMTP-Port",
"settings.smtp_username": "SMTP-Benutzername",
"settings.smtp_username_placeholder": "z.B. user@example.com",
"settings.smtp_password": "SMTP-Passwort",
"settings.smtp_password_placeholder": "Geben Sie das SMTP-Passwort ein",
"settings.smtp_sender": "Absender-E-Mail",
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "TLS verwenden (empfohlen)",
"settings.send_test_email": "Test-E-Mail senden",
"settings.fail2ban": "Fail2Ban-Konfiguration",
"settings.enable_bantime_increment": "Bantime-Inkrement aktivieren",
"settings.default_bantime": "Standard-Bantime",
"settings.default_bantime_placeholder": "z.B. 48h",
"settings.default_findtime": "Standard-Findtime",
"settings.default_findtime_placeholder": "z.B. 30m",
"settings.default_max_retry": "Standard-Maximalversuche",
"settings.default_max_retry_placeholder": "Geben Sie die maximale Anzahl der Versuche ein",
"settings.ignore_ips": "IP-Adressen ignorieren",
"settings.ignore_ips_placeholder": "IP-Adressen, getrennt durch Leerzeichen",
"settings.save": "Speichern",
"modal.filter_config": "Filter-Konfiguration:",
"modal.cancel": "Abbrechen",
"modal.save": "Speichern",
"loading": "Lade..."
}

View File

@@ -0,0 +1,69 @@
{
"page.title": "Fail2ban UI Dashboard",
"nav.dashboard": "Dashboard",
"nav.filter_debug": "Filter Debug",
"nav.settings": "Istellige",
"reload_banner.message": "D'Konfiguration isch gänderet worde!",
"reload_banner.button": "Fail2ban neu lade",
"dashboard.title": "Dashboard",
"dashboard.overview": "Übersicht vo de aktive Jails und Blocks",
"dashboard.search_label": "Suech nach g'sperrte IPs",
"dashboard.search_placeholder": "Gib d'IP adrässe i, wo du suechsch",
"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.last_bans": "Letschti 5 Sperrvorgäng",
"dashboard.table.time": "Zyt",
"dashboard.table.jail": "Jail",
"dashboard.table.ip": "IP",
"dashboard.table.log_line": "Log-Zile",
"dashboard.no_recent_bans": "Kei aktuelli Sperrvorgäng gfunde.",
"dashboard.no_banned_ips": "Kei g'sperrti IPs",
"dashboard.unban": "Entsperre",
"filter_debug.title": "Filter Debug",
"filter_debug.select_filter": "Wähl en Filter us",
"filter_debug.log_lines": "Log-Zile",
"filter_debug.log_lines_placeholder": "Gib ä Log-Zile da ii...",
"filter_debug.test_filter": "Filter teste",
"filter_debug.test_results_title": "Testergebnis",
"filter_debug.no_matches": "Kei Übereinstimmige gfunde.",
"settings.title": "Istellige",
"settings.general": "Allgemeini Istellige",
"settings.language": "Sprach",
"settings.enable_debug": "Debug-Modus aktivierä",
"settings.alert": "Alarm-Istellige",
"settings.destination_email": "Ziil-Email (Alarmempfänger)",
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
"settings.alert_countries": "Alarm-Länder",
"settings.alert_countries_description": "Wähl d'Länder us, für weli du per Email ä Alarm becho wetsch, wenn e Sperrig passiert.",
"settings.smtp": "SMTP-Konfiguration",
"settings.smtp_host": "SMTP-Host",
"settings.smtp_host_placeholder": "z.B. smtp.gmail.com",
"settings.smtp_port": "SMTP-Port",
"settings.smtp_username": "SMTP-Benutzername",
"settings.smtp_username_placeholder": "z.B. user@example.com",
"settings.smtp_password": "SMTP-Passwort",
"settings.smtp_password_placeholder": "Gib s'SMTP-Passwort ii",
"settings.smtp_sender": "Absänder-Email",
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "TLS bruuche (empfohlen)",
"settings.send_test_email": "Test-Email schicke",
"settings.fail2ban": "Fail2Ban-Konfiguration",
"settings.enable_bantime_increment": "Bantime-Inkrement aktivierä",
"settings.default_bantime": "Standard-Bantime",
"settings.default_bantime_placeholder": "z.B. 48h",
"settings.default_findtime": "Standard-Findtime",
"settings.default_findtime_placeholder": "z.B. 30m",
"settings.default_max_retry": "Standard-Maximalversüech",
"settings.default_max_retry_placeholder": "Gib d'maximal Versüech ii",
"settings.ignore_ips": "IPs ignorierä",
"settings.ignore_ips_placeholder": "IPs, getrennt dur e Leerzeichä",
"settings.save": "Speicherä",
"modal.filter_config": "Filter-Konfiguration:",
"modal.cancel": "Abbräche",
"modal.save": "Speicherä",
"loading": "Lade..."
}

69
internal/locales/en.json Normal file
View File

@@ -0,0 +1,69 @@
{
"page.title": "Fail2ban UI Dashboard",
"nav.dashboard": "Dashboard",
"nav.filter_debug": "Filter Debug",
"nav.settings": "Settings",
"reload_banner.message": "Configuration changed!",
"reload_banner.button": "Reload Fail2ban",
"dashboard.title": "Dashboard",
"dashboard.overview": "Overview active Jails and Blocks",
"dashboard.search_label": "Search Banned IPs",
"dashboard.search_placeholder": "Enter IP address to search",
"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.last_bans": "Last 5 Ban Events",
"dashboard.table.time": "Time",
"dashboard.table.jail": "Jail",
"dashboard.table.ip": "IP",
"dashboard.table.log_line": "Log Line",
"dashboard.no_recent_bans": "No recent bans found.",
"dashboard.no_banned_ips": "No banned IPs",
"dashboard.unban": "Unban",
"filter_debug.title": "Filter Debug",
"filter_debug.select_filter": "Select a Filter",
"filter_debug.log_lines": "Log Lines",
"filter_debug.log_lines_placeholder": "Enter log lines here...",
"filter_debug.test_filter": "Test Filter",
"filter_debug.test_results_title": "Test Results",
"filter_debug.no_matches": "No matches found.",
"settings.title": "Settings",
"settings.general": "General Settings",
"settings.language": "Language",
"settings.enable_debug": "Enable Debug Log",
"settings.alert": "Alert Settings",
"settings.destination_email": "Destination Email (Alerts Receiver)",
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
"settings.alert_countries": "Alert Countries",
"settings.alert_countries_description": "Choose the countries for which you want to receive email alerts when a block is triggered.",
"settings.smtp": "SMTP Configuration",
"settings.smtp_host": "SMTP Host",
"settings.smtp_host_placeholder": "e.g., smtp.gmail.com",
"settings.smtp_port": "SMTP Port",
"settings.smtp_username": "SMTP Username",
"settings.smtp_username_placeholder": "e.g., user@example.com",
"settings.smtp_password": "SMTP Password",
"settings.smtp_password_placeholder": "Enter SMTP Password",
"settings.smtp_sender": "Sender Email",
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Use TLS (Recommended)",
"settings.send_test_email": "Send Test Email",
"settings.fail2ban": "Fail2Ban Configuration",
"settings.enable_bantime_increment": "Enable Bantime Increment",
"settings.default_bantime": "Default Bantime",
"settings.default_bantime_placeholder": "e.g., 48h",
"settings.default_findtime": "Default Findtime",
"settings.default_findtime_placeholder": "e.g., 30m",
"settings.default_max_retry": "Default Max Retry",
"settings.default_max_retry_placeholder": "Enter maximum retries",
"settings.ignore_ips": "Ignore IPs",
"settings.ignore_ips_placeholder": "IPs to ignore, separated by spaces",
"settings.save": "Save",
"modal.filter_config": "Filter Config:",
"modal.cancel": "Cancel",
"modal.save": "Save",
"loading": "Loading..."
}

68
internal/locales/es.json Normal file
View File

@@ -0,0 +1,68 @@
{
"page.title": "Panel de control Fail2ban UI",
"nav.dashboard": "Panel de control",
"nav.filter_debug": "Depuración de filtros",
"nav.settings": "Configuración",
"reload_banner.message": "¡La configuración ha sido modificada!",
"reload_banner.button": "Recargar Fail2ban",
"dashboard.title": "Panel de control",
"dashboard.overview": "Resumen de Jails y Bloqueos activos",
"dashboard.search_label": "Buscar IP bloqueadas",
"dashboard.search_placeholder": "Introduce la dirección IP a buscar",
"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.last_bans": "Últimos 5 eventos de bloqueo",
"dashboard.table.time": "Hora",
"dashboard.table.jail": "Jail",
"dashboard.table.ip": "IP",
"dashboard.table.log_line": "Línea de log",
"dashboard.no_recent_bans": "No se encontraron bloqueos recientes.",
"dashboard.no_banned_ips": "No hay IP bloqueadas",
"dashboard.unban": "Desbloquear",
"filter_debug.title": "Depuración de filtros",
"filter_debug.select_filter": "Selecciona un filtro",
"filter_debug.log_lines": "Líneas de log",
"filter_debug.log_lines_placeholder": "Introduce las líneas de log aquí...",
"filter_debug.test_filter": "Probar filtro",
"filter_debug.test_results_title": "Resultados de la prueba",
"filter_debug.no_matches": "No se encontraron coincidencias.",
"settings.title": "Configuración",
"settings.general": "Configuración general",
"settings.language": "Idioma",
"settings.enable_debug": "Habilitar el modo de depuración",
"settings.alert": "Configuración de alertas",
"settings.destination_email": "Correo electrónico de destino (receptor de alertas)",
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
"settings.alert_countries": "Países para alerta",
"settings.alert_countries_description": "Elige los países para los que deseas recibir alertas por correo electrónico cuando se produzca un bloqueo.",
"settings.smtp": "Configuración SMTP",
"settings.smtp_host": "Host SMTP",
"settings.smtp_host_placeholder": "p.ej., smtp.gmail.com",
"settings.smtp_port": "Puerto SMTP",
"settings.smtp_username": "Nombre de usuario SMTP",
"settings.smtp_username_placeholder": "p.ej., usuario@example.com",
"settings.smtp_password": "Contraseña SMTP",
"settings.smtp_password_placeholder": "Introduce la contraseña SMTP",
"settings.smtp_sender": "Correo electrónico del remitente",
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Usar TLS (recomendado)",
"settings.send_test_email": "Enviar correo de prueba",
"settings.fail2ban": "Configuración de Fail2Ban",
"settings.enable_bantime_increment": "Habilitar incremento de Bantime",
"settings.default_bantime": "Bantime por defecto",
"settings.default_bantime_placeholder": "p.ej., 48h",
"settings.default_findtime": "Findtime por defecto",
"settings.default_findtime_placeholder": "p.ej., 30m",
"settings.default_max_retry": "Número máximo de reintentos por defecto",
"settings.default_max_retry_placeholder": "Introduce el número máximo de reintentos",
"settings.ignore_ips": "Ignorar IPs",
"settings.ignore_ips_placeholder": "IPs a ignorar, separadas por espacios",
"settings.save": "Guardar",
"modal.filter_config": "Configuración del filtro:",
"modal.cancel": "Cancelar",
"modal.save": "Guardar",
"loading": "Cargando..."
}

68
internal/locales/fr.json Normal file
View File

@@ -0,0 +1,68 @@
{
"page.title": "Tableau de bord Fail2ban UI",
"nav.dashboard": "Tableau de bord",
"nav.filter_debug": "Débogage des filtres",
"nav.settings": "Paramètres",
"reload_banner.message": "Configuration modifiée!",
"reload_banner.button": "Recharger Fail2ban",
"dashboard.title": "Tableau de bord",
"dashboard.overview": "Vue d'ensemble des jails et blocages actifs",
"dashboard.search_label": "Rechercher des IP bloquées",
"dashboard.search_placeholder": "Entrez l'adresse IP à rechercher",
"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.last_bans": "5 derniers événements de blocage",
"dashboard.table.time": "Heure",
"dashboard.table.jail": "Jail",
"dashboard.table.ip": "IP",
"dashboard.table.log_line": "Ligne de log",
"dashboard.no_recent_bans": "Aucun blocage récent trouvé.",
"dashboard.no_banned_ips": "Aucune IP bloquée",
"dashboard.unban": "Débloquer",
"filter_debug.title": "Débogage des filtres",
"filter_debug.select_filter": "Sélectionnez un filtre",
"filter_debug.log_lines": "Lignes de log",
"filter_debug.log_lines_placeholder": "Entrez les lignes de log ici...",
"filter_debug.test_filter": "Tester le filtre",
"filter_debug.test_results_title": "Résultats du test",
"filter_debug.no_matches": "Aucune correspondance trouvée.",
"settings.title": "Paramètres",
"settings.general": "Paramètres généraux",
"settings.language": "Langue",
"settings.enable_debug": "Activer le mode débogage",
"settings.alert": "Paramètres d'alerte",
"settings.destination_email": "Email de destination (récepteur des alertes)",
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
"settings.alert_countries": "Pays d'alerte",
"settings.alert_countries_description": "Choisissez les pays pour lesquels vous souhaitez recevoir des alertes par email lors d'un blocage.",
"settings.smtp": "Configuration SMTP",
"settings.smtp_host": "Hôte SMTP",
"settings.smtp_host_placeholder": "par exemple, smtp.gmail.com",
"settings.smtp_port": "Port SMTP",
"settings.smtp_username": "Nom d'utilisateur SMTP",
"settings.smtp_username_placeholder": "par exemple, utilisateur@example.com",
"settings.smtp_password": "Mot de passe SMTP",
"settings.smtp_password_placeholder": "Entrez le mot de passe SMTP",
"settings.smtp_sender": "Email de l'expéditeur",
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Utiliser TLS (recommandé)",
"settings.send_test_email": "Envoyer un email de test",
"settings.fail2ban": "Configuration Fail2Ban",
"settings.enable_bantime_increment": "Activer l'incrémentation du Bantime",
"settings.default_bantime": "Bantime par défaut",
"settings.default_bantime_placeholder": "par exemple, 48h",
"settings.default_findtime": "Findtime par défaut",
"settings.default_findtime_placeholder": "par exemple, 30m",
"settings.default_max_retry": "Nombre maximal de réessais par défaut",
"settings.default_max_retry_placeholder": "Entrez le nombre maximal de réessais",
"settings.ignore_ips": "Ignorer les IPs",
"settings.ignore_ips_placeholder": "IPs à ignorer, séparées par des espaces",
"settings.save": "Enregistrer",
"modal.filter_config": "Configuration du filtre:",
"modal.cancel": "Annuler",
"modal.save": "Enregistrer",
"loading": "Chargement..."
}

68
internal/locales/it.json Normal file
View File

@@ -0,0 +1,68 @@
{
"page.title": "Cruscotto Fail2ban UI",
"nav.dashboard": "Cruscotto",
"nav.filter_debug": "Debug Filtro",
"nav.settings": "Impostazioni",
"reload_banner.message": "Configurazione modificata!",
"reload_banner.button": "Ricarica Fail2ban",
"dashboard.title": "Cruscotto",
"dashboard.overview": "Panoramica dei jail e dei blocchi attivi",
"dashboard.search_label": "Cerca IP bloccate",
"dashboard.search_placeholder": "Inserisci l'indirizzo IP da cercare",
"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.last_bans": "Ultimi 5 eventi di blocco",
"dashboard.table.time": "Ora",
"dashboard.table.jail": "Jail",
"dashboard.table.ip": "IP",
"dashboard.table.log_line": "Riga di log",
"dashboard.no_recent_bans": "Nessun blocco recente trovato.",
"dashboard.no_banned_ips": "Nessuna IP bloccata",
"dashboard.unban": "Sblocca",
"filter_debug.title": "Debug Filtro",
"filter_debug.select_filter": "Seleziona un filtro",
"filter_debug.log_lines": "Righe di log",
"filter_debug.log_lines_placeholder": "Inserisci qui le righe di log...",
"filter_debug.test_filter": "Testa filtro",
"filter_debug.test_results_title": "Risultati del test",
"filter_debug.no_matches": "Nessuna corrispondenza trovata.",
"settings.title": "Impostazioni",
"settings.general": "Impostazioni generali",
"settings.language": "Lingua",
"settings.enable_debug": "Abilita debug",
"settings.alert": "Impostazioni di allarme",
"settings.destination_email": "Email di destinazione (ricevente allarmi)",
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
"settings.alert_countries": "Paesi per allarme",
"settings.alert_countries_description": "Seleziona i paesi per i quali desideri ricevere allarmi via email quando si verifica un blocco.",
"settings.smtp": "Configurazione SMTP",
"settings.smtp_host": "Host SMTP",
"settings.smtp_host_placeholder": "es. smtp.gmail.com",
"settings.smtp_port": "Porta SMTP",
"settings.smtp_username": "Nome utente SMTP",
"settings.smtp_username_placeholder": "es. utente@example.com",
"settings.smtp_password": "Password SMTP",
"settings.smtp_password_placeholder": "Inserisci la password SMTP",
"settings.smtp_sender": "Email del mittente",
"settings.smtp_sender_placeholder": "noreply@swissmakers.ch",
"settings.smtp_tls": "Usa TLS (raccomandato)",
"settings.send_test_email": "Invia email di test",
"settings.fail2ban": "Configurazione Fail2Ban",
"settings.enable_bantime_increment": "Abilita incremento del Bantime",
"settings.default_bantime": "Bantime predefinito",
"settings.default_bantime_placeholder": "es. 48h",
"settings.default_findtime": "Findtime predefinito",
"settings.default_findtime_placeholder": "es. 30m",
"settings.default_max_retry": "Numero massimo di tentativi predefinito",
"settings.default_max_retry_placeholder": "Inserisci il numero massimo di tentativi",
"settings.ignore_ips": "Ignora IP",
"settings.ignore_ips_placeholder": "IP da ignorare, separate da spazi",
"settings.save": "Salva",
"modal.filter_config": "Configurazione del filtro:",
"modal.cancel": "Annulla",
"modal.save": "Salva",
"loading": "Caricamento..."
}

View File

@@ -22,6 +22,10 @@ import (
// RegisterRoutes sets up the routes for the Fail2ban UI.
func RegisterRoutes(r *gin.Engine) {
// Serve static files for locales from the "internal/locales" directory.
// (This makes the translation files available under the /locales/ URL.)
r.Static("/locales", "./internal/locales")
// Render the dashboard
r.GET("/", IndexHandler)
@@ -30,19 +34,19 @@ func RegisterRoutes(r *gin.Engine) {
api.GET("/summary", SummaryHandler)
api.POST("/jails/:jail/unban/:ip", UnbanIPHandler)
// config endpoints
// Config endpoints
api.GET("/jails/:jail/config", GetJailFilterConfigHandler)
api.POST("/jails/:jail/config", SetJailFilterConfigHandler)
// settings
// Settings endpoints
api.GET("/settings", GetSettingsHandler)
api.POST("/settings", UpdateSettingsHandler)
api.POST("/settings/test-email", TestEmailHandler)
// filter debugger
// Filter debugger endpoints
api.GET("/filters", ListFiltersHandler)
api.POST("/filters/test", TestFilterHandler)
// TODO create or generate new filters
// TODO: create or generate new filters
// api.POST("/filters/generate", GenerateFilterHandler)
// Reload endpoint

View File

@@ -21,7 +21,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Fail2ban UI Dashboard</title>
<title data-i18n="page.title">Fail2ban UI Dashboard</title>
<!-- Bootstrap 5 (CDN) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<!-- Select2 CSS -->
@@ -31,15 +31,18 @@
#loading-overlay {
display: none; /* hidden by default */
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999; /* on top */
align-items: center;
justify-content: center;
}
.spinner-border {
width: 4rem; height: 4rem;
width: 4rem;
height: 4rem;
}
/* Reload banner */
@@ -49,7 +52,8 @@
/* Improve table readability on small screens */
@media (max-width: 575px) {
.table th, .table td {
.table th,
.table td {
font-size: 0.8rem;
padding: 5px;
}
@@ -61,105 +65,108 @@
</head>
<body class="bg-light">
<!-- ******************************************************************* -->
<!-- NAVIGATION : -->
<!-- ******************************************************************* -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<!-- ******************************************************************* -->
<!-- NAVIGATION : -->
<!-- ******************************************************************* -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<strong>Fail2ban UI</strong>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#" onclick="showSection('dashboardSection')">Dashboard</a>
<a class="nav-link" href="#" onclick="showSection('dashboardSection')" data-i18n="nav.dashboard">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showSection('filterSection')">Filter Debug</a>
<a class="nav-link" href="#" onclick="showSection('filterSection')" data-i18n="nav.filter_debug">Filter Debug</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" onclick="showSection('settingsSection')">Settings</a>
<a class="nav-link" href="#" onclick="showSection('settingsSection')" data-i18n="nav.settings">Settings</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- ******************************************************************* -->
</nav>
<!-- ******************************************************************* -->
<!-- Reload Banner -->
<div id="reloadBanner" class="bg-warning text-dark p-3 text-center">
<strong>Configuration changed! </strong>
<button class="btn btn-dark" onclick="reloadFail2ban()">
Reload Fail2ban
</button>
</div>
<!-- Reload Banner -->
<div id="reloadBanner" class="bg-warning text-dark p-3 text-center">
<strong data-i18n="reload_banner.message">Configuration changed!</strong>
<button class="btn btn-dark" onclick="reloadFail2ban()" data-i18n="reload_banner.button">Reload Fail2ban</button>
</div>
<!-- ******************************************************************* -->
<!-- APP Sections (Pages) : -->
<!-- ******************************************************************* -->
<!-- ******************************************************************* -->
<!-- APP Sections (Pages) : -->
<!-- ******************************************************************* -->
<!-- Dashboard Section -->
<div id="dashboardSection" class="container my-4">
<h1 class="mb-4">Dashboard</h1>
<!-- Dashboard Section -->
<div id="dashboardSection" class="container my-4">
<h1 class="mb-4" data-i18n="dashboard.title">Dashboard</h1>
<div id="dashboard"></div>
</div>
</div>
<!-- Filter Debug Section -->
<div id="filterSection" style="display: none;" class="container my-4">
<h2>Filter Debug</h2>
<!-- Filter Debug Section -->
<div id="filterSection" style="display: none;" class="container my-4">
<h2 data-i18n="filter_debug.title">Filter Debug</h2>
<!-- Dropdown of available jail/filters -->
<div class="mb-3">
<label for="filterSelect" class="form-label">Select a Filter</label>
<label for="filterSelect" class="form-label" data-i18n="filter_debug.select_filter">Select a Filter</label>
<select id="filterSelect" class="form-select"></select>
</div>
<!-- Textarea for log lines to test -->
<div class="mb-3">
<label class="form-label">Log Lines</label>
<textarea id="logLinesTextarea" class="form-control" rows="6" disabled></textarea>
<label class="form-label" data-i18n="filter_debug.log_lines">Log Lines</label>
<textarea id="logLinesTextarea" class="form-control" rows="6" disabled
data-i18n-placeholder="filter_debug.log_lines_placeholder" placeholder="Enter log lines here..."></textarea>
</div>
<button class="btn btn-secondary" onclick="testSelectedFilter()">Test Filter</button>
<button class="btn btn-secondary" onclick="testSelectedFilter()" data-i18n="filter_debug.test_filter">Test Filter</button>
<hr/>
<div id="testResults"></div>
</div>
</div>
<!-- Settings Section -->
<div id="settingsSection" style="display: none;" class="container my-4">
<h2>Settings</h2>
<!-- Settings Section -->
<div id="settingsSection" style="display: none;" class="container my-4">
<h2 data-i18n="settings.title">Settings</h2>
<form onsubmit="saveSettings(event)">
<!-- General Settings Group -->
<fieldset class="border p-3 rounded mb-4">
<legend class="w-auto px-2">General Settings</legend>
<legend class="w-auto px-2" data-i18n="settings.general">General Settings</legend>
<!-- Language Selection -->
<div class="mb-3">
<label for="languageSelect" class="form-label">Language</label>
<select id="languageSelect" class="form-select" disabled>
<label for="languageSelect" class="form-label" data-i18n="settings.language">Language</label>
<select id="languageSelect" class="form-select">
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="it">Italiano</option>
<option value="de_ch">Schwiizerdütsch</option>
</select>
</div>
<!-- Debug Log Output -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="debugMode">
<label for="debugMode" class="form-check-label">Enable Debug Log</label>
<label for="debugMode" class="form-check-label" data-i18n="settings.enable_debug">Enable Debug Log</label>
</div>
</fieldset>
<!-- Alert Settings Group -->
<fieldset class="border p-3 rounded mb-4">
<legend class="w-auto px-2">Alert Settings</legend>
<legend class="w-auto px-2" data-i18n="settings.alert">Alert Settings</legend>
<div class="mb-3">
<label for="destEmail" class="form-label">Destination Email (Alerts Receiver)</label>
<input type="email" class="form-control" id="destEmail" placeholder="alerts@swissmakers.ch" />
<label for="destEmail" class="form-label" data-i18n="settings.destination_email">Destination Email (Alerts Receiver)</label>
<input type="email" class="form-control" id="destEmail"
data-i18n-placeholder="settings.destination_email_placeholder" placeholder="alerts@swissmakers.ch" />
</div>
<div class="mb-3">
<label for="alertCountries" class="form-label">Alert Countries</label>
<p class="text-muted">
<label for="alertCountries" class="form-label" data-i18n="settings.alert_countries">Alert Countries</label>
<p class="text-muted" data-i18n="settings.alert_countries_description">
Choose the countries for which you want to receive email alerts when a block is triggered.
</p>
<select id="alertCountries" class="form-select" multiple>
@@ -365,165 +372,164 @@
<!-- SMTP Configuration Group -->
<fieldset class="border p-3 rounded mb-4">
<legend class="w-auto px-2">SMTP Configuration</legend>
<legend class="w-auto px-2" data-i18n="settings.smtp">SMTP Configuration</legend>
<div class="mb-3">
<label for="smtpHost" class="form-label">SMTP Host</label>
<input type="text" class="form-control" id="smtpHost" placeholder="e.g., smtp.gmail.com" required />
<label for="smtpHost" class="form-label" data-i18n="settings.smtp_host">SMTP Host</label>
<input type="text" class="form-control" id="smtpHost"
data-i18n-placeholder="settings.smtp_host_placeholder" placeholder="e.g., smtp.gmail.com" required />
</div>
<label for="smtpPort">SMTP Port</label>
<label for="smtpPort" data-i18n="settings.smtp_port">SMTP Port</label>
<select id="smtpPort" class="form-select">
<option value="587" selected>587 (Recommended - STARTTLS)</option>
<option value="465" disabled>465 (Not Supported)</option>
</select>
<div class="mb-3">
<label for="smtpUsername" class="form-label">SMTP Username</label>
<input type="text" class="form-control" id="smtpUsername" placeholder="e.g., user@example.com" required />
<label for="smtpUsername" class="form-label" data-i18n="settings.smtp_username">SMTP Username</label>
<input type="text" class="form-control" id="smtpUsername"
data-i18n-placeholder="settings.smtp_username_placeholder" placeholder="e.g., user@example.com" required />
</div>
<div class="mb-3">
<label for="smtpPassword" class="form-label">SMTP Password</label>
<input type="password" class="form-control" id="smtpPassword" placeholder="Enter SMTP Password" required />
<label for="smtpPassword" class="form-label" data-i18n="settings.smtp_password">SMTP Password</label>
<input type="password" class="form-control" id="smtpPassword"
data-i18n-placeholder="settings.smtp_password_placeholder" placeholder="Enter SMTP Password" required />
</div>
<div class="mb-3">
<label for="smtpFrom" class="form-label">Sender Email</label>
<input type="email" class="form-control" id="smtpFrom" placeholder="noreply@swissmakers.ch" required />
<label for="smtpFrom" class="form-label" data-i18n="settings.smtp_sender">Sender Email</label>
<input type="email" class="form-control" id="smtpFrom"
data-i18n-placeholder="settings.smtp_sender_placeholder" placeholder="noreply@swissmakers.ch" required />
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="smtpUseTLS">
<label for="smtpUseTLS" class="form-check-label">Use TLS (Recommended)</label>
<label for="smtpUseTLS" class="form-check-label" data-i18n="settings.smtp_tls">Use TLS (Recommended)</label>
</div>
<button type="button" class="btn btn-secondary mt-2" onclick="sendTestEmail()">Send Test Email</button>
<button type="button" class="btn btn-secondary mt-2" onclick="sendTestEmail()" data-i18n="settings.send_test_email">Send Test Email</button>
</fieldset>
<!-- Fail2Ban Configuration Group -->
<fieldset class="border p-3 rounded mb-4">
<legend class="w-auto px-2">Fail2Ban Configuration</legend>
<legend class="w-auto px-2" data-i18n="settings.fail2ban">Fail2Ban Configuration</legend>
<!-- Bantime Increment -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="bantimeIncrement" />
<label for="bantimeIncrement" class="form-check-label">Enable Bantime Increment</label>
<label for="bantimeIncrement" class="form-check-label" data-i18n="settings.enable_bantime_increment">Enable Bantime Increment</label>
</div>
<!-- Bantime -->
<div class="mb-3">
<label for="banTime" class="form-label">Default Bantime</label>
<input type="text" class="form-control" id="banTime" placeholder="e.g., 48h" />
<label for="banTime" class="form-label" data-i18n="settings.default_bantime">Default Bantime</label>
<input type="text" class="form-control" id="banTime"
data-i18n-placeholder="settings.default_bantime_placeholder" placeholder="e.g., 48h" />
</div>
<!-- Findtime -->
<div class="mb-3">
<label for="findTime" class="form-label">Default Findtime</label>
<input type="text" class="form-control" id="findTime" placeholder="e.g., 30m" />
<label for="findTime" class="form-label" data-i18n="settings.default_findtime">Default Findtime</label>
<input type="text" class="form-control" id="findTime"
data-i18n-placeholder="settings.default_findtime_placeholder" placeholder="e.g., 30m" />
</div>
<!-- Max Retry -->
<div class="mb-3">
<label for="maxRetry" class="form-label">Default Max Retry</label>
<input type="number" class="form-control" id="maxRetry" placeholder="Enter maximum retries" />
<label for="maxRetry" class="form-label" data-i18n="settings.default_max_retry">Default Max Retry</label>
<input type="number" class="form-control" id="maxRetry"
data-i18n-placeholder="settings.default_max_retry_placeholder" placeholder="Enter maximum retries" />
</div>
<!-- Ignore IPs -->
<div class="mb-3">
<label for="ignoreIP" class="form-label">Ignore IPs</label>
<textarea class="form-control" id="ignoreIP" rows="2" placeholder="IPs to ignore, separated by spaces"></textarea>
<label for="ignoreIP" class="form-label" data-i18n="settings.ignore_ips">Ignore IPs</label>
<textarea class="form-control" id="ignoreIP" rows="2"
data-i18n-placeholder="settings.ignore_ips_placeholder" placeholder="IPs to ignore, separated by spaces"></textarea>
</div>
</fieldset>
<button type="submit" class="btn btn-primary">Save</button>
<button type="submit" class="btn btn-primary" data-i18n="settings.save">Save</button>
</form>
</div>
<!-- ******************************************************************* -->
</div>
<!-- ******************************************************************* -->
<!-- Footer -->
<footer class="text-center mt-4 mb-4">
<!-- Footer -->
<footer class="text-center mt-4 mb-4">
<p class="mb-0">
&copy; <a href="https://swissmakers.ch" target="_blank">Swissmakers GmbH</a>
-
<a href="https://github.com/swissmakers/fail2ban-ui" target="_blank">
GitHub
</a>
<a href="https://github.com/swissmakers/fail2ban-ui" target="_blank">GitHub</a>
</p>
</footer>
</footer>
<!-- ******************************************************************* -->
<!-- APP Components (HTML) : -->
<!-- ******************************************************************* -->
<!-- Loading Overlay -->
<div id="loading-overlay" class="d-flex">
<!-- ******************************************************************* -->
<!-- APP Components (HTML) : -->
<!-- ******************************************************************* -->
<!-- Loading Overlay -->
<div id="loading-overlay" class="d-flex">
<div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span>
<span class="visually-hidden" data-i18n="loading">Loading...</span>
</div>
</div>
</div>
<!-- Jail Config Modal -->
<div class="modal fade" id="jailConfigModal" tabindex="-1" aria-hidden="true">
<!-- Jail Config Modal -->
<div class="modal fade" id="jailConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
Filter Config: <span id="modalJailName"></span>
<span data-i18n="modal.filter_config">Filter Config:</span> <span id="modalJailName"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea id="jailConfigTextarea" class="form-control" rows="15"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveJailConfig()">
Save
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="modal.cancel">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveJailConfig()" data-i18n="modal.save">Save</button>
</div>
</div>
</div>
</div>
<!-- ******************************************************************* -->
</div>
<!-- ******************************************************************* -->
<!-- Bootstrap 5 JS (for modal, etc.) -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js">
</script>
<!-- jQuery (used by Select2) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Select2 JS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
<!-- Bootstrap 5 JS (for modal, etc.) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery (used by Select2) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Select2 JS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
<script>
// For information: We avoid ES6 backticks in our JS, to prevent confusion with the Go template parser.
"use strict";
<script>
// For information: We avoid ES6 backticks in our JS, to prevent confusion with the Go template parser.
"use strict";
//*******************************************************************
//* Init page and main-components : *
//*******************************************************************
//*******************************************************************
//* Init page and main-components : *
//*******************************************************************
// Init and run first function, when DOM is ready
var currentJailForConfig = null;
window.addEventListener('DOMContentLoaded', function() {
var currentJailForConfig = null;
window.addEventListener('DOMContentLoaded', function() {
showLoading(true);
checkReloadNeeded();
fetchSummary().then(function() {
showLoading(false);
initializeTooltips(); // Initialize tooltips after fetching and rendering
getTranslationsSettingsOnPageload();
});
});
});
// Function to initialize Bootstrap tooltips
function initializeTooltips() {
// Function to initialize Bootstrap tooltips
function initializeTooltips() {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
new bootstrap.Tooltip(tooltipTriggerEl);
});
}
}
// Toggle the loading overlay (with !important)
function showLoading(show) {
// Toggle the loading overlay (with !important)
function showLoading(show) {
var overlay = document.getElementById('loading-overlay');
if (show) {
overlay.style.setProperty('display', 'flex', 'important');
} else {
overlay.style.setProperty('display', 'none', 'important');
}
}
}
// Check if there is still a reload of the fail2ban service needed
function checkReloadNeeded() {
// Check if there is still a reload of the fail2ban service needed
function checkReloadNeeded() {
fetch('/api/settings')
.then(res => res.json())
.then(data => {
@@ -534,10 +540,10 @@ function checkReloadNeeded() {
}
})
.catch(err => console.error('Error checking reloadNeeded:', err));
}
}
// Load dynamically the other pages when navigating in nav
function showSection(sectionId) {
// Load dynamically the other pages when navigating in nav
function showSection(sectionId) {
// hide all sections
document.getElementById('dashboardSection').style.display = 'none';
document.getElementById('filterSection').style.display = 'none';
@@ -561,14 +567,14 @@ function showSection(sectionId) {
let navbarToggler = document.querySelector('.navbar-toggler');
navbarToggler.click();
}
}
}
//*******************************************************************
//* Fetch data and render dashboard : *
//*******************************************************************
//*******************************************************************
//* Fetch data and render dashboard : *
//*******************************************************************
// Fetch summary (jails, stats, last bans)
function fetchSummary() {
// Fetch summary (jails, stats, last bans)
function fetchSummary() {
return fetch('/api/summary')
.then(function(res) { return res.json(); })
.then(function(data) {
@@ -583,35 +589,35 @@ function fetchSummary() {
document.getElementById('dashboard').innerHTML =
'<div class="alert alert-danger">Error: ' + err + '</div>';
});
}
}
// Render the main dashboard
function renderDashboard(data) {
// Render the main dashboard
function renderDashboard(data) {
var html = "";
// Add a search bar
html += `
<fieldset class="border p-3 rounded mb-4">
<legend class="w-auto px-2"><span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The Overview displays the currently enabled jails that you have added to your jail.local configuration.">Overview active Jails and Blocks</span></legend>
<legend class="w-auto px-2"><span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-original-title="The Overview displays the currently enabled jails that you have added to your jail.local configuration." data-i18n="dashboard.overview">Overview active Jails and Blocks</span></legend>
<div class="mb-3">
<label for="ipSearch" class="form-label">Search Banned IPs</label>
<input type="text" id="ipSearch" class="form-control" placeholder="Enter IP address to search" onkeyup="filterIPs()">
<label for="ipSearch" class="form-label" data-i18n="dashboard.search_label">Search Banned IPs</label>
<input type="text" id="ipSearch" class="form-control" placeholder="Enter IP address to search" data-i18n-placeholder="dashboard.search_placeholder" onkeyup="filterIPs()">
</div>
`;
// Jails table
if (!data.jails || data.jails.length === 0) {
html += '<p>No jails found.</p>';
html += '<p data-i18n="dashboard.no_jails">No jails found.</p>';
} else {
html += ''
+ '<div class="table-responsive">'
+ '<table class="table table-striped" id="jailsTable">'
+ ' <thead>'
+ ' <tr>'
+ ' <th>Jail Name</th>'
+ ' <th>Total Banned</th>'
+ ' <th>New Last Hour</th>'
+ ' <th>Banned IPs (Unban)</th>'
+ ' <th data-i18n="dashboard.table.jail_name">Jail Name</th>'
+ ' <th data-i18n="dashboard.table.total_banned">Total Banned</th>'
+ ' <th data-i18n="dashboard.table.new_last_hour">New Last Hour</th>'
+ ' <th data-i18n="dashboard.table.banned_ips">Banned IPs (Unban)</th>'
+ ' </tr>'
+ ' </thead>'
+ ' <tbody>';
@@ -637,19 +643,19 @@ function renderDashboard(data) {
// Last 5 bans
html += '<fieldset class="border p-3 rounded mb-4">';
html += ' <legend class="w-auto px-2">Last 5 Ban Events</legend>';
html += ' <legend class="w-auto px-2" data-i18n="dashboard.last_bans">Last 5 Ban Events</legend>';
if (!data.lastBans || data.lastBans.length === 0) {
html += '<p>No recent bans found.</p>';
html += '<p data-i18n="dashboard.no_recent_bans">No recent bans found.</p>';
} else {
html += ''
+ '<div class="table-responsive">'
+ '<table class="table table-bordered">'
+ ' <thead>'
+ ' <tr>'
+ ' <th>Time</th>'
+ ' <th>Jail</th>'
+ ' <th>IP</th>'
+ ' <th>Log Line</th>'
+ ' <th data-i18n="dashboard.table.time">Time</th>'
+ ' <th data-i18n="dashboard.table.jail">Jail</th>'
+ ' <th data-i18n="dashboard.table.ip">IP</th>'
+ ' <th data-i18n="dashboard.table.log_line">Log Line</th>'
+ ' </tr>'
+ ' </thead>'
+ ' <tbody></div>';
@@ -668,12 +674,12 @@ function renderDashboard(data) {
}
document.getElementById('dashboard').innerHTML = html;
}
}
// Render banned IPs with "Unban" button
function renderBannedIPs(jailName, ips) {
// Render banned IPs with "Unban" button
function renderBannedIPs(jailName, ips) {
if (!ips || ips.length === 0) {
return '<em>No banned IPs</em>';
return '<em data-i18n="dashboard.no_banned_ips">No banned IPs</em>';
}
var content = '<ul class="list-unstyled mb-0">';
ips.forEach(function(ip) {
@@ -682,53 +688,48 @@ function renderBannedIPs(jailName, ips) {
+ ' <span class="me-auto">' + ip + '</span>'
+ ' <button class="btn btn-sm btn-warning"'
+ ' onclick="unbanIP(\'' + jailName + '\', \'' + ip + '\')">'
+ ' Unban'
+ ' <span data-i18n="dashboard.unban">Unban</span>'
+ ' </button>'
+ '</li>';
});
content += '</ul>';
return content;
}
}
// Filter IPs on dashboard table
function filterIPs() {
const query = document.getElementById("ipSearch").value.toLowerCase(); // Get the search query
const rows = document.querySelectorAll("#jailsTable .jail-row"); // Get all jail rows
// Filter IPs on dashboard table
function filterIPs() {
const query = document.getElementById("ipSearch").value.toLowerCase();
const rows = document.querySelectorAll("#jailsTable .jail-row");
rows.forEach((row) => {
const ipSpans = row.querySelectorAll("ul li span"); // Find all IP span elements in this row
let matchFound = false; // Reset match flag for the row
const ipSpans = row.querySelectorAll("ul li span");
let matchFound = false;
ipSpans.forEach((span) => {
const originalText = span.textContent; // The full original text
const originalText = span.textContent;
const ipText = originalText.toLowerCase();
if (query && ipText.includes(query)) {
matchFound = true; // Match found in this row
// Highlight the matching part
matchFound = true;
const highlightedText = originalText.replace(
new RegExp(query, "gi"), // Case-insensitive match
(match) => `<mark>${match}</mark>` // Wrap match in <mark>
new RegExp(query, "gi"),
(match) => `<mark>${match}</mark>`
);
span.innerHTML = highlightedText; // Update span's HTML with highlighting
span.innerHTML = highlightedText;
} else {
// Remove highlighting if no match or search is cleared
span.innerHTML = originalText;
}
});
// Show the row if a match is found or the query is empty
row.style.display = matchFound || !query ? "" : "none";
});
}
}
//*******************************************************************
//* Functions to manage IP-bans : *
//*******************************************************************
//*******************************************************************
//* Functions to manage IP-bans : *
//*******************************************************************
// Unban IP
function unbanIP(jail, ip) {
function unbanIP(jail, ip) {
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
return;
}
@@ -749,14 +750,13 @@ function unbanIP(jail, ip) {
.finally(function() {
showLoading(false);
});
}
}
//*******************************************************************
//* Filter-mod and config-mod actions : *
//*******************************************************************
//*******************************************************************
//* Filter-mod and config-mod actions : *
//*******************************************************************
// Open jail/filter modal and load filter-config
function openJailConfigModal(jailName) {
function openJailConfigModal(jailName) {
currentJailForConfig = jailName;
var textArea = document.getElementById('jailConfigTextarea');
textArea.value = '';
@@ -782,10 +782,9 @@ function openJailConfigModal(jailName) {
.finally(function() {
showLoading(false);
});
}
}
// Save filter config for the current opened jail
function saveJailConfig() {
function saveJailConfig() {
if (!currentJailForConfig) return;
showLoading(true);
@@ -800,13 +799,10 @@ function saveJailConfig() {
if (data.error) {
alert("Error saving config: " + data.error);
} else {
//alert(data.message || "Config saved");
console.log("Filter saved successfully. Reload needed? " + data.reloadNeeded);
// Hide modal
var modalEl = document.getElementById('jailConfigModal');
var modalObj = bootstrap.Modal.getInstance(modalEl);
modalObj.hide();
// Show the reload banner
document.getElementById('reloadBanner').style.display = 'block';
}
})
@@ -816,28 +812,27 @@ function saveJailConfig() {
.finally(function() {
showLoading(false);
});
}
}
// Load current settings when opening settings page
function loadSettings() {
//*******************************************************************
//* Load current settings when opening settings page : *
//*******************************************************************
function loadSettings() {
showLoading(true);
fetch('/api/settings')
.then(res => res.json())
.then(data => {
// Get current general settings
document.getElementById('languageSelect').value = data.language || 'en';
document.getElementById('debugMode').checked = data.debug || false;
// Get Alert settings
document.getElementById('destEmail').value = data.destemail || '';
// Get Alert countries selection
const select = document.getElementById('alertCountries');
for (let i = 0; i < select.options.length; i++) {
select.options[i].selected = false;
}
if (!data.alertCountries || data.alertCountries.length === 0) {
// default to "ALL"
select.options[0].selected = true;
} else {
for (let i = 0; i < select.options.length; i++) {
@@ -847,10 +842,8 @@ function loadSettings() {
}
}
}
// Update Select2 UI
$('#alertCountries').trigger('change');
// Get SMTP settings
if (data.smtp) {
document.getElementById('smtpHost').value = data.smtp.host || '';
document.getElementById('smtpPort').value = data.smtp.port || 587;
@@ -860,7 +853,6 @@ function loadSettings() {
document.getElementById('smtpUseTLS').checked = data.smtp.useTLS || false;
}
// Get current Fail2Ban settings
document.getElementById('bantimeIncrement').checked = data.bantimeIncrement || false;
document.getElementById('banTime').value = data.bantime || '';
document.getElementById('findTime').value = data.findtime || '';
@@ -871,12 +863,14 @@ function loadSettings() {
alert('Error loading settings: ' + err);
})
.finally(() => showLoading(false));
}
}
// Save settings when hitting the save button
function saveSettings(event) {
event.preventDefault(); // prevent form submission
//*******************************************************************
//* Save settings when hitting the save button : *
//*******************************************************************
function saveSettings(event) {
event.preventDefault();
showLoading(true);
const smtpSettings = {
@@ -895,15 +889,12 @@ function saveSettings(event) {
debug: document.getElementById('debugMode').checked,
destemail: document.getElementById('destEmail').value.trim(),
alertCountries: selectedCountries.length > 0 ? selectedCountries : ["ALL"],
bantimeIncrement: document.getElementById('bantimeIncrement').checked,
bantime: document.getElementById('banTime').value.trim(),
findtime: document.getElementById('findTime').value.trim(),
maxretry: parseInt(document.getElementById('maxRetry').value, 10) || 3,
ignoreip: document.getElementById('ignoreIP').value.trim(),
smtp: smtpSettings // (Includes SMTP settings)
smtp: smtpSettings
};
fetch('/api/settings', {
@@ -916,6 +907,8 @@ function saveSettings(event) {
if (data.error) {
alert('Error saving settings: ' + data.error + data.details);
} else {
var selectedLang = $('#languageSelect').val();
loadTranslations(selectedLang);
console.log("Settings saved successfully. Reload needed? " + data.reloadNeeded);
if (data.reloadNeeded) {
document.getElementById('reloadBanner').style.display = 'block';
@@ -924,10 +917,13 @@ function saveSettings(event) {
})
.catch(err => alert('Error: ' + err))
.finally(() => showLoading(false));
}
}
// Load the list of filters from /api/filters
function loadFilters() {
//*******************************************************************
//* Load the list of filters from /api/filters : *
//*******************************************************************
function loadFilters() {
showLoading(true);
fetch('/api/filters')
.then(res => res.json())
@@ -937,7 +933,7 @@ function loadFilters() {
return;
}
const select = document.getElementById('filterSelect');
select.innerHTML = ''; // clear existing
select.innerHTML = '';
if (!data.filters || data.filters.length === 0) {
const opt = document.createElement('option');
opt.value = '';
@@ -956,9 +952,9 @@ function loadFilters() {
alert('Error loading filters: ' + err);
})
.finally(() => showLoading(false));
}
}
function sendTestEmail() {
function sendTestEmail() {
showLoading(true);
fetch('/api/settings/test-email', {
@@ -975,10 +971,10 @@ function sendTestEmail() {
})
.catch(error => alert('Error: ' + error))
.finally(() => showLoading(false));
}
}
// Called when clicking "Test Filter" button
function testSelectedFilter() {
// Called when clicking "Test Filter" button
function testSelectedFilter() {
const filterName = document.getElementById('filterSelect').value;
const lines = document.getElementById('logLinesTextarea').value.split('\n');
@@ -1008,12 +1004,12 @@ function testSelectedFilter() {
alert('Error: ' + err);
})
.finally(() => showLoading(false));
}
}
function renderTestResults(matches) {
let html = '<h5>Test Results</h5>';
function renderTestResults(matches) {
let html = '<h5 data-i18n="filter_debug.test_results_title">Test Results</h5>';
if (!matches || matches.length === 0) {
html += '<p>No matches found.</p>';
html += '<p data-i18n="filter_debug.no_matches">No matches found.</p>';
} else {
html += '<ul>';
matches.forEach(m => {
@@ -1022,21 +1018,20 @@ function renderTestResults(matches) {
html += '</ul>';
}
document.getElementById('testResults').innerHTML = html;
}
}
// When showing the filter section
function showFilterSection() {
loadFilters(); // fetch the filter list
// When showing the filter section
function showFilterSection() {
loadFilters();
document.getElementById('testResults').innerHTML = '';
document.getElementById('logLinesTextarea').value = '';
}
}
//*******************************************************************
//* Reload fail2ban action : *
//*******************************************************************
//*******************************************************************
//* Reload fail2ban action : *
//*******************************************************************
// Reload Fail2ban
function reloadFail2ban() {
function reloadFail2ban() {
if (!confirm("It can happen that some logs are not parsed during the reload of fail2ban. Reload Fail2ban now?")) return;
showLoading(true);
fetch('/api/fail2ban/reload', { method: 'POST' })
@@ -1045,9 +1040,7 @@ function reloadFail2ban() {
if (data.error) {
alert("Error: " + data.error);
} else {
// Hide reload banner
document.getElementById('reloadBanner').style.display = 'none';
// Refresh data
return fetchSummary();
}
})
@@ -1057,21 +1050,19 @@ function reloadFail2ban() {
.finally(function() {
showLoading(false);
});
}
}
//*******************************************************************
//* Is executed when doc-ready : *
//*******************************************************************
//*******************************************************************
//* Is executed when doc-ready : *
//*******************************************************************
$(document).ready(function() {
// Initialize the Select2 widget with a placeholder and full width.
$('#alertCountries').select2({
placeholder: 'Select countries..',
allowClear: true,
width: '100%'
});
// Custom logic: If "ALL" is selected, remove other selections.
$('#alertCountries').on('select2:select', function(e) {
var selectedValue = e.params.data.id;
var currentValues = $('#alertCountries').val() || [];
@@ -1089,6 +1080,57 @@ function reloadFail2ban() {
}
});
});
</script>
//*******************************************************************
//* Translation Related Functions : *
//*******************************************************************
var translations = {};
// Loads translation JSON file for given language (e.g., en, de, etc.)
function loadTranslations(lang) {
$.getJSON('/locales/' + lang + '.json')
.done(function(data) {
translations = data;
updateTranslations();
})
.fail(function() {
console.error('Failed to load translations for language:', lang);
});
}
// Updates all elements with data-i18n attribute with corresponding translation.
function updateTranslations() {
$('[data-i18n]').each(function() {
var key = $(this).data('i18n');
if (translations[key]) {
$(this).text(translations[key]);
}
});
// Updates placeholders.
$('[data-i18n-placeholder]').each(function() {
var key = $(this).data('i18n-placeholder');
if (translations[key]) {
$(this).attr('placeholder', translations[key]);
}
});
}
function getTranslationsSettingsOnPageload() {
// Fetch settings to get the current language preference
fetch('/api/settings')
.then(function(res) { return res.json(); })
.then(function(data) {
var lang = data.language || 'en'; // Use the language from settings or default to "en"
$('#languageSelect').val(lang); // Update the language dropdown accordingly
loadTranslations(lang); // Load the appropriate translation file
})
.catch(function(err) {
console.error('Error loading initial settings:', err);
// In case of an error, fallback to English
loadTranslations('en');
});
}
</script>
</body>
</html>