aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.html20
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.scss4
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.ts42
-rw-r--r--client/src/app/helpers/utils/url.ts5
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts8
-rw-r--r--client/src/app/shared/shared-moderation/blocklist.service.ts15
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.html17
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.scss5
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.ts41
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts46
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'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
5import { getAPIHost } from '@app/helpers'
5import { AdvancedInputFilter } from '@app/shared/shared-forms' 6import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main' 7import { Actor, DropdownAction } from '@app/shared/shared-main'
7import { UserBanModalComponent } from '@app/shared/shared-moderation' 8import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
8import { UserAdminService } from '@app/shared/shared-users' 9import { UserAdminService } from '@app/shared/shared-users'
9import { User, UserRole } from '@shared/models' 10import { User, UserRole } from '@shared/models'
10 11
@@ -23,7 +24,7 @@ type UserForList = User & {
23export class UserListComponent extends RestTable implements OnInit { 24export 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
16function getAPIHost () {
17 return new URL(getAbsoluteAPIUrl()).host
18}
19
16function getAbsoluteEmbedUrl () { 20function 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) {
52export { 56export {
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 @@
1import { getAbsoluteAPIUrl } from '@app/helpers' 1import { getAbsoluteAPIUrl, getAPIHost } from '@app/helpers'
2import { Actor as ServerActor, ActorImage } from '@shared/models' 2import { Actor as ServerActor, ActorImage } from '@shared/models'
3 3
4export abstract class Actor implements ServerActor { 4export 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 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { catchError, map } from 'rxjs/operators' 2import { from } from 'rxjs'
3import { catchError, concatMap, map, toArray } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
5import { RestExtractor, RestPagination, RestService } from '@app/core' 6import { 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
4textarea { 9textarea {
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 @@
1import { forkJoin } from 'rxjs'
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 2import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 4import { 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'
7import { BulkService } from './bulk.service' 7import { BulkService } from './bulk.service'
8import { UserBanModalComponent } from './user-ban-modal.component' 8import { UserBanModalComponent } from './user-ban-modal.component'
9 9
10export type AccountMutedStatus =
11 Pick<Account, 'id' | 'nameWithHost' | 'host' | 'userId' |
12 'mutedByInstance' | 'mutedByUser' | 'mutedServerByInstance' | 'mutedServerByUser'>
13
14export 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`,