aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+admin/users/user-list
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-10-27 09:36:37 +0200
committerChocobozzz <chocobozzz@cpy.re>2021-10-29 11:48:21 +0200
commit00004f7f6b966a975498612117212b5373f4103c (patch)
tree4899b77da3c55bf1fbb77f927d9da5cd873e2c2a /client/src/app/+admin/users/user-list
parentbd898dd76babf6ab33a0040297bfb40a69a69dda (diff)
downloadPeerTube-00004f7f6b966a975498612117212b5373f4103c.tar.gz
PeerTube-00004f7f6b966a975498612117212b5373f4103c.tar.zst
PeerTube-00004f7f6b966a975498612117212b5373f4103c.zip
Put admin users in overview tab
Diffstat (limited to 'client/src/app/+admin/users/user-list')
-rw-r--r--client/src/app/+admin/users/user-list/index.ts1
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html163
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.scss65
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts246
4 files changed, 0 insertions, 475 deletions
diff --git a/client/src/app/+admin/users/user-list/index.ts b/client/src/app/+admin/users/user-list/index.ts
deleted file mode 100644
index 1826a4abe..000000000
--- a/client/src/app/+admin/users/user-list/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './user-list.component'
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
deleted file mode 100644
index c82f3c06f..000000000
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ /dev/null
@@ -1,163 +0,0 @@
1<p-table
2 [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers"
4 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
5 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
6 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
7 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
8>
9 <ng-template pTemplate="caption">
10 <div class="caption">
11 <div class="left-buttons">
12 <my-action-dropdown
13 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
14 [actions]="bulkUserActions" [entry]="selectedUsers"
15 >
16 </my-action-dropdown>
17
18 <a *ngIf="!isInSelectionMode()" class="add-button" routerLink="/admin/users/create">
19 <my-global-icon iconName="user-add" aria-hidden="true"></my-global-icon>
20 <ng-container i18n>Create user</ng-container>
21 </a>
22 </div>
23
24 <div class="ml-auto">
25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
26 </div>
27
28 </div>
29 </ng-template>
30
31 <ng-template pTemplate="header">
32 <tr>
33 <th style="width: 40px">
34 <p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
35 </th>
36 <th style="width: 40px"></th>
37 <th style="width: 60px;">
38 <div class="c-hand column-toggle" ngbDropdown placement="bottom-left auto" container="body" autoClose="outside">
39 <my-global-icon iconName="columns" ngbDropdownToggle></my-global-icon>
40
41 <div role="menu" class="dropdown-menu" ngbDropdownMenu>
42 <div class="dropdown-header" i18n>Table parameters</div>
43 <div ngbDropdownItem class="dropdown-item">
44 <my-select-checkbox
45 name="columns"
46 [availableItems]="columns"
47 [selectableGroup]="false" [(ngModel)]="selectedColumns"
48 i18n-placeholder placeholder="Select columns"
49 >
50 </my-select-checkbox>
51 </div>
52 <div ngbDropdownItem class="dropdown-item">
53 <my-peertube-checkbox inputName="highlightBannedUsers" [(ngModel)]="highlightBannedUsers"
54 i18n-labelText labelText="Highlight banned users"></my-peertube-checkbox>
55 </div>
56 </div>
57 </div>
58 </th>
59 <th *ngIf="isSelected('username')" pResizableColumn pSortableColumn="username">{{ getColumn('username').label }} <p-sortIcon field="username"></p-sortIcon></th>
60 <th *ngIf="isSelected('email')">{{ getColumn('email').label }}</th>
61 <th *ngIf="isSelected('quota')" style="width: 160px;" pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
62 <th *ngIf="isSelected('quotaDaily')" style="width: 160px;">{{ getColumn('quotaDaily').label }}</th>
63 <th *ngIf="isSelected('role')" style="width: 120px;" pSortableColumn="role">{{ getColumn('role').label }} <p-sortIcon field="role"></p-sortIcon></th>
64 <th *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn >{{ getColumn('pluginAuth').label }}</th>
65 <th *ngIf="isSelected('createdAt')" style="width: 150px;" pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th>
66 <th *ngIf="isSelected('lastLoginDate')" style="width: 150px;" pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th>
67 </tr>
68 </ng-template>
69
70 <ng-template pTemplate="body" let-expanded="expanded" let-user>
71
72 <tr [pSelectableRow]="user" [ngClass]="{ banned: highlightBannedUsers && user.blocked }">
73 <td class="checkbox-cell">
74 <p-tableCheckbox [value]="user" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
75 </td>
76
77 <td class="expand-cell" [ngClass]="{ 'empty-cell': !user.blockedReason }">
78 <span *ngIf="user.blockedReason" class="expander" [pRowToggler]="user">
79 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
80 </span>
81 </td>
82
83 <td class="action-cell">
84 <my-user-moderation-dropdown *ngIf="!isInSelectionMode()" [user]="user" container="body"
85 (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()">
86 </my-user-moderation-dropdown>
87 </td>
88
89 <td *ngIf="isSelected('username')">
90 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/a/' + user.username ]">
91 <div class="chip two-lines">
92 <my-actor-avatar [account]="user?.account" size="32"></my-actor-avatar>
93 <div>
94 <span class="user-table-primary-text">{{ user.account.displayName }}</span>
95 <span class="text-muted">{{ user.username }}</span>
96 </div>
97 </div>
98 </a>
99 </td>
100
101 <td *ngIf="isSelected('email')" [title]="user.email">
102 <ng-container *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">
103 <a class="table-email" [href]="'mailto:' + user.email">{{ user.email }}</a>
104 </ng-container>
105 </td>
106
107 <ng-template #emailWithVerificationStatus>
108 <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login">
109 <em>? {{ user.email }}</em>
110 </td>
111 <ng-template #emailVerifiedNotFalse>
112 <td i18n-title title="User's email is verified / User can login without email verification">
113 &#x2713; {{ user.email }}
114 </td>
115 </ng-template>
116 </ng-template>
117
118 <td *ngIf="isSelected('quota')">
119 <div class="progress" i18n-title title="Total video quota">
120 <div class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaPercentage(user) + '%' }"
121 [attr.aria-valuenow]="user.rawVideoQuotaUsed" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuota">
122 </div>
123 <span>{{ user.videoQuotaUsed }}</span>
124 <span>{{ user.videoQuota }}</span>
125 </div>
126 </td>
127
128 <td *ngIf="isSelected('quotaDaily')">
129 <div class="progress" i18n-title title="Total daily video quota">
130 <div class="progress-bar secondary" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }"
131 [attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily">
132 </div>
133 <span>{{ user.videoQuotaUsedDaily }}</span>
134 <span>{{ user.videoQuotaDaily }}</span>
135 </div>
136 </td>
137
138 <td *ngIf="isSelected('role')">
139 <span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span>
140 <span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span>
141 </td>
142
143 <td *ngIf="isSelected('pluginAuth')">
144 <ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container>
145 </td>
146
147 <td *ngIf="isSelected('createdAt')" [title]="user.createdAt">{{ user.createdAt | date: 'short' }}</td>
148
149 <td *ngIf="isSelected('lastLoginDate')" [title]="user.lastLoginDate">{{ user.lastLoginDate | date: 'short' }}</td>
150 </tr>
151 </ng-template>
152
153 <ng-template pTemplate="rowexpansion" let-user>
154 <tr class="user-blocked-reason">
155 <td colspan="7">
156 <span i18n class="ban-reason-label">Ban reason:</span>
157 {{ user.blockedReason }}
158 </td>
159 </tr>
160 </ng-template>
161</p-table>
162
163<my-user-ban-modal #userBanModal (userBanned)="onUserChanged()"></my-user-ban-modal>
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
deleted file mode 100644
index e425306b5..000000000
--- a/client/src/app/+admin/users/user-list/user-list.component.scss
+++ /dev/null
@@ -1,65 +0,0 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.add-button {
5 @include create-button;
6}
7
8tr.banned > td {
9 background-color: lighten($color: $red, $amount: 40) !important;
10}
11
12.table-email {
13 @include disable-default-a-behaviour;
14
15 color: pvar(--mainForegroundColor);
16}
17
18.banned-info {
19 font-style: italic;
20}
21
22.ban-reason-label {
23 font-weight: $font-semibold;
24}
25
26.user-table-primary-text .glyphicon {
27 @include margin-left(0.1rem);
28
29 font-size: 80%;
30 color: #808080;
31}
32
33p-tableCheckbox {
34 position: relative;
35 top: -2.5px;
36}
37
38my-global-icon {
39 width: 18px;
40}
41
42.chip {
43 @include chip;
44}
45
46.badge {
47 @include table-badge;
48}
49
50.progress {
51 @include progressbar($small: true);
52
53 width: auto;
54 max-width: 100%;
55}
56
57@media screen and (max-width: $primeng-breakpoint) {
58 .progress {
59 width: 100%;
60 }
61
62 .empty-cell {
63 padding: 0;
64 }
65}
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
deleted file mode 100644
index 548e6e80f..000000000
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ /dev/null
@@ -1,246 +0,0 @@
1import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core'
5import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main'
7import { UserBanModalComponent } from '@app/shared/shared-moderation'
8import { User, UserRole } from '@shared/models'
9
10type UserForList = User & {
11 rawVideoQuota: number
12 rawVideoQuotaUsed: number
13 rawVideoQuotaDaily: number
14 rawVideoQuotaUsedDaily: number
15}
16
17@Component({
18 selector: 'my-user-list',
19 templateUrl: './user-list.component.html',
20 styleUrls: [ './user-list.component.scss' ]
21})
22export class UserListComponent extends RestTable implements OnInit {
23 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
24
25 users: User[] = []
26
27 totalRecords = 0
28 sort: SortMeta = { field: 'createdAt', order: 1 }
29 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
30
31 highlightBannedUsers = false
32
33 selectedUsers: User[] = []
34 bulkUserActions: DropdownAction<User[]>[][] = []
35 columns: { id: string, label: string }[]
36
37 inputFilters: AdvancedInputFilter[] = [
38 {
39 title: $localize`Advanced filters`,
40 children: [
41 {
42 queryParams: { search: 'banned:true' },
43 label: $localize`Banned users`
44 }
45 ]
46 }
47 ]
48
49 requiresEmailVerification = false
50
51 private _selectedColumns: string[]
52
53 constructor (
54 protected route: ActivatedRoute,
55 protected router: Router,
56 private notifier: Notifier,
57 private confirmService: ConfirmService,
58 private serverService: ServerService,
59 private auth: AuthService,
60 private userService: UserService
61 ) {
62 super()
63 }
64
65 get authUser () {
66 return this.auth.getUser()
67 }
68
69 get selectedColumns () {
70 return this._selectedColumns
71 }
72
73 set selectedColumns (val: string[]) {
74 this._selectedColumns = val
75 }
76
77 ngOnInit () {
78 this.serverService.getConfig()
79 .subscribe(config => this.requiresEmailVerification = config.signup.requiresEmailVerification)
80
81 this.initialize()
82
83 this.bulkUserActions = [
84 [
85 {
86 label: $localize`Delete`,
87 description: $localize`Videos will be deleted, comments will be tombstoned.`,
88 handler: users => this.removeUsers(users),
89 isDisplayed: users => users.every(u => this.authUser.canManage(u))
90 },
91 {
92 label: $localize`Ban`,
93 description: $localize`User won't be able to login anymore, but videos and comments will be kept as is.`,
94 handler: users => this.openBanUserModal(users),
95 isDisplayed: users => users.every(u => this.authUser.canManage(u) && u.blocked === false)
96 },
97 {
98 label: $localize`Unban`,
99 handler: users => this.unbanUsers(users),
100 isDisplayed: users => users.every(u => this.authUser.canManage(u) && u.blocked === true)
101 }
102 ],
103 [
104 {
105 label: $localize`Set Email as Verified`,
106 handler: users => this.setEmailsAsVerified(users),
107 isDisplayed: users => {
108 return this.requiresEmailVerification &&
109 users.every(u => this.authUser.canManage(u) && !u.blocked && u.emailVerified === false)
110 }
111 }
112 ]
113 ]
114
115 this.columns = [
116 { id: 'username', label: $localize`Username` },
117 { id: 'email', label: $localize`Email` },
118 { id: 'quota', label: $localize`Video quota` },
119 { id: 'role', label: $localize`Role` },
120 { id: 'createdAt', label: $localize`Created` }
121 ]
122
123 this.selectedColumns = this.columns.map(c => c.id)
124
125 this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` })
126 this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` })
127 this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` })
128 }
129
130 getIdentifier () {
131 return 'UserListComponent'
132 }
133
134 getRoleClass (role: UserRole) {
135 switch (role) {
136 case UserRole.ADMINISTRATOR:
137 return 'badge-purple'
138 case UserRole.MODERATOR:
139 return 'badge-blue'
140 default:
141 return 'badge-yellow'
142 }
143 }
144
145 isSelected (id: string) {
146 return this.selectedColumns.find(c => c === id)
147 }
148
149 getColumn (id: string) {
150 return this.columns.find(c => c.id === id)
151 }
152
153 getUserVideoQuotaPercentage (user: UserForList) {
154 return user.rawVideoQuotaUsed * 100 / user.rawVideoQuota
155 }
156
157 getUserVideoQuotaDailyPercentage (user: UserForList) {
158 return user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily
159 }
160
161 openBanUserModal (users: User[]) {
162 for (const user of users) {
163 if (user.username === 'root') {
164 this.notifier.error($localize`You cannot ban root.`)
165 return
166 }
167 }
168
169 this.userBanModal.openModal(users)
170 }
171
172 onUserChanged () {
173 this.reloadData()
174 }
175
176 async unbanUsers (users: User[]) {
177 const res = await this.confirmService.confirm($localize`Do you really want to unban ${users.length} users?`, $localize`Unban`)
178 if (res === false) return
179
180 this.userService.unbanUsers(users)
181 .subscribe({
182 next: () => {
183 this.notifier.success($localize`${users.length} users unbanned.`)
184 this.reloadData()
185 },
186
187 error: err => this.notifier.error(err.message)
188 })
189 }
190
191 async removeUsers (users: User[]) {
192 for (const user of users) {
193 if (user.username === 'root') {
194 this.notifier.error($localize`You cannot delete root.`)
195 return
196 }
197 }
198
199 const message = $localize`If you remove these users, you will not be able to create others with the same username!`
200 const res = await this.confirmService.confirm(message, $localize`Delete`)
201 if (res === false) return
202
203 this.userService.removeUser(users)
204 .subscribe({
205 next: () => {
206 this.notifier.success($localize`${users.length} users deleted.`)
207 this.reloadData()
208 },
209
210 error: err => this.notifier.error(err.message)
211 })
212 }
213
214 setEmailsAsVerified (users: User[]) {
215 this.userService.updateUsers(users, { emailVerified: true })
216 .subscribe({
217 next: () => {
218 this.notifier.success($localize`${users.length} users email set as verified.`)
219 this.reloadData()
220 },
221
222 error: err => this.notifier.error(err.message)
223 })
224 }
225
226 isInSelectionMode () {
227 return this.selectedUsers.length !== 0
228 }
229
230 protected reloadData () {
231 this.selectedUsers = []
232
233 this.userService.getUsers({
234 pagination: this.pagination,
235 sort: this.sort,
236 search: this.search
237 }).subscribe({
238 next: resultList => {
239 this.users = resultList.data
240 this.totalRecords = resultList.total
241 },
242
243 error: err => this.notifier.error(err.message)
244 })
245 }
246}