diff options
Diffstat (limited to 'client/src/app')
10 files changed, 137 insertions, 66 deletions
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html index 7eb89fea1..0662e3ac3 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.html +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html | |||
@@ -62,10 +62,10 @@ | |||
62 | </div> | 62 | </div> |
63 | </th> | 63 | </th> |
64 | <th *ngIf="isSelected('username')" pResizableColumn pSortableColumn="username">{{ getColumn('username').label }} <p-sortIcon field="username"></p-sortIcon></th> | 64 | <th *ngIf="isSelected('username')" pResizableColumn pSortableColumn="username">{{ getColumn('username').label }} <p-sortIcon field="username"></p-sortIcon></th> |
65 | <th *ngIf="isSelected('role')" style="width: 120px;" pSortableColumn="role">{{ getColumn('role').label }} <p-sortIcon field="role"></p-sortIcon></th> | ||
65 | <th *ngIf="isSelected('email')">{{ getColumn('email').label }}</th> | 66 | <th *ngIf="isSelected('email')">{{ getColumn('email').label }}</th> |
66 | <th *ngIf="isSelected('quota')" style="width: 160px;" pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th> | 67 | <th *ngIf="isSelected('quota')" style="width: 160px;" pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th> |
67 | <th *ngIf="isSelected('quotaDaily')" style="width: 160px;">{{ getColumn('quotaDaily').label }}</th> | 68 | <th *ngIf="isSelected('quotaDaily')" style="width: 160px;">{{ getColumn('quotaDaily').label }}</th> |
68 | <th *ngIf="isSelected('role')" style="width: 120px;" pSortableColumn="role">{{ getColumn('role').label }} <p-sortIcon field="role"></p-sortIcon></th> | ||
69 | <th *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn >{{ getColumn('pluginAuth').label }}</th> | 69 | <th *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn >{{ getColumn('pluginAuth').label }}</th> |
70 | <th *ngIf="isSelected('createdAt')" style="width: 150px;" pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th> | 70 | <th *ngIf="isSelected('createdAt')" style="width: 150px;" pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th> |
71 | <th *ngIf="isSelected('lastLoginDate')" style="width: 150px;" pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th> | 71 | <th *ngIf="isSelected('lastLoginDate')" style="width: 150px;" pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th> |
@@ -84,8 +84,9 @@ | |||
84 | </td> | 84 | </td> |
85 | 85 | ||
86 | <td class="action-cell"> | 86 | <td class="action-cell"> |
87 | <my-user-moderation-dropdown *ngIf="!isInSelectionMode()" [user]="user" container="body" | 87 | <my-user-moderation-dropdown |
88 | (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()"> | 88 | *ngIf="!isInSelectionMode()" [user]="user" [account]="user.accountMutedStatus" [displayOptions]="userModerationDisplayOptions" |
89 | container="body" (userChanged)="onUserChanged()" (userDeleted)="onUserChanged()"> | ||
89 | </my-user-moderation-dropdown> | 90 | </my-user-moderation-dropdown> |
90 | </td> | 91 | </td> |
91 | 92 | ||
@@ -99,6 +100,14 @@ | |||
99 | </div> | 100 | </div> |
100 | </div> | 101 | </div> |
101 | </a> | 102 | </a> |
103 | |||
104 | <div *ngIf="user.accountMutedStatus.mutedByInstance" class="badges-username badge badge-red" i18n>Muted</div> | ||
105 | <div *ngIf="user.blocked" class="badges-username badge badge-red" i18n>Banned</div> | ||
106 | </td> | ||
107 | |||
108 | <td *ngIf="isSelected('role')"> | ||
109 | <span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span> | ||
110 | <span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span> | ||
102 | </td> | 111 | </td> |
103 | 112 | ||
104 | <td *ngIf="isSelected('email')" [title]="user.email"> | 113 | <td *ngIf="isSelected('email')" [title]="user.email"> |
@@ -138,11 +147,6 @@ | |||
138 | </div> | 147 | </div> |
139 | </td> | 148 | </td> |
140 | 149 | ||
141 | <td *ngIf="isSelected('role')"> | ||
142 | <span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span> | ||
143 | <span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span> | ||
144 | </td> | ||
145 | |||
146 | <td *ngIf="isSelected('pluginAuth')"> | 150 | <td *ngIf="isSelected('pluginAuth')"> |
147 | <ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container> | 151 | <ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container> |
148 | </td> | 152 | </td> |
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.scss b/client/src/app/+admin/overview/users/user-list/user-list.component.scss index 335bd2995..8160703f0 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.scss +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.scss | |||
@@ -23,6 +23,10 @@ tr.banned > td { | |||
23 | font-weight: $font-semibold; | 23 | font-weight: $font-semibold; |
24 | } | 24 | } |
25 | 25 | ||
26 | .badges-username { | ||
27 | margin-left: 15px; | ||
28 | } | ||
29 | |||
26 | .user-table-primary-text .glyphicon { | 30 | .user-table-primary-text .glyphicon { |
27 | @include margin-left(0.1rem); | 31 | @include margin-left(0.1rem); |
28 | 32 | ||
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts index 9a9d0f5c6..d22e1355e 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts | |||
@@ -2,9 +2,10 @@ import { SortMeta } from 'primeng/api' | |||
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' | 4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' |
5 | import { getAPIHost } from '@app/helpers' | ||
5 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
6 | import { DropdownAction } from '@app/shared/shared-main' | 7 | import { Actor, DropdownAction } from '@app/shared/shared-main' |
7 | import { UserBanModalComponent } from '@app/shared/shared-moderation' | 8 | import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' |
8 | import { UserAdminService } from '@app/shared/shared-users' | 9 | import { UserAdminService } from '@app/shared/shared-users' |
9 | import { User, UserRole } from '@shared/models' | 10 | import { User, UserRole } from '@shared/models' |
10 | 11 | ||
@@ -23,7 +24,7 @@ type UserForList = User & { | |||
23 | export class UserListComponent extends RestTable implements OnInit { | 24 | export class UserListComponent extends RestTable implements OnInit { |
24 | @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent | 25 | @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent |
25 | 26 | ||
26 | users: User[] = [] | 27 | users: (User & { accountMutedStatus: AccountMutedStatus })[] = [] |
27 | 28 | ||
28 | totalRecords = 0 | 29 | totalRecords = 0 |
29 | sort: SortMeta = { field: 'createdAt', order: 1 } | 30 | sort: SortMeta = { field: 'createdAt', order: 1 } |
@@ -47,6 +48,12 @@ export class UserListComponent extends RestTable implements OnInit { | |||
47 | } | 48 | } |
48 | ] | 49 | ] |
49 | 50 | ||
51 | userModerationDisplayOptions: UserModerationDisplayType = { | ||
52 | instanceAccount: true, | ||
53 | instanceUser: true, | ||
54 | myAccount: false | ||
55 | } | ||
56 | |||
50 | requiresEmailVerification = false | 57 | requiresEmailVerification = false |
51 | 58 | ||
52 | private _selectedColumns: string[] | 59 | private _selectedColumns: string[] |
@@ -58,6 +65,7 @@ export class UserListComponent extends RestTable implements OnInit { | |||
58 | private confirmService: ConfirmService, | 65 | private confirmService: ConfirmService, |
59 | private serverService: ServerService, | 66 | private serverService: ServerService, |
60 | private auth: AuthService, | 67 | private auth: AuthService, |
68 | private blocklist: BlocklistService, | ||
61 | private userAdminService: UserAdminService | 69 | private userAdminService: UserAdminService |
62 | ) { | 70 | ) { |
63 | super() | 71 | super() |
@@ -115,9 +123,9 @@ export class UserListComponent extends RestTable implements OnInit { | |||
115 | 123 | ||
116 | this.columns = [ | 124 | this.columns = [ |
117 | { id: 'username', label: $localize`Username` }, | 125 | { id: 'username', label: $localize`Username` }, |
126 | { id: 'role', label: $localize`Role` }, | ||
118 | { id: 'email', label: $localize`Email` }, | 127 | { id: 'email', label: $localize`Email` }, |
119 | { id: 'quota', label: $localize`Video quota` }, | 128 | { id: 'quota', label: $localize`Video quota` }, |
120 | { id: 'role', label: $localize`Role` }, | ||
121 | { id: 'createdAt', label: $localize`Created` } | 129 | { id: 'createdAt', label: $localize`Created` } |
122 | ] | 130 | ] |
123 | 131 | ||
@@ -237,11 +245,35 @@ export class UserListComponent extends RestTable implements OnInit { | |||
237 | search: this.search | 245 | search: this.search |
238 | }).subscribe({ | 246 | }).subscribe({ |
239 | next: resultList => { | 247 | next: resultList => { |
240 | this.users = resultList.data | 248 | this.users = resultList.data.map(u => ({ |
249 | ...u, | ||
250 | |||
251 | accountMutedStatus: { | ||
252 | ...u.account, | ||
253 | |||
254 | nameWithHost: Actor.CREATE_BY_STRING(u.account.name, u.account.host), | ||
255 | |||
256 | mutedByInstance: false, | ||
257 | mutedByUser: false, | ||
258 | mutedServerByInstance: false, | ||
259 | mutedServerByUser: false | ||
260 | } | ||
261 | })) | ||
241 | this.totalRecords = resultList.total | 262 | this.totalRecords = resultList.total |
263 | |||
264 | this.loadMutedStatus() | ||
242 | }, | 265 | }, |
243 | 266 | ||
244 | error: err => this.notifier.error(err.message) | 267 | error: err => this.notifier.error(err.message) |
245 | }) | 268 | }) |
246 | } | 269 | } |
270 | |||
271 | private loadMutedStatus () { | ||
272 | this.blocklist.getStatus({ accounts: this.users.map(u => u.username + '@' + getAPIHost()) }) | ||
273 | .subscribe(blockStatus => { | ||
274 | for (const user of this.users) { | ||
275 | user.accountMutedStatus.mutedByInstance = blockStatus.accounts[user.username + '@' + getAPIHost()].blockedByServer | ||
276 | } | ||
277 | }) | ||
278 | } | ||
247 | } | 279 | } |
diff --git a/client/src/app/helpers/utils/url.ts b/client/src/app/helpers/utils/url.ts index b3cded8f4..08c27e3c1 100644 --- a/client/src/app/helpers/utils/url.ts +++ b/client/src/app/helpers/utils/url.ts | |||
@@ -13,6 +13,10 @@ function getAbsoluteAPIUrl () { | |||
13 | return absoluteAPIUrl | 13 | return absoluteAPIUrl |
14 | } | 14 | } |
15 | 15 | ||
16 | function getAPIHost () { | ||
17 | return new URL(getAbsoluteAPIUrl()).host | ||
18 | } | ||
19 | |||
16 | function getAbsoluteEmbedUrl () { | 20 | function getAbsoluteEmbedUrl () { |
17 | let absoluteEmbedUrl = environment.originServerUrl | 21 | let absoluteEmbedUrl = environment.originServerUrl |
18 | if (!absoluteEmbedUrl) { | 22 | if (!absoluteEmbedUrl) { |
@@ -52,5 +56,6 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) { | |||
52 | export { | 56 | export { |
53 | objectToFormData, | 57 | objectToFormData, |
54 | getAbsoluteAPIUrl, | 58 | getAbsoluteAPIUrl, |
59 | getAPIHost, | ||
55 | getAbsoluteEmbedUrl | 60 | getAbsoluteEmbedUrl |
56 | } | 61 | } |
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts index a54f51aa4..bd693860d 100644 --- a/client/src/app/shared/shared-main/account/actor.model.ts +++ b/client/src/app/shared/shared-main/account/actor.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { getAbsoluteAPIUrl } from '@app/helpers' | 1 | import { getAbsoluteAPIUrl, getAPIHost } from '@app/helpers' |
2 | import { Actor as ServerActor, ActorImage } from '@shared/models' | 2 | import { Actor as ServerActor, ActorImage } from '@shared/models' |
3 | 3 | ||
4 | export abstract class Actor implements ServerActor { | 4 | export abstract class Actor implements ServerActor { |
@@ -32,8 +32,7 @@ export abstract class Actor implements ServerActor { | |||
32 | } | 32 | } |
33 | 33 | ||
34 | static CREATE_BY_STRING (accountName: string, host: string, forceHostname = false) { | 34 | static CREATE_BY_STRING (accountName: string, host: string, forceHostname = false) { |
35 | const absoluteAPIUrl = getAbsoluteAPIUrl() | 35 | const thisHost = getAPIHost() |
36 | const thisHost = new URL(absoluteAPIUrl).host | ||
37 | 36 | ||
38 | if (host.trim() === thisHost && !forceHostname) return accountName | 37 | if (host.trim() === thisHost && !forceHostname) return accountName |
39 | 38 | ||
@@ -41,8 +40,7 @@ export abstract class Actor implements ServerActor { | |||
41 | } | 40 | } |
42 | 41 | ||
43 | static IS_LOCAL (host: string) { | 42 | static IS_LOCAL (host: string) { |
44 | const absoluteAPIUrl = getAbsoluteAPIUrl() | 43 | const thisHost = getAPIHost() |
45 | const thisHost = new URL(absoluteAPIUrl).host | ||
46 | 44 | ||
47 | return host.trim() === thisHost | 45 | return host.trim() === thisHost |
48 | } | 46 | } |
diff --git a/client/src/app/shared/shared-moderation/blocklist.service.ts b/client/src/app/shared/shared-moderation/blocklist.service.ts index f4836c6c4..3e92c2831 100644 --- a/client/src/app/shared/shared-moderation/blocklist.service.ts +++ b/client/src/app/shared/shared-moderation/blocklist.service.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { catchError, map } from 'rxjs/operators' | 2 | import { from } from 'rxjs' |
3 | import { catchError, concatMap, map, toArray } from 'rxjs/operators' | ||
3 | import { HttpClient, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpParams } from '@angular/common/http' |
4 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
5 | import { RestExtractor, RestPagination, RestService } from '@app/core' | 6 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
@@ -120,11 +121,15 @@ export class BlocklistService { | |||
120 | ) | 121 | ) |
121 | } | 122 | } |
122 | 123 | ||
123 | blockAccountByInstance (account: Pick<Account, 'nameWithHost'>) { | 124 | blockAccountByInstance (accountsArg: Pick<Account, 'nameWithHost'> | Pick<Account, 'nameWithHost'>[]) { |
124 | const body = { accountName: account.nameWithHost } | 125 | const accounts = Array.isArray(accountsArg) ? accountsArg : [ accountsArg ] |
125 | 126 | ||
126 | return this.authHttp.post(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', body) | 127 | return from(accounts) |
127 | .pipe(catchError(err => this.restExtractor.handleError(err))) | 128 | .pipe( |
129 | concatMap(a => this.authHttp.post(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', { accountName: a.nameWithHost })), | ||
130 | toArray(), | ||
131 | catchError(err => this.restExtractor.handleError(err)) | ||
132 | ) | ||
128 | } | 133 | } |
129 | 134 | ||
130 | unblockAccountByInstance (account: Pick<Account, 'nameWithHost'>) { | 135 | unblockAccountByInstance (account: Pick<Account, 'nameWithHost'>) { |
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.html b/client/src/app/shared/shared-moderation/user-ban-modal.component.html index 6c83cc9cc..2b6726bdc 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.html +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.html | |||
@@ -1,11 +1,15 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Ban</h4> | 3 | <h4 i18n class="modal-title">{{ getModalTitle() }}</h4> |
4 | 4 | ||
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | 5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
6 | </div> | 6 | </div> |
7 | 7 | ||
8 | <div class="modal-body"> | 8 | <div class="modal-body"> |
9 | <div class="description" i18n> | ||
10 | A banned user will no longer be able to login. | ||
11 | </div> | ||
12 | |||
9 | <form novalidate [formGroup]="form" (ngSubmit)="banUser()"> | 13 | <form novalidate [formGroup]="form" (ngSubmit)="banUser()"> |
10 | <div class="form-group"> | 14 | <div class="form-group"> |
11 | <textarea | 15 | <textarea |
@@ -17,15 +21,12 @@ | |||
17 | </div> | 21 | </div> |
18 | </div> | 22 | </div> |
19 | 23 | ||
20 | <div i18n> | ||
21 | A banned user will no longer be able to login. | ||
22 | </div> | ||
23 | |||
24 | <div class="form-group"> | 24 | <div class="form-group"> |
25 | <my-peertube-checkbox | 25 | <my-peertube-checkbox |
26 | inputName="banMute" formControlName="mute" | 26 | inputName="banMute" formControlName="mute" |
27 | i18n-labelText labelText="Mute this account" | 27 | i18n-labelText labelText="Mute to also hide videos/comments" |
28 | ></my-peertube-checkbox> | 28 | > |
29 | </my-peertube-checkbox> | ||
29 | </div> | 30 | </div> |
30 | 31 | ||
31 | <div class="form-group inputs"> | 32 | <div class="form-group inputs"> |
@@ -34,7 +35,7 @@ | |||
34 | (click)="hide()" (key.enter)="hide()" | 35 | (click)="hide()" (key.enter)="hide()" |
35 | > | 36 | > |
36 | 37 | ||
37 | <input type="submit" i18n-value [value]="modalMessage" class="peertube-button orange-button" [disabled]="!form.valid" /> | 38 | <input type="submit" i18n-value [value]="getModalTitle()" class="peertube-button orange-button" [disabled]="!form.valid" /> |
38 | </div> | 39 | </div> |
39 | </form> | 40 | </form> |
40 | </div> | 41 | </div> |
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.scss b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss index 08e072d8f..2c46c3d03 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.scss +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss | |||
@@ -1,6 +1,11 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .description { | ||
5 | font-size: 15px; | ||
6 | margin-bottom: 15px; | ||
7 | } | ||
8 | |||
4 | textarea { | 9 | textarea { |
5 | @include peertube-textarea(100%, 60px); | 10 | @include peertube-textarea(100%, 60px); |
6 | } | 11 | } |
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts index b3f03990f..9edfac388 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { forkJoin } from 'rxjs' | ||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 2 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
@@ -42,9 +43,6 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
42 | openModal (user: User | User[]) { | 43 | openModal (user: User | User[]) { |
43 | this.usersToBan = user | 44 | this.usersToBan = user |
44 | this.openedModal = this.modalService.open(this.modal, { centered: true }) | 45 | this.openedModal = this.modalService.open(this.modal, { centered: true }) |
45 | |||
46 | const isSingleUser = !(Array.isArray(this.usersToBan) && this.usersToBan.length > 1) | ||
47 | this.modalMessage = isSingleUser ? $localize`Ban this user` : $localize`Ban these users` | ||
48 | } | 46 | } |
49 | 47 | ||
50 | hide () { | 48 | hide () { |
@@ -56,7 +54,13 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
56 | const reason = this.form.value['reason'] || undefined | 54 | const reason = this.form.value['reason'] || undefined |
57 | const mute = this.form.value['mute'] | 55 | const mute = this.form.value['mute'] |
58 | 56 | ||
59 | this.userAdminService.banUsers(this.usersToBan, reason) | 57 | const observables = [ |
58 | this.userAdminService.banUsers(this.usersToBan, reason) | ||
59 | ] | ||
60 | |||
61 | if (mute) observables.push(this.muteAccounts()) | ||
62 | |||
63 | forkJoin(observables) | ||
60 | .subscribe({ | 64 | .subscribe({ |
61 | next: () => { | 65 | next: () => { |
62 | const message = Array.isArray(this.usersToBan) | 66 | const message = Array.isArray(this.usersToBan) |
@@ -67,22 +71,6 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
67 | 71 | ||
68 | this.userBanned.emit(this.usersToBan) | 72 | this.userBanned.emit(this.usersToBan) |
69 | 73 | ||
70 | if (mute) { | ||
71 | const users = Array.isArray(this.usersToBan) ? this.usersToBan : [ this.usersToBan ] | ||
72 | users.forEach(user => { | ||
73 | const account = new Account(user.account) | ||
74 | this.blocklistService.blockAccountByInstance(account) | ||
75 | .subscribe({ | ||
76 | next: () => { | ||
77 | this.notifier.success($localize`Account ${user.username} muted by the instance.`) | ||
78 | account.mutedByInstance = true | ||
79 | }, | ||
80 | |||
81 | error: err => this.notifier.error(err.message) | ||
82 | }) | ||
83 | }) | ||
84 | } | ||
85 | |||
86 | this.hide() | 74 | this.hide() |
87 | }, | 75 | }, |
88 | 76 | ||
@@ -90,4 +78,17 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
90 | }) | 78 | }) |
91 | } | 79 | } |
92 | 80 | ||
81 | getModalTitle () { | ||
82 | if (Array.isArray(this.usersToBan)) return $localize`Ban ${this.usersToBan.length} users` | ||
83 | |||
84 | return $localize`Ban "${this.usersToBan.username}"` | ||
85 | } | ||
86 | |||
87 | private muteAccounts () { | ||
88 | const accounts = Array.isArray(this.usersToBan) | ||
89 | ? this.usersToBan.map(u => new Account(u.account)) | ||
90 | : new Account(this.usersToBan.account) | ||
91 | |||
92 | return this.blocklistService.blockAccountByInstance(accounts) | ||
93 | } | ||
93 | } | 94 | } |
diff --git a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts index 0d19565ef..787318c2c 100644 --- a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts | |||
@@ -7,6 +7,16 @@ import { BlocklistService } from './blocklist.service' | |||
7 | import { BulkService } from './bulk.service' | 7 | import { BulkService } from './bulk.service' |
8 | import { UserBanModalComponent } from './user-ban-modal.component' | 8 | import { UserBanModalComponent } from './user-ban-modal.component' |
9 | 9 | ||
10 | export type AccountMutedStatus = | ||
11 | Pick<Account, 'id' | 'nameWithHost' | 'host' | 'userId' | | ||
12 | 'mutedByInstance' | 'mutedByUser' | 'mutedServerByInstance' | 'mutedServerByUser'> | ||
13 | |||
14 | export type UserModerationDisplayType = { | ||
15 | myAccount?: boolean | ||
16 | instanceAccount?: boolean | ||
17 | instanceUser?: boolean | ||
18 | } | ||
19 | |||
10 | @Component({ | 20 | @Component({ |
11 | selector: 'my-user-moderation-dropdown', | 21 | selector: 'my-user-moderation-dropdown', |
12 | templateUrl: './user-moderation-dropdown.component.html' | 22 | templateUrl: './user-moderation-dropdown.component.html' |
@@ -15,8 +25,8 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
15 | @ViewChild('userBanModal') userBanModal: UserBanModalComponent | 25 | @ViewChild('userBanModal') userBanModal: UserBanModalComponent |
16 | 26 | ||
17 | @Input() user: User | 27 | @Input() user: User |
18 | @Input() account: Account | 28 | @Input() account: AccountMutedStatus |
19 | @Input() prependActions: DropdownAction<{ user: User, account: Account }>[] | 29 | @Input() prependActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[] |
20 | 30 | ||
21 | @Input() buttonSize: 'normal' | 'small' = 'normal' | 31 | @Input() buttonSize: 'normal' | 'small' = 'normal' |
22 | @Input() buttonStyled = true | 32 | @Input() buttonStyled = true |
@@ -24,10 +34,16 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
24 | @Input() label: string | 34 | @Input() label: string |
25 | @Input() container: 'body' | undefined = undefined | 35 | @Input() container: 'body' | undefined = undefined |
26 | 36 | ||
37 | @Input() displayOptions: UserModerationDisplayType = { | ||
38 | myAccount: true, | ||
39 | instanceAccount: true, | ||
40 | instanceUser: true | ||
41 | } | ||
42 | |||
27 | @Output() userChanged = new EventEmitter() | 43 | @Output() userChanged = new EventEmitter() |
28 | @Output() userDeleted = new EventEmitter() | 44 | @Output() userDeleted = new EventEmitter() |
29 | 45 | ||
30 | userActions: DropdownAction<{ user: User, account: Account }>[][] = [] | 46 | userActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[][] = [] |
31 | 47 | ||
32 | requiresEmailVerification = false | 48 | requiresEmailVerification = false |
33 | 49 | ||
@@ -111,7 +127,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
111 | }) | 127 | }) |
112 | } | 128 | } |
113 | 129 | ||
114 | blockAccountByUser (account: Account) { | 130 | blockAccountByUser (account: AccountMutedStatus) { |
115 | this.blocklistService.blockAccountByUser(account) | 131 | this.blocklistService.blockAccountByUser(account) |
116 | .subscribe({ | 132 | .subscribe({ |
117 | next: () => { | 133 | next: () => { |
@@ -125,7 +141,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
125 | }) | 141 | }) |
126 | } | 142 | } |
127 | 143 | ||
128 | unblockAccountByUser (account: Account) { | 144 | unblockAccountByUser (account: AccountMutedStatus) { |
129 | this.blocklistService.unblockAccountByUser(account) | 145 | this.blocklistService.unblockAccountByUser(account) |
130 | .subscribe({ | 146 | .subscribe({ |
131 | next: () => { | 147 | next: () => { |
@@ -167,7 +183,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
167 | }) | 183 | }) |
168 | } | 184 | } |
169 | 185 | ||
170 | blockAccountByInstance (account: Account) { | 186 | blockAccountByInstance (account: AccountMutedStatus) { |
171 | this.blocklistService.blockAccountByInstance(account) | 187 | this.blocklistService.blockAccountByInstance(account) |
172 | .subscribe({ | 188 | .subscribe({ |
173 | next: () => { | 189 | next: () => { |
@@ -181,7 +197,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
181 | }) | 197 | }) |
182 | } | 198 | } |
183 | 199 | ||
184 | unblockAccountByInstance (account: Account) { | 200 | unblockAccountByInstance (account: AccountMutedStatus) { |
185 | this.blocklistService.unblockAccountByInstance(account) | 201 | this.blocklistService.unblockAccountByInstance(account) |
186 | .subscribe({ | 202 | .subscribe({ |
187 | next: () => { | 203 | next: () => { |
@@ -246,7 +262,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
246 | return user && this.authService.getUser().id === user.id | 262 | return user && this.authService.getUser().id === user.id |
247 | } | 263 | } |
248 | 264 | ||
249 | private isMyAccount (account: Account) { | 265 | private isMyAccount (account: AccountMutedStatus) { |
250 | return account && this.authService.getUser().account.id === account.id | 266 | return account && this.authService.getUser().account.id === account.id |
251 | } | 267 | } |
252 | 268 | ||
@@ -267,9 +283,9 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
267 | } | 283 | } |
268 | 284 | ||
269 | private buildMyAccountModerationActions () { | 285 | private buildMyAccountModerationActions () { |
270 | if (!this.account || !this.authService.isLoggedIn()) return [] | 286 | if (!this.account || !this.displayOptions.myAccount || !this.authService.isLoggedIn()) return [] |
271 | 287 | ||
272 | const myAccountActions: DropdownAction<{ user: User, account: Account }>[] = [ | 288 | const myAccountActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[] = [ |
273 | { | 289 | { |
274 | label: $localize`My account moderation`, | 290 | label: $localize`My account moderation`, |
275 | class: [ 'red' ], | 291 | class: [ 'red' ], |
@@ -315,9 +331,9 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
315 | 331 | ||
316 | const authUser = this.authService.getUser() | 332 | const authUser = this.authService.getUser() |
317 | 333 | ||
318 | let instanceActions: DropdownAction<{ user: User, account: Account }>[] = [] | 334 | let instanceActions: DropdownAction<{ user: User, account: AccountMutedStatus }>[] = [] |
319 | 335 | ||
320 | if (this.user && authUser.hasRight(UserRight.MANAGE_USERS) && authUser.canManage(this.user)) { | 336 | if (this.user && this.displayOptions.instanceUser && authUser.hasRight(UserRight.MANAGE_USERS) && authUser.canManage(this.user)) { |
321 | instanceActions = instanceActions.concat([ | 337 | instanceActions = instanceActions.concat([ |
322 | { | 338 | { |
323 | label: $localize`Edit user`, | 339 | label: $localize`Edit user`, |
@@ -351,7 +367,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
351 | } | 367 | } |
352 | 368 | ||
353 | // Instance actions on account blocklists | 369 | // Instance actions on account blocklists |
354 | if (this.account && authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { | 370 | if (this.account && this.displayOptions.instanceAccount && authUser.hasRight(UserRight.MANAGE_ACCOUNTS_BLOCKLIST)) { |
355 | instanceActions = instanceActions.concat([ | 371 | instanceActions = instanceActions.concat([ |
356 | { | 372 | { |
357 | label: $localize`Mute this account`, | 373 | label: $localize`Mute this account`, |
@@ -369,7 +385,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
369 | } | 385 | } |
370 | 386 | ||
371 | // Instance actions on server blocklists | 387 | // Instance actions on server blocklists |
372 | if (this.account && authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { | 388 | if (this.account && this.displayOptions.instanceAccount && authUser.hasRight(UserRight.MANAGE_SERVERS_BLOCKLIST)) { |
373 | instanceActions = instanceActions.concat([ | 389 | instanceActions = instanceActions.concat([ |
374 | { | 390 | { |
375 | label: $localize`Mute the instance`, | 391 | label: $localize`Mute the instance`, |
@@ -386,7 +402,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges { | |||
386 | ]) | 402 | ]) |
387 | } | 403 | } |
388 | 404 | ||
389 | if (this.account && authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) { | 405 | if (this.account && this.displayOptions.instanceAccount && authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)) { |
390 | instanceActions = instanceActions.concat([ | 406 | instanceActions = instanceActions.concat([ |
391 | { | 407 | { |
392 | label: $localize`Remove comments from your instance`, | 408 | label: $localize`Remove comments from your instance`, |