PeerTube/client/src/app/+admin/overview/videos/video-list.component.ts

346 lines
11 KiB
TypeScript
Raw Normal View History

2024-03-04 10:01:52 +01:00
import { SortMeta, SharedModule } from 'primeng/api'
2021-11-02 11:17:17 +01:00
import { finalize } from 'rxjs/operators'
2021-11-17 11:18:49 +01:00
import { Component, OnInit, ViewChild } from '@angular/core'
2021-10-27 11:42:05 +02:00
import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
2023-07-28 11:07:03 +02:00
import { formatICU, getAbsoluteAPIUrl } from '@app/helpers'
import { getAllFiles } from '@peertube/peertube-core-utils'
import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@peertube/peertube-models'
2021-11-02 14:14:26 +01:00
import { VideoAdminService } from './video-admin.service'
2024-03-04 10:01:52 +01:00
import { BytesPipe } from '../../../shared/shared-main/angular/bytes.pipe'
import { EmbedComponent } from '../../../shared/shared-main/video/embed.component'
import { AutoColspanDirective } from '../../../shared/shared-main/angular/auto-colspan.directive'
import { VideoCellComponent } from '../../../shared/shared-tables/video-cell.component'
import {
VideoActionsDisplayType,
VideoActionsDropdownComponent
} from '../../../shared/shared-video-miniature/video-actions-dropdown.component'
2024-03-04 10:01:52 +01:00
import { TableExpanderIconComponent } from '../../../shared/shared-tables/table-expander-icon.component'
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
import { AdvancedInputFilter, AdvancedInputFilterComponent } from '../../../shared/shared-forms/advanced-input-filter.component'
import { ActionDropdownComponent, DropdownAction } from '../../../shared/shared-main/buttons/action-dropdown.component'
2024-03-04 10:01:52 +01:00
import { NgClass, NgIf, NgFor, DatePipe } from '@angular/common'
import { TableModule } from 'primeng/table'
import { GlobalIconComponent } from '../../../shared/shared-icons/global-icon.component'
import { VideoService } from '@app/shared/shared-main/video/video.service'
import { Video } from '@app/shared/shared-main/video/video.model'
import { VideoBlockComponent } from '@app/shared/shared-moderation/video-block.component'
import { VideoBlockService } from '@app/shared/shared-moderation/video-block.service'
2021-10-27 11:42:05 +02:00
@Component({
selector: 'my-video-list',
templateUrl: './video-list.component.html',
2024-03-04 10:01:52 +01:00
styleUrls: [ './video-list.component.scss' ],
standalone: true,
imports: [
GlobalIconComponent,
TableModule,
NgClass,
SharedModule,
NgIf,
ActionDropdownComponent,
AdvancedInputFilterComponent,
ButtonComponent,
NgbTooltip,
TableExpanderIconComponent,
VideoActionsDropdownComponent,
VideoCellComponent,
AutoColspanDirective,
NgFor,
EmbedComponent,
VideoBlockComponent,
DatePipe,
BytesPipe
]
2021-10-27 11:42:05 +02:00
})
export class VideoListComponent extends RestTable <Video> implements OnInit {
2021-11-17 11:18:49 +01:00
@ViewChild('videoBlockModal') videoBlockModal: VideoBlockComponent
2021-10-27 11:42:05 +02:00
videos: Video[] = []
totalRecords = 0
2021-11-02 11:00:40 +01:00
sort: SortMeta = { field: 'publishedAt', order: -1 }
2021-10-27 11:42:05 +02:00
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
bulkActions: DropdownAction<Video[]>[][] = []
2021-10-27 11:42:05 +02:00
2021-11-02 11:50:03 +01:00
inputFilters: AdvancedInputFilter[]
2021-10-27 11:42:05 +02:00
videoActionsOptions: VideoActionsDisplayType = {
playlist: false,
download: false,
update: true,
blacklist: true,
delete: true,
report: false,
duplicate: true,
mute: true,
liveInfo: false,
2021-11-18 14:35:08 +01:00
removeFiles: true,
2022-04-05 14:03:52 +02:00
transcoding: true,
studio: true,
stats: true
2021-10-27 11:42:05 +02:00
}
2021-11-02 11:50:03 +01:00
loading = true
2021-11-02 11:17:17 +01:00
2021-10-27 11:42:05 +02:00
constructor (
protected route: ActivatedRoute,
protected router: Router,
private confirmService: ConfirmService,
private auth: AuthService,
private notifier: Notifier,
2021-11-02 14:14:26 +01:00
private videoService: VideoService,
2021-11-17 11:18:49 +01:00
private videoAdminService: VideoAdminService,
private videoBlockService: VideoBlockService
2021-10-27 11:42:05 +02:00
) {
super()
}
get authUser () {
return this.auth.getUser()
}
ngOnInit () {
this.initialize()
2021-11-02 14:14:26 +01:00
this.inputFilters = this.videoAdminService.buildAdminInputFilter()
2021-11-02 11:50:03 +01:00
this.bulkActions = [
2021-10-27 11:42:05 +02:00
[
{
label: $localize`Delete`,
handler: videos => this.removeVideos(videos),
isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO),
iconName: 'delete'
2021-11-17 11:18:49 +01:00
},
{
label: $localize`Block`,
handler: videos => this.videoBlockModal.show(videos),
isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => !v.blacklisted),
iconName: 'no'
2021-11-17 11:18:49 +01:00
},
{
label: $localize`Unblock`,
handler: videos => this.unblockVideos(videos),
isDisplayed: videos => this.authUser.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) && videos.every(v => v.blacklisted),
iconName: 'undo'
}
],
[
2021-11-18 14:35:08 +01:00
{
label: $localize`Run HLS transcoding`,
handler: videos => this.runTranscoding(videos, 'hls'),
isDisplayed: videos => videos.every(v => v.canRunTranscoding(this.authUser)),
iconName: 'cog'
},
{
label: $localize`Run Web Video transcoding`,
handler: videos => this.runTranscoding(videos, 'web-video'),
2021-11-18 14:35:08 +01:00
isDisplayed: videos => videos.every(v => v.canRunTranscoding(this.authUser)),
iconName: 'cog'
},
{
label: $localize`Delete HLS files`,
handler: videos => this.removeVideoFiles(videos, 'hls'),
2021-11-18 14:35:08 +01:00
isDisplayed: videos => videos.every(v => v.canRemoveFiles(this.authUser)),
iconName: 'delete'
},
{
label: $localize`Delete Web Video files`,
handler: videos => this.removeVideoFiles(videos, 'web-videos'),
2021-11-18 14:35:08 +01:00
isDisplayed: videos => videos.every(v => v.canRemoveFiles(this.authUser)),
iconName: 'delete'
2021-10-27 11:42:05 +02:00
}
]
]
}
getIdentifier () {
return 'VideoListComponent'
}
2021-11-03 11:32:41 +01:00
getPrivacyBadgeClass (video: Video) {
2021-11-03 14:23:55 +01:00
if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green'
return 'badge-yellow'
}
2021-11-03 11:32:41 +01:00
isUnpublished (video: Video) {
return video.state.id !== VideoState.LIVE_ENDED && video.state.id !== VideoState.PUBLISHED
}
isAccountBlocked (video: Video) {
return video.blockedOwner
}
isServerBlocked (video: Video) {
return video.blockedServer
}
isVideoBlocked (video: Video) {
return video.blacklisted
}
2021-11-03 11:32:41 +01:00
isImport (video: Video) {
return video.state.id === VideoState.TO_IMPORT
}
hasHLS (video: Video) {
2021-11-03 09:59:53 +01:00
const p = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
if (!p) return false
return p.files.length !== 0
}
hasWebVideos (video: Video) {
return video.files.length !== 0
}
2022-10-10 14:42:51 +02:00
hasObjectStorage (video: Video) {
if (!video.isLocal) return false
const files = getAllFiles(video)
2023-07-28 11:07:03 +02:00
return files.some(f => !f.fileUrl.startsWith(getAbsoluteAPIUrl()))
2022-10-10 14:42:51 +02:00
}
canRemoveOneFile (video: Video) {
return video.canRemoveOneFile(this.authUser)
}
getFilesSize (video: Video) {
let files = video.files
if (this.hasHLS(video)) {
files = files.concat(video.streamingPlaylists[0].files)
}
return files.reduce((p, f) => p += f.size, 0)
}
async removeVideoFile (video: Video, file: VideoFile, type: 'hls' | 'web-videos') {
2023-01-20 15:06:08 +01:00
const message = $localize`Are you sure you want to delete this ${file.resolution.label} file?`
const res = await this.confirmService.confirm(message, $localize`Delete file`)
if (res === false) return
this.videoService.removeFile(video.uuid, file.id, type)
.subscribe({
next: () => {
this.notifier.success($localize`File removed.`)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
2021-10-27 11:42:05 +02:00
2023-01-20 15:06:08 +01:00
protected reloadDataInternal () {
2021-11-02 11:17:17 +01:00
this.loading = true
2021-11-02 14:14:26 +01:00
this.videoAdminService.getAdminVideos({
2021-10-27 11:42:05 +02:00
pagination: this.pagination,
sort: this.sort,
2023-02-25 16:22:33 +01:00
nsfw: 'both', // Always list NSFW video, overriding instance/user setting
2021-10-27 11:42:05 +02:00
search: this.search
2021-11-02 11:17:17 +01:00
}).pipe(finalize(() => this.loading = false))
.subscribe({
next: resultList => {
this.videos = resultList.data
this.totalRecords = resultList.total
},
error: err => this.notifier.error(err.message)
})
2021-10-27 11:42:05 +02:00
}
private async removeVideos (videos: Video[]) {
2023-06-06 14:32:47 +02:00
const message = formatICU(
$localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`,
{ count: videos.length }
2022-05-24 16:29:01 +02:00
)
2021-10-27 11:42:05 +02:00
const res = await this.confirmService.confirm(message, $localize`Delete`)
if (res === false) return
this.videoService.removeVideo(videos.map(v => v.id))
.subscribe({
next: () => {
2022-05-24 16:29:01 +02:00
this.notifier.success(
2023-06-06 14:32:47 +02:00
formatICU(
$localize`Deleted {count, plural, =1 {1 video} other {{count} videos}}.`,
{ count: videos.length }
2022-05-24 16:29:01 +02:00
)
)
2021-11-17 11:18:49 +01:00
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
private unblockVideos (videos: Video[]) {
this.videoBlockService.unblockVideo(videos.map(v => v.id))
.subscribe({
next: () => {
2022-05-24 16:29:01 +02:00
this.notifier.success(
2023-06-06 14:32:47 +02:00
formatICU(
$localize`Unblocked {count, plural, =1 {1 video} other {{count} videos}}.`,
{ count: videos.length }
2022-05-24 16:29:01 +02:00
)
)
2021-10-27 11:42:05 +02:00
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
private async removeVideoFiles (videos: Video[], type: 'hls' | 'web-videos') {
2022-05-24 16:29:01 +02:00
let message: string
if (type === 'hls') {
// eslint-disable-next-line max-len
2023-06-06 14:32:47 +02:00
message = formatICU(
$localize`Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {{count} HLS streaming playlists}}?`,
{ count: videos.length }
2022-05-24 16:29:01 +02:00
)
} else {
// eslint-disable-next-line max-len
2023-06-06 14:32:47 +02:00
message = formatICU(
$localize`Are you sure you want to delete Web Video files of {count, plural, =1 {1 video} other {{count} videos}}?`,
2023-06-06 14:32:47 +02:00
{ count: videos.length }
2022-05-24 16:29:01 +02:00
)
}
const res = await this.confirmService.confirm(message, $localize`Delete`)
if (res === false) return
this.videoService.removeVideoFiles(videos.map(v => v.id), type)
.subscribe({
next: () => {
this.notifier.success($localize`Files were removed.`)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
2021-11-18 14:35:08 +01:00
private runTranscoding (videos: Video[], type: 'hls' | 'web-video') {
2023-07-28 11:07:03 +02:00
this.videoService.runTranscoding({ videoIds: videos.map(v => v.id), type, askForForceTranscodingIfNeeded: false })
2021-11-18 14:35:08 +01:00
.subscribe({
next: () => {
this.notifier.success($localize`Transcoding jobs created.`)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
2021-10-27 11:42:05 +02:00
}