mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Raw implement lotr-idea
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -75,8 +76,15 @@ func main() {
|
||||
// Register all application routes, including the static files and templates.
|
||||
web.RegisterRoutes(router)
|
||||
|
||||
printWelcomeBanner(serverPort)
|
||||
log.Println("--- Fail2Ban-UI started in", gin.Mode(), "mode ---")
|
||||
// Check if LOTR mode is active
|
||||
isLOTRMode := isLOTRModeActive(settings.AlertCountries)
|
||||
printWelcomeBanner(serverPort, isLOTRMode)
|
||||
if isLOTRMode {
|
||||
log.Println("--- Middle-earth Security Realm activated ---")
|
||||
log.Println("🎭 LOTR Mode: The guardians of Middle-earth stand ready!")
|
||||
} else {
|
||||
log.Println("--- Fail2Ban-UI started in", gin.Mode(), "mode ---")
|
||||
}
|
||||
log.Println("Server listening on port", serverPort, ".")
|
||||
|
||||
// Start the server on port 8080.
|
||||
@@ -85,10 +93,45 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// isLOTRModeActive checks if LOTR mode is enabled in alert countries
|
||||
func isLOTRModeActive(alertCountries []string) bool {
|
||||
if len(alertCountries) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, country := range alertCountries {
|
||||
if strings.EqualFold(country, "LOTR") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printWelcomeBanner prints a cool Tux banner with startup info.
|
||||
func printWelcomeBanner(appPort string) {
|
||||
func printWelcomeBanner(appPort string, isLOTRMode bool) {
|
||||
greeting := getGreeting()
|
||||
const tuxBanner = `
|
||||
|
||||
if isLOTRMode {
|
||||
const lotrBanner = `
|
||||
.--.
|
||||
|o_o | %s
|
||||
|:_/ |
|
||||
// \ \
|
||||
(| | )
|
||||
/'\_ _/'\
|
||||
\___)=(___/
|
||||
|
||||
Middle-earth Security Realm - LOTR Mode Activated
|
||||
══════════════════════════════════════════════════
|
||||
⚔️ The guardians of Middle-earth stand ready! ⚔️
|
||||
Developers: https://swissmakers.ch
|
||||
Mode: %s
|
||||
Listening on: http://0.0.0.0:%s
|
||||
══════════════════════════════════════════════════
|
||||
|
||||
`
|
||||
fmt.Printf(lotrBanner, greeting, gin.Mode(), appPort)
|
||||
} else {
|
||||
const tuxBanner = `
|
||||
.--.
|
||||
|o_o | %s
|
||||
|:_/ |
|
||||
@@ -105,7 +148,8 @@ Listening on: http://0.0.0.0:%s
|
||||
----------------------------------------------
|
||||
|
||||
`
|
||||
fmt.Printf(tuxBanner, greeting, gin.Mode(), appPort)
|
||||
fmt.Printf(tuxBanner, greeting, gin.Mode(), appPort)
|
||||
}
|
||||
}
|
||||
|
||||
// getGreeting returns a friendly greeting based on the time of day.
|
||||
|
||||
@@ -255,6 +255,17 @@
|
||||
"email.test.sample_logs": "2025-01-01T12:00:00Z Beispiel-Log-Eintrag von Fail2ban-UI.",
|
||||
"email.whois.no_data": "WHOIS-Daten wurden für dieses Ereignis nicht erfasst.",
|
||||
"email.logs.no_data": "Für diesen Block wurden keine Log-Einträge erfasst.",
|
||||
"email.footer.text": "Diese Nachricht wurde automatisch von Fail2Ban-UI generiert"
|
||||
"email.footer.text": "Diese Nachricht wurde automatisch von Fail2Ban-UI generiert",
|
||||
"lotr.email.title": "Ein dunkler Diener wurde verbannt",
|
||||
"lotr.email.intro": "Die Wächter von Mittelerde haben eine Bedrohung erkannt und aus dem Reich verbannt.",
|
||||
"lotr.email.you_shall_not_pass": "DU KANNST NICHT VORBEI",
|
||||
"lotr.email.footer": "Mögen die Server geschützt sein. Ein Bann, um sie alle zu beherrschen.",
|
||||
"lotr.email.details.dark_servant_location": "Die Position des dunklen Dieners",
|
||||
"lotr.email.details.realm_protection": "Das Reich des Schutzes",
|
||||
"lotr.email.details.origins": "Herkunft aus den",
|
||||
"lotr.email.details.banished_at": "Verbannt zur",
|
||||
"lotr.banished": "Aus dem Reich verbannt",
|
||||
"lotr.realms_protected": "Geschützte Reiche",
|
||||
"lotr.threats_banished": "Verbannte Bedrohungen"
|
||||
}
|
||||
|
||||
@@ -255,6 +255,17 @@
|
||||
"email.test.sample_logs": "2025-01-01T12:00:00Z Bispil-Log-Itrag vom Fail2ban-UI.",
|
||||
"email.whois.no_data": "WHOIS-Date si für das Ereignis nid erfasst worde.",
|
||||
"email.logs.no_data": "Für de Block sind keni Log-Iiträg erfasst worde.",
|
||||
"email.footer.text": "Diä Nachricht isch automatisch vom Fail2Ban-UI generiert worde"
|
||||
"email.footer.text": "Diä Nachricht isch automatisch vom Fail2Ban-UI generiert worde",
|
||||
"lotr.email.title": "E dunkle Diener isch verbannt worde",
|
||||
"lotr.email.intro": "D Wächter vo Mittelerde hei e Bedrohig erkannt und us dim Riich verbannt.",
|
||||
"lotr.email.you_shall_not_pass": "DU DARFSCH NID VERBII",
|
||||
"lotr.email.footer": "Möge d Server gschützt si. E Bann, um si alli z beherrsche.",
|
||||
"lotr.email.details.dark_servant_location": "Dr Ort vom garstige Ork",
|
||||
"lotr.email.details.realm_protection": "S Riich vom Schutz",
|
||||
"lotr.email.details.origins": "Herkunft us de",
|
||||
"lotr.email.details.banished_at": "Verbannt zur",
|
||||
"lotr.banished": "Us em Riich verbannt",
|
||||
"lotr.realms_protected": "Gschützti Riich",
|
||||
"lotr.threats_banished": "Verbannti Bedrohige"
|
||||
}
|
||||
|
||||
@@ -255,6 +255,17 @@
|
||||
"email.test.sample_logs": "2025-01-01T12:00:00Z Sample log entry of Fail2ban-UI.",
|
||||
"email.whois.no_data": "WHOIS data was not captured for this event.",
|
||||
"email.logs.no_data": "No log entries were captured for this block.",
|
||||
"email.footer.text": "This message was generated automatically by Fail2Ban-UI"
|
||||
"email.footer.text": "This message was generated automatically by Fail2Ban-UI",
|
||||
"lotr.email.title": "A Dark Servant Has Been Banished",
|
||||
"lotr.email.intro": "The guardians of Middle-earth have detected a threat and banished it from the realm.",
|
||||
"lotr.email.you_shall_not_pass": "YOU SHALL NOT PASS",
|
||||
"lotr.email.footer": "May the servers be protected. One ban to rule them all.",
|
||||
"lotr.email.details.dark_servant_location": "The Dark Servant's Location",
|
||||
"lotr.email.details.realm_protection": "The Realm of Protection",
|
||||
"lotr.email.details.origins": "Origins from the",
|
||||
"lotr.email.details.banished_at": "Banished at the",
|
||||
"lotr.banished": "Banished from the realm",
|
||||
"lotr.realms_protected": "Realms Protected",
|
||||
"lotr.threats_banished": "Threats Banished"
|
||||
}
|
||||
|
||||
@@ -255,5 +255,16 @@
|
||||
"email.test.sample_logs": "2025-01-01T12:00:00Z Entrada de registro de ejemplo de Fail2ban-UI.",
|
||||
"email.whois.no_data": "No se capturaron datos WHOIS para este evento.",
|
||||
"email.logs.no_data": "No se capturaron entradas de registro para este bloqueo.",
|
||||
"email.footer.text": "Este mensaje fue generado automáticamente por Fail2Ban-UI"
|
||||
"email.footer.text": "Este mensaje fue generado automáticamente por Fail2Ban-UI",
|
||||
"lotr.email.title": "Un siervo oscuro ha sido desterrado",
|
||||
"lotr.email.intro": "Los guardianes de la Tierra Media han detectado una amenaza y la han desterrado del reino.",
|
||||
"lotr.email.you_shall_not_pass": "NO PASARÁS",
|
||||
"lotr.email.footer": "Que los servidores estén protegidos. Un ban para gobernarlos a todos.",
|
||||
"lotr.email.details.dark_servant_location": "La ubicación del siervo oscuro",
|
||||
"lotr.email.details.realm_protection": "El reino de la protección",
|
||||
"lotr.email.details.origins": "Orígenes de las",
|
||||
"lotr.email.details.banished_at": "Desterrado a las",
|
||||
"lotr.banished": "Desterrado del reino",
|
||||
"lotr.realms_protected": "Reinos protegidos",
|
||||
"lotr.threats_banished": "Amenazas desterradas"
|
||||
}
|
||||
|
||||
@@ -253,7 +253,18 @@
|
||||
"email.test.details.triggered_at": "Déclenché à",
|
||||
"email.test.whois_no_data": "Aucune recherche WHOIS n'est exécutée pour les emails de test.",
|
||||
"email.test.sample_logs": "2025-01-01T12:00:00Z Entrée de journal d'exemple de Fail2ban-UI.",
|
||||
"email.whois.no_data": "Les données WHOIS n'ont pas été capturées pour cet événement.",
|
||||
"email.logs.no_data": "Aucune entrée de journal n'a été capturée pour ce blocage.",
|
||||
"email.footer.text": "Ce message a été généré automatiquement par Fail2Ban-UI"
|
||||
}
|
||||
"email.whois.no_data": "Les données WHOIS n'ont pas été capturées pour cet événement.",
|
||||
"email.logs.no_data": "Aucune entrée de journal n'a été capturée pour ce blocage.",
|
||||
"email.footer.text": "Ce message a été généré automatiquement par Fail2Ban-UI",
|
||||
"lotr.email.title": "Un serviteur des ténèbres a été banni",
|
||||
"lotr.email.intro": "Les gardiens de la Terre du Milieu ont détecté une menace et l'ont bannie du royaume.",
|
||||
"lotr.email.you_shall_not_pass": "TU NE PASSERAS PAS",
|
||||
"lotr.email.footer": "Que les serveurs soient protégés. Un bannissement pour les gouverner tous.",
|
||||
"lotr.email.details.dark_servant_location": "L'emplacement du serviteur des ténèbres",
|
||||
"lotr.email.details.realm_protection": "Le royaume de la protection",
|
||||
"lotr.email.details.origins": "Origines des",
|
||||
"lotr.email.details.banished_at": "Banni à",
|
||||
"lotr.banished": "Banni du royaume",
|
||||
"lotr.realms_protected": "Royaumes protégés",
|
||||
"lotr.threats_banished": "Menaces bannies"
|
||||
}
|
||||
|
||||
@@ -253,7 +253,18 @@
|
||||
"email.test.details.triggered_at": "Attivato alle",
|
||||
"email.test.whois_no_data": "Nessuna ricerca WHOIS viene eseguita per le email di test.",
|
||||
"email.test.sample_logs": "2025-01-01T12:00:00Z Voce di log di esempio di Fail2ban-UI.",
|
||||
"email.whois.no_data": "I dati WHOIS non sono stati acquisiti per questo evento.",
|
||||
"email.logs.no_data": "Nessuna voce di log è stata acquisita per questo blocco.",
|
||||
"email.footer.text": "Questo messaggio è stato generato automaticamente da Fail2Ban-UI"
|
||||
}
|
||||
"email.whois.no_data": "I dati WHOIS non sono stati acquisiti per questo evento.",
|
||||
"email.logs.no_data": "Nessuna voce di log è stata acquisita per questo blocco.",
|
||||
"email.footer.text": "Questo messaggio è stato generato automaticamente da Fail2Ban-UI",
|
||||
"lotr.email.title": "Un servitore oscuro è stato bandito",
|
||||
"lotr.email.intro": "I guardiani della Terra di Mezzo hanno rilevato una minaccia e l'hanno bandita dal regno.",
|
||||
"lotr.email.you_shall_not_pass": "NON PASSERAI",
|
||||
"lotr.email.footer": "Possano i server essere protetti. Un ban per governarli tutti.",
|
||||
"lotr.email.details.dark_servant_location": "La posizione del servitore oscuro",
|
||||
"lotr.email.details.realm_protection": "Il regno della protezione",
|
||||
"lotr.email.details.origins": "Origini dalle",
|
||||
"lotr.email.details.banished_at": "Bandito alle",
|
||||
"lotr.banished": "Bandito dal regno",
|
||||
"lotr.realms_protected": "Regni protetti",
|
||||
"lotr.threats_banished": "Minacce bandite"
|
||||
}
|
||||
|
||||
@@ -857,24 +857,24 @@ func GetSettingsHandler(c *gin.Context) {
|
||||
config.DebugLog("----------------------------")
|
||||
config.DebugLog("GetSettingsHandler called (handlers.go)") // entry point
|
||||
s := config.GetSettings()
|
||||
|
||||
|
||||
// Check if PORT environment variable is set
|
||||
envPort, envPortSet := config.GetPortFromEnv()
|
||||
|
||||
|
||||
// Create response with PORT env info
|
||||
response := make(map[string]interface{})
|
||||
responseBytes, _ := json.Marshal(s)
|
||||
json.Unmarshal(responseBytes, &response)
|
||||
|
||||
|
||||
// Add PORT environment variable information
|
||||
response["portFromEnv"] = envPort
|
||||
response["portEnvSet"] = envPortSet
|
||||
|
||||
|
||||
// If PORT env is set, override the port value in response
|
||||
if envPortSet {
|
||||
response["port"] = envPort
|
||||
}
|
||||
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
@@ -1131,6 +1131,19 @@ func getEmailStyle() string {
|
||||
return "modern"
|
||||
}
|
||||
|
||||
// isLOTRModeActive checks if LOTR mode is enabled in alert countries
|
||||
func isLOTRModeActive(alertCountries []string) bool {
|
||||
if len(alertCountries) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, country := range alertCountries {
|
||||
if strings.EqualFold(country, "LOTR") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// *******************************************************************
|
||||
// * Unified Email Sending Function : *
|
||||
// *******************************************************************
|
||||
@@ -1308,6 +1321,101 @@ func buildClassicEmailBody(title, intro string, details []emailDetail, whoisHTML
|
||||
</html>`, html.EscapeString(title), html.EscapeString(title), html.EscapeString(intro), detailRows, html.EscapeString(whoisTitle), whoisHTML, html.EscapeString(logsTitle), logsHTML, html.EscapeString(footerText), html.EscapeString(supportEmail), html.EscapeString(supportEmail), year)
|
||||
}
|
||||
|
||||
// buildLOTREmailBody creates the dramatic LOTR-themed email template with "You Shall Not Pass" styling
|
||||
func buildLOTREmailBody(title, intro string, details []emailDetail, whoisHTML, logsHTML, whoisTitle, logsTitle, footerText string) string {
|
||||
detailRows := renderEmailDetails(details)
|
||||
year := strconv.Itoa(time.Now().Year())
|
||||
return fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>%s</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { margin:0; padding:0; background: linear-gradient(135deg, #0d2818 0%%, #1a4d2e 50%%, #2d0a4f 100%%); font-family: Georgia, "Times New Roman", serif; color:#f4e8d0; line-height:1.6; -webkit-font-smoothing:antialiased; }
|
||||
.email-wrapper { width:100%%; padding:20px 10px; background: linear-gradient(135deg, #0d2818 0%%, #1a4d2e 50%%, #2d0a4f 100%%); }
|
||||
.email-container { max-width:640px; margin:0 auto; background:#f4e8d0; border:4px solid #d4af37; border-radius:12px; box-shadow:0 8px 32px rgba(0,0,0,0.6), inset 0 0 40px rgba(212,175,55,0.1); overflow:hidden; position:relative; }
|
||||
.email-container::before { content:''; position:absolute; top:0; left:0; right:0; bottom:0; background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(139,115,85,0.03) 2px, rgba(139,115,85,0.03) 4px); pointer-events:none; }
|
||||
.email-header { background: linear-gradient(180deg, #c1121f 0%%, #ff6b35 30%%, #d4af37 70%%, #1a4d2e 100%%); color:#ffffff; padding:40px 28px; text-align:center; position:relative; overflow:hidden; }
|
||||
.email-header::before { content:''; position:absolute; top:0; left:0; right:0; bottom:0; background: radial-gradient(circle at center, rgba(255,255,255,0.1) 0%%, transparent 70%%); animation: fireFlicker 3s ease-in-out infinite; }
|
||||
@keyframes fireFlicker { 0%%,100%% { opacity:0.6; } 50%% { opacity:1; } }
|
||||
.email-header-brand { margin:0 0 12px; font-size:12px; letter-spacing:0.4em; text-transform:uppercase; opacity:0.9; font-weight:600; font-family:'Cinzel', serif; position:relative; z-index:1; }
|
||||
.email-header-title { margin:20px 0; font-size:42px; font-weight:700; line-height:1.1; text-shadow: 0 0 20px rgba(255,255,255,0.8), 0 0 40px rgba(255,107,53,0.6), 0 0 60px rgba(193,18,31,0.4); font-family:'Cinzel', serif; letter-spacing:0.1em; position:relative; z-index:1; animation: textGlow 2s ease-in-out infinite; }
|
||||
@keyframes textGlow { 0%%,100%% { text-shadow: 0 0 20px rgba(255,255,255,0.8), 0 0 40px rgba(255,107,53,0.6), 0 0 60px rgba(193,18,31,0.4); } 50%% { text-shadow: 0 0 30px rgba(255,255,255,1), 0 0 60px rgba(255,107,53,0.8), 0 0 90px rgba(193,18,31,0.6); } }
|
||||
.ring-divider { text-align:center; margin:30px 0; position:relative; }
|
||||
.ring-divider::before { content:'⚔'; position:absolute; left:20%%; top:50%%; transform:translateY(-50%%); font-size:24px; color:#d4af37; background:#f4e8d0; padding:0 15px; }
|
||||
.ring-divider::after { content:'⚔'; position:absolute; right:20%%; top:50%%; transform:translateY(-50%%); font-size:24px; color:#d4af37; background:#f4e8d0; padding:0 15px; }
|
||||
.ring-divider-line { height:3px; background:linear-gradient(90deg, transparent 0%%, #d4af37 20%%, #d4af37 80%%, transparent 100%%); margin:0 25%%; }
|
||||
.email-body { padding:36px 28px; background:#f4e8d0; color:#3d2817; }
|
||||
.email-intro { font-size:18px; line-height:1.8; margin:0 0 28px; color:#3d2817; font-style:italic; text-align:center; }
|
||||
.email-details-wrapper { background:#e8d5b7; border:3px solid #8b7355; border-radius:8px; padding:24px; margin:0 0 32px; box-shadow:inset 0 2px 4px rgba(0,0,0,0.1); }
|
||||
.email-details-wrapper p { margin:12px 0; font-size:15px; line-height:1.7; color:#3d2817; }
|
||||
.email-details-wrapper p:first-child { margin-top:0; }
|
||||
.email-details-wrapper p:last-child { margin-bottom:0; }
|
||||
.email-detail-label { font-weight:700; color:#1a4d2e; margin-right:8px; font-family:'Cinzel', serif; }
|
||||
.email-section { margin:36px 0 0; }
|
||||
.email-section-title { font-size:16px; text-transform:uppercase; letter-spacing:0.2em; color:#1a4d2e; margin:0 0 16px; font-weight:700; font-family:'Cinzel', serif; border-bottom:2px solid #d4af37; padding-bottom:8px; }
|
||||
.email-terminal { background:#1a1a1a; color:#d4af37; padding:20px; font-family:"Courier New", Courier, monospace; border-radius:8px; font-size:13px; line-height:1.7; white-space:pre-wrap; word-break:break-word; overflow-x:auto; margin:0; border:2px solid #8b7355; box-shadow:inset 0 0 20px rgba(212,175,55,0.1); }
|
||||
.email-log-stack { background:#0f0f0f; border-radius:8px; padding:16px; border:2px solid #8b7355; }
|
||||
.email-log-line { font-family:"Courier New", Courier, monospace; font-size:12px; line-height:1.6; color:#d4af37; padding:8px 12px; border-radius:6px; margin:0 0 6px; background:rgba(212,175,55,0.1); border-left:3px solid #d4af37; }
|
||||
.email-log-line:last-child { margin-bottom:0; }
|
||||
.email-log-line-alert { background:rgba(193,18,31,0.3); color:#ff6b35; border-left-color:#c1121f; }
|
||||
.email-muted { color:#8b7355; font-size:14px; line-height:1.6; font-style:italic; }
|
||||
.email-footer { border-top:3px solid #d4af37; padding:24px 28px; font-size:13px; color:#3d2817; text-align:center; background:#e8d5b7; font-family:'Cinzel', serif; }
|
||||
.email-footer-text { margin:0 0 8px; font-weight:600; }
|
||||
.email-footer-copyright { margin:0; font-size:11px; color:#8b7355; }
|
||||
@media only screen and (max-width:600px) {
|
||||
.email-wrapper { padding:12px 8px; }
|
||||
.email-header { padding:30px 20px; }
|
||||
.email-header-title { font-size:32px; }
|
||||
.email-body { padding:28px 20px; }
|
||||
.email-intro { font-size:16px; }
|
||||
.email-details-wrapper { padding:20px; }
|
||||
.email-footer { padding:20px 16px; }
|
||||
}
|
||||
@media only screen and (max-width:480px) {
|
||||
.email-header-title { font-size:28px; }
|
||||
.email-body { padding:24px 16px; }
|
||||
.email-details-wrapper { padding:16px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-wrapper">
|
||||
<div class="email-container">
|
||||
<div class="email-header">
|
||||
<p class="email-header-brand">Middle-earth Security</p>
|
||||
<h1 class="email-header-title">YOU SHALL NOT PASS</h1>
|
||||
<div class="ring-divider">
|
||||
<div class="ring-divider-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<p class="email-intro">%s</p>
|
||||
<div class="email-details-wrapper">
|
||||
%s
|
||||
</div>
|
||||
<div class="email-section">
|
||||
<p class="email-section-title">%s</p>
|
||||
%s
|
||||
</div>
|
||||
<div class="email-section">
|
||||
<p class="email-section-title">%s</p>
|
||||
%s
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<p class="email-footer-text">%s</p>
|
||||
<p class="email-footer-copyright">© %s Swissmakers GmbH. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`, html.EscapeString(title), html.EscapeString(intro), detailRows, html.EscapeString(whoisTitle), whoisHTML, html.EscapeString(logsTitle), logsHTML, html.EscapeString(footerText), year)
|
||||
}
|
||||
|
||||
// buildModernEmailBody creates the modern responsive email template (new design)
|
||||
func buildModernEmailBody(title, intro string, details []emailDetail, whoisHTML, logsHTML, whoisTitle, logsTitle, footerText string) string {
|
||||
detailRows := renderEmailDetails(details)
|
||||
@@ -1497,38 +1605,112 @@ func sendBanAlert(ip, jail, hostname, failures, whois, logs, country string, set
|
||||
lang = "en"
|
||||
}
|
||||
|
||||
// Get translations
|
||||
subject := fmt.Sprintf("[Fail2Ban] %s: %s %s %s %s", jail,
|
||||
getEmailTranslation(lang, "email.ban.subject.banned"),
|
||||
ip,
|
||||
getEmailTranslation(lang, "email.ban.subject.from"),
|
||||
hostname)
|
||||
// Check if LOTR mode is active for subject line
|
||||
isLOTRMode := isLOTRModeActive(settings.AlertCountries)
|
||||
|
||||
details := []emailDetail{
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.banned_ip"), Value: ip},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.jail"), Value: jail},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.hostname"), Value: hostname},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.failed_attempts"), Value: failures},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.country"), Value: country},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.timestamp"), Value: time.Now().UTC().Format(time.RFC3339)},
|
||||
// Get translations
|
||||
var subject string
|
||||
if isLOTRMode {
|
||||
subject = fmt.Sprintf("[Middle-earth] The Dark Lord's Servant Has Been Banished: %s from %s", ip, hostname)
|
||||
} else {
|
||||
subject = fmt.Sprintf("[Fail2Ban] %s: %s %s %s %s", jail,
|
||||
getEmailTranslation(lang, "email.ban.subject.banned"),
|
||||
ip,
|
||||
getEmailTranslation(lang, "email.ban.subject.from"),
|
||||
hostname)
|
||||
}
|
||||
|
||||
title := getEmailTranslation(lang, "email.ban.title")
|
||||
intro := getEmailTranslation(lang, "email.ban.intro")
|
||||
whoisTitle := getEmailTranslation(lang, "email.ban.whois_title")
|
||||
logsTitle := getEmailTranslation(lang, "email.ban.logs_title")
|
||||
footerText := getEmailTranslation(lang, "email.footer.text")
|
||||
supportEmail := "support@swissmakers.ch"
|
||||
|
||||
// Determine email style
|
||||
// Determine email style and LOTR mode
|
||||
emailStyle := getEmailStyle()
|
||||
isModern := emailStyle == "modern"
|
||||
|
||||
// Get translations - use LOTR translations if in LOTR mode
|
||||
var title, intro, whoisTitle, logsTitle, footerText string
|
||||
if isLOTRMode {
|
||||
title = getEmailTranslation(lang, "lotr.email.title")
|
||||
if title == "lotr.email.title" {
|
||||
title = "A Dark Servant Has Been Banished"
|
||||
}
|
||||
intro = getEmailTranslation(lang, "lotr.email.intro")
|
||||
if intro == "lotr.email.intro" {
|
||||
intro = "The guardians of Middle-earth have detected a threat and banished it from the realm."
|
||||
}
|
||||
whoisTitle = getEmailTranslation(lang, "email.ban.whois_title")
|
||||
logsTitle = getEmailTranslation(lang, "email.ban.logs_title")
|
||||
footerText = getEmailTranslation(lang, "lotr.email.footer")
|
||||
if footerText == "lotr.email.footer" {
|
||||
footerText = "May the servers be protected. One ban to rule them all."
|
||||
}
|
||||
} else {
|
||||
title = getEmailTranslation(lang, "email.ban.title")
|
||||
intro = getEmailTranslation(lang, "email.ban.intro")
|
||||
whoisTitle = getEmailTranslation(lang, "email.ban.whois_title")
|
||||
logsTitle = getEmailTranslation(lang, "email.ban.logs_title")
|
||||
footerText = getEmailTranslation(lang, "email.footer.text")
|
||||
}
|
||||
supportEmail := "support@swissmakers.ch"
|
||||
|
||||
// Format details with LOTR terminology if in LOTR mode
|
||||
var details []emailDetail
|
||||
if isLOTRMode {
|
||||
// Transform labels to LOTR terminology
|
||||
bannedIPLabel := getEmailTranslation(lang, "lotr.email.details.dark_servant_location")
|
||||
if bannedIPLabel == "lotr.email.details.dark_servant_location" {
|
||||
bannedIPLabel = "The Dark Servant's Location"
|
||||
}
|
||||
jailLabel := getEmailTranslation(lang, "lotr.email.details.realm_protection")
|
||||
if jailLabel == "lotr.email.details.realm_protection" {
|
||||
jailLabel = "The Realm of Protection"
|
||||
}
|
||||
countryLabelKey := getEmailTranslation(lang, "lotr.email.details.origins")
|
||||
var countryLabel string
|
||||
if countryLabelKey == "lotr.email.details.origins" {
|
||||
// Use default English format
|
||||
if country != "" {
|
||||
countryLabel = fmt.Sprintf("Origins from the %s Lands", country)
|
||||
} else {
|
||||
countryLabel = "Origins from Unknown Lands"
|
||||
}
|
||||
} else {
|
||||
// Use translated label and append country
|
||||
if country != "" {
|
||||
countryLabel = fmt.Sprintf("%s %s", countryLabelKey, country)
|
||||
} else {
|
||||
countryLabel = fmt.Sprintf("%s Unknown", countryLabelKey)
|
||||
}
|
||||
}
|
||||
timestampLabel := getEmailTranslation(lang, "lotr.email.details.banished_at")
|
||||
if timestampLabel == "lotr.email.details.banished_at" {
|
||||
timestampLabel = "Banished at the"
|
||||
}
|
||||
|
||||
details = []emailDetail{
|
||||
{Label: bannedIPLabel, Value: ip},
|
||||
{Label: jailLabel, Value: jail},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.hostname"), Value: hostname},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.failed_attempts"), Value: failures},
|
||||
{Label: countryLabel, Value: ""},
|
||||
{Label: timestampLabel, Value: time.Now().UTC().Format(time.RFC3339)},
|
||||
}
|
||||
} else {
|
||||
details = []emailDetail{
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.banned_ip"), Value: ip},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.jail"), Value: jail},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.hostname"), Value: hostname},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.failed_attempts"), Value: failures},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.country"), Value: country},
|
||||
{Label: getEmailTranslation(lang, "email.ban.details.timestamp"), Value: time.Now().UTC().Format(time.RFC3339)},
|
||||
}
|
||||
}
|
||||
|
||||
whoisHTML := formatWhoisForEmail(whois, lang, isModern)
|
||||
logsHTML := formatLogsForEmail(ip, logs, lang, isModern)
|
||||
|
||||
var body string
|
||||
if isModern {
|
||||
if isLOTRMode {
|
||||
// Use LOTR-themed email template
|
||||
body = buildLOTREmailBody(title, intro, details, whoisHTML, logsHTML, whoisTitle, logsTitle, footerText)
|
||||
} else if isModern {
|
||||
body = buildModernEmailBody(title, intro, details, whoisHTML, logsHTML, whoisTitle, logsTitle, footerText)
|
||||
} else {
|
||||
body = buildClassicEmailBody(title, intro, details, whoisHTML, logsHTML, whoisTitle, logsTitle, footerText, supportEmail)
|
||||
|
||||
439
pkg/web/static/lotr-theme.css
Normal file
439
pkg/web/static/lotr-theme.css
Normal file
@@ -0,0 +1,439 @@
|
||||
/* ============================================
|
||||
LOTR Easter Egg Theme - Middle-earth Styling
|
||||
============================================ */
|
||||
|
||||
/* Only apply when body has lotr-mode class */
|
||||
body.lotr-mode {
|
||||
/* Color Variables */
|
||||
--lotr-forest-green: #1a4d2e;
|
||||
--lotr-dark-green: #0d2818;
|
||||
--lotr-gold: #d4af37;
|
||||
--lotr-dark-gold: #b8941f;
|
||||
--lotr-brown: #3d2817;
|
||||
--lotr-stone-gray: #8b7355;
|
||||
--lotr-parchment: #f4e8d0;
|
||||
--lotr-dark-parchment: #e8d5b7;
|
||||
--lotr-purple: #4a148c;
|
||||
--lotr-dark-purple: #2d0a4f;
|
||||
--lotr-fire-orange: #ff6b35;
|
||||
--lotr-fire-red: #c1121f;
|
||||
}
|
||||
|
||||
/* Base Theme Overrides */
|
||||
body.lotr-mode {
|
||||
background: linear-gradient(135deg, var(--lotr-dark-green) 0%, var(--lotr-forest-green) 50%, var(--lotr-brown) 100%);
|
||||
background-attachment: fixed;
|
||||
color: var(--lotr-parchment);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
body.lotr-mode h1,
|
||||
body.lotr-mode h2,
|
||||
body.lotr-mode h3,
|
||||
body.lotr-mode .text-2xl,
|
||||
body.lotr-mode .text-xl {
|
||||
font-family: 'Cinzel', serif;
|
||||
color: var(--lotr-gold);
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
body.lotr-mode h1 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
body.lotr-mode h2,
|
||||
body.lotr-mode h3 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Cards - Parchment Style */
|
||||
body.lotr-mode .bg-white {
|
||||
background: var(--lotr-parchment) !important;
|
||||
border: 3px solid var(--lotr-gold);
|
||||
border-radius: 8px;
|
||||
box-shadow:
|
||||
0 4px 6px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.lotr-mode .bg-white::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(139, 115, 85, 0.03) 2px,
|
||||
rgba(139, 115, 85, 0.03) 4px
|
||||
);
|
||||
pointer-events: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
body.lotr-mode .bg-white .text-gray-800,
|
||||
body.lotr-mode .bg-white .text-gray-900 {
|
||||
color: var(--lotr-brown) !important;
|
||||
}
|
||||
|
||||
body.lotr-mode .bg-white .text-gray-700 {
|
||||
color: var(--lotr-brown) !important;
|
||||
}
|
||||
|
||||
/* Buttons - Medieval Shield Style */
|
||||
body.lotr-mode button,
|
||||
body.lotr-mode .bg-blue-500,
|
||||
body.lotr-mode .bg-blue-600 {
|
||||
background: linear-gradient(135deg, var(--lotr-gold) 0%, var(--lotr-dark-gold) 100%) !important;
|
||||
border: 2px solid var(--lotr-brown) !important;
|
||||
color: var(--lotr-brown) !important;
|
||||
font-weight: 600;
|
||||
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.3);
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
body.lotr-mode button:hover,
|
||||
body.lotr-mode .bg-blue-500:hover,
|
||||
body.lotr-mode .bg-blue-600:hover {
|
||||
background: linear-gradient(135deg, var(--lotr-dark-gold) 0%, var(--lotr-gold) 100%) !important;
|
||||
box-shadow:
|
||||
0 4px 8px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
body.lotr-mode button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Input Fields - Scroll Style */
|
||||
body.lotr-mode input,
|
||||
body.lotr-mode select,
|
||||
body.lotr-mode textarea {
|
||||
background: var(--lotr-dark-parchment) !important;
|
||||
border: 2px solid var(--lotr-stone-gray) !important;
|
||||
color: var(--lotr-brown) !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
body.lotr-mode input:focus,
|
||||
body.lotr-mode select:focus,
|
||||
body.lotr-mode textarea:focus {
|
||||
border-color: var(--lotr-gold) !important;
|
||||
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.2) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
body.lotr-mode nav {
|
||||
background: linear-gradient(135deg, var(--lotr-brown) 0%, var(--lotr-dark-green) 100%) !important;
|
||||
border-bottom: 3px solid var(--lotr-gold);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
body.lotr-mode nav .text-gray-700,
|
||||
body.lotr-mode nav .text-gray-800 {
|
||||
color: var(--lotr-gold) !important;
|
||||
}
|
||||
|
||||
/* Loading Spinner - One Ring Animation */
|
||||
body.lotr-mode #loading-overlay .animate-spin {
|
||||
border-color: var(--lotr-gold);
|
||||
border-top-color: transparent;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.lotr-mode #loading-overlay .animate-spin::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--lotr-gold);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 0 20px var(--lotr-gold),
|
||||
inset 0 0 20px var(--lotr-gold);
|
||||
animation: ringGlow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ringGlow {
|
||||
0%, 100% {
|
||||
opacity: 0.6;
|
||||
box-shadow:
|
||||
0 0 20px var(--lotr-gold),
|
||||
inset 0 0 20px var(--lotr-gold);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
box-shadow:
|
||||
0 0 40px var(--lotr-gold),
|
||||
0 0 60px var(--lotr-gold),
|
||||
inset 0 0 30px var(--lotr-gold);
|
||||
}
|
||||
}
|
||||
|
||||
/* Toast Notifications - Scroll Style */
|
||||
body.lotr-mode .toast {
|
||||
background: var(--lotr-parchment) !important;
|
||||
border: 2px solid var(--lotr-gold);
|
||||
color: var(--lotr-brown) !important;
|
||||
box-shadow:
|
||||
0 4px 12px rgba(0, 0, 0, 0.4),
|
||||
inset 0 0 20px rgba(212, 175, 55, 0.1);
|
||||
position: relative;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
body.lotr-mode .toast::before {
|
||||
content: '⚔';
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body.lotr-mode .toast-success {
|
||||
border-color: var(--lotr-forest-green);
|
||||
}
|
||||
|
||||
body.lotr-mode .toast-error {
|
||||
border-color: var(--lotr-fire-red);
|
||||
}
|
||||
|
||||
body.lotr-mode .toast-info {
|
||||
border-color: var(--lotr-gold);
|
||||
}
|
||||
|
||||
/* Dashboard Cards - Medieval Banners */
|
||||
body.lotr-mode .bg-blue-50,
|
||||
body.lotr-mode .bg-gray-50 {
|
||||
background: var(--lotr-dark-parchment) !important;
|
||||
}
|
||||
|
||||
body.lotr-mode .text-blue-600 {
|
||||
color: var(--lotr-gold) !important;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
body.lotr-mode table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
body.lotr-mode table th {
|
||||
background: linear-gradient(135deg, var(--lotr-brown) 0%, var(--lotr-stone-gray) 100%);
|
||||
color: var(--lotr-gold);
|
||||
border: 2px solid var(--lotr-gold);
|
||||
font-family: 'Cinzel', serif;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body.lotr-mode table td {
|
||||
background: var(--lotr-parchment);
|
||||
border: 1px solid var(--lotr-stone-gray);
|
||||
color: var(--lotr-brown);
|
||||
}
|
||||
|
||||
body.lotr-mode table tr:hover td {
|
||||
background: var(--lotr-dark-parchment);
|
||||
}
|
||||
|
||||
/* Modal - Parchment Scroll */
|
||||
body.lotr-mode .modal-content {
|
||||
background: var(--lotr-parchment) !important;
|
||||
border: 4px solid var(--lotr-gold);
|
||||
border-radius: 12px;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.5),
|
||||
inset 0 0 40px rgba(212, 175, 55, 0.1);
|
||||
}
|
||||
|
||||
body.lotr-mode .modal-content h2,
|
||||
body.lotr-mode .modal-content h3 {
|
||||
border-bottom: 2px solid var(--lotr-gold);
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Badges and Labels */
|
||||
body.lotr-mode .bg-green-100,
|
||||
body.lotr-mode .bg-green-500 {
|
||||
background: var(--lotr-forest-green) !important;
|
||||
color: var(--lotr-gold) !important;
|
||||
}
|
||||
|
||||
body.lotr-mode .bg-red-100,
|
||||
body.lotr-mode .bg-red-500 {
|
||||
background: var(--lotr-fire-red) !important;
|
||||
color: var(--lotr-parchment) !important;
|
||||
}
|
||||
|
||||
body.lotr-mode .bg-yellow-400 {
|
||||
background: var(--lotr-gold) !important;
|
||||
color: var(--lotr-brown) !important;
|
||||
}
|
||||
|
||||
/* Restart Banner */
|
||||
body.lotr-mode #restartBanner {
|
||||
background: linear-gradient(135deg, var(--lotr-gold) 0%, var(--lotr-dark-gold) 100%) !important;
|
||||
border-top: 3px solid var(--lotr-brown);
|
||||
border-bottom: 3px solid var(--lotr-brown);
|
||||
color: var(--lotr-brown) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Select2 Styling */
|
||||
body.lotr-mode .select2-container--default .select2-selection {
|
||||
background: var(--lotr-dark-parchment) !important;
|
||||
border: 2px solid var(--lotr-stone-gray) !important;
|
||||
color: var(--lotr-brown) !important;
|
||||
}
|
||||
|
||||
body.lotr-mode .select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||
background: var(--lotr-gold) !important;
|
||||
border: 1px solid var(--lotr-brown) !important;
|
||||
color: var(--lotr-brown) !important;
|
||||
}
|
||||
|
||||
/* Scrollbar Styling */
|
||||
body.lotr-mode ::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
body.lotr-mode ::-webkit-scrollbar-track {
|
||||
background: var(--lotr-dark-green);
|
||||
border: 1px solid var(--lotr-stone-gray);
|
||||
}
|
||||
|
||||
body.lotr-mode ::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(135deg, var(--lotr-gold) 0%, var(--lotr-dark-gold) 100%);
|
||||
border: 2px solid var(--lotr-brown);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
body.lotr-mode ::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(135deg, var(--lotr-dark-gold) 0%, var(--lotr-gold) 100%);
|
||||
}
|
||||
|
||||
/* Decorative Elements */
|
||||
body.lotr-mode .lotr-divider {
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
var(--lotr-gold) 20%,
|
||||
var(--lotr-gold) 80%,
|
||||
transparent 100%);
|
||||
margin: 20px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.lotr-mode .lotr-divider::before,
|
||||
body.lotr-mode .lotr-divider::after {
|
||||
content: '⚔';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--lotr-gold);
|
||||
font-size: 20px;
|
||||
background: var(--lotr-parchment);
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
body.lotr-mode .lotr-divider::before {
|
||||
left: 20%;
|
||||
}
|
||||
|
||||
body.lotr-mode .lotr-divider::after {
|
||||
right: 20%;
|
||||
}
|
||||
|
||||
/* Glow Effects */
|
||||
body.lotr-mode .lotr-glow {
|
||||
text-shadow:
|
||||
0 0 10px var(--lotr-gold),
|
||||
0 0 20px var(--lotr-gold),
|
||||
0 0 30px var(--lotr-gold);
|
||||
animation: gentleGlow 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gentleGlow {
|
||||
0%, 100% {
|
||||
text-shadow:
|
||||
0 0 10px var(--lotr-gold),
|
||||
0 0 20px var(--lotr-gold),
|
||||
0 0 30px var(--lotr-gold);
|
||||
}
|
||||
50% {
|
||||
text-shadow:
|
||||
0 0 15px var(--lotr-gold),
|
||||
0 0 30px var(--lotr-gold),
|
||||
0 0 45px var(--lotr-gold);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fire Effect (for email headers) */
|
||||
body.lotr-mode .lotr-fire {
|
||||
background: linear-gradient(180deg,
|
||||
var(--lotr-fire-red) 0%,
|
||||
var(--lotr-fire-orange) 50%,
|
||||
var(--lotr-gold) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 0 20px var(--lotr-fire-orange);
|
||||
animation: fireFlicker 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes fireFlicker {
|
||||
0%, 100% {
|
||||
filter: brightness(1);
|
||||
}
|
||||
25% {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
50% {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
75% {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth Theme Transition */
|
||||
body.lotr-mode,
|
||||
body.lotr-mode * {
|
||||
transition: background-color 0.5s ease,
|
||||
color 0.5s ease,
|
||||
border-color 0.5s ease;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
body.lotr-mode .bg-white {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
body.lotr-mode h1,
|
||||
body.lotr-mode h2,
|
||||
body.lotr-mode h3 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" />
|
||||
<!-- Fail2ban UI CSS -->
|
||||
<link rel="stylesheet" href="/static/fail2ban-ui.css">
|
||||
<!-- LOTR Theme CSS (loaded conditionally) -->
|
||||
<link rel="stylesheet" href="/static/lotr-theme.css" id="lotr-theme-css" disabled>
|
||||
<!-- Google Fonts for LOTR theme -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=MedievalSharp&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-50 overflow-y-scroll">
|
||||
@@ -1798,6 +1804,10 @@
|
||||
if (typeof updateTranslations === 'function') {
|
||||
updateTranslations();
|
||||
}
|
||||
// Update LOTR terminology if active
|
||||
if (isLOTRModeActive) {
|
||||
updateDashboardLOTRTerminology(true);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLogOverviewSection() {
|
||||
@@ -2554,7 +2564,10 @@
|
||||
//*******************************************************************
|
||||
|
||||
function unbanIP(jail, ip) {
|
||||
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
|
||||
const confirmMsg = isLOTRModeActive
|
||||
? `Restore ${ip} to the realm from ${jail}?`
|
||||
: `Unban IP ${ip} from jail ${jail}?`;
|
||||
if (!confirm(confirmMsg)) {
|
||||
return;
|
||||
}
|
||||
showLoading(true);
|
||||
@@ -2992,6 +3005,153 @@
|
||||
});
|
||||
}
|
||||
|
||||
//*******************************************************************
|
||||
//* LOTR Mode Detection and Management : *
|
||||
//*******************************************************************
|
||||
|
||||
// Global variable to track LOTR mode state
|
||||
let isLOTRModeActive = false;
|
||||
|
||||
// Check if LOTR mode should be active based on alertCountries
|
||||
function isLOTRMode(alertCountries) {
|
||||
if (!alertCountries || !Array.isArray(alertCountries)) {
|
||||
return false;
|
||||
}
|
||||
return alertCountries.includes('LOTR');
|
||||
}
|
||||
|
||||
// Apply or remove LOTR theme
|
||||
function applyLOTRTheme(active) {
|
||||
const body = document.body;
|
||||
const lotrCSS = document.getElementById('lotr-theme-css');
|
||||
|
||||
if (active) {
|
||||
body.classList.add('lotr-mode');
|
||||
if (lotrCSS) {
|
||||
lotrCSS.disabled = false;
|
||||
}
|
||||
isLOTRModeActive = true;
|
||||
console.log('🎭 LOTR Mode Activated - Welcome to Middle-earth!');
|
||||
} else {
|
||||
body.classList.remove('lotr-mode');
|
||||
if (lotrCSS) {
|
||||
lotrCSS.disabled = true;
|
||||
}
|
||||
isLOTRModeActive = false;
|
||||
console.log('🎭 LOTR Mode Deactivated');
|
||||
}
|
||||
}
|
||||
|
||||
// Check and apply LOTR theme based on current settings
|
||||
function checkAndApplyLOTRTheme(alertCountries) {
|
||||
const shouldBeActive = isLOTRMode(alertCountries);
|
||||
if (shouldBeActive !== isLOTRModeActive) {
|
||||
applyLOTRTheme(shouldBeActive);
|
||||
updateLOTRTerminology(shouldBeActive);
|
||||
}
|
||||
}
|
||||
|
||||
// Update UI terminology when LOTR mode is active
|
||||
function updateLOTRTerminology(active) {
|
||||
if (active) {
|
||||
// Update navigation title
|
||||
const navTitle = document.querySelector('nav .text-xl');
|
||||
if (navTitle) {
|
||||
navTitle.textContent = 'Middle-earth Security';
|
||||
}
|
||||
|
||||
// Update page title
|
||||
const pageTitle = document.querySelector('title');
|
||||
if (pageTitle) {
|
||||
pageTitle.textContent = 'Middle-earth Security Realm';
|
||||
}
|
||||
|
||||
// Update dashboard terminology
|
||||
updateDashboardLOTRTerminology(true);
|
||||
|
||||
// Add decorative elements
|
||||
addLOTRDecorations();
|
||||
} else {
|
||||
// Restore original text
|
||||
const navTitle = document.querySelector('nav .text-xl');
|
||||
if (navTitle) {
|
||||
navTitle.textContent = 'Fail2ban UI';
|
||||
}
|
||||
|
||||
const pageTitle = document.querySelector('title');
|
||||
if (pageTitle && pageTitle.hasAttribute('data-i18n')) {
|
||||
const i18nKey = pageTitle.getAttribute('data-i18n');
|
||||
pageTitle.textContent = t(i18nKey, 'Fail2ban UI Dashboard');
|
||||
}
|
||||
|
||||
// Restore dashboard terminology
|
||||
updateDashboardLOTRTerminology(false);
|
||||
|
||||
// Remove decorative elements
|
||||
removeLOTRDecorations();
|
||||
}
|
||||
}
|
||||
|
||||
// Update dashboard terminology for LOTR mode
|
||||
function updateDashboardLOTRTerminology(active) {
|
||||
// Update text elements that use data-i18n
|
||||
const elements = document.querySelectorAll('[data-i18n]');
|
||||
elements.forEach(el => {
|
||||
const i18nKey = el.getAttribute('data-i18n');
|
||||
if (active) {
|
||||
// Check for LOTR-specific translations
|
||||
if (i18nKey === 'dashboard.cards.total_banned') {
|
||||
el.textContent = t('lotr.threats_banished', 'Threats Banished');
|
||||
} else if (i18nKey === 'dashboard.table.banned_ips') {
|
||||
el.textContent = t('lotr.threats_banished', 'Threats Banished');
|
||||
} else if (i18nKey === 'dashboard.search_label') {
|
||||
el.textContent = t('lotr.threats_banished', 'Search Banished Threats');
|
||||
} else if (i18nKey === 'dashboard.manage_servers') {
|
||||
el.textContent = t('lotr.realms_protected', 'Manage Realms');
|
||||
}
|
||||
} else {
|
||||
// Restore original translations
|
||||
if (i18nKey) {
|
||||
el.textContent = t(i18nKey, el.textContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update "Unban" buttons
|
||||
const unbanButtons = document.querySelectorAll('button, a');
|
||||
unbanButtons.forEach(btn => {
|
||||
if (btn.textContent && btn.textContent.includes('Unban')) {
|
||||
if (active) {
|
||||
btn.textContent = btn.textContent.replace(/Unban/gi, t('lotr.banished', 'Restore to Realm'));
|
||||
} else {
|
||||
btn.textContent = btn.textContent.replace(/Restore to Realm/gi, t('dashboard.unban', 'Unban'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add LOTR decorative elements to the UI
|
||||
function addLOTRDecorations() {
|
||||
// Add decorative divider to settings section if not already present
|
||||
const settingsSection = document.getElementById('settingsSection');
|
||||
if (settingsSection && !settingsSection.querySelector('.lotr-divider')) {
|
||||
const divider = document.createElement('div');
|
||||
divider.className = 'lotr-divider';
|
||||
divider.style.marginTop = '20px';
|
||||
divider.style.marginBottom = '20px';
|
||||
const firstChild = settingsSection.querySelector('.bg-white');
|
||||
if (firstChild) {
|
||||
settingsSection.insertBefore(divider, firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove LOTR decorative elements
|
||||
function removeLOTRDecorations() {
|
||||
const dividers = document.querySelectorAll('.lotr-divider');
|
||||
dividers.forEach(div => div.remove());
|
||||
}
|
||||
|
||||
//*******************************************************************
|
||||
//* Load current settings when opening settings page : *
|
||||
//*******************************************************************
|
||||
@@ -3063,6 +3223,9 @@
|
||||
}
|
||||
}
|
||||
$('#alertCountries').trigger('change');
|
||||
|
||||
// Check and apply LOTR theme
|
||||
checkAndApplyLOTRTheme(data.alertCountries || []);
|
||||
|
||||
if (data.smtp) {
|
||||
document.getElementById('smtpHost').value = data.smtp.host || '';
|
||||
@@ -3145,6 +3308,11 @@
|
||||
var selectedLang = $('#languageSelect').val();
|
||||
loadTranslations(selectedLang);
|
||||
console.log("Settings saved successfully. Restart needed? " + data.restartNeeded);
|
||||
|
||||
// Check and apply LOTR theme after saving
|
||||
const selectedCountries = Array.from(document.getElementById('alertCountries').selectedOptions).map(opt => opt.value);
|
||||
checkAndApplyLOTRTheme(selectedCountries.length > 0 ? selectedCountries : ["ALL"]);
|
||||
|
||||
showToast(t('settings.save_success', 'Settings saved'), 'success');
|
||||
if (data.restartNeeded) {
|
||||
loadServers().then(function() {
|
||||
@@ -3557,6 +3725,19 @@
|
||||
$('#alertCountries').val(newValues).trigger('change');
|
||||
}
|
||||
}
|
||||
// Check LOTR mode after selection change
|
||||
setTimeout(function() {
|
||||
const selectedCountries = $('#alertCountries').val() || [];
|
||||
checkAndApplyLOTRTheme(selectedCountries);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
$('#alertCountries').on('select2:unselect', function(e) {
|
||||
// Check LOTR mode after deselection
|
||||
setTimeout(function() {
|
||||
const selectedCountries = $('#alertCountries').val() || [];
|
||||
checkAndApplyLOTRTheme(selectedCountries);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
var sshKeySelect = document.getElementById('serverSSHKeySelect');
|
||||
|
||||
Reference in New Issue
Block a user