Skip to content

Commit 5b5bb8d

Browse files
denyskonGiteaBot
andauthored
User details page (#26713)
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 <[email protected]>
1 parent 3d10986 commit 5b5bb8d

File tree

12 files changed

+242
-43
lines changed

12 files changed

+242
-43
lines changed

options/locale/locale_en-US.ini

+1
Original file line numberDiff line numberDiff line change
@@ -2823,6 +2823,7 @@ users.list_status_filter.is_prohibit_login = Prohibit Login
28232823
users.list_status_filter.not_prohibit_login = Allow Login
28242824
users.list_status_filter.is_2fa_enabled = 2FA Enabled
28252825
users.list_status_filter.not_2fa_enabled = 2FA Disabled
2826+
users.details = User Details
28262827

28272828
emails.email_manage_panel = User Email Management
28282829
emails.primary = Primary

routers/web/admin/users.go

+58
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"code.gitea.io/gitea/models"
1414
"code.gitea.io/gitea/models/auth"
1515
"code.gitea.io/gitea/models/db"
16+
org_model "code.gitea.io/gitea/models/organization"
17+
repo_model "code.gitea.io/gitea/models/repo"
1618
system_model "code.gitea.io/gitea/models/system"
1719
user_model "code.gitea.io/gitea/models/user"
1820
"code.gitea.io/gitea/modules/auth/password"
@@ -32,6 +34,7 @@ import (
3234
const (
3335
tplUsers base.TplName = "admin/user/list"
3436
tplUserNew base.TplName = "admin/user/new"
37+
tplUserView base.TplName = "admin/user/view"
3538
tplUserEdit base.TplName = "admin/user/edit"
3639
)
3740

@@ -249,6 +252,61 @@ func prepareUserInfo(ctx *context.Context) *user_model.User {
249252
return u
250253
}
251254

255+
func ViewUser(ctx *context.Context) {
256+
ctx.Data["Title"] = ctx.Tr("admin.users.details")
257+
ctx.Data["PageIsAdminUsers"] = true
258+
ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
259+
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
260+
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
261+
262+
u := prepareUserInfo(ctx)
263+
if ctx.Written() {
264+
return
265+
}
266+
267+
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
268+
ListOptions: db.ListOptions{
269+
ListAll: true,
270+
},
271+
OwnerID: u.ID,
272+
OrderBy: db.SearchOrderByAlphabetically,
273+
Private: true,
274+
Collaborate: util.OptionalBoolFalse,
275+
})
276+
if err != nil {
277+
ctx.ServerError("SearchRepository", err)
278+
return
279+
}
280+
281+
ctx.Data["Repos"] = repos
282+
ctx.Data["ReposTotal"] = int(count)
283+
284+
emails, err := user_model.GetEmailAddresses(ctx.Doer.ID)
285+
if err != nil {
286+
ctx.ServerError("GetEmailAddresses", err)
287+
return
288+
}
289+
ctx.Data["Emails"] = emails
290+
ctx.Data["EmailsTotal"] = len(emails)
291+
292+
orgs, err := org_model.FindOrgs(org_model.FindOrgOptions{
293+
ListOptions: db.ListOptions{
294+
ListAll: true,
295+
},
296+
UserID: u.ID,
297+
IncludePrivate: true,
298+
})
299+
if err != nil {
300+
ctx.ServerError("FindOrgs", err)
301+
return
302+
}
303+
304+
ctx.Data["Users"] = orgs // needed to be able to use explore/user_list template
305+
ctx.Data["OrgsTotal"] = len(orgs)
306+
307+
ctx.HTML(http.StatusOK, tplUserView)
308+
}
309+
252310
// EditUser show editing user page
253311
func EditUser(ctx *context.Context) {
254312
ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")

routers/web/web.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,8 @@ func registerRoutes(m *web.Route) {
573573
m.Group("/users", func() {
574574
m.Get("", admin.Users)
575575
m.Combo("/new").Get(admin.NewUser).Post(web.Bind(forms.AdminCreateUserForm{}), admin.NewUserPost)
576-
m.Combo("/{userid}").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost)
576+
m.Get("/{userid}", admin.ViewUser)
577+
m.Combo("/{userid}/edit").Get(admin.EditUser).Post(web.Bind(forms.AdminEditUserForm{}), admin.EditUserPost)
577578
m.Post("/{userid}/delete", admin.DeleteUser)
578579
m.Post("/{userid}/avatar", web.Bind(forms.AvatarForm{}), admin.AvatarPost)
579580
m.Post("/{userid}/avatar/delete", admin.DeleteAvatar)

templates/admin/layout_head.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{template "base/head" .ctxData}}
22
<div role="main" aria-label="{{.ctxData.Title}}" class="page-content {{.pageClass}}">
3-
<div class="ui container">
3+
<div class="ui container gt-mb-4">
44
{{template "base/alert" .ctxData}}
55
</div>
66
<div class="ui container flex-container">

templates/admin/user/list.tmpl

+6-7
Original file line numberDiff line numberDiff line change
@@ -68,36 +68,35 @@
6868
</th>
6969
<th>{{.locale.Tr "email"}}</th>
7070
<th>{{.locale.Tr "admin.users.activated"}}</th>
71-
<th>{{.locale.Tr "admin.users.admin"}}</th>
7271
<th>{{.locale.Tr "admin.users.restricted"}}</th>
7372
<th>{{.locale.Tr "admin.users.2fa"}}</th>
74-
<th>{{.locale.Tr "admin.users.repos"}}</th>
7573
<th>{{.locale.Tr "admin.users.created"}}</th>
7674
<th data-sortt-asc="lastlogin" data-sortt-desc="reverselastlogin">
7775
{{.locale.Tr "admin.users.last_login"}}
7876
{{SortArrow "lastlogin" "reverselastlogin" $.SortType false}}
7977
</th>
80-
<th>{{.locale.Tr "admin.users.edit"}}</th>
8178
</tr>
8279
</thead>
8380
<tbody>
8481
{{range .Users}}
8582
<tr>
8683
<td>{{.ID}}</td>
87-
<td><a href="{{.HomeLink}}">{{.Name}}</a></td>
84+
<td>
85+
<a href="{{$.Link}}/{{.ID}}">{{.Name}}</a>
86+
{{if .IsAdmin}}
87+
<span class="ui basic label">{{$.locale.Tr "admin.users.admin"}}</span>
88+
{{end}}
89+
</td>
8890
<td class="gt-ellipsis gt-max-width-12rem">{{.Email}}</td>
8991
<td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
90-
<td>{{if .IsAdmin}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
9192
<td>{{if .IsRestricted}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
9293
<td>{{if index $.UsersTwoFaStatus .ID}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
93-
<td>{{.NumRepos}}</td>
9494
<td>{{DateTime "short" .CreatedUnix}}</td>
9595
{{if .LastLoginUnix}}
9696
<td>{{DateTime "short" .LastLoginUnix}}</td>
9797
{{else}}
9898
<td><span>{{$.locale.Tr "admin.users.never_login"}}</span></td>
9999
{{end}}
100-
<td><a href="{{$.Link}}/{{.ID}}">{{svg "octicon-pencil"}}</a></td>
101100
</tr>
102101
{{end}}
103102
</tbody>

templates/admin/user/view.tmpl

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin view user")}}
2+
3+
<div class="admin-setting-content">
4+
<div class="admin-responsive-columns">
5+
<div class="gt-f1">
6+
<h4 class="ui top attached header">
7+
{{.Title}}
8+
<div class="ui right">
9+
<a class="ui primary tiny button" href="{{.Link}}/edit">{{ctx.Locale.Tr "admin.users.edit"}}</a>
10+
</div>
11+
</h4>
12+
<div class="ui attached segment">
13+
{{template "admin/user/view_details" .}}
14+
</div>
15+
</div>
16+
<div class="gt-f1">
17+
<h4 class="ui top attached header">
18+
{{ctx.Locale.Tr "admin.emails"}}
19+
<div class="ui right">
20+
{{.EmailsTotal}}
21+
</div>
22+
</h4>
23+
<div class="ui attached segment">
24+
{{template "admin/user/view_emails" .}}
25+
</div>
26+
</div>
27+
</div>
28+
<h4 class="ui top attached header">
29+
{{ctx.Locale.Tr "admin.repositories"}}
30+
<div class="ui right">
31+
{{.ReposTotal}}
32+
</div>
33+
</h4>
34+
<div class="ui attached segment">
35+
{{template "explore/repo_list" .}}
36+
</div>
37+
<h4 class="ui top attached header">
38+
{{ctx.Locale.Tr "settings.organization"}}
39+
<div class="ui right">
40+
{{.OrgsTotal}}
41+
</div>
42+
</h4>
43+
<div class="ui attached segment">
44+
{{template "explore/user_list" .}}
45+
</div>
46+
</div>
47+
48+
{{template "admin/layout_footer" .}}
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<div class="flex-list">
2+
<div class="flex-item">
3+
<div class="flex-item-leading">
4+
{{ctx.AvatarUtils.Avatar .User 48}}
5+
</div>
6+
<div class="flex-item-main">
7+
<div class="flex-item-title">
8+
{{template "shared/user/name" .User}}
9+
{{if .User.IsAdmin}}
10+
<span class="ui basic label">{{ctx.Locale.Tr "admin.users.admin"}}</span>
11+
{{end}}
12+
</div>
13+
<div class="flex-item-body">
14+
<b>{{ctx.Locale.Tr "admin.users.auth_source"}}:</b>
15+
{{if eq .LoginSource.ID 0}}
16+
{{ctx.Locale.Tr "admin.users.local"}}
17+
{{else}}
18+
{{.LoginSource.Name}}
19+
{{end}}
20+
</div>
21+
<div class="flex-item-body">
22+
<b>{{ctx.Locale.Tr "admin.users.activated"}}:</b>
23+
{{if .User.IsActive}}
24+
{{svg "octicon-check"}}
25+
{{else}}
26+
{{svg "octicon-x"}}
27+
{{end}}
28+
</div>
29+
<div class="flex-item-body">
30+
<b>{{ctx.Locale.Tr "admin.users.restricted"}}:</b>
31+
{{if .User.IsRestricted}}
32+
{{svg "octicon-check"}}
33+
{{else}}
34+
{{svg "octicon-x"}}
35+
{{end}}
36+
</div>
37+
<div class="flex-item-body">
38+
<b>{{ctx.Locale.Tr "settings.visibility"}}:</b>
39+
{{if .User.Visibility.IsLimited}}{{ctx.Locale.Tr "settings.visibility.limited"}}{{end}}
40+
{{if .User.Visibility.IsPrivate}}{{ctx.Locale.Tr "settings.visibility.private"}}{{end}}
41+
</div>
42+
<div class="flex-item-body">
43+
<b>{{ctx.Locale.Tr "admin.users.2fa"}}:</b>
44+
{{if .TwoFactorEnabled}}
45+
<span class="text green">{{svg "octicon-check"}}</span>
46+
{{else}}
47+
{{svg "octicon-x"}}
48+
{{end}}
49+
</div>
50+
{{if .User.Location}}
51+
<div class="flex-item-body">
52+
<span class="flex-text-inline">{{svg "octicon-location"}}{{.User.Location}}</span>
53+
</div>
54+
{{end}}
55+
{{if .User.Website}}
56+
<div class="flex-item-body">
57+
<span class="flex-text-inline">
58+
{{svg "octicon-link"}}
59+
<a target="_blank" href="{{.User.Website}}">{{.User.Website}}</a>
60+
</span>
61+
</div>
62+
{{end}}
63+
</div>
64+
</div>
65+
</div>

templates/admin/user/view_emails.tmpl

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div class="flex-list">
2+
{{range .Emails}}
3+
<div class="flex-item">
4+
<div class="flex-item-main">
5+
<div class="flex-text-block">
6+
{{.Email}}
7+
{{if .IsPrimary}}
8+
<div class="ui primary label">{{ctx.Locale.Tr "settings.primary"}}</div>
9+
{{end}}
10+
{{if .IsActivated}}
11+
<div class="ui green label">{{ctx.Locale.Tr "settings.activated"}}</div>
12+
{{else}}
13+
<div class="ui label">{{ctx.Locale.Tr "settings.requires_activation"}}</div>
14+
{{end}}
15+
</div>
16+
</div>
17+
</div>
18+
{{end}}
19+
</div>

templates/explore/user_list.tmpl

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<div class="flex-list">
2+
{{range .Users}}
3+
<div class="flex-item flex-item-center">
4+
<div class="flex-item-leading">
5+
{{ctx.AvatarUtils.Avatar . 48}}
6+
</div>
7+
<div class="flex-item-main">
8+
<div class="flex-item-title">
9+
{{template "shared/user/name" .}}
10+
{{if .Visibility.IsPrivate}}
11+
<span class="ui basic tiny label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
12+
{{end}}
13+
</div>
14+
<div class="flex-item-body">
15+
{{if .Location}}
16+
<span class="flex-text-inline">{{svg "octicon-location"}}{{.Location}}</span>
17+
{{end}}
18+
{{if and .Email (or (and $.ShowUserEmail $.IsSigned (not .KeepEmailPrivate)) $.PageIsAdminUsers)}}
19+
<span class="flex-text-inline">
20+
{{svg "octicon-mail"}}
21+
<a href="mailto:{{.Email}}">{{.Email}}</a>
22+
</span>
23+
{{end}}
24+
<span class="flex-text-inline">{{svg "octicon-calendar"}}{{ctx.Locale.Tr "user.joined_on" (DateTime "short" .CreatedUnix) | Safe}}</span>
25+
</div>
26+
</div>
27+
</div>
28+
{{else}}
29+
<div class="flex-item">{{ctx.Locale.Tr "explore.user_no_results"}}</div>
30+
{{end}}
31+
</div>

templates/explore/users.tmpl

+1-31
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,7 @@
44
<div class="ui container">
55
{{template "explore/search" .}}
66

7-
<div class="flex-list">
8-
{{range .Users}}
9-
<div class="flex-item flex-item-center">
10-
<div class="flex-item-leading">
11-
{{ctx.AvatarUtils.Avatar . 48}}
12-
</div>
13-
<div class="flex-item-main">
14-
<div class="flex-item-title">
15-
{{template "shared/user/name" .}}
16-
{{if .Visibility.IsPrivate}}
17-
<span class="ui basic tiny label">{{$.locale.Tr "repo.desc.private"}}</span>
18-
{{end}}
19-
</div>
20-
<div class="flex-item-body">
21-
{{if .Location}}
22-
<span class="flex-text-inline">{{svg "octicon-location"}}{{.Location}}</span>
23-
{{end}}
24-
{{if and $.ShowUserEmail .Email $.IsSigned (not .KeepEmailPrivate)}}
25-
<span class="flex-text-inline">
26-
{{svg "octicon-mail"}}
27-
<a href="mailto:{{.Email}}" rel="nofollow">{{.Email}}</a>
28-
</span>
29-
{{end}}
30-
<span class="flex-text-inline">{{svg "octicon-calendar"}}{{$.locale.Tr "user.joined_on" (DateTime "short" .CreatedUnix) | Safe}}</span>
31-
</div>
32-
</div>
33-
</div>
34-
{{else}}
35-
<div class="flex-item">{{$.locale.Tr "explore.user_no_results"}}</div>
36-
{{end}}
37-
</div>
7+
{{template "explore/user_list" .}}
388

399
{{template "base/paginate" .}}
4010
</div>

tests/integration/admin_user_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ func testSuccessfullEdit(t *testing.T, formData user_model.User) {
5151

5252
func makeRequest(t *testing.T, formData user_model.User, headerCode int) {
5353
session := loginUser(t, "user1")
54-
csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID)))
55-
req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID)), map[string]string{
54+
csrf := GetCSRF(t, session, "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit")
55+
req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit", map[string]string{
5656
"_csrf": csrf,
5757
"user_name": formData.Name,
5858
"login_name": formData.LoginName,
@@ -72,7 +72,7 @@ func TestAdminDeleteUser(t *testing.T) {
7272

7373
session := loginUser(t, "user1")
7474

75-
csrf := GetCSRF(t, session, "/admin/users/8")
75+
csrf := GetCSRF(t, session, "/admin/users/8/edit")
7676
req := NewRequestWithValues(t, "POST", "/admin/users/8/delete", map[string]string{
7777
"_csrf": csrf,
7878
})

web_src/css/admin.css

+7
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,10 @@
4242
.admin .table th {
4343
white-space: nowrap;
4444
}
45+
46+
.admin-responsive-columns {
47+
display: flex;
48+
flex-wrap: wrap;
49+
gap: 1rem;
50+
margin-bottom: 1rem;
51+
}

0 commit comments

Comments
 (0)