import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { VideoRedundancyInformationComponent } from '@app/+admin/follows/video-redundancies-list/video-redundancy-information.component'
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'
@NgModule({
DebugComponent,
ConfigComponent,
- EditCustomConfigComponent,
-
- BatchDomainsModalComponent
+ EditCustomConfigComponent
],
exports: [
<ng-template pTemplate="emptymessage">
<tr>
<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>Your instance doesn't have any follower.</ng-container>
</div>
<ng-template pTemplate="emptymessage">
<tr>
<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>Your instance is not following anyone.</ng-container>
</div>
flex-grow: 0;
margin-right: 30px;
}
-
-.empty-table-message {
- @include empty-state;
-}
<ng-template pTemplate="emptymessage">
<tr>
<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 has no mirrored videos.</ng-container>
</div>
<ng-template pTemplate="emptymessage">
<tr>
<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.</ng-container>
</div>
-import { Component, OnInit } from '@angular/core'
-import { Notifier } from '@app/core'
-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'
+import { Component } from '@angular/core'
+import { GenericAccountBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
@Component({
selector: 'my-instance-account-blocklist',
- styleUrls: [ '../moderation.component.scss', './instance-account-blocklist.component.scss' ],
- templateUrl: './instance-account-blocklist.component.html'
+ styleUrls: [ '../moderation.component.scss', '../../../shared/blocklist/account-blocklist.component.scss' ],
+ templateUrl: '../../../shared/blocklist/account-blocklist.component.html'
})
-export class InstanceAccountBlocklistComponent extends RestTable implements OnInit {
- 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()
- }
-
- ngOnInit () {
- this.initialize()
- }
+export class InstanceAccountBlocklistComponent extends GenericAccountBlocklistComponent {
+ mode = BlocklistComponentType.Instance
getIdentifier () {
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)
- )
- }
}
+++ /dev/null
-<p-table
- [value]="blockedServers" [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 instances"
->
- <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>
- <a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()">
- <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
- <ng-container i18n>Mute domain</ng-container>
- </a>
- </div>
- </ng-template>
-
- <ng-template pTemplate="header">
- <tr>
- <th style="width: 100%;" i18n>Instance</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-serverBlock>
- <tr>
- <td>
- <a [href]="'https://' + serverBlock.blockedServer.host" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
- {{ serverBlock.blockedServer.host }}
- <span class="glyphicon glyphicon-new-window"></span>
- </a>
- </td>
- <td>{{ serverBlock.createdAt | date: 'short' }}</td>
- <td class="action-cell">
- <button class="unblock-button" (click)="unblockServer(serverBlock)" i18n>Unmute</button>
- </td>
- </tr>
- </ng-template>
-
- <ng-template pTemplate="emptymessage">
- <tr>
- <td colspan="6">
- <div class="empty-table-message">
- <ng-container *ngIf="search" i18n>No server found matching current filters.</ng-container>
- <ng-container *ngIf="!search" i18n>No server found.</ng-container>
- </div>
- </td>
- </tr>
- </ng-template>
-</p-table>
-
-<my-batch-domains-modal #batchDomainsModal i18n-action action="Mute domains" (domains)="onDomainsToBlock($event)"></my-batch-domains-modal>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-a {
- @include disable-default-a-behaviour;
- display: inline-block;
-
- &, &:hover {
- color: pvar(--mainForegroundColor);
- }
-
- span {
- font-size: 80%;
- color: pvar(--inputPlaceholderColor);
- }
-}
-
-.unblock-button {
- @include peertube-button;
- @include grey-button;
-}
-
-.block-button {
- @include create-button;
-}
-import { Component, OnInit, ViewChild } from '@angular/core'
-import { Notifier } from '@app/core'
-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'
+import { Component } from '@angular/core'
+import { GenericServerBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
@Component({
selector: 'my-instance-server-blocklist',
- styleUrls: [ '../moderation.component.scss', './instance-server-blocklist.component.scss' ],
- templateUrl: './instance-server-blocklist.component.html'
+ styleUrls: [ '../../../shared/blocklist/server-blocklist.component.scss' ],
+ templateUrl: '../../../shared/blocklist/server-blocklist.component.html'
})
-export class InstanceServerBlocklistComponent extends RestTable implements OnInit {
- @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent
-
- 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()
- }
+export class InstanceServerBlocklistComponent extends GenericServerBlocklistComponent {
+ mode = BlocklistComponentType.Instance
getIdentifier () {
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)
- )
- }
}
}
}
-.empty-table-message {
- @include empty-state;
-}
-
.moderation-expanded {
font-size: 90%;
<ng-template pTemplate="emptymessage">
<tr>
<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.</ng-container>
</div>
<ng-template pTemplate="emptymessage">
<tr>
<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.</ng-container>
</div>
+++ /dev/null
-<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>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.unblock-button {
- @include peertube-button;
- @include grey-button;
-}
\ No newline at end of file
-import { Component, OnInit } from '@angular/core'
-import { Notifier } from '@app/core'
-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 { Component } from '@angular/core'
+import { GenericAccountBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
@Component({
selector: 'my-account-blocklist',
- styleUrls: [ './my-account-blocklist.component.scss' ],
- templateUrl: './my-account-blocklist.component.html'
+ styleUrls: [ '../../shared/blocklist/account-blocklist.component.scss' ],
+ templateUrl: '../../shared/blocklist/account-blocklist.component.html'
})
-export class MyAccountBlocklistComponent extends RestTable implements OnInit {
- 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()
- }
-
- ngOnInit () {
- this.initialize()
- }
+export class MyAccountBlocklistComponent extends GenericAccountBlocklistComponent {
+ mode = BlocklistComponentType.Account
getIdentifier () {
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)
- )
- }
}
+++ /dev/null
-<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>
+++ /dev/null
-@import '_variables';
-@import '_mixins';
-
-.unblock-button {
- @include peertube-button;
- @include grey-button;
-}
\ No newline at end of file
-import { Component, OnInit } from '@angular/core'
-import { Notifier } from '@app/core'
-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'
+import { Component } from '@angular/core'
+import { GenericServerBlocklistComponent, BlocklistComponentType } from '@app/shared/blocklist'
@Component({
selector: 'my-account-server-blocklist',
- styleUrls: [ './my-account-server-blocklist.component.scss' ],
- templateUrl: './my-account-server-blocklist.component.html'
+ styleUrls: [ '../../+admin/moderation/moderation.component.scss', '../../shared/blocklist/server-blocklist.component.scss' ],
+ templateUrl: '../../shared/blocklist/server-blocklist.component.html'
})
-export class MyAccountServerBlocklistComponent extends RestTable implements OnInit {
- 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()
- }
+export class MyAccountServerBlocklistComponent extends GenericServerBlocklistComponent {
+ mode = BlocklistComponentType.Account
getIdentifier () {
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)
- )
- }
}
component: MyAccountServerBlocklistComponent,
data: {
meta: {
- title: 'Muted instances'
+ title: 'Muted servers'
}
}
},
iconName: 'user'
},
{
- label: this.i18n('Muted instances'),
+ label: this.i18n('Muted servers'),
routerLink: '/my-account/blocklist/servers',
iconName: 'server'
},
--- /dev/null
+<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>
--- /dev/null
+@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;
+}
\ No newline at end of file
--- /dev/null
+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)
+ )
+ }
+}
import { Account } from '@app/shared/account/account.model'
import { AccountBlock } from '@app/shared/blocklist/account-block.model'
+export enum BlocklistComponentType { Account, Instance }
+
@Injectable()
export class BlocklistService {
static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/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()
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 })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),
/*********************** 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()
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 })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),
/*********************** Instance -> Account blocklist ***********************/
- getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) {
+ getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
const { pagination, sort, search } = options
let params = new HttpParams()
/*********************** Instance -> Server blocklist ***********************/
- getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) {
+ getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search?: string }) {
const { pagination, sort, search } = options
let params = new HttpParams()
export * from './blocklist.service'
export * from './account-block.model'
+export * from './server-blocklist.component'
+export * from './account-blocklist.component'
--- /dev/null
+<p-table
+ [value]="blockedServers" [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 instances"
+>
+ <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>
+ <a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()">
+ <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
+ <ng-container i18n>Mute domain</ng-container>
+ </a>
+ </div>
+ </ng-template>
+
+ <ng-template pTemplate="header">
+ <tr>
+ <th style="width: 100%;" i18n>Instance</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-serverBlock>
+ <tr>
+ <td>
+ <a [href]="'https://' + serverBlock.blockedServer.host" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
+ {{ serverBlock.blockedServer.host }}
+ <span class="glyphicon glyphicon-new-window"></span>
+ </a>
+ </td>
+ <td>{{ serverBlock.createdAt | date: 'short' }}</td>
+ <td class="action-cell">
+ <button class="unblock-button" (click)="unblockServer(serverBlock)" 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 server found matching current filters.</ng-container>
+ <ng-container *ngIf="!search" i18n>No server found.</ng-container>
+ </div>
+ </td>
+ </tr>
+ </ng-template>
+</p-table>
+
+<my-batch-domains-modal #batchDomainsModal i18n-action action="Mute domains" (domains)="onDomainsToBlock($event)"></my-batch-domains-modal>
--- /dev/null
+@import '_variables';
+@import '_mixins';
+
+a {
+ @include disable-default-a-behaviour;
+ display: inline-block;
+
+ &, &:hover {
+ color: pvar(--mainForegroundColor);
+ }
+
+ span {
+ font-size: 80%;
+ color: pvar(--inputPlaceholderColor);
+ }
+}
+
+.caption {
+ justify-content: flex-end;
+
+ input {
+ @include peertube-input-text(250px);
+ flex-grow: 1;
+ }
+}
+
+.unblock-button {
+ @include peertube-button;
+ @include grey-button;
+}
+
+.block-button {
+ @include create-button;
+}
--- /dev/null
+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)
+ )
+ }
+}
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router'
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 { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
MyAccountVideoSettingsComponent,
MyAccountInterfaceSettingsComponent,
- ActorAvatarInfoComponent
+ ActorAvatarInfoComponent,
+ BatchDomainsModalComponent
],
exports: [
MyAccountVideoSettingsComponent,
MyAccountInterfaceSettingsComponent,
- ActorAvatarInfoComponent
+ ActorAvatarInfoComponent,
+ BatchDomainsModalComponent
],
providers: [
.no-results {
height: 40vh;
+ max-height: 500px;
+
display: flex;
align-items: center;
justify-content: center;
}
}
-@mixin empty-state {
- min-height: 40vh;
- max-height: 500px;
-
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
@mixin admin-sub-header-responsive ($horizontal-margins) {
flex-direction: column;