1
0
Fork 0

factorize account/server blocklists for users and instance (#2875)

This commit is contained in:
Rigel Kent 2020-06-15 13:18:22 +02:00 committed by GitHub
parent 7dfe352886
commit 228393302d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 326 additions and 341 deletions

View file

@ -27,7 +27,6 @@ import { SelectButtonModule } from 'primeng/selectbutton'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { VideoRedundancyInformationComponent } from '@app/+admin/follows/video-redundancies-list/video-redundancy-information.component' import { VideoRedundancyInformationComponent } from '@app/+admin/follows/video-redundancies-list/video-redundancy-information.component'
import { ChartModule } from 'primeng/chart' import { ChartModule } from 'primeng/chart'
import { BatchDomainsModalComponent } from './config/shared/batch-domains-modal.component'
import { VideoAbuseDetailsComponent } from './moderation/video-abuse-list/video-abuse-details.component' import { VideoAbuseDetailsComponent } from './moderation/video-abuse-list/video-abuse-details.component'
@NgModule({ @NgModule({
@ -76,9 +75,7 @@ import { VideoAbuseDetailsComponent } from './moderation/video-abuse-list/video-
DebugComponent, DebugComponent,
ConfigComponent, ConfigComponent,
EditCustomConfigComponent, EditCustomConfigComponent
BatchDomainsModalComponent
], ],
exports: [ exports: [

View file

@ -56,7 +56,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container> <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container>
</div> </div>

View file

@ -58,7 +58,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="search" i18n>No host found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No host found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>Your instance is not following anyone.</ng-container> <ng-container *ngIf="!search" i18n>Your instance is not following anyone.</ng-container>
</div> </div>

View file

@ -4,7 +4,3 @@
flex-grow: 0; flex-grow: 0;
margin-right: 30px; margin-right: 30px;
} }
.empty-table-message {
@include empty-state;
}

View file

@ -73,7 +73,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="isDisplayingRemoteVideos()" i18n>Your instance doesn't mirror any video.</ng-container> <ng-container *ngIf="isDisplayingRemoteVideos()" i18n>Your instance doesn't mirror any video.</ng-container>
<ng-container *ngIf="!isDisplayingRemoteVideos()" i18n>Your instance has no mirrored videos.</ng-container> <ng-container *ngIf="!isDisplayingRemoteVideos()" i18n>Your instance has no mirrored videos.</ng-container>
</div> </div>

View file

@ -54,7 +54,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No account found.</ng-container> <ng-container *ngIf="!search" i18n>No account found.</ng-container>
</div> </div>

View file

@ -1,70 +1,15 @@
import { Component, OnInit } from '@angular/core' import { Component } from '@angular/core'
import { Notifier } from '@app/core' import { GenericAccountBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { RestPagination, RestTable } from '@app/shared'
import { SortMeta } from 'primeng/api'
import { AccountBlock, BlocklistService } from '@app/shared/blocklist'
import { Actor } from '@app/shared/actor/actor.model'
@Component({ @Component({
selector: 'my-instance-account-blocklist', selector: 'my-instance-account-blocklist',
styleUrls: [ '../moderation.component.scss', './instance-account-blocklist.component.scss' ], styleUrls: [ '../moderation.component.scss', '../../../shared/blocklist/account-blocklist.component.scss' ],
templateUrl: './instance-account-blocklist.component.html' templateUrl: '../../../shared/blocklist/account-blocklist.component.html'
}) })
export class InstanceAccountBlocklistComponent extends RestTable implements OnInit { export class InstanceAccountBlocklistComponent extends GenericAccountBlocklistComponent {
blockedAccounts: AccountBlock[] = [] mode = BlocklistComponentType.Instance
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
private notifier: Notifier,
private blocklistService: BlocklistService,
private i18n: I18n
) {
super()
}
ngOnInit () {
this.initialize()
}
getIdentifier () { getIdentifier () {
return 'InstanceAccountBlocklistComponent' return 'InstanceAccountBlocklistComponent'
} }
switchToDefaultAvatar ($event: Event) {
($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
}
unblockAccount (accountBlock: AccountBlock) {
const blockedAccount = accountBlock.blockedAccount
this.blocklistService.unblockAccountByInstance(blockedAccount)
.subscribe(
() => {
this.notifier.success(
this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost })
)
this.loadData()
}
)
}
protected loadData () {
return this.blocklistService.getInstanceAccountBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
.subscribe(
resultList => {
this.blockedAccounts = resultList.data
this.totalRecords = resultList.total
},
err => this.notifier.error(err.message)
)
}
} }

View file

@ -1,84 +1,15 @@
import { Component, OnInit, ViewChild } from '@angular/core' import { Component } from '@angular/core'
import { Notifier } from '@app/core' import { GenericServerBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { RestPagination, RestTable } from '@app/shared'
import { SortMeta } from 'primeng/api'
import { BlocklistService } from '@app/shared/blocklist'
import { ServerBlock } from '../../../../../../shared'
import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
@Component({ @Component({
selector: 'my-instance-server-blocklist', selector: 'my-instance-server-blocklist',
styleUrls: [ '../moderation.component.scss', './instance-server-blocklist.component.scss' ], styleUrls: [ '../../../shared/blocklist/server-blocklist.component.scss' ],
templateUrl: './instance-server-blocklist.component.html' templateUrl: '../../../shared/blocklist/server-blocklist.component.html'
}) })
export class InstanceServerBlocklistComponent extends RestTable implements OnInit { export class InstanceServerBlocklistComponent extends GenericServerBlocklistComponent {
@ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent mode = BlocklistComponentType.Instance
blockedServers: ServerBlock[] = []
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
private notifier: Notifier,
private blocklistService: BlocklistService,
private i18n: I18n
) {
super()
}
ngOnInit () {
this.initialize()
}
getIdentifier () { getIdentifier () {
return 'InstanceServerBlocklistComponent' return 'InstanceServerBlocklistComponent'
} }
unblockServer (serverBlock: ServerBlock) {
const host = serverBlock.blockedServer.host
this.blocklistService.unblockServerByInstance(host)
.subscribe(
() => {
this.notifier.success(this.i18n('Instance {{host}} unmuted by your instance.', { host }))
this.loadData()
}
)
}
addServersToBlock () {
this.batchDomainsModal.openModal()
}
onDomainsToBlock (domains: string[]) {
domains.forEach(domain => {
this.blocklistService.blockServerByInstance(domain)
.subscribe(
() => {
this.notifier.success(this.i18n('Instance {{domain}} muted by your instance.', { domain }))
this.loadData()
}
)
})
}
protected loadData () {
return this.blocklistService.getInstanceServerBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
.subscribe(
resultList => {
this.blockedServers = resultList.data
this.totalRecords = resultList.total
},
err => this.notifier.error(err.message)
)
}
} }

View file

@ -16,10 +16,6 @@
} }
} }
.empty-table-message {
@include empty-state;
}
.moderation-expanded { .moderation-expanded {
font-size: 90%; font-size: 90%;

View file

@ -137,7 +137,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="search" i18n>No video abuses found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No video abuses found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No video abuses found.</ng-container> <ng-container *ngIf="!search" i18n>No video abuses found.</ng-container>
</div> </div>

View file

@ -102,7 +102,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="search" i18n>No blocked video found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No blocked video found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No blocked video found.</ng-container> <ng-container *ngIf="!search" i18n>No blocked video found.</ng-container>
</div> </div>

View file

@ -1,27 +0,0 @@
<div class="admin-sub-header">
<h1 i18n class="form-sub-title">Muted accounts</h1>
</div>
<p-table
[value]="blockedAccounts" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<ng-template pTemplate="header">
<tr>
<th i18n>Account</th>
<th i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
<th></th> <!-- column for action buttons -->
</tr>
</ng-template>
<ng-template pTemplate="body" let-accountBlock>
<tr>
<td>{{ accountBlock.blockedAccount.nameWithHost }}</td>
<td>{{ accountBlock.createdAt }}</td>
<td class="action-cell">
<button class="unblock-button" (click)="unblockAccount(accountBlock)" i18n>Unmute</button>
</td>
</tr>
</ng-template>
</p-table>

View file

@ -1,7 +0,0 @@
@import '_variables';
@import '_mixins';
.unblock-button {
@include peertube-button;
@include grey-button;
}

View file

@ -1,59 +1,15 @@
import { Component, OnInit } from '@angular/core' import { Component } from '@angular/core'
import { Notifier } from '@app/core' import { GenericAccountBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { RestPagination, RestTable } from '@app/shared'
import { SortMeta } from 'primeng/api'
import { AccountBlock, BlocklistService } from '@app/shared/blocklist'
@Component({ @Component({
selector: 'my-account-blocklist', selector: 'my-account-blocklist',
styleUrls: [ './my-account-blocklist.component.scss' ], styleUrls: [ '../../shared/blocklist/account-blocklist.component.scss' ],
templateUrl: './my-account-blocklist.component.html' templateUrl: '../../shared/blocklist/account-blocklist.component.html'
}) })
export class MyAccountBlocklistComponent extends RestTable implements OnInit { export class MyAccountBlocklistComponent extends GenericAccountBlocklistComponent {
blockedAccounts: AccountBlock[] = [] mode = BlocklistComponentType.Account
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
private notifier: Notifier,
private blocklistService: BlocklistService,
private i18n: I18n
) {
super()
}
ngOnInit () {
this.initialize()
}
getIdentifier () { getIdentifier () {
return 'MyAccountBlocklistComponent' return 'MyAccountBlocklistComponent'
} }
unblockAccount (accountBlock: AccountBlock) {
const blockedAccount = accountBlock.blockedAccount
this.blocklistService.unblockAccountByUser(blockedAccount)
.subscribe(
() => {
this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost }))
this.loadData()
}
)
}
protected loadData () {
return this.blocklistService.getUserAccountBlocklist(this.pagination, this.sort)
.subscribe(
resultList => {
this.blockedAccounts = resultList.data
this.totalRecords = resultList.total
},
err => this.notifier.error(err.message)
)
}
} }

