diff options
20 files changed, 126 insertions, 50 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f3724a3bf..ca27aa3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,10 +4,11 @@ | |||
4 | 4 | ||
5 | ### IMPORTANT NOTES | 5 | ### IMPORTANT NOTES |
6 | 6 | ||
7 | * **Important** SQL migrations (in particular `0685-multiple-actor-images`) can take several minutes to complete | ||
8 | * **Important** You need to execute manually a migration script (can be executed after your upgrade, while your PeerTube instance is running) to generate smaller avatar miniatures: | 7 | * **Important** You need to execute manually a migration script (can be executed after your upgrade, while your PeerTube instance is running) to generate smaller avatar miniatures: |
9 | * Classic installation: `cd /var/www/peertube/peertube-latest && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production node dist/scripts/migrations/peertube-4.2.js` | 8 | * Classic installation: `cd /var/www/peertube/peertube-latest && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production node dist/scripts/migrations/peertube-4.2.js` |
10 | * Docker installation: `cd /var/www/peertube-docker && docker-compose exec -u peertube peertube node dist/scripts/migrations/peertube-4.2.js` | 9 | * Docker installation: `cd /var/www/peertube-docker && docker-compose exec -u peertube peertube node dist/scripts/migrations/peertube-4.2.js` |
10 | * **Important** SQL migrations (in particular `0685-multiple-actor-images`) can take several minutes to complete | ||
11 | * **Important** You must update your nginx configuration to support video web editor: https://docs.joinpeertube.org/install-any-os?id=nginx | ||
11 | * REST API: | 12 | * REST API: |
12 | * `PUT /api/v1/videos/{id}/watching` is deprecated, use `POST /api/v1/videos/videos/{id}/views` instead: https://docs.joinpeertube.org/api-rest-reference.html#operation/addView | 13 | * `PUT /api/v1/videos/{id}/watching` is deprecated, use `POST /api/v1/videos/videos/{id}/views` instead: https://docs.joinpeertube.org/api-rest-reference.html#operation/addView |
13 | 14 | ||
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html index 30d10e3cf..62eeef8fe 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.html +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html | |||
@@ -5,7 +5,7 @@ | |||
5 | 5 | ||
6 | <p-table | 6 | <p-table |
7 | [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" | 7 | [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" |
8 | [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers" | 8 | [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers" |
9 | [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" | 9 | [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" |
10 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate | 10 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate |
11 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" | 11 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" |
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts index d22e1355e..9d11bd02e 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' | 4 | import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' |
5 | import { getAPIHost } from '@app/helpers' | 5 | import { getAPIHost } from '@app/helpers' |
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { Actor, DropdownAction } from '@app/shared/shared-main' | 7 | import { Actor, DropdownAction } from '@app/shared/shared-main' |
@@ -22,6 +22,8 @@ type UserForList = User & { | |||
22 | styleUrls: [ './user-list.component.scss' ] | 22 | styleUrls: [ './user-list.component.scss' ] |
23 | }) | 23 | }) |
24 | export class UserListComponent extends RestTable implements OnInit { | 24 | export class UserListComponent extends RestTable implements OnInit { |
25 | private static readonly LOCAL_STORAGE_SELECTED_COLUMNS_KEY = 'admin-user-list-selected-columns' | ||
26 | |||
25 | @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent | 27 | @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent |
26 | 28 | ||
27 | users: (User & { accountMutedStatus: AccountMutedStatus })[] = [] | 29 | users: (User & { accountMutedStatus: AccountMutedStatus })[] = [] |
@@ -56,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit { | |||
56 | 58 | ||
57 | requiresEmailVerification = false | 59 | requiresEmailVerification = false |
58 | 60 | ||
59 | private _selectedColumns: string[] | 61 | private _selectedColumns: string[] = [] |
60 | 62 | ||
61 | constructor ( | 63 | constructor ( |
62 | protected route: ActivatedRoute, | 64 | protected route: ActivatedRoute, |
@@ -66,7 +68,8 @@ export class UserListComponent extends RestTable implements OnInit { | |||
66 | private serverService: ServerService, | 68 | private serverService: ServerService, |
67 | private auth: AuthService, | 69 | private auth: AuthService, |
68 | private blocklist: BlocklistService, | 70 | private blocklist: BlocklistService, |
69 | private userAdminService: UserAdminService | 71 | private userAdminService: UserAdminService, |
72 | private peertubeLocalStorage: LocalStorageService | ||
70 | ) { | 73 | ) { |
71 | super() | 74 | super() |
72 | } | 75 | } |
@@ -76,11 +79,13 @@ export class UserListComponent extends RestTable implements OnInit { | |||
76 | } | 79 | } |
77 | 80 | ||
78 | get selectedColumns () { | 81 | get selectedColumns () { |
79 | return this._selectedColumns | 82 | return this._selectedColumns || [] |
80 | } | 83 | } |
81 | 84 | ||
82 | set selectedColumns (val: string[]) { | 85 | set selectedColumns (val: string[]) { |
83 | this._selectedColumns = val | 86 | this._selectedColumns = val |
87 | |||
88 | this.saveSelectedColumns() | ||
84 | } | 89 | } |
85 | 90 | ||
86 | ngOnInit () { | 91 | ngOnInit () { |
@@ -126,14 +131,35 @@ export class UserListComponent extends RestTable implements OnInit { | |||
126 | { id: 'role', label: $localize`Role` }, | 131 | { id: 'role', label: $localize`Role` }, |
127 | { id: 'email', label: $localize`Email` }, | 132 | { id: 'email', label: $localize`Email` }, |
128 | { id: 'quota', label: $localize`Video quota` }, | 133 | { id: 'quota', label: $localize`Video quota` }, |
129 | { id: 'createdAt', label: $localize`Created` } | 134 | { id: 'createdAt', label: $localize`Created` }, |
135 | { id: 'lastLoginDate', label: $localize`Last login` }, | ||
136 | |||
137 | { id: 'quotaDaily', label: $localize`Daily quota` }, | ||
138 | { id: 'pluginAuth', label: $localize`Auth plugin` } | ||
130 | ] | 139 | ] |
131 | 140 | ||
132 | this.selectedColumns = this.columns.map(c => c.id) | 141 | this.loadSelectedColumns() |
142 | } | ||
143 | |||
144 | loadSelectedColumns () { | ||
145 | const result = this.peertubeLocalStorage.getItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY) | ||
146 | |||
147 | if (result) { | ||
148 | try { | ||
149 | this.selectedColumns = JSON.parse(result) | ||
150 | return | ||
151 | } catch (err) { | ||
152 | console.error('Cannot load selected columns.', err) | ||
153 | } | ||
154 | } | ||
155 | |||
156 | // Default behaviour | ||
157 | this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ] | ||
158 | return | ||
159 | } | ||
133 | 160 | ||
134 | this.columns.push({ id: 'quotaDaily', label: $localize`Daily quota` }) | 161 | saveSelectedColumns () { |
135 | this.columns.push({ id: 'pluginAuth', label: $localize`Auth plugin` }) | 162 | this.peertubeLocalStorage.setItem(UserListComponent.LOCAL_STORAGE_SELECTED_COLUMNS_KEY, JSON.stringify(this.selectedColumns)) |
136 | this.columns.push({ id: 'lastLoginDate', label: $localize`Last login` }) | ||
137 | } | 163 | } |
138 | 164 | ||
139 | getIdentifier () { | 165 | getIdentifier () { |
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts index d8b039187..7b765f7fc 100644 --- a/client/src/app/core/rest/rest-table.ts +++ b/client/src/app/core/rest/rest-table.ts | |||
@@ -39,6 +39,10 @@ export abstract class RestTable { | |||
39 | } | 39 | } |
40 | } | 40 | } |
41 | 41 | ||
42 | saveSort () { | ||
43 | peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) | ||
44 | } | ||
45 | |||
42 | loadLazy (event: LazyLoadEvent) { | 46 | loadLazy (event: LazyLoadEvent) { |
43 | logger('Load lazy %o.', event) | 47 | logger('Load lazy %o.', event) |
44 | 48 | ||
@@ -60,10 +64,6 @@ export abstract class RestTable { | |||
60 | this.saveSort() | 64 | this.saveSort() |
61 | } | 65 | } |
62 | 66 | ||
63 | saveSort () { | ||
64 | peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) | ||
65 | } | ||
66 | |||
67 | onSearch (search: string) { | 67 | onSearch (search: string) { |
68 | this.search = search | 68 | this.search = search |
69 | this.reloadData() | 69 | this.reloadData() |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html index a07b8b5ee..fe7a59bdb 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.html +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.html | |||
@@ -44,6 +44,7 @@ | |||
44 | [searchable]="false" | 44 | [searchable]="false" |
45 | > | 45 | > |
46 | <ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option> | 46 | <ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option> |
47 | <ng-option i18n value="-originallyPublishedAt">Sort by <strong>"Original Publication Date"</strong></ng-option> | ||
47 | 48 | ||
48 | <ng-option i18n *ngIf="isTrendingSortEnabled('most-viewed')" value="-trending">Sort by <strong>"Recent Views"</strong></ng-option> | 49 | <ng-option i18n *ngIf="isTrendingSortEnabled('most-viewed')" value="-trending">Sort by <strong>"Recent Views"</strong></ng-option> |
49 | <ng-option i18n *ngIf="isTrendingSortEnabled('hot')" value="-hot">Sort by <strong>"Hot"</strong></ng-option> | 50 | <ng-option i18n *ngIf="isTrendingSortEnabled('hot')" value="-hot">Sort by <strong>"Hot"</strong></ng-option> |
diff --git a/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss b/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss index 8cb1ff5b8..6a968ed5c 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-filters-header.component.scss | |||
@@ -101,7 +101,7 @@ | |||
101 | } | 101 | } |
102 | 102 | ||
103 | .sort { | 103 | .sort { |
104 | min-width: 200px; | 104 | min-width: 250px; |
105 | max-width: 300px; | 105 | max-width: 300px; |
106 | height: min-content; | 106 | height: min-content; |
107 | 107 | ||
diff --git a/config/default.yaml b/config/default.yaml index 689f81343..c0d17decf 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -297,6 +297,16 @@ webadmin: | |||
297 | # Set this to false if you don't want to allow config edition in the web interface by instance admins | 297 | # Set this to false if you don't want to allow config edition in the web interface by instance admins |
298 | allowed: true | 298 | allowed: true |
299 | 299 | ||
300 | # XML, Atom or JSON feeds | ||
301 | feeds: | ||
302 | videos: | ||
303 | # Default number of videos displayed in feeds | ||
304 | count: 20 | ||
305 | |||
306 | comments: | ||
307 | # Default number of comments displayed in feeds | ||
308 | count: 20 | ||
309 | |||
300 | cache: | 310 | cache: |
301 | previews: | 311 | previews: |
302 | size: 500 # Max number of previews you want to cache | 312 | size: 500 # Max number of previews you want to cache |
@@ -470,6 +480,9 @@ import: | |||
470 | # Amount of import jobs to execute in parallel | 480 | # Amount of import jobs to execute in parallel |
471 | concurrency: 1 | 481 | concurrency: 1 |
472 | 482 | ||
483 | # Set a custom video import timeout to not block import queue | ||
484 | timeout: '2 hours' | ||
485 | |||
473 | # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html | 486 | # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html |
474 | http: | 487 | http: |
475 | # We recommend to use a HTTP proxy if you enable HTTP import to prevent private URL access from this server | 488 | # We recommend to use a HTTP proxy if you enable HTTP import to prevent private URL access from this server |
diff --git a/config/production.yaml.example b/config/production.yaml.example index 6db43fccf..8fd8d805f 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -293,15 +293,25 @@ webadmin: | |||
293 | # Set this to false if you don't want to allow config edition in the web interface by instance admins | 293 | # Set this to false if you don't want to allow config edition in the web interface by instance admins |
294 | allowed: true | 294 | allowed: true |
295 | 295 | ||
296 | # XML, Atom or JSON feeds | ||
297 | feeds: | ||
298 | videos: | ||
299 | # Default number of videos displayed in feeds | ||
300 | count: 20 | ||
301 | |||
302 | comments: | ||
303 | # Default number of comments displayed in feeds | ||
304 | count: 20 | ||
305 | |||
296 | ############################################################################### | 306 | ############################################################################### |
297 | # | 307 | # |
298 | # From this point, all the following keys can be overridden by the web interface | 308 | # From this point, almost all following keys can be overridden by the web interface |
299 | # (local-production.json file). If you need to change some values, prefer to | 309 | # (local-production.json file). If you need to change some values, prefer to |
300 | # use the web interface because the configuration will be automatically | 310 | # use the web interface because the configuration will be automatically |
301 | # reloaded without any need to restart PeerTube | 311 | # reloaded without any need to restart PeerTube |
302 | # | 312 | # |
303 | # /!\ If you already have a local-production.json file, the modification of the | 313 | # /!\ If you already have a local-production.json file, modification of some of |
304 | # following keys will have no effect /!\ | 314 | # the following keys will have no effect /!\ |
305 | # | 315 | # |
306 | ############################################################################### | 316 | ############################################################################### |
307 | 317 | ||
@@ -478,6 +488,9 @@ import: | |||
478 | # Amount of import jobs to execute in parallel | 488 | # Amount of import jobs to execute in parallel |
479 | concurrency: 1 | 489 | concurrency: 1 |
480 | 490 | ||
491 | # Set a custom video import timeout to not block import queue | ||
492 | timeout: '2 hours' | ||
493 | |||
481 | # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html | 494 | # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html |
482 | http: | 495 | http: |
483 | # We recommend to use a HTTP proxy if you enable HTTP import to prevent private URL access from this server | 496 | # We recommend to use a HTTP proxy if you enable HTTP import to prevent private URL access from this server |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index 8a06bfe93..e13e31aaf 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -32,7 +32,7 @@ import { | |||
32 | usersListValidator, | 32 | usersListValidator, |
33 | usersRegisterValidator, | 33 | usersRegisterValidator, |
34 | usersRemoveValidator, | 34 | usersRemoveValidator, |
35 | usersSortValidator, | 35 | adminUsersSortValidator, |
36 | usersUpdateValidator | 36 | usersUpdateValidator |
37 | } from '../../../middlewares' | 37 | } from '../../../middlewares' |
38 | import { | 38 | import { |
@@ -84,7 +84,7 @@ usersRouter.get('/', | |||
84 | authenticate, | 84 | authenticate, |
85 | ensureUserHasRight(UserRight.MANAGE_USERS), | 85 | ensureUserHasRight(UserRight.MANAGE_USERS), |
86 | paginationValidator, | 86 | paginationValidator, |
87 | usersSortValidator, | 87 | adminUsersSortValidator, |
88 | setDefaultSort, | 88 | setDefaultSort, |
89 | setDefaultPagination, | 89 | setDefaultPagination, |
90 | usersListValidator, | 90 | usersListValidator, |
@@ -277,7 +277,7 @@ async function autocompleteUsers (req: express.Request, res: express.Response) { | |||
277 | } | 277 | } |
278 | 278 | ||
279 | async function listUsers (req: express.Request, res: express.Response) { | 279 | async function listUsers (req: express.Request, res: express.Response) { |
280 | const resultList = await UserModel.listForApi({ | 280 | const resultList = await UserModel.listForAdminApi({ |
281 | start: req.query.start, | 281 | start: req.query.start, |
282 | count: req.query.count, | 282 | count: req.query.count, |
283 | sort: req.query.sort, | 283 | sort: req.query.sort, |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index c929a6726..9eb31ed93 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { Feed } from '@peertube/feed' | ||
3 | import { extname } from 'path' | 2 | import { extname } from 'path' |
3 | import { Feed } from '@peertube/feed' | ||
4 | import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown' | 4 | import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown' |
5 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
6 | import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' | 6 | import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils' |
7 | import { VideoInclude } from '@shared/models' | 7 | import { VideoInclude } from '@shared/models' |
8 | import { buildNSFWFilter } from '../helpers/express-utils' | 8 | import { buildNSFWFilter } from '../helpers/express-utils' |
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import { FEEDS, MIMETYPES, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' | 10 | import { MIMETYPES, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' |
11 | import { | 11 | import { |
12 | asyncMiddleware, | 12 | asyncMiddleware, |
13 | commonVideosFiltersValidator, | 13 | commonVideosFiltersValidator, |
@@ -76,7 +76,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
76 | 76 | ||
77 | const comments = await VideoCommentModel.listForFeed({ | 77 | const comments = await VideoCommentModel.listForFeed({ |
78 | start, | 78 | start, |
79 | count: FEEDS.COUNT, | 79 | count: CONFIG.FEEDS.COMMENTS.COUNT, |
80 | videoId: video ? video.id : undefined, | 80 | videoId: video ? video.id : undefined, |
81 | accountId: account ? account.id : undefined, | 81 | accountId: account ? account.id : undefined, |
82 | videoChannelId: videoChannel ? videoChannel.id : undefined | 82 | videoChannelId: videoChannel ? videoChannel.id : undefined |
@@ -166,7 +166,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
166 | const server = await getServerActor() | 166 | const server = await getServerActor() |
167 | const { data } = await VideoModel.listForApi({ | 167 | const { data } = await VideoModel.listForApi({ |
168 | start, | 168 | start, |
169 | count: FEEDS.COUNT, | 169 | count: CONFIG.FEEDS.VIDEOS.COUNT, |
170 | sort: req.query.sort, | 170 | sort: req.query.sort, |
171 | displayOnlyForFollower: { | 171 | displayOnlyForFollower: { |
172 | actorId: server.id, | 172 | actorId: server.id, |
@@ -202,7 +202,7 @@ async function generateVideoFeedForSubscriptions (req: express.Request, res: exp | |||
202 | 202 | ||
203 | const { data } = await VideoModel.listForApi({ | 203 | const { data } = await VideoModel.listForApi({ |
204 | start, | 204 | start, |
205 | count: FEEDS.COUNT, | 205 | count: CONFIG.FEEDS.VIDEOS.COUNT, |
206 | sort: req.query.sort, | 206 | sort: req.query.sort, |
207 | nsfw, | 207 | nsfw, |
208 | 208 | ||
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 794303743..359f0c31d 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -31,8 +31,8 @@ function checkMissedConfig () { | |||
31 | 'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', | 31 | 'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', |
32 | 'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p', | 32 | 'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p', |
33 | 'transcoding.resolutions.2160p', 'video_studio.enabled', | 33 | 'transcoding.resolutions.2160p', 'video_studio.enabled', |
34 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'auto_blacklist.videos.of_users.enabled', | 34 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'import.videos.timeout', |
35 | 'trending.videos.interval_days', | 35 | 'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days', |
36 | 'client.videos.miniature.display_author_avatar', | 36 | 'client.videos.miniature.display_author_avatar', |
37 | 'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth', | 37 | 'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth', |
38 | 'defaults.publish.download_enabled', 'defaults.publish.comments_enabled', 'defaults.publish.privacy', 'defaults.publish.licence', | 38 | 'defaults.publish.download_enabled', 'defaults.publish.comments_enabled', 'defaults.publish.privacy', 'defaults.publish.licence', |
@@ -44,6 +44,7 @@ function checkMissedConfig () { | |||
44 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', | 44 | 'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.ip_view_expiration', |
45 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', | 45 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', |
46 | 'theme.default', | 46 | 'theme.default', |
47 | 'feeds.videos.count', 'feeds.comments.count', | ||
47 | 'geo_ip.enabled', 'geo_ip.country.database_url', | 48 | 'geo_ip.enabled', 'geo_ip.country.database_url', |
48 | 'remote_redundancy.videos.accept_from', | 49 | 'remote_redundancy.videos.accept_from', |
49 | 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions', | 50 | 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 59a65d6a5..c76a839bc 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -247,6 +247,14 @@ const CONFIG = { | |||
247 | } | 247 | } |
248 | } | 248 | } |
249 | }, | 249 | }, |
250 | FEEDS: { | ||
251 | VIDEOS: { | ||
252 | COUNT: config.get<number>('feeds.videos.count') | ||
253 | }, | ||
254 | COMMENTS: { | ||
255 | COUNT: config.get<number>('feeds.comments.count') | ||
256 | } | ||
257 | }, | ||
250 | ADMIN: { | 258 | ADMIN: { |
251 | get EMAIL () { return config.get<string>('admin.email') } | 259 | get EMAIL () { return config.get<string>('admin.email') } |
252 | }, | 260 | }, |
@@ -349,6 +357,7 @@ const CONFIG = { | |||
349 | IMPORT: { | 357 | IMPORT: { |
350 | VIDEOS: { | 358 | VIDEOS: { |
351 | get CONCURRENCY () { return config.get<number>('import.videos.concurrency') }, | 359 | get CONCURRENCY () { return config.get<number>('import.videos.concurrency') }, |
360 | get TIMEOUT () { return parseDurationToMs(config.get<string>('import.videos.timeout')) }, | ||
352 | 361 | ||
353 | HTTP: { | 362 | HTTP: { |
354 | get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }, | 363 | get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 909fffdb6..824a30bd2 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -58,7 +58,7 @@ const WEBSERVER = { | |||
58 | 58 | ||
59 | // Sortable columns per schema | 59 | // Sortable columns per schema |
60 | const SORTABLE_COLUMNS = { | 60 | const SORTABLE_COLUMNS = { |
61 | USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ], | 61 | ADMIN_USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ], |
62 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], | 62 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], |
63 | ACCOUNTS: [ 'createdAt' ], | 63 | ACCOUNTS: [ 'createdAt' ], |
64 | JOBS: [ 'createdAt' ], | 64 | JOBS: [ 'createdAt' ], |
@@ -183,7 +183,7 @@ const JOB_TTL: { [id in JobType]: number } = { | |||
183 | 'video-file-import': 1000 * 3600, // 1 hour | 183 | 'video-file-import': 1000 * 3600, // 1 hour |
184 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long | 184 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long |
185 | 'video-studio-edition': 1000 * 3600 * 10, // 10 hours | 185 | 'video-studio-edition': 1000 * 3600 * 10, // 10 hours |
186 | 'video-import': 1000 * 3600 * 2, // 2 hours | 186 | 'video-import': CONFIG.IMPORT.VIDEOS.TIMEOUT, |
187 | 'email': 60000 * 10, // 10 minutes | 187 | 'email': 60000 * 10, // 10 minutes |
188 | 'actor-keys': 60000 * 20, // 20 minutes | 188 | 'actor-keys': 60000 * 20, // 20 minutes |
189 | 'videos-views-stats': undefined, // Unlimited | 189 | 'videos-views-stats': undefined, // Unlimited |
@@ -766,12 +766,6 @@ const CUSTOM_HTML_TAG_COMMENTS = { | |||
766 | SERVER_CONFIG: '<!-- server config -->' | 766 | SERVER_CONFIG: '<!-- server config -->' |
767 | } | 767 | } |
768 | 768 | ||
769 | // --------------------------------------------------------------------------- | ||
770 | |||
771 | const FEEDS = { | ||
772 | COUNT: 20 | ||
773 | } | ||
774 | |||
775 | const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000 | 769 | const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000 |
776 | const LOG_FILENAME = 'peertube.log' | 770 | const LOG_FILENAME = 'peertube.log' |
777 | const AUDIT_LOG_FILENAME = 'peertube-audit.log' | 771 | const AUDIT_LOG_FILENAME = 'peertube-audit.log' |
@@ -939,7 +933,6 @@ export { | |||
939 | ROUTE_CACHE_LIFETIME, | 933 | ROUTE_CACHE_LIFETIME, |
940 | SORTABLE_COLUMNS, | 934 | SORTABLE_COLUMNS, |
941 | HLS_STREAMING_PLAYLIST_DIRECTORY, | 935 | HLS_STREAMING_PLAYLIST_DIRECTORY, |
942 | FEEDS, | ||
943 | JOB_TTL, | 936 | JOB_TTL, |
944 | DEFAULT_THEME_NAME, | 937 | DEFAULT_THEME_NAME, |
945 | NSFW_POLICY_TYPES, | 938 | NSFW_POLICY_TYPES, |
diff --git a/server/lib/schedulers/geo-ip-update-scheduler.ts b/server/lib/schedulers/geo-ip-update-scheduler.ts index 9dda6d76c..b06f5a9b5 100644 --- a/server/lib/schedulers/geo-ip-update-scheduler.ts +++ b/server/lib/schedulers/geo-ip-update-scheduler.ts | |||
@@ -6,7 +6,7 @@ export class GeoIPUpdateScheduler extends AbstractScheduler { | |||
6 | 6 | ||
7 | private static instance: AbstractScheduler | 7 | private static instance: AbstractScheduler |
8 | 8 | ||
9 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.YOUTUBE_DL_UPDATE | 9 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.GEO_IP_UPDATE |
10 | 10 | ||
11 | private constructor () { | 11 | private constructor () { |
12 | super() | 12 | super() |
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index 3ba668460..c9978e3b4 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -28,7 +28,7 @@ function createSortableColumns (sortableColumns: string[]) { | |||
28 | return sortableColumns.concat(sortableColumnDesc) | 28 | return sortableColumns.concat(sortableColumnDesc) |
29 | } | 29 | } |
30 | 30 | ||
31 | const usersSortValidator = checkSortFactory(SORTABLE_COLUMNS.USERS) | 31 | const adminUsersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ADMIN_USERS) |
32 | const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) | 32 | const accountsSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNTS) |
33 | const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) | 33 | const jobsSortValidator = checkSortFactory(SORTABLE_COLUMNS.JOBS, [ 'jobs' ]) |
34 | const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) | 34 | const abusesSortValidator = checkSortFactory(SORTABLE_COLUMNS.ABUSES) |
@@ -59,7 +59,7 @@ const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CH | |||
59 | // --------------------------------------------------------------------------- | 59 | // --------------------------------------------------------------------------- |
60 | 60 | ||
61 | export { | 61 | export { |
62 | usersSortValidator, | 62 | adminUsersSortValidator, |
63 | abusesSortValidator, | 63 | abusesSortValidator, |
64 | videoChannelsSortValidator, | 64 | videoChannelsSortValidator, |
65 | videoImportsSortValidator, | 65 | videoImportsSortValidator, |
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 326b2e789..20c2222a7 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -66,7 +66,7 @@ import { ActorModel } from '../actor/actor' | |||
66 | import { ActorFollowModel } from '../actor/actor-follow' | 66 | import { ActorFollowModel } from '../actor/actor-follow' |
67 | import { ActorImageModel } from '../actor/actor-image' | 67 | import { ActorImageModel } from '../actor/actor-image' |
68 | import { OAuthTokenModel } from '../oauth/oauth-token' | 68 | import { OAuthTokenModel } from '../oauth/oauth-token' |
69 | import { getSort, throwIfNotValid } from '../utils' | 69 | import { getAdminUsersSort, throwIfNotValid } from '../utils' |
70 | import { VideoModel } from '../video/video' | 70 | import { VideoModel } from '../video/video' |
71 | import { VideoChannelModel } from '../video/video-channel' | 71 | import { VideoChannelModel } from '../video/video-channel' |
72 | import { VideoImportModel } from '../video/video-import' | 72 | import { VideoImportModel } from '../video/video-import' |
@@ -461,7 +461,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
461 | return this.count() | 461 | return this.count() |
462 | } | 462 | } |
463 | 463 | ||
464 | static listForApi (parameters: { | 464 | static listForAdminApi (parameters: { |
465 | start: number | 465 | start: number |
466 | count: number | 466 | count: number |
467 | sort: string | 467 | sort: string |
@@ -497,7 +497,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
497 | const query: FindOptions = { | 497 | const query: FindOptions = { |
498 | offset: start, | 498 | offset: start, |
499 | limit: count, | 499 | limit: count, |
500 | order: getSort(sort), | 500 | order: getAdminUsersSort(sort), |
501 | where | 501 | where |
502 | } | 502 | } |
503 | 503 | ||
diff --git a/server/models/utils.ts b/server/models/utils.ts index b57290aff..88e31f22e 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -11,8 +11,6 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt | |||
11 | 11 | ||
12 | if (field.toLowerCase() === 'match') { // Search | 12 | if (field.toLowerCase() === 'match') { // Search |
13 | finalField = Sequelize.col('similarity') | 13 | finalField = Sequelize.col('similarity') |
14 | } else if (field === 'videoQuotaUsed') { // Users list | ||
15 | finalField = Sequelize.col('videoQuotaUsed') | ||
16 | } else { | 14 | } else { |
17 | finalField = field | 15 | finalField = field |
18 | } | 16 | } |
@@ -20,6 +18,25 @@ function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderIt | |||
20 | return [ [ finalField, direction ], lastSort ] | 18 | return [ [ finalField, direction ], lastSort ] |
21 | } | 19 | } |
22 | 20 | ||
21 | function getAdminUsersSort (value: string): OrderItem[] { | ||
22 | const { direction, field } = buildDirectionAndField(value) | ||
23 | |||
24 | let finalField: string | ReturnType<typeof Sequelize.col> | ||
25 | |||
26 | if (field === 'videoQuotaUsed') { // Users list | ||
27 | finalField = Sequelize.col('videoQuotaUsed') | ||
28 | } else { | ||
29 | finalField = field | ||
30 | } | ||
31 | |||
32 | const nullPolicy = direction === 'ASC' | ||
33 | ? 'NULLS FIRST' | ||
34 | : 'NULLS LAST' | ||
35 | |||
36 | // FIXME: typings | ||
37 | return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ] | ||
38 | } | ||
39 | |||
23 | function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { | 40 | function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
24 | const { direction, field } = buildDirectionAndField(value) | 41 | const { direction, field } = buildDirectionAndField(value) |
25 | 42 | ||
@@ -260,6 +277,7 @@ export { | |||
260 | buildLocalAccountIdsIn, | 277 | buildLocalAccountIdsIn, |
261 | getSort, | 278 | getSort, |
262 | getCommentSort, | 279 | getCommentSort, |
280 | getAdminUsersSort, | ||
263 | getVideoSort, | 281 | getVideoSort, |
264 | getBlacklistSort, | 282 | getBlacklistSort, |
265 | createSimilarityAttribute, | 283 | createSimilarityAttribute, |
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts index 9b8fbe3e2..c497f7840 100644 --- a/server/tests/api/live/live.ts +++ b/server/tests/api/live/live.ts | |||
@@ -654,7 +654,7 @@ describe('Test live', function () { | |||
654 | }) | 654 | }) |
655 | 655 | ||
656 | it('Should save a non permanent live replay', async function () { | 656 | it('Should save a non permanent live replay', async function () { |
657 | this.timeout(120000) | 657 | this.timeout(240000) |
658 | 658 | ||
659 | await commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) | 659 | await commands[0].waitUntilPublished({ videoId: liveVideoReplayId }) |
660 | 660 | ||
diff --git a/shared/models/videos/video-sort-field.type.ts b/shared/models/videos/video-sort-field.type.ts index 5073848b8..7fa07fa73 100644 --- a/shared/models/videos/video-sort-field.type.ts +++ b/shared/models/videos/video-sort-field.type.ts | |||
@@ -2,6 +2,7 @@ export type VideoSortField = | |||
2 | 'name' | '-name' | | 2 | 'name' | '-name' | |
3 | 'duration' | '-duration' | | 3 | 'duration' | '-duration' | |
4 | 'publishedAt' | '-publishedAt' | | 4 | 'publishedAt' | '-publishedAt' | |
5 | 'originallyPublishedAt' | '-originallyPublishedAt' | | ||
5 | 'createdAt' | '-createdAt' | | 6 | 'createdAt' | '-createdAt' | |
6 | 'views' | '-views' | | 7 | 'views' | '-views' | |
7 | 'likes' | '-likes' | | 8 | 'likes' | '-likes' | |
diff --git a/support/doc/production.md b/support/doc/production.md index 3c75b6cab..6d7744b1f 100644 --- a/support/doc/production.md +++ b/support/doc/production.md | |||
@@ -328,7 +328,7 @@ Copy new configuration defaults values and update your configuration file: | |||
328 | 328 | ||
329 | ```bash | 329 | ```bash |
330 | $ sudo -u peertube cp /var/www/peertube/versions/peertube-${VERSION}/config/default.yaml /var/www/peertube/config/default.yaml | 330 | $ sudo -u peertube cp /var/www/peertube/versions/peertube-${VERSION}/config/default.yaml /var/www/peertube/config/default.yaml |
331 | $ diff /var/www/peertube/versions/peertube-${VERSION}/config/production.yaml.example /var/www/peertube/config/production.yaml | 331 | $ diff -u /var/www/peertube/versions/peertube-${VERSION}/config/production.yaml.example /var/www/peertube/config/production.yaml |
332 | ``` | 332 | ``` |
333 | 333 | ||
334 | Change the link to point to the latest version: | 334 | Change the link to point to the latest version: |
@@ -345,7 +345,7 @@ Check changes in nginx configuration: | |||
345 | 345 | ||
346 | ```bash | 346 | ```bash |
347 | $ cd /var/www/peertube/versions | 347 | $ cd /var/www/peertube/versions |
348 | $ diff "$(ls --sort=t | head -2 | tail -1)/support/nginx/peertube" "$(ls --sort=t | head -1)/support/nginx/peertube" | 348 | $ diff -u "$(ls --sort=t | head -2 | tail -1)/support/nginx/peertube" "$(ls --sort=t | head -1)/support/nginx/peertube" |
349 | ``` | 349 | ``` |
350 | 350 | ||
351 | ### systemd | 351 | ### systemd |
@@ -354,7 +354,7 @@ Check changes in systemd configuration: | |||
354 | 354 | ||
355 | ```bash | 355 | ```bash |
356 | $ cd /var/www/peertube/versions | 356 | $ cd /var/www/peertube/versions |
357 | $ diff "$(ls --sort=t | head -2 | tail -1)/support/systemd/peertube.service" "$(ls --sort=t | head -1)/support/systemd/peertube.service" | 357 | $ diff -u "$(ls --sort=t | head -2 | tail -1)/support/systemd/peertube.service" "$(ls --sort=t | head -1)/support/systemd/peertube.service" |
358 | ``` | 358 | ``` |
359 | 359 | ||
360 | ### Restart PeerTube | 360 | ### Restart PeerTube |