Implemented a real-time console log streaming via WebSocket for debugging purposes. Users can enable console output in settings to view application logs directly in the web interface.

This commit is contained in:
2026-01-14 21:47:17 +01:00
parent e997059e2a
commit 44da16977c
16 changed files with 501 additions and 15 deletions

View File

@@ -82,6 +82,9 @@ type AppSettings struct {
// Email alert preferences
EmailAlertsForBans bool `json:"emailAlertsForBans"` // Enable email alerts for ban events (default: true)
EmailAlertsForUnbans bool `json:"emailAlertsForUnbans"` // Enable email alerts for unban events (default: false)
// Console output preferences
ConsoleOutput bool `json:"consoleOutput"` // Enable console output in web UI (default: false)
}
type AdvancedActionsConfig struct {
@@ -425,6 +428,7 @@ func applyAppSettingsRecordLocked(rec storage.AppSettingsRecord) {
currentSettings.CallbackSecret = rec.CallbackSecret
currentSettings.EmailAlertsForBans = rec.EmailAlertsForBans
currentSettings.EmailAlertsForUnbans = rec.EmailAlertsForUnbans
currentSettings.ConsoleOutput = rec.ConsoleOutput
}
func applyServerRecordsLocked(records []storage.ServerRecord) {
@@ -511,6 +515,8 @@ func toAppSettingsRecordLocked() (storage.AppSettingsRecord, error) {
GeoIPProvider: currentSettings.GeoIPProvider,
GeoIPDatabasePath: currentSettings.GeoIPDatabasePath,
MaxLogLines: currentSettings.MaxLogLines,
// Console output settings
ConsoleOutput: currentSettings.ConsoleOutput,
}, nil
}
@@ -1529,9 +1535,31 @@ func UpdateSettings(new AppSettings) (AppSettings, error) {
}
DebugLog("New settings applied: %v", currentSettings) // Log settings applied
// Update console log enabled state if it changed
if old.ConsoleOutput != new.ConsoleOutput {
// Import web package to update console log state
// We'll handle this via a callback or direct call
updateConsoleLogState(new.ConsoleOutput)
}
if err := persistAllLocked(); err != nil {
fmt.Println("Error saving settings:", err)
return currentSettings, err
}
return currentSettings, nil
}
// updateConsoleLogState updates the console log writer enabled state
// This is called from UpdateSettings when console output setting changes
var updateConsoleLogStateFunc func(bool)
// SetUpdateConsoleLogStateFunc sets the callback function to update console log state
func SetUpdateConsoleLogStateFunc(fn func(bool)) {
updateConsoleLogStateFunc = fn
}
func updateConsoleLogState(enabled bool) {
if updateConsoleLogStateFunc != nil {
updateConsoleLogStateFunc(enabled)
}
}

View File

@@ -115,6 +115,10 @@
"settings.port_env_hint": "Um den Port über die Weboberfläche zu ändern, entfernen Sie die PORT-Umgebungsvariable und starten Sie den Container neu.",
"settings.port_restart_hint": "⚠️ Port-Änderungen erfordern einen Neustart des Containers, um wirksam zu werden.",
"settings.enable_debug": "Debug-Protokoll aktivieren",
"settings.enable_console": "Konsolenausgabe aktivieren",
"settings.console.title": "Konsolenausgabe",
"settings.console.clear": "Löschen",
"settings.console.save_hint": "Bitte speichern Sie zuerst Ihre Einstellungen, bevor hier Logs angezeigt werden.",
"settings.alert": "Alarm-Einstellungen",
"settings.callback_url": "Fail2ban Callback-URL",
"settings.callback_url_placeholder": "http://127.0.0.1:8080",

View File

@@ -115,6 +115,10 @@
"settings.port_env_hint": "Um de Port über d Weboberflächi z ändere, entferne d PORT-Umgebigsvariable und start de Container neu.",
"settings.port_restart_hint": "⚠️ Port-Änderige erfordere ä Neustart vom Container, zum wirksam z werde.",
"settings.enable_debug": "Debug-Modus aktivierä",
"settings.enable_console": "Konsolenusgab aktivierä",
"settings.console.title": "Konsolenusgab",
"settings.console.clear": "Löschä",
"settings.console.save_hint": "Bitte speichere zerscht dini Istellige, bevor hiä Logs azeigt wärde.",
"settings.alert": "Alarm-Istellige",
"settings.callback_url": "Fail2ban Callback-URL",
"settings.callback_url_placeholder": "http://127.0.0.1:8080",

