diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-07-02 22:49:51 +0200 |
---|---|---|
committer | Rigel Kent <sendmemail@rigelk.eu> | 2020-07-02 22:50:33 +0200 |
commit | 8491293b02ed2ec53eb0fa128161ea0b08d3def9 (patch) | |
tree | b3d8dd2732f5876e39c73b7d917a7ad02f4e021a /client/src | |
parent | 2b587cad93381a1901df3c993bf1db90bbb0891f (diff) | |
download | PeerTube-8491293b02ed2ec53eb0fa128161ea0b08d3def9.tar.gz PeerTube-8491293b02ed2ec53eb0fa128161ea0b08d3def9.tar.zst PeerTube-8491293b02ed2ec53eb0fa128161ea0b08d3def9.zip |
add blocked filter in users list to filter banned users
fixes #2914
Diffstat (limited to 'client/src')
5 files changed, 106 insertions, 19 deletions
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html index 27d4a5787..9580a3c8a 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.html +++ b/client/src/app/+admin/users/user-list/user-list.component.html | |||
@@ -16,14 +16,27 @@ | |||
16 | </my-action-dropdown> | 16 | </my-action-dropdown> |
17 | </div> | 17 | </div> |
18 | 18 | ||
19 | <div class="ml-auto has-feedback has-clear"> | 19 | <div class="ml-auto"> |
20 | <input | 20 | <div class="input-group has-feedback has-clear"> |
21 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 21 | <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body"> |
22 | (keyup)="onSearch($event)" | 22 | <div class="input-group-text" ngbDropdownToggle> |
23 | > | 23 | <span class="caret" aria-haspopup="menu" role="button"></span> |
24 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | 24 | </div> |
25 | <span class="sr-only" i18n>Clear filters</span> | 25 | |
26 | <div role="menu" ngbDropdownMenu> | ||
27 | <h6 class="dropdown-header" i18n>Advanced user filters</h6> | ||
28 | <a [routerLink]="[ '/admin/users/list' ]" [queryParams]="{ 'search': 'banned:true' }" class="dropdown-item" i18n>Banned users</a> | ||
29 | </div> | ||
30 | </div> | ||
31 | <input | ||
32 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | ||
33 | (keyup)="onUserSearch($event)" | ||
34 | > | ||
35 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a> | ||
36 | <span class="sr-only" i18n>Clear filters</span> | ||
37 | </div> | ||
26 | </div> | 38 | </div> |
39 | |||
27 | <a class="ml-2 add-button" routerLink="/admin/users/create"> | 40 | <a class="ml-2 add-button" routerLink="/admin/users/create"> |
28 | <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> | 41 | <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> |
29 | <ng-container i18n>Create user</ng-container> | 42 | <ng-container i18n>Create user</ng-container> |
@@ -70,7 +83,7 @@ | |||
70 | alt="Avatar" | 83 | alt="Avatar" |
71 | > | 84 | > |
72 | <div> | 85 | <div> |
73 | <span> | 86 | <span class="user-table-primary-text"> |
74 | <span *ngIf="user.blocked" i18n-title title="The user was banned" class="glyphicon glyphicon-ban-circle"></span> | 87 | <span *ngIf="user.blocked" i18n-title title="The user was banned" class="glyphicon glyphicon-ban-circle"></span> |
75 | {{ user.account.displayName }} | 88 | {{ user.account.displayName }} |
76 | </span> | 89 | </span> |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss index 697b2c11b..2b84dec75 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/users/user-list/user-list.component.scss | |||
@@ -17,6 +17,12 @@ tr.banned > td { | |||
17 | font-weight: $font-semibold; | 17 | font-weight: $font-semibold; |
18 | } | 18 | } |
19 | 19 | ||
20 | .user-table-primary-text .glyphicon { | ||
21 | font-size: 80%; | ||
22 | color: gray; | ||
23 | margin-left: 0.1rem; | ||
24 | } | ||
25 | |||
20 | .caption { | 26 | .caption { |
21 | justify-content: space-between; | 27 | justify-content: space-between; |
22 | 28 | ||
@@ -33,3 +39,14 @@ p-tableCheckbox { | |||
33 | .chip { | 39 | .chip { |
34 | @include chip; | 40 | @include chip; |
35 | } | 41 | } |
42 | |||
43 | .input-group { | ||
44 | @include peertube-input-group(300px); | ||
45 | input { | ||
46 | flex: 1; | ||
47 | } | ||
48 | |||
49 | .dropdown-toggle::after { | ||
50 | margin-left: 0; | ||
51 | } | ||
52 | } | ||
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index 8f01c7d51..0b72b07c1 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts | |||
@@ -5,6 +5,7 @@ import { Actor, DropdownAction } from '@app/shared/shared-main' | |||
5 | import { UserBanModalComponent } from '@app/shared/shared-moderation' | 5 | import { UserBanModalComponent } from '@app/shared/shared-moderation' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { ServerConfig, User } from '@shared/models' | 7 | import { ServerConfig, User } from '@shared/models' |
8 | import { Params, Router, ActivatedRoute } from '@angular/router' | ||
8 | 9 | ||
9 | @Component({ | 10 | @Component({ |
10 | selector: 'my-user-list', | 11 | selector: 'my-user-list', |
@@ -30,6 +31,8 @@ export class UserListComponent extends RestTable implements OnInit { | |||
30 | private serverService: ServerService, | 31 | private serverService: ServerService, |
31 | private userService: UserService, | 32 | private userService: UserService, |
32 | private auth: AuthService, | 33 | private auth: AuthService, |
34 | private route: ActivatedRoute, | ||
35 | private router: Router, | ||
33 | private i18n: I18n | 36 | private i18n: I18n |
34 | ) { | 37 | ) { |
35 | super() | 38 | super() |
@@ -50,6 +53,14 @@ export class UserListComponent extends RestTable implements OnInit { | |||
50 | 53 | ||
51 | this.initialize() | 54 | this.initialize() |
52 | 55 | ||
56 | this.route.queryParams | ||
57 | .subscribe(params => { | ||
58 | this.search = params.search || '' | ||
59 | |||
60 | this.setTableFilter(this.search) | ||
61 | this.loadData() | ||
62 | }) | ||
63 | |||
53 | this.bulkUserActions = [ | 64 | this.bulkUserActions = [ |
54 | [ | 65 | [ |
55 | { | 66 | { |
@@ -102,6 +113,26 @@ export class UserListComponent extends RestTable implements OnInit { | |||
102 | this.loadData() | 113 | this.loadData() |
103 | } | 114 | } |
104 | 115 | ||
116 | /* Table filter functions */ | ||
117 | onUserSearch (event: Event) { | ||
118 | this.onSearch(event) | ||
119 | this.setQueryParams((event.target as HTMLInputElement).value) | ||
120 | } | ||
121 | |||
122 | setQueryParams (search: string) { | ||
123 | const queryParams: Params = {} | ||
124 | if (search) Object.assign(queryParams, { search }) | ||
125 | |||
126 | this.router.navigate([ '/admin/users/list' ], { queryParams }) | ||
127 | } | ||
128 | |||
129 | resetTableFilter () { | ||
130 | this.setTableFilter('') | ||
131 | this.setQueryParams('') | ||
132 | this.resetSearch() | ||
133 | } | ||
134 | /* END Table filter functions */ | ||
135 | |||
105 | switchToDefaultAvatar ($event: Event) { | 136 | switchToDefaultAvatar ($event: Event) { |
106 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() | 137 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() |
107 | } | 138 | } |
@@ -165,14 +196,17 @@ export class UserListComponent extends RestTable implements OnInit { | |||
165 | protected loadData () { | 196 | protected loadData () { |
166 | this.selectedUsers = [] | 197 | this.selectedUsers = [] |
167 | 198 | ||
168 | this.userService.getUsers(this.pagination, this.sort, this.search) | 199 | this.userService.getUsers({ |
169 | .subscribe( | 200 | pagination: this.pagination, |
170 | resultList => { | 201 | sort: this.sort, |
171 | this.users = resultList.data | 202 | search: this.search |
172 | this.totalRecords = resultList.total | 203 | }).subscribe( |
173 | }, | 204 | resultList => { |
205 | this.users = resultList.data | ||
206 | this.totalRecords = resultList.total | ||
207 | }, | ||
174 | 208 | ||
175 | err => this.notifier.error(err.message) | 209 | err => this.notifier.error(err.message) |
176 | ) | 210 | ) |
177 | } | 211 | } |
178 | } | 212 | } |
diff --git a/client/src/app/core/rest/rest.service.ts b/client/src/app/core/rest/rest.service.ts index c12b6bd41..9e32c6d58 100644 --- a/client/src/app/core/rest/rest.service.ts +++ b/client/src/app/core/rest/rest.service.ts | |||
@@ -9,11 +9,12 @@ interface QueryStringFilterPrefixes { | |||
9 | prefix: string | 9 | prefix: string |
10 | handler?: (v: string) => string | number | 10 | handler?: (v: string) => string | number |
11 | multiple?: boolean | 11 | multiple?: boolean |
12 | isBoolean?: boolean | ||
12 | } | 13 | } |
13 | } | 14 | } |
14 | 15 | ||
15 | type ParseQueryStringFilterResult = { | 16 | type ParseQueryStringFilterResult = { |
16 | [key: string]: string | number | (string | number)[] | 17 | [key: string]: string | number | boolean | (string | number | boolean)[] |
17 | } | 18 | } |
18 | 19 | ||
19 | @Injectable() | 20 | @Injectable() |
@@ -96,6 +97,7 @@ export class RestService { | |||
96 | return t | 97 | return t |
97 | }) | 98 | }) |
98 | .filter(t => !!t || t === 0) | 99 | .filter(t => !!t || t === 0) |
100 | .map(t => prefixObj.isBoolean ? t === 'true' : t) | ||
99 | 101 | ||
100 | if (matchedTokens.length === 0) continue | 102 | if (matchedTokens.length === 0) continue |
101 | 103 | ||
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts index ab395b1f9..2c817d45e 100644 --- a/client/src/app/core/users/user.service.ts +++ b/client/src/app/core/users/user.service.ts | |||
@@ -290,11 +290,32 @@ export class UserService { | |||
290 | }) | 290 | }) |
291 | } | 291 | } |
292 | 292 | ||
293 | getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<UserServerModel>> { | 293 | getUsers (parameters: { |
294 | pagination: RestPagination | ||
295 | sort: SortMeta | ||
296 | search?: string | ||
297 | }): Observable<ResultList<UserServerModel>> { | ||
298 | const { pagination, sort, search } = parameters | ||
299 | |||
294 | let params = new HttpParams() | 300 | let params = new HttpParams() |
295 | params = this.restService.addRestGetParams(params, pagination, sort) | 301 | params = this.restService.addRestGetParams(params, pagination, sort) |
296 | 302 | ||
297 | if (search) params = params.append('search', search) | 303 | if (search) { |
304 | const filters = this.restService.parseQueryStringFilter(search, { | ||
305 | blocked: { | ||
306 | prefix: 'banned:', | ||
307 | isBoolean: true, | ||
308 | handler: v => { | ||
309 | if (v === 'true') return v | ||
310 | if (v === 'false') return v | ||
311 | |||
312 | return undefined | ||
313 | } | ||
314 | } | ||
315 | }) | ||
316 | |||
317 | params = this.restService.addObjectParams(params, filters) | ||
318 | } | ||
298 | 319 | ||
299 | return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params }) | 320 | return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params }) |
300 | .pipe( | 321 | .pipe( |