Compare commits

...

2 Commits

Author SHA1 Message Date
Florian Walther
13c791b1ae added password counter
All checks were successful
Docker Release Build / push_to_registry (push) Successful in 58s
2026-02-07 23:45:57 +01:00
Florian Walther
8869db0f6b screenshot auf v0.6.0 altualisiert 2026-02-07 16:49:46 +01:00
7 changed files with 153 additions and 31 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 33 KiB

72
main.go
View File

@@ -1,10 +1,15 @@
package main package main
import ( import (
"os"
"strconv"
"strings"
"sync"
"crypto/rand" "crypto/rand"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"encoding/json"
) )
const ( const (
@@ -12,15 +17,47 @@ const (
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
) )
var templates = make(map[string]*template.Template) var (
var AppVersion = "development" 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() { func loadTemplates() {
// 1. FuncMap definieren // 1. FuncMap definieren
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"getAppVersion": func() string { "getAppVersion": func() string { return AppVersion },
return AppVersion "getPassCount": func() int { return GetPasswordCount() },
},
} }
// 2. Templates mit FuncMap laden // 2. Templates mit FuncMap laden
@@ -39,6 +76,7 @@ func loadTemplates() {
} }
func generatePassword() string { func generatePassword() string {
log.Printf("called generatePassword\n")
password := make([]byte, passwordLength) password := make([]byte, passwordLength)
_, err := rand.Read(password) _, err := rand.Read(password)
if err != nil { if err != nil {
@@ -47,10 +85,29 @@ func generatePassword() string {
for i := 0; i < passwordLength; i++ { for i := 0; i < passwordLength; i++ {
password[i] = chars[int(password[i])%len(chars)] password[i] = chars[int(password[i])%len(chars)]
} }
IncrementPasswordCount()
return string(password) return string(password)
} }
func passwordHandler(w http.ResponseWriter, r *http.Request) { 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() password := generatePassword()
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(password)) w.Write([]byte(password))
@@ -59,11 +116,13 @@ func passwordHandler(w http.ResponseWriter, r *http.Request) {
func indexHandler(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) log.Printf("call indexHandler: Request %s %s\n", r.Method, r.URL)
password := generatePassword() password := generatePassword()
//password := "load..."
data := struct { data := struct {
Password string Password string
}{ }{
Password: password, Password: password,
} }
log.Printf("prepare template for index\n")
err := templates["index.html"].ExecuteTemplate(w, "base.html", data) err := templates["index.html"].ExecuteTemplate(w, "base.html", data)
if err != nil { if err != nil {
log.Printf("Fehler beim Rendern des Templates: %v", err) log.Printf("Fehler beim Rendern des Templates: %v", err)
@@ -86,7 +145,8 @@ func main() {
http.Handle("/static/", http.StripPrefix("/static/", fs)) http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/", indexHandler) http.HandleFunc("/", indexHandler)
http.HandleFunc("/api/password", passwordHandler) http.HandleFunc("/api/password", passwordAPIHandler)
http.HandleFunc("/json/password", passwordHandler)
http.HandleFunc("/help", helpHandler) http.HandleFunc("/help", helpHandler)
log.Println("Server läuft auf http://localhost:8080") log.Println("Server läuft auf http://localhost:8080")

View File

@@ -3,6 +3,8 @@ services:
image: gitea.scu.si/florian.walther/password-generator:latest image: gitea.scu.si/florian.walther/password-generator:latest
container_name: password-generator container_name: password-generator
restart: always restart: always
volumes:
- ./app_data:/data
expose: expose:
- "8080:8080" - "8080:8080"
labels: labels:

View File

@@ -3,6 +3,8 @@ services:
image: gitea.scu.si/florian.walther/password-generator:latest image: gitea.scu.si/florian.walther/password-generator:latest
container_name: password-generator container_name: password-generator
restart: always restart: always
volumes:
- ./app_data:/data
ports: ports:
- "8080:8080" - "8080:8080"
# Falls die Registry privat ist, muss der Host zuvor mit # Falls die Registry privat ist, muss der Host zuvor mit

View File

@@ -34,6 +34,7 @@ body {
background-color: var(--bg-color); background-color: var(--bg-color);
color: var(--text-color); color: var(--text-color);
transition: background-color 0.3s, color 0.3s; transition: background-color 0.3s, color 0.3s;
margin-bottom: 60px;
} }
.container { .container {
@@ -48,30 +49,63 @@ body {
} }
footer { footer {
/* Fixierung am unteren Rand */
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
/* Ausdehnung */
width: 100%; width: 100%;
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;
}
/* Design & Abstände */ .footer-container {
background: var(--password-bg); display: flex;
border-top: 1px solid #e0e0e0; justify-content: flex-end; /* Schiebt alles nach rechts */
padding: 8px 16px; gap: 20px; /* Abstand zwischen Counter und Version */
align-items: center;
}
/* Text-Ausrichtung */ .footer-item {
text-align: left; 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-family: monospace; /* Monospace sieht für Versionen oft "technischer" aus */
font-size: 12px; font-size: 12px;
color: var(--text-color); color: var(--text-color);
/* Sicherstellen, dass nichts drüber liegt */
z-index: 9999;
/* Padding in die Breite einrechnen */
box-sizing: border-box;
} }
#password { #password {

View File

@@ -46,6 +46,21 @@
<button id="theme-toggle">🌓</button> <button id="theme-toggle">🌓</button>
{{ block "body" . }}{{end}} {{ block "body" . }}{{end}}
<footer>Version: {{getAppVersion}} | made with golang and ♥️ {{ block "footer" . }}{{ end }}</footer> <!-- <footer>Version: {{getAppVersion}} | made with golang and ♥️ {{ block "footer" . }}{{ end }}</footer> -->
<footer>
<div class="footer-container">
<div class="footer-item">
<span class="label">Passwörter generiert:</span>
<span id="global-counter" class="value badge-blue">{{getPassCount}}</span>
</div>
<div class="footer-item">
<span class="label">Version:</span>
<span class="value badge-gray">{{getAppVersion}}</span>
</div>
<div class="footer-item">
<span class="claim">made with golang and ♥️ </span>
</div>
</div>
</footer>
</body> </body>
</html> </html>

View File

@@ -17,10 +17,19 @@
} }
function generateNewPassword() { function generateNewPassword() {
fetch("/api/password") fetch("/json/password")
.then(response => response.text()) .then(response => response.json()) // Jetzt .json() statt .text()
.then(password => { .then(data => {
document.getElementById("password").innerText = password; // 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)); .catch(error => console.error("Fehler:", error));
} }