diff options
author | Chocobozzz <me@florianbigard.com> | 2021-10-27 11:42:05 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2021-10-29 11:48:21 +0200 |
commit | 33f6dce136ca6e969fe374efa099bee3f2a3599d (patch) | |
tree | 7a0d6228bab085944015a01267ad31aa1ec6082e /client/src/app/+admin/overview | |
parent | 00004f7f6b966a975498612117212b5373f4103c (diff) | |
download | PeerTube-33f6dce136ca6e969fe374efa099bee3f2a3599d.tar.gz PeerTube-33f6dce136ca6e969fe374efa099bee3f2a3599d.tar.zst PeerTube-33f6dce136ca6e969fe374efa099bee3f2a3599d.zip |
Add videos list admin component
Diffstat (limited to 'client/src/app/+admin/overview')
7 files changed, 255 insertions, 1 deletions
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 @@ | |||
1 | export * from './users' | 1 | export * from './users' |
2 | export * from './videos' | ||
2 | export * from './overview.routes' | 3 | export * 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 @@ | |||
1 | import { Routes } from '@angular/router' | 1 | import { Routes } from '@angular/router' |
2 | import { UsersRoutes } from './users' | 2 | import { UsersRoutes } from './users' |
3 | import { VideosRoutes } from './videos' | ||
3 | 4 | ||
4 | export const OverviewRoutes: Routes = [ | 5 | export 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 @@ | |||
1 | export * from './video-list.component' | ||
2 | export * 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 *; | ||
3 | my-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 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { Component, OnInit } from '@angular/core' | ||
3 | import { ActivatedRoute, Router } from '@angular/router' | ||
4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' | ||
5 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | ||
6 | import { UserRight } from '@shared/models' | ||
7 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | ||
8 | import { 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 | }) | ||
15 | export 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 @@ | |||
1 | import { Routes } from '@angular/router' | ||
2 | import { UserRightGuard } from '@app/core' | ||
3 | import { UserRight } from '@shared/models' | ||
4 | import { VideoListComponent } from './video-list.component' | ||
5 | |||
6 | export 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 | ] | ||