diff options
author | Chocobozzz <me@florianbigard.com> | 2018-10-05 16:56:14 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-10-05 17:02:10 +0200 |
commit | 79bd2632d62f2f600d663815fcc00a01ca981aa1 (patch) | |
tree | 2a6b5df65fdd6025dda16cf7cd743c4530c63bf6 /client | |
parent | e724fa93c71d76d709e819a05e5e2904a3c4205b (diff) | |
download | PeerTube-79bd2632d62f2f600d663815fcc00a01ca981aa1.tar.gz PeerTube-79bd2632d62f2f600d663815fcc00a01ca981aa1.tar.zst PeerTube-79bd2632d62f2f600d663815fcc00a01ca981aa1.zip |
Add user moderation in the account page
Diffstat (limited to 'client')
13 files changed, 121 insertions, 45 deletions
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html index 69f648269..036e794d2 100644 --- a/client/src/app/+accounts/accounts.component.html +++ b/client/src/app/+accounts/accounts.component.html | |||
@@ -8,6 +8,11 @@ | |||
8 | <div class="actor-names"> | 8 | <div class="actor-names"> |
9 | <div class="actor-display-name">{{ account.displayName }}</div> | 9 | <div class="actor-display-name">{{ account.displayName }}</div> |
10 | <div class="actor-name">{{ account.nameWithHost }}</div> | 10 | <div class="actor-name">{{ account.nameWithHost }}</div> |
11 | |||
12 | <span *ngIf="user?.blocked" [ngbTooltip]="user.blockedReason" class="badge badge-danger" i18n>Banned</span> | ||
13 | |||
14 | <my-user-moderation-dropdown buttonSize="small" [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserDeleted()"> | ||
15 | </my-user-moderation-dropdown> | ||
11 | </div> | 16 | </div> |
12 | <div i18n class="actor-followers">{{ account.followersCount }} subscribers</div> | 17 | <div i18n class="actor-followers">{{ account.followersCount }} subscribers</div> |
13 | </div> | 18 | </div> |
diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss index 909b65bc7..3cedda889 100644 --- a/client/src/app/+accounts/accounts.component.scss +++ b/client/src/app/+accounts/accounts.component.scss | |||
@@ -3,4 +3,16 @@ | |||
3 | 3 | ||
4 | .sub-menu { | 4 | .sub-menu { |
5 | @include sub-menu-with-actor; | 5 | @include sub-menu-with-actor; |
6 | } | ||
7 | |||
8 | my-user-moderation-dropdown, | ||
9 | .badge { | ||
10 | margin-left: 10px; | ||
11 | |||
12 | position: relative; | ||
13 | top: 3px; | ||
14 | } | ||
15 | |||
16 | .badge { | ||
17 | font-size: 13px; | ||
6 | } \ No newline at end of file | 18 | } \ No newline at end of file |
diff --git a/client/src/app/+accounts/accounts.component.ts b/client/src/app/+accounts/accounts.component.ts index af0451e91..e19927d6b 100644 --- a/client/src/app/+accounts/accounts.component.ts +++ b/client/src/app/+accounts/accounts.component.ts | |||
@@ -1,10 +1,14 @@ | |||
1 | import { Component, OnInit, OnDestroy } from '@angular/core' | 1 | import { Component, OnDestroy, OnInit } from '@angular/core' |
2 | import { ActivatedRoute } from '@angular/router' | 2 | import { ActivatedRoute } from '@angular/router' |
3 | import { AccountService } from '@app/shared/account/account.service' | 3 | import { AccountService } from '@app/shared/account/account.service' |
4 | import { Account } from '@app/shared/account/account.model' | 4 | import { Account } from '@app/shared/account/account.model' |
5 | import { RestExtractor } from '@app/shared' | 5 | import { RestExtractor, UserService } from '@app/shared' |
6 | import { catchError, switchMap, distinctUntilChanged, map } from 'rxjs/operators' | 6 | import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators' |
7 | import { Subscription } from 'rxjs' | 7 | import { Subscription } from 'rxjs' |
8 | import { NotificationsService } from 'angular2-notifications' | ||
9 | import { User, UserRight } from '../../../../shared' | ||
10 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
11 | import { AuthService, RedirectService } from '@app/core' | ||
8 | 12 | ||
9 | @Component({ | 13 | @Component({ |
10 | templateUrl: './accounts.component.html', | 14 | templateUrl: './accounts.component.html', |
@@ -12,13 +16,19 @@ import { Subscription } from 'rxjs' | |||
12 | }) | 16 | }) |
13 | export class AccountsComponent implements OnInit, OnDestroy { | 17 | export class AccountsComponent implements OnInit, OnDestroy { |
14 | account: Account | 18 | account: Account |
19 | user: User | ||
15 | 20 | ||
16 | private routeSub: Subscription | 21 | private routeSub: Subscription |
17 | 22 | ||
18 | constructor ( | 23 | constructor ( |
19 | private route: ActivatedRoute, | 24 | private route: ActivatedRoute, |
25 | private userService: UserService, | ||
20 | private accountService: AccountService, | 26 | private accountService: AccountService, |
21 | private restExtractor: RestExtractor | 27 | private notificationsService: NotificationsService, |
28 | private restExtractor: RestExtractor, | ||
29 | private redirectService: RedirectService, | ||
30 | private authService: AuthService, | ||
31 | private i18n: I18n | ||
22 | ) {} | 32 | ) {} |
23 | 33 | ||
24 | ngOnInit () { | 34 | ngOnInit () { |
@@ -27,12 +37,40 @@ export class AccountsComponent implements OnInit, OnDestroy { | |||
27 | map(params => params[ 'accountId' ]), | 37 | map(params => params[ 'accountId' ]), |
28 | distinctUntilChanged(), | 38 | distinctUntilChanged(), |
29 | switchMap(accountId => this.accountService.getAccount(accountId)), | 39 | switchMap(accountId => this.accountService.getAccount(accountId)), |
40 | tap(account => this.getUserIfNeeded(account)), | ||
30 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) | 41 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ])) |
31 | ) | 42 | ) |
32 | .subscribe(account => this.account = account) | 43 | .subscribe( |
44 | account => this.account = account, | ||
45 | |||
46 | err => this.notificationsService.error(this.i18n('Error'), err.message) | ||
47 | ) | ||
33 | } | 48 | } |
34 | 49 | ||
35 | ngOnDestroy () { | 50 | ngOnDestroy () { |
36 | if (this.routeSub) this.routeSub.unsubscribe() | 51 | if (this.routeSub) this.routeSub.unsubscribe() |
37 | } | 52 | } |
53 | |||
54 | onUserChanged () { | ||
55 | this.getUserIfNeeded(this.account) | ||
56 | } | ||
57 | |||
58 | onUserDeleted () { | ||
59 | this.redirectService.redirectToHomepage() | ||
60 | } | ||
61 | |||
62 | private getUserIfNeeded (account: Account) { | ||
63 | if (!account.userId) return | ||
64 | if (!this.authService.isLoggedIn()) return | ||
65 | |||
66 | const user = this.authService.getUser() | ||
67 | if (user.hasRight(UserRight.MANAGE_USERS)) { | ||
68 | this.userService.getUser(account.userId) | ||
69 | .subscribe( | ||
70 | user => this.user = user, | ||
71 | |||
72 | err => this.notificationsService.error(this.i18n('Error'), err.message) | ||
73 | ) | ||
74 | } | ||
75 | } | ||
38 | } | 76 | } |
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 2479ce9e4..cca057ba1 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,8 @@ | |||
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-user-moderation-dropdown [user]="user" (userChanged)="onUserChanged()"></my-user-moderation-dropdown> | 43 | <my-user-moderation-dropdown [user]="user" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()"> |
44 | </my-user-moderation-dropdown> | ||
44 | </td> | 45 | </td> |
45 | </tr> | 46 | </tr> |
46 | </ng-template> | 47 | </ng-template> |
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts index 5058e372f..42f2cfeaf 100644 --- a/client/src/app/shared/account/account.model.ts +++ b/client/src/app/shared/account/account.model.ts | |||
@@ -6,11 +6,14 @@ export class Account extends Actor implements ServerAccount { | |||
6 | description: string | 6 | description: string |
7 | nameWithHost: string | 7 | nameWithHost: string |
8 | 8 | ||
9 | userId?: number | ||
10 | |||
9 | constructor (hash: ServerAccount) { | 11 | constructor (hash: ServerAccount) { |
10 | super(hash) | 12 | super(hash) |
11 | 13 | ||
12 | this.displayName = hash.displayName | 14 | this.displayName = hash.displayName |
13 | this.description = hash.description | 15 | this.description = hash.description |
16 | this.userId = hash.userId | ||
14 | this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) | 17 | this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) |
15 | } | 18 | } |
16 | } | 19 | } |
diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html index 8b7241379..8110e2515 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.html +++ b/client/src/app/shared/buttons/action-dropdown.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <div class="dropdown-root" ngbDropdown [placement]="placement"> | 1 | <div class="dropdown-root" ngbDropdown [placement]="placement"> |
2 | <div class="action-button" ngbDropdownToggle role="button"> | 2 | <div class="action-button" [ngClass]="{ small: buttonSize === 'small' }" ngbDropdownToggle role="button"> |
3 | <span class="icon icon-action"></span> | 3 | <span class="icon icon-action"></span> |
4 | </div> | 4 | </div> |
5 | 5 | ||
diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/buttons/action-dropdown.component.scss index 615511093..00f120fb8 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.scss +++ b/client/src/app/shared/buttons/action-dropdown.component.scss | |||
@@ -22,6 +22,12 @@ | |||
22 | background-image: url('../../../assets/images/video/more.svg'); | 22 | background-image: url('../../../assets/images/video/more.svg'); |
23 | top: -1px; | 23 | top: -1px; |
24 | } | 24 | } |
25 | |||
26 | &.small { | ||
27 | font-size: 14px; | ||
28 | height: 20px; | ||
29 | line-height: 20px; | ||
30 | } | ||
25 | } | 31 | } |
26 | 32 | ||
27 | .dropdown-menu { | 33 | .dropdown-menu { |
diff --git a/client/src/app/shared/buttons/action-dropdown.component.ts b/client/src/app/shared/buttons/action-dropdown.component.ts index 17f9cc618..1838ff697 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.ts +++ b/client/src/app/shared/buttons/action-dropdown.component.ts | |||
@@ -17,4 +17,5 @@ export class ActionDropdownComponent<T> { | |||
17 | @Input() actions: DropdownAction<T>[] = [] | 17 | @Input() actions: DropdownAction<T>[] = [] |
18 | @Input() entry: T | 18 | @Input() entry: T |
19 | @Input() placement = 'left' | 19 | @Input() placement = 'left' |
20 | @Input() buttonSize: 'normal' | 'small' = 'normal' | ||
20 | } | 21 | } |
diff --git a/client/src/app/shared/moderation/index.ts b/client/src/app/shared/moderation/index.ts index 2245294c5..9a77c64c0 100644 --- a/client/src/app/shared/moderation/index.ts +++ b/client/src/app/shared/moderation/index.ts | |||
@@ -1,2 +1,2 @@ | |||
1 | export * from './user-ban-modal.component' | 1 | export * from './user-ban-modal.component' |
2 | export * from './user-moderation-dropdown.component' \ No newline at end of file | 2 | export * from './user-moderation-dropdown.component' |
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/moderation/user-ban-modal.component.ts index d49783cd2..67ae38e48 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/moderation/user-ban-modal.component.ts | |||
@@ -5,7 +5,8 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | |||
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
7 | import { FormReactive, UserValidatorsService } from '@app/shared/forms' | 7 | import { FormReactive, UserValidatorsService } from '@app/shared/forms' |
8 | import { User, UserService } from '@app/shared/users' | 8 | import { UserService } from '@app/shared/users' |
9 | import { User } from '../../../../../shared' | ||
9 | 10 | ||
10 | @Component({ | 11 | @Component({ |
11 | selector: 'my-user-ban-modal', | 12 | 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 index ed8a4dc66..ed1a4c863 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.html +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.html | |||
@@ -1,3 +1,5 @@ | |||
1 | <my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal> | 1 | <ng-container *ngIf="user && userActions.length !== 0"> |
2 | <my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal> | ||
2 | 3 | ||
3 | <my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown> \ No newline at end of file | 4 | <my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user" [buttonSize]="buttonSize"></my-action-dropdown> |
5 | </ng-container> \ No newline at end of file | ||
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts index d92423476..4f88456de 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts | |||
@@ -4,9 +4,9 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
4 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | 4 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
6 | import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' | 6 | import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' |
7 | import { User, UserService } from '@app/shared/users' | 7 | import { UserService } from '@app/shared/users' |
8 | import { AuthService, ConfirmService } from '@app/core' | 8 | import { AuthService, ConfirmService } from '@app/core' |
9 | import { UserRight } from '../../../../../shared/models/users' | 9 | import { User, UserRight } from '../../../../../shared/models/users' |
10 | 10 | ||
11 | @Component({ | 11 | @Component({ |
12 | selector: 'my-user-moderation-dropdown', | 12 | selector: 'my-user-moderation-dropdown', |
@@ -17,7 +17,10 @@ export class UserModerationDropdownComponent implements OnInit { | |||
17 | @ViewChild('userBanModal') userBanModal: UserBanModalComponent | 17 | @ViewChild('userBanModal') userBanModal: UserBanModalComponent |
18 | 18 | ||
19 | @Input() user: User | 19 | @Input() user: User |
20 | @Input() buttonSize: 'normal' | 'small' = 'normal' | ||
21 | |||
20 | @Output() userChanged = new EventEmitter() | 22 | @Output() userChanged = new EventEmitter() |
23 | @Output() userDeleted = new EventEmitter() | ||
21 | 24 | ||
22 | userActions: DropdownAction<User>[] = [] | 25 | userActions: DropdownAction<User>[] = [] |
23 | 26 | ||
@@ -32,34 +35,7 @@ export class UserModerationDropdownComponent implements OnInit { | |||
32 | ) { } | 35 | ) { } |
33 | 36 | ||
34 | ngOnInit () { | 37 | ngOnInit () { |
35 | this.userActions = [] | 38 | this.buildActions() |
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 | } | 39 | } |
64 | 40 | ||
65 | hideBanUserModal () { | 41 | hideBanUserModal () { |
@@ -115,7 +91,7 @@ export class UserModerationDropdownComponent implements OnInit { | |||
115 | this.i18n('Success'), | 91 | this.i18n('Success'), |
116 | this.i18n('User {{username}} deleted.', { username: user.username }) | 92 | this.i18n('User {{username}} deleted.', { username: user.username }) |
117 | ) | 93 | ) |
118 | this.userChanged.emit() | 94 | this.userDeleted.emit() |
119 | }, | 95 | }, |
120 | 96 | ||
121 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 97 | err => this.notificationsService.error(this.i18n('Error'), err.message) |
@@ -125,4 +101,35 @@ export class UserModerationDropdownComponent implements OnInit { | |||
125 | getRouterUserEditLink (user: User) { | 101 | getRouterUserEditLink (user: User) { |
126 | return [ '/admin', 'users', 'update', user.id ] | 102 | return [ '/admin', 'users', 'update', user.id ] |
127 | } | 103 | } |
104 | |||
105 | private buildActions () { | ||
106 | this.userActions = [] | ||
107 | |||
108 | if (this.authService.isLoggedIn()) { | ||
109 | const authUser = this.authService.getUser() | ||
110 | |||
111 | if (authUser.hasRight(UserRight.MANAGE_USERS)) { | ||
112 | this.userActions = this.userActions.concat([ | ||
113 | { | ||
114 | label: this.i18n('Edit'), | ||
115 | linkBuilder: this.getRouterUserEditLink | ||
116 | }, | ||
117 | { | ||
118 | label: this.i18n('Delete'), | ||
119 | handler: user => this.removeUser(user) | ||
120 | }, | ||
121 | { | ||
122 | label: this.i18n('Ban'), | ||
123 | handler: user => this.openBanUserModal(user), | ||
124 | isDisplayed: user => !user.blocked | ||
125 | }, | ||
126 | { | ||
127 | label: this.i18n('Unban'), | ||
128 | handler: user => this.unbanUser(user), | ||
129 | isDisplayed: user => user.blocked | ||
130 | } | ||
131 | ]) | ||
132 | } | ||
133 | } | ||
134 | } | ||
128 | } | 135 | } |
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 5ab290a59..d9b81c181 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts | |||
@@ -170,19 +170,19 @@ export class UserService { | |||
170 | ) | 170 | ) |
171 | } | 171 | } |
172 | 172 | ||
173 | removeUser (user: User) { | 173 | removeUser (user: { id: number }) { |
174 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) | 174 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) |
175 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 175 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
176 | } | 176 | } |
177 | 177 | ||
178 | banUser (user: User, reason?: string) { | 178 | banUser (user: { id: number }, reason?: string) { |
179 | const body = reason ? { reason } : {} | 179 | const body = reason ? { reason } : {} |
180 | 180 | ||
181 | return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body) | 181 | return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body) |
182 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 182 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
183 | } | 183 | } |
184 | 184 | ||
185 | unbanUser (user: User) { | 185 | unbanUser (user: { id: number }) { |
186 | return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {}) | 186 | return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {}) |
187 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 187 | .pipe(catchError(err => this.restExtractor.handleError(err))) |
188 | } | 188 | } |