diff options
Diffstat (limited to 'client')
5 files changed, 101 insertions, 17 deletions
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 9b536ec11..6e4fb4c6f 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.html +++ b/client/src/app/+admin/overview/videos/video-list.component.html | |||
@@ -57,7 +57,7 @@ | |||
57 | <td class="action-cell"> | 57 | <td class="action-cell"> |
58 | <my-video-actions-dropdown | 58 | <my-video-actions-dropdown |
59 | placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" | 59 | placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video" |
60 | [displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()" | 60 | [displayOptions]="videoActionsOptions" (videoRemoved)="reloadData()" (videoFilesRemoved)="reloadData()" |
61 | ></my-video-actions-dropdown> | 61 | ></my-video-actions-dropdown> |
62 | </td> | 62 | </td> |
63 | 63 | ||
@@ -127,4 +127,4 @@ | |||
127 | </ng-template> | 127 | </ng-template> |
128 | </p-table> | 128 | </p-table> |
129 | 129 | ||
130 | <my-video-block #videoBlockModal (videoBlocked)="onVideoBlocked()"></my-video-block> | 130 | <my-video-block #videoBlockModal (videoBlocked)="reloadData()"></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 7f268bb23..3c21adb44 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.ts +++ b/client/src/app/+admin/overview/videos/video-list.component.ts | |||
@@ -39,7 +39,8 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
39 | report: false, | 39 | report: false, |
40 | duplicate: true, | 40 | duplicate: true, |
41 | mute: true, | 41 | mute: true, |
42 | liveInfo: false | 42 | liveInfo: false, |
43 | removeFiles: true | ||
43 | } | 44 | } |
44 | 45 | ||
45 | loading = true | 46 | loading = true |
@@ -71,17 +72,34 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
71 | { | 72 | { |
72 | label: $localize`Delete`, | 73 | label: $localize`Delete`, |
73 | handler: videos => this.removeVideos(videos), | 74 | handler: videos => this.removeVideos(videos), |
74 | isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO) | 75 | isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO), |
76 | iconName: 'delete' | ||
75 | }, | 77 | }, |
76 | { | 78 | { |
77 | label: $localize`Block`, | 79 | label: $localize`Block`, |
78 | handler: videos => this.videoBlockModal.show(videos), | 80 | handler: videos => this.videoBlockModal.show(videos), |
79 | isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => !v.blacklisted) | 81 | isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => !v.blacklisted), |
82 | iconName: 'no' | ||
80 | }, | 83 | }, |
81 | { | 84 | { |
82 | label: $localize`Unblock`, | 85 | label: $localize`Unblock`, |
83 | handler: videos => this.unblockVideos(videos), | 86 | handler: videos => this.unblockVideos(videos), |
84 | isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => v.blacklisted) | 87 | isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => v.blacklisted), |
88 | iconName: 'undo' | ||
89 | } | ||
90 | ], | ||
91 | [ | ||
92 | { | ||
93 | label: $localize`Delete HLS files`, | ||
94 | handler: videos => this.removeVideoFiles(videos, 'hls'), | ||
95 | isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_FILES) && videos.every(v => v.hasHLS() && v.hasWebTorrent()), | ||
96 | iconName: 'delete' | ||
97 | }, | ||
98 | { | ||
99 | label: $localize`Delete WebTorrent files`, | ||
100 | handler: videos => this.removeVideoFiles(videos, 'webtorrent'), | ||
101 | isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_FILES) && videos.every(v => v.hasHLS() && v.hasWebTorrent()), | ||
102 | iconName: 'delete' | ||
85 | } | 103 | } |
86 | ] | 104 | ] |
87 | ] | 105 | ] |
@@ -95,10 +113,6 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
95 | return this.selectedVideos.length !== 0 | 113 | return this.selectedVideos.length !== 0 |
96 | } | 114 | } |
97 | 115 | ||
98 | onVideoRemoved () { | ||
99 | this.reloadData() | ||
100 | } | ||
101 | |||
102 | getPrivacyBadgeClass (video: Video) { | 116 | getPrivacyBadgeClass (video: Video) { |
103 | if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green' | 117 | if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green' |
104 | 118 | ||
@@ -146,11 +160,7 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
146 | return files.reduce((p, f) => p += f.size, 0) | 160 | return files.reduce((p, f) => p += f.size, 0) |
147 | } | 161 | } |
148 | 162 | ||
149 | onVideoBlocked () { | 163 | reloadData () { |
150 | this.reloadData() | ||
151 | } | ||
152 | |||
153 | protected reloadData () { | ||
154 | this.selectedVideos = [] | 164 | this.selectedVideos = [] |
155 | 165 | ||
156 | this.loading = true | 166 | this.loading = true |
@@ -197,4 +207,23 @@ export class VideoListComponent extends RestTable implements OnInit { | |||
197 | error: err => this.notifier.error(err.message) | 207 | error: err => this.notifier.error(err.message) |
198 | }) | 208 | }) |
199 | } | 209 | } |
210 | |||
211 | private async removeVideoFiles (videos: Video[], type: 'hls' | 'webtorrent') { | ||
212 | const message = type === 'hls' | ||
213 | ? $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?` | ||
214 | : $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?` | ||
215 | |||
216 | const res = await this.confirmService.confirm(message, $localize`Delete`) | ||
217 | if (res === false) return | ||
218 | |||
219 | this.videoService.removeVideoFiles(videos.map(v => v.id), type) | ||
220 | .subscribe({ | ||
221 | next: () => { | ||
222 | this.notifier.success($localize`Files were removed.`) | ||
223 | this.reloadData() | ||
224 | }, | ||
225 | |||
226 | error: err => this.notifier.error(err.message) | ||
227 | }) | ||
228 | } | ||
200 | } | 229 | } |
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index 472a8c810..4203ff1c0 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts | |||
@@ -14,7 +14,8 @@ import { | |||
14 | VideoPrivacy, | 14 | VideoPrivacy, |
15 | VideoScheduleUpdate, | 15 | VideoScheduleUpdate, |
16 | VideoState, | 16 | VideoState, |
17 | VideoStreamingPlaylist | 17 | VideoStreamingPlaylist, |
18 | VideoStreamingPlaylistType | ||
18 | } from '@shared/models' | 19 | } from '@shared/models' |
19 | 20 | ||
20 | export class Video implements VideoServerModel { | 21 | export class Video implements VideoServerModel { |
@@ -219,6 +220,14 @@ export class Video implements VideoServerModel { | |||
219 | return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO)) | 220 | return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO)) |
220 | } | 221 | } |
221 | 222 | ||
223 | hasHLS () { | ||
224 | return this.streamingPlaylists?.some(p => p.type === VideoStreamingPlaylistType.HLS) | ||
225 | } | ||
226 | |||
227 | hasWebTorrent () { | ||
228 | return this.files && this.files.length !== 0 | ||
229 | } | ||
230 | |||
222 | isLiveInfoAvailableBy (user: AuthUser) { | 231 | isLiveInfoAvailableBy (user: AuthUser) { |
223 | return this.isLive && | 232 | return this.isLive && |
224 | user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.GET_ANY_LIVE)) | 233 | user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.GET_ANY_LIVE)) |
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 570e8e3be..d135a27dc 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -299,6 +299,15 @@ export class VideoService { | |||
299 | ) | 299 | ) |
300 | } | 300 | } |
301 | 301 | ||
302 | removeVideoFiles (videoIds: (number | string)[], type: 'hls' | 'webtorrent') { | ||
303 | return from(videoIds) | ||
304 | .pipe( | ||
305 | concatMap(id => this.authHttp.delete(VideoService.BASE_VIDEO_URL + '/' + id + '/' + type)), | ||
306 | toArray(), | ||
307 | catchError(err => this.restExtractor.handleError(err)) | ||
308 | ) | ||
309 | } | ||
310 | |||
302 | loadCompleteDescription (descriptionPath: string) { | 311 | loadCompleteDescription (descriptionPath: string) { |
303 | return this.authHttp | 312 | return this.authHttp |
304 | .get<{ description: string }>(environment.apiUrl + descriptionPath) | 313 | .get<{ description: string }>(environment.apiUrl + descriptionPath) |
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 eff56b40e..82c084791 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 | |||
@@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@a | |||
2 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' | 2 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' |
3 | import { BlocklistService, VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' | 3 | import { BlocklistService, VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' |
4 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' |
5 | import { VideoCaption } from '@shared/models' | 5 | import { UserRight, VideoCaption } from '@shared/models' |
6 | import { | 6 | import { |
7 | Actor, | 7 | Actor, |
8 | DropdownAction, | 8 | DropdownAction, |
@@ -27,6 +27,7 @@ export type VideoActionsDisplayType = { | |||
27 | duplicate?: boolean | 27 | duplicate?: boolean |
28 | mute?: boolean | 28 | mute?: boolean |
29 | liveInfo?: boolean | 29 | liveInfo?: boolean |
30 | removeFiles?: boolean | ||
30 | } | 31 | } |
31 | 32 | ||
32 | @Component({ | 33 | @Component({ |
@@ -65,6 +66,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
65 | @Input() buttonSize: DropdownButtonSize = 'normal' | 66 | @Input() buttonSize: DropdownButtonSize = 'normal' |
66 | @Input() buttonDirection: DropdownDirection = 'vertical' | 67 | @Input() buttonDirection: DropdownDirection = 'vertical' |
67 | 68 | ||
69 | @Output() videoFilesRemoved = new EventEmitter() | ||
68 | @Output() videoRemoved = new EventEmitter() | 70 | @Output() videoRemoved = new EventEmitter() |
69 | @Output() videoUnblocked = new EventEmitter() | 71 | @Output() videoUnblocked = new EventEmitter() |
70 | @Output() videoBlocked = new EventEmitter() | 72 | @Output() videoBlocked = new EventEmitter() |
@@ -174,6 +176,10 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
174 | return this.video.account.id !== this.user.account.id | 176 | return this.video.account.id !== this.user.account.id |
175 | } | 177 | } |
176 | 178 | ||
179 | canRemoveVideoFiles () { | ||
180 | return this.user.hasRight(UserRight.MANAGE_VIDEO_FILES) && this.video.hasHLS() && this.video.hasWebTorrent() | ||
181 | } | ||
182 | |||
177 | /* Action handlers */ | 183 | /* Action handlers */ |
178 | 184 | ||
179 | async unblockVideo () { | 185 | async unblockVideo () { |
@@ -245,6 +251,23 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
245 | }) | 251 | }) |
246 | } | 252 | } |
247 | 253 | ||
254 | async removeVideoFiles (video: Video, type: 'hls' | 'webtorrent') { | ||
255 | const confirmMessage = $localize`Do you really want to remove "${this.video.name}" files?` | ||
256 | |||
257 | const res = await this.confirmService.confirm(confirmMessage, $localize`Remove "${this.video.name}" files`) | ||
258 | if (res === false) return | ||
259 | |||
260 | this.videoService.removeVideoFiles([ video.id ], type) | ||
261 | .subscribe({ | ||
262 | next: () => { | ||
263 | this.notifier.success($localize`Removed files of ${video.name}.`) | ||
264 | this.videoFilesRemoved.emit() | ||
265 | }, | ||
266 | |||
267 | error: err => this.notifier.error(err.message) | ||
268 | }) | ||
269 | } | ||
270 | |||
248 | onVideoBlocked () { | 271 | onVideoBlocked () { |
249 | this.videoBlocked.emit() | 272 | this.videoBlocked.emit() |
250 | } | 273 | } |
@@ -317,6 +340,20 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
317 | iconName: 'flag' | 340 | iconName: 'flag' |
318 | } | 341 | } |
319 | ], | 342 | ], |
343 | [ | ||
344 | { | ||
345 | label: $localize`Delete HLS files`, | ||
346 | handler: ({ video }) => this.removeVideoFiles(video, 'hls'), | ||
347 | isDisplayed: () => this.displayOptions.removeFiles && this.canRemoveVideoFiles(), | ||
348 | iconName: 'delete' | ||
349 | }, | ||
350 | { | ||
351 | label: $localize`Delete WebTorrent files`, | ||
352 | handler: ({ video }) => this.removeVideoFiles(video, 'webtorrent'), | ||
353 | isDisplayed: () => this.displayOptions.removeFiles && this.canRemoveVideoFiles(), | ||
354 | iconName: 'delete' | ||
355 | } | ||
356 | ], | ||
320 | [ // actions regarding the account/its server | 357 | [ // actions regarding the account/its server |
321 | { | 358 | { |
322 | label: $localize`Mute account`, | 359 | label: $localize`Mute account`, |