diff options
-rw-r--r-- | client/src/app/+my-account/my-account-settings/my-account-settings.component.ts | 8 | ||||
-rw-r--r-- | client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html | 19 | ||||
-rw-r--r-- | client/src/app/+videos/+video-edit/video-add-components/video-upload.component.scss | 12 | ||||
-rw-r--r-- | client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts | 57 | ||||
-rw-r--r-- | client/src/app/core/notification/notifier.service.ts | 20 | ||||
-rw-r--r-- | client/src/app/helpers/utils.ts | 33 | ||||
-rw-r--r-- | client/src/app/shared/shared-main/video/video.service.ts | 6 | ||||
-rw-r--r-- | client/src/sass/bootstrap.scss | 5 | ||||
-rw-r--r-- | client/src/sass/include/_mixins.scss | 4 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 4 | ||||
-rw-r--r-- | shared/core-utils/miscs/http-error-codes.ts | 419 | ||||
-rw-r--r-- | shared/core-utils/miscs/index.ts | 1 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 16 | ||||
-rw-r--r-- | support/nginx/peertube | 25 |
14 files changed, 574 insertions, 55 deletions
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index 7ea4610d4..d5d019b35 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | import { ViewportScroller } from '@angular/common' | 1 | import { ViewportScroller } from '@angular/common' |
2 | import { HttpErrorResponse } from '@angular/common/http' | ||
2 | import { AfterViewChecked, Component, OnInit } from '@angular/core' | 3 | import { AfterViewChecked, Component, OnInit } from '@angular/core' |
3 | import { AuthService, Notifier, User, UserService } from '@app/core' | 4 | import { AuthService, Notifier, User, UserService } from '@app/core' |
5 | import { uploadErrorHandler } from '@app/helpers' | ||
4 | 6 | ||
5 | @Component({ | 7 | @Component({ |
6 | selector: 'my-account-settings', | 8 | selector: 'my-account-settings', |
@@ -44,7 +46,11 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked { | |||
44 | this.user.updateAccountAvatar(data.avatar) | 46 | this.user.updateAccountAvatar(data.avatar) |
45 | }, | 47 | }, |
46 | 48 | ||
47 | err => this.notifier.error(err.message) | 49 | (err: HttpErrorResponse) => uploadErrorHandler({ |
50 | err, | ||
51 | name: $localize`avatar`, | ||
52 | notifier: this.notifier | ||
53 | }) | ||
48 | ) | 54 | ) |
49 | } | 55 | } |
50 | } | 56 | } |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html index 677fa1197..88ee4e32a 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html | |||
@@ -44,17 +44,30 @@ | |||
44 | </div> | 44 | </div> |
45 | </div> | 45 | </div> |
46 | 46 | ||
47 | <!-- Upload progress/cancel/error/success header --> | ||
47 | <div *ngIf="isUploadingVideo && !error" class="upload-progress-cancel"> | 48 | <div *ngIf="isUploadingVideo && !error" class="upload-progress-cancel"> |
48 | <div class="progress" i18n-title title="Total video quota"> | 49 | <div class="progress" i18n-title title="Total video uploaded"> |
49 | <div class="progress-bar" role="progressbar" [style]="{ width: videoUploadPercents + '%' }" [attr.aria-valuenow]="videoUploadPercents" aria-valuemin="0" [attr.aria-valuemax]="100"> | 50 | <div class="progress-bar" role="progressbar" [style]="{ width: videoUploadPercents + '%' }" [attr.aria-valuenow]="videoUploadPercents" aria-valuemin="0" [attr.aria-valuemax]="100"> |
50 | <span *ngIf="videoUploadPercents === 100 && videoUploaded === false" i18n>Processing…</span> | 51 | <span *ngIf="videoUploadPercents === 100 && videoUploaded === false" i18n>Processing…</span> |
51 | <span *ngIf="videoUploadPercents !== 100 || videoUploaded">{{ videoUploadPercents }}%</span> | 52 | <span *ngIf="videoUploadPercents !== 100 || videoUploaded">{{ videoUploadPercents }}%</span> |
52 | </div> | 53 | </div> |
53 | </div> | 54 | </div> |
54 | <input *ngIf="videoUploaded === false" type="button" value="Cancel" (click)="cancelUpload()" /> | 55 | <input *ngIf="videoUploaded === false" type="button" i18n-value="Cancel ongoing upload of a video" value="Cancel" (click)="cancelUpload()" /> |
55 | </div> | 56 | </div> |
56 | 57 | ||
57 | <div *ngIf="error" class="alert alert-danger"> | 58 | <div *ngIf="error && enableRetryAfterError" class="upload-progress-retry"> |
59 | <div class="progress"> | ||
60 | <div class="progress-bar red" role="progressbar" [style]="{ width: '100%' }" [attr.aria-valuenow]="100" aria-valuemin="0" [attr.aria-valuemax]="100"> | ||
61 | <span>{{ error }}</span> | ||
62 | </div> | ||
63 | </div> | ||
64 | <div class="btn-group" role="group"> | ||
65 | <input type="button" class="btn" i18n-value="Retry failed upload of a video" value="Retry" (click)="retryUpload()" /> | ||
66 | <input type="button" class="btn" i18n-value="Cancel ongoing upload of a video" value="Cancel" (click)="cancelUpload()" /> | ||
67 | </div> | ||
68 | </div> | ||
69 | |||
70 | <div *ngIf="error && !enableRetryAfterError" class="alert alert-danger"> | ||
58 | <div i18n>Sorry, but something went wrong</div> | 71 | <div i18n>Sorry, but something went wrong</div> |
59 | {{ error }} | 72 | {{ error }} |
60 | </div> | 73 | </div> |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.scss b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.scss index 9ebfa2f2f..9549257f6 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.scss +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.scss | |||
@@ -16,27 +16,27 @@ | |||
16 | } | 16 | } |
17 | } | 17 | } |
18 | 18 | ||
19 | .upload-progress-retry, | ||
19 | .upload-progress-cancel { | 20 | .upload-progress-cancel { |
20 | display: flex; | 21 | display: flex; |
21 | margin-top: 25px; | ||
22 | margin-bottom: 40px; | 22 | margin-bottom: 40px; |
23 | 23 | ||
24 | .progress { | 24 | .progress { |
25 | @include progressbar; | 25 | @include progressbar; |
26 | flex-grow: 1; | 26 | flex-grow: 1; |
27 | height: 30px; | 27 | height: 30px; |
28 | font-size: 15px; | 28 | font-size: 14px; |
29 | background-color: rgba(11, 204, 41, 0.16); | 29 | background-color: rgba(11, 204, 41, 0.16); |
30 | 30 | ||
31 | .progress-bar { | 31 | .progress-bar { |
32 | background-color: $green; | 32 | background-color: $green; |
33 | line-height: 30px; | 33 | line-height: 30px; |
34 | text-align: left; | 34 | text-align: left; |
35 | font-weight: $font-bold; | 35 | font-weight: $font-semibold; |
36 | 36 | ||
37 | span { | 37 | span { |
38 | color: pvar(--mainBackgroundColor); | 38 | color: pvar(--mainBackgroundColor); |
39 | margin-left: 18px; | 39 | margin-left: 13px; |
40 | } | 40 | } |
41 | } | 41 | } |
42 | } | 42 | } |
@@ -47,4 +47,8 @@ | |||
47 | 47 | ||
48 | margin-left: 10px; | 48 | margin-left: 10px; |
49 | } | 49 | } |
50 | |||
51 | .btn-group > input:not(:first-child) { | ||
52 | margin-left: 0; | ||
53 | } | ||
50 | } | 54 | } |
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts index 258f5c7a0..fdd0a56e5 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { Subscription } from 'rxjs' | 1 | import { Subscription } from 'rxjs' |
2 | import { HttpEventType, HttpResponse } from '@angular/common/http' | 2 | import { HttpErrorResponse, HttpEventType, HttpResponse } from '@angular/common/http' |
3 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' | 3 | import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' |
4 | import { Router } from '@angular/router' | 4 | import { Router } from '@angular/router' |
5 | import { AuthService, CanComponentDeactivate, Notifier, ServerService, UserService } from '@app/core' | 5 | import { AuthService, CanComponentDeactivate, Notifier, ServerService, UserService } from '@app/core' |
6 | import { scrollToTop } from '@app/helpers' | 6 | import { scrollToTop, uploadErrorHandler } from '@app/helpers' |
7 | import { FormValidatorService } from '@app/shared/shared-forms' | 7 | import { FormValidatorService } from '@app/shared/shared-forms' |
8 | import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' | 8 | import { BytesPipe, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' |
9 | import { LoadingBarService } from '@ngx-loading-bar/core' | 9 | import { LoadingBarService } from '@ngx-loading-bar/core' |
@@ -41,11 +41,13 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
41 | id: 0, | 41 | id: 0, |
42 | uuid: '' | 42 | uuid: '' |
43 | } | 43 | } |
44 | formData: FormData | ||
44 | 45 | ||
45 | waitTranscodingEnabled = true | 46 | waitTranscodingEnabled = true |
46 | previewfileUpload: File | 47 | previewfileUpload: File |
47 | 48 | ||
48 | error: string | 49 | error: string |
50 | enableRetryAfterError: boolean | ||
49 | 51 | ||
50 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC | 52 | protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC |
51 | 53 | ||
@@ -118,6 +120,12 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
118 | this.uploadFirstStep() | 120 | this.uploadFirstStep() |
119 | } | 121 | } |
120 | 122 | ||
123 | retryUpload () { | ||
124 | this.enableRetryAfterError = false | ||
125 | this.error = '' | ||
126 | this.uploadVideo() | ||
127 | } | ||
128 | |||
121 | cancelUpload () { | 129 | cancelUpload () { |
122 | if (this.videoUploadObservable !== null) { | 130 | if (this.videoUploadObservable !== null) { |
123 | this.videoUploadObservable.unsubscribe() | 131 | this.videoUploadObservable.unsubscribe() |
@@ -127,6 +135,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
127 | this.videoUploadObservable = null | 135 | this.videoUploadObservable = null |
128 | 136 | ||
129 | this.firstStepError.emit() | 137 | this.firstStepError.emit() |
138 | this.enableRetryAfterError = false | ||
139 | this.error = '' | ||
130 | 140 | ||
131 | this.notifier.info($localize`Upload cancelled`) | 141 | this.notifier.info($localize`Upload cancelled`) |
132 | } | 142 | } |
@@ -163,20 +173,20 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
163 | const downloadEnabled = true | 173 | const downloadEnabled = true |
164 | const channelId = this.firstStepChannelId.toString() | 174 | const channelId = this.firstStepChannelId.toString() |
165 | 175 | ||
166 | const formData = new FormData() | 176 | this.formData = new FormData() |
167 | formData.append('name', name) | 177 | this.formData.append('name', name) |
168 | // Put the video "private" -> we are waiting the user validation of the second step | 178 | // Put the video "private" -> we are waiting the user validation of the second step |
169 | formData.append('privacy', VideoPrivacy.PRIVATE.toString()) | 179 | this.formData.append('privacy', VideoPrivacy.PRIVATE.toString()) |
170 | formData.append('nsfw', '' + nsfw) | 180 | this.formData.append('nsfw', '' + nsfw) |
171 | formData.append('commentsEnabled', '' + commentsEnabled) | 181 | this.formData.append('commentsEnabled', '' + commentsEnabled) |
172 | formData.append('downloadEnabled', '' + downloadEnabled) | 182 | this.formData.append('downloadEnabled', '' + downloadEnabled) |
173 | formData.append('waitTranscoding', '' + waitTranscoding) | 183 | this.formData.append('waitTranscoding', '' + waitTranscoding) |
174 | formData.append('channelId', '' + channelId) | 184 | this.formData.append('channelId', '' + channelId) |
175 | formData.append('videofile', videofile) | 185 | this.formData.append('videofile', videofile) |
176 | 186 | ||
177 | if (this.previewfileUpload) { | 187 | if (this.previewfileUpload) { |
178 | formData.append('previewfile', this.previewfileUpload) | 188 | this.formData.append('previewfile', this.previewfileUpload) |
179 | formData.append('thumbnailfile', this.previewfileUpload) | 189 | this.formData.append('thumbnailfile', this.previewfileUpload) |
180 | } | 190 | } |
181 | 191 | ||
182 | this.isUploadingVideo = true | 192 | this.isUploadingVideo = true |
@@ -190,7 +200,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
190 | previewfile: this.previewfileUpload | 200 | previewfile: this.previewfileUpload |
191 | }) | 201 | }) |
192 | 202 | ||
193 | this.videoUploadObservable = this.videoService.uploadVideo(formData).subscribe( | 203 | this.uploadVideo() |
204 | } | ||
205 | |||
206 | uploadVideo () { | ||
207 | this.videoUploadObservable = this.videoService.uploadVideo(this.formData).subscribe( | ||
194 | event => { | 208 | event => { |
195 | if (event.type === HttpEventType.UploadProgress) { | 209 | if (event.type === HttpEventType.UploadProgress) { |
196 | this.videoUploadPercents = Math.round(100 * event.loaded / event.total) | 210 | this.videoUploadPercents = Math.round(100 * event.loaded / event.total) |
@@ -203,13 +217,18 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
203 | } | 217 | } |
204 | }, | 218 | }, |
205 | 219 | ||
206 | err => { | 220 | (err: HttpErrorResponse) => { |
207 | // Reset progress | 221 | // Reset progress (but keep isUploadingVideo true) |
208 | this.isUploadingVideo = false | ||
209 | this.videoUploadPercents = 0 | 222 | this.videoUploadPercents = 0 |
210 | this.videoUploadObservable = null | 223 | this.videoUploadObservable = null |
211 | this.firstStepError.emit() | 224 | this.enableRetryAfterError = true |
212 | this.notifier.error(err.message) | 225 | |
226 | this.error = uploadErrorHandler({ | ||
227 | err, | ||
228 | name: $localize`video`, | ||
229 | notifier: this.notifier, | ||
230 | sticky: false | ||
231 | }) | ||
213 | } | 232 | } |
214 | ) | 233 | ) |
215 | } | 234 | } |
diff --git a/client/src/app/core/notification/notifier.service.ts b/client/src/app/core/notification/notifier.service.ts index f736672bb..165bb0c76 100644 --- a/client/src/app/core/notification/notifier.service.ts +++ b/client/src/app/core/notification/notifier.service.ts | |||
@@ -7,31 +7,35 @@ export class Notifier { | |||
7 | 7 | ||
8 | constructor (private messageService: MessageService) { } | 8 | constructor (private messageService: MessageService) { } |
9 | 9 | ||
10 | info (text: string, title?: string, timeout?: number) { | 10 | info (text: string, title?: string, timeout?: number, sticky?: boolean) { |
11 | if (!title) title = $localize`Info` | 11 | if (!title) title = $localize`Info` |
12 | 12 | ||
13 | return this.notify('info', text, title, timeout) | 13 | console.info(`${title}: ${text}`) |
14 | return this.notify('info', text, title, timeout, sticky) | ||
14 | } | 15 | } |
15 | 16 | ||
16 | error (text: string, title?: string, timeout?: number) { | 17 | error (text: string, title?: string, timeout?: number, sticky?: boolean) { |
17 | if (!title) title = $localize`Error` | 18 | if (!title) title = $localize`Error` |
18 | 19 | ||
19 | return this.notify('error', text, title, timeout) | 20 | console.error(`${title}: ${text}`) |
21 | return this.notify('error', text, title, timeout, sticky) | ||
20 | } | 22 | } |
21 | 23 | ||
22 | success (text: string, title?: string, timeout?: number) { | 24 | success (text: string, title?: string, timeout?: number, sticky?: boolean) { |
23 | if (!title) title = $localize`Success` | 25 | if (!title) title = $localize`Success` |
24 | 26 | ||
25 | return this.notify('success', text, title, timeout) | 27 | console.log(`${title}: ${text}`) |
28 | return this.notify('success', text, title, timeout, sticky) | ||
26 | } | 29 | } |
27 | 30 | ||
28 | private notify (severity: 'success' | 'info' | 'warn' | 'error', text: string, title: string, timeout?: number) { | 31 | private notify (severity: 'success' | 'info' | 'warn' | 'error', text: string, title: string, timeout?: number, sticky?: boolean) { |
29 | this.messageService.add({ | 32 | this.messageService.add({ |
30 | severity, | 33 | severity, |
31 | summary: title, | 34 | summary: title, |
32 | detail: text, | 35 | detail: text, |
33 | closable: true, | 36 | closable: true, |
34 | life: timeout || this.TIMEOUT | 37 | life: timeout || this.TIMEOUT, |
38 | sticky | ||
35 | }) | 39 | }) |
36 | } | 40 | } |
37 | } | 41 | } |
diff --git a/client/src/app/helpers/utils.ts b/client/src/app/helpers/utils.ts index 9c805b4ca..f96f26fff 100644 --- a/client/src/app/helpers/utils.ts +++ b/client/src/app/helpers/utils.ts | |||
@@ -1,7 +1,10 @@ | |||
1 | import { DatePipe } from '@angular/common' | 1 | import { DatePipe } from '@angular/common' |
2 | import { HttpErrorResponse } from '@angular/common/http' | ||
3 | import { Notifier } from '@app/core' | ||
2 | import { SelectChannelItem } from '@app/shared/shared-forms' | 4 | import { SelectChannelItem } from '@app/shared/shared-forms' |
3 | import { environment } from '../../environments/environment' | 5 | import { environment } from '../../environments/environment' |
4 | import { AuthService } from '../core/auth' | 6 | import { AuthService } from '../core/auth' |
7 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
5 | 8 | ||
6 | // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript | 9 | // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript |
7 | function getParameterByName (name: string, url: string) { | 10 | function getParameterByName (name: string, url: string) { |
@@ -172,6 +175,33 @@ function isXPercentInViewport (el: HTMLElement, percentVisible: number) { | |||
172 | ) | 175 | ) |
173 | } | 176 | } |
174 | 177 | ||
178 | function uploadErrorHandler (parameters: { | ||
179 | err: HttpErrorResponse | ||
180 | name: string | ||
181 | notifier: Notifier | ||
182 | sticky?: boolean | ||
183 | }) { | ||
184 | const { err, name, notifier, sticky } = { sticky: false, ...parameters } | ||
185 | const title = $localize`The upload failed` | ||
186 | let message = err.message | ||
187 | |||
188 | if (err instanceof ErrorEvent) { // network error | ||
189 | message = $localize`The connection was interrupted` | ||
190 | notifier.error(message, title, null, sticky) | ||
191 | } else if (err.status === HttpStatusCode.REQUEST_TIMEOUT_408) { | ||
192 | message = $localize`Your ${name} file couldn't be transferred before the set timeout (usually 10min)` | ||
193 | notifier.error(message, title, null, sticky) | ||
194 | } else if (err.status === HttpStatusCode.PAYLOAD_TOO_LARGE_413) { | ||
195 | const maxFileSize = err.headers?.get('X-File-Maximum-Size') || '8G' | ||
196 | message = $localize`Your ${name} file was too large (max. size: ${maxFileSize})` | ||
197 | notifier.error(message, title, null, sticky) | ||
198 | } else { | ||
199 | notifier.error(err.message, title) | ||
200 | } | ||
201 | |||
202 | return message | ||
203 | } | ||
204 | |||
175 | export { | 205 | export { |
176 | sortBy, | 206 | sortBy, |
177 | durationToString, | 207 | durationToString, |
@@ -187,5 +217,6 @@ export { | |||
187 | removeElementFromArray, | 217 | removeElementFromArray, |
188 | scrollToTop, | 218 | scrollToTop, |
189 | isInViewport, | 219 | isInViewport, |
190 | isXPercentInViewport | 220 | isXPercentInViewport, |
221 | uploadErrorHandler | ||
191 | } | 222 | } |
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 70be5d7d2..59860c5cb 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Observable } from 'rxjs' | 1 | import { Observable, of, throwError } from 'rxjs' |
2 | import { catchError, map, switchMap } from 'rxjs/operators' | 2 | import { catchError, map, mergeMap, switchMap } from 'rxjs/operators' |
3 | import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' | 3 | import { HttpClient, HttpErrorResponse, HttpParams, HttpRequest } from '@angular/common/http' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService, AuthService } from '@app/core' | 5 | import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService, AuthService } from '@app/core' |
6 | import { objectToFormData } from '@app/helpers' | 6 | import { objectToFormData } from '@app/helpers' |
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index b90bffbfc..208c7f582 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss | |||
@@ -44,6 +44,11 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; | |||
44 | z-index: inherit !important; | 44 | z-index: inherit !important; |
45 | } | 45 | } |
46 | 46 | ||
47 | .btn-group > .btn:not(:first-child) { | ||
48 | border-top-left-radius: 0 !important; | ||
49 | border-bottom-left-radius: 0 !important; | ||
50 | } | ||
51 | |||
47 | .dropdown-menu { | 52 | .dropdown-menu { |
48 | z-index: z(dropdown) + 1 !important; | 53 | z-index: z(dropdown) + 1 !important; |
49 | 54 | ||
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 1a94de5b2..fecae9fbc 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -732,6 +732,10 @@ | |||
732 | &.secondary { | 732 | &.secondary { |
733 | background-color: pvar(--secondaryColor); | 733 | background-color: pvar(--secondaryColor); |
734 | } | 734 | } |
735 | |||
736 | &.red { | ||
737 | background-color: lighten($color: #c54130, $amount: 10); | ||
738 | } | ||
735 | } | 739 | } |
736 | } | 740 | } |
737 | 741 | ||
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index e8480d749..0dcd38ad2 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -174,8 +174,8 @@ function listVideoPrivacies (req: express.Request, res: express.Response) { | |||
174 | } | 174 | } |
175 | 175 | ||
176 | async function addVideo (req: express.Request, res: express.Response) { | 176 | async function addVideo (req: express.Request, res: express.Response) { |
177 | // Processing the video could be long | 177 | // Transferring the video could be long |
178 | // Set timeout to 10 minutes | 178 | // Set timeout to 10 minutes, as Express's default is 2 minutes |
179 | req.setTimeout(1000 * 60 * 10, () => { | 179 | req.setTimeout(1000 * 60 * 10, () => { |
180 | logger.error('Upload video has timed out.') | 180 | logger.error('Upload video has timed out.') |
181 | return res.sendStatus(408) | 181 | return res.sendStatus(408) |
diff --git a/shared/core-utils/miscs/http-error-codes.ts b/shared/core-utils/miscs/http-error-codes.ts new file mode 100644 index 000000000..6dfe73c2e --- /dev/null +++ b/shared/core-utils/miscs/http-error-codes.ts | |||
@@ -0,0 +1,419 @@ | |||
1 | /** | ||
2 | * Hypertext Transfer Protocol (HTTP) response status codes. | ||
3 | * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} | ||
4 | */ | ||
5 | export enum HttpStatusCode { | ||
6 | |||
7 | /** | ||
8 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1 | ||
9 | * | ||
10 | * The server has received the request headers and the client should proceed to send the request body | ||
11 | * (in the case of a request for which a body needs to be sent; for example, a POST request). | ||
12 | * Sending a large request body to a server after a request has been rejected for inappropriate headers would be inefficient. | ||
13 | * To have a server check the request's headers, a client must send Expect: 100-continue as a header in its initial request | ||
14 | * and receive a 100 Continue status code in response before sending the body. The response 417 Expectation Failed indicates | ||
15 | * the request should not be continued. | ||
16 | */ | ||
17 | CONTINUE_100 = 100, | ||
18 | |||
19 | /** | ||
20 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.2 | ||
21 | * | ||
22 | * This code is sent in response to an Upgrade request header by the client, and indicates the protocol the server is switching too. | ||
23 | */ | ||
24 | SWITCHING_PROTOCOLS_101 = 101, | ||
25 | |||
26 | /** | ||
27 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.1 | ||
28 | * | ||
29 | * Standard response for successful HTTP requests. The actual response will depend on the request method used: | ||
30 | * GET: The resource has been fetched and is transmitted in the message body. | ||
31 | * HEAD: The entity headers are in the message body. | ||
32 | * POST: The resource describing the result of the action is transmitted in the message body. | ||
33 | * TRACE: The message body contains the request message as received by the server | ||
34 | */ | ||
35 | OK_200 = 200, | ||
36 | |||
37 | /** | ||
38 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
39 | * | ||
40 | * The request has been fulfilled, resulting in the creation of a new resource, typically after a PUT. | ||
41 | */ | ||
42 | CREATED_201 = 201, | ||
43 | |||
44 | /** | ||
45 | * The request has been accepted for processing, but the processing has not been completed. | ||
46 | * The request might or might not be eventually acted upon, and may be disallowed when processing occurs. | ||
47 | */ | ||
48 | ACCEPTED_202 = 202, | ||
49 | |||
50 | /** | ||
51 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.4 | ||
52 | * | ||
53 | * SINCE HTTP/1.1 | ||
54 | * The server is a transforming proxy that received a 200 OK from its origin, | ||
55 | * but is returning a modified version of the origin's response. | ||
56 | */ | ||
57 | NON_AUTHORITATIVE_INFORMATION_203 = 203, | ||
58 | |||
59 | /** | ||
60 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5 | ||
61 | * | ||
62 | * There is no content to send for this request, but the headers may be useful. | ||
63 | * The user-agent may update its cached headers for this resource with the new ones. | ||
64 | */ | ||
65 | NO_CONTENT_204 = 204, | ||
66 | |||
67 | /** | ||
68 | * The server successfully processed the request, but is not returning any content. | ||
69 | * Unlike a 204 response, this response requires that the requester reset the document view. | ||
70 | */ | ||
71 | RESET_CONTENT_205 = 205, | ||
72 | |||
73 | /** | ||
74 | * The server is delivering only part of the resource (byte serving) due to a range header sent by the client. | ||
75 | * The range header is used by HTTP clients to enable resuming of interrupted downloads, | ||
76 | * or split a download into multiple simultaneous streams. | ||
77 | */ | ||
78 | PARTIAL_CONTENT_206 = 206, | ||
79 | |||
80 | /** | ||
81 | * The message body that follows is an XML message and can contain a number of separate response codes, | ||
82 | * depending on how many sub-requests were made. | ||
83 | */ | ||
84 | MULTI_STATUS_207 = 207, | ||
85 | |||
86 | /** | ||
87 | * The server has fulfilled a request for the resource, | ||
88 | * and the response is a representation of the result of one or more instance-manipulations applied to the current instance. | ||
89 | */ | ||
90 | IM_USED_226 = 226, | ||
91 | |||
92 | /** | ||
93 | * Indicates multiple options for the resource from which the client may choose (via agent-driven content negotiation). | ||
94 | * For example, this code could be used to present multiple video format options, | ||
95 | * to list files with different filename extensions, or to suggest word-sense disambiguation. | ||
96 | */ | ||
97 | MULTIPLE_CHOICES_300 = 300, | ||
98 | |||
99 | /** | ||
100 | * This and all future requests should be directed to the given URI. | ||
101 | */ | ||
102 | MOVED_PERMANENTLY_301 = 301, | ||
103 | |||
104 | /** | ||
105 | * This is an example of industry practice contradicting the standard. | ||
106 | * The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect | ||
107 | * (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 | ||
108 | * with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 | ||
109 | * to distinguish between the two behaviours. However, some Web applications and frameworks | ||
110 | * use the 302 status code as if it were the 303. | ||
111 | */ | ||
112 | FOUND_302 = 302, | ||
113 | |||
114 | /** | ||
115 | * SINCE HTTP/1.1 | ||
116 | * The response to the request can be found under another URI using a GET method. | ||
117 | * When received in response to a POST (or PUT/DELETE), the client should presume that | ||
118 | * the server has received the data and should issue a redirect with a separate GET message. | ||
119 | */ | ||
120 | SEE_OTHER_303 = 303, | ||
121 | |||
122 | /** | ||
123 | * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.1 | ||
124 | * | ||
125 | * Indicates that the resource has not been modified since the version specified by the request headers | ||
126 | * `If-Modified-Since` or `If-None-Match`. | ||
127 | * In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy. | ||
128 | */ | ||
129 | NOT_MODIFIED_304 = 304, | ||
130 | |||
131 | /** | ||
132 | * @deprecated | ||
133 | * SINCE HTTP/1.1 | ||
134 | * The requested resource is available only through a proxy, the address for which is provided in the response. | ||
135 | * Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly handle responses with this status | ||
136 | * code, primarily for security reasons. | ||
137 | */ | ||
138 | USE_PROXY_305 = 305, | ||
139 | |||
140 | /** | ||
141 | * No longer used. Originally meant "Subsequent requests should use the specified proxy." | ||
142 | */ | ||
143 | SWITCH_PROXY_306 = 306, | ||
144 | |||
145 | /** | ||
146 | * SINCE HTTP/1.1 | ||
147 | * In this case, the request should be repeated with another URI; however, future requests should still use the original URI. | ||
148 | * In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the | ||
149 | * original request. | ||
150 | * For example, a POST request should be repeated using another POST request. | ||
151 | */ | ||
152 | TEMPORARY_REDIRECT_307 = 307, | ||
153 | |||
154 | /** | ||
155 | * The request and all future requests should be repeated using another URI. | ||
156 | * 307 and 308 parallel the behaviors of 302 and 301, but do not allow the HTTP method to change. | ||
157 | * So, for example, submitting a form to a permanently redirected resource may continue smoothly. | ||
158 | */ | ||
159 | PERMANENT_REDIRECT_308 = 308, | ||
160 | |||
161 | /** | ||
162 | * The server cannot or will not process the request due to an apparent client error | ||
163 | * (e.g., malformed request syntax, too large size, invalid request message framing, or deceptive request routing). | ||
164 | */ | ||
165 | BAD_REQUEST_400 = 400, | ||
166 | |||
167 | /** | ||
168 | * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.1 | ||
169 | * | ||
170 | * Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet | ||
171 | * been provided. The response must include a `WWW-Authenticate` header field containing a challenge applicable to the | ||
172 | * requested resource. See Basic access authentication and Digest access authentication. 401 semantically means | ||
173 | * "unauthenticated",i.e. the user does not have the necessary credentials. | ||
174 | */ | ||
175 | UNAUTHORIZED_401 = 401, | ||
176 | |||
177 | /** | ||
178 | * Reserved for future use. The original intention was that this code might be used as part of some form of digital | ||
179 | * cash or micro payment scheme, but that has not happened, and this code is not usually used. | ||
180 | * Google Developers API uses this status if a particular developer has exceeded the daily limit on requests. | ||
181 | */ | ||
182 | PAYMENT_REQUIRED_402 = 402, | ||
183 | |||
184 | /** | ||
185 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.3 | ||
186 | * | ||
187 | * The client does not have access rights to the content, i.e. they are unauthorized, so server is rejecting to | ||
188 | * give proper response. Unlike 401, the client's identity is known to the server. | ||
189 | */ | ||
190 | FORBIDDEN_403 = 403, | ||
191 | |||
192 | /** | ||
193 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.2 | ||
194 | * | ||
195 | * The requested resource could not be found but may be available in the future. | ||
196 | * Subsequent requests by the client are permissible. | ||
197 | */ | ||
198 | NOT_FOUND_404 = 404, | ||
199 | |||
200 | /** | ||
201 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.5 | ||
202 | * | ||
203 | * A request method is not supported for the requested resource; | ||
204 | * for example, a GET request on a form that requires data to be presented via POST, or a PUT request on a read-only resource. | ||
205 | */ | ||
206 | METHOD_NOT_ALLOWED_405 = 405, | ||
207 | |||
208 | /** | ||
209 | * The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request. | ||
210 | */ | ||
211 | NOT_ACCEPTABLE_406 = 406, | ||
212 | |||
213 | /** | ||
214 | * The client must first authenticate itself with the proxy. | ||
215 | */ | ||
216 | PROXY_AUTHENTICATION_REQUIRED_407 = 407, | ||
217 | |||
218 | /** | ||
219 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7 | ||
220 | * | ||
221 | * This response is sent on an idle connection by some servers, even without any previous request by the client. | ||
222 | * It means that the server would like to shut down this unused connection. This response is used much more since | ||
223 | * some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing. Also | ||
224 | * note that some servers merely shut down the connection without sending this message. | ||
225 | */ | ||
226 | REQUEST_TIMEOUT_408 = 408, | ||
227 | |||
228 | /** | ||
229 | * Indicates that the request could not be processed because of conflict in the request, | ||
230 | * such as an edit conflict between multiple simultaneous updates. | ||
231 | */ | ||
232 | CONFLICT_409 = 409, | ||
233 | |||
234 | /** | ||
235 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.9 | ||
236 | * | ||
237 | * Indicates that the resource requested is no longer available and will not be available again. | ||
238 | * This should be used when a resource has been intentionally removed and the resource should be purged. | ||
239 | * Upon receiving a 410 status code, the client should not request the resource in the future. | ||
240 | * Clients such as search engines should remove the resource from their indices. | ||
241 | * Most use cases do not require clients and search engines to purge the resource, and a "404 Not Found" may be used instead. | ||
242 | */ | ||
243 | GONE_410 = 410, | ||
244 | |||
245 | /** | ||
246 | * The request did not specify the length of its content, which is required by the requested resource. | ||
247 | */ | ||
248 | LENGTH_REQUIRED_411 = 411, | ||
249 | |||
250 | /** | ||
251 | * The server does not meet one of the preconditions that the requester put on the request. | ||
252 | */ | ||
253 | PRECONDITION_FAILED_412 = 412, | ||
254 | |||
255 | /** | ||
256 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.11 | ||
257 | * | ||
258 | * The request is larger than the server is willing or able to process ; the server might close the connection | ||
259 | * or return an Retry-After header field. | ||
260 | * Previously called "Request Entity Too Large". | ||
261 | */ | ||
262 | PAYLOAD_TOO_LARGE_413 = 413, | ||
263 | |||
264 | /** | ||
265 | * The URI provided was too long for the server to process. Often the result of too much data being encoded as a | ||
266 | * query-string of a GET request, in which case it should be converted to a POST request. | ||
267 | * Called "Request-URI Too Long" previously. | ||
268 | */ | ||
269 | URI_TOO_LONG_414 = 414, | ||
270 | |||
271 | /** | ||
272 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.13 | ||
273 | * | ||
274 | * The request entity has a media type which the server or resource does not support. | ||
275 | * For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format. | ||
276 | */ | ||
277 | UNSUPPORTED_MEDIA_TYPE_415 = 415, | ||
278 | |||
279 | /** | ||
280 | * The client has asked for a portion of the file (byte serving), but the server cannot supply that portion. | ||
281 | * For example, if the client asked for a part of the file that lies beyond the end of the file. | ||
282 | * Called "Requested Range Not Satisfiable" previously. | ||
283 | */ | ||
284 | RANGE_NOT_SATISFIABLE_416 = 416, | ||
285 | |||
286 | /** | ||
287 | * The server cannot meet the requirements of the Expect request-header field. | ||
288 | */ | ||
289 | EXPECTATION_FAILED_417 = 417, | ||
290 | |||
291 | /** | ||
292 | * This code was defined in 1998 as one of the traditional IETF April Fools' jokes, in RFC 2324, Hyper Text Coffee Pot Control Protocol, | ||
293 | * and is not expected to be implemented by actual HTTP servers. The RFC specifies this code should be returned by | ||
294 | * teapots requested to brew coffee. This HTTP status is used as an Easter egg in some websites, including Google.com. | ||
295 | */ | ||
296 | I_AM_A_TEAPOT_418 = 418, | ||
297 | |||
298 | /** | ||
299 | * The request was directed at a server that is not able to produce a response (for example because a connection reuse). | ||
300 | */ | ||
301 | MISDIRECTED_REQUEST_421 = 421, | ||
302 | |||
303 | /** | ||
304 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.3 | ||
305 | * | ||
306 | * The request was well-formed but was unable to be followed due to semantic errors. | ||
307 | */ | ||
308 | UNPROCESSABLE_ENTITY_422 = 422, | ||
309 | |||
310 | /** | ||
311 | * The resource that is being accessed is locked. | ||
312 | */ | ||
313 | LOCKED_423 = 423, | ||
314 | |||
315 | /** | ||
316 | * The request failed due to failure of a previous request (e.g., a PROPPATCH). | ||
317 | */ | ||
318 | FAILED_DEPENDENCY_424 = 424, | ||
319 | |||
320 | /** | ||
321 | * The client should switch to a different protocol such as TLS/1.0, given in the Upgrade header field. | ||
322 | */ | ||
323 | UPGRADE_REQUIRED_426 = 426, | ||
324 | |||
325 | /** | ||
326 | * The origin server requires the request to be conditional. | ||
327 | * Intended to prevent "the 'lost update' problem, where a client | ||
328 | * GETs a resource's state, modifies it, and PUTs it back to the server, | ||
329 | * when meanwhile a third party has modified the state on the server, leading to a conflict." | ||
330 | */ | ||
331 | PRECONDITION_REQUIRED_428 = 428, | ||
332 | |||
333 | /** | ||
334 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-4 | ||
335 | * | ||
336 | * The user has sent too many requests in a given amount of time. Intended for use with rate-limiting schemes. | ||
337 | */ | ||
338 | TOO_MANY_REQUESTS_429 = 429, | ||
339 | |||
340 | /** | ||
341 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-5 | ||
342 | * | ||
343 | * The server is unwilling to process the request because either an individual header field, | ||
344 | * or all the header fields collectively, are too large. | ||
345 | */ | ||
346 | REQUEST_HEADER_FIELDS_TOO_LARGE_431 = 431, | ||
347 | |||
348 | /** | ||
349 | * Official Documentation @ https://tools.ietf.org/html/rfc7725 | ||
350 | * | ||
351 | * A server operator has received a legal demand to deny access to a resource or to a set of resources | ||
352 | * that includes the requested resource. The code 451 was chosen as a reference to the novel Fahrenheit 451. | ||
353 | */ | ||
354 | UNAVAILABLE_FOR_LEGAL_REASONS_451 = 451, | ||
355 | |||
356 | /** | ||
357 | * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. | ||
358 | */ | ||
359 | INTERNAL_SERVER_ERROR_500 = 500, | ||
360 | |||
361 | /** | ||
362 | * The server either does not recognize the request method, or it lacks the ability to fulfill the request. | ||
363 | * Usually this implies future availability (e.g., a new feature of a web-service API). | ||
364 | */ | ||
365 | NOT_IMPLEMENTED_501 = 501, | ||
366 | |||
367 | /** | ||
368 | * The server was acting as a gateway or proxy and received an invalid response from the upstream server. | ||
369 | */ | ||
370 | BAD_GATEWAY_502 = 502, | ||
371 | |||
372 | /** | ||
373 | * The server is currently unavailable (because it is overloaded or down for maintenance). | ||
374 | * Generally, this is a temporary state. | ||
375 | */ | ||
376 | SERVICE_UNAVAILABLE_503 = 503, | ||
377 | |||
378 | /** | ||
379 | * The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. | ||
380 | */ | ||
381 | GATEWAY_TIMEOUT_504 = 504, | ||
382 | |||
383 | /** | ||
384 | * The server does not support the HTTP protocol version used in the request | ||
385 | */ | ||
386 | HTTP_VERSION_NOT_SUPPORTED_505 = 505, | ||
387 | |||
388 | /** | ||
389 | * Transparent content negotiation for the request results in a circular reference. | ||
390 | */ | ||
391 | VARIANT_ALSO_NEGOTIATES_506 = 506, | ||
392 | |||
393 | /** | ||
394 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6 | ||
395 | * | ||
396 | * The 507 (Insufficient Storage) status code means the method could not be performed on the resource because the | ||
397 | * server is unable to store the representation needed to successfully complete the request. This condition is | ||
398 | * considered to be temporary. If the request which received this status code was the result of a user action, | ||
399 | * the request MUST NOT be repeated until it is requested by a separate user action. | ||
400 | */ | ||
401 | INSUFFICIENT_STORAGE_507 = 507, | ||
402 | |||
403 | /** | ||
404 | * The server detected an infinite loop while processing the request. | ||
405 | */ | ||
406 | LOOP_DETECTED_508 = 508, | ||
407 | |||
408 | /** | ||
409 | * Further extensions to the request are required for the server to fulfill it. | ||
410 | */ | ||
411 | NOT_EXTENDED_510 = 510, | ||
412 | |||
413 | /** | ||
414 | * The client needs to authenticate to gain network access. | ||
415 | * Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used | ||
416 | * to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot). | ||
417 | */ | ||
418 | NETWORK_AUTHENTICATION_REQUIRED_511 = 511 | ||
419 | } | ||
diff --git a/shared/core-utils/miscs/index.ts b/shared/core-utils/miscs/index.ts index afd147f24..898fd4791 100644 --- a/shared/core-utils/miscs/index.ts +++ b/shared/core-utils/miscs/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './date' | 1 | export * from './date' |
2 | export * from './miscs' | 2 | export * from './miscs' |
3 | export * from './types' | 3 | export * from './types' |
4 | export * from './http-error-codes' | ||
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 6dd51ec7c..4f9bca729 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -972,6 +972,14 @@ paths: | |||
972 | application/json: | 972 | application/json: |
973 | schema: | 973 | schema: |
974 | $ref: '#/components/schemas/Avatar' | 974 | $ref: '#/components/schemas/Avatar' |
975 | '413': | ||
976 | description: image file too large | ||
977 | headers: | ||
978 | X-File-Maximum-Size: | ||
979 | schema: | ||
980 | type: string | ||
981 | format: Nginx size | ||
982 | description: Maximum file size for the avatar | ||
975 | requestBody: | 983 | requestBody: |
976 | content: | 984 | content: |
977 | multipart/form-data: | 985 | multipart/form-data: |
@@ -1308,6 +1316,14 @@ paths: | |||
1308 | description: user video quota is exceeded with this video | 1316 | description: user video quota is exceeded with this video |
1309 | '408': | 1317 | '408': |
1310 | description: upload has timed out | 1318 | description: upload has timed out |
1319 | '413': | ||
1320 | description: video file too large | ||
1321 | headers: | ||
1322 | X-File-Maximum-Size: | ||
1323 | schema: | ||
1324 | type: string | ||
1325 | format: Nginx size | ||
1326 | description: Maximum file size for the video | ||
1311 | '422': | 1327 | '422': |
1312 | description: invalid input file | 1328 | description: invalid input file |
1313 | requestBody: | 1329 | requestBody: |
diff --git a/support/nginx/peertube b/support/nginx/peertube index f1ef4ccd1..641d254af 100644 --- a/support/nginx/peertube +++ b/support/nginx/peertube | |||
@@ -62,9 +62,9 @@ server { | |||
62 | ## | 62 | ## |
63 | 63 | ||
64 | location @api { | 64 | location @api { |
65 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | 65 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
66 | proxy_set_header Host $host; | 66 | proxy_set_header Host $host; |
67 | proxy_set_header X-Real-IP $remote_addr; | 67 | proxy_set_header X-Real-IP $remote_addr; |
68 | 68 | ||
69 | client_max_body_size 100k; # default is 1M | 69 | client_max_body_size 100k; # default is 1M |
70 | 70 | ||
@@ -81,26 +81,23 @@ server { | |||
81 | } | 81 | } |
82 | 82 | ||
83 | location = /api/v1/users/me/avatar/pick { | 83 | location = /api/v1/users/me/avatar/pick { |
84 | limit_except POST { deny all; } | 84 | limit_except POST HEAD { deny all; } |
85 | 85 | ||
86 | client_max_body_size 2M; # default is 1M | 86 | client_max_body_size 2M; # default is 1M |
87 | add_header X-File-Maximum-Size 2M always; # inform backend of the set value in bytes | ||
87 | 88 | ||
88 | try_files /dev/null @api; | 89 | try_files /dev/null @api; |
89 | } | 90 | } |
90 | 91 | ||
91 | location = /api/v1/videos/upload { | 92 | location = /api/v1/videos/upload { |
92 | limit_except POST { deny all; } | 93 | limit_except POST HEAD { deny all; } |
93 | 94 | ||
94 | # This is the maximum upload size, which roughly matches the maximum size of a video file | 95 | # This is the maximum upload size, which roughly matches the maximum size of a video file. |
95 | # you can send via the API or the web interface. By default we set it to 8GB, but administrators | ||
96 | # can increase or decrease the limit. Currently there's no way to communicate this limit | ||
97 | # to users automatically, so you may want to leave a note in your instance 'about' page if | ||
98 | # you change this. | ||
99 | # | ||
100 | # Note that temporary space is needed equal to the total size of all concurrent uploads. | 96 | # Note that temporary space is needed equal to the total size of all concurrent uploads. |
101 | # This data gets stored in /var/lib/nginx by default, so you may want to put this directory | 97 | # This data gets stored in /var/lib/nginx by default, so you may want to put this directory |
102 | # on a dedicated filesystem. | 98 | # on a dedicated filesystem. |
103 | client_max_body_size 8G; # default is 1M | 99 | client_max_body_size 8G; # default is 1M |
100 | add_header X-File-Maximum-Size 8G always; # inform backend of the set value in bytes | ||
104 | 101 | ||
105 | try_files /dev/null @api; | 102 | try_files /dev/null @api; |
106 | } | 103 | } |