View file

@ -1,27 +0,0 @@
<div class="admin-sub-header">
<h1 i18n class="form-sub-title">Muted instances</h1>
</div>
<p-table
[value]="blockedServers" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
>
<ng-template pTemplate="header">
<tr>
<th i18n>Instance</th>
<th i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
<th></th> <!-- column for action buttons -->
</tr>
</ng-template>
<ng-template pTemplate="body" let-serverBlock>
<tr>
<td>{{ serverBlock.blockedServer.host }}</td>
<td>{{ serverBlock.createdAt }}</td>
<td class="action-cell">
<button class="unblock-button" (click)="unblockServer(serverBlock)" i18n>Unmute</button>
</td>
</tr>
</ng-template>
</p-table>

View file

@ -1,7 +0,0 @@
@import '_variables';
@import '_mixins';
.unblock-button {
@include peertube-button;
@include grey-button;
}

View file

@ -1,60 +1,15 @@
import { Component, OnInit } from '@angular/core' import { Component } from '@angular/core'
import { Notifier } from '@app/core' import { GenericServerBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { RestPagination, RestTable } from '@app/shared'
import { SortMeta } from 'primeng/api'
import { ServerBlock } from '../../../../../shared'
import { BlocklistService } from '@app/shared/blocklist'
@Component({ @Component({
selector: 'my-account-server-blocklist', selector: 'my-account-server-blocklist',
styleUrls: [ './my-account-server-blocklist.component.scss' ], styleUrls: [ '../../+admin/moderation/moderation.component.scss', '../../shared/blocklist/server-blocklist.component.scss' ],
templateUrl: './my-account-server-blocklist.component.html' templateUrl: '../../shared/blocklist/server-blocklist.component.html'
}) })
export class MyAccountServerBlocklistComponent extends RestTable implements OnInit { export class MyAccountServerBlocklistComponent extends GenericServerBlocklistComponent {
blockedServers: ServerBlock[] = [] mode = BlocklistComponentType.Account
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
private notifier: Notifier,
private blocklistService: BlocklistService,
private i18n: I18n
) {
super()
}
ngOnInit () {
this.initialize()
}
getIdentifier () { getIdentifier () {
return 'MyAccountServerBlocklistComponent' return 'MyAccountServerBlocklistComponent'
} }
unblockServer (serverBlock: ServerBlock) {
const host = serverBlock.blockedServer.host
this.blocklistService.unblockServerByUser(host)
.subscribe(
() => {
this.notifier.success(this.i18n('Instance {{host}} unmuted.', { host }))
this.loadData()
}
)
}
protected loadData () {
return this.blocklistService.getUserServerBlocklist(this.pagination, this.sort)
.subscribe(
resultList => {
this.blockedServers = resultList.data
this.totalRecords = resultList.total
},
err => this.notifier.error(err.message)
)
}
} }

