From e724fa93c71d76d709e819a05e5e2904a3c4205b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Oct 2018 15:24:29 +0200 Subject: Move user moderation tool in a separate component --- client/src/app/+admin/admin.module.ts | 5 +- client/src/app/+admin/users/index.ts | 1 - client/src/app/+admin/users/shared/index.ts | 1 - client/src/app/+admin/users/shared/user.service.ts | 96 ---------------- .../users/user-edit/user-create.component.ts | 2 +- .../users/user-edit/user-update.component.ts | 2 +- .../users/user-list/user-ban-modal.component.html | 32 ------ .../users/user-list/user-ban-modal.component.scss | 6 - .../users/user-list/user-ban-modal.component.ts | 69 ----------- .../users/user-list/user-list.component.html | 3 +- .../+admin/users/user-list/user-list.component.ts | 95 +-------------- client/src/app/shared/moderation/index.ts | 2 + .../moderation/user-ban-modal.component.html | 32 ++++++ .../moderation/user-ban-modal.component.scss | 6 + .../shared/moderation/user-ban-modal.component.ts | 68 +++++++++++ .../user-moderation-dropdown.component.html | 3 + .../user-moderation-dropdown.component.scss | 0 .../user-moderation-dropdown.component.ts | 128 +++++++++++++++++++++ client/src/app/shared/shared.module.ts | 8 +- client/src/app/shared/users/user.service.ts | 91 ++++++++++++++- 20 files changed, 339 insertions(+), 311 deletions(-) delete mode 100644 client/src/app/+admin/users/shared/index.ts delete mode 100644 client/src/app/+admin/users/shared/user.service.ts delete mode 100644 client/src/app/+admin/users/user-list/user-ban-modal.component.html delete mode 100644 client/src/app/+admin/users/user-list/user-ban-modal.component.scss delete mode 100644 client/src/app/+admin/users/user-list/user-ban-modal.component.ts create mode 100644 client/src/app/shared/moderation/index.ts create mode 100644 client/src/app/shared/moderation/user-ban-modal.component.html create mode 100644 client/src/app/shared/moderation/user-ban-modal.component.scss create mode 100644 client/src/app/shared/moderation/user-ban-modal.component.ts create mode 100644 client/src/app/shared/moderation/user-moderation-dropdown.component.html create mode 100644 client/src/app/shared/moderation/user-moderation-dropdown.component.scss create mode 100644 client/src/app/shared/moderation/user-moderation-dropdown.component.ts (limited to 'client/src') 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. import { JobsComponent } from './jobs/job.component' import { JobsListComponent } from './jobs/jobs-list/jobs-list.component' import { JobService } from './jobs/shared/job.service' -import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users' +import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent } from './users' import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation' -import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component' import { ModerationComponent } from '@app/+admin/moderation/moderation.component' import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' @@ -37,7 +36,6 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service UserCreateComponent, UserUpdateComponent, UserListComponent, - UserBanModalComponent, ModerationComponent, VideoBlacklistListComponent, @@ -58,7 +56,6 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service providers: [ FollowService, RedundancyService, - UserService, JobService, ConfigService ] 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 @@ -export * from './shared' export * from './user-edit' export * from './user-list' export * 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 @@ -export * 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 @@ -import { catchError, map } from 'rxjs/operators' -import { HttpClient, HttpParams } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { BytesPipe } from 'ngx-pipes' -import { SortMeta } from 'primeng/components/common/sortmeta' -import { Observable } from 'rxjs' -import { ResultList, UserCreate, UserUpdate, User, UserRole } from '../../../../../../shared' -import { environment } from '../../../../environments/environment' -import { RestExtractor, RestPagination, RestService } from '../../../shared' -import { I18n } from '@ngx-translate/i18n-polyfill' - -@Injectable() -export class UserService { - private static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/' - private bytesPipe = new BytesPipe() - - constructor ( - private authHttp: HttpClient, - private restService: RestService, - private restExtractor: RestExtractor, - private i18n: I18n - ) { } - - addUser (userCreate: UserCreate) { - return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateUser (userId: number, userUpdate: UserUpdate) { - return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getUser (userId: number) { - return this.authHttp.get(UserService.BASE_USERS_URL + userId) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - getUsers (pagination: RestPagination, sort: SortMeta): Observable> { - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination, sort) - - return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) - .pipe( - map(res => this.restExtractor.convertResultListDateToHuman(res)), - map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - removeUser (user: User) { - return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - banUser (user: User, reason?: string) { - const body = reason ? { reason } : {} - - return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - unbanUser (user: User) { - return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {}) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - private formatUser (user: User) { - let videoQuota - if (user.videoQuota === -1) { - videoQuota = this.i18n('Unlimited') - } else { - videoQuota = this.bytesPipe.transform(user.videoQuota, 0) - } - - const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0) - - const roleLabels: { [ id in UserRole ]: string } = { - [UserRole.USER]: this.i18n('User'), - [UserRole.ADMINISTRATOR]: this.i18n('Administrator'), - [UserRole.MODERATOR]: this.i18n('Moderator') - } - - return Object.assign(user, { - roleLabel: roleLabels[user.role], - videoQuota, - videoQuotaUsed - }) - } -} 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 @@ import { Component, OnInit } from '@angular/core' import { Router } from '@angular/router' import { NotificationsService } from 'angular2-notifications' -import { UserService } from '../shared' import { ServerService } from '../../../core' import { UserCreate, UserRole } from '../../../../../../shared' import { UserEdit } from './user-edit' @@ -9,6 +8,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' import { ConfigService } from '@app/+admin/config/shared/config.service' +import { UserService } from '@app/shared' @Component({ 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' import { ActivatedRoute, Router } from '@angular/router' import { Subscription } from 'rxjs' import { NotificationsService } from 'angular2-notifications' -import { UserService } from '../shared' import { ServerService } from '../../../core' import { UserEdit } from './user-edit' import { User, UserUpdate } from '../../../../../../shared' @@ -10,6 +9,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' import { ConfigService } from '@app/+admin/config/shared/config.service' +import { UserService } from '@app/shared' @Component({ selector: 'my-user-update', diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.html b/client/src/app/+admin/users/user-list/user-ban-modal.component.html deleted file mode 100644 index b2958caa4..000000000 --- a/client/src/app/+admin/users/user-list/user-ban-modal.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.scss b/client/src/app/+admin/users/user-list/user-ban-modal.component.scss deleted file mode 100644 index 84562f15c..000000000 --- a/client/src/app/+admin/users/user-list/user-ban-modal.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import 'variables'; -@import 'mixins'; - -textarea { - @include peertube-textarea(100%, 60px); -} diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.ts b/client/src/app/+admin/users/user-list/user-ban-modal.component.ts deleted file mode 100644 index 4fd4d561c..000000000 --- a/client/src/app/+admin/users/user-list/user-ban-modal.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' -import { NotificationsService } from 'angular2-notifications' -import { FormReactive, UserValidatorsService } from '../../../shared' -import { UserService } from '../shared' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' -import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' -import { User } from '../../../../../../shared' - -@Component({ - selector: 'my-user-ban-modal', - templateUrl: './user-ban-modal.component.html', - styleUrls: [ './user-ban-modal.component.scss' ] -}) -export class UserBanModalComponent extends FormReactive implements OnInit { - @ViewChild('modal') modal: NgbModal - @Output() userBanned = new EventEmitter() - - private userToBan: User - private openedModal: NgbModalRef - - constructor ( - protected formValidatorService: FormValidatorService, - private modalService: NgbModal, - private notificationsService: NotificationsService, - private userService: UserService, - private userValidatorsService: UserValidatorsService, - private i18n: I18n - ) { - super() - } - - ngOnInit () { - this.buildForm({ - reason: this.userValidatorsService.USER_BAN_REASON - }) - } - - openModal (user: User) { - this.userToBan = user - this.openedModal = this.modalService.open(this.modal) - } - - hideBanUserModal () { - this.userToBan = undefined - this.openedModal.close() - } - - async banUser () { - const reason = this.form.value['reason'] || undefined - - this.userService.banUser(this.userToBan, reason) - .subscribe( - () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('User {{username}} banned.', { username: this.userToBan.username }) - ) - - this.userBanned.emit(this.userToBan) - this.hideBanUserModal() - }, - - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) - } - -} 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 @@ {{ user.roleLabel }} {{ user.createdAt }} - + @@ -55,4 +55,3 @@ - \ 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 @@ -import { Component, OnInit, ViewChild } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { NotificationsService } from 'angular2-notifications' import { SortMeta } from 'primeng/components/common/sortmeta' import { ConfirmService } from '../../../core' -import { RestPagination, RestTable } from '../../../shared' -import { UserService } from '../shared' +import { RestPagination, RestTable, UserService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' -import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' -import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' -import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component' import { User } from '../../../../../../shared' @Component({ @@ -16,16 +12,11 @@ import { User } from '../../../../../../shared' styleUrls: [ './user-list.component.scss' ] }) export class UserListComponent extends RestTable implements OnInit { - @ViewChild('userBanModal') userBanModal: UserBanModalComponent - users: User[] = [] totalRecords = 0 rowsPerPage = 10 sort: SortMeta = { field: 'createdAt', order: 1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } - userActions: DropdownAction[] = [] - - private openedModal: NgbModalRef constructor ( private notificationsService: NotificationsService, @@ -34,96 +25,16 @@ export class UserListComponent extends RestTable implements OnInit { private i18n: I18n ) { super() - - this.userActions = [ - { - label: this.i18n('Edit'), - linkBuilder: this.getRouterUserEditLink - }, - { - label: this.i18n('Delete'), - handler: user => this.removeUser(user) - }, - { - label: this.i18n('Ban'), - handler: user => this.openBanUserModal(user), - isDisplayed: user => !user.blocked - }, - { - label: this.i18n('Unban'), - handler: user => this.unbanUser(user), - isDisplayed: user => user.blocked - } - ] } ngOnInit () { this.loadSort() } - hideBanUserModal () { - this.openedModal.close() - } - - openBanUserModal (user: User) { - if (user.username === 'root') { - this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.')) - return - } - - this.userBanModal.openModal(user) - } - - onUserBanned () { + onUserChanged () { this.loadData() } - async unbanUser (user: User) { - const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username }) - const res = await this.confirmService.confirm(message, this.i18n('Unban')) - if (res === false) return - - this.userService.unbanUser(user) - .subscribe( - () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('User {{username}} unbanned.', { username: user.username }) - ) - this.loadData() - }, - - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) - } - - async removeUser (user: User) { - if (user.username === 'root') { - this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.')) - return - } - - const message = this.i18n('If you remove this user, you will not be able to create another with the same username!') - const res = await this.confirmService.confirm(message, this.i18n('Delete')) - if (res === false) return - - this.userService.removeUser(user).subscribe( - () => { - this.notificationsService.success( - this.i18n('Success'), - this.i18n('User {{username}} deleted.', { username: user.username }) - ) - this.loadData() - }, - - err => this.notificationsService.error(this.i18n('Error'), err.message) - ) - } - - getRouterUserEditLink (user: User) { - return [ '/admin', 'users', 'update', user.id ] - } - protected loadData () { this.userService.getUsers(this.pagination, this.sort) .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 @@ +export * from './user-ban-modal.component' +export * 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 @@ + + + + + + \ 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 @@ +@import 'variables'; +@import 'mixins'; + +textarea { + @include peertube-textarea(100%, 60px); +} 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 @@ +import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { FormReactive, UserValidatorsService } from '@app/shared/forms' +import { User, UserService } from '@app/shared/users' + +@Component({ + selector: 'my-user-ban-modal', + templateUrl: './user-ban-modal.component.html', + styleUrls: [ './user-ban-modal.component.scss' ] +}) +export class UserBanModalComponent extends FormReactive implements OnInit { + @ViewChild('modal') modal: NgbModal + @Output() userBanned = new EventEmitter() + + private userToBan: User + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private notificationsService: NotificationsService, + private userService: UserService, + private userValidatorsService: UserValidatorsService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + this.buildForm({ + reason: this.userValidatorsService.USER_BAN_REASON + }) + } + + openModal (user: User) { + this.userToBan = user + this.openedModal = this.modalService.open(this.modal) + } + + hideBanUserModal () { + this.userToBan = undefined + this.openedModal.close() + } + + async banUser () { + const reason = this.form.value['reason'] || undefined + + this.userService.banUser(this.userToBan, reason) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('User {{username}} banned.', { username: this.userToBan.username }) + ) + + this.userBanned.emit(this.userToBan) + this.hideBanUserModal() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + +} 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 @@ + + + \ 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 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 @@ +import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' +import { NotificationsService } from 'angular2-notifications' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' +import { User, UserService } from '@app/shared/users' +import { AuthService, ConfirmService } from '@app/core' +import { UserRight } from '../../../../../shared/models/users' + +@Component({ + selector: 'my-user-moderation-dropdown', + templateUrl: './user-moderation-dropdown.component.html', + styleUrls: [ './user-moderation-dropdown.component.scss' ] +}) +export class UserModerationDropdownComponent implements OnInit { + @ViewChild('userBanModal') userBanModal: UserBanModalComponent + + @Input() user: User + @Output() userChanged = new EventEmitter() + + userActions: DropdownAction[] = [] + + private openedModal: NgbModalRef + + constructor ( + private authService: AuthService, + private notificationsService: NotificationsService, + private confirmService: ConfirmService, + private userService: UserService, + private i18n: I18n + ) { } + + ngOnInit () { + this.userActions = [] + + if (this.authService.isLoggedIn()) { + const authUser = this.authService.getUser() + + if (authUser.hasRight(UserRight.MANAGE_USERS)) { + this.userActions = this.userActions.concat([ + { + label: this.i18n('Edit'), + linkBuilder: this.getRouterUserEditLink + }, + { + label: this.i18n('Delete'), + handler: user => this.removeUser(user) + }, + { + label: this.i18n('Ban'), + handler: user => this.openBanUserModal(user), + isDisplayed: user => !user.blocked + }, + { + label: this.i18n('Unban'), + handler: user => this.unbanUser(user), + isDisplayed: user => user.blocked + } + ]) + } + } + } + + hideBanUserModal () { + this.openedModal.close() + } + + openBanUserModal (user: User) { + if (user.username === 'root') { + this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.')) + return + } + + this.userBanModal.openModal(user) + } + + onUserBanned () { + this.userChanged.emit() + } + + async unbanUser (user: User) { + const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username }) + const res = await this.confirmService.confirm(message, this.i18n('Unban')) + if (res === false) return + + this.userService.unbanUser(user) + .subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('User {{username}} unbanned.', { username: user.username }) + ) + + this.userChanged.emit() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + async removeUser (user: User) { + if (user.username === 'root') { + this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.')) + return + } + + const message = this.i18n('If you remove this user, you will not be able to create another with the same username!') + const res = await this.confirmService.confirm(message, this.i18n('Delete')) + if (res === false) return + + this.userService.removeUser(user).subscribe( + () => { + this.notificationsService.success( + this.i18n('Success'), + this.i18n('User {{username}} deleted.', { username: user.username }) + ) + this.userChanged.emit() + }, + + err => this.notificationsService.error(this.i18n('Error'), err.message) + ) + } + + getRouterUserEditLink (user: User) { + return [ '/admin', 'users', 'update', user.id ] + } +} 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 import { SubscribeButtonComponent, RemoteSubscribeComponent, UserSubscriptionService } from '@app/shared/user-subscription' import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' import { OverviewService } from '@app/shared/overview' +import { UserBanModalComponent } from '@app/shared/moderation' +import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' @NgModule({ imports: [ @@ -94,7 +96,9 @@ import { OverviewService } from '@app/shared/overview' PeertubeCheckboxComponent, SubscribeButtonComponent, RemoteSubscribeComponent, - InstanceFeaturesTableComponent + InstanceFeaturesTableComponent, + UserBanModalComponent, + UserModerationDropdownComponent ], exports: [ @@ -130,6 +134,8 @@ import { OverviewService } from '@app/shared/overview' SubscribeButtonComponent, RemoteSubscribeComponent, InstanceFeaturesTableComponent, + UserBanModalComponent, + UserModerationDropdownComponent, NumberFormatterPipe, 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' import { catchError, map } from 'rxjs/operators' import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' -import { UserCreate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' +import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' import { environment } from '../../../environments/environment' -import { RestExtractor } from '../rest' +import { RestExtractor, RestPagination, RestService } from '../rest' import { Avatar } from '../../../../../shared/models/avatars/avatar.model' +import { SortMeta } from 'primeng/api' +import { BytesPipe } from 'ngx-pipes' +import { I18n } from '@ngx-translate/i18n-polyfill' @Injectable() export class UserService { static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/' + private bytesPipe = new BytesPipe() + constructor ( private authHttp: HttpClient, - private restExtractor: RestExtractor - ) { - } + private restExtractor: RestExtractor, + private restService: RestService, + private i18n: I18n + ) { } changePassword (currentPassword: string, newPassword: string) { const url = UserService.BASE_USERS_URL + 'me' @@ -128,4 +134,79 @@ export class UserService { .get(url, { params }) .pipe(catchError(res => this.restExtractor.handleError(res))) } + + /* ###### Admin methods ###### */ + + addUser (userCreate: UserCreate) { + return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } + + updateUser (userId: number, userUpdate: UserUpdate) { + return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } + + getUser (userId: number) { + return this.authHttp.get(UserService.BASE_USERS_URL + userId) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + getUsers (pagination: RestPagination, sort: SortMeta): Observable> { + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) + .pipe( + map(res => this.restExtractor.convertResultListDateToHuman(res)), + map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))), + catchError(err => this.restExtractor.handleError(err)) + ) + } + + removeUser (user: User) { + return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + banUser (user: User, reason?: string) { + const body = reason ? { reason } : {} + + return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + unbanUser (user: User) { + return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {}) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } + + private formatUser (user: User) { + let videoQuota + if (user.videoQuota === -1) { + videoQuota = this.i18n('Unlimited') + } else { + videoQuota = this.bytesPipe.transform(user.videoQuota, 0) + } + + const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0) + + const roleLabels: { [ id in UserRole ]: string } = { + [UserRole.USER]: this.i18n('User'), + [UserRole.ADMINISTRATOR]: this.i18n('Administrator'), + [UserRole.MODERATOR]: this.i18n('Moderator') + } + + return Object.assign(user, { + roleLabel: roleLabels[user.role], + videoQuota, + videoQuotaUsed + }) + } } -- cgit v1.2.3