aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+admin
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/+admin')
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html8
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html8
-rw-r--r--client/src/app/+admin/follows/follows.component.scss4
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.html10
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html81
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.scss19
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts48
7 files changed, 150 insertions, 28 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index f50828bb9..050fe40fb 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -41,8 +41,12 @@
41 </a> 41 </a>
42 </td> 42 </td>
43 43
44 <td *ngIf="follow.state === 'accepted'" i18n>Accepted</td> 44 <td *ngIf="follow.state === 'accepted'">
45 <td *ngIf="follow.state === 'pending'" i18n>Pending</td> 45 <span class="badge badge-green" i18n>Accepted</span>
46 </td>
47 <td *ngIf="follow.state === 'pending'">
48 <span class="badge badge-yellow" i18n>Pending</span>
49 </td>
46 50
47 <td>{{ follow.score }}</td> 51 <td>{{ follow.score }}</td>
48 <td>{{ follow.createdAt | date: 'short' }}</td> 52 <td>{{ follow.createdAt | date: 'short' }}</td>
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index 7d1a3d7f3..9dead2557 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -45,8 +45,12 @@
45 </a> 45 </a>
46 </td> 46 </td>
47 47
48 <td *ngIf="follow.state === 'accepted'" i18n>Accepted</td> 48 <td *ngIf="follow.state === 'accepted'">
49 <td *ngIf="follow.state === 'pending'" i18n>Pending</td> 49 <span class="badge badge-green" i18n>Accepted</span>
50 </td>
51 <td *ngIf="follow.state === 'pending'">
52 <span class="badge badge-yellow" i18n>Pending</span>
53 </td>
50 54
51 <td>{{ follow.createdAt | date: 'short' }}</td> 55 <td>{{ follow.createdAt | date: 'short' }}</td>
52 <td> 56 <td>
diff --git a/client/src/app/+admin/follows/follows.component.scss b/client/src/app/+admin/follows/follows.component.scss
index 0cffcb555..33ff17539 100644
--- a/client/src/app/+admin/follows/follows.component.scss
+++ b/client/src/app/+admin/follows/follows.component.scss
@@ -4,3 +4,7 @@
4 flex-grow: 0; 4 flex-grow: 0;
5 margin-right: 30px; 5 margin-right: 30px;
6} 6}
7
8.badge {
9 @include table-badge;
10}
diff --git a/client/src/app/+admin/system/jobs/jobs.component.html b/client/src/app/+admin/system/jobs/jobs.component.html
index 596117ab7..185fae220 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.html
+++ b/client/src/app/+admin/system/jobs/jobs.component.html
@@ -26,10 +26,10 @@
26 <ng-template pTemplate="header"> 26 <ng-template pTemplate="header">
27 <tr> 27 <tr>
28 <th style="width: 40px"></th> 28 <th style="width: 40px"></th>
29 <th class="job-id" i18n>ID</th> 29 <th style="width: 100%" class="job-id" i18n>ID</th>
30 <th class="job-type" i18n>Type</th> 30 <th style="width: 200px" class="job-type" i18n>Type</th>
31 <th class="job-date" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 31 <th style="width: 150px" class="job-date" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
32 <th class="job-state" i18n>State</th> 32 <th style="width: 150px" class="job-state" i18n>State</th>
33 </tr> 33 </tr>
34 </ng-template> 34 </ng-template>
35 35
@@ -43,7 +43,7 @@
43 43
44 <td class="job-id" [title]="job.id">{{ job.id }}</td> 44 <td class="job-id" [title]="job.id">{{ job.id }}</td>
45 <td class="job-type">{{ job.type }}</td> 45 <td class="job-type">{{ job.type }}</td>
46 <td class="job-date">{{ job.createdAt }}</td> 46 <td class="job-date">{{ job.createdAt | date: 'short' }}</td>
47 <td class="job-state" *ngIf="job.state === 'delayed'" class="text-muted"><span class="glyphicon glyphicon-repeat"></span> <span i18n>Delayed</span></td> 47 <td class="job-state" *ngIf="job.state === 'delayed'" class="text-muted"><span class="glyphicon glyphicon-repeat"></span> <span i18n>Delayed</span></td>
48 <td class="job-state" *ngIf="job.state === 'waiting'" class="text-warning"><span class="glyphicon glyphicon-hourglass"></span> <span i18n>Will start soon...</span></td> 48 <td class="job-state" *ngIf="job.state === 'waiting'" class="text-warning"><span class="glyphicon glyphicon-hourglass"></span> <span i18n>Will start soon...</span></td>
49 <td class="job-state" *ngIf="job.state === 'active'" class="text-warning"><span class="glyphicon glyphicon-cog"></span> <span i18n>Running...</span></td> 49 <td class="job-state" *ngIf="job.state === 'active'" class="text-warning"><span class="glyphicon glyphicon-cog"></span> <span i18n>Running...</span></td>
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 b022331db..571c780d6 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
@@ -50,19 +50,41 @@
50 <p-tableHeaderCheckbox></p-tableHeaderCheckbox> 50 <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
51 </th> 51 </th>
52 <th style="width: 40px"></th> 52 <th style="width: 40px"></th>
53 <th pResizableColumn i18n pSortableColumn="username">Username <p-sortIcon field="username"></p-sortIcon></th> 53 <th *ngIf="getColumn('username')" pResizableColumn i18n pSortableColumn="username">{{ getColumn('username').label }} <p-sortIcon field="username"></p-sortIcon></th>
54 <th i18n>Email</th> 54 <th *ngIf="getColumn('email')" i18n>{{ getColumn('email').label }}</th>
55 <th style="width: 140px;" i18n pSortableColumn="videoQuotaUsed">Video quota <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th> 55 <th *ngIf="getColumn('quota')" style="width: 160px;" i18n pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
56 <th style="width: 120px;" i18n>Role</th> 56 <th *ngIf="getColumn('quotaDaily')" style="width: 160px;" i18n>{{ getColumn('quotaDaily').label }}</th>
57 <th style="width: 140px;" pResizableColumn i18n>Auth plugin</th> 57 <th *ngIf="getColumn('role')" style="width: 120px;" i18n pSortableColumn="role">{{ getColumn('role').label }} <p-sortIcon field="role"></p-sortIcon></th>
58 <th style="width: 150px;" i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th> 58 <th *ngIf="getColumn('pluginAuth')" style="width: 140px;" pResizableColumn i18n>{{ getColumn('pluginAuth').label }}</th>
59 <th style="width: 60px;"></th> 59 <th *ngIf="getColumn('createdAt')" style="width: 150px;" i18n pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th>
60 <th *ngIf="getColumn('lastLoginDate')" style="width: 150px;" i18n pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th>
61 <th style="width: 60px;">
62 <div class="c-hand" ngbDropdown placement="bottom-right auto" container="body" autoClose="outside">
63 <my-global-icon iconName="columns" ngbDropdownToggle></my-global-icon>
64
65 <div role="menu" class="dropdown-menu" ngbDropdownMenu>
66 <div class="dropdown-header" i18n>Table parameters</div>
67 <div ngbDropdownItem class="dropdown-item">
68 <p-multiSelect
69 [options]="columns" [showToggleAll]="true" [(ngModel)]="selectedColumns" optionLabel="label"
70 emptyFilterMessage="No matching column found" i18n-emptyFilterMessage [filter]="false"
71 selectedItemsLabel="{0} columns displayed" i18n-emptyFilterMessage [showHeader]="false"
72 [maxSelectedLabels]="4"
73 ></p-multiSelect>
74 </div>
75 <div ngbDropdownItem class="dropdown-item">
76 <my-peertube-checkbox inputName="highlightBannedUsers" [(ngModel)]="highlightBannedUsers"
77 i18n-labelText labelText="Highlight banned users"></my-peertube-checkbox>
78 </div>
79 </div>
80 </div>
81 </th>
60 </tr> 82 </tr>
61 </ng-template> 83 </ng-template>
62 84
63 <ng-template pTemplate="body" let-expanded="expanded" let-user> 85 <ng-template pTemplate="body" let-expanded="expanded" let-user>
64 86
65 <tr [pSelectableRow]="user" [ngClass]="{ banned: user.blocked }"> 87 <tr [pSelectableRow]="user" [ngClass]="{ banned: highlightBannedUsers && user.blocked }">
66 <td> 88 <td>
67 <p-tableCheckbox [value]="user"></p-tableCheckbox> 89 <p-tableCheckbox [value]="user"></p-tableCheckbox>
68 </td> 90 </td>
@@ -73,7 +95,7 @@
73 </span> 95 </span>
74 </td> 96 </td>
75 97
76 <td> 98 <td *ngIf="getColumn('username')">
77 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]"> 99 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]">
78 <div class="chip two-lines"> 100 <div class="chip two-lines">
79 <img 101 <img
@@ -83,17 +105,16 @@
83 alt="Avatar" 105 alt="Avatar"
84 > 106 >
85 <div> 107 <div>
86 <span class="user-table-primary-text"> 108 <span class="user-table-primary-text">{{ user.account.displayName }}</span>
87 <span *ngIf="user.blocked" i18n-title title="The user was banned" class="glyphicon glyphicon-ban-circle"></span>
88 {{ user.account.displayName }}
89 </span>
90 <span class="text-muted">{{ user.username }}</span> 109 <span class="text-muted">{{ user.username }}</span>
91 </div> 110 </div>
92 </div> 111 </div>
93 </a> 112 </a>
94 </td> 113 </td>
95 114
96 <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus" [title]="user.email">{{ user.email }}</td> 115 <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus" [title]="user.email">
116 <a class="table-email" [href]="'mailto:' + user.email">{{ user.email }}</a>
117 </td>
97 118
98 <ng-template #emailWithVerificationStatus> 119 <ng-template #emailWithVerificationStatus>
99 <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login"> 120 <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login">
@@ -106,14 +127,38 @@
106 </ng-template> 127 </ng-template>
107 </ng-template> 128 </ng-template>
108 129
109 <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td> 130 <td *ngIf="getColumn('quota')">
110 <td>{{ user.roleLabel }}</td> 131 <div class="progress" i18n-title title="Total video quota">
132 <div class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaPercentage(user) + '%' }"
133 [attr.aria-valuenow]="user.rawVideoQuotaUsed" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuota">
134 </div>
135 <span>{{ user.videoQuotaUsed }}</span>
136 <span>{{ user.videoQuota }}</span>
137 </div>
138 </td>
139
140 <td *ngIf="getColumn('quotaDaily')">
141 <div class="progress" i18n-title title="Total daily video quota">
142 <div class="progress-bar secondary" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }"
143 [attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily">
144 </div>
145 <span>{{ user.videoQuotaUsedDaily }}</span>
146 <span>{{ user.videoQuotaDaily }}</span>
147 </div>
148 </td>
111 149
112 <td> 150 <td *ngIf="getColumn('role')">
151 <span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span>
152 <span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span>
153 </td>
154
155 <td *ngIf="getColumn('pluginAuth')">
113 <ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container> 156 <ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container>
114 </td> 157 </td>
115 158
116 <td [title]="user.createdAt">{{ user.createdAt | date: 'short' }}</td> 159 <td *ngIf="getColumn('createdAt')" [title]="user.createdAt">{{ user.createdAt | date: 'short' }}</td>
160
161 <td *ngIf="getColumn('lastLoginDate')" [title]="user.lastLoginDate">{{ user.lastLoginDate | date: 'short' }}</td>
117 162
118 <td class="action-cell"> 163 <td class="action-cell">
119 <my-user-moderation-dropdown 164 <my-user-moderation-dropdown
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 2b84dec75..59ad7da35 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
@@ -9,6 +9,11 @@ tr.banned > td {
9 background-color: lighten($color: $red, $amount: 40) !important; 9 background-color: lighten($color: $red, $amount: 40) !important;
10} 10}
11 11
12.table-email {
13 @include disable-default-a-behaviour;
14 color: pvar(--mainForegroundColor);
15}
16
12.banned-info { 17.banned-info {
13 font-style: italic; 18 font-style: italic;
14} 19}
@@ -36,10 +41,24 @@ p-tableCheckbox {
36 top: -2.5px; 41 top: -2.5px;
37} 42}
38 43
44my-global-icon {
45 width: 18px;
46}
47
39.chip { 48.chip {
40 @include chip; 49 @include chip;
41} 50}
42 51
52.badge {
53 @include table-badge;
54}
55
56.progress {
57 @include progressbar;
58 width: auto;
59 max-width: 100%;
60}
61
43.input-group { 62.input-group {
44 @include peertube-input-group(300px); 63 @include peertube-input-group(300px);
45 input { 64 input {
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 0b72b07c1..b2978212e 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
@@ -4,7 +4,7 @@ import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, Serve
4import { Actor, DropdownAction } from '@app/shared/shared-main' 4import { Actor, DropdownAction } from '@app/shared/shared-main'
5import { UserBanModalComponent } from '@app/shared/shared-moderation' 5import { UserBanModalComponent } from '@app/shared/shared-moderation'
6import { I18n } from '@ngx-translate/i18n-polyfill' 6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { ServerConfig, User } from '@shared/models' 7import { ServerConfig, User, UserRole } from '@shared/models'
8import { Params, Router, ActivatedRoute } from '@angular/router' 8import { Params, Router, ActivatedRoute } from '@angular/router'
9 9
10@Component({ 10@Component({
@@ -19,9 +19,12 @@ export class UserListComponent extends RestTable implements OnInit {
19 totalRecords = 0 19 totalRecords = 0
20 sort: SortMeta = { field: 'createdAt', order: 1 } 20 sort: SortMeta = { field: 'createdAt', order: 1 }
21 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 21 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
22 highlightBannedUsers = false
22 23
23 selectedUsers: User[] = [] 24 selectedUsers: User[] = []
24 bulkUserActions: DropdownAction<User[]>[][] = [] 25 bulkUserActions: DropdownAction<User[]>[][] = []
26 columns: { key: string, label: string }[]
27 _selectedColumns: { key: string, label: string }[]
25 28
26 private serverConfig: ServerConfig 29 private serverConfig: ServerConfig
27 30
@@ -46,6 +49,14 @@ export class UserListComponent extends RestTable implements OnInit {
46 return this.serverConfig.signup.requiresEmailVerification 49 return this.serverConfig.signup.requiresEmailVerification
47 } 50 }
48 51
52 get selectedColumns () {
53 return this._selectedColumns
54 }
55
56 set selectedColumns (val) {
57 this._selectedColumns = val
58 }
59
49 ngOnInit () { 60 ngOnInit () {
50 this.serverConfig = this.serverService.getTmpConfig() 61 this.serverConfig = this.serverService.getTmpConfig()
51 this.serverService.getConfig() 62 this.serverService.getConfig()
@@ -92,12 +103,47 @@ export class UserListComponent extends RestTable implements OnInit {
92 } 103 }
93 ] 104 ]
94 ] 105 ]
106
107 this.columns = [
108 { key: 'username', label: 'Username' },
109 { key: 'email', label: 'Email' },
110 { key: 'quota', label: 'Video quota' },
111 { key: 'role', label: 'Role' },
112 { key: 'createdAt', label: 'Created' }
113 ]
114 this.selectedColumns = [...this.columns]
115 this.columns.push({ key: 'quotaDaily', label: 'Daily quota' })
116 this.columns.push({ key: 'pluginAuth', label: 'Auth plugin' })
117 this.columns.push({ key: 'lastLoginDate', label: 'Last login' })
95 } 118 }
96 119
97 getIdentifier () { 120 getIdentifier () {
98 return 'UserListComponent' 121 return 'UserListComponent'
99 } 122 }
100 123
124 getRoleClass (role: UserRole) {
125 switch (role) {
126 case UserRole.ADMINISTRATOR:
127 return 'badge-purple'
128 case UserRole.MODERATOR:
129 return 'badge-blue'
130 default:
131 return 'badge-yellow'
132 }
133 }
134
135 getColumn (key: string) {
136 return this.selectedColumns.find((col: any) => col.key === key)
137 }
138
139 getUserVideoQuotaPercentage (user: User & { rawVideoQuota: number, rawVideoQuotaUsed: number}) {
140 return user.rawVideoQuotaUsed * 100 / user.rawVideoQuota
141 }
142
143 getUserVideoQuotaDailyPercentage (user: User & { rawVideoQuotaDaily: number, rawVideoQuotaUsedDaily: number}) {
144 return user.rawVideoQuotaUsedDaily * 100 / user.rawVideoQuotaDaily
145 }
146
101 openBanUserModal (users: User[]) { 147 openBanUserModal (users: User[]) {
102 for (const user of users) { 148 for (const user of users) {
103 if (user.username === 'root') { 149 if (user.username === 'root') {