aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+admin
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-10-27 11:42:05 +0200
committerChocobozzz <chocobozzz@cpy.re>2021-10-29 11:48:21 +0200
commit33f6dce136ca6e969fe374efa099bee3f2a3599d (patch)
tree7a0d6228bab085944015a01267ad31aa1ec6082e /client/src/app/+admin
parent00004f7f6b966a975498612117212b5373f4103c (diff)
downloadPeerTube-33f6dce136ca6e969fe374efa099bee3f2a3599d.tar.gz
PeerTube-33f6dce136ca6e969fe374efa099bee3f2a3599d.tar.zst
PeerTube-33f6dce136ca6e969fe374efa099bee3f2a3599d.zip
Add videos list admin component
Diffstat (limited to 'client/src/app/+admin')
-rw-r--r--client/src/app/+admin/admin.component.ts12
-rw-r--r--client/src/app/+admin/admin.module.ts8
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.html29
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts13
-rw-r--r--client/src/app/+admin/overview/index.ts1
-rw-r--r--client/src/app/+admin/overview/overview.routes.ts4
-rw-r--r--client/src/app/+admin/overview/videos/index.ts2
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.html86
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.scss10
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts123
-rw-r--r--client/src/app/+admin/overview/videos/video.routes.ts30
11 files changed, 284 insertions, 34 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index 27d5e0a10..b8a957d1c 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -44,6 +44,14 @@ export class AdminComponent implements OnInit {
44 }) 44 })
45 } 45 }
46 46
47 if (this.hasVideosRight()) {
48 overviewItems.children.push({
49 label: $localize`Videos`,
50 routerLink: '/admin/videos',
51 iconName: 'videos'
52 })
53 }
54
47 if (overviewItems.children.length !== 0) { 55 if (overviewItems.children.length !== 0) {
48 this.menuEntries.push(overviewItems) 56 this.menuEntries.push(overviewItems)
49 } 57 }
@@ -217,4 +225,8 @@ export class AdminComponent implements OnInit {
217 private hasVideoCommentsRight () { 225 private hasVideoCommentsRight () {
218 return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS) 226 return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS)
219 } 227 }
228
229 private hasVideosRight () {
230 return this.auth.getUser().hasRight(UserRight.SEE_ALL_VIDEOS)
231 }
220} 232}
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index a2bd88880..d04c11a20 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -10,7 +10,9 @@ import { SharedFormModule } from '@app/shared/shared-forms'
10import { SharedGlobalIconModule } from '@app/shared/shared-icons' 10import { SharedGlobalIconModule } from '@app/shared/shared-icons'
11import { SharedMainModule } from '@app/shared/shared-main' 11import { SharedMainModule } from '@app/shared/shared-main'
12import { SharedModerationModule } from '@app/shared/shared-moderation' 12import { SharedModerationModule } from '@app/shared/shared-moderation'
13import { SharedTablesModule } from '@app/shared/shared-tables'
13import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' 14import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
15import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
14import { AdminRoutingModule } from './admin-routing.module' 16import { AdminRoutingModule } from './admin-routing.module'
15import { AdminComponent } from './admin.component' 17import { AdminComponent } from './admin.component'
16import { 18import {
@@ -33,7 +35,7 @@ import { AbuseListComponent, VideoBlockListComponent } from './moderation'
33import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' 35import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
34import { ModerationComponent } from './moderation/moderation.component' 36import { ModerationComponent } from './moderation/moderation.component'
35import { VideoCommentListComponent } from './moderation/video-comment-list' 37import { VideoCommentListComponent } from './moderation/video-comment-list'
36import { UserCreateComponent, UserListComponent, UserPasswordComponent, UserUpdateComponent } from './overview' 38import { UserCreateComponent, UserListComponent, UserPasswordComponent, UserUpdateComponent, VideoListComponent } from './overview'
37import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component' 39import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
38import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' 40import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
39import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' 41import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
@@ -56,6 +58,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
56 SharedActorImageModule, 58 SharedActorImageModule,
57 SharedActorImageEditModule, 59 SharedActorImageEditModule,
58 SharedCustomMarkupModule, 60 SharedCustomMarkupModule,
61 SharedVideoMiniatureModule,
62 SharedTablesModule,
59 63
60 TableModule, 64 TableModule,
61 SelectButtonModule, 65 SelectButtonModule,
@@ -65,6 +69,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
65 declarations: [ 69 declarations: [
66 AdminComponent, 70 AdminComponent,
67 71
72 VideoListComponent,
73
68 FollowsComponent, 74 FollowsComponent,
69 FollowersListComponent, 75 FollowersListComponent,
70 FollowingListComponent, 76 FollowingListComponent,
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
index 7efa87dd0..3cd69cfbc 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
@@ -34,9 +34,7 @@
34 <tr> 34 <tr>
35 <td *ngIf="!videoBlock.reason"></td> 35 <td *ngIf="!videoBlock.reason"></td>
36 <td *ngIf="videoBlock.reason" class="expand-cell c-hand" [pRowToggler]="videoBlock" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body"> 36 <td *ngIf="videoBlock.reason" class="expand-cell c-hand" [pRowToggler]="videoBlock" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
37 <span class="expander"> 37 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
38 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
39 </span>
40 </td> 38 </td>
41 39
42 <td class="action-cell"> 40 <td class="action-cell">
@@ -47,22 +45,11 @@
47 </td> 45 </td>
48 46
49 <td> 47 <td>
50 <a [href]="getVideoUrl(videoBlock)" class="table-video-link" [title]="videoBlock.video.name" target="_blank" rel="noopener noreferrer"> 48 <my-video-cell [video]="videoBlock.video">
51 <div class="table-video"> 49 <span name>
52 <div class="table-video-image"> 50 <my-global-icon *ngIf="videoBlock.type === 2" i18n-title title="The video was blocked due to automatic blocking of new videos" iconName="robot"></my-global-icon>
53 <img [src]="videoBlock.video.thumbnailPath"> 51 </span>
54 </div> 52 </my-video-cell>
55
56 <div class="table-video-text">
57 <div>
58 <my-global-icon i18n-title title="The video was blocked due to automatic blocking of new videos" *ngIf="videoBlock.type === 2" iconName="robot"></my-global-icon>
59 {{ videoBlock.video.name }}
60 </div>
61
62 <div class="text-muted">by {{ videoBlock.video.channel?.displayName }} on {{ videoBlock.video.channel?.host }} </div>
63 </div>
64 </div>
65 </a>
66 </td> 53 </td>
67 54
68 <td> 55 <td>
@@ -90,9 +77,7 @@
90 </div> 77 </div>
91 78
92 <div class="right"> 79 <div class="right">
93 <div class="screenratio"> 80 <my-embed [video]="videoBlock.video"></my-embed>
94 <div [innerHTML]="videoBlock.embedHtml"></div>
95 </div>
96 </div> 81 </div>
97 82
98 </div> 83 </div>
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 7baf34ca2..1fe8d0f9d 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
@@ -3,11 +3,10 @@ import { switchMap } from 'rxjs/operators'
3import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' 3import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
4import { environment } from 'src/environments/environment' 4import { environment } from 'src/environments/environment'
5import { Component, OnInit } from '@angular/core' 5import { Component, OnInit } from '@angular/core'
6import { DomSanitizer } from '@angular/platform-browser'
7import { ActivatedRoute, Router } from '@angular/router' 6import { ActivatedRoute, Router } from '@angular/router'
8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 7import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
9import { AdvancedInputFilter } from '@app/shared/shared-forms' 8import { AdvancedInputFilter } from '@app/shared/shared-forms'
10import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 9import { DropdownAction, VideoService } from '@app/shared/shared-main'
11import { VideoBlockService } from '@app/shared/shared-moderation' 10import { VideoBlockService } from '@app/shared/shared-moderation'
12import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils' 11import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
13import { VideoBlacklist, VideoBlacklistType } from '@shared/models' 12import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@@ -18,7 +17,7 @@ import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
18 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ] 17 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ]
19}) 18})
20export class VideoBlockListComponent extends RestTable implements OnInit { 19export class VideoBlockListComponent extends RestTable implements OnInit {
21 blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = [] 20 blocklist: (VideoBlacklist & { reasonHtml?: string })[] = []
22 totalRecords = 0 21 totalRecords = 0
23 sort: SortMeta = { field: 'createdAt', order: -1 } 22 sort: SortMeta = { field: 'createdAt', order: -1 }
24 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 23 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
@@ -50,7 +49,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
50 private confirmService: ConfirmService, 49 private confirmService: ConfirmService,
51 private videoBlocklistService: VideoBlockService, 50 private videoBlocklistService: VideoBlockService,
52 private markdownRenderer: MarkdownService, 51 private markdownRenderer: MarkdownService,
53 private sanitizer: DomSanitizer,
54 private videoService: VideoService 52 private videoService: VideoService
55 ) { 53 ) {
56 super() 54 super()
@@ -125,10 +123,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
125 return 'VideoBlockListComponent' 123 return 'VideoBlockListComponent'
126 } 124 }
127 125
128 getVideoUrl (videoBlock: VideoBlacklist) {
129 return Video.buildWatchUrl(videoBlock.video)
130 }
131
132 toHtml (text: string) { 126 toHtml (text: string) {
133 return this.markdownRenderer.textMarkdownToHTML(text) 127 return this.markdownRenderer.textMarkdownToHTML(text)
134 } 128 }
@@ -176,8 +170,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
176 170
177 for (const element of this.blocklist) { 171 for (const element of this.blocklist) {
178 Object.assign(element, { 172 Object.assign(element, {
179 reasonHtml: await this.toHtml(element.reason), 173 reasonHtml: await this.toHtml(element.reason)
180 embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(element))
181 }) 174 })
182 } 175 }
183 }, 176 },
diff --git a/client/src/app/+admin/overview/index.ts b/client/src/app/+admin/overview/index.ts
index b71a6a45f..a9c46893f 100644
--- a/client/src/app/+admin/overview/index.ts
+++ b/client/src/app/+admin/overview/index.ts
@@ -1,2 +1,3 @@
1export * from './users' 1export * from './users'
2export * from './videos'
2export * from './overview.routes' 3export * from './overview.routes'
diff --git a/client/src/app/+admin/overview/overview.routes.ts b/client/src/app/+admin/overview/overview.routes.ts
index cb5986072..1e6686d16 100644
--- a/client/src/app/+admin/overview/overview.routes.ts
+++ b/client/src/app/+admin/overview/overview.routes.ts
@@ -1,6 +1,8 @@
1import { Routes } from '@angular/router' 1import { Routes } from '@angular/router'
2import { UsersRoutes } from './users' 2import { UsersRoutes } from './users'
3import { VideosRoutes } from './videos'
3 4
4export const OverviewRoutes: Routes = [ 5export const OverviewRoutes: Routes = [
5 ...UsersRoutes 6 ...UsersRoutes,
7 ...VideosRoutes
6] 8]
diff --git a/client/src/app/+admin/overview/videos/index.ts b/client/src/app/+admin/overview/videos/index.ts
new file mode 100644
index 000000000..40c2ffe72
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/index.ts
@@ -0,0 +1,2 @@
1export * from './video-list.component'
2export * from './video.routes'
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html
new file mode 100644
index 000000000..1f1e9cc6e
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video-list.component.html
@@ -0,0 +1,86 @@
1<h1>
2 <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Videos</ng-container>
4</h1>
5
6<p-table
7 [value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
8 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedVideos"
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos"
12 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
13>
14 <ng-template pTemplate="caption">
15 <div class="caption">
16 <div class="left-buttons">
17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkVideoActions" [entry]="selectedVideos"
20 >
21 </my-action-dropdown>
22 </div>
23
24 <div class="ml-auto">
25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
26 </div>
27
28 </div>
29 </ng-template>
30
31 <ng-template pTemplate="header">
32 <tr>
33 <th style="width: 40px">
34 <p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
35 </th>
36 <th style="width: 40px"></th>
37 <th style="width: 60px;"></th>
38 <th i18n>Video</th>
39 <th i18n>Info</th>
40 <th style="width: 150px;" i18n pSortableColumn="publishedAt">Published <p-sortIcon field="publishedAt"></p-sortIcon></th>
41 </tr>
42 </ng-template>
43
44 <ng-template pTemplate="body" let-expanded="expanded" let-video>
45
46 <tr [pSelectableRow]="video">
47 <td class="checkbox-cell">
48 <p-tableCheckbox [value]="video" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
49 </td>
50
51 <td class="expand-cell" [pRowToggler]="video">
52 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
53 </td>
54
55 <td class="action-cell">
56 <my-video-actions-dropdown
57 placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video"
58 [displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()"
59 ></my-video-actions-dropdown>
60 </td>
61
62 <td>
63 <my-video-cell [video]="video"></my-video-cell>
64 </td>
65
66 <td>
67 <span class="badge badge-blue" i18n>{{ video.privacy.label }}</span>
68 <span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
69 <span *ngIf="video.blocked" class="badge badge-red" i18n>NSFW</span>
70 </td>
71
72 <td>
73 {{ video.publishedAt | date: 'short' }}
74 </td>
75
76 </tr>
77 </ng-template>
78
79 <ng-template pTemplate="rowexpansion" let-video>
80 <tr>
81 <td colspan="50">
82 <my-embed [video]="video"></my-embed>
83 </td>
84 </tr>
85 </ng-template>
86</p-table>
diff --git a/client/src/app/+admin/overview/videos/video-list.component.scss b/client/src/app/+admin/overview/videos/video-list.component.scss
new file mode 100644
index 000000000..fcdb457f2
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video-list.component.scss
@@ -0,0 +1,10 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3my-embed {
4 display: block;
5 max-width: 500px;
6}
7
8.badge {
9 @include table-badge;
10}
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
new file mode 100644
index 000000000..a445bc209
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -0,0 +1,123 @@
1import { SortMeta } from 'primeng/api'
2import { Component, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
5import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
6import { UserRight } from '@shared/models'
7import { AdvancedInputFilter } from '@app/shared/shared-forms'
8import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
9
10@Component({
11 selector: 'my-video-list',
12 templateUrl: './video-list.component.html',
13 styleUrls: [ './video-list.component.scss' ]
14})
15export class VideoListComponent extends RestTable implements OnInit {
16 videos: Video[] = []
17
18 totalRecords = 0
19 sort: SortMeta = { field: 'publishedAt', order: 1 }
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21
22 bulkVideoActions: DropdownAction<Video[]>[][] = []
23
24 selectedVideos: Video[] = []
25
26 inputFilters: AdvancedInputFilter[] = [
27 {
28 title: $localize`Advanced filters`,
29 children: [
30 {
31 queryParams: { search: 'local:true' },
32 label: $localize`Only local videos`
33 }
34 ]
35 }
36 ]
37
38 videoActionsOptions: VideoActionsDisplayType = {
39 playlist: false,
40 download: false,
41 update: true,
42 blacklist: true,
43 delete: true,
44 report: false,
45 duplicate: true,
46 mute: true,
47 liveInfo: false
48 }
49
50 constructor (
51 protected route: ActivatedRoute,
52 protected router: Router,
53 private confirmService: ConfirmService,
54 private auth: AuthService,
55 private notifier: Notifier,
56 private videoService: VideoService
57 ) {
58 super()
59 }
60
61 get authUser () {
62 return this.auth.getUser()
63 }
64
65 ngOnInit () {
66 this.initialize()
67
68 this.bulkVideoActions = [
69 [
70 {
71 label: $localize`Delete`,
72 handler: videos => this.removeVideos(videos),
73 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO)
74 }
75 ]
76 ]
77 }
78
79 getIdentifier () {
80 return 'VideoListComponent'
81 }
82
83 isInSelectionMode () {
84 return this.selectedVideos.length !== 0
85 }
86
87 onVideoRemoved () {
88 this.reloadData()
89 }
90
91 protected reloadData () {
92 this.selectedVideos = []
93
94 this.videoService.getAdminVideos({
95 pagination: this.pagination,
96 sort: this.sort,
97 search: this.search
98 }).subscribe({
99 next: resultList => {
100 this.videos = resultList.data
101 this.totalRecords = resultList.total
102 },
103
104 error: err => this.notifier.error(err.message)
105 })
106 }
107
108 private async removeVideos (videos: Video[]) {
109 const message = $localize`Are you sure you want to delete these ${videos.length} videos?`
110 const res = await this.confirmService.confirm(message, $localize`Delete`)
111 if (res === false) return
112
113 this.videoService.removeVideo(videos.map(v => v.id))
114 .subscribe({
115 next: () => {
116 this.notifier.success($localize`${videos.length} videos deleted.`)
117 this.reloadData()
118 },
119
120 error: err => this.notifier.error(err.message)
121 })
122 }
123}
diff --git a/client/src/app/+admin/overview/videos/video.routes.ts b/client/src/app/+admin/overview/videos/video.routes.ts
new file mode 100644
index 000000000..984df7b82
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video.routes.ts
@@ -0,0 +1,30 @@
1import { Routes } from '@angular/router'
2import { UserRightGuard } from '@app/core'
3import { UserRight } from '@shared/models'
4import { VideoListComponent } from './video-list.component'
5
6export const VideosRoutes: Routes = [
7 {
8 path: 'videos',
9 canActivate: [ UserRightGuard ],
10 data: {
11 userRight: UserRight.SEE_ALL_VIDEOS
12 },
13 children: [
14 {
15 path: '',
16 redirectTo: 'list',
17 pathMatch: 'full'
18 },
19 {
20 path: 'list',
21 component: VideoListComponent,
22 data: {
23 meta: {
24 title: $localize`Videos list`
25 }
26 }
27 }
28 ]
29 }
30]