mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-17 05:53:15 +02:00
Merge pull request #10 from swissmakers/dev
Refactor WebUI to TailwindCSS, Improve Logging & Debugging, and Various Fixes
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# =========================================
|
# =========================================
|
||||||
# STAGE 1: Build Fail2Ban UI Binary
|
# STAGE 1: Build Fail2Ban UI Binary
|
||||||
# =========================================
|
# =========================================
|
||||||
FROM golang:1.22.9 AS builder
|
FROM golang:1.23 AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ WORKDIR /config
|
|||||||
COPY --from=builder /app/fail2ban-ui /app/fail2ban-ui
|
COPY --from=builder /app/fail2ban-ui /app/fail2ban-ui
|
||||||
RUN chown fail2ban:0 /app/fail2ban-ui && chmod +x /app/fail2ban-ui
|
RUN chown fail2ban:0 /app/fail2ban-ui && chmod +x /app/fail2ban-ui
|
||||||
COPY --from=builder /app/pkg/web/templates /app/templates
|
COPY --from=builder /app/pkg/web/templates /app/templates
|
||||||
|
COPY --from=builder /app/internal/locales /app/locales
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV CONTAINER=true
|
ENV CONTAINER=true
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
// Fail2ban UI - A Swiss made, management interface for Fail2ban.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025 Swissmakers GmbH (https://swissmakers.ch)
|
||||||
|
//
|
||||||
|
// Licensed under the GNU General Public License, Version 3 (GPL-3.0)
|
||||||
|
// You may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -32,12 +48,14 @@ func main() {
|
|||||||
if container {
|
if container {
|
||||||
// In container, templates are assumed to be in /app/templates
|
// In container, templates are assumed to be in /app/templates
|
||||||
router.LoadHTMLGlob("/app/templates/*")
|
router.LoadHTMLGlob("/app/templates/*")
|
||||||
|
router.Static("/locales", "/app/locales")
|
||||||
} else {
|
} else {
|
||||||
// When running locally, load templates from pkg/web/templates
|
// When running locally, load templates from pkg/web/templates
|
||||||
router.LoadHTMLGlob("pkg/web/templates/*")
|
router.LoadHTMLGlob("pkg/web/templates/*")
|
||||||
|
router.Static("/locales", "./internal/locales")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register all application routes, including the static file serving route for locales.
|
// Register all application routes, including the static files and templates.
|
||||||
web.RegisterRoutes(router)
|
web.RegisterRoutes(router)
|
||||||
|
|
||||||
printWelcomeBanner(serverPort)
|
printWelcomeBanner(serverPort)
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -4,6 +4,7 @@ go 1.23
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/go-playground/validator/v10 v10.26.0
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +16,6 @@ require (
|
|||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
@@ -27,10 +27,10 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.13.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.4 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -21,8 +21,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
|||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
@@ -67,15 +67,15 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
|||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||||
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||||
|
|||||||
@@ -191,6 +191,8 @@ func initializeFromJailFile() error {
|
|||||||
|
|
||||||
// initializeFail2banAction writes a custom action configuration for Fail2ban to use AlertCountries.
|
// initializeFail2banAction writes a custom action configuration for Fail2ban to use AlertCountries.
|
||||||
func initializeFail2banAction() error {
|
func initializeFail2banAction() error {
|
||||||
|
DebugLog("----------------------------")
|
||||||
|
DebugLog("Running initial initializeFail2banAction()") // entry point
|
||||||
// Ensure the jail.local is configured correctly
|
// Ensure the jail.local is configured correctly
|
||||||
if err := setupGeoCustomAction(); err != nil {
|
if err := setupGeoCustomAction(); err != nil {
|
||||||
fmt.Println("Error setup GeoCustomAction in jail.local:", err)
|
fmt.Println("Error setup GeoCustomAction in jail.local:", err)
|
||||||
@@ -205,6 +207,7 @@ func initializeFail2banAction() error {
|
|||||||
|
|
||||||
// setupGeoCustomAction checks and replaces the default action in jail.local with our from fail2ban-UI
|
// setupGeoCustomAction checks and replaces the default action in jail.local with our from fail2ban-UI
|
||||||
func setupGeoCustomAction() error {
|
func setupGeoCustomAction() error {
|
||||||
|
DebugLog("Running initial setupGeoCustomAction()") // entry point
|
||||||
file, err := os.Open(jailFile)
|
file, err := os.Open(jailFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Fallback: Copy default file if jail.local is not found
|
// Fallback: Copy default file if jail.local is not found
|
||||||
@@ -284,6 +287,7 @@ func copyFile(src, dst string) error {
|
|||||||
|
|
||||||
// ensureJailDConfig checks if the jail.d file exists and creates it if necessary
|
// ensureJailDConfig checks if the jail.d file exists and creates it if necessary
|
||||||
func ensureJailDConfig() error {
|
func ensureJailDConfig() error {
|
||||||
|
DebugLog("Running initial ensureJailDConfig()") // entry point
|
||||||
// Check if the file already exists
|
// Check if the file already exists
|
||||||
if _, err := os.Stat(jailDFile); err == nil {
|
if _, err := os.Stat(jailDFile); err == nil {
|
||||||
// File already exists, do nothing
|
// File already exists, do nothing
|
||||||
@@ -310,6 +314,8 @@ action_mwlg = %(action_)s
|
|||||||
|
|
||||||
// writeFail2banAction creates or updates the action file with the AlertCountries.
|
// writeFail2banAction creates or updates the action file with the AlertCountries.
|
||||||
func writeFail2banAction() error {
|
func writeFail2banAction() error {
|
||||||
|
DebugLog("Running initial writeFail2banAction()") // entry point
|
||||||
|
DebugLog("----------------------------")
|
||||||
// Define the Fail2Ban action file content
|
// Define the Fail2Ban action file content
|
||||||
actionConfig := `[INCLUDES]
|
actionConfig := `[INCLUDES]
|
||||||
|
|
||||||
@@ -344,8 +350,8 @@ name = default
|
|||||||
logpath = /dev/null
|
logpath = /dev/null
|
||||||
|
|
||||||
# Number of log lines to include in the email
|
# Number of log lines to include in the email
|
||||||
# grepmax = 1000
|
grepmax = 200
|
||||||
# grepopts = -m <grepmax>`
|
grepopts = -m <grepmax>`
|
||||||
|
|
||||||
// Write the action file
|
// Write the action file
|
||||||
err := os.WriteFile(actionFile, []byte(actionConfig), 0644)
|
err := os.WriteFile(actionFile, []byte(actionConfig), 0644)
|
||||||
@@ -395,7 +401,7 @@ func saveSettings() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
DebugLog("Error writing to file: %v", err) // Debug
|
DebugLog("Error writing to file: %v", err) // Debug
|
||||||
}
|
}
|
||||||
// Update the Fail2ban action file
|
// Write again the Fail2ban-UI action file (in the future not used anymore)
|
||||||
return writeFail2banAction()
|
return writeFail2banAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,8 +445,6 @@ func UpdateSettings(new AppSettings) (AppSettings, error) {
|
|||||||
old.Bantime != new.Bantime ||
|
old.Bantime != new.Bantime ||
|
||||||
old.Findtime != new.Findtime ||
|
old.Findtime != new.Findtime ||
|
||||||
//old.Maxretry != new.Maxretry ||
|
//old.Maxretry != new.Maxretry ||
|
||||||
old.Destemail != new.Destemail ||
|
|
||||||
//old.Sender != new.Sender {
|
|
||||||
old.Maxretry != new.Maxretry {
|
old.Maxretry != new.Maxretry {
|
||||||
new.RestartNeeded = true
|
new.RestartNeeded = true
|
||||||
} else {
|
} else {
|
||||||
@@ -448,11 +452,6 @@ func UpdateSettings(new AppSettings) (AppSettings, error) {
|
|||||||
new.RestartNeeded = new.RestartNeeded || old.RestartNeeded
|
new.RestartNeeded = new.RestartNeeded || old.RestartNeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
// Countries change? Currently also requires a reload
|
|
||||||
if !equalStringSlices(old.AlertCountries, new.AlertCountries) {
|
|
||||||
new.RestartNeeded = true
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSettings = new
|
currentSettings = new
|
||||||
DebugLog("New settings applied: %v", currentSettings) // Log settings applied
|
DebugLog("New settings applied: %v", currentSettings) // Log settings applied
|
||||||
|
|
||||||
@@ -464,19 +463,3 @@ func UpdateSettings(new AppSettings) (AppSettings, error) {
|
|||||||
fmt.Println("Settings saved to file successfully") // Log save success
|
fmt.Println("Settings saved to file successfully") // Log save success
|
||||||
return currentSettings, nil
|
return currentSettings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func equalStringSlices(a, b []string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
m := make(map[string]bool)
|
|
||||||
for _, x := range a {
|
|
||||||
m[x] = false
|
|
||||||
}
|
|
||||||
for _, x := range b {
|
|
||||||
if _, ok := m[x]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package fail2ban
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -152,6 +153,11 @@ func ReloadFail2ban() error {
|
|||||||
|
|
||||||
// RestartFail2ban restarts the Fail2ban service.
|
// RestartFail2ban restarts the Fail2ban service.
|
||||||
func RestartFail2ban() error {
|
func RestartFail2ban() error {
|
||||||
|
|
||||||
|
// Check if running inside a container.
|
||||||
|
if _, container := os.LookupEnv("CONTAINER"); container {
|
||||||
|
return fmt.Errorf("restart not supported inside container; please restart fail2ban on the host")
|
||||||
|
}
|
||||||
cmd := "systemctl restart fail2ban"
|
cmd := "systemctl restart fail2ban"
|
||||||
out, err := execCommand(cmd)
|
out, err := execCommand(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"nav.dashboard": "Dashboard",
|
"nav.dashboard": "Dashboard",
|
||||||
"nav.filter_debug": "Filter-Debug",
|
"nav.filter_debug": "Filter-Debug",
|
||||||
"nav.settings": "Einstellungen",
|
"nav.settings": "Einstellungen",
|
||||||
"restart_banner.message": "Fail2ban Konfiguration geändert! Um Änderungen zu übernehmen bitte: ",
|
"restart_banner.message": "Fail2ban Konfiguration geändert. Um Änderungen zu übernehmen bitte ",
|
||||||
"restart_banner.button": "Service neu starten",
|
"restart_banner.button": "Service neu starten",
|
||||||
"dashboard.title": "Dashboard",
|
"dashboard.title": "Dashboard",
|
||||||
"dashboard.overview": "Aktive Jails und Blocks Übersicht",
|
"dashboard.overview": "Aktive Jails und Blocks Übersicht",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"nav.dashboard": "Dashboard",
|
"nav.dashboard": "Dashboard",
|
||||||
"nav.filter_debug": "Filter Debug",
|
"nav.filter_debug": "Filter Debug",
|
||||||
"nav.settings": "Settings",
|
"nav.settings": "Settings",
|
||||||
"restart_banner.message": "Fail2ban configuration changed! To apply the changes, please: ",
|
"restart_banner.message": "Fail2ban configuration changed. To apply the changes, please ",
|
||||||
"restart_banner.button": "Restart Service",
|
"restart_banner.button": "Restart Service",
|
||||||
"dashboard.title": "Dashboard",
|
"dashboard.title": "Dashboard",
|
||||||
"dashboard.overview": "Overview active Jails and Blocks",
|
"dashboard.overview": "Overview active Jails and Blocks",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"nav.dashboard": "Panel de control",
|
"nav.dashboard": "Panel de control",
|
||||||
"nav.filter_debug": "Depuración de filtros",
|
"nav.filter_debug": "Depuración de filtros",
|
||||||
"nav.settings": "Configuración",
|
"nav.settings": "Configuración",
|
||||||
"restart_banner.message": "¡Configuración de Fail2ban modificada! Para aplicar los cambios, por favor: ",
|
"restart_banner.message": "¡Configuración de Fail2ban modificada. Para aplicar los cambios, por favor ",
|
||||||
"restart_banner.button": "Reiniciar servicio",
|
"restart_banner.button": "Reiniciar servicio",
|
||||||
"dashboard.title": "Panel de control",
|
"dashboard.title": "Panel de control",
|
||||||
"dashboard.overview": "Resumen de Jails y Bloqueos activos",
|
"dashboard.overview": "Resumen de Jails y Bloqueos activos",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"nav.dashboard": "Tableau de bord",
|
"nav.dashboard": "Tableau de bord",
|
||||||
"nav.filter_debug": "Débogage des filtres",
|
"nav.filter_debug": "Débogage des filtres",
|
||||||
"nav.settings": "Paramètres",
|
"nav.settings": "Paramètres",
|
||||||
"restart_banner.message": "Configuration Fail2ban modifiée ! Pour appliquer les changements, veuillez: ",
|
"restart_banner.message": "Configuration Fail2ban modifiée. Pour appliquer les changements, veuillez ",
|
||||||
"restart_banner.button": "Redémarrer le service",
|
"restart_banner.button": "Redémarrer le service",
|
||||||
"dashboard.title": "Tableau de bord",
|
"dashboard.title": "Tableau de bord",
|
||||||
"dashboard.overview": "Vue d'ensemble des jails et blocages actifs",
|
"dashboard.overview": "Vue d'ensemble des jails et blocages actifs",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"nav.dashboard": "Cruscotto",
|
"nav.dashboard": "Cruscotto",
|
||||||
"nav.filter_debug": "Debug Filtro",
|
"nav.filter_debug": "Debug Filtro",
|
||||||
"nav.settings": "Impostazioni",
|
"nav.settings": "Impostazioni",
|
||||||
"restart_banner.message": "Configurazione di Fail2ban modificata! Per applicare le modifiche, per favore: ",
|
"restart_banner.message": "Configurazione di Fail2ban modificata. Per applicare le modifiche, per favore ",
|
||||||
"restart_banner.button": "Riavvia il servizio",
|
"restart_banner.button": "Riavvia il servizio",
|
||||||
"dashboard.title": "Cruscotto",
|
"dashboard.title": "Cruscotto",
|
||||||
"dashboard.overview": "Panoramica dei jail e dei blocchi attivi",
|
"dashboard.overview": "Panoramica dei jail e dei blocchi attivi",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/oschwald/maxminddb-golang"
|
"github.com/oschwald/maxminddb-golang"
|
||||||
"github.com/swissmakers/fail2ban-ui/internal/config"
|
"github.com/swissmakers/fail2ban-ui/internal/config"
|
||||||
"github.com/swissmakers/fail2ban-ui/internal/fail2ban"
|
"github.com/swissmakers/fail2ban-ui/internal/fail2ban"
|
||||||
@@ -112,14 +113,33 @@ func BanNotificationHandler(c *gin.Context) {
|
|||||||
|
|
||||||
// **DEBUGGING: Log Raw JSON Body**
|
// **DEBUGGING: Log Raw JSON Body**
|
||||||
body, _ := io.ReadAll(c.Request.Body)
|
body, _ := io.ReadAll(c.Request.Body)
|
||||||
|
log.Printf("----------------------------------------------------")
|
||||||
|
log.Printf("Request Content-Length: %d", c.Request.ContentLength)
|
||||||
|
log.Printf("Request Headers: %v", c.Request.Header)
|
||||||
|
log.Printf("Request Headers: %v", c.Request.Body)
|
||||||
|
|
||||||
|
log.Printf("----------------------------------------------------")
|
||||||
|
|
||||||
config.DebugLog("📩 Incoming Ban Notification: %s\n", string(body))
|
config.DebugLog("📩 Incoming Ban Notification: %s\n", string(body))
|
||||||
|
|
||||||
// Rebind body so Gin can parse it again (important!)
|
// Rebind body so Gin can parse it again (important!)
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||||
|
|
||||||
|
log.Printf("Request Content-Length: %d", c.Request.ContentLength)
|
||||||
|
log.Printf("Request Headers: %v", c.Request.Header)
|
||||||
|
log.Printf("Request Headers: %v", c.Request.Body)
|
||||||
|
|
||||||
// Parse JSON request body
|
// Parse JSON request body
|
||||||
if err := c.ShouldBindJSON(&request); err != nil {
|
if err := c.ShouldBindJSON(&request); err != nil {
|
||||||
log.Printf("❌ Invalid request: %v\n", err)
|
var verr validator.ValidationErrors
|
||||||
|
if errors.As(err, &verr) {
|
||||||
|
for _, fe := range verr {
|
||||||
|
log.Printf("❌ Validierungsfehler: Feld '%s' verletzt Regel '%s'", fe.Field(), fe.ActualTag())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("❌ JSON-Parsing Fehler: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("Raw JSON: %s", string(body))
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -439,13 +459,22 @@ func RestartFail2banHandler(c *gin.Context) {
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Then restart
|
// Attempt to restart the fail2ban service.
|
||||||
if err := fail2ban.RestartFail2ban(); err != nil {
|
restartErr := fail2ban.RestartFail2ban()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
if restartErr != nil {
|
||||||
|
// Check if running inside a container.
|
||||||
|
if _, container := os.LookupEnv("CONTAINER"); container {
|
||||||
|
// In a container, the restart command may fail (since fail2ban runs on the host).
|
||||||
|
// Log the error and continue, so we can mark the restart as done.
|
||||||
|
log.Printf("Warning: restart failed inside container (expected behavior): %v", restartErr)
|
||||||
|
} else {
|
||||||
|
// On the host, a restart error is not acceptable.
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": restartErr.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We set restart done in config
|
// Only call MarkRestartDone if we either successfully restarted the service or we are in a container.
|
||||||
if err := config.MarkRestartDone(); err != nil {
|
if err := config.MarkRestartDone(); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -475,7 +504,8 @@ func sendEmail(to, subject, body string, settings config.AppSettings) error {
|
|||||||
smtpAddr := net.JoinHostPort(smtpHost, fmt.Sprintf("%d", smtpPort))
|
smtpAddr := net.JoinHostPort(smtpHost, fmt.Sprintf("%d", smtpPort))
|
||||||
|
|
||||||
// **Choose Connection Type**
|
// **Choose Connection Type**
|
||||||
if smtpPort == 465 {
|
switch smtpPort {
|
||||||
|
case 465:
|
||||||
// SMTPS (Implicit TLS) - Not supported at the moment.
|
// SMTPS (Implicit TLS) - Not supported at the moment.
|
||||||
tlsConfig := &tls.Config{ServerName: smtpHost}
|
tlsConfig := &tls.Config{ServerName: smtpHost}
|
||||||
conn, err := tls.Dial("tcp", smtpAddr, tlsConfig)
|
conn, err := tls.Dial("tcp", smtpAddr, tlsConfig)
|
||||||
@@ -496,7 +526,7 @@ func sendEmail(to, subject, body string, settings config.AppSettings) error {
|
|||||||
|
|
||||||
return sendSMTPMessage(client, settings.SMTP.From, to, msg)
|
return sendSMTPMessage(client, settings.SMTP.From, to, msg)
|
||||||
|
|
||||||
} else if smtpPort == 587 {
|
case 587:
|
||||||
// STARTTLS (Explicit TLS)
|
// STARTTLS (Explicit TLS)
|
||||||
conn, err := net.Dial("tcp", smtpAddr)
|
conn, err := net.Dial("tcp", smtpAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ import (
|
|||||||
|
|
||||||
// RegisterRoutes sets up the routes for the Fail2ban UI.
|
// RegisterRoutes sets up the routes for the Fail2ban UI.
|
||||||
func RegisterRoutes(r *gin.Engine) {
|
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
|
// Render the dashboard
|
||||||
r.GET("/", IndexHandler)
|
r.GET("/", IndexHandler)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
1306
pkg/web/templates/index_bootstrap.html
Normal file
1306
pkg/web/templates/index_bootstrap.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user