diff --git a/backend/00-router.go b/backend/00-router.go index c505ae2..bdd6818 100644 --- a/backend/00-router.go +++ b/backend/00-router.go @@ -19,6 +19,7 @@ func RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/logout", logoutHandler) mux.HandleFunc("/users", usersHandler) mux.HandleFunc("/users/save", userSaveHandler) + mux.HandleFunc("/users/toggle-active", userToggleActiveHandler) mux.HandleFunc("/users/delete", userDeleteHandler) // Umzug (Konten pflegen + starten/beobachten) @@ -63,9 +64,9 @@ func renderShell(w http.ResponseWriter, active, readingPane string) { fmt.Fprintf(w, ` Mail-Graveyard - + - +
@@ -453,6 +454,46 @@ func userDeleteHandler(w http.ResponseWriter, r *http.Request) { redirectUsers(w, r, "", "Benutzer geloescht.") } +func userToggleActiveHandler(w http.ResponseWriter, r *http.Request) { + if !requireManager(w, r) { + return + } + if r.Method != http.MethodPost { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + username := strings.TrimSpace(r.FormValue("username")) + active := r.FormValue("active") == "1" + if username == "" { + redirectUsers(w, r, "", "Kein Benutzer gewaehlt.") + return + } + current := CurrentUser(r) + if strings.EqualFold(username, current.Username) && !active { + redirectUsers(w, r, username, "Eigenen Benutzer nicht sperren.") + return + } + target, err := GetAppUser(username) + if err != nil { + redirectUsers(w, r, username, err.Error()) + return + } + if !IsAdmin(r) && target.Role != roleUser { + redirectUsers(w, r, username, "Nur Admins duerfen Verwalter oder Admins sperren.") + return + } + if err := SaveAppUser(username, "", target.Role, active); err != nil { + redirectUsers(w, r, username, err.Error()) + return + } + if !active { + _, _ = DB.Exec(`DELETE FROM app_sessions WHERE user_id=?`, target.ID) + redirectUsers(w, r, username, "Benutzer gesperrt.") + return + } + redirectUsers(w, r, username, "Benutzer aktiviert.") +} + func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account, msg, errMsg string) { if edit.Name == "" { edit = Account{SrcPort: 993, SrcSecurity: "tls", SrcProto: "imap", DstPort: 993, DstSecurity: "tls", Active: true} @@ -462,9 +503,9 @@ func renderAccountsPage(w http.ResponseWriter, accounts []Account, edit Account, fmt.Fprintf(w, ` Konten-Verwaltung - Mail-Graveyard - + - +
@@ -507,9 +548,9 @@ func renderArchivesPage(w http.ResponseWriter, archives []string, accounts []Acc fmt.Fprintf(w, ` Archiv-mbox Verwaltung - Mail-Graveyard - + - +
@@ -555,9 +596,9 @@ func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, ed fmt.Fprintf(w, ` Benutzerverwaltung - Mail-Graveyard - + - +
@@ -625,9 +666,9 @@ func renderTransferPage(w http.ResponseWriter, mode transferMode, accounts []Acc fmt.Fprintf(w, ` %s - Mail-Graveyard - + - +
@@ -666,9 +707,9 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, accounts []Account) { fmt.Fprintf(w, ` Quell-Postfach - Mail-Graveyard - + - +
@@ -708,9 +749,9 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, accounts []Account) { fmt.Fprintf(w, ` Ziel-Postfach - Mail-Graveyard - + - +
@@ -966,11 +1007,21 @@ func renderUsersTable(w http.ResponseWriter, users []AppUser, editName string) { } status := "aktiv" if !u.Active { - status = "inaktiv" + status = "gesperrt" } + nextActive := "0" + toggleLabel := "Sperren" + toggleClass := "btn compact secondary" + if !u.Active { + nextActive = "1" + toggleLabel = "Aktivieren" + } + editButton := fmt.Sprintf(`Bearbeiten`, urlQuery(u.Username)) + toggleButton := fmt.Sprintf(`
`, + html.EscapeString(u.Username), nextActive, toggleClass, toggleLabel) deleteButton := fmt.Sprintf(`
`, html.EscapeString(u.Username)) - fmt.Fprintf(w, `%s%s%s%s`, - activeClass, urlQuery(u.Username), html.EscapeString(u.Username), html.EscapeString(u.Role), status, deleteButton) + fmt.Fprintf(w, `%s%s%s%s%s%s`, + activeClass, html.EscapeString(u.Username), html.EscapeString(u.Role), status, editButton, toggleButton, deleteButton) } fmt.Fprint(w, ``) } @@ -987,6 +1038,7 @@ func renderUserForm(w http.ResponseWriter, r *http.Request, u AppUser) {
Zugang +
%s
@@ -996,7 +1048,7 @@ func renderUserForm(w http.ResponseWriter, r *http.Request, u AppUser) { Neu
`, - html.EscapeString(u.Username), checked(u.Active), passwordPlaceholder(u.Username), roleField) + html.EscapeString(u.Username), checked(u.Active), passwordHint(u.Username), passwordPlaceholder(u.Username), roleField) } func roleSelect(current string, admin bool) string { @@ -1017,6 +1069,13 @@ func passwordPlaceholder(username string) string { return "leer lassen = behalten" } +func passwordHint(username string) string { + if username == "" { + return "Neuer Benutzer: Passwort eintragen." + } + return "Bearbeiten: Neues Passwort eintragen oder leer lassen, um das bestehende Passwort zu behalten." +} + func renderArchiveList(w http.ResponseWriter, archives []string, accounts []Account) { if len(archives) == 0 { fmt.Fprint(w, `
Noch keine Archiv-Mailbox angelegt.
`) diff --git a/backend/03-auth.go b/backend/03-auth.go index 1b222ff..6d3755f 100644 --- a/backend/03-auth.go +++ b/backend/03-auth.go @@ -169,7 +169,7 @@ func renderLoginPage(w http.ResponseWriter, errMsg string) { fmt.Fprintf(w, ` Login - Mail-Graveyard - +
diff --git a/frontend-js/dist/style.css b/frontend-js/dist/style.css index d71b061..da1de14 100644 --- a/frontend-js/dist/style.css +++ b/frontend-js/dist/style.css @@ -490,6 +490,11 @@ a.tree-node { display: block; color: inherit; text-decoration: none; } min-width: 204px; overflow: visible; } +.user-table .actions-col, +.user-table .row-actions { + width: 292px; + min-width: 292px; +} .account-table th { color: var(--ol-muted); font-weight: 600; @@ -550,6 +555,11 @@ a.tree-node { display: block; color: inherit; text-decoration: none; } } .form-actions a { text-decoration: none; } .btn.danger { color: #8a1f11; } +.form-hint { + color: var(--ol-muted); + margin: 0 0 10px; + line-height: 1.35; +} .archive-table { width: 100%; border-collapse: collapse;