View File

@@ -115,6 +115,10 @@
"settings.port_env_hint": "To change the port via Web UI, remove the PORT environment variable and restart the container.",
"settings.port_restart_hint": "⚠️ Port changes require a container restart to take effect.",
"settings.enable_debug": "Enable Debug Log",
"settings.enable_console": "Enable Console Output",
"settings.console.title": "Console Output",
"settings.console.clear": "Clear",
"settings.console.save_hint": "Please save your settings first before logs will be displayed here.",
"settings.alert": "Alert Settings",
"settings.callback_url": "Fail2ban Callback URL",
"settings.callback_url_placeholder": "http://127.0.0.1:8080",

View File

@@ -115,6 +115,10 @@
"settings.port_env_hint": "Para cambiar el puerto mediante la interfaz web, elimine la variable de entorno PORT y reinicie el contenedor.",
"settings.port_restart_hint": "⚠️ Los cambios de puerto requieren un reinicio del contenedor para surtir efecto.",
"settings.enable_debug": "Habilitar el modo de depuración",
"settings.enable_console": "Habilitar salida de consola",
"settings.console.title": "Salida de consola",
"settings.console.clear": "Limpiar",
"settings.console.save_hint": "Por favor, guarde primero su configuración antes de que se muestren los registros aquí.",
"settings.alert": "Configuración de alertas",
"settings.callback_url": "URL de retorno de Fail2ban",
"settings.callback_url_placeholder": "http://127.0.0.1:8080",

View File

@@ -115,6 +115,10 @@
"settings.port_env_hint": "Pour modifier le port via l'interface Web, supprimez la variable d'environnement PORT et redémarrez le conteneur.",
"settings.port_restart_hint": "⚠️ Les modifications du port nécessitent un redémarrage du conteneur pour prendre effet.",
"settings.enable_debug": "Activer le mode débogage",
"settings.enable_console": "Activer la sortie console",
"settings.console.title": "Sortie console",
"settings.console.clear": "Effacer",
"settings.console.save_hint": "Veuillez d'abord enregistrer vos paramètres avant que les journaux ne s'affichent ici.",
"settings.alert": "Paramètres d'alerte",
"settings.callback_url": "URL de rappel Fail2ban",
"settings.callback_url_placeholder": "http://127.0.0.1:8080",

View File

@@ -115,6 +115,10 @@
"settings.port_env_hint": "Per modificare la porta tramite l'interfaccia Web, rimuovere la variabile d'ambiente PORT e riavviare il contenitore.",
"settings.port_restart_hint": "⚠️ Le modifiche alla porta richiedono un riavvio del contenitore per avere effetto.",
"settings.enable_debug": "Abilita debug",
"settings.enable_console": "Abilita output console",
"settings.console.title": "Output console",
"settings.console.clear": "Pulisci",
"settings.console.save_hint": "Si prega di salvare prima le impostazioni prima che i log vengano visualizzati qui.",
"settings.alert": "Impostazioni di allarme",
"settings.callback_url": "URL di callback Fail2ban",
"settings.callback_url_placeholder": "http://127.0.0.1:8080",

View File