View file

@ -140,7 +140,7 @@ const myAccountRoutes: Routes = [
component: MyAccountServerBlocklistComponent, component: MyAccountServerBlocklistComponent,
data: { data: {
meta: { meta: {
title: 'Muted instances' title: 'Muted servers'
} }
} }
}, },

View file

@ -72,7 +72,7 @@ export class MyAccountComponent implements OnInit {
iconName: 'user' iconName: 'user'
}, },
{ {
label: this.i18n('Muted instances'), label: this.i18n('Muted servers'),
routerLink: '/my-account/blocklist/servers', routerLink: '/my-account/blocklist/servers',
iconName: 'server' iconName: 'server'
}, },

View file

@ -0,0 +1,64 @@
<p-table
[value]="blockedAccounts" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" (onPage)="onPage($event)"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
>
<ng-template pTemplate="caption">
<div class="caption">
<div class="ml-auto has-feedback has-clear">
<input
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
(keyup)="onSearch($event)"
>
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
<span class="sr-only" i18n>Clear filters</span>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th style="width: 100%;" i18n>Account</th>
<th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
<th style="width: 150px;"></th> <!-- column for action buttons -->
</tr>
</ng-template>
<ng-template pTemplate="body" let-accountBlock>
<tr>
<td>
<a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
<div class="chip two-lines">
<img
class="avatar"
[src]="accountBlock.blockedAccount.avatar?.path"
(error)="switchToDefaultAvatar($event)"
alt="Avatar"
>
<div>
{{ accountBlock.blockedAccount.displayName }}
<span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
</div>
</div>
</a>
</td>
<td>{{ accountBlock.createdAt | date: 'short' }}</td>
<td class="action-cell">
<button class="unblock-button" (click)="unblockAccount(accountBlock)" i18n>Unmute</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="6">
<div class="no-results">
<ng-container *ngIf="search" i18n>No account found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No account found.</ng-container>
</div>
</td>
</tr>
</ng-template>
</p-table>

