feat: ability to filter listed accounts by type in admin dashboard (#9455)
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9455 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: famfo <famfo@famfo.xyz> Co-committed-by: famfo <famfo@famfo.xyz>
This commit is contained in:
parent
c271c73e53
commit
98073ac28d
7 changed files with 79 additions and 7 deletions
|
@ -1562,3 +1562,41 @@
|
|||
theme: ""
|
||||
keep_activity_private: false
|
||||
created_unix: 1672578400
|
||||
|
||||
-
|
||||
id: 42
|
||||
lower_name: federated-example.net
|
||||
name: federated-example.net
|
||||
full_name: federated
|
||||
email: f73240e82-c061-41ef-b7d6-4376cb6f2e1c@example.com
|
||||
keep_email_private: false
|
||||
email_notifications_preference: enabled
|
||||
passwd: ZogKvWdyEx:password
|
||||
passwd_hash_algo: dummy
|
||||
must_change_password: false
|
||||
login_source: 0
|
||||
login_name: federated-example.net
|
||||
type: 5
|
||||
salt: ZogKvWdyEx
|
||||
max_repo_creation: -1
|
||||
is_active: false
|
||||
is_admin: false
|
||||
is_restricted: false
|
||||
allow_git_hook: false
|
||||
allow_import_local: false
|
||||
allow_create_organization: false
|
||||
prohibit_login: false
|
||||
avatar: ""
|
||||
avatar_email: f73240e82-c061-41ef-b7d6-4376cb6f2e1c@example.com
|
||||
use_custom_avatar: false
|
||||
num_followers: 0
|
||||
num_following: 0
|
||||
num_stars: 0
|
||||
num_repos: 0
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
repo_admin_change_team_access: false
|
||||
theme: ""
|
||||
keep_activity_private: false
|
||||
created_unix: 1759086716
|
||||
|
|
|
@ -38,6 +38,7 @@ type SearchUserOptions struct {
|
|||
IsRestricted optional.Option[bool]
|
||||
IsTwoFactorEnabled optional.Option[bool]
|
||||
IsProhibitLogin optional.Option[bool]
|
||||
AccountType optional.Option[UserType]
|
||||
IncludeReserved bool
|
||||
|
||||
Load2FAStatus bool
|
||||
|
@ -123,6 +124,10 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
|
|||
cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.Value()})
|
||||
}
|
||||
|
||||
if opts.AccountType.Has() {
|
||||
cond = cond.And(builder.Eq{"type": opts.AccountType.Value()})
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
if !opts.IsTwoFactorEnabled.Has() {
|
||||
return e.Where(cond)
|
||||
|
|
|
@ -219,10 +219,10 @@ func TestSearchUsers(t *testing.T) {
|
|||
}
|
||||
|
||||
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
|
||||
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 1041})
|
||||
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 42, 1041})
|
||||
|
||||
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
|
||||
[]int64{9})
|
||||
[]int64{42, 9})
|
||||
|
||||
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
|
||||
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40, 1041})
|
||||
|
|
|
@ -48,7 +48,7 @@ func Users(ctx *context.Context) {
|
|||
ctx.Data["PageIsAdminUsers"] = true
|
||||
|
||||
extraParamStrings := map[string]string{}
|
||||
statusFilterKeys := []string{"is_active", "is_admin", "is_restricted", "is_2fa_enabled", "is_prohibit_login"}
|
||||
statusFilterKeys := []string{"is_active", "is_admin", "is_restricted", "is_2fa_enabled", "is_prohibit_login", "account_type"}
|
||||
statusFilterMap := map[string]string{}
|
||||
for _, filterKey := range statusFilterKeys {
|
||||
paramKey := "status_filter[" + filterKey + "]"
|
||||
|
@ -59,6 +59,19 @@ func Users(ctx *context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
accountType := statusFilterMap["account_type"]
|
||||
accountTypeFilter := optional.None[user_model.UserType]()
|
||||
if accountType != "" {
|
||||
accountTypeInt, err := strconv.ParseInt(accountType, 10, 0)
|
||||
if err != nil {
|
||||
ctx.ServerError("account_type int", err)
|
||||
return
|
||||
}
|
||||
|
||||
accountTypeFilter = optional.Some(user_model.UserType(accountTypeInt))
|
||||
extraParamStrings["account_type"] = accountType
|
||||
}
|
||||
|
||||
sortType := ctx.FormString("sort")
|
||||
if sortType == "" {
|
||||
sortType = UserSearchDefaultAdminSort
|
||||
|
@ -81,6 +94,7 @@ func Users(ctx *context.Context) {
|
|||
IsRestricted: optional.ParseBool(statusFilterMap["is_restricted"]),
|
||||
IsTwoFactorEnabled: optional.ParseBool(statusFilterMap["is_2fa_enabled"]),
|
||||
IsProhibitLogin: optional.ParseBool(statusFilterMap["is_prohibit_login"]),
|
||||
AccountType: accountTypeFilter,
|
||||
IncludeReserved: true, // administrator needs to list all accounts include reserved, bot, remote ones
|
||||
Load2FAStatus: true,
|
||||
ExtraParamStrings: extraParamStrings,
|
||||
|
|
|
@ -32,6 +32,11 @@
|
|||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="status_filter[is_2fa_enabled]" value="1"> {{ctx.Locale.Tr "admin.users.list_status_filter.is_2fa_enabled"}}</label>
|
||||
<label class="item"><input type="radio" name="status_filter[is_2fa_enabled]" value="0"> {{ctx.Locale.Tr "admin.users.list_status_filter.not_2fa_enabled"}}</label>
|
||||
<div class="divider"></div>
|
||||
<label class="item"><input type="radio" name="status_filter[account_type]" value="0"> {{ctx.Locale.Tr "admin.users.local"}}</label>
|
||||
<label class="item"><input type="radio" name="status_filter[account_type]" value="2"> {{ctx.Locale.Tr "admin.users.reserved"}}</label>
|
||||
<label class="item"><input type="radio" name="status_filter[account_type]" value="4"> {{ctx.Locale.Tr "admin.users.bot"}}</label>
|
||||
<label class="item"><input type="radio" name="status_filter[account_type]" value="5"> {{ctx.Locale.Tr "admin.users.remote"}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -39,6 +39,14 @@ func TestAdminViewUsers(t *testing.T) {
|
|||
// 6th column is the 2FA column.
|
||||
// One user that has TOTP and another user that has WebAuthn.
|
||||
assert.Equal(t, 2, htmlDoc.Find(".admin-setting-content table tbody tr td:nth-child(6) .octicon-check").Length())
|
||||
|
||||
// account type 5 is for remote users (eg. users from the federation)
|
||||
req = NewRequest(t, "GET", "/admin/users?status_filter[account_type]=5")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Only one user (id 42) is a remote user
|
||||
assert.Equal(t, 1, htmlDoc.Find("table tbody tr").Length())
|
||||
})
|
||||
|
||||
t.Run("Normal user", func(t *testing.T) {
|
||||
|
@ -148,7 +156,7 @@ func TestSourceId(t *testing.T) {
|
|||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &users)
|
||||
assert.Len(t, users, 1)
|
||||
assert.Equal(t, "the_34-user.with.all.allowedChars", users[0].UserName)
|
||||
assert.Equal(t, "federated-example.net", users[0].UserName)
|
||||
|
||||
// Now our new user should be in the list, because we filter by source_id 23
|
||||
req = NewRequest(t, "GET", "/api/v1/admin/users?limit=1&source_id=23").AddTokenAuth(token)
|
||||
|
@ -192,9 +200,9 @@ func TestAdminViewUsersSorted(t *testing.T) {
|
|||
sortType string
|
||||
expectedUsers []string
|
||||
}{
|
||||
{0, "alphabetically", []string{"the_34-user.with.all.allowedChars", "user1", "user10", "user11"}},
|
||||
{0, "alphabetically", []string{"federated-example.net", "the_34-user.with.all.allowedChars", "user1", "user10"}},
|
||||
{0, "reversealphabetically", []string{"user9", "user8", "user5", "user40"}},
|
||||
{0, "newest", []string{"user40", "user39", "user38", "user37"}},
|
||||
{0, "newest", []string{"federated-example.net", "user40", "user39", "user38"}},
|
||||
{0, "oldest", []string{"user1", "user2", "user4", "user5"}},
|
||||
{44, "recentupdate", []string{"sorttest1", "sorttest2", "sorttest3", "sorttest4"}},
|
||||
{44, "leastupdate", []string{"sorttest10", "sorttest9", "sorttest8", "sorttest7"}},
|
||||
|
|
|
@ -126,7 +126,9 @@ func TestAPIListUsers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0")
|
||||
numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") +
|
||||
unittest.GetCount(t, &user_model.User{}, "type = 5")
|
||||
|
||||
assert.Len(t, users, numberOfUsers)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue