aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/moderation/index.ts2
-rw-r--r--client/src/app/shared/moderation/user-ban-modal.component.html32
-rw-r--r--client/src/app/shared/moderation/user-ban-modal.component.scss6
-rw-r--r--client/src/app/shared/moderation/user-ban-modal.component.ts68
-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
9 files changed, 332 insertions, 6 deletions
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/shared/moderation/user-ban-modal.component.html b/client/src/app/shared/moderation/user-ban-modal.component.html
new file mode 100644
index 000000000..b2958caa4
--- /dev/null
+++ b/client/src/app/shared/moderation/user-ban-modal.component.html
@@ -0,0 +1,32 @@
1<ng-template #modal>
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Ban {{ userToBan.username }}</h4>
4 <span class="close" aria-hidden="true" (click)="hideBanUserModal()"></span>
5 </div>
6
7 <div class="modal-body">
8 <form novalidate [formGroup]="form" (ngSubmit)="banUser()">
9 <div class="form-group">
10 <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
11 </textarea>
12 <div *ngIf="formErrors.reason" class="form-error">
13 {{ formErrors.reason }}
14 </div>
15 </div>
16
17 <div i18n>
18 A banned user will no longer be able to login.
19 </div>
20
21 <div class="form-group inputs">
22 <span i18n class="action-button action-button-cancel" (click)="hideBanUserModal()">Cancel</span>
23
24 <input
25 type="submit" i18n-value value="Ban this user" class="action-button-submit"
26 [disabled]="!form.valid"
27 >
28 </div>
29 </form>
30 </div>
31
32</ng-template> \ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.scss b/client/src/app/shared/moderation/user-ban-modal.component.scss
new file mode 100644
index 000000000..84562f15c
--- /dev/null
+++ b/client/src/app/shared/moderation/user-ban-modal.component.scss
@@ -0,0 +1,6 @@
1@import 'variables';
2@import 'mixins';
3
4textarea {
5 @include peertube-textarea(100%, 60px);
6}
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/moderation/user-ban-modal.component.ts
new file mode 100644
index 000000000..d49783cd2
--- /dev/null
+++ b/client/src/app/shared/moderation/user-ban-modal.component.ts
@@ -0,0 +1,68 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
7import { FormReactive, UserValidatorsService } from '@app/shared/forms'
8import { User, UserService } from '@app/shared/users'
9
10@Component({
11 selector: 'my-user-ban-modal',
12 templateUrl: './user-ban-modal.component.html',
13 styleUrls: [ './user-ban-modal.component.scss' ]
14})
15export class UserBanModalComponent extends FormReactive implements OnInit {
16 @ViewChild('modal') modal: NgbModal
17 @Output() userBanned = new EventEmitter<User>()
18
19 private userToBan: User
20 private openedModal: NgbModalRef
21
22 constructor (
23 protected formValidatorService: FormValidatorService,
24 private modalService: NgbModal,
25 private notificationsService: NotificationsService,
26 private userService: UserService,
27 private userValidatorsService: UserValidatorsService,
28 private i18n: I18n
29 ) {
30 super()
31 }
32
33 ngOnInit () {
34 this.buildForm({
35 reason: this.userValidatorsService.USER_BAN_REASON
36 })
37 }
38
39 openModal (user: User) {
40 this.userToBan = user
41 this.openedModal = this.modalService.open(this.modal)
42 }
43
44 hideBanUserModal () {
45 this.userToBan = undefined
46 this.openedModal.close()
47 }
48
49 async banUser () {
50 const reason = this.form.value['reason'] || undefined
51
52 this.userService.banUser(this.userToBan, reason)
53 .subscribe(
54 () => {
55 this.notificationsService.success(
56 this.i18n('Success'),
57 this.i18n('User {{username}} banned.', { username: this.userToBan.username })
58 )
59
60 this.userBanned.emit(this.userToBan)
61 this.hideBanUserModal()
62 },
63
64 err => this.notificationsService.error(this.i18n('Error'), err.message)
65 )
66 }
67
68}
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}