View file

@ -0,0 +1,16 @@
@import '_variables';
@import '_mixins';
.caption {
justify-content: flex-end;
input {
@include peertube-input-text(250px);
flex-grow: 1;
}
}
.unblock-button {
@include peertube-button;
@include grey-button;
}

View file

@ -0,0 +1,79 @@
import { OnInit } from '@angular/core'
import { Notifier } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { RestPagination, RestTable } from '@app/shared/rest'
import { SortMeta } from 'primeng/api'
import { AccountBlock } from './account-block.model'
import { BlocklistService, BlocklistComponentType } from './blocklist.service'
import { Actor } from '@app/shared/actor/actor.model'
export class GenericAccountBlocklistComponent extends RestTable implements OnInit {
// @ts-ignore: "Abstract methods can only appear within an abstract class"
abstract mode: BlocklistComponentType
blockedAccounts: AccountBlock[] = []
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
private notifier: Notifier,
private blocklistService: BlocklistService,
private i18n: I18n
) {
super()
}
// @ts-ignore: "Abstract methods can only appear within an abstract class"
abstract getIdentifier (): string
ngOnInit () {
this.initialize()
}
switchToDefaultAvatar ($event: Event) {
($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL()
}
unblockAccount (accountBlock: AccountBlock) {
const blockedAccount = accountBlock.blockedAccount
const operation = this.mode === BlocklistComponentType.Account
? this.blocklistService.unblockAccountByUser(blockedAccount)
: this.blocklistService.unblockAccountByInstance(blockedAccount)
operation.subscribe(
() => {
this.notifier.success(
this.mode === BlocklistComponentType.Account
? this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost })
: this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost })
)
this.loadData()
}
)
}
protected loadData () {
const operation = this.mode === BlocklistComponentType.Account
? this.blocklistService.getUserAccountBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
: this.blocklistService.getInstanceAccountBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
return operation.subscribe(
resultList => {
this.blockedAccounts = resultList.data
this.totalRecords = resultList.total
},
err => this.notifier.error(err.message)
)
}
}

