diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-06-15 13:18:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-15 13:18:22 +0200 |
commit | 228393302d98136d4dc35c5f197edc8cebd5d64f (patch) | |
tree | f92b3ad80bcc9c89088ff1d4de5ebff76a3f46ed /client/src/app/shared/blocklist | |
parent | 7dfe35288613967f5ac69cd46901ec60c5050b93 (diff) | |
download | PeerTube-228393302d98136d4dc35c5f197edc8cebd5d64f.tar.gz PeerTube-228393302d98136d4dc35c5f197edc8cebd5d64f.tar.zst PeerTube-228393302d98136d4dc35c5f197edc8cebd5d64f.zip |
factorize account/server blocklists for users and instance (#2875)
Diffstat (limited to 'client/src/app/shared/blocklist')
8 files changed, 369 insertions, 4 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 @@ | |||
1 | import { OnInit } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { RestPagination, RestTable } from '@app/shared/rest' | ||
5 | import { SortMeta } from 'primeng/api' | ||
6 | import { AccountBlock } from './account-block.model' | ||
7 | import { BlocklistService, BlocklistComponentType } from './blocklist.service' | ||
8 | import { Actor } from '@app/shared/actor/actor.model' | ||
9 | |||
10 | export 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 '../ | |||
8 | import { Account } from '@app/shared/account/account.model' | 8 | import { Account } from '@app/shared/account/account.model' |
9 | import { AccountBlock } from '@app/shared/blocklist/account-block.model' | 9 | import { AccountBlock } from '@app/shared/blocklist/account-block.model' |
10 | 10 | ||
11 | export enum BlocklistComponentType { Account, Instance } | ||
12 | |||
11 | @Injectable() | 13 | @Injectable() |
12 | export class BlocklistService { | 14 | export 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 @@ | |||
1 | export * from './blocklist.service' | 1 | export * from './blocklist.service' |
2 | export * from './account-block.model' | 2 | export * from './account-block.model' |
3 | export * from './server-blocklist.component' | ||
4 | export * 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 | |||
4 | a { | ||
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 @@ | |||
1 | import { OnInit, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { RestPagination, RestTable } from '@app/shared/rest' | ||
5 | import { SortMeta } from 'primeng/api' | ||
6 | import { BlocklistService, BlocklistComponentType } from './blocklist.service' | ||
7 | import { ServerBlock } from '../../../../../shared/models/blocklist/server-block.model' | ||
8 | import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component' | ||
9 | |||
10 | export 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 | } | ||