diff --git a/main.go b/main.go index 6e72c78..2826527 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,15 @@ package main import ( + "os" + "strconv" + "strings" + "sync" "crypto/rand" "html/template" "log" "net/http" + "encoding/json" ) const ( @@ -12,15 +17,47 @@ const ( chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ) -var templates = make(map[string]*template.Template) -var AppVersion = "development" +var ( + templates = make(map[string]*template.Template) + AppVersion = "development" + counterFile = "/data/counter.txt" + mu sync.Mutex +) + +// Diese Funktion wird nur intern aufgerufen, wenn der Mutex bereits gesperrt ist +func getCount() int { + data, err := os.ReadFile(counterFile) + if err != nil { + return 0 + } + count, _ := strconv.Atoi(strings.TrimSpace(string(data))) + return count +} + +// Öffentliche Funktion für das Template (mit Lock) +func GetPasswordCount() int { + mu.Lock() + defer mu.Unlock() + return getCount() +} + +// Öffentliche Funktion zum Erhöhen (mit Lock) +func IncrementPasswordCount() { + mu.Lock() + defer mu.Unlock() + + // Wir rufen jetzt die interne Funktion auf, die NICHT versucht, + // den Mutex erneut zu sperren + count := getCount() + count++ + os.WriteFile(counterFile, []byte(strconv.Itoa(count)), 0644) +} func loadTemplates() { // 1. FuncMap definieren funcMap := template.FuncMap{ - "getAppVersion": func() string { - return AppVersion - }, + "getAppVersion": func() string { return AppVersion }, + "getPassCount": func() int { return GetPasswordCount() }, } // 2. Templates mit FuncMap laden @@ -39,6 +76,7 @@ func loadTemplates() { } func generatePassword() string { + log.Printf("called generatePassword\n") password := make([]byte, passwordLength) _, err := rand.Read(password) if err != nil { @@ -47,10 +85,29 @@ func generatePassword() string { for i := 0; i < passwordLength; i++ { password[i] = chars[int(password[i])%len(chars)] } + IncrementPasswordCount() return string(password) } func passwordHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("called passwordHandler\n") + password := generatePassword() + currentCount := GetPasswordCount() + response := map[string]interface{}{ + "password": password, + "count": currentCount, + } + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(response) + if err != nil { + log.Printf("Fehler beim Senden des JSON: %v", err) + http.Error(w, "Interner Fehler", http.StatusInternalServerError) + return + } +} + +func passwordAPIHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("called passwordHandler\n") password := generatePassword() w.Header().Set("Content-Type", "text/plain") w.Write([]byte(password)) @@ -59,11 +116,13 @@ func passwordHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) { log.Printf("call indexHandler: Request %s %s\n", r.Method, r.URL) password := generatePassword() + //password := "load..." data := struct { Password string }{ Password: password, } + log.Printf("prepare template for index\n") err := templates["index.html"].ExecuteTemplate(w, "base.html", data) if err != nil { log.Printf("Fehler beim Rendern des Templates: %v", err) @@ -86,7 +145,8 @@ func main() { http.Handle("/static/", http.StripPrefix("/static/", fs)) http.HandleFunc("/", indexHandler) - http.HandleFunc("/api/password", passwordHandler) + http.HandleFunc("/api/password", passwordAPIHandler) + http.HandleFunc("/json/password", passwordHandler) http.HandleFunc("/help", helpHandler) log.Println("Server läuft auf http://localhost:8080") diff --git a/misc/docker-compose.traefik.yml b/misc/docker-compose.traefik.yml index 8c8f90d..e42b662 100644 --- a/misc/docker-compose.traefik.yml +++ b/misc/docker-compose.traefik.yml @@ -3,6 +3,8 @@ services: image: gitea.scu.si/florian.walther/password-generator:latest container_name: password-generator restart: always + volumes: + - ./app_data:/data expose: - "8080:8080" labels: diff --git a/misc/docker-compose.yml b/misc/docker-compose.yml index e964b81..fd025f6 100644 --- a/misc/docker-compose.yml +++ b/misc/docker-compose.yml @@ -3,6 +3,8 @@ services: image: gitea.scu.si/florian.walther/password-generator:latest container_name: password-generator restart: always + volumes: + - ./app_data:/data ports: - "8080:8080" # Falls die Registry privat ist, muss der Host zuvor mit diff --git a/static/style.css b/static/style.css index e9d4eb8..8f29071 100644 --- a/static/style.css +++ b/static/style.css @@ -34,6 +34,7 @@ body { background-color: var(--bg-color); color: var(--text-color); transition: background-color 0.3s, color 0.3s; + margin-bottom: 60px; } .container { @@ -48,30 +49,63 @@ body { } footer { - /* Fixierung am unteren Rand */ position: fixed; bottom: 0; left: 0; - - /* Ausdehnung */ width: 100%; - - /* Design & Abstände */ - background: var(--password-bg); - border-top: 1px solid #e0e0e0; - padding: 8px 16px; - - /* Text-Ausrichtung */ - text-align: left; + background-color: var(--bg-color); + border-top: 1px solid var(--border-color); + padding: 10px 20px; + box-sizing: border-box; + box-shadow: 0 2px 10px var(--shadow-color); + z-index: 1000; +} + +.footer-container { + display: flex; + justify-content: flex-end; /* Schiebt alles nach rechts */ + gap: 20px; /* Abstand zwischen Counter und Version */ + align-items: center; +} + +.footer-item { + display: flex; + align-items: center; + gap: 8px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-size: 13px; + color:x#4b5563; +} + +.label { + font-weight: 500; +} + +/* Die Badges (Status-Pillen) */ +.value { + padding: 2px 8px; + border-radius: 12px; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; + font-weight: 600; + font-size: 11px; +} + +.badge-blue { + background-color: var(--bg-color); + color: var(--text-color); + border: 1px solid var(--border-color); +} + +.badge-gray { + background-color: var(--bg-color); + color: var(--text-color); + border: 1px solid var(border-color); +} + +.claim { font-family: monospace; /* Monospace sieht für Versionen oft "technischer" aus */ font-size: 12px; color: var(--text-color); - - /* Sicherstellen, dass nichts drüber liegt */ - z-index: 9999; - - /* Padding in die Breite einrechnen */ - box-sizing: border-box; } #password { diff --git a/templates/base.html b/templates/base.html index a14f214..0dfe32a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,6 +46,21 @@ {{ block "body" . }}{{end}} - + + diff --git a/templates/index.html b/templates/index.html index 3d756ed..9521f76 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,13 +17,22 @@ } function generateNewPassword() { - fetch("/api/password") - .then(response => response.text()) - .then(password => { - document.getElementById("password").innerText = password; - }) - .catch(error => console.error("Fehler:", error)); - } + fetch("/json/password") + .then(response => response.json()) // Jetzt .json() statt .text() + .then(data => { + // Passwort aktualisieren + document.getElementById("password").innerText = data.password; + + // Counter im Footer aktualisieren + // Wir suchen das Element mit der Klasse 'badge-blue' (oder gib ihm eine ID) + const counterElement = document.querySelector(".badge-blue"); + if (counterElement) { + //counterElement.innerText = data.count; + document.getElementById("global-counter").innerText = data.count; + } + }) + .catch(error => console.error("Fehler:", error)); +} {{ end }}