// ui_users.go — Settings → Users HTML handlers (admin-only). // // Routes (wired in server.go's admin band): // // GET /settings/users → handleUIUsersList (this task) // GET /settings/users/new → F2 // POST /settings/users/new → F2 // GET /settings/users/{id}/edit → F3 // POST /settings/users/{id}/edit → F3 // GET /settings/users/{id}/setup-link → F2 // POST /settings/users/{id}/disable → F3 // POST /settings/users/{id}/enable → F3 // POST /settings/users/{id}/regenerate-setup → F3 // POST /settings/users/{id}/force-logout → F3 package http import ( "log/slog" stdhttp "net/http" ) type usersPage struct { Users []userRow ShowDisabled bool } type userRow struct { ID string Username string Email string Role string LastLoginAt string // pre-formatted "2006-01-02 15:04:05" or "never" Disabled bool MustChangePassword bool } func (s *Server) handleUIUsersList(w stdhttp.ResponseWriter, r *stdhttp.Request) { u := s.requireUIUser(w, r) if u == nil { return } showDisabled := r.URL.Query().Get("show_disabled") == "1" users, err := s.deps.Store.ListUsers(r.Context()) if err != nil { slog.Error("ui users: list", "err", err) stdhttp.Error(w, "internal", stdhttp.StatusInternalServerError) return } rows := make([]userRow, 0, len(users)) for _, ux := range users { if !showDisabled && ux.DisabledAt != nil { continue } em := "" if ux.Email != nil { em = *ux.Email } ll := "never" if ux.LastLoginAt != nil { ll = ux.LastLoginAt.UTC().Format("2006-01-02 15:04:05") } rows = append(rows, userRow{ ID: ux.ID, Username: ux.Username, Email: em, Role: string(ux.Role), LastLoginAt: ll, Disabled: ux.DisabledAt != nil, MustChangePassword: ux.MustChangePassword, }) } view := s.baseView(r, u) view.Title = "Users · restic-manager" view.Active = "settings" view.Page = usersPage{Users: rows, ShowDisabled: showDisabled} if err := s.deps.UI.Render(w, "users", view); err != nil { slog.Error("ui users: render", "err", err) } }