]> source.dussan.org Git - gitea.git/commitdiff
User details page (#26713)
authorDenys Konovalov <kontakt@denyskon.de>
Thu, 31 Aug 2023 09:21:18 +0000 (11:21 +0200)
committerGitHub <noreply@github.com>
Thu, 31 Aug 2023 09:21:18 +0000 (11:21 +0200)
This PR implements a proposal to clean up the admin users table by
moving some information out to a separate user details page (which also
displays some additional information).

Other changes:
- move edit user page from `/admin/users/{id}` to
`/admin/users/{id}/edit` -> `/admin/users/{id}` now shows the user
details page
- show if user is instance administrator as a label instead of a
separate column
- separate explore users template into a page- and a shared one, to make
it possible to use it on the user details page
- fix issue where there was no margin between alert message and
following content on admin pages

<details>

<summary>Screenshots</summary>

![grafik](https://github.com/go-gitea/gitea/assets/47871822/1ad57ac9-f20a-45a4-8477-ffe572a41e9e)

![grafik](https://github.com/go-gitea/gitea/assets/47871822/25786ecd-cb9d-4c92-90f4-e7f4292c073b)

</details>

Partially resolves #25939

---------

Co-authored-by: Giteabot <teabot@gitea.io>
12 files changed:
options/locale/locale_en-US.ini
routers/web/admin/users.go
routers/web/web.go
templates/admin/layout_head.tmpl
templates/admin/user/list.tmpl
templates/admin/user/view.tmpl [new file with mode: 0644]
templates/admin/user/view_details.tmpl [new file with mode: 0644]
templates/admin/user/view_emails.tmpl [new file with mode: 0644]
templates/explore/user_list.tmpl [new file with mode: 0644]
templates/explore/users.tmpl
tests/integration/admin_user_test.go
web_src/css/admin.css

index f8e068fe19706e28df0a5fcde800f2e49a4a8be0..401692388e5d432316f03080fcc198569dd3131e 100644 (file)
@@ -2823,6 +2823,7 @@ users.list_status_filter.is_prohibit_login = Prohibit Login
 users.list_status_filter.not_prohibit_login = Allow Login
 users.list_status_filter.is_2fa_enabled = 2FA Enabled
 users.list_status_filter.not_2fa_enabled = 2FA Disabled
+users.details = User Details
 
 emails.email_manage_panel = User Email Management
 emails.primary = Primary
index e560a88b4cffdc706e461644d9e511e78b2faa91..61df49b85ba1dffac929e9078b49edfcd2e9f336 100644 (file)
@@ -13,6 +13,8 @@ import (
        "code.gitea.io/gitea/models"
        "code.gitea.io/gitea/models/auth"
        "code.gitea.io/gitea/models/db"
+       org_model "code.gitea.io/gitea/models/organization"
+       repo_model "code.gitea.io/gitea/models/repo"
        system_model "code.gitea.io/gitea/models/system"
        user_model "code.gitea.io/gitea/models/user"
        "code.gitea.io/gitea/modules/auth/password"
@@ -32,6 +34,7 @@ import (
 const (
        tplUsers    base.TplName = "admin/user/list"
        tplUserNew  base.TplName = "admin/user/new"
+       tplUserView base.TplName = "admin/user/view"
        tplUserEdit base.TplName = "admin/user/edit"
 )
 
@@ -249,6 +252,61 @@ func prepareUserInfo(ctx *context.Context) *user_model.User {
        return u
 }
 
+func ViewUser(ctx *context.Context) {
+       ctx.Data["Title"] = ctx.Tr("admin.users.details")
+       ctx.Data["PageIsAdminUsers"] = true
+       ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
+       ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
+       ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
+
+       u := prepareUserInfo(ctx)
+       if ctx.Written() {
+               return
+       }
+
+       repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
+               ListOptions: db.ListOptions{
+                       ListAll: true,
+               },
+               OwnerID:     u.ID,
+               OrderBy:     db.SearchOrderByAlphabetically,
+               Private:     true,
+               Collaborate: util.OptionalBoolFalse,
+       })
+       if err != nil {
+               ctx.ServerError("SearchRepository", err)
+               return
+       }
+
+       ctx.Data["Repos"] = repos
+       ctx.Data["ReposTotal"] = int(count)
+
+       emails, err := user_model.GetEmailAddresses(ctx.Doer.ID)
+       if err != nil {
+               ctx.ServerError("GetEmailAddresses", err)
+               return
+       }
+       ctx.Data["Emails"] = emails
+       ctx.Data["EmailsTotal"] = len(emails)
+
+       orgs, err := org_model.FindOrgs(org_model.FindOrgOptions{
+               ListOptions: db.ListOptions{
+                       ListAll: true,
+               },
+               UserID:         u.ID,
+               IncludePrivate: true,
+       })
+       if err != nil {
+               ctx.ServerError("FindOrgs", err)
+               return
+       }
+
+       ctx.Data["Users"] = orgs // needed to be able to use explore/user_list template
+       ctx.Data["OrgsTotal"] = len(orgs)
+
+       ctx.HTML(http.StatusOK, tplUserView)
+}
+
 // EditUser show editing user page
 func EditUser(ctx *context.Context) {
        ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")
index bbab9b37b5973bbd76a8d1d7bed7ea96866f2e70..ec6742f6ce76559262926c55e94bbd645b55267b 100644 (file)
@@ -573,7 +573,8 @@ func registerRoutes(m *web.Route) {
                m.Group("/users", func() {
                        m.Get("", admin.Users)
                        m.Combo("/new").Get(admin.NewUser).Post(web.Bind(forms.AdminCreateUserForm{}), admin.NewUserPost)
-                       m.Combo("/{userid}").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost)
+                       m.Get("/{userid}", admin.ViewUser)
+                       m.Combo("/{userid}/edit").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost)
                        m.Post("/{userid}/delete", admin.DeleteUser)
                        m.Post("/{userid}/avatar", web.Bind(forms.AvatarForm{}), admin.AvatarPost)
                        m.Post("/{userid}/avatar/delete", admin.DeleteAvatar)
index 64d03d9e54889f9e43d16ccdfe071d4859f899cc..0067f336e05c63df26a8f90cc40ebe67ab137d5a 100644 (file)
@@ -1,6 +1,6 @@
 {{template "base/head" .ctxData}}
 <div role="main" aria-label="{{.ctxData.Title}}" class="page-content {{.pageClass}}">
-       <div class="ui container">
+       <div class="ui container gt-mb-4">
                {{template "base/alert" .ctxData}}
        </div>
        <div class="ui container flex-container">
index 45fed87a4001349ad2e67a5f125bd3f1539dc294..b3e0caa16915b91693d360e208fd53c561b7f2b4 100644 (file)
                                                </th>
                                                <th>{{.locale.Tr "email"}}</th>
                                                <th>{{.locale.Tr "admin.users.activated"}}</th>
-                                               <th>{{.locale.Tr "admin.users.admin"}}</th>
                                                <th>{{.locale.Tr "admin.users.restricted"}}</th>
                                                <th>{{.locale.Tr "admin.users.2fa"}}</th>
-                                               <th>{{.locale.Tr "admin.users.repos"}}</th>
                                                <th>{{.locale.Tr "admin.users.created"}}</th>
                                                <th data-sortt-asc="lastlogin" data-sortt-desc="reverselastlogin">
                                                        {{.locale.Tr "admin.users.last_login"}}
                                                        {{SortArrow "lastlogin" "reverselastlogin" $.SortType false}}
                                                </th>
-                                               <th>{{.locale.Tr "admin.users.edit"}}</th>
                                        </tr>
                                </thead>
                                <tbody>
                                        {{range .Users}}
                                                <tr>
                                                        <td>{{.ID}}</td>
-                                                       <td><a href="{{.HomeLink}}">{{.Name}}</a></td>
+                                                       <td>
+                                                               <a href="{{$.Link}}/{{.ID}}">{{.Name}}</a>
+                                                               {{if .IsAdmin}}
+                                                                       <span class="ui basic label">{{$.locale.Tr "admin.users.admin"}}</span>
+                                                               {{end}}
+                                                       </td>
                                                        <td class="gt-ellipsis gt-max-width-12rem">{{.Email}}</td>
                                                        <td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
-                                                       <td>{{if .IsAdmin}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
                                                        <td>{{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
                                                        <td>{{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
-                                                       <td>{{.NumRepos}}</td>
                                                        <td>{{DateTime "short" .CreatedUnix}}</td>
                                                        {{if .LastLoginUnix}}
                                                                <td>{{DateTime "short" .LastLoginUnix}}</td>
                                                        {{else}}
                                                                <td><span>{{$.locale.Tr "admin.users.never_login"}}</span></td>
                                                        {{end}}
-                                                       <td><a href="{{$.Link}}/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
                                                </tr>
                                        {{end}}
                                </tbody>
diff --git a/templates/admin/user/view.tmpl b/templates/admin/user/view.tmpl
new file mode 100644 (file)
index 0000000..fd30176
--- /dev/null
@@ -0,0 +1,48 @@
+{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin view user")}}
+
+<div class="admin-setting-content">
+       <div class="admin-responsive-columns">
+               <div class="gt-f1">
+                       <h4 class="ui top attached header">
+                               {{.Title}}
+                               <div class="ui right">
+                                       <a class="ui primary tiny button" href="{{.Link}}/edit">{{ctx.Locale.Tr "admin.users.edit"}}</a>
+                               </div>
+                       </h4>
+                       <div class="ui attached segment">
+                               {{template "admin/user/view_details" .}}
+                       </div>
+               </div>
+               <div class="gt-f1">
+                       <h4 class="ui top attached header">
+                               {{ctx.Locale.Tr "admin.emails"}}
+                               <div class="ui right">
+                                       {{.EmailsTotal}}
+                               </div>
+                       </h4>
+                       <div class="ui attached segment">
+                               {{template "admin/user/view_emails" .}}
+                       </div>
+               </div>
+       </div>
+       <h4 class="ui top attached header">
+               {{ctx.Locale.Tr "admin.repositories"}}
+               <div class="ui right">
+                       {{.ReposTotal}}
+               </div>
+       </h4>
+       <div class="ui attached segment">
+               {{template "explore/repo_list" .}}
+       </div>
+       <h4 class="ui top attached header">
+               {{ctx.Locale.Tr "settings.organization"}}
+               <div class="ui right">
+                       {{.OrgsTotal}}
+               </div>
+       </h4>
+       <div class="ui attached segment">
+               {{template "explore/user_list" .}}
+       </div>
+</div>
+
+{{template "admin/layout_footer" .}}
diff --git a/templates/admin/user/view_details.tmpl b/templates/admin/user/view_details.tmpl
new file mode 100644 (file)
index 0000000..ceb3b9a
--- /dev/null
@@ -0,0 +1,65 @@
+<div class="flex-list">
+       <div class="flex-item">
+               <div class="flex-item-leading">
+                       {{ctx.AvatarUtils.Avatar .User 48}}
+               </div>
+               <div class="flex-item-main">
+                       <div class="flex-item-title">
+                               {{template "shared/user/name" .User}}
+                               {{if .User.IsAdmin}}
+                                       <span class="ui basic label">{{ctx.Locale.Tr "admin.users.admin"}}</span>
+                               {{end}}
+                       </div>
+                       <div class="flex-item-body">
+                               <b>{{ctx.Locale.Tr "admin.users.auth_source"}}:</b>
+                               {{if eq .LoginSource.ID 0}}
+                                       {{ctx.Locale.Tr "admin.users.local"}}
+                               {{else}}
+                                       {{.LoginSource.Name}}
+                               {{end}}
+                       </div>
+                       <div class="flex-item-body">
+                               <b>{{ctx.Locale.Tr "admin.users.activated"}}:</b>
+                               {{if .User.IsActive}}
+                                       {{svg "octicon-check"}}
+                               {{else}}
+                                       {{svg "octicon-x"}}
+                               {{end}}
+                       </div>
+                       <div class="flex-item-body">
+                               <b>{{ctx.Locale.Tr "admin.users.restricted"}}:</b>
+                               {{if .User.IsRestricted}}
+                                       {{svg "octicon-check"}}
+                               {{else}}
+                                       {{svg "octicon-x"}}
+                               {{end}}
+                       </div>
+                       <div class="flex-item-body">
+                               <b>{{ctx.Locale.Tr "settings.visibility"}}:</b>
+                               {{if .User.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
+                               {{if .User.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}}
+                       </div>
+                       <div class="flex-item-body">
+                               <b>{{ctx.Locale.Tr "admin.users.2fa"}}:</b>
+                               {{if .TwoFactorEnabled}}
+                                       <span class="text green">{{svg "octicon-check"}}</span>
+                               {{else}}
+                                       {{svg "octicon-x"}}
+                               {{end}}
+                       </div>
+                       {{if .User.Location}}
+                               <div class="flex-item-body">
+                                       <span class="flex-text-inline">{{svg "octicon-location"}}{{.User.Location}}</span>
+                               </div>
+                       {{end}}
+                       {{if .User.Website}}
+                               <div class="flex-item-body">
+                                       <span class="flex-text-inline">
+                                               {{svg "octicon-link"}}
+                                               <a target="_blank" href="{{.User.Website}}">{{.User.Website}}</a>
+                                       </span>
+                               </div>
+                       {{end}}
+               </div>
+       </div>
+</div>
diff --git a/templates/admin/user/view_emails.tmpl b/templates/admin/user/view_emails.tmpl
new file mode 100644 (file)
index 0000000..22ce305
--- /dev/null
@@ -0,0 +1,19 @@
+<div class="flex-list">
+       {{range .Emails}}
+               <div class="flex-item">
+                       <div class="flex-item-main">
+                               <div class="flex-text-block">
+                                       {{.Email}}
+                                       {{if .IsPrimary}}
+                                               <div class="ui primary label">{{ctx.Locale.Tr "settings.primary"}}</div>
+                                       {{end}}
+                                       {{if .IsActivated}}
+                                               <div class="ui green label">{{ctx.Locale.Tr "settings.activated"}}</div>
+                                       {{else}}
+                                               <div class="ui label">{{ctx.Locale.Tr "settings.requires_activation"}}</div>
+                                       {{end}}
+                               </div>
+                       </div>
+               </div>
+       {{end}}
+</div>
diff --git a/templates/explore/user_list.tmpl b/templates/explore/user_list.tmpl
new file mode 100644 (file)
index 0000000..cf6a293
--- /dev/null
@@ -0,0 +1,31 @@
+<div class="flex-list">
+       {{range .Users}}
+               <div class="flex-item flex-item-center">
+                       <div class="flex-item-leading">
+                               {{ctx.AvatarUtils.Avatar . 48}}
+                       </div>
+                       <div class="flex-item-main">
+                               <div class="flex-item-title">
+                                       {{template "shared/user/name" .}}
+                                       {{if .Visibility.IsPrivate}}
+                                               <span class="ui basic tiny label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
+                                       {{end}}
+                               </div>
+                               <div class="flex-item-body">
+                                       {{if .Location}}
+                                               <span class="flex-text-inline">{{svg "octicon-location"}}{{.Location}}</span>
+                                       {{end}}
+                                       {{if and .Email (or (and $.ShowUserEmail $.IsSigned (not .KeepEmailPrivate)) $.PageIsAdminUsers)}}
+                                               <span class="flex-text-inline">
+                                                       {{svg "octicon-mail"}}
+                                                       <a href="mailto:{{.Email}}">{{.Email}}</a>
+                                               </span>
+                                       {{end}}
+                                       <span class="flex-text-inline">{{svg "octicon-calendar"}}{{ctx.Locale.Tr "user.joined_on" (DateTime "short" .CreatedUnix) | Safe}}</span>
+                               </div>
+                       </div>
+               </div>
+       {{else}}
+               <div class="flex-item">{{ctx.Locale.Tr "explore.user_no_results"}}</div>
+       {{end}}
+</div>
index 1280f4add6641fba179a6e580d70020813f819be..7e15ae3d47f4684a6bf8bf36ac7b33d9b41cd603 100644 (file)
@@ -4,37 +4,7 @@
        <div class="ui container">
                {{template "explore/search" .}}
 
-               <div class="flex-list">
-                       {{range .Users}}
-                               <div class="flex-item flex-item-center">
-                                       <div class="flex-item-leading">
-                                               {{ctx.AvatarUtils.Avatar . 48}}
-                                       </div>
-                                       <div class="flex-item-main">
-                                               <div class="flex-item-title">
-                                                       {{template "shared/user/name" .}}
-                                                       {{if .Visibility.IsPrivate}}
-                                                               <span class="ui basic tiny label">{{$.locale.Tr "repo.desc.private"}}</span>
-                                                       {{end}}
-                                               </div>
-                                               <div class="flex-item-body">
-                                                       {{if .Location}}
-                                                               <span class="flex-text-inline">{{svg "octicon-location"}}{{.Location}}</span>
-                                                       {{end}}
-                                                       {{if and $.ShowUserEmail .Email $.IsSigned (not .KeepEmailPrivate)}}
-                                                               <span class="flex-text-inline">
-                                                                       {{svg "octicon-mail"}}
-                                                                       <a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a>
-                                                               </span>
-                                                       {{end}}
-                                                       <span class="flex-text-inline">{{svg "octicon-calendar"}}{{$.locale.Tr "user.joined_on" (DateTime "short" .CreatedUnix) | Safe}}</span>
-                                               </div>
-                                       </div>
-                               </div>
-                       {{else}}
-                               <div class="flex-item">{{$.locale.Tr "explore.user_no_results"}}</div>
-                       {{end}}
-               </div>
+               {{template "explore/user_list" .}}
 
                {{template "base/paginate" .}}
        </div>
index dd6b9ccbbeb8ca2060e7b372e020f4a89129bdda..669060c787d486cdc38bae0b4a141ef3a7d53d03 100644 (file)
@@ -51,8 +51,8 @@ func testSuccessfullEdit(t *testing.T, formData user_model.User) {
 
 func makeRequest(t *testing.T, formData user_model.User, headerCode int) {
        session := loginUser(t, "user1")
-       csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID)))
-       req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID)), map[string]string{
+       csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit")
+       req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit", map[string]string{
                "_csrf":      csrf,
                "user_name":  formData.Name,
                "login_name": formData.LoginName,
@@ -72,7 +72,7 @@ func TestAdminDeleteUser(t *testing.T) {
 
        session := loginUser(t, "user1")
 
-       csrf := GetCSRF(t, session, "/admin/users/8")
+       csrf := GetCSRF(t, session, "/admin/users/8/edit")
        req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{
                "_csrf": csrf,
        })
index fecae5f2bf9891dbcb2cf903aaf0eb6422e48efa..e6866b27a688141c26d2a10a2a068b726b55bb9e 100644 (file)
 .admin .table th {
   white-space: nowrap;
 }
+
+.admin-responsive-columns {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 1rem;
+  margin-bottom: 1rem;
+}