aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/admin.module.ts5
-rw-r--r--client/src/app/+admin/users/index.ts1
-rw-r--r--client/src/app/+admin/users/shared/index.ts1
-rw-r--r--client/src/app/+admin/users/shared/user.service.ts96
-rw-r--r--client/src/app/+admin/users/user-edit/user-create.component.ts2
-rw-r--r--client/src/app/+admin/users/user-edit/user-update.component.ts2
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html3
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts95
-rw-r--r--client/src/app/shared/moderation/index.ts2
-rw-r--r--client/src/app/shared/moderation/user-ban-modal.component.html (renamed from client/src/app/+admin/users/user-list/user-ban-modal.component.html)0
-rw-r--r--client/src/app/shared/moderation/user-ban-modal.component.scss (renamed from client/src/app/+admin/users/user-list/user-ban-modal.component.scss)0
-rw-r--r--client/src/app/shared/moderation/user-ban-modal.component.ts (renamed from client/src/app/+admin/users/user-list/user-ban-modal.component.ts)5
-rw-r--r--client/src/app/shared/moderation/user-moderation-dropdown.component.html3
-rw-r--r--client/src/app/shared/moderation/user-moderation-dropdown.component.scss0
-rw-r--r--client/src/app/shared/moderation/user-moderation-dropdown.component.ts128
-rw-r--r--client/src/app/shared/shared.module.ts8
-rw-r--r--client/src/app/shared/users/user.service.ts91
17 files changed, 235 insertions, 207 deletions
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 5784609ef..8c6db98d9 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -10,9 +10,8 @@ import { FollowingListComponent } from './follows/following-list/following-list.
10import { JobsComponent } from './jobs/job.component' 10import { JobsComponent } from './jobs/job.component'
11import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' 11import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
12import { JobService } from './jobs/shared/job.service' 12import { JobService } from './jobs/shared/job.service'
13import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' 13import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent } from './users'
14import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation' 14import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation'
15import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
16import { ModerationComponent } from '@app/+admin/moderation/moderation.component' 15import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
17import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' 16import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component'
18import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' 17import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
@@ -37,7 +36,6 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service
37 UserCreateComponent, 36 UserCreateComponent,
38 UserUpdateComponent, 37 UserUpdateComponent,
39 UserListComponent, 38 UserListComponent,
40 UserBanModalComponent,
41 39
42 ModerationComponent, 40 ModerationComponent,
43 VideoBlacklistListComponent, 41 VideoBlacklistListComponent,
@@ -58,7 +56,6 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service
58 providers: [ 56 providers: [
59 FollowService, 57 FollowService,
60 RedundancyService, 58 RedundancyService,
61 UserService,
62 JobService, 59 JobService,
63 ConfigService 60 ConfigService
64 ] 61 ]
diff --git a/client/src/app/+admin/users/index.ts b/client/src/app/+admin/users/index.ts
index efcd0d9cb..156e54d89 100644
--- a/client/src/app/+admin/users/index.ts
+++ b/client/src/app/+admin/users/index.ts
@@ -1,4 +1,3 @@
1export * from './shared'
2export * from './user-edit' 1export * from './user-edit'
3export * from './user-list' 2export * from './user-list'
4export * from './users.component' 3export * from './users.component'
diff --git a/client/src/app/+admin/users/shared/index.ts b/client/src/app/+admin/users/shared/index.ts
deleted file mode 100644
index 1f1302dc5..000000000
--- a/client/src/app/+admin/users/shared/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './user.service'
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
deleted file mode 100644
index 470beef08..000000000
--- a/client/src/app/+admin/users/shared/user.service.ts
+++ /dev/null
@@ -1,96 +0,0 @@
1import { catchError, map } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { BytesPipe } from 'ngx-pipes'
5import { SortMeta } from 'primeng/components/common/sortmeta'
6import { Observable } from 'rxjs'
7import { ResultList, UserCreate, UserUpdate, User, UserRole } from '../../../../../../shared'
8import { environment } from '../../../../environments/environment'
9import { RestExtractor, RestPagination, RestService } from '../../../shared'
10import { I18n } from '@ngx-translate/i18n-polyfill'
11
12@Injectable()
13export class UserService {
14 private static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
15 private bytesPipe = new BytesPipe()
16
17 constructor (
18 private authHttp: HttpClient,
19 private restService: RestService,
20 private restExtractor: RestExtractor,
21 private i18n: I18n
22 ) { }
23
24 addUser (userCreate: UserCreate) {
25 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
26 .pipe(
27 map(this.restExtractor.extractDataBool),
28 catchError(err => this.restExtractor.handleError(err))
29 )
30 }
31
32 updateUser (userId: number, userUpdate: UserUpdate) {
33 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
34 .pipe(
35 map(this.restExtractor.extractDataBool),
36 catchError(err => this.restExtractor.handleError(err))
37 )
38 }
39
40 getUser (userId: number) {
41 return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId)
42 .pipe(catchError(err => this.restExtractor.handleError(err)))
43 }
44
45 getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> {
46 let params = new HttpParams()
47 params = this.restService.addRestGetParams(params, pagination, sort)
48
49 return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
50 .pipe(
51 map(res => this.restExtractor.convertResultListDateToHuman(res)),
52 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
53 catchError(err => this.restExtractor.handleError(err))
54 )
55 }
56
57 removeUser (user: User) {
58 return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
59 .pipe(catchError(err => this.restExtractor.handleError(err)))
60 }
61
62 banUser (user: User, reason?: string) {
63 const body = reason ? { reason } : {}
64
65 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body)
66 .pipe(catchError(err => this.restExtractor.handleError(err)))
67 }
68
69 unbanUser (user: User) {
70 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {})
71 .pipe(catchError(err => this.restExtractor.handleError(err)))
72 }
73
74 private formatUser (user: User) {
75 let videoQuota
76 if (user.videoQuota === -1) {
77 videoQuota = this.i18n('Unlimited')
78 } else {
79 videoQuota = this.bytesPipe.transform(user.videoQuota, 0)
80 }
81
82 const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0)
83
84 const roleLabels: { [ id in UserRole ]: string } = {
85 [UserRole.USER]: this.i18n('User'),
86 [UserRole.ADMINISTRATOR]: this.i18n('Administrator'),
87 [UserRole.MODERATOR]: this.i18n('Moderator')
88 }
89
90 return Object.assign(user, {
91 roleLabel: roleLabels[user.role],
92 videoQuota,
93 videoQuotaUsed
94 })
95 }
96}
diff --git a/client/src/app/+admin/users/user-edit/user-create.component.ts b/client/src/app/+admin/users/user-edit/user-create.component.ts
index 132e280b9..dd8e4efd5 100644
--- a/client/src/app/+admin/users/user-edit/user-create.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-create.component.ts
@@ -1,7 +1,6 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router' 2import { Router } from '@angular/router'
3import { NotificationsService } from 'angular2-notifications' 3import { NotificationsService } from 'angular2-notifications'
4import { UserService } from '../shared'
5import { ServerService } from '../../../core' 4import { ServerService } from '../../../core'
6import { UserCreate, UserRole } from '../../../../../../shared' 5import { UserCreate, UserRole } from '../../../../../../shared'
7import { UserEdit } from './user-edit' 6import { UserEdit } from './user-edit'
@@ -9,6 +8,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
9import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 8import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
10import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' 9import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
11import { ConfigService } from '@app/+admin/config/shared/config.service' 10import { ConfigService } from '@app/+admin/config/shared/config.service'
11import { UserService } from '@app/shared'
12 12
13@Component({ 13@Component({
14 selector: 'my-user-create', 14 selector: 'my-user-create',
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts
index 9eb91ac95..cd3885a99 100644
--- a/client/src/app/+admin/users/user-edit/user-update.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-update.component.ts
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { Subscription } from 'rxjs' 3import { Subscription } from 'rxjs'
4import { NotificationsService } from 'angular2-notifications' 4import { NotificationsService } from 'angular2-notifications'
5import { UserService } from '../shared'
6import { ServerService } from '../../../core' 5import { ServerService } from '../../../core'
7import { UserEdit } from './user-edit' 6import { UserEdit } from './user-edit'
8import { User, UserUpdate } from '../../../../../../shared' 7import { User, UserUpdate } from '../../../../../../shared'
@@ -10,6 +9,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
10import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 9import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
11import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' 10import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
12import { ConfigService } from '@app/+admin/config/shared/config.service' 11import { ConfigService } from '@app/+admin/config/shared/config.service'
12import { UserService } from '@app/shared'
13 13
14@Component({ 14@Component({
15 selector: 'my-user-update', 15 selector: 'my-user-update',
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 bb1b26442..2479ce9e4 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
@@ -40,7 +40,7 @@
40 <td>{{ user.roleLabel }}</td> 40 <td>{{ user.roleLabel }}</td>
41 <td>{{ user.createdAt }}</td> 41 <td>{{ user.createdAt }}</td>
42 <td class="action-cell"> 42 <td class="action-cell">
43 <my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown> 43 <my-user-moderation-dropdown [user]="user" (userChanged)="onUserChanged()"></my-user-moderation-dropdown>
44 </td> 44 </td>
45 </tr> 45 </tr>
46 </ng-template> 46 </ng-template>
@@ -55,4 +55,3 @@
55 </ng-template> 55 </ng-template>
56</p-table> 56</p-table>
57 57
58<my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal> \ No newline at end of file
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 100ffc00e..dee3ed643 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
@@ -1,13 +1,9 @@
1import { Component, OnInit, ViewChild } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
3import { SortMeta } from 'primeng/components/common/sortmeta' 3import { SortMeta } from 'primeng/components/common/sortmeta'
4import { ConfirmService } from '../../../core' 4import { ConfirmService } from '../../../core'
5import { RestPagination, RestTable } from '../../../shared' 5import { RestPagination, RestTable, UserService } from '../../../shared'
6import { UserService } from '../shared'
7import { I18n } from '@ngx-translate/i18n-polyfill' 6import { I18n } from '@ngx-translate/i18n-polyfill'
8import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
9import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
10import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
11import { User } from '../../../../../../shared' 7import { User } from '../../../../../../shared'
12 8
13@Component({ 9@Component({
@@ -16,16 +12,11 @@ import { User } from '../../../../../../shared'
16 styleUrls: [ './user-list.component.scss' ] 12 styleUrls: [ './user-list.component.scss' ]
17}) 13})
18export class UserListComponent extends RestTable implements OnInit { 14export class UserListComponent extends RestTable implements OnInit {
19 @ViewChild('userBanModal') userBanModal: UserBanModalComponent
20
21 users: User[] = [] 15 users: User[] = []
22 totalRecords = 0 16 totalRecords = 0
23 rowsPerPage = 10 17 rowsPerPage = 10
24 sort: SortMeta = { field: 'createdAt', order: 1 } 18 sort: SortMeta = { field: 'createdAt', order: 1 }
25 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 19 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
26 userActions: DropdownAction<User>[] = []
27
28 private openedModal: NgbModalRef
29 20
30 constructor ( 21 constructor (
31 private notificationsService: NotificationsService, 22 private notificationsService: NotificationsService,
@@ -34,96 +25,16 @@ export class UserListComponent extends RestTable implements OnInit {
34 private i18n: I18n 25 private i18n: I18n
35 ) { 26 ) {
36 super() 27 super()
37
38 this.userActions = [
39 {
40 label: this.i18n('Edit'),
41 linkBuilder: this.getRouterUserEditLink
42 },
43 {
44 label: this.i18n('Delete'),
45 handler: user => this.removeUser(user)
46 },
47 {
48 label: this.i18n('Ban'),
49 handler: user => this.openBanUserModal(user),
50 isDisplayed: user => !user.blocked
51 },
52 {
53 label: this.i18n('Unban'),
54 handler: user => this.unbanUser(user),
55 isDisplayed: user => user.blocked
56 }
57 ]
58 } 28 }
59 29
60 ngOnInit () { 30 ngOnInit () {
61 this.loadSort() 31 this.loadSort()
62 } 32 }
63 33
64 hideBanUserModal () { 34 onUserChanged () {
65 this.openedModal.close()
66 }
67
68 openBanUserModal (user: User) {
69 if (user.username === 'root') {
70 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
71 return
72 }
73
74 this.userBanModal.openModal(user)
75 }
76
77 onUserBanned () {
78 this.loadData() 35 this.loadData()
79 } 36 }
80 37
81 async unbanUser (user: User) {
82 const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username })
83 const res = await this.confirmService.confirm(message, this.i18n('Unban'))
84 if (res === false) return
85
86 this.userService.unbanUser(user)
87 .subscribe(
88 () => {
89 this.notificationsService.success(
90 this.i18n('Success'),
91 this.i18n('User {{username}} unbanned.', { username: user.username })
92 )
93 this.loadData()
94 },
95
96 err => this.notificationsService.error(this.i18n('Error'), err.message)
97 )
98 }
99
100 async removeUser (user: User) {
101 if (user.username === 'root') {
102 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
103 return
104 }
105
106 const message = this.i18n('If you remove this user, you will not be able to create another with the same username!')
107 const res = await this.confirmService.confirm(message, this.i18n('Delete'))
108 if (res === false) return
109
110 this.userService.removeUser(user).subscribe(
111 () => {
112 this.notificationsService.success(
113 this.i18n('Success'),
114 this.i18n('User {{username}} deleted.', { username: user.username })
115 )
116 this.loadData()
117 },
118
119 err => this.notificationsService.error(this.i18n('Error'), err.message)
120 )
121 }
122
123 getRouterUserEditLink (user: User) {
124 return [ '/admin', 'users', 'update', user.id ]
125 }
126
127 protected loadData () { 38 protected loadData () {
128 this.userService.getUsers(this.pagination, this.sort) 39 this.userService.getUsers(this.pagination, this.sort)
129 .subscribe( 40 .subscribe(
diff --git a/client/src/app/shared/moderation/index.ts b/client/src/app/shared/moderation/index.ts
new file mode 100644
index 000000000..2245294c5
--- /dev/null
+++ b/client/src/app/shared/moderation/index.ts
@@ -0,0 +1,2 @@
1export * from './user-ban-modal.component'
2export * from './user-moderation-dropdown.component' \ No newline at end of file
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.html b/client/src/app/shared/moderation/user-ban-modal.component.html
index b2958caa4..b2958caa4 100644
--- a/client/src/app/+admin/users/user-list/user-ban-modal.component.html
+++ b/client/src/app/shared/moderation/user-ban-modal.component.html
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.scss b/client/src/app/shared/moderation/user-ban-modal.component.scss
index 84562f15c..84562f15c 100644
--- a/client/src/app/+admin/users/user-list/user-ban-modal.component.scss
+++ b/client/src/app/shared/moderation/user-ban-modal.component.scss
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.ts b/client/src/app/shared/moderation/user-ban-modal.component.ts
index 4fd4d561c..d49783cd2 100644
--- a/client/src/app/+admin/users/user-list/user-ban-modal.component.ts
+++ b/client/src/app/shared/moderation/user-ban-modal.component.ts
@@ -1,12 +1,11 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
3import { FormReactive, UserValidatorsService } from '../../../shared'
4import { UserService } from '../shared'
5import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
8import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
9import { User } from '../../../../../../shared' 7import { FormReactive, UserValidatorsService } from '@app/shared/forms'
8import { User, UserService } from '@app/shared/users'
10 9
11@Component({ 10@Component({
12 selector: 'my-user-ban-modal', 11 selector: 'my-user-ban-modal',
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
new file mode 100644
index 000000000..ed8a4dc66
--- /dev/null
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
@@ -0,0 +1,3 @@
1<my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal>
2
3<my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown> \ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.scss b/client/src/app/shared/moderation/user-moderation-dropdown.component.scss
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.scss
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
new file mode 100644
index 000000000..d92423476
--- /dev/null
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
@@ -0,0 +1,128 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component'
7import { User, UserService } from '@app/shared/users'
8import { AuthService, ConfirmService } from '@app/core'
9import { UserRight } from '../../../../../shared/models/users'
10
11@Component({
12 selector: 'my-user-moderation-dropdown',
13 templateUrl: './user-moderation-dropdown.component.html',
14 styleUrls: [ './user-moderation-dropdown.component.scss' ]
15})
16export class UserModerationDropdownComponent implements OnInit {
17 @ViewChild('userBanModal') userBanModal: UserBanModalComponent
18
19 @Input() user: User
20 @Output() userChanged = new EventEmitter()
21
22 userActions: DropdownAction<User>[] = []
23
24 private openedModal: NgbModalRef
25
26 constructor (
27 private authService: AuthService,
28 private notificationsService: NotificationsService,
29 private confirmService: ConfirmService,
30 private userService: UserService,
31 private i18n: I18n
32 ) { }
33
34 ngOnInit () {
35 this.userActions = []
36
37 if (this.authService.isLoggedIn()) {
38 const authUser = this.authService.getUser()
39
40 if (authUser.hasRight(UserRight.MANAGE_USERS)) {
41 this.userActions = this.userActions.concat([
42 {
43 label: this.i18n('Edit'),
44 linkBuilder: this.getRouterUserEditLink
45 },
46 {
47 label: this.i18n('Delete'),
48 handler: user => this.removeUser(user)
49 },
50 {
51 label: this.i18n('Ban'),
52 handler: user => this.openBanUserModal(user),
53 isDisplayed: user => !user.blocked
54 },
55 {
56 label: this.i18n('Unban'),
57 handler: user => this.unbanUser(user),
58 isDisplayed: user => user.blocked
59 }
60 ])
61 }
62 }
63 }
64
65 hideBanUserModal () {
66 this.openedModal.close()
67 }
68
69 openBanUserModal (user: User) {
70 if (user.username === 'root') {
71 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
72 return
73 }
74
75 this.userBanModal.openModal(user)
76 }
77
78 onUserBanned () {
79 this.userChanged.emit()
80 }
81
82 async unbanUser (user: User) {
83 const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username })
84 const res = await this.confirmService.confirm(message, this.i18n('Unban'))
85 if (res === false) return
86
87 this.userService.unbanUser(user)
88 .subscribe(
89 () => {
90 this.notificationsService.success(
91 this.i18n('Success'),
92 this.i18n('User {{username}} unbanned.', { username: user.username })
93 )
94
95 this.userChanged.emit()
96 },
97
98 err => this.notificationsService.error(this.i18n('Error'), err.message)
99 )
100 }
101
102 async removeUser (user: User) {
103 if (user.username === 'root') {
104 this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
105 return
106 }
107
108 const message = this.i18n('If you remove this user, you will not be able to create another with the same username!')
109 const res = await this.confirmService.confirm(message, this.i18n('Delete'))
110 if (res === false) return
111
112 this.userService.removeUser(user).subscribe(
113 () => {
114 this.notificationsService.success(
115 this.i18n('Success'),
116 this.i18n('User {{username}} deleted.', { username: user.username })
117 )
118 this.userChanged.emit()
119 },
120
121 err => this.notificationsService.error(this.i18n('Error'), err.message)
122 )
123 }
124
125 getRouterUserEditLink (user: User) {
126 return [ '/admin', 'users', 'update', user.id ]
127 }
128}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 076f1d275..9647a7966 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -56,6 +56,8 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
56import { SubscribeButtonComponent, RemoteSubscribeComponent, UserSubscriptionService } from '@app/shared/user-subscription' 56import { SubscribeButtonComponent, RemoteSubscribeComponent, UserSubscriptionService } from '@app/shared/user-subscription'
57import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' 57import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
58import { OverviewService } from '@app/shared/overview' 58import { OverviewService } from '@app/shared/overview'
59import { UserBanModalComponent } from '@app/shared/moderation'
60import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
59 61
60@NgModule({ 62@NgModule({
61 imports: [ 63 imports: [
@@ -94,7 +96,9 @@ import { OverviewService } from '@app/shared/overview'
94 PeertubeCheckboxComponent, 96 PeertubeCheckboxComponent,
95 SubscribeButtonComponent, 97 SubscribeButtonComponent,
96 RemoteSubscribeComponent, 98 RemoteSubscribeComponent,
97 InstanceFeaturesTableComponent 99 InstanceFeaturesTableComponent,
100 UserBanModalComponent,
101 UserModerationDropdownComponent
98 ], 102 ],
99 103
100 exports: [ 104 exports: [
@@ -130,6 +134,8 @@ import { OverviewService } from '@app/shared/overview'
130 SubscribeButtonComponent, 134 SubscribeButtonComponent,
131 RemoteSubscribeComponent, 135 RemoteSubscribeComponent,
132 InstanceFeaturesTableComponent, 136 InstanceFeaturesTableComponent,
137 UserBanModalComponent,
138 UserModerationDropdownComponent,
133 139
134 NumberFormatterPipe, 140 NumberFormatterPipe,
135 ObjectLengthPipe, 141 ObjectLengthPipe,
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index bd5cd45d4..5ab290a59 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -2,20 +2,26 @@ import { Observable } from 'rxjs'
2import { catchError, map } from 'rxjs/operators' 2import { catchError, map } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { UserCreate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' 5import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared'
6import { environment } from '../../../environments/environment' 6import { environment } from '../../../environments/environment'
7import { RestExtractor } from '../rest' 7import { RestExtractor, RestPagination, RestService } from '../rest'
8import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 8import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
9import { SortMeta } from 'primeng/api'
10import { BytesPipe } from 'ngx-pipes'
11import { I18n } from '@ngx-translate/i18n-polyfill'
9 12
10@Injectable() 13@Injectable()
11export class UserService { 14export class UserService {
12 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/' 15 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
13 16
17 private bytesPipe = new BytesPipe()
18
14 constructor ( 19 constructor (
15 private authHttp: HttpClient, 20 private authHttp: HttpClient,
16 private restExtractor: RestExtractor 21 private restExtractor: RestExtractor,
17 ) { 22 private restService: RestService,
18 } 23 private i18n: I18n
24 ) { }
19 25
20 changePassword (currentPassword: string, newPassword: string) { 26 changePassword (currentPassword: string, newPassword: string) {
21 const url = UserService.BASE_USERS_URL + 'me' 27 const url = UserService.BASE_USERS_URL + 'me'
@@ -128,4 +134,79 @@ export class UserService {
128 .get<string[]>(url, { params }) 134 .get<string[]>(url, { params })
129 .pipe(catchError(res => this.restExtractor.handleError(res))) 135 .pipe(catchError(res => this.restExtractor.handleError(res)))
130 } 136 }
137
138 /* ###### Admin methods ###### */
139
140 addUser (userCreate: UserCreate) {
141 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
142 .pipe(
143 map(this.restExtractor.extractDataBool),
144 catchError(err => this.restExtractor.handleError(err))
145 )
146 }
147
148 updateUser (userId: number, userUpdate: UserUpdate) {
149 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
150 .pipe(
151 map(this.restExtractor.extractDataBool),
152 catchError(err => this.restExtractor.handleError(err))
153 )
154 }
155
156 getUser (userId: number) {
157 return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId)
158 .pipe(catchError(err => this.restExtractor.handleError(err)))
159 }
160
161 getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> {
162 let params = new HttpParams()
163 params = this.restService.addRestGetParams(params, pagination, sort)
164
165 return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
166 .pipe(
167 map(res => this.restExtractor.convertResultListDateToHuman(res)),
168 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
169 catchError(err => this.restExtractor.handleError(err))
170 )
171 }
172
173 removeUser (user: User) {
174 return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
175 .pipe(catchError(err => this.restExtractor.handleError(err)))
176 }
177
178 banUser (user: User, reason?: string) {
179 const body = reason ? { reason } : {}
180
181 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body)
182 .pipe(catchError(err => this.restExtractor.handleError(err)))
183 }
184
185 unbanUser (user: User) {
186 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {})
187 .pipe(catchError(err => this.restExtractor.handleError(err)))
188 }
189
190 private formatUser (user: User) {
191 let videoQuota
192 if (user.videoQuota === -1) {
193 videoQuota = this.i18n('Unlimited')
194 } else {
195 videoQuota = this.bytesPipe.transform(user.videoQuota, 0)
196 }
197
198 const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0)
199
200 const roleLabels: { [ id in UserRole ]: string } = {
201 [UserRole.USER]: this.i18n('User'),
202 [UserRole.ADMINISTRATOR]: this.i18n('Administrator'),
203 [UserRole.MODERATOR]: this.i18n('Moderator')
204 }
205
206 return Object.assign(user, {
207 roleLabel: roleLabels[user.role],
208 videoQuota,
209 videoQuotaUsed
210 })
211 }
131} 212}