View file

@ -8,6 +8,8 @@ import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '../
import { Account } from '@app/shared/account/account.model' import { Account } from '@app/shared/account/account.model'
import { AccountBlock } from '@app/shared/blocklist/account-block.model' import { AccountBlock } from '@app/shared/blocklist/account-block.model'
export enum BlocklistComponentType { Account, Instance }
@Injectable() @Injectable()
export class BlocklistService { export class BlocklistService {
static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist' static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist'
@ -21,10 +23,14 @@ export class BlocklistService {
/*********************** User -> Account blocklist ***********************/ /*********************** User -> Account blocklist ***********************/
getUserAccountBlocklist (pagination: RestPagination, sort: SortMeta) { getUserAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
const { pagination, sort, search } = options
let params = new HttpParams() let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort) params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
return this.authHttp.get<ResultList<AccountBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', { params }) return this.authHttp.get<ResultList<AccountBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', { params })
.pipe( .pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)), map(res => this.restExtractor.convertResultListDateToHuman(res)),
@ -49,10 +55,14 @@ export class BlocklistService {
/*********************** User -> Server blocklist ***********************/ /*********************** User -> Server blocklist ***********************/
getUserServerBlocklist (pagination: RestPagination, sort: SortMeta) { getUserServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
const { pagination, sort, search } = options
let params = new HttpParams() let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort) params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
return this.authHttp.get<ResultList<ServerBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers', { params }) return this.authHttp.get<ResultList<ServerBlock>>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers', { params })
.pipe( .pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)), map(res => this.restExtractor.convertResultListDateToHuman(res)),
@ -76,7 +86,7 @@ export class BlocklistService {
/*********************** Instance -> Account blocklist ***********************/ /*********************** Instance -> Account blocklist ***********************/
getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) { getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
const { pagination, sort, search } = options const { pagination, sort, search } = options
let params = new HttpParams() let params = new HttpParams()
@ -108,7 +118,7 @@ export class BlocklistService {
/*********************** Instance -> Server blocklist ***********************/ /*********************** Instance -> Server blocklist ***********************/
getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) { getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
const { pagination, sort, search } = options const { pagination, sort, search } = options
let params = new HttpParams() let params = new HttpParams()

View file

@ -1,2 +1,4 @@
export * from './blocklist.service' export * from './blocklist.service'
export * from './account-block.model' export * from './account-block.model'
export * from './server-blocklist.component'
export * from './account-blocklist.component'

View file

@ -47,7 +47,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="6"> <td colspan="6">
<div class="empty-table-message"> <div class="no-results">
<ng-container *ngIf="search" i18n>No server found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No server found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>No server found.</ng-container> <ng-container *ngIf="!search" i18n>No server found.</ng-container>
</div> </div>

View file

@ -15,6 +15,15 @@ a {
} }
} }
.caption {
justify-content: flex-end;
input {
@include peertube-input-text(250px);
flex-grow: 1;
}
}
.unblock-button { .unblock-button {
@include peertube-button; @include peertube-button;
@include grey-button; @include grey-button;

View file

@ -0,0 +1,101 @@
import { OnInit, ViewChild } from '@angular/core'
import { Notifier } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { RestPagination, RestTable } from '@app/shared/rest'
import { SortMeta } from 'primeng/api'
import { BlocklistService, BlocklistComponentType } from './blocklist.service'
import { ServerBlock } from '../../../../../shared/models/blocklist/server-block.model'
import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
export class GenericServerBlocklistComponent extends RestTable implements OnInit {
@ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent
// @ts-ignore: "Abstract methods can only appear within an abstract class"
public abstract mode: BlocklistComponentType
blockedServers: ServerBlock[] = []
totalRecords = 0
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
constructor (
protected notifier: Notifier,
protected blocklistService: BlocklistService,
protected i18n: I18n
) {
super()
}
ngOnInit () {
this.initialize()
}
// @ts-ignore: "Abstract methods can only appear within an abstract class"
public abstract getIdentifier (): string
unblockServer (serverBlock: ServerBlock) {
const operation = (host: string) => this.mode === BlocklistComponentType.Account
? this.blocklistService.unblockServerByUser(host)
: this.blocklistService.unblockServerByInstance(host)
const host = serverBlock.blockedServer.host
operation(host).subscribe(
() => {
this.notifier.success(
this.mode === BlocklistComponentType.Account
? this.i18n('Instance {{host}} unmuted.', { host })
: this.i18n('Instance {{host}} unmuted by your instance.', { host })
)
this.loadData()
}
)
}
addServersToBlock () {
this.batchDomainsModal.openModal()
}
onDomainsToBlock (domains: string[]) {
const operation = (domain: string) => this.mode === BlocklistComponentType.Account
? this.blocklistService.blockServerByUser(domain)
: this.blocklistService.blockServerByInstance(domain)
domains.forEach(domain => {
operation(domain).subscribe(
() => {
this.notifier.success(
this.mode === BlocklistComponentType.Account
? this.i18n('Instance {{domain}} muted.', { domain })
: this.i18n('Instance {{domain}} muted by your instance.', { domain })
)
this.loadData()
}
)
})
}
protected loadData () {
const operation = this.mode === BlocklistComponentType.Account
? this.blocklistService.getUserServerBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
: this.blocklistService.getInstanceServerBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
return operation.subscribe(
resultList => {
this.blockedServers = resultList.data
this.totalRecords = resultList.total
},
err => this.notifier.error(err.message)
)
}
}

View file

@ -10,6 +10,7 @@ import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service'
import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
@ -192,7 +193,8 @@ import { VideoService } from './video/video.service'
MyAccountVideoSettingsComponent, MyAccountVideoSettingsComponent,
MyAccountInterfaceSettingsComponent, MyAccountInterfaceSettingsComponent,
ActorAvatarInfoComponent ActorAvatarInfoComponent,
BatchDomainsModalComponent
], ],
exports: [ exports: [
@ -274,7 +276,8 @@ import { VideoService } from './video/video.service'
MyAccountVideoSettingsComponent, MyAccountVideoSettingsComponent,
MyAccountInterfaceSettingsComponent, MyAccountInterfaceSettingsComponent,
ActorAvatarInfoComponent ActorAvatarInfoComponent,
BatchDomainsModalComponent
], ],
providers: [ providers: [

View file

@ -258,6 +258,8 @@ table {
.no-results { .no-results {
height: 40vh; height: 40vh;
max-height: 500px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View file

@ -856,15 +856,6 @@
} }
} }
@mixin empty-state {
min-height: 40vh;
max-height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
@mixin admin-sub-header-responsive ($horizontal-margins) { @mixin admin-sub-header-responsive ($horizontal-margins) {
flex-direction: column; flex-direction: column;