@@ -60,6 +60,8 @@ type AppSettingsRecord struct {
AlertCountriesJSON string
EmailAlertsForBans bool
EmailAlertsForUnbans bool
// Console output settings
ConsoleOutput bool
// SMTP settings
SMTPHost string
SMTPPort int
@@ -190,17 +192,17 @@ func GetAppSettings(ctx context.Context) (AppSettingsRecord, bool, error) {
}
row := db.QueryRowContext(ctx, `
SELECT language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines
SELECT language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines, console_output
FROM app_settings
WHERE id = 1`)
var (
lang, callback, callbackSecret, alerts, smtpHost, smtpUser, smtpPass, smtpFrom, ignoreIP, bantime, findtime, destemail, banaction, banactionAllports, advancedActions, geoipProvider, geoipDatabasePath sql.NullString
port, smtpPort, maxretry, maxLogLines sql.NullInt64
debug, restartNeeded, smtpTLS, bantimeInc, defaultJailEn, emailAlertsForBans, emailAlertsForUnbans sql.NullInt64
debug, restartNeeded, smtpTLS, bantimeInc, defaultJailEn, emailAlertsForBans, emailAlertsForUnbans, consoleOutput sql.NullInt64
)
err := row.Scan(&lang, &port, &debug, &restartNeeded, &callback, &callbackSecret, &alerts, &emailAlertsForBans, &emailAlertsForUnbans, &smtpHost, &smtpPort, &smtpUser, &smtpPass, &smtpFrom, &smtpTLS, &bantimeInc, &defaultJailEn, &ignoreIP, &bantime, &findtime, &maxretry, &destemail, &banaction, &banactionAllports, &advancedActions, &geoipProvider, &geoipDatabasePath, &maxLogLines)
err := row.Scan(&lang, &port, &debug, &restartNeeded, &callback, &callbackSecret, &alerts, &emailAlertsForBans, &emailAlertsForUnbans, &smtpHost, &smtpPort, &smtpUser, &smtpPass, &smtpFrom, &smtpTLS, &bantimeInc, &defaultJailEn, &ignoreIP, &bantime, &findtime, &maxretry, &destemail, &banaction, &banactionAllports, &advancedActions, &geoipProvider, &geoipDatabasePath, &maxLogLines, &consoleOutput)
if errors.Is(err, sql.ErrNoRows) {
return AppSettingsRecord{}, false, nil
}
@@ -243,6 +245,8 @@ WHERE id = 1`)
GeoIPProvider: stringFromNull(geoipProvider),
GeoIPDatabasePath: stringFromNull(geoipDatabasePath),
MaxLogLines: intFromNull(maxLogLines),
// Console output settings
ConsoleOutput: intToBool(intFromNull(consoleOutput)),
}
return rec, true, nil
@@ -254,9 +258,9 @@ func SaveAppSettings(ctx context.Context, rec AppSettingsRecord) error {
}
_, err := db.ExecContext(ctx, `
INSERT INTO app_settings (
id, language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines
id, language, port, debug, restart_needed, callback_url, callback_secret, alert_countries, email_alerts_for_bans, email_alerts_for_unbans, smtp_host, smtp_port, smtp_username, smtp_password, smtp_from, smtp_use_tls, bantime_increment, default_jail_enable, ignore_ip, bantime, findtime, maxretry, destemail, banaction, banaction_allports, advanced_actions, geoip_provider, geoip_database_path, max_log_lines, console_output
) VALUES (
1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
) ON CONFLICT(id) DO UPDATE SET
language = excluded.language,
port = excluded.port,
@@ -285,7 +289,8 @@ INSERT INTO app_settings (
advanced_actions = excluded.advanced_actions,
geoip_provider = excluded.geoip_provider,
geoip_database_path = excluded.geoip_database_path,
max_log_lines = excluded.max_log_lines
max_log_lines = excluded.max_log_lines,
console_output = excluded.console_output
`, rec.Language,
rec.Port,
boolToInt(rec.Debug),
@@ -314,7 +319,7 @@ INSERT INTO app_settings (
rec.GeoIPProvider,
rec.GeoIPDatabasePath,
rec.MaxLogLines,
)
boolToInt(rec.ConsoleOutput))
return err
}
@@ -861,7 +866,9 @@ CREATE TABLE IF NOT EXISTS app_settings (
advanced_actions TEXT,
geoip_provider TEXT,
geoip_database_path TEXT,
max_log_lines INTEGER
max_log_lines INTEGER,
-- Console output settings
console_output INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS servers (
@@ -933,6 +940,13 @@ CREATE INDEX IF NOT EXISTS idx_perm_blocks_status ON permanent_blocks(status);
// return err
// }
// }
// Migration: Add console_output column if it doesn't exist
if _, err := db.ExecContext(ctx, `ALTER TABLE app_settings ADD COLUMN console_output INTEGER DEFAULT 0`); err != nil {
if err != nil && !strings.Contains(strings.ToLower(err.Error()), "duplicate column name") {
return err
}
}
_ = strings.Contains // Keep strings import for migration example above
return nil