aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-11-17 11:18:49 +0100
committerChocobozzz <me@florianbigard.com>2021-11-17 11:18:49 +0100
commit3cfa817672657df18260ece5b354efa0f3b6e317 (patch)
tree318a7113fac4fcf1e6d0f7888cda1939aeefc500
parent4bdff96d77c03e5cce6052188f69a65bf6ea5781 (diff)
downloadPeerTube-3cfa817672657df18260ece5b354efa0f3b6e317.tar.gz
PeerTube-3cfa817672657df18260ece5b354efa0f3b6e317.tar.zst
PeerTube-3cfa817672657df18260ece5b354efa0f3b6e317.zip
Add ability to bulk block videos
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts2
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.html2
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts36
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.html4
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts2
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.html24
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.ts51
-rw-r--r--client/src/app/shared/shared-moderation/video-block.service.ts24
-rw-r--r--client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html2
-rw-r--r--client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts2
10 files changed, 110 insertions, 39 deletions
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index dca746f4e..67752c15a 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -64,7 +64,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
64 label: $localize`Switch video block to manual`, 64 label: $localize`Switch video block to manual`,
65 handler: videoBlock => { 65 handler: videoBlock => {
66 this.videoBlocklistService.unblockVideo(videoBlock.video.id).pipe( 66 this.videoBlocklistService.unblockVideo(videoBlock.video.id).pipe(
67 switchMap(_ => this.videoBlocklistService.blockVideo(videoBlock.video.id, undefined, true)) 67 switchMap(_ => this.videoBlocklistService.blockVideo([ { videoId: videoBlock.video.id, unfederate: true } ]))
68 ).subscribe({ 68 ).subscribe({
69 next: () => { 69 next: () => {
70 this.notifier.success($localize`Video ${videoBlock.video.name} switched to manual block.`) 70 this.notifier.success($localize`Video ${videoBlock.video.name} switched to manual block.`)
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html
index 6b0dc3abd..9b536ec11 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.html
+++ b/client/src/app/+admin/overview/videos/video-list.component.html
@@ -126,3 +126,5 @@
126 </tr> 126 </tr>
127 </ng-template> 127 </ng-template>
128</p-table> 128</p-table>
129
130<my-video-block #videoBlockModal (videoBlocked)="onVideoBlocked()"></my-video-block>
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
index 0f98a5d33..7f268bb23 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.ts
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -1,10 +1,11 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { finalize } from 'rxjs/operators' 2import { finalize } from 'rxjs/operators'
3import { Component, OnInit } from '@angular/core' 3import { Component, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' 5import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
6import { AdvancedInputFilter } from '@app/shared/shared-forms' 6import { AdvancedInputFilter } from '@app/shared/shared-forms'
7import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 7import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
8import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
8import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature' 9import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
9import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models' 10import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
10import { VideoAdminService } from './video-admin.service' 11import { VideoAdminService } from './video-admin.service'
@@ -15,6 +16,8 @@ import { VideoAdminService } from './video-admin.service'
15 styleUrls: [ './video-list.component.scss' ] 16 styleUrls: [ './video-list.component.scss' ]
16}) 17})
17export class VideoListComponent extends RestTable implements OnInit { 18export class VideoListComponent extends RestTable implements OnInit {
19 @ViewChild('videoBlockModal') videoBlockModal: VideoBlockComponent
20
18 videos: Video[] = [] 21 videos: Video[] = []
19 22
20 totalRecords = 0 23 totalRecords = 0
@@ -48,7 +51,8 @@ export class VideoListComponent extends RestTable implements OnInit {
48 private auth: AuthService, 51 private auth: AuthService,
49 private notifier: Notifier, 52 private notifier: Notifier,
50 private videoService: VideoService, 53 private videoService: VideoService,
51 private videoAdminService: VideoAdminService 54 private videoAdminService: VideoAdminService,
55 private videoBlockService: VideoBlockService
52 ) { 56 ) {
53 super() 57 super()
54 } 58 }
@@ -68,6 +72,16 @@ export class VideoListComponent extends RestTable implements OnInit {
68 label: $localize`Delete`, 72 label: $localize`Delete`,
69 handler: videos => this.removeVideos(videos), 73 handler: videos => this.removeVideos(videos),
70 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO) 74 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO)
75 },
76 {
77 label: $localize`Block`,
78 handler: videos => this.videoBlockModal.show(videos),
79 isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => !v.blacklisted)
80 },
81 {
82 label: $localize`Unblock`,
83 handler: videos => this.unblockVideos(videos),
84 isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => v.blacklisted)
71 } 85 }
72 ] 86 ]
73 ] 87 ]
@@ -132,6 +146,10 @@ export class VideoListComponent extends RestTable implements OnInit {
132 return files.reduce((p, f) => p += f.size, 0) 146 return files.reduce((p, f) => p += f.size, 0)
133 } 147 }
134 148
149 onVideoBlocked () {
150 this.reloadData()
151 }
152
135 protected reloadData () { 153 protected reloadData () {
136 this.selectedVideos = [] 154 this.selectedVideos = []
137 155
@@ -160,7 +178,19 @@ export class VideoListComponent extends RestTable implements OnInit {
160 this.videoService.removeVideo(videos.map(v => v.id)) 178 this.videoService.removeVideo(videos.map(v => v.id))
161 .subscribe({ 179 .subscribe({
162 next: () => { 180 next: () => {
163 this.notifier.success($localize`${videos.length} videos deleted.`) 181 this.notifier.success($localize`Deleted ${videos.length} videos.`)
182 this.reloadData()
183 },
184
185 error: err => this.notifier.error(err.message)
186 })
187 }
188
189 private unblockVideos (videos: Video[]) {
190 this.videoBlockService.unblockVideo(videos.map(v => v.id))
191 .subscribe({
192 next: () => {
193 this.notifier.success($localize`Unblocked ${videos.length} videos.`)
164 this.reloadData() 194 this.reloadData()
165 }, 195 },
166 196
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
index d0eef7d4b..0d75a21d7 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
@@ -77,10 +77,6 @@
77 77
78 <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse"> 78 <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
79 <div class="table-video" i18n-title title="Video was deleted"> 79 <div class="table-video" i18n-title title="Video was deleted">
80 <div class="table-video-image">
81 <span i18n>Deleted</span>
82 </div>
83
84 <div class="table-video-text"> 80 <div class="table-video-text">
85 <div> 81 <div>
86 {{ abuse.video.name }} 82 {{ abuse.video.name }}
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index b902726fa..08cf297cc 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -338,7 +338,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
338 label: $localize`Block video`, 338 label: $localize`Block video`,
339 isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted, 339 isDisplayed: abuse => abuse.video && !abuse.video.deleted && !abuse.video.blacklisted,
340 handler: abuse => { 340 handler: abuse => {
341 this.videoBlocklistService.blockVideo(abuse.video.id, undefined, abuse.video.channel.isLocal) 341 this.videoBlocklistService.blockVideo([ { videoId: abuse.video.id, unfederate: abuse.video.channel.isLocal } ])
342 .subscribe({ 342 .subscribe({
343 next: () => { 343 next: () => {
344 this.notifier.success($localize`Video blocked.`) 344 this.notifier.success($localize`Video blocked.`)
diff --git a/client/src/app/shared/shared-moderation/video-block.component.html b/client/src/app/shared/shared-moderation/video-block.component.html
index 5e9e8493c..e5793f2ca 100644
--- a/client/src/app/shared/shared-moderation/video-block.component.html
+++ b/client/src/app/shared/shared-moderation/video-block.component.html
@@ -1,7 +1,14 @@
1<ng-template #modal> 1<ng-template #modal>
2 <div class="modal-header"> 2 <div class="modal-header">
3 <h4 i18n class="modal-title" *ngIf="!video.isLive">Block video "{{ video.name }}"</h4> 3 <ng-container *ngIf="isMultiple()">
4 <h4 i18n class="modal-title" *ngIf="video.isLive">Block live "{{ video.name }}"</h4> 4 <h4 i18n class="modal-title">Block {{ videos.length }} videos</h4>
5 </ng-container>
6
7 <ng-container *ngIf="!isMultiple()">
8 <h4 i18n class="modal-title" *ngIf="!getSingleVideo().isLive">Block video "{{ getSingleVideo().name }}"</h4>
9 <h4 i18n class="modal-title" *ngIf="getSingleVideo().isLive">Block live "{{ getSingleVideo().name }}"</h4>
10 </ng-container>
11
5 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> 12 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
6 </div> 13 </div>
7 14
@@ -18,19 +25,20 @@
18 </div> 25 </div>
19 </div> 26 </div>
20 27
21 <div class="form-group" *ngIf="video.isLocal"> 28 <div class="form-group" *ngIf="hasLocal()">
22 <my-peertube-checkbox 29 <my-peertube-checkbox
23 inputName="unfederate" formControlName="unfederate" 30 inputName="unfederate" formControlName="unfederate"
24 i18n-labelText labelText="Unfederate the video" 31 i18n-labelText labelText="Unfederate"
25 > 32 >
26 <ng-container ngProjectAs="description"> 33 <ng-container ngProjectAs="description">
27 <span i18n>This will ask remote instances to delete it</span> 34 <span *ngIf="isMultiple()" i18n>This will ask remote instances to delete local videos</span>
35 <span *ngIf="!isMultiple()" i18n>This will ask remote instances to delete this video</span>
28 </ng-container> 36 </ng-container>
29 </my-peertube-checkbox> 37 </my-peertube-checkbox>
30 </div> 38 </div>
31 39
32 <strong class="live-info" *ngIf="video.isLive" i18n> 40 <strong class="live-info" *ngIf="hasLive()" i18n>
33 Blocking this live will automatically terminate the live stream. 41 Blocking a live will automatically terminate the live stream.
34 </strong> 42 </strong>
35 43
36 <div class="form-group inputs"> 44 <div class="form-group inputs">
@@ -39,7 +47,7 @@
39 (click)="hide()" (key.enter)="hide()" 47 (click)="hide()" (key.enter)="hide()"
40 > 48 >
41 49
42 <input type="submit" i18n-value value="Submit" class="peertube-button orange-button" [disabled]="!form.valid" /> 50 <input type="submit" i18n-value value="Block" class="peertube-button orange-button" [disabled]="!form.valid" />
43 </div> 51 </div>
44 </form> 52 </form>
45 53
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts
index a6180dd14..400913f02 100644
--- a/client/src/app/shared/shared-moderation/video-block.component.ts
+++ b/client/src/app/shared/shared-moderation/video-block.component.ts
@@ -1,4 +1,4 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
4import { Video } from '@app/shared/shared-main' 4import { Video } from '@app/shared/shared-main'
@@ -13,12 +13,12 @@ import { VideoBlockService } from './video-block.service'
13 styleUrls: [ './video-block.component.scss' ] 13 styleUrls: [ './video-block.component.scss' ]
14}) 14})
15export class VideoBlockComponent extends FormReactive implements OnInit { 15export class VideoBlockComponent extends FormReactive implements OnInit {
16 @Input() video: Video = null
17
18 @ViewChild('modal', { static: true }) modal: NgbModal 16 @ViewChild('modal', { static: true }) modal: NgbModal
19 17
20 @Output() videoBlocked = new EventEmitter() 18 @Output() videoBlocked = new EventEmitter()
21 19
20 videos: Video[]
21
22 error: string = null 22 error: string = null
23 23
24 private openedModal: NgbModalRef 24 private openedModal: NgbModalRef
@@ -41,7 +41,25 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
41 }, defaultValues) 41 }, defaultValues)
42 } 42 }
43 43
44 show () { 44 isMultiple () {
45 return this.videos.length > 1
46 }
47
48 getSingleVideo () {
49 return this.videos[0]
50 }
51
52 hasLive () {
53 return this.videos.some(v => v.isLive)
54 }
55
56 hasLocal () {
57 return this.videos.some(v => v.isLocal)
58 }
59
60 show (videos: Video[]) {
61 this.videos = videos
62
45 this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) 63 this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
46 } 64 }
47 65
@@ -51,17 +69,30 @@ export class VideoBlockComponent extends FormReactive implements OnInit {
51 } 69 }
52 70
53 block () { 71 block () {
54 const reason = this.form.value['reason'] || undefined 72 const options = this.videos.map(v => ({
55 const unfederate = this.video.isLocal ? this.form.value['unfederate'] : undefined 73 videoId: v.id,
74 reason: this.form.value['reason'] || undefined,
75 unfederate: v.isLocal
76 ? this.form.value['unfederate']
77 : undefined
78 }))
56 79
57 this.videoBlocklistService.blockVideo(this.video.id, reason, unfederate) 80 this.videoBlocklistService.blockVideo(options)
58 .subscribe({ 81 .subscribe({
59 next: () => { 82 next: () => {
60 this.notifier.success($localize`Video blocked.`) 83 const message = this.isMultiple
84 ? $localize`Blocked ${this.videos.length} videos.`
85 : $localize`Blocked ${this.getSingleVideo().name}`
86
87 this.notifier.success(message)
61 this.hide() 88 this.hide()
62 89
63 this.video.blacklisted = true 90 for (const o of options) {
64 this.video.blacklistedReason = reason 91 const video = this.videos.find(v => v.id === o.videoId)
92
93 video.blacklisted = true
94 video.blacklistedReason = o.reason
95 }
65 96
66 this.videoBlocked.emit() 97 this.videoBlocked.emit()
67 }, 98 },
diff --git a/client/src/app/shared/shared-moderation/video-block.service.ts b/client/src/app/shared/shared-moderation/video-block.service.ts
index c22ceefcc..5dfb0d7d4 100644
--- a/client/src/app/shared/shared-moderation/video-block.service.ts
+++ b/client/src/app/shared/shared-moderation/video-block.service.ts
@@ -63,16 +63,20 @@ export class VideoBlockService {
63 ) 63 )
64 } 64 }
65 65
66 blockVideo (videoId: number, reason: string, unfederate: boolean) { 66 blockVideo (options: {
67 const body = { 67 videoId: number
68 unfederate, 68 reason?: string
69 reason 69 unfederate: boolean
70 } 70 }[]) {
71 return observableFrom(options)
72 .pipe(
73 concatMap(({ videoId, unfederate, reason }) => {
74 const body = { unfederate, reason }
71 75
72 return this.authHttp.post(VideoBlockService.BASE_VIDEOS_URL + videoId + '/blacklist', body) 76 return this.authHttp.post(VideoBlockService.BASE_VIDEOS_URL + videoId + '/blacklist', body)
73 .pipe( 77 }),
74 map(this.restExtractor.extractDataBool), 78 toArray(),
75 catchError(res => this.restExtractor.handleError(res)) 79 catchError(res => this.restExtractor.handleError(res))
76 ) 80 )
77 } 81 }
78} 82}
diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html
index 7a6394202..3fea2a8a4 100644
--- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html
+++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html
@@ -20,6 +20,6 @@
20 20
21 <my-video-download #videoDownloadModal></my-video-download> 21 <my-video-download #videoDownloadModal></my-video-download>
22 <my-video-report #videoReportModal [video]="video"></my-video-report> 22 <my-video-report #videoReportModal [video]="video"></my-video-report>
23 <my-video-block #videoBlockModal [video]="video" (videoBlocked)="onVideoBlocked()"></my-video-block> 23 <my-video-block #videoBlockModal (videoBlocked)="onVideoBlocked()"></my-video-block>
24 <my-live-stream-information #liveStreamInformationModal *ngIf="displayOptions.liveInfo"></my-live-stream-information> 24 <my-live-stream-information #liveStreamInformationModal *ngIf="displayOptions.liveInfo"></my-live-stream-information>
25</ng-container> 25</ng-container>
diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
index 790ae2a5e..eff56b40e 100644
--- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
@@ -128,7 +128,7 @@ export class VideoActionsDropdownComponent implements OnChanges {
128 showBlockModal () { 128 showBlockModal () {
129 this.modalOpened.emit() 129 this.modalOpened.emit()
130 130
131 this.videoBlockModal.show() 131 this.videoBlockModal.show([ this.video ])
132 } 132 }
133 133
134 showLiveInfoModal (video: Video) { 134 showLiveInfoModal (video: Video) {