From bb152476c819e4c7487d080433c616f0d523e049 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Tue, 14 Apr 2020 10:55:34 +0200 Subject: [PATCH] Refactor follow/mute as modals in admin, add actions in abuse list --- client/src/app/+admin/admin.module.ts | 8 +- .../shared/batch-domains-modal.component.html | 43 ++++++++++ .../shared/batch-domains-modal.component.scss | 3 + .../shared/batch-domains-modal.component.ts | 54 ++++++++++++ .../batch-domains-validators.service.ts | 68 +++++++++++++++ .../followers-list.component.ts | 2 +- .../following-add.component.html | 22 ----- .../following-add.component.scss | 11 --- .../following-add/following-add.component.ts | 85 ------------------- .../app/+admin/follows/following-add/index.ts | 1 - .../following-list.component.html | 8 +- .../following-list.component.scss | 6 +- .../following-list.component.ts | 22 ++++- .../app/+admin/follows/follows.component.html | 2 - .../src/app/+admin/follows/follows.routes.ts | 8 +- client/src/app/+admin/follows/index.ts | 1 - .../instance-server-blocklist.component.html | 16 ++++ .../instance-server-blocklist.component.scss | 6 +- .../instance-server-blocklist.component.ts | 26 +++++- .../video-abuse-list.component.html | 4 +- .../video-abuse-list.component.ts | 84 ++++++++++++------ .../video-blacklist-list.component.ts | 2 +- .../buttons/action-dropdown.component.html | 16 +++- .../buttons/action-dropdown.component.scss | 4 + .../buttons/action-dropdown.component.ts | 1 + client/src/app/shared/shared.module.ts | 2 + .../comment/video-comment-add.component.ts | 2 - server/middlewares/validators/blocklist.ts | 6 +- server/tests/api/check-params/blocklist.ts | 12 +-- 29 files changed, 341 insertions(+), 184 deletions(-) create mode 100644 client/src/app/+admin/config/shared/batch-domains-modal.component.html create mode 100644 client/src/app/+admin/config/shared/batch-domains-modal.component.scss create mode 100644 client/src/app/+admin/config/shared/batch-domains-modal.component.ts create mode 100644 client/src/app/+admin/config/shared/batch-domains-validators.service.ts delete mode 100644 client/src/app/+admin/follows/following-add/following-add.component.html delete mode 100644 client/src/app/+admin/follows/following-add/following-add.component.scss delete mode 100644 client/src/app/+admin/follows/following-add/following-add.component.ts delete mode 100644 client/src/app/+admin/follows/following-add/index.ts diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index fdbe70314..16273f6d8 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts @@ -5,7 +5,7 @@ import { TableModule } from 'primeng/table' import { SharedModule } from '../shared' import { AdminRoutingModule } from './admin-routing.module' import { AdminComponent } from './admin.component' -import { FollowersListComponent, FollowingAddComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows' +import { FollowersListComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows' import { FollowingListComponent } from './follows/following-list/following-list.component' import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users' import { @@ -28,6 +28,7 @@ import { SelectButtonModule } from 'primeng/selectbutton' 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' @NgModule({ imports: [ @@ -44,7 +45,6 @@ import { ChartModule } from 'primeng/chart' AdminComponent, FollowsComponent, - FollowingAddComponent, FollowersListComponent, FollowingListComponent, RedundancyCheckboxComponent, @@ -76,7 +76,9 @@ import { ChartModule } from 'primeng/chart' DebugComponent, ConfigComponent, - EditCustomConfigComponent + EditCustomConfigComponent, + + BatchDomainsModalComponent ], exports: [ diff --git a/client/src/app/+admin/config/shared/batch-domains-modal.component.html b/client/src/app/+admin/config/shared/batch-domains-modal.component.html new file mode 100644 index 000000000..1b85c8f48 --- /dev/null +++ b/client/src/app/+admin/config/shared/batch-domains-modal.component.html @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/src/app/+admin/config/shared/batch-domains-modal.component.scss b/client/src/app/+admin/config/shared/batch-domains-modal.component.scss new file mode 100644 index 000000000..9621a566f --- /dev/null +++ b/client/src/app/+admin/config/shared/batch-domains-modal.component.scss @@ -0,0 +1,3 @@ +textarea { + height: 200px; +} diff --git a/client/src/app/+admin/config/shared/batch-domains-modal.component.ts b/client/src/app/+admin/config/shared/batch-domains-modal.component.ts new file mode 100644 index 000000000..620f2726b --- /dev/null +++ b/client/src/app/+admin/config/shared/batch-domains-modal.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from '@angular/core' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { FormReactive } from '@app/shared/forms' +import { BatchDomainsValidatorsService } from './batch-domains-validators.service' + +@Component({ + selector: 'my-batch-domains-modal', + templateUrl: './batch-domains-modal.component.html', + styleUrls: [ './batch-domains-modal.component.scss' ] +}) +export class BatchDomainsModalComponent extends FormReactive implements OnInit { + @ViewChild('modal', { static: true }) modal: NgbModal + @Input() placeholder = 'example.com' + @Input() action: string + @Output() domains = new EventEmitter() + + private openedModal: NgbModalRef + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private batchDomainsValidatorsService: BatchDomainsValidatorsService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + if (!this.action) this.action = this.i18n('Process domains') + + this.buildForm({ + domains: this.batchDomainsValidatorsService.DOMAINS + }) + } + + openModal () { + this.openedModal = this.modalService.open(this.modal, { centered: true }) + } + + hide () { + this.openedModal.close() + } + + submit () { + this.domains.emit( + this.batchDomainsValidatorsService.getNotEmptyHosts(this.form.controls['domains'].value) + ) + this.form.reset() + this.hide() + } +} diff --git a/client/src/app/+admin/config/shared/batch-domains-validators.service.ts b/client/src/app/+admin/config/shared/batch-domains-validators.service.ts new file mode 100644 index 000000000..154ef3a23 --- /dev/null +++ b/client/src/app/+admin/config/shared/batch-domains-validators.service.ts @@ -0,0 +1,68 @@ +import { I18n } from '@ngx-translate/i18n-polyfill' +import { Validators, ValidatorFn } from '@angular/forms' +import { Injectable } from '@angular/core' +import { BuildFormValidator, validateHost } from '@app/shared' + +@Injectable() +export class BatchDomainsValidatorsService { + readonly DOMAINS: BuildFormValidator + + constructor (private i18n: I18n) { + this.DOMAINS = { + VALIDATORS: [ Validators.required, this.validDomains, this.isHostsUnique ], + MESSAGES: { + 'required': this.i18n('Domain is required.'), + 'validDomains': this.i18n('Domains entered are invalid.'), + 'uniqueDomains': this.i18n('Domains entered contain duplicates.') + } + } + } + + getNotEmptyHosts (hosts: string) { + return hosts + .split('\n') + .filter((host: string) => host && host.length !== 0) // Eject empty hosts + } + + private validDomains: ValidatorFn = (control) => { + if (!control.value) return null + + const newHostsErrors = [] + const hosts = this.getNotEmptyHosts(control.value) + + for (const host of hosts) { + if (validateHost(host) === false) { + newHostsErrors.push(this.i18n('{{host}} is not valid', { host })) + } + } + + /* Is not valid. */ + if (newHostsErrors.length !== 0) { + return { + 'validDomains': { + reason: 'invalid', + value: newHostsErrors.join('. ') + '.' + } + } + } + + /* Is valid. */ + return null + } + + private isHostsUnique: ValidatorFn = (control) => { + if (!control.value) return null + + const hosts = this.getNotEmptyHosts(control.value) + + if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) { + return null + } else { + return { + 'uniqueDomains': { + reason: 'invalid' + } + } + } + } +} diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts index aff59a691..585902827 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts @@ -15,7 +15,7 @@ export class FollowersListComponent extends RestTable implements OnInit { followers: ActorFollow[] = [] totalRecords = 0 rowsPerPage = 10 - sort: SortMeta = { field: 'createdAt', order: 1 } + sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( diff --git a/client/src/app/+admin/follows/following-add/following-add.component.html b/client/src/app/+admin/follows/following-add/following-add.component.html deleted file mode 100644 index e08decb3f..000000000 --- a/client/src/app/+admin/follows/following-add/following-add.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
{{ error }}
- -
-
- - - - -
- {{ hostsError }} -
-
- -
- It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers. -
- - -
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.scss b/client/src/app/+admin/follows/following-add/following-add.component.scss deleted file mode 100644 index 7594b502c..000000000 --- a/client/src/app/+admin/follows/following-add/following-add.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -textarea { - height: 250px; -} - -input[type=submit] { - @include peertube-button; - @include orange-button; -} diff --git a/client/src/app/+admin/follows/following-add/following-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts deleted file mode 100644 index 308bbb0c5..000000000 --- a/client/src/app/+admin/follows/following-add/following-add.component.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Component } from '@angular/core' -import { Router } from '@angular/router' -import { Notifier } from '@app/core' -import { ConfirmService } from '../../../core' -import { validateHost } from '../../../shared' -import { FollowService } from '@app/shared/instance/follow.service' -import { I18n } from '@ngx-translate/i18n-polyfill' - -@Component({ - selector: 'my-following-add', - templateUrl: './following-add.component.html', - styleUrls: [ './following-add.component.scss' ] -}) -export class FollowingAddComponent { - hostsString = '' - hostsError: string = null - error: string = null - - constructor ( - private router: Router, - private notifier: Notifier, - private confirmService: ConfirmService, - private followService: FollowService, - private i18n: I18n - ) {} - - httpEnabled () { - return window.location.protocol === 'https:' - } - - onHostsChanged () { - this.hostsError = null - - const newHostsErrors = [] - const hosts = this.getNotEmptyHosts() - - for (const host of hosts) { - if (validateHost(host) === false) { - newHostsErrors.push(this.i18n('{{host}} is not valid', { host })) - } - } - - if (newHostsErrors.length !== 0) { - this.hostsError = newHostsErrors.join('. ') - } - } - - async addFollowing () { - this.error = '' - - const hosts = this.getNotEmptyHosts() - if (hosts.length === 0) { - this.error = this.i18n('You need to specify hosts to follow.') - } - - if (!this.isHostsUnique(hosts)) { - this.error = this.i18n('Hosts need to be unique.') - return - } - - const confirmMessage = this.i18n('If you confirm, you will send a follow request to:
- ') + hosts.join('
- ') - const res = await this.confirmService.confirm(confirmMessage, this.i18n('Follow new server(s)')) - if (res === false) return - - this.followService.follow(hosts).subscribe( - () => { - this.notifier.success(this.i18n('Follow request(s) sent!')) - - setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500) - }, - - err => this.notifier.error(err.message) - ) - } - - private isHostsUnique (hosts: string[]) { - return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host)) - } - - private getNotEmptyHosts () { - return this.hostsString - .split('\n') - .filter(host => host && host.length !== 0) // Eject empty hosts - } -} diff --git a/client/src/app/+admin/follows/following-add/index.ts b/client/src/app/+admin/follows/following-add/index.ts deleted file mode 100644 index 1b1897ffa..000000000 --- a/client/src/app/+admin/follows/following-add/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './following-add.component' diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html index 01aba0c11..cb62d52dd 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.html +++ b/client/src/app/+admin/follows/following-list/following-list.component.html @@ -4,12 +4,16 @@ >
-
+
+ + + Follow domain +
@@ -42,3 +46,5 @@ + + diff --git a/client/src/app/+admin/follows/following-list/following-list.component.scss b/client/src/app/+admin/follows/following-list/following-list.component.scss index a6f0656b8..f4656b88d 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.scss +++ b/client/src/app/+admin/follows/following-list/following-list.component.scss @@ -7,4 +7,8 @@ input { @include peertube-input-text(250px); } -} \ No newline at end of file +} + +.follow-button { + @include create-button; +} diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts index dd7629ead..477a6c0d7 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.ts +++ b/client/src/app/+admin/follows/following-list/following-list.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core' +import { Component, OnInit, ViewChild } from '@angular/core' import { Notifier } from '@app/core' import { SortMeta } from 'primeng/api' import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' @@ -6,6 +6,7 @@ import { ConfirmService } from '../../../core/confirm/confirm.service' import { RestPagination, RestTable } from '../../../shared' import { FollowService } from '@app/shared/instance/follow.service' import { I18n } from '@ngx-translate/i18n-polyfill' +import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component' @Component({ selector: 'my-followers-list', @@ -13,10 +14,12 @@ import { I18n } from '@ngx-translate/i18n-polyfill' styleUrls: [ './following-list.component.scss' ] }) export class FollowingListComponent extends RestTable implements OnInit { + @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent + following: ActorFollow[] = [] totalRecords = 0 rowsPerPage = 10 - sort: SortMeta = { field: 'createdAt', order: 1 } + sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( @@ -36,6 +39,21 @@ export class FollowingListComponent extends RestTable implements OnInit { return 'FollowingListComponent' } + addDomainsToFollow () { + this.batchDomainsModal.openModal() + } + + async addFollowing (hosts: string[]) { + this.followService.follow(hosts).subscribe( + () => { + this.notifier.success(this.i18n('Follow request(s) sent!')) + this.loadData() + }, + + err => this.notifier.error(err.message) + ) + } + async removeFollowing (follow: ActorFollow) { const res = await this.confirmService.confirm( this.i18n('Do you really want to unfollow {{host}}?', { host: follow.following.host }), diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html index 46581daf9..7b5bcc2db 100644 --- a/client/src/app/+admin/follows/follows.component.html +++ b/client/src/app/+admin/follows/follows.component.html @@ -4,8 +4,6 @@
Following - Follow - Followers Video redundancies diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts index 298733eb0..8270ae444 100644 --- a/client/src/app/+admin/follows/follows.routes.ts +++ b/client/src/app/+admin/follows/follows.routes.ts @@ -2,7 +2,6 @@ import { Routes } from '@angular/router' import { UserRightGuard } from '../../core' import { FollowsComponent } from './follows.component' -import { FollowingAddComponent } from './following-add' import { FollowersListComponent } from './followers-list' import { UserRight } from '../../../../../shared' import { FollowingListComponent } from './following-list/following-list.component' @@ -42,12 +41,7 @@ export const FollowsRoutes: Routes = [ }, { path: 'following-add', - component: FollowingAddComponent, - data: { - meta: { - title: 'Add follow' - } - } + redirectTo: 'following-list' }, { path: 'video-redundancies-list', diff --git a/client/src/app/+admin/follows/index.ts b/client/src/app/+admin/follows/index.ts index 4fcb35cb1..285955468 100644 --- a/client/src/app/+admin/follows/index.ts +++ b/client/src/app/+admin/follows/index.ts @@ -1,4 +1,3 @@ -export * from './following-add' export * from './followers-list' export * from './following-list' export * from './video-redundancies-list' diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html index 44c5c2fb8..0e072d84b 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html @@ -4,6 +4,14 @@ [showCurrentPageReport]="true" i18n-currentPageReportTemplate currentPageReportTemplate="Showing {first} to {last} of {totalRecords} muted instances" > + + + @@ -23,3 +31,11 @@ + + + +
+ It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers. +
+
+
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss index 6028b75ea..9d3bedd80 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.scss @@ -4,4 +4,8 @@ .unblock-button { @include peertube-button; @include grey-button; -} \ No newline at end of file +} + +.block-button { + @include create-button; +} diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts index 5af6d8f76..431729ef2 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts @@ -1,10 +1,11 @@ -import { Component, OnInit } from '@angular/core' +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' @Component({ selector: 'my-instance-server-blocklist', @@ -12,6 +13,8 @@ import { ServerBlock } from '../../../../../../shared' templateUrl: './instance-server-blocklist.component.html' }) export class InstanceServerBlocklistComponent extends RestTable implements OnInit { + @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent + blockedServers: ServerBlock[] = [] totalRecords = 0 rowsPerPage = 10 @@ -47,6 +50,27 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni ) } + httpEnabled () { + return window.location.protocol === 'https:' + } + + 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(this.pagination, this.sort) .subscribe( diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 155d10dda..3899ee07f 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html @@ -48,13 +48,13 @@ - + - + diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index b135792a7..5e48cf24f 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts @@ -1,9 +1,9 @@ import { Component, OnInit, ViewChild } from '@angular/core' -import { Account } from '../../../shared/account/account.model' +import { Account } from '@app/shared/account/account.model' import { Notifier } from '@app/core' import { SortMeta } from 'primeng/api' import { VideoAbuse, VideoAbuseState } from '../../../../../../shared' -import { RestPagination, RestTable, VideoAbuseService } from '../../../shared' +import { RestPagination, RestTable, VideoAbuseService, VideoBlacklistService } from '../../../shared' import { I18n } from '@ngx-translate/i18n-polyfill' import { DropdownAction } from '../../../shared/buttons/action-dropdown.component' import { ConfirmService } from '../../../core/index' @@ -14,6 +14,7 @@ import { Actor } from '@app/shared/actor/actor.model' import { buildVideoLink, buildVideoEmbed } from 'src/assets/player/utils' import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' import { DomSanitizer } from '@angular/platform-browser' +import { BlocklistService } from '@app/shared/blocklist' @Component({ selector: 'my-video-abuse-list', @@ -29,11 +30,13 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { sort: SortMeta = { field: 'createdAt', order: 1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } - videoAbuseActions: DropdownAction[] = [] + videoAbuseActions: DropdownAction[][] = [] constructor ( private notifier: Notifier, private videoAbuseService: VideoAbuseService, + private blocklistService: BlocklistService, + private videoBlacklistService: VideoBlacklistService, private confirmService: ConfirmService, private i18n: I18n, private markdownRenderer: MarkdownService, @@ -42,30 +45,57 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { super() this.videoAbuseActions = [ - { - label: this.i18n('Delete this report'), - handler: videoAbuse => this.removeVideoAbuse(videoAbuse) - }, - { - label: this.i18n('Add note'), - handler: videoAbuse => this.openModerationCommentModal(videoAbuse), - isDisplayed: videoAbuse => !videoAbuse.moderationComment - }, - { - label: this.i18n('Update note'), - handler: videoAbuse => this.openModerationCommentModal(videoAbuse), - isDisplayed: videoAbuse => !!videoAbuse.moderationComment - }, - { - label: this.i18n('Mark as accepted'), - handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), - isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) - }, - { - label: this.i18n('Mark as rejected'), - handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), - isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) - } + [ + { + label: this.i18n('Internal actions'), + isHeader: true + }, + { + label: this.i18n('Delete report'), + handler: videoAbuse => this.removeVideoAbuse(videoAbuse) + }, + { + label: this.i18n('Add note'), + handler: videoAbuse => this.openModerationCommentModal(videoAbuse), + isDisplayed: videoAbuse => !videoAbuse.moderationComment + }, + { + label: this.i18n('Update note'), + handler: videoAbuse => this.openModerationCommentModal(videoAbuse), + isDisplayed: videoAbuse => !!videoAbuse.moderationComment + }, + { + label: this.i18n('Mark as accepted'), + handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), + isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) + }, + { + label: this.i18n('Mark as rejected'), + handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), + isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) + } + ], + [ + { + label: this.i18n('Actions for the video'), + isHeader: true + }, + { + label: this.i18n('Blacklist video'), + handler: videoAbuse => { + this.videoBlacklistService.blacklistVideo(videoAbuse.video.id, undefined, true) + .subscribe( + () => { + this.notifier.success(this.i18n('Video blacklisted.')) + + this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) + }, + + err => this.notifier.error(err.message) + ) + } + } + ] ] } diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts index 43b6863af..4e9965bee 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts @@ -18,7 +18,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit { blacklist: (VideoBlacklist & { reasonHtml?: string })[] = [] totalRecords = 0 rowsPerPage = 10 - sort: SortMeta = { field: 'createdAt', order: 1 } + sort: SortMeta = { field: 'createdAt', order: -1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } listBlacklistTypeFilter: VideoBlacklistType = undefined diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html index cd993db9f..14cfe9a22 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.html +++ b/client/src/app/shared/buttons/action-dropdown.component.html @@ -24,17 +24,27 @@
- + + + diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/buttons/action-dropdown.component.scss index 442c90984..7a030f32c 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.scss +++ b/client/src/app/shared/buttons/action-dropdown.component.scss @@ -51,6 +51,10 @@ } .dropdown-menu { + .dropdown-header { + padding: 0.2rem 1rem; + } + .dropdown-item { display: flex; cursor: pointer; diff --git a/client/src/app/shared/buttons/action-dropdown.component.ts b/client/src/app/shared/buttons/action-dropdown.component.ts index 6649b092a..8fcaa38b9 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.ts +++ b/client/src/app/shared/buttons/action-dropdown.component.ts @@ -9,6 +9,7 @@ export type DropdownAction = { handler?: (a: T) => any linkBuilder?: (a: T) => (string | number)[] isDisplayed?: (a: T) => boolean + isHeader?: boolean } export type DropdownButtonSize = 'normal' | 'small' diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index a952880a6..01735c187 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -107,6 +107,7 @@ import { InputSwitchModule } from 'primeng/inputswitch' import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' +import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' @NgModule({ imports: [ @@ -297,6 +298,7 @@ import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-i LoginValidatorsService, ResetPasswordValidatorsService, UserValidatorsService, + BatchDomainsValidatorsService, VideoPlaylistValidatorsService, VideoAbuseValidatorsService, VideoChannelValidatorsService, diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts index 0f7c19765..e1a8f6260 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts @@ -11,7 +11,6 @@ import { VideoCommentService } from './video-comment.service' import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' import { VideoCommentValidatorsService } from '@app/shared/forms/form-validators/video-comment-validators.service' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { AuthService } from '@app/core/auth' @Component({ selector: 'my-video-comment-add', @@ -38,7 +37,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { private videoCommentValidatorsService: VideoCommentValidatorsService, private notifier: Notifier, private videoCommentService: VideoCommentService, - private authService: AuthService, private modalService: NgbModal, private router: Router ) { diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index 47a0b1a1c..b2183437c 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts @@ -84,11 +84,9 @@ const blockServerValidator = [ .end() } - const server = await ServerModel.loadByHost(host) + let server = await ServerModel.loadByHost(host) if (!server) { - return res.status(404) - .send({ error: 'Server host not found.' }) - .end() + server = await ServerModel.create({ host }) } res.locals.server = server diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts index fb459f756..1219ec9bd 100644 --- a/server/tests/api/check-params/blocklist.ts +++ b/server/tests/api/check-params/blocklist.ts @@ -175,13 +175,13 @@ describe('Test blocklist API validators', function () { }) }) - it('Should fail with an unknown server', async function () { + it('Should succeed with an unknown server', async function () { await makePostBodyRequest({ url: server.url, token: server.accessToken, path, fields: { host: 'localhost:9003' }, - statusCodeExpected: 404 + statusCodeExpected: 204 }) }) @@ -218,7 +218,7 @@ describe('Test blocklist API validators', function () { it('Should fail with an unknown server block', async function () { await makeDeleteRequest({ url: server.url, - path: path + '/localhost:9003', + path: path + '/localhost:9004', token: server.accessToken, statusCodeExpected: 404 }) @@ -415,13 +415,13 @@ describe('Test blocklist API validators', function () { }) }) - it('Should fail with an unknown server', async function () { + it('Should succeed with an unknown server', async function () { await makePostBodyRequest({ url: server.url, token: server.accessToken, path, fields: { host: 'localhost:9003' }, - statusCodeExpected: 404 + statusCodeExpected: 204 }) }) @@ -467,7 +467,7 @@ describe('Test blocklist API validators', function () { it('Should fail with an unknown server block', async function () { await makeDeleteRequest({ url: server.url, - path: path + '/localhost:9003', + path: path + '/localhost:9004', token: server.accessToken, statusCodeExpected: 404 })