aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/blocklist/account-blocklist.component.html64
-rw-r--r--client/src/app/shared/blocklist/account-blocklist.component.scss16
-rw-r--r--client/src/app/shared/blocklist/account-blocklist.component.ts79
-rw-r--r--client/src/app/shared/blocklist/blocklist.service.ts18
-rw-r--r--client/src/app/shared/blocklist/index.ts2
-rw-r--r--client/src/app/shared/blocklist/server-blocklist.component.html59
-rw-r--r--client/src/app/shared/blocklist/server-blocklist.component.scss34
-rw-r--r--client/src/app/shared/blocklist/server-blocklist.component.ts101
-rw-r--r--client/src/app/shared/shared.module.ts7
9 files changed, 374 insertions, 6 deletions
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.html b/client/src/app/shared/blocklist/account-blocklist.component.html
new file mode 100644
index 000000000..486785f35
--- /dev/null
+++ b/client/src/app/shared/blocklist/account-blocklist.component.html
@@ -0,0 +1,64 @@
1<p-table
2 [value]="blockedAccounts" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" (onPage)="onPage($event)"
4 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
5 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
6>
7 <ng-template pTemplate="caption">
8 <div class="caption">
9 <div class="ml-auto has-feedback has-clear">
10 <input
11 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
12 (keyup)="onSearch($event)"
13 >
14 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
15 <span class="sr-only" i18n>Clear filters</span>
16 </div>
17 </div>
18 </ng-template>
19
20 <ng-template pTemplate="header">
21 <tr>
22 <th style="width: 100%;" i18n>Account</th>
23 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
24 <th style="width: 150px;"></th> <!-- column for action buttons -->
25 </tr>
26 </ng-template>
27
28 <ng-template pTemplate="body" let-accountBlock>
29 <tr>
30 <td>
31 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
32 <div class="chip two-lines">
33 <img
34 class="avatar"
35 [src]="accountBlock.blockedAccount.avatar?.path"
36 (error)="switchToDefaultAvatar($event)"
37 alt="Avatar"
38 >
39 <div>
40 {{ accountBlock.blockedAccount.displayName }}
41 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
42 </div>
43 </div>
44 </a>
45 </td>
46
47 <td>{{ accountBlock.createdAt | date: 'short' }}</td>
48 <td class="action-cell">
49 <button class="unblock-button" (click)="unblockAccount(accountBlock)" i18n>Unmute</button>
50 </td>
51 </tr>
52 </ng-template>
53
54 <ng-template pTemplate="emptymessage">
55 <tr>
56 <td colspan="6">
57 <div class="no-results">
58 <ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container>
59 <ng-container *ngIf="!search" i18n>No account found.</ng-container>
60 </div>
61 </td>
62 </tr>
63 </ng-template>
64</p-table>
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.scss b/client/src/app/shared/blocklist/account-blocklist.component.scss
new file mode 100644
index 000000000..aa8363ff4
--- /dev/null
+++ b/client/src/app/shared/blocklist/account-blocklist.component.scss
@@ -0,0 +1,16 @@
1@import '_variables';
2@import '_mixins';
3
4.caption {
5 justify-content: flex-end;
6
7 input {
8 @include peertube-input-text(250px);
9 flex-grow: 1;
10 }
11}
12
13.unblock-button {
14 @include peertube-button;
15 @include grey-button;
16} \ No newline at end of file
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.ts b/client/src/app/shared/blocklist/account-blocklist.component.ts
new file mode 100644
index 000000000..dc5ac4044
--- /dev/null
+++ b/client/src/app/shared/blocklist/account-blocklist.component.ts
@@ -0,0 +1,79 @@
1import { OnInit } from '@angular/core'
2import { Notifier } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { RestPagination, RestTable } from '@app/shared/rest'
5import { SortMeta } from 'primeng/api'
6import { AccountBlock } from './account-block.model'
7import { BlocklistService, BlocklistComponentType } from './blocklist.service'
8import { Actor } from '@app/shared/actor/actor.model'
9
10export class GenericAccountBlocklistComponent extends RestTable implements OnInit {
11 // @ts-ignore: "Abstract methods can only appear within an abstract class"
12 abstract mode: BlocklistComponentType
13
14 blockedAccounts: AccountBlock[] = []
15 totalRecords = 0
16 sort: SortMeta = { field: 'createdAt', order: -1 }
17 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
18
19 constructor (
20 private notifier: Notifier,
21 private blocklistService: BlocklistService,
22 private i18n: I18n
23 ) {
24 super()
25 }
26
27 // @ts-ignore: "Abstract methods can only appear within an abstract class"
28 abstract getIdentifier (): string
29
30 ngOnInit () {
31 this.initialize()
32 }
33
34 switchToDefaultAvatar ($event: Event) {
35 ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
36 }
37
38 unblockAccount (accountBlock: AccountBlock) {
39 const blockedAccount = accountBlock.blockedAccount
40 const operation = this.mode === BlocklistComponentType.Account
41 ? this.blocklistService.unblockAccountByUser(blockedAccount)
42 : this.blocklistService.unblockAccountByInstance(blockedAccount)
43
44 operation.subscribe(
45 () => {
46 this.notifier.success(
47 this.mode === BlocklistComponentType.Account
48 ? this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost })
49 : this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost })
50 )
51
52 this.loadData()
53 }
54 )
55 }
56
57 protected loadData () {
58 const operation = this.mode === BlocklistComponentType.Account
59 ? this.blocklistService.getUserAccountBlocklist({
60 pagination: this.pagination,
61 sort: this.sort,
62 search: this.search
63 })
64 : this.blocklistService.getInstanceAccountBlocklist({
65 pagination: this.pagination,
66 sort: this.sort,
67 search: this.search
68 })
69
70 return operation.subscribe(
71 resultList => {
72 this.blockedAccounts = resultList.data
73 this.totalRecords = resultList.total
74 },
75
76 err => this.notifier.error(err.message)
77 )
78 }
79}
diff --git a/client/src/app/shared/blocklist/blocklist.service.ts b/client/src/app/shared/blocklist/blocklist.service.ts
index 5cf265bc1..c70a8173a 100644
--- a/client/src/app/shared/blocklist/blocklist.service.ts
+++ b/client/src/app/shared/blocklist/blocklist.service.ts
@@ -8,6 +8,8 @@ import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '../
8import { Account } from '@app/shared/account/account.model' 8import { Account } from '@app/shared/account/account.model'
9import { AccountBlock } from '@app/shared/blocklist/account-block.model' 9import { AccountBlock } from '@app/shared/blocklist/account-block.model'
10 10
11export enum BlocklistComponentType { Account, Instance }
12
11@Injectable() 13@Injectable()
12export class BlocklistService { 14export class BlocklistService {
13 static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist' 15 static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist'
@@ -21,10 +23,14 @@ export class BlocklistService {
21 23
22 /*********************** User -> Account blocklist ***********************/ 24 /*********************** User -> Account blocklist ***********************/
23 25
24 getUserAccountBlocklist (pagination: RestPagination, sort: SortMeta) { 26 getUserAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
27 const { pagination, sort, search } = options
28
25 let params = new HttpParams() 29 let params = new HttpParams()
26 params = this.restService.addRestGetParams(params, pagination, sort) 30 params = this.restService.addRestGetParams(params, pagination, sort)
27 31
32 if (search) params = params.append('search', search)
33
28 return this.authHttp.get<ResultList<AccountBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', { params }) 34 return this.authHttp.get<ResultList<AccountBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', { params })
29 .pipe( 35 .pipe(
30 map(res => this.restExtractor.convertResultListDateToHuman(res)), 36 map(res => this.restExtractor.convertResultListDateToHuman(res)),
@@ -49,10 +55,14 @@ export class BlocklistService {
49 55
50 /*********************** User -> Server blocklist ***********************/ 56 /*********************** User -> Server blocklist ***********************/
51 57
52 getUserServerBlocklist (pagination: RestPagination, sort: SortMeta) { 58 getUserServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
59 const { pagination, sort, search } = options
60
53 let params = new HttpParams() 61 let params = new HttpParams()
54 params = this.restService.addRestGetParams(params, pagination, sort) 62 params = this.restService.addRestGetParams(params, pagination, sort)
55 63
64 if (search) params = params.append('search', search)
65
56 return this.authHttp.get<ResultList<ServerBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers', { params }) 66 return this.authHttp.get<ResultList<ServerBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers', { params })
57 .pipe( 67 .pipe(
58 map(res => this.restExtractor.convertResultListDateToHuman(res)), 68 map(res => this.restExtractor.convertResultListDateToHuman(res)),
@@ -76,7 +86,7 @@ export class BlocklistService {
76 86
77 /*********************** Instance -> Account blocklist ***********************/ 87 /*********************** Instance -> Account blocklist ***********************/
78 88
79 getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) { 89 getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
80 const { pagination, sort, search } = options 90 const { pagination, sort, search } = options
81 91
82 let params = new HttpParams() 92 let params = new HttpParams()
@@ -108,7 +118,7 @@ export class BlocklistService {
108 118
109 /*********************** Instance -> Server blocklist ***********************/ 119 /*********************** Instance -> Server blocklist ***********************/
110 120
111 getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) { 121 getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
112 const { pagination, sort, search } = options 122 const { pagination, sort, search } = options
113 123
114 let params = new HttpParams() 124 let params = new HttpParams()
diff --git a/client/src/app/shared/blocklist/index.ts b/client/src/app/shared/blocklist/index.ts
index 5886ca07e..188057b19 100644
--- a/client/src/app/shared/blocklist/index.ts
+++ b/client/src/app/shared/blocklist/index.ts
@@ -1,2 +1,4 @@
1export * from './blocklist.service' 1export * from './blocklist.service'
2export * from './account-block.model' 2export * from './account-block.model'
3export * from './server-blocklist.component'
4export * from './account-blocklist.component'
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.html b/client/src/app/shared/blocklist/server-blocklist.component.html
new file mode 100644
index 000000000..977e0e141
--- /dev/null
+++ b/client/src/app/shared/blocklist/server-blocklist.component.html
@@ -0,0 +1,59 @@
1<p-table
2 [value]="blockedServers" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" (onPage)="onPage($event)"
4 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
5 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted instances"
6>
7 <ng-template pTemplate="caption">
8 <div class="caption">
9 <div class="ml-auto has-feedback has-clear">
10 <input
11 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
12 (keyup)="onSearch($event)"
13 >
14 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
15 <span class="sr-only" i18n>Clear filters</span>
16 </div>
17 <a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()">
18 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
19 <ng-container i18n>Mute domain</ng-container>
20 </a>
21 </div>
22 </ng-template>
23
24 <ng-template pTemplate="header">
25 <tr>
26 <th style="width: 100%;" i18n>Instance</th>
27 <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
28 <th style="width: 150px;"></th> <!-- column for action buttons -->
29 </tr>
30 </ng-template>
31
32 <ng-template pTemplate="body" let-serverBlock>
33 <tr>
34 <td>
35 <a [href]="'https://' + serverBlock.blockedServer.host" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
36 {{ serverBlock.blockedServer.host }}
37 <span class="glyphicon glyphicon-new-window"></span>
38 </a>
39 </td>
40 <td>{{ serverBlock.createdAt | date: 'short' }}</td>
41 <td class="action-cell">
42 <button class="unblock-button" (click)="unblockServer(serverBlock)" i18n>Unmute</button>
43 </td>
44 </tr>
45 </ng-template>
46
47 <ng-template pTemplate="emptymessage">
48 <tr>
49 <td colspan="6">
50 <div class="no-results">
51 <ng-container *ngIf="search" i18n>No server found matching current filters.</ng-container>
52 <ng-container *ngIf="!search" i18n>No server found.</ng-container>
53 </div>
54 </td>
55 </tr>
56 </ng-template>
57</p-table>
58
59<my-batch-domains-modal #batchDomainsModal i18n-action action="Mute domains" (domains)="onDomainsToBlock($event)"></my-batch-domains-modal>
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.scss b/client/src/app/shared/blocklist/server-blocklist.component.scss
new file mode 100644
index 000000000..9ddb76850
--- /dev/null
+++ b/client/src/app/shared/blocklist/server-blocklist.component.scss
@@ -0,0 +1,34 @@
1@import '_variables';
2@import '_mixins';
3
4a {
5 @include disable-default-a-behaviour;
6 display: inline-block;
7
8 &, &:hover {
9 color: pvar(--mainForegroundColor);
10 }
11
12 span {
13 font-size: 80%;
14 color: pvar(--inputPlaceholderColor);
15 }
16}
17
18.caption {
19 justify-content: flex-end;
20
21 input {
22 @include peertube-input-text(250px);
23 flex-grow: 1;
24 }
25}
26
27.unblock-button {
28 @include peertube-button;
29 @include grey-button;
30}
31
32.block-button {
33 @include create-button;
34}
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.ts b/client/src/app/shared/blocklist/server-blocklist.component.ts
new file mode 100644
index 000000000..f2b36badc
--- /dev/null
+++ b/client/src/app/shared/blocklist/server-blocklist.component.ts
@@ -0,0 +1,101 @@
1import { OnInit, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { RestPagination, RestTable } from '@app/shared/rest'
5import { SortMeta } from 'primeng/api'
6import { BlocklistService, BlocklistComponentType } from './blocklist.service'
7import { ServerBlock } from '../../../../../shared/models/blocklist/server-block.model'
8import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
9
10export class GenericServerBlocklistComponent extends RestTable implements OnInit {
11 @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent
12
13 // @ts-ignore: "Abstract methods can only appear within an abstract class"
14 public abstract mode: BlocklistComponentType
15
16 blockedServers: ServerBlock[] = []
17 totalRecords = 0
18 sort: SortMeta = { field: 'createdAt', order: -1 }
19 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
20
21 constructor (
22 protected notifier: Notifier,
23 protected blocklistService: BlocklistService,
24 protected i18n: I18n
25 ) {
26 super()
27 }
28
29 ngOnInit () {
30 this.initialize()
31 }
32
33 // @ts-ignore: "Abstract methods can only appear within an abstract class"
34 public abstract getIdentifier (): string
35
36 unblockServer (serverBlock: ServerBlock) {
37 const operation = (host: string) => this.mode === BlocklistComponentType.Account
38 ? this.blocklistService.unblockServerByUser(host)
39 : this.blocklistService.unblockServerByInstance(host)
40 const host = serverBlock.blockedServer.host
41
42 operation(host).subscribe(
43 () => {
44 this.notifier.success(
45 this.mode === BlocklistComponentType.Account
46 ? this.i18n('Instance {{host}} unmuted.', { host })
47 : this.i18n('Instance {{host}} unmuted by your instance.', { host })
48 )
49
50 this.loadData()
51 }
52 )
53 }
54
55 addServersToBlock () {
56 this.batchDomainsModal.openModal()
57 }
58
59 onDomainsToBlock (domains: string[]) {
60 const operation = (domain: string) => this.mode === BlocklistComponentType.Account
61 ? this.blocklistService.blockServerByUser(domain)
62 : this.blocklistService.blockServerByInstance(domain)
63
64 domains.forEach(domain => {
65 operation(domain).subscribe(
66 () => {
67 this.notifier.success(
68 this.mode === BlocklistComponentType.Account
69 ? this.i18n('Instance {{domain}} muted.', { domain })
70 : this.i18n('Instance {{domain}} muted by your instance.', { domain })
71 )
72
73 this.loadData()
74 }
75 )
76 })
77 }
78
79 protected loadData () {
80 const operation = this.mode === BlocklistComponentType.Account
81 ? this.blocklistService.getUserServerBlocklist({
82 pagination: this.pagination,
83 sort: this.sort,
84 search: this.search
85 })
86 : this.blocklistService.getInstanceServerBlocklist({
87 pagination: this.pagination,
88 sort: this.sort,
89 search: this.search
90 })
91
92 return operation.subscribe(
93 resultList => {
94 this.blockedServers = resultList.data
95 this.totalRecords = resultList.total
96 },
97
98 err => this.notifier.error(err.message)
99 )
100 }
101}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 2035097d7..98fab9e16 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -10,6 +10,7 @@ import { NgModule } from '@angular/core'
10import { FormsModule, ReactiveFormsModule } from '@angular/forms' 10import { FormsModule, ReactiveFormsModule } from '@angular/forms'
11import { RouterModule } from '@angular/router' 11import { RouterModule } from '@angular/router'
12import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' 12import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service'
13import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
13import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' 14import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
14import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' 15import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
15import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' 16import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
@@ -192,7 +193,8 @@ import { VideoService } from './video/video.service'
192 193
193 MyAccountVideoSettingsComponent, 194 MyAccountVideoSettingsComponent,
194 MyAccountInterfaceSettingsComponent, 195 MyAccountInterfaceSettingsComponent,
195 ActorAvatarInfoComponent 196 ActorAvatarInfoComponent,
197 BatchDomainsModalComponent
196 ], 198 ],
197 199
198 exports: [ 200 exports: [
@@ -274,7 +276,8 @@ import { VideoService } from './video/video.service'
274 276
275 MyAccountVideoSettingsComponent, 277 MyAccountVideoSettingsComponent,
276 MyAccountInterfaceSettingsComponent, 278 MyAccountInterfaceSettingsComponent,
277 ActorAvatarInfoComponent 279 ActorAvatarInfoComponent,
280 BatchDomainsModalComponent
278 ], 281 ],
279 282
280 providers: [ 283 providers: [