diff options
author | Felix Ableitner <me@nutomic.com> | 2018-08-28 02:01:35 -0500 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-28 09:01:35 +0200 |
commit | bee0abffff73804d816b90c7fd599e0a51c09d61 (patch) | |
tree | fae6d58637f9c63a3800090277f8e130b43442dd /client/src | |
parent | c907c2fa3fd7c0a741117a0204d0ebca675124bd (diff) | |
download | PeerTube-bee0abffff73804d816b90c7fd599e0a51c09d61.tar.gz PeerTube-bee0abffff73804d816b90c7fd599e0a51c09d61.tar.zst PeerTube-bee0abffff73804d816b90c7fd599e0a51c09d61.zip |
Implement daily upload limit (#956)
* Implement daily upload limit (ref #652)
* remove duplicate code
* review fixes
* fix tests?
* whitespace fixes, finish leftover todo
* fix tests
* added some new tests
* use different config value for tests
* remove todo
Diffstat (limited to 'client/src')
9 files changed, 88 insertions, 21 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index 49b89cef4..ca7890d84 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -142,6 +142,20 @@ | |||
142 | {{ formErrors.userVideoQuota }} | 142 | {{ formErrors.userVideoQuota }} |
143 | </div> | 143 | </div> |
144 | </div> | 144 | </div> |
145 | |||
146 | <div class="form-group"> | ||
147 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> | ||
148 | <div class="peertube-select-container"> | ||
149 | <select id="userVideoQuotaDaily" formControlName="userVideoQuotaDaily"> | ||
150 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> | ||
151 | {{ videoQuotaDailyOption.label }} | ||
152 | </option> | ||
153 | </select> | ||
154 | </div> | ||
155 | <div *ngIf="formErrors.userVideoQuotaDaily" class="form-error"> | ||
156 | {{ formErrors.userVideoQuotaDaily }} | ||
157 | </div> | ||
158 | </div> | ||
145 | </ng-template> | 159 | </ng-template> |
146 | </ngb-tab> | 160 | </ngb-tab> |
147 | 161 | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index fd6784415..3b6dabcb9 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -15,10 +15,7 @@ import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/ | |||
15 | styleUrls: [ './edit-custom-config.component.scss' ] | 15 | styleUrls: [ './edit-custom-config.component.scss' ] |
16 | }) | 16 | }) |
17 | export class EditCustomConfigComponent extends FormReactive implements OnInit { | 17 | export class EditCustomConfigComponent extends FormReactive implements OnInit { |
18 | customConfig: CustomConfig | 18 | static videoQuotaOptions = [ |
19 | resolutions = [ '240p', '360p', '480p', '720p', '1080p' ] | ||
20 | |||
21 | videoQuotaOptions = [ | ||
22 | { value: -1, label: 'Unlimited' }, | 19 | { value: -1, label: 'Unlimited' }, |
23 | { value: 0, label: '0' }, | 20 | { value: 0, label: '0' }, |
24 | { value: 100 * 1024 * 1024, label: '100MB' }, | 21 | { value: 100 * 1024 * 1024, label: '100MB' }, |
@@ -28,6 +25,20 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
28 | { value: 20 * 1024 * 1024 * 1024, label: '20GB' }, | 25 | { value: 20 * 1024 * 1024 * 1024, label: '20GB' }, |
29 | { value: 50 * 1024 * 1024 * 1024, label: '50GB' } | 26 | { value: 50 * 1024 * 1024 * 1024, label: '50GB' } |
30 | ] | 27 | ] |
28 | static videoQuotaDailyOptions = [ | ||
29 | { value: -1, label: 'Unlimited' }, | ||
30 | { value: 0, label: '0' }, | ||
31 | { value: 10 * 1024 * 1024, label: '10MB' }, | ||
32 | { value: 50 * 1024 * 1024, label: '50MB' }, | ||
33 | { value: 100 * 1024 * 1024, label: '100MB' }, | ||
34 | { value: 500 * 1024 * 1024, label: '500MB' }, | ||
35 | { value: 2 * 1024 * 1024 * 1024, label: '2GB' }, | ||
36 | { value: 5 * 1024 * 1024 * 1024, label: '5GB' } | ||
37 | ] | ||
38 | |||
39 | customConfig: CustomConfig | ||
40 | resolutions = [ '240p', '360p', '480p', '720p', '1080p' ] | ||
41 | |||
31 | transcodingThreadOptions = [ | 42 | transcodingThreadOptions = [ |
32 | { value: 0, label: 'Auto (via ffmpeg)' }, | 43 | { value: 0, label: 'Auto (via ffmpeg)' }, |
33 | { value: 1, label: '1' }, | 44 | { value: 1, label: '1' }, |
@@ -75,6 +86,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
75 | importVideosTorrentEnabled: null, | 86 | importVideosTorrentEnabled: null, |
76 | adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, | 87 | adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, |
77 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 88 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, |
89 | userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | ||
78 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, | 90 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, |
79 | transcodingEnabled: null, | 91 | transcodingEnabled: null, |
80 | customizationJavascript: null, | 92 | customizationJavascript: null, |
@@ -173,7 +185,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
173 | email: this.form.value['adminEmail'] | 185 | email: this.form.value['adminEmail'] |
174 | }, | 186 | }, |
175 | user: { | 187 | user: { |
176 | videoQuota: this.form.value['userVideoQuota'] | 188 | videoQuota: this.form.value['userVideoQuota'], |
189 | videoQuotaDaily: this.form.value['userVideoQuotaDaily'] | ||
177 | }, | 190 | }, |
178 | transcoding: { | 191 | transcoding: { |
179 | enabled: this.form.value['transcodingEnabled'], | 192 | enabled: this.form.value['transcodingEnabled'], |
@@ -231,6 +244,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
231 | signupLimit: this.customConfig.signup.limit, | 244 | signupLimit: this.customConfig.signup.limit, |
232 | adminEmail: this.customConfig.admin.email, | 245 | adminEmail: this.customConfig.admin.email, |
233 | userVideoQuota: this.customConfig.user.videoQuota, | 246 | userVideoQuota: this.customConfig.user.videoQuota, |
247 | userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, | ||
234 | transcodingThreads: this.customConfig.transcoding.threads, | 248 | transcodingThreads: this.customConfig.transcoding.threads, |
235 | transcodingEnabled: this.customConfig.transcoding.enabled, | 249 | transcodingEnabled: this.customConfig.transcoding.enabled, |
236 | customizationJavascript: this.customConfig.instance.customizations.javascript, | 250 | customizationJavascript: this.customConfig.instance.customizations.javascript, |
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html index 4626a40c9..bb745d6aa 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.component.html +++ b/client/src/app/+admin/users/user-edit/user-edit.component.html | |||
@@ -61,6 +61,15 @@ | |||
61 | </option> | 61 | </option> |
62 | </select> | 62 | </select> |
63 | </div> | 63 | </div> |
64 | |||
65 | <label i18n for="videoQuotaDaily">Daily video quota</label> | ||
66 | <div class="peertube-select-container"> | ||
67 | <select id="videoQuotaDaily" formControlName="videoQuotaDaily"> | ||
68 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> | ||
69 | {{ videoQuotaDailyOption.label }} | ||
70 | </option> | ||
71 | </select> | ||
72 | </div> | ||
64 | 73 | ||
65 | <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()"> | 74 | <div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()"> |
66 | Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br /> | 75 | Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br /> |
diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts index ea8c733c3..4e7ca8a1b 100644 --- a/client/src/app/+admin/users/user-edit/user-edit.ts +++ b/client/src/app/+admin/users/user-edit/user-edit.ts | |||
@@ -1,18 +1,15 @@ | |||
1 | import { ServerService } from '../../../core' | 1 | import { ServerService } from '../../../core' |
2 | import { FormReactive } from '../../../shared' | 2 | import { FormReactive } from '../../../shared' |
3 | import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared' | 3 | import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared' |
4 | import { EditCustomConfigComponent } from '../../../+admin/config/edit-custom-config/' | ||
4 | 5 | ||
5 | export abstract class UserEdit extends FormReactive { | 6 | export abstract class UserEdit extends FormReactive { |
6 | videoQuotaOptions = [ | 7 | |
7 | { value: -1, label: 'Unlimited' }, | 8 | // These are used by a HTML select, so convert key into strings |
8 | { value: 0, label: '0' }, | 9 | videoQuotaOptions = EditCustomConfigComponent.videoQuotaOptions |
9 | { value: 100 * 1024 * 1024, label: '100MB' }, | 10 | .map(q => ({ value: q.value.toString(), label: q.label })) |
10 | { value: 500 * 1024 * 1024, label: '500MB' }, | 11 | videoQuotaDailyOptions = EditCustomConfigComponent.videoQuotaDailyOptions |
11 | { value: 1024 * 1024 * 1024, label: '1GB' }, | 12 | .map(q => ({ value: q.value.toString(), label: q.label })) |
12 | { value: 5 * 1024 * 1024 * 1024, label: '5GB' }, | ||
13 | { value: 20 * 1024 * 1024 * 1024, label: '20GB' }, | ||
14 | { value: 50 * 1024 * 1024 * 1024, label: '50GB' } | ||
15 | ].map(q => ({ value: q.value.toString(), label: q.label })) // Used by a HTML select, so convert key into strings | ||
16 | 13 | ||
17 | roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] })) | 14 | roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] })) |
18 | 15 | ||
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts index 06bde582e..5821229b3 100644 --- a/client/src/app/+admin/users/user-edit/user-update.component.ts +++ b/client/src/app/+admin/users/user-edit/user-update.component.ts | |||
@@ -36,11 +36,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
36 | } | 36 | } |
37 | 37 | ||
38 | ngOnInit () { | 38 | ngOnInit () { |
39 | const defaultValues = { videoQuota: '-1' } | 39 | const defaultValues = { videoQuota: '-1', videoQuotaDaily: '-1' } |
40 | this.buildForm({ | 40 | this.buildForm({ |
41 | email: this.userValidatorsService.USER_EMAIL, | 41 | email: this.userValidatorsService.USER_EMAIL, |
42 | role: this.userValidatorsService.USER_ROLE, | 42 | role: this.userValidatorsService.USER_ROLE, |
43 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA | 43 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, |
44 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY | ||
44 | }, defaultValues) | 45 | }, defaultValues) |
45 | 46 | ||
46 | this.paramsSub = this.route.params.subscribe(routeParams => { | 47 | this.paramsSub = this.route.params.subscribe(routeParams => { |
@@ -64,6 +65,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
64 | 65 | ||
65 | // A select in HTML is always mapped as a string, we convert it to number | 66 | // A select in HTML is always mapped as a string, we convert it to number |
66 | userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10) | 67 | userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10) |
68 | userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10) | ||
67 | 69 | ||
68 | this.userService.updateUser(this.userId, userUpdate).subscribe( | 70 | this.userService.updateUser(this.userId, userUpdate).subscribe( |
69 | () => { | 71 | () => { |
@@ -93,7 +95,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy { | |||
93 | this.form.patchValue({ | 95 | this.form.patchValue({ |
94 | email: userJson.email, | 96 | email: userJson.email, |
95 | role: userJson.role, | 97 | role: userJson.role, |
96 | videoQuota: userJson.videoQuota | 98 | videoQuota: userJson.videoQuota, |
99 | videoQuotaDaily: userJson.videoQuotaDaily | ||
97 | }) | 100 | }) |
98 | } | 101 | } |
99 | } | 102 | } |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index 7823fa80e..a1ce12069 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -67,7 +67,8 @@ export class ServerService { | |||
67 | } | 67 | } |
68 | }, | 68 | }, |
69 | user: { | 69 | user: { |
70 | videoQuota: -1 | 70 | videoQuota: -1, |
71 | videoQuotaDaily: -1 | ||
71 | }, | 72 | }, |
72 | import: { | 73 | import: { |
73 | videos: { | 74 | videos: { |
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/forms/form-validators/user-validators.service.ts index ec9566ef3..424553d74 100644 --- a/client/src/app/shared/forms/form-validators/user-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/user-validators.service.ts | |||
@@ -9,6 +9,7 @@ export class UserValidatorsService { | |||
9 | readonly USER_EMAIL: BuildFormValidator | 9 | readonly USER_EMAIL: BuildFormValidator |
10 | readonly USER_PASSWORD: BuildFormValidator | 10 | readonly USER_PASSWORD: BuildFormValidator |
11 | readonly USER_VIDEO_QUOTA: BuildFormValidator | 11 | readonly USER_VIDEO_QUOTA: BuildFormValidator |
12 | readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator | ||
12 | readonly USER_ROLE: BuildFormValidator | 13 | readonly USER_ROLE: BuildFormValidator |
13 | readonly USER_DISPLAY_NAME: BuildFormValidator | 14 | readonly USER_DISPLAY_NAME: BuildFormValidator |
14 | readonly USER_DESCRIPTION: BuildFormValidator | 15 | readonly USER_DESCRIPTION: BuildFormValidator |
@@ -61,6 +62,13 @@ export class UserValidatorsService { | |||
61 | 'min': this.i18n('Quota must be greater than -1.') | 62 | 'min': this.i18n('Quota must be greater than -1.') |
62 | } | 63 | } |
63 | } | 64 | } |
65 | this.USER_VIDEO_QUOTA_DAILY = { | ||
66 | VALIDATORS: [ Validators.required, Validators.min(-1) ], | ||
67 | MESSAGES: { | ||
68 | 'required': this.i18n('Daily upload limit is required.'), | ||
69 | 'min': this.i18n('Daily upload limit must be greater than -1.') | ||
70 | } | ||
71 | } | ||
64 | 72 | ||
65 | this.USER_ROLE = { | 73 | this.USER_ROLE = { |
66 | VALIDATORS: [ Validators.required ], | 74 | VALIDATORS: [ Validators.required ], |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 2748001d0..877f1bf3a 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -16,6 +16,7 @@ export type UserConstructorHash = { | |||
16 | email: string, | 16 | email: string, |
17 | role: UserRole, | 17 | role: UserRole, |
18 | videoQuota?: number, | 18 | videoQuota?: number, |
19 | videoQuotaDaily?: number, | ||
19 | nsfwPolicy?: NSFWPolicyType, | 20 | nsfwPolicy?: NSFWPolicyType, |
20 | autoPlayVideo?: boolean, | 21 | autoPlayVideo?: boolean, |
21 | createdAt?: Date, | 22 | createdAt?: Date, |
@@ -33,6 +34,7 @@ export class User implements UserServerModel { | |||
33 | nsfwPolicy: NSFWPolicyType | 34 | nsfwPolicy: NSFWPolicyType |
34 | autoPlayVideo: boolean | 35 | autoPlayVideo: boolean |
35 | videoQuota: number | 36 | videoQuota: number |
37 | videoQuotaDaily: number | ||
36 | account: Account | 38 | account: Account |
37 | videoChannels: VideoChannel[] | 39 | videoChannels: VideoChannel[] |
38 | createdAt: Date | 40 | createdAt: Date |
@@ -48,6 +50,7 @@ export class User implements UserServerModel { | |||
48 | 50 | ||
49 | this.videoChannels = hash.videoChannels | 51 | this.videoChannels = hash.videoChannels |
50 | this.videoQuota = hash.videoQuota | 52 | this.videoQuota = hash.videoQuota |
53 | this.videoQuotaDaily = hash.videoQuotaDaily | ||
51 | this.nsfwPolicy = hash.nsfwPolicy | 54 | this.nsfwPolicy = hash.nsfwPolicy |
52 | this.autoPlayVideo = hash.autoPlayVideo | 55 | this.autoPlayVideo = hash.autoPlayVideo |
53 | this.createdAt = hash.createdAt | 56 | this.createdAt = hash.createdAt |
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 3ec89ff62..c9ab35b1d 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 | |||
@@ -31,6 +31,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
31 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY | 31 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY |
32 | 32 | ||
33 | userVideoQuotaUsed = 0 | 33 | userVideoQuotaUsed = 0 |
34 | userVideoQuotaUsedDaily = 0 | ||
34 | 35 | ||
35 | isUploadingVideo = false | 36 | isUploadingVideo = false |
36 | isUpdatingVideo = false | 37 | isUpdatingVideo = false |
@@ -68,6 +69,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
68 | 69 | ||
69 | this.userService.getMyVideoQuotaUsed() | 70 | this.userService.getMyVideoQuotaUsed() |
70 | .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed) | 71 | .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed) |
72 | |||
73 | this.userService.getMyVideoQuotaUsed() | ||
74 | .subscribe(data => this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily) | ||
71 | } | 75 | } |
72 | 76 | ||
73 | ngOnDestroy () { | 77 | ngOnDestroy () { |
@@ -115,10 +119,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
115 | return | 119 | return |
116 | } | 120 | } |
117 | 121 | ||
122 | const bytePipes = new BytesPipe() | ||
118 | const videoQuota = this.authService.getUser().videoQuota | 123 | const videoQuota = this.authService.getUser().videoQuota |
119 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { | 124 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { |
120 | const bytePipes = new BytesPipe() | ||
121 | |||
122 | const msg = this.i18n( | 125 | const msg = this.i18n( |
123 | 'Your video quota is exceeded with this video (video size: {{ videoSize }}, used: {{ videoQuotaUsed }}, quota: {{ videoQuota }})', | 126 | 'Your video quota is exceeded with this video (video size: {{ videoSize }}, used: {{ videoQuotaUsed }}, quota: {{ videoQuota }})', |
124 | { | 127 | { |
@@ -131,6 +134,21 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
131 | return | 134 | return |
132 | } | 135 | } |
133 | 136 | ||
137 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily | ||
138 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { | ||
139 | const msg = this.i18n( | ||
140 | 'Your daily video quota is exceeded with this video (video size: {{ videoSize }}, ' + | ||
141 | 'used: {{ videoQuotaUsedDaily }}, quota: {{ videoQuotaDaily }})', | ||
142 | { | ||
143 | videoSize: bytePipes.transform(videofile.size, 0), | ||
144 | videoQuotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0), | ||
145 | videoQuotaDaily: bytePipes.transform(videoQuotaDaily, 0) | ||
146 | } | ||
147 | ) | ||
148 | this.notificationsService.error(this.i18n('Error'), msg) | ||
149 | return | ||
150 | } | ||
151 | |||
134 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') | 152 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') |
135 | let name: string | 153 | let name: string |
136 | 154 | ||