From 9a66d2c87a56a22340b2d587d3334b863d66b216 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Wed, 26 Feb 2025 20:40:31 +0100 Subject: [PATCH 1/8] Fix Dockerfile to include language files --- Dockerfile | 3 ++- cmd/server/main.go | 4 +++- pkg/web/routes.go | 3 --- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index ddda406..43c1646 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ========================================= # STAGE 1: Build Fail2Ban UI Binary # ========================================= -FROM golang:1.22.9 AS builder +FROM golang:1.23 AS builder WORKDIR /app @@ -41,6 +41,7 @@ WORKDIR /config COPY --from=builder /app/fail2ban-ui /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/internal/locales /app/locales # Set environment variables ENV CONTAINER=true diff --git a/cmd/server/main.go b/cmd/server/main.go index 75af774..89a2e78 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -32,12 +32,14 @@ func main() { if container { // In container, templates are assumed to be in /app/templates router.LoadHTMLGlob("/app/templates/*") + router.Static("/locales", "/app/locales") } else { // When running locally, load templates from 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) printWelcomeBanner(serverPort) diff --git a/pkg/web/routes.go b/pkg/web/routes.go index 074e42b..04d65dc 100644 --- a/pkg/web/routes.go +++ b/pkg/web/routes.go @@ -22,9 +22,6 @@ 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) From 8c84909c95bd1d81b46884c39c43f4547ff957c5 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Thu, 27 Feb 2025 14:03:08 +0100 Subject: [PATCH 2/8] workaround for the restart command may fail in container (since fail2ban runs on the host) --- internal/config/settings.go | 31 +++++++------------------------ internal/fail2ban/client.go | 6 ++++++ pkg/web/handlers.go | 19 ++++++++++++++----- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/internal/config/settings.go b/internal/config/settings.go index 45f0ff9..887b178 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -191,6 +191,8 @@ func initializeFromJailFile() error { // initializeFail2banAction writes a custom action configuration for Fail2ban to use AlertCountries. func initializeFail2banAction() error { + DebugLog("----------------------------") + DebugLog("Running initial initializeFail2banAction()") // entry point // Ensure the jail.local is configured correctly if err := setupGeoCustomAction(); err != nil { 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 func setupGeoCustomAction() error { + DebugLog("Running initial setupGeoCustomAction()") // entry point file, err := os.Open(jailFile) if err != nil { // 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 func ensureJailDConfig() error { + DebugLog("Running initial ensureJailDConfig()") // entry point // Check if the file already exists if _, err := os.Stat(jailDFile); err == nil { // File already exists, do nothing @@ -310,6 +314,8 @@ action_mwlg = %(action_)s // writeFail2banAction creates or updates the action file with the AlertCountries. func writeFail2banAction() error { + DebugLog("Running initial writeFail2banAction()") // entry point + DebugLog("----------------------------") // Define the Fail2Ban action file content actionConfig := `[INCLUDES] @@ -395,7 +401,7 @@ func saveSettings() error { if err != nil { 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() } @@ -439,8 +445,6 @@ func UpdateSettings(new AppSettings) (AppSettings, error) { old.Bantime != new.Bantime || old.Findtime != new.Findtime || //old.Maxretry != new.Maxretry || - old.Destemail != new.Destemail || - //old.Sender != new.Sender { old.Maxretry != new.Maxretry { new.RestartNeeded = true } else { @@ -448,11 +452,6 @@ func UpdateSettings(new AppSettings) (AppSettings, error) { new.RestartNeeded = new.RestartNeeded || old.RestartNeeded } - // Countries change? Currently also requires a reload - if !equalStringSlices(old.AlertCountries, new.AlertCountries) { - new.RestartNeeded = true - } - currentSettings = new 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 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 -} diff --git a/internal/fail2ban/client.go b/internal/fail2ban/client.go index 49ae4bb..6ed4b07 100644 --- a/internal/fail2ban/client.go +++ b/internal/fail2ban/client.go @@ -19,6 +19,7 @@ package fail2ban import ( "errors" "fmt" + "os" "os/exec" "strings" "time" @@ -152,6 +153,11 @@ func ReloadFail2ban() error { // RestartFail2ban restarts the Fail2ban service. 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" out, err := execCommand(cmd) if err != nil { diff --git a/pkg/web/handlers.go b/pkg/web/handlers.go index 4044002..4abde35 100644 --- a/pkg/web/handlers.go +++ b/pkg/web/handlers.go @@ -439,13 +439,22 @@ func RestartFail2banHandler(c *gin.Context) { // return // } - // Then restart - if err := fail2ban.RestartFail2ban(); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return + // Attempt to restart the fail2ban service. + restartErr := fail2ban.RestartFail2ban() + 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 + } } - // 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 { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return From 86699209dbe5b89f3fb3f2afa2997d3d3fc7ec7e Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Tue, 1 Apr 2025 16:06:16 +0200 Subject: [PATCH 3/8] fix filtering --- pkg/web/templates/index.html | 65 ++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/pkg/web/templates/index.html b/pkg/web/templates/index.html index f5daa0e..867ab41 100644 --- a/pkg/web/templates/index.html +++ b/pkg/web/templates/index.html @@ -535,6 +535,7 @@ fetchSummary().then(function() { showLoading(false); initializeTooltips(); // Initialize tooltips after fetching and rendering + initializeSearch(); getTranslationsSettingsOnPageload(); }); }); @@ -547,6 +548,18 @@ }); } + // Function to initialize the IP search + function initializeSearch() { + const ipSearch = document.getElementById("ipSearch"); + if (ipSearch) { + ipSearch.addEventListener("keypress", function(event) { + const char = String.fromCharCode(event.which); + if (!/[0-9.]/.test(char)) { + event.preventDefault(); + } + }); + } + } // Toggle the loading overlay (with !important) function showLoading(show) { var overlay = document.getElementById('loading-overlay'); @@ -630,7 +643,7 @@ Overview active Jails and Blocks
- +
`; @@ -727,30 +740,46 @@ // Filter IPs on dashboard table function filterIPs() { - const query = document.getElementById("ipSearch").value.toLowerCase(); - const rows = document.querySelectorAll("#jailsTable .jail-row"); + const input = document.getElementById("ipSearch"); + const query = input.value.trim(); - rows.forEach((row) => { - const ipSpans = row.querySelectorAll("ul li span"); - let matchFound = false; + // Process each row in the jails table + document.querySelectorAll("#jailsTable .jail-row").forEach(row => { + // Get all list items (each representing an IP entry) + const ipItems = row.querySelectorAll("ul li"); + let rowHasMatch = false; - ipSpans.forEach((span) => { - const originalText = span.textContent; - const ipText = originalText.toLowerCase(); + ipItems.forEach(li => { + // Find the span that contains the IP text. + const span = li.querySelector("span.me-auto"); + if (!span) return; - if (query && ipText.includes(query)) { - matchFound = true; - const highlightedText = originalText.replace( - new RegExp(query, "gi"), - (match) => `${match}` - ); - span.innerHTML = highlightedText; + // Retrieve the original IP text from a data attribute; if not set, save it. + let originalIP = span.getAttribute("data-original"); + if (!originalIP) { + originalIP = span.textContent.trim(); + span.setAttribute("data-original", originalIP); + } + + if (query === "") { + // When the search query is empty, show all IP items and reset their inner HTML. + li.style.display = ""; + span.innerHTML = originalIP; + rowHasMatch = true; + } else if (originalIP.includes(query)) { + // If the IP contains the query, show the item and highlight the matching text. + li.style.display = ""; + const regex = new RegExp(query, "gi"); + span.innerHTML = originalIP.replace(regex, (match) => `${match}`); + rowHasMatch = true; } else { - span.innerHTML = originalText; + // Hide the IP item if it does not match. + li.style.setProperty("display", "none", "important"); } }); - row.style.display = matchFound || !query ? "" : "none"; + // If none of the IP items in the row match the query, hide the entire row. + row.style.display = rowHasMatch ? "" : "none"; }); } From c9ae9e74d2bdc46be1356829b1b6d78bc25c4a1e Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Wed, 16 Jul 2025 15:47:33 +0200 Subject: [PATCH 4/8] try to improove DEBUGGING for log Raw JSON Body --- cmd/server/main.go | 16 ++++++++++++++++ go.mod | 8 ++++---- go.sum | 16 ++++++++-------- pkg/web/handlers.go | 27 ++++++++++++++++++++++++--- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 89a2e78..655c812 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -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 import ( diff --git a/go.mod b/go.mod index e8d035b..f46960a 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23 require ( 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 ) @@ -15,7 +16,6 @@ require ( github.com/gin-contrib/sse v1.0.0 // indirect github.com/go-playground/locales v0.14.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/json-iterator/go v1.1.12 // 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/ugorji/go/codec v1.2.12 // 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/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect google.golang.org/protobuf v1.36.4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3b97256..e055ceb 100644 --- a/go.sum +++ b/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/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/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= -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 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +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/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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= golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= 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.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +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/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 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.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= diff --git a/pkg/web/handlers.go b/pkg/web/handlers.go index 4abde35..286f615 100644 --- a/pkg/web/handlers.go +++ b/pkg/web/handlers.go @@ -31,6 +31,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" "github.com/oschwald/maxminddb-golang" "github.com/swissmakers/fail2ban-ui/internal/config" "github.com/swissmakers/fail2ban-ui/internal/fail2ban" @@ -112,14 +113,33 @@ func BanNotificationHandler(c *gin.Context) { // **DEBUGGING: Log Raw JSON 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)) // Rebind body so Gin can parse it again (important!) 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 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()}) return } @@ -484,7 +504,8 @@ func sendEmail(to, subject, body string, settings config.AppSettings) error { smtpAddr := net.JoinHostPort(smtpHost, fmt.Sprintf("%d", smtpPort)) // **Choose Connection Type** - if smtpPort == 465 { + switch smtpPort { + case 465: // SMTPS (Implicit TLS) - Not supported at the moment. tlsConfig := &tls.Config{ServerName: smtpHost} conn, err := tls.Dial("tcp", smtpAddr, tlsConfig) @@ -505,7 +526,7 @@ func sendEmail(to, subject, body string, settings config.AppSettings) error { return sendSMTPMessage(client, settings.SMTP.From, to, msg) - } else if smtpPort == 587 { + case 587: // STARTTLS (Explicit TLS) conn, err := net.Dial("tcp", smtpAddr) if err != nil { From c3a2fed0078ebff1074107f493f4ecb8721c8715 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Thu, 17 Jul 2025 10:36:26 +0200 Subject: [PATCH 5/8] add own external IP-lookup and fix fade-in/out --- pkg/web/templates/index.html | 60 ++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/pkg/web/templates/index.html b/pkg/web/templates/index.html index 867ab41..c09888e 100644 --- a/pkg/web/templates/index.html +++ b/pkg/web/templates/index.html @@ -39,7 +39,17 @@ z-index: 9999; /* on top */ align-items: center; justify-content: center; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + opacity: 0; + transition: opacity 0.4s ease; } + + #loading-overlay.show { + display: flex; + opacity: 1; + } + .spinner-border { width: 4rem; height: 4rem; @@ -107,9 +117,19 @@
-
+

Dashboard

- +
+
+ Your ext. IP: Loading... +
+ +
+
@@ -527,10 +547,11 @@ //******************************************************************* //* Init page and main-components : * //******************************************************************* + showLoading(true); var currentJailForConfig = null; window.addEventListener('DOMContentLoaded', function() { - showLoading(true); + displayExternalIP(); checkRestartNeeded(); fetchSummary().then(function() { showLoading(false); @@ -540,6 +561,30 @@ }); }); + // Toggle the loading overlay (with !important) + function showLoading(show) { + var overlay = document.getElementById('loading-overlay'); + if (show) { + overlay.style.setProperty('display', 'flex', 'important'); + setTimeout(() => overlay.classList.add('show'), 10); + } else { + overlay.classList.remove('show'); // Start fade-out + setTimeout(() => overlay.style.setProperty('display', 'none', 'important'), 400); + } + } + + // Fetch and display own external IP for webUI + function displayExternalIP() { + fetch('https://api.ipify.org?format=json') + .then(res => res.json()) + .then(data => { + document.getElementById('external-ip').textContent = data.ip; + }) + .catch(() => { + document.getElementById('external-ip').textContent = 'Unavailable'; + }); + } + // Function to initialize Bootstrap tooltips function initializeTooltips() { const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); @@ -560,15 +605,6 @@ }); } } - // 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 checkRestartNeeded() { From fa2faf8067cde0a358add0e895642b89975329d0 Mon Sep 17 00:00:00 2001 From: Michael Reber Date: Thu, 17 Jul 2025 16:48:15 +0200 Subject: [PATCH 6/8] Refactor whole webUI from Bootstrap to tailwindcss, fixed mobile view, added possibility to search own IP in blocklists by only one click --- internal/locales/de.json | 2 +- internal/locales/en.json | 2 +- internal/locales/es.json | 2 +- internal/locales/fr.json | 2 +- internal/locales/it.json | 2 +- pkg/web/templates/index.html | 1314 ++++++++++++++---------- pkg/web/templates/index_bootstrap.html | 1306 +++++++++++++++++++++++ 7 files changed, 2061 insertions(+), 569 deletions(-) create mode 100644 pkg/web/templates/index_bootstrap.html diff --git a/internal/locales/de.json b/internal/locales/de.json index 50951fb..66b23f5 100644 --- a/internal/locales/de.json +++ b/internal/locales/de.json @@ -3,7 +3,7 @@ "nav.dashboard": "Dashboard", "nav.filter_debug": "Filter-Debug", "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", "dashboard.title": "Dashboard", "dashboard.overview": "Aktive Jails und Blocks Übersicht", diff --git a/internal/locales/en.json b/internal/locales/en.json index 906ae2d..c3a202c 100644 --- a/internal/locales/en.json +++ b/internal/locales/en.json @@ -3,7 +3,7 @@ "nav.dashboard": "Dashboard", "nav.filter_debug": "Filter Debug", "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", "dashboard.title": "Dashboard", "dashboard.overview": "Overview active Jails and Blocks", diff --git a/internal/locales/es.json b/internal/locales/es.json index 5cc45fd..0945863 100644 --- a/internal/locales/es.json +++ b/internal/locales/es.json @@ -3,7 +3,7 @@ "nav.dashboard": "Panel de control", "nav.filter_debug": "Depuración de filtros", "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", "dashboard.title": "Panel de control", "dashboard.overview": "Resumen de Jails y Bloqueos activos", diff --git a/internal/locales/fr.json b/internal/locales/fr.json index 32ff476..8f37b50 100644 --- a/internal/locales/fr.json +++ b/internal/locales/fr.json @@ -3,7 +3,7 @@ "nav.dashboard": "Tableau de bord", "nav.filter_debug": "Débogage des filtres", "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", "dashboard.title": "Tableau de bord", "dashboard.overview": "Vue d'ensemble des jails et blocages actifs", diff --git a/internal/locales/it.json b/internal/locales/it.json index 9336462..28505a2 100644 --- a/internal/locales/it.json +++ b/internal/locales/it.json @@ -3,7 +3,7 @@ "nav.dashboard": "Cruscotto", "nav.filter_debug": "Debug Filtro", "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", "dashboard.title": "Cruscotto", "dashboard.overview": "Panoramica dei jail e dei blocchi attivi", diff --git a/pkg/web/templates/index.html b/pkg/web/templates/index.html index c09888e..200c06f 100644 --- a/pkg/web/templates/index.html +++ b/pkg/web/templates/index.html @@ -22,21 +22,23 @@ Fail2ban UI Dashboard - - + + + + - - - - - - + - -
-
- Fail2ban configuration changed! To apply the changes, please: - -
-
- + + +

Dashboard

@@ -208,42 +213,50 @@
-
+
+ - + + + + + - + + + +