diff options
author | Chocobozzz <me@florianbigard.com> | 2021-11-17 11:18:49 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-11-17 11:18:49 +0100 |
commit | 3cfa817672657df18260ece5b354efa0f3b6e317 (patch) | |
tree | 318a7113fac4fcf1e6d0f7888cda1939aeefc500 | |
parent | 4bdff96d77c03e5cce6052188f69a65bf6ea5781 (diff) | |
download | PeerTube-3cfa817672657df18260ece5b354efa0f3b6e317.tar.gz PeerTube-3cfa817672657df18260ece5b354efa0f3b6e317.tar.zst PeerTube-3cfa817672657df18260ece5b354efa0f3b6e317.zip |
Add ability to bulk block videos
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 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { finalize } from 'rxjs/operators' | 2 | import { finalize } from 'rxjs/operators' |
3 | import { Component, OnInit } from '@angular/core' | 3 | import { Component, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' | 5 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' |
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 7 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
8 | import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' | ||
8 | import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature' | 9 | import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature' |
9 | import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models' | 10 | import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models' |
10 | import { VideoAdminService } from './video-admin.service' | 11 | import { 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 | }) |
17 | export class VideoListComponent extends RestTable implements OnInit { | 18 | export 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 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { Video } from '@app/shared/shared-main' | 4 | import { 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 | }) |
15 | export class VideoBlockComponent extends FormReactive implements OnInit { | 15 | export 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) { |