diff options
author | Chocobozzz <me@florianbigard.com> | 2018-01-17 10:32:03 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-01-17 10:41:27 +0100 |
commit | fd206f0b2d7e5c8e00e2817266d90ec54f79e1da (patch) | |
tree | 86b096cf2abd7eb49b892de1c9be855f45a41a9c | |
parent | 9581cabc596acb18c0ad86bcf3a07c2b45e8e47e (diff) | |
download | PeerTube-fd206f0b2d7e5c8e00e2817266d90ec54f79e1da.tar.gz PeerTube-fd206f0b2d7e5c8e00e2817266d90ec54f79e1da.tar.zst PeerTube-fd206f0b2d7e5c8e00e2817266d90ec54f79e1da.zip |
Add ability to update some configuration keys
35 files changed, 964 insertions, 65 deletions
diff --git a/.gitignore b/.gitignore index 2805af0fc..b373b368b 100644 --- a/.gitignore +++ b/.gitignore | |||
@@ -7,6 +7,7 @@ | |||
7 | /test6/ | 7 | /test6/ |
8 | /storage/ | 8 | /storage/ |
9 | /config/production.yaml | 9 | /config/production.yaml |
10 | /config/local.json | ||
10 | /ffmpeg/ | 11 | /ffmpeg/ |
11 | /*.sublime-project | 12 | /*.sublime-project |
12 | /*.sublime-workspace | 13 | /*.sublime-workspace |
diff --git a/client/src/app/+admin/admin-routing.module.ts b/client/src/app/+admin/admin-routing.module.ts index 7ef5c6105..0301d7601 100644 --- a/client/src/app/+admin/admin-routing.module.ts +++ b/client/src/app/+admin/admin-routing.module.ts | |||
@@ -1,14 +1,15 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { RouterModule, Routes } from '@angular/router' | 2 | import { RouterModule, Routes } from '@angular/router' |
3 | import { ConfigRoutes } from '@app/+admin/config' | ||
3 | 4 | ||
4 | import { MetaGuard } from '@ngx-meta/core' | 5 | import { MetaGuard } from '@ngx-meta/core' |
5 | 6 | ||
6 | import { AdminComponent } from './admin.component' | 7 | import { AdminComponent } from './admin.component' |
7 | import { FollowsRoutes } from './follows' | 8 | import { FollowsRoutes } from './follows' |
9 | import { JobsRoutes } from './jobs/job.routes' | ||
8 | import { UsersRoutes } from './users' | 10 | import { UsersRoutes } from './users' |
9 | import { VideoAbusesRoutes } from './video-abuses' | 11 | import { VideoAbusesRoutes } from './video-abuses' |
10 | import { VideoBlacklistRoutes } from './video-blacklist' | 12 | import { VideoBlacklistRoutes } from './video-blacklist' |
11 | import { JobsRoutes } from './jobs/job.routes' | ||
12 | 13 | ||
13 | const adminRoutes: Routes = [ | 14 | const adminRoutes: Routes = [ |
14 | { | 15 | { |
@@ -26,7 +27,8 @@ const adminRoutes: Routes = [ | |||
26 | ...UsersRoutes, | 27 | ...UsersRoutes, |
27 | ...VideoAbusesRoutes, | 28 | ...VideoAbusesRoutes, |
28 | ...VideoBlacklistRoutes, | 29 | ...VideoBlacklistRoutes, |
29 | ...JobsRoutes | 30 | ...JobsRoutes, |
31 | ...ConfigRoutes | ||
30 | ] | 32 | ] |
31 | } | 33 | } |
32 | ] | 34 | ] |
diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html index 0bf4c8aac..e4644498b 100644 --- a/client/src/app/+admin/admin.component.html +++ b/client/src/app/+admin/admin.component.html | |||
@@ -19,6 +19,10 @@ | |||
19 | <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page"> | 19 | <a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page"> |
20 | Jobs | 20 | Jobs |
21 | </a> | 21 | </a> |
22 | |||
23 | <a *ngIf="hasConfigRight()" routerLink="/admin/config" routerLinkActive="active" class="title-page"> | ||
24 | Configuration | ||
25 | </a> | ||
22 | </div> | 26 | </div> |
23 | 27 | ||
24 | <div class="margin-content"> | 28 | <div class="margin-content"> |
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index 75cd50cc7..1a4dd6786 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts | |||
@@ -28,4 +28,8 @@ export class AdminComponent { | |||
28 | hasJobsRight () { | 28 | hasJobsRight () { |
29 | return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) | 29 | return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS) |
30 | } | 30 | } |
31 | |||
32 | hasConfigRight () { | ||
33 | return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION) | ||
34 | } | ||
31 | } | 35 | } |
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index 74ceb25ef..1d9120490 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { ConfigComponent, EditCustomConfigComponent } from '@app/+admin/config' | ||
3 | import { ConfigService } from '@app/+admin/config/shared/config.service' | ||
2 | import { TabsModule } from 'ngx-bootstrap/tabs' | 4 | import { TabsModule } from 'ngx-bootstrap/tabs' |
3 | import { DataTableModule } from 'primeng/components/datatable/datatable' | 5 | import { DataTableModule } from 'primeng/components/datatable/datatable' |
4 | import { SharedModule } from '../shared' | 6 | import { SharedModule } from '../shared' |
@@ -41,7 +43,10 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl | |||
41 | VideoAbuseListComponent, | 43 | VideoAbuseListComponent, |
42 | 44 | ||
43 | JobsComponent, | 45 | JobsComponent, |
44 | JobsListComponent | 46 | JobsListComponent, |
47 | |||
48 | ConfigComponent, | ||
49 | EditCustomConfigComponent | ||
45 | ], | 50 | ], |
46 | 51 | ||
47 | exports: [ | 52 | exports: [ |
@@ -51,7 +56,8 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl | |||
51 | providers: [ | 56 | providers: [ |
52 | FollowService, | 57 | FollowService, |
53 | UserService, | 58 | UserService, |
54 | JobService | 59 | JobService, |
60 | ConfigService | ||
55 | ] | 61 | ] |
56 | }) | 62 | }) |
57 | export class AdminModule { } | 63 | export class AdminModule { } |
diff --git a/client/src/app/+admin/config/config.component.ts b/client/src/app/+admin/config/config.component.ts new file mode 100644 index 000000000..e0eb77278 --- /dev/null +++ b/client/src/app/+admin/config/config.component.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | import { Component } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | template: '<router-outlet></router-outlet>' | ||
5 | }) | ||
6 | export class ConfigComponent { | ||
7 | } | ||
diff --git a/client/src/app/+admin/config/config.routes.ts b/client/src/app/+admin/config/config.routes.ts new file mode 100644 index 000000000..a46b0ddfd --- /dev/null +++ b/client/src/app/+admin/config/config.routes.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | import { Routes } from '@angular/router' | ||
2 | import { EditCustomConfigComponent } from '@app/+admin/config/edit-custom-config' | ||
3 | import { UserRightGuard } from '@app/core' | ||
4 | import { UserRight } from '../../../../../shared/models/users' | ||
5 | import { ConfigComponent } from './config.component' | ||
6 | |||
7 | export const ConfigRoutes: Routes = [ | ||
8 | { | ||
9 | path: 'config', | ||
10 | component: ConfigComponent, | ||
11 | canActivate: [ UserRightGuard ], | ||
12 | data: { | ||
13 | userRight: UserRight.MANAGE_CONFIGURATION | ||
14 | }, | ||
15 | children: [ | ||
16 | { | ||
17 | path: '', | ||
18 | redirectTo: 'edit-custom', | ||
19 | pathMatch: 'full' | ||
20 | }, | ||
21 | { | ||
22 | path: 'edit-custom', | ||
23 | component: EditCustomConfigComponent, | ||
24 | data: { | ||
25 | meta: { | ||
26 | title: 'Following list' | ||
27 | } | ||
28 | } | ||
29 | } | ||
30 | ] | ||
31 | } | ||
32 | ] | ||
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 new file mode 100644 index 000000000..c568a43b4 --- /dev/null +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -0,0 +1,97 @@ | |||
1 | <div class="admin-sub-title">Update PeerTube configuration</div> | ||
2 | |||
3 | <form role="form" (ngSubmit)="formValidated()" [formGroup]="form"> | ||
4 | |||
5 | <div class="inner-form-title">Cache</div> | ||
6 | |||
7 | <div class="form-group"> | ||
8 | <label for="cachePreviewsSize">Preview cache size</label> | ||
9 | <input | ||
10 | type="text" id="cachePreviewsSize" | ||
11 | formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }" | ||
12 | > | ||
13 | <div *ngIf="formErrors.cachePreviewsSize" class="form-error"> | ||
14 | {{ formErrors.cachePreviewsSize }} | ||
15 | </div> | ||
16 | </div> | ||
17 | |||
18 | <div class="inner-form-title">Signup</div> | ||
19 | |||
20 | <div class="form-group"> | ||
21 | <input type="checkbox" id="signupEnabled" formControlName="signupEnabled"> | ||
22 | |||
23 | <label for="signupEnabled"></label> | ||
24 | <label for="signupEnabled">Signup enabled</label> | ||
25 | </div> | ||
26 | |||
27 | <div *ngIf="isSignupEnabled()" class="form-group"> | ||
28 | <label for="signupLimit">Signup limit</label> | ||
29 | <input | ||
30 | type="text" id="signupLimit" | ||
31 | formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }" | ||
32 | > | ||
33 | <div *ngIf="formErrors.signupLimit" class="form-error"> | ||
34 | {{ formErrors.signupLimit }} | ||
35 | </div> | ||
36 | </div> | ||
37 | |||
38 | <div class="inner-form-title">Administrator</div> | ||
39 | |||
40 | <div class="form-group"> | ||
41 | <label for="adminEmail">Admin email</label> | ||
42 | <input | ||
43 | type="text" id="adminEmail" | ||
44 | formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }" | ||
45 | > | ||
46 | <div *ngIf="formErrors.adminEmail" class="form-error"> | ||
47 | {{ formErrors.adminEmail }} | ||
48 | </div> | ||
49 | </div> | ||
50 | |||
51 | <div class="inner-form-title">Users</div> | ||
52 | |||
53 | <div class="form-group"> | ||
54 | <label for="userVideoQuota">User default video quota</label> | ||
55 | <div class="peertube-select-container"> | ||
56 | <select id="userVideoQuota" formControlName="userVideoQuota"> | ||
57 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> | ||
58 | {{ videoQuotaOption.label }} | ||
59 | </option> | ||
60 | </select> | ||
61 | </div> | ||
62 | </div> | ||
63 | |||
64 | <div class="inner-form-title">Transcoding</div> | ||
65 | |||
66 | <div class="form-group"> | ||
67 | <input type="checkbox" id="transcodingEnabled" formControlName="transcodingEnabled"> | ||
68 | |||
69 | <label for="transcodingEnabled"></label> | ||
70 | <label for="transcodingEnabled">Transcoding enabled</label> | ||
71 | </div> | ||
72 | |||
73 | <ng-template [ngIf]="isTranscodingEnabled()"> | ||
74 | |||
75 | <div class="form-group"> | ||
76 | <label for="transcodingThreads">Transcoding threads</label> | ||
77 | <div class="peertube-select-container"> | ||
78 | <select id="transcodingThreads" formControlName="transcodingThreads"> | ||
79 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | ||
80 | {{ transcodingThreadOption.label }} | ||
81 | </option> | ||
82 | </select> | ||
83 | </div> | ||
84 | </div> | ||
85 | |||
86 | <div class="form-group" *ngFor="let resolution of resolutions"> | ||
87 | <input | ||
88 | type="checkbox" [id]="getResolutionKey(resolution)" | ||
89 | [formControlName]="getResolutionKey(resolution)" | ||
90 | > | ||
91 | <label [for]="getResolutionKey(resolution)"></label> | ||
92 | <label [for]="getResolutionKey(resolution)">Resolution {{ resolution }} enabled</label> | ||
93 | </div> | ||
94 | </ng-template> | ||
95 | |||
96 | <input type="submit" value="Update configuration" [disabled]="!form.valid"> | ||
97 | </form> | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss new file mode 100644 index 000000000..0195f44eb --- /dev/null +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -0,0 +1,31 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | input[type=text] { | ||
5 | @include peertube-input-text(340px); | ||
6 | display: block; | ||
7 | } | ||
8 | |||
9 | input[type=checkbox] { | ||
10 | @include peertube-checkbox(1px); | ||
11 | } | ||
12 | |||
13 | .peertube-select-container { | ||
14 | @include peertube-select-container(340px); | ||
15 | } | ||
16 | |||
17 | input[type=submit] { | ||
18 | @include peertube-button; | ||
19 | @include orange-button; | ||
20 | |||
21 | margin-top: 20px; | ||
22 | } | ||
23 | |||
24 | .inner-form-title { | ||
25 | text-transform: uppercase; | ||
26 | color: $orange-color; | ||
27 | font-weight: $font-bold; | ||
28 | font-size: 13px; | ||
29 | margin-top: 30px; | ||
30 | margin-bottom: 10px; | ||
31 | } | ||
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 new file mode 100644 index 000000000..1b3522786 --- /dev/null +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -0,0 +1,174 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { FormBuilder, FormGroup } from '@angular/forms' | ||
3 | import { Router } from '@angular/router' | ||
4 | import { ConfigService } from '@app/+admin/config/shared/config.service' | ||
5 | import { ServerService } from '@app/core/server/server.service' | ||
6 | import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared' | ||
7 | import { ADMIN_EMAIL, CACHE_PREVIEWS_SIZE, SIGNUP_LIMIT, TRANSCODING_THREADS } from '@app/shared/forms/form-validators/custom-config' | ||
8 | import { NotificationsService } from 'angular2-notifications' | ||
9 | import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model' | ||
10 | |||
11 | @Component({ | ||
12 | selector: 'my-edit-custom-config', | ||
13 | templateUrl: './edit-custom-config.component.html', | ||
14 | styleUrls: [ './edit-custom-config.component.scss' ] | ||
15 | }) | ||
16 | export class EditCustomConfigComponent extends FormReactive implements OnInit { | ||
17 | customConfig: CustomConfig | ||
18 | resolutions = [ '240p', '360p', '480p', '720p', '1080p' ] | ||
19 | |||
20 | videoQuotaOptions = [ | ||
21 | { value: -1, label: 'Unlimited' }, | ||
22 | { value: 0, label: '0' }, | ||
23 | { value: 100 * 1024 * 1024, label: '100MB' }, | ||
24 | { value: 500 * 1024 * 1024, label: '500MB' }, | ||
25 | { value: 1024 * 1024 * 1024, label: '1GB' }, | ||
26 | { value: 5 * 1024 * 1024 * 1024, label: '5GB' }, | ||
27 | { value: 20 * 1024 * 1024 * 1024, label: '20GB' }, | ||
28 | { value: 50 * 1024 * 1024 * 1024, label: '50GB' } | ||
29 | ] | ||
30 | transcodingThreadOptions = [ | ||
31 | { value: 1, label: '1' }, | ||
32 | { value: 2, label: '2' }, | ||
33 | { value: 4, label: '4' }, | ||
34 | { value: 8, label: '8' } | ||
35 | ] | ||
36 | |||
37 | form: FormGroup | ||
38 | formErrors = { | ||
39 | cachePreviewsSize: '', | ||
40 | signupLimit: '', | ||
41 | adminEmail: '', | ||
42 | userVideoQuota: '', | ||
43 | transcodingThreads: '' | ||
44 | } | ||
45 | validationMessages = { | ||
46 | cachePreviewsSize: CACHE_PREVIEWS_SIZE.MESSAGES, | ||
47 | signupLimit: SIGNUP_LIMIT.MESSAGES, | ||
48 | adminEmail: ADMIN_EMAIL.MESSAGES, | ||
49 | userVideoQuota: USER_VIDEO_QUOTA.MESSAGES | ||
50 | } | ||
51 | |||
52 | constructor ( | ||
53 | private formBuilder: FormBuilder, | ||
54 | private router: Router, | ||
55 | private notificationsService: NotificationsService, | ||
56 | private configService: ConfigService, | ||
57 | private serverService: ServerService | ||
58 | ) { | ||
59 | super() | ||
60 | } | ||
61 | |||
62 | getResolutionKey (resolution: string) { | ||
63 | return 'transcodingResolution' + resolution | ||
64 | } | ||
65 | |||
66 | buildForm () { | ||
67 | const formGroupData = { | ||
68 | cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ], | ||
69 | signupEnabled: [ ], | ||
70 | signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ], | ||
71 | adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ], | ||
72 | userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ], | ||
73 | transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ], | ||
74 | transcodingEnabled: [ ] | ||
75 | } | ||
76 | |||
77 | for (const resolution of this.resolutions) { | ||
78 | const key = this.getResolutionKey(resolution) | ||
79 | formGroupData[key] = [ false ] | ||
80 | } | ||
81 | |||
82 | this.form = this.formBuilder.group(formGroupData) | ||
83 | |||
84 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) | ||
85 | } | ||
86 | |||
87 | ngOnInit () { | ||
88 | this.buildForm() | ||
89 | |||
90 | this.configService.getCustomConfig() | ||
91 | .subscribe( | ||
92 | res => { | ||
93 | this.customConfig = res | ||
94 | |||
95 | this.updateForm() | ||
96 | }, | ||
97 | |||
98 | err => this.notificationsService.error('Error', err.message) | ||
99 | ) | ||
100 | } | ||
101 | |||
102 | isTranscodingEnabled () { | ||
103 | return this.form.value['transcodingEnabled'] === true | ||
104 | } | ||
105 | |||
106 | isSignupEnabled () { | ||
107 | return this.form.value['signupEnabled'] === true | ||
108 | } | ||
109 | |||
110 | formValidated () { | ||
111 | const data = { | ||
112 | cache: { | ||
113 | previews: { | ||
114 | size: this.form.value['cachePreviewsSize'] | ||
115 | } | ||
116 | }, | ||
117 | signup: { | ||
118 | enabled: this.form.value['signupEnabled'], | ||
119 | limit: this.form.value['signupLimit'] | ||
120 | }, | ||
121 | admin: { | ||
122 | email: this.form.value['adminEmail'] | ||
123 | }, | ||
124 | user: { | ||
125 | videoQuota: this.form.value['userVideoQuota'] | ||
126 | }, | ||
127 | transcoding: { | ||
128 | enabled: this.form.value['transcodingEnabled'], | ||
129 | threads: this.form.value['transcodingThreads'], | ||
130 | resolutions: { | ||
131 | '240p': this.form.value[this.getResolutionKey('240p')], | ||
132 | '360p': this.form.value[this.getResolutionKey('360p')], | ||
133 | '480p': this.form.value[this.getResolutionKey('480p')], | ||
134 | '720p': this.form.value[this.getResolutionKey('720p')], | ||
135 | '1080p': this.form.value[this.getResolutionKey('1080p')] | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | this.configService.updateCustomConfig(data) | ||
141 | .subscribe( | ||
142 | res => { | ||
143 | this.customConfig = res | ||
144 | |||
145 | // Reload general configuration | ||
146 | this.serverService.loadConfig() | ||
147 | |||
148 | this.updateForm() | ||
149 | }, | ||
150 | |||
151 | err => this.notificationsService.error('Error', err.message) | ||
152 | ) | ||
153 | } | ||
154 | |||
155 | private updateForm () { | ||
156 | const data = { | ||
157 | cachePreviewsSize: this.customConfig.cache.previews.size, | ||
158 | signupEnabled: this.customConfig.signup.enabled, | ||
159 | signupLimit: this.customConfig.signup.limit, | ||
160 | adminEmail: this.customConfig.admin.email, | ||
161 | userVideoQuota: this.customConfig.user.videoQuota, | ||
162 | transcodingThreads: this.customConfig.transcoding.threads, | ||
163 | transcodingEnabled: this.customConfig.transcoding.enabled | ||
164 | } | ||
165 | |||
166 | for (const resolution of this.resolutions) { | ||
167 | const key = this.getResolutionKey(resolution) | ||
168 | data[key] = this.customConfig.transcoding.resolutions[resolution] | ||
169 | } | ||
170 | |||
171 | this.form.patchValue(data) | ||
172 | } | ||
173 | |||
174 | } | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/index.ts b/client/src/app/+admin/config/edit-custom-config/index.ts new file mode 100644 index 000000000..1ec12631f --- /dev/null +++ b/client/src/app/+admin/config/edit-custom-config/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './edit-custom-config.component' | |||
diff --git a/client/src/app/+admin/config/index.ts b/client/src/app/+admin/config/index.ts new file mode 100644 index 000000000..b47ebf8db --- /dev/null +++ b/client/src/app/+admin/config/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './edit-custom-config' | ||
2 | export * from './config.component' | ||
3 | export * from './config.routes' | ||
diff --git a/client/src/app/+admin/config/shared/config.service.ts b/client/src/app/+admin/config/shared/config.service.ts new file mode 100644 index 000000000..13f1f6cd2 --- /dev/null +++ b/client/src/app/+admin/config/shared/config.service.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import { HttpClient } from '@angular/common/http' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model' | ||
4 | import { environment } from '../../../../environments/environment' | ||
5 | import { RestExtractor, RestService } from '../../../shared' | ||
6 | |||
7 | @Injectable() | ||
8 | export class ConfigService { | ||
9 | private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/config' | ||
10 | |||
11 | constructor ( | ||
12 | private authHttp: HttpClient, | ||
13 | private restService: RestService, | ||
14 | private restExtractor: RestExtractor | ||
15 | ) {} | ||
16 | |||
17 | getCustomConfig () { | ||
18 | return this.authHttp.get<CustomConfig>(ConfigService.BASE_APPLICATION_URL + '/custom') | ||
19 | .catch(res => this.restExtractor.handleError(res)) | ||
20 | } | ||
21 | |||
22 | updateCustomConfig (data: CustomConfig) { | ||
23 | return this.authHttp.put<CustomConfig>(ConfigService.BASE_APPLICATION_URL + '/custom', data) | ||
24 | .catch(res => this.restExtractor.handleError(res)) | ||
25 | } | ||
26 | } | ||
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.html b/client/src/app/account/account-settings/account-details/account-details.component.html index c8e1e73b0..8f1475a4d 100644 --- a/client/src/app/account/account-settings/account-details/account-details.component.html +++ b/client/src/app/account/account-settings/account-details/account-details.component.html | |||
@@ -1,5 +1,3 @@ | |||
1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
2 | |||
3 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | 1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> |
4 | <div class="form-group"> | 2 | <div class="form-group"> |
5 | <input | 3 | <input |
diff --git a/client/src/app/account/account-settings/account-details/account-details.component.ts b/client/src/app/account/account-settings/account-details/account-details.component.ts index b8c19d8d6..917f31651 100644 --- a/client/src/app/account/account-settings/account-details/account-details.component.ts +++ b/client/src/app/account/account-settings/account-details/account-details.component.ts | |||
@@ -14,8 +14,6 @@ import { FormReactive, User, UserService } from '../../../shared' | |||
14 | export class AccountDetailsComponent extends FormReactive implements OnInit { | 14 | export class AccountDetailsComponent extends FormReactive implements OnInit { |
15 | @Input() user: User = null | 15 | @Input() user: User = null |
16 | 16 | ||
17 | error: string = null | ||
18 | |||
19 | form: FormGroup | 17 | form: FormGroup |
20 | formErrors = {} | 18 | formErrors = {} |
21 | validationMessages = {} | 19 | validationMessages = {} |
@@ -50,7 +48,6 @@ export class AccountDetailsComponent extends FormReactive implements OnInit { | |||
50 | autoPlayVideo | 48 | autoPlayVideo |
51 | } | 49 | } |
52 | 50 | ||
53 | this.error = null | ||
54 | this.userService.updateMyDetails(details).subscribe( | 51 | this.userService.updateMyDetails(details).subscribe( |
55 | () => { | 52 | () => { |
56 | this.notificationsService.success('Success', 'Information updated.') | 53 | this.notificationsService.success('Success', 'Information updated.') |
@@ -58,7 +55,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit { | |||
58 | this.authService.refreshUserInformation() | 55 | this.authService.refreshUserInformation() |
59 | }, | 56 | }, |
60 | 57 | ||
61 | err => this.error = err.message | 58 | err => this.notificationsService.error('Error', err.message) |
62 | ) | 59 | ) |
63 | } | 60 | } |
64 | } | 61 | } |
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index d138d2ba7..94f82e352 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -14,7 +14,7 @@ | |||
14 | 14 | ||
15 | <ul *dropdownMenu class="dropdown-menu"> | 15 | <ul *dropdownMenu class="dropdown-menu"> |
16 | <li> | 16 | <li> |
17 | <a routerLink="/account/settings" class="dropdown-item" title="My account"> | 17 | <a i18n routerLink="/account/settings" class="dropdown-item" title="My account"> |
18 | My account | 18 | My account |
19 | </a> | 19 | </a> |
20 | 20 | ||
diff --git a/client/src/app/shared/forms/form-validators/custom-config.ts b/client/src/app/shared/forms/form-validators/custom-config.ts new file mode 100644 index 000000000..17ae0e75c --- /dev/null +++ b/client/src/app/shared/forms/form-validators/custom-config.ts | |||
@@ -0,0 +1,35 @@ | |||
1 | import { Validators } from '@angular/forms' | ||
2 | |||
3 | export const CACHE_PREVIEWS_SIZE = { | ||
4 | VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], | ||
5 | MESSAGES: { | ||
6 | 'required': 'Preview cache size is required.', | ||
7 | 'min': 'Preview cache size must be greater than 1.', | ||
8 | 'pattern': 'Preview cache size must be a number.' | ||
9 | } | ||
10 | } | ||
11 | |||
12 | export const SIGNUP_LIMIT = { | ||
13 | VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], | ||
14 | MESSAGES: { | ||
15 | 'required': 'Signup limit is required.', | ||
16 | 'min': 'Signup limit must be greater than 1.', | ||
17 | 'pattern': 'Preview cache size must be a number.' | ||
18 | } | ||
19 | } | ||
20 | |||
21 | export const ADMIN_EMAIL = { | ||
22 | VALIDATORS: [ Validators.required, Validators.email ], | ||
23 | MESSAGES: { | ||
24 | 'required': 'Admin email is required.', | ||
25 | 'email': 'Admin email must be valid.' | ||
26 | } | ||
27 | } | ||
28 | |||
29 | export const TRANSCODING_THREADS = { | ||
30 | VALIDATORS: [ Validators.required, Validators.min(1) ], | ||
31 | MESSAGES: { | ||
32 | 'required': 'Transcoding threads is required.', | ||
33 | 'min': 'Transcoding threads must be greater than 1.' | ||
34 | } | ||
35 | } | ||
diff --git a/scripts/clean/server/test.sh b/scripts/clean/server/test.sh index 35d3ad50f..2ceb71244 100755 --- a/scripts/clean/server/test.sh +++ b/scripts/clean/server/test.sh | |||
@@ -3,5 +3,7 @@ | |||
3 | for i in $(seq 1 6); do | 3 | for i in $(seq 1 6); do |
4 | dropdb "peertube_test$i" | 4 | dropdb "peertube_test$i" |
5 | rm -rf "./test$i" | 5 | rm -rf "./test$i" |
6 | rm -f "./config/local-test.json" | ||
7 | rm -f "./config/local-test-$i.json" | ||
6 | createdb "peertube_test$i" | 8 | createdb "peertube_test$i" |
7 | done | 9 | done |
diff --git a/scripts/release.sh b/scripts/release.sh index 2864232a4..ec76bb846 100755 --- a/scripts/release.sh +++ b/scripts/release.sh | |||
@@ -35,6 +35,7 @@ git commit package.json client/package.json -m "Bumped to version $version" || e | |||
35 | git tag -s -a "$version" -m "$version" | 35 | git tag -s -a "$version" -m "$version" |
36 | 36 | ||
37 | npm run build || exit -1 | 37 | npm run build || exit -1 |
38 | rm "./client/dist/stats.json" || exit -1 | ||
38 | 39 | ||
39 | cd ../ || exit -1 | 40 | cd ../ || exit -1 |
40 | 41 | ||
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 35c89835b..f0b2c3d79 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -1,15 +1,34 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { ServerConfig, UserRight } from '../../../shared' | ||
3 | import { CustomConfig } from '../../../shared/models/config/custom-config.model' | ||
4 | import { unlinkPromise, writeFilePromise } from '../../helpers/core-utils' | ||
2 | import { isSignupAllowed } from '../../helpers/utils' | 5 | import { isSignupAllowed } from '../../helpers/utils' |
3 | 6 | import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers' | |
4 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 7 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' |
5 | import { asyncMiddleware } from '../../middlewares' | 8 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' |
6 | import { ServerConfig } from '../../../shared' | 9 | import { omit } from 'lodash' |
7 | 10 | ||
8 | const configRouter = express.Router() | 11 | const configRouter = express.Router() |
9 | 12 | ||
10 | configRouter.get('/', | 13 | configRouter.get('/', |
11 | asyncMiddleware(getConfig) | 14 | asyncMiddleware(getConfig) |
12 | ) | 15 | ) |
16 | configRouter.get('/custom', | ||
17 | authenticate, | ||
18 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | ||
19 | asyncMiddleware(getCustomConfig) | ||
20 | ) | ||
21 | configRouter.put('/custom', | ||
22 | authenticate, | ||
23 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | ||
24 | asyncMiddleware(customConfigUpdateValidator), | ||
25 | asyncMiddleware(updateCustomConfig) | ||
26 | ) | ||
27 | configRouter.delete('/custom', | ||
28 | authenticate, | ||
29 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | ||
30 | asyncMiddleware(deleteCustomConfig) | ||
31 | ) | ||
13 | 32 | ||
14 | async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | 33 | async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) { |
15 | const allowed = await isSignupAllowed() | 34 | const allowed = await isSignupAllowed() |
@@ -43,8 +62,72 @@ async function getConfig (req: express.Request, res: express.Response, next: exp | |||
43 | return res.json(json) | 62 | return res.json(json) |
44 | } | 63 | } |
45 | 64 | ||
65 | async function getCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
66 | const data = customConfig() | ||
67 | |||
68 | return res.json(data).end() | ||
69 | } | ||
70 | |||
71 | async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
72 | await unlinkPromise(CONFIG.CUSTOM_FILE) | ||
73 | |||
74 | reloadConfig() | ||
75 | |||
76 | const data = customConfig() | ||
77 | |||
78 | return res.json(data).end() | ||
79 | } | ||
80 | |||
81 | async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
82 | const toUpdate: CustomConfig = req.body | ||
83 | |||
84 | // Need to change the videoQuota key a little bit | ||
85 | const toUpdateJSON = omit(toUpdate, 'videoQuota') | ||
86 | toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota | ||
87 | |||
88 | await writeFilePromise(CONFIG.CUSTOM_FILE, JSON.stringify(toUpdateJSON)) | ||
89 | |||
90 | reloadConfig() | ||
91 | |||
92 | const data = customConfig() | ||
93 | return res.json(data).end() | ||
94 | } | ||
95 | |||
46 | // --------------------------------------------------------------------------- | 96 | // --------------------------------------------------------------------------- |
47 | 97 | ||
48 | export { | 98 | export { |
49 | configRouter | 99 | configRouter |
50 | } | 100 | } |
101 | |||
102 | // --------------------------------------------------------------------------- | ||
103 | |||
104 | function customConfig (): CustomConfig { | ||
105 | return { | ||
106 | cache: { | ||
107 | previews: { | ||
108 | size: CONFIG.CACHE.PREVIEWS.SIZE | ||
109 | } | ||
110 | }, | ||
111 | signup: { | ||
112 | enabled: CONFIG.SIGNUP.ENABLED, | ||
113 | limit: CONFIG.SIGNUP.LIMIT | ||
114 | }, | ||
115 | admin: { | ||
116 | email: CONFIG.ADMIN.EMAIL | ||
117 | }, | ||
118 | user: { | ||
119 | videoQuota: CONFIG.USER.VIDEO_QUOTA | ||
120 | }, | ||
121 | transcoding: { | ||
122 | enabled: CONFIG.TRANSCODING.ENABLED, | ||
123 | threads: CONFIG.TRANSCODING.THREADS, | ||
124 | resolutions: { | ||
125 | '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], | ||
126 | '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ], | ||
127 | '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], | ||
128 | '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], | ||
129 | '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ] | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | } | ||
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index b61d6e3fa..79c3b5858 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -104,7 +104,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
104 | ] | 104 | ] |
105 | 105 | ||
106 | for (const resolution of resolutions) { | 106 | for (const resolution of resolutions) { |
107 | if (configResolutions[resolution.toString()] === true && videoFileHeight > resolution) { | 107 | if (configResolutions[resolution + 'p'] === true && videoFileHeight > resolution) { |
108 | resolutionsEnabled.push(resolution) | 108 | resolutionsEnabled.push(resolution) |
109 | } | 109 | } |
110 | } | 110 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 759880201..7b63a9ccd 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -1,11 +1,14 @@ | |||
1 | import * as config from 'config' | 1 | import { IConfig } from 'config' |
2 | import { join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { JobCategory, JobState, VideoRateType } from '../../shared/models' | 3 | import { JobCategory, JobState, VideoRateType } from '../../shared/models' |
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 4 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 5 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
7 | // Do not use barrels, remain constants as independent as possible | 7 | // Do not use barrels, remain constants as independent as possible |
8 | import { buildPath, isTestInstance, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' | 8 | import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' |
9 | |||
10 | // Use a variable to reload the configuration if we need | ||
11 | let config: IConfig = require('config') | ||
9 | 12 | ||
10 | // --------------------------------------------------------------------------- | 13 | // --------------------------------------------------------------------------- |
11 | 14 | ||
@@ -82,6 +85,7 @@ let SCHEDULER_INTERVAL = 60000 * 60 | |||
82 | // --------------------------------------------------------------------------- | 85 | // --------------------------------------------------------------------------- |
83 | 86 | ||
84 | const CONFIG = { | 87 | const CONFIG = { |
88 | CUSTOM_FILE: getLocalConfigFilePath(), | ||
85 | LISTEN: { | 89 | LISTEN: { |
86 | PORT: config.get<number>('listen.port') | 90 | PORT: config.get<number>('listen.port') |
87 | }, | 91 | }, |
@@ -110,29 +114,29 @@ const CONFIG = { | |||
110 | HOST: '' | 114 | HOST: '' |
111 | }, | 115 | }, |
112 | ADMIN: { | 116 | ADMIN: { |
113 | EMAIL: config.get<string>('admin.email') | 117 | get EMAIL () { return config.get<string>('admin.email') } |
114 | }, | 118 | }, |
115 | SIGNUP: { | 119 | SIGNUP: { |
116 | ENABLED: config.get<boolean>('signup.enabled'), | 120 | get ENABLED () { return config.get<boolean>('signup.enabled') }, |
117 | LIMIT: config.get<number>('signup.limit') | 121 | get LIMIT () { return config.get<number>('signup.limit') } |
118 | }, | 122 | }, |
119 | USER: { | 123 | USER: { |
120 | VIDEO_QUOTA: config.get<number>('user.video_quota') | 124 | get VIDEO_QUOTA () { return config.get<number>('user.video_quota') } |
121 | }, | 125 | }, |
122 | TRANSCODING: { | 126 | TRANSCODING: { |
123 | ENABLED: config.get<boolean>('transcoding.enabled'), | 127 | get ENABLED () { return config.get<boolean>('transcoding.enabled') }, |
124 | THREADS: config.get<number>('transcoding.threads'), | 128 | get THREADS () { return config.get<number>('transcoding.threads') }, |
125 | RESOLUTIONS: { | 129 | RESOLUTIONS: { |
126 | '240' : config.get<boolean>('transcoding.resolutions.240p'), | 130 | get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, |
127 | '360': config.get<boolean>('transcoding.resolutions.360p'), | 131 | get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') }, |
128 | '480': config.get<boolean>('transcoding.resolutions.480p'), | 132 | get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') }, |
129 | '720': config.get<boolean>('transcoding.resolutions.720p'), | 133 | get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') }, |
130 | '1080': config.get<boolean>('transcoding.resolutions.1080p') | 134 | get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') } |
131 | } | 135 | } |
132 | }, | 136 | }, |
133 | CACHE: { | 137 | CACHE: { |
134 | PREVIEWS: { | 138 | PREVIEWS: { |
135 | SIZE: config.get<number>('cache.previews.size') | 139 | get SIZE () { return config.get<number>('cache.previews.size') } |
136 | } | 140 | } |
137 | } | 141 | } |
138 | } | 142 | } |
@@ -361,8 +365,7 @@ if (isTestInstance() === true) { | |||
361 | SCHEDULER_INTERVAL = 10000 | 365 | SCHEDULER_INTERVAL = 10000 |
362 | } | 366 | } |
363 | 367 | ||
364 | CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) | 368 | updateWebserverConfig() |
365 | CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) | ||
366 | 369 | ||
367 | // --------------------------------------------------------------------------- | 370 | // --------------------------------------------------------------------------- |
368 | 371 | ||
@@ -404,3 +407,50 @@ export { | |||
404 | AVATAR_MIMETYPE_EXT, | 407 | AVATAR_MIMETYPE_EXT, |
405 | SCHEDULER_INTERVAL | 408 | SCHEDULER_INTERVAL |
406 | } | 409 | } |
410 | |||
411 | // --------------------------------------------------------------------------- | ||
412 | |||
413 | function getLocalConfigFilePath () { | ||
414 | const configSources = config.util.getConfigSources() | ||
415 | if (configSources.length === 0) throw new Error('Invalid config source.') | ||
416 | |||
417 | let filename = 'local' | ||
418 | if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` | ||
419 | if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` | ||
420 | |||
421 | return join(dirname(configSources[ 0 ].name), filename + '.json') | ||
422 | } | ||
423 | |||
424 | function updateWebserverConfig () { | ||
425 | CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) | ||
426 | CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) | ||
427 | } | ||
428 | |||
429 | export function reloadConfig () { | ||
430 | |||
431 | function directory () { | ||
432 | if (process.env.NODE_CONFIG_DIR) { | ||
433 | return process.env.NODE_CONFIG_DIR | ||
434 | } | ||
435 | |||
436 | return join(root(), 'config') | ||
437 | } | ||
438 | |||
439 | function purge () { | ||
440 | for (const fileName in require.cache) { | ||
441 | if (-1 === fileName.indexOf(directory())) { | ||
442 | continue | ||
443 | } | ||
444 | |||
445 | delete require.cache[fileName] | ||
446 | } | ||
447 | |||
448 | delete require.cache[require.resolve('config')] | ||
449 | } | ||
450 | |||
451 | purge() | ||
452 | |||
453 | config = require('config') | ||
454 | |||
455 | updateWebserverConfig() | ||
456 | } | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts new file mode 100644 index 000000000..800aaf107 --- /dev/null +++ b/server/middlewares/validators/config.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body } from 'express-validator/check' | ||
3 | import { isUserVideoQuotaValid } from '../../helpers/custom-validators/users' | ||
4 | import { logger } from '../../helpers/logger' | ||
5 | import { areValidationErrors } from './utils' | ||
6 | |||
7 | const customConfigUpdateValidator = [ | ||
8 | body('cache.previews.size').isInt().withMessage('Should have a valid previews size'), | ||
9 | body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'), | ||
10 | body('signup.limit').isInt().withMessage('Should have a valid signup limit'), | ||
11 | body('admin.email').isEmail().withMessage('Should have a valid administrator email'), | ||
12 | body('user.videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid video quota'), | ||
13 | body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'), | ||
14 | body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'), | ||
15 | body('transcoding.resolutions.240p').isBoolean().withMessage('Should have a valid transcoding 240p resolution enabled boolean'), | ||
16 | body('transcoding.resolutions.360p').isBoolean().withMessage('Should have a valid transcoding 360p resolution enabled boolean'), | ||
17 | body('transcoding.resolutions.480p').isBoolean().withMessage('Should have a valid transcoding 480p resolution enabled boolean'), | ||
18 | body('transcoding.resolutions.720p').isBoolean().withMessage('Should have a valid transcoding 720p resolution enabled boolean'), | ||
19 | body('transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'), | ||
20 | |||
21 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
22 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | ||
23 | |||
24 | if (areValidationErrors(req, res)) return | ||
25 | |||
26 | return next() | ||
27 | } | ||
28 | ] | ||
29 | |||
30 | export { | ||
31 | customConfigUpdateValidator | ||
32 | } | ||
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts new file mode 100644 index 000000000..59a0c3049 --- /dev/null +++ b/server/tests/api/check-params/config.ts | |||
@@ -0,0 +1,152 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import { omit } from 'lodash' | ||
4 | import 'mocha' | ||
5 | import { CustomConfig } from '../../../../shared/models/config/custom-config.model' | ||
6 | |||
7 | import { | ||
8 | createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo, | ||
9 | setAccessTokensToServers, userLogin | ||
10 | } from '../../utils' | ||
11 | |||
12 | describe('Test config API validators', function () { | ||
13 | const path = '/api/v1/config/custom' | ||
14 | let server: ServerInfo | ||
15 | let userAccessToken: string | ||
16 | const updateParams: CustomConfig = { | ||
17 | cache: { | ||
18 | previews: { | ||
19 | size: 2 | ||
20 | } | ||
21 | }, | ||
22 | signup: { | ||
23 | enabled: false, | ||
24 | limit: 5 | ||
25 | }, | ||
26 | admin: { | ||
27 | email: 'superadmin1@example.com' | ||
28 | }, | ||
29 | user: { | ||
30 | videoQuota: 5242881 | ||
31 | }, | ||
32 | transcoding: { | ||
33 | enabled: true, | ||
34 | threads: 1, | ||
35 | resolutions: { | ||
36 | '240p': false, | ||
37 | '360p': true, | ||
38 | '480p': true, | ||
39 | '720p': false, | ||
40 | '1080p': false | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | // --------------------------------------------------------------- | ||
46 | |||
47 | before(async function () { | ||
48 | this.timeout(20000) | ||
49 | |||
50 | await flushTests() | ||
51 | server = await runServer(1) | ||
52 | |||
53 | await setAccessTokensToServers([ server ]) | ||
54 | |||
55 | const user = { | ||
56 | username: 'user1', | ||
57 | password: 'password' | ||
58 | } | ||
59 | await createUser(server.url, server.accessToken, user.username, user.password) | ||
60 | userAccessToken = await userLogin(server, user) | ||
61 | }) | ||
62 | |||
63 | describe('When getting the configuration', function () { | ||
64 | it('Should fail without token', async function () { | ||
65 | await makeGetRequest({ | ||
66 | url: server.url, | ||
67 | path, | ||
68 | statusCodeExpected: 401 | ||
69 | }) | ||
70 | }) | ||
71 | |||
72 | it('Should fail if the user is not an administrator', async function () { | ||
73 | await makeGetRequest({ | ||
74 | url: server.url, | ||
75 | path, | ||
76 | token: userAccessToken, | ||
77 | statusCodeExpected: 403 | ||
78 | }) | ||
79 | }) | ||
80 | }) | ||
81 | |||
82 | describe('When updating the configuration', function () { | ||
83 | it('Should fail without token', async function () { | ||
84 | await makePutBodyRequest({ | ||
85 | url: server.url, | ||
86 | path, | ||
87 | fields: updateParams, | ||
88 | statusCodeExpected: 401 | ||
89 | }) | ||
90 | }) | ||
91 | |||
92 | it('Should fail if the user is not an administrator', async function () { | ||
93 | await makePutBodyRequest({ | ||
94 | url: server.url, | ||
95 | path, | ||
96 | fields: updateParams, | ||
97 | token: userAccessToken, | ||
98 | statusCodeExpected: 403 | ||
99 | }) | ||
100 | }) | ||
101 | |||
102 | it('Should fail if it misses a key', async function () { | ||
103 | const newUpdateParams = omit(updateParams, 'admin.email') | ||
104 | |||
105 | await makePutBodyRequest({ | ||
106 | url: server.url, | ||
107 | path, | ||
108 | fields: newUpdateParams, | ||
109 | token: server.accessToken, | ||
110 | statusCodeExpected: 400 | ||
111 | }) | ||
112 | }) | ||
113 | |||
114 | it('Should success with the correct parameters', async function () { | ||
115 | await makePutBodyRequest({ | ||
116 | url: server.url, | ||
117 | path, | ||
118 | fields: updateParams, | ||
119 | token: server.accessToken, | ||
120 | statusCodeExpected: 200 | ||
121 | }) | ||
122 | }) | ||
123 | }) | ||
124 | |||
125 | describe('When deleting the configuration', function () { | ||
126 | it('Should fail without token', async function () { | ||
127 | await makeDeleteRequest({ | ||
128 | url: server.url, | ||
129 | path, | ||
130 | statusCodeExpected: 401 | ||
131 | }) | ||
132 | }) | ||
133 | |||
134 | it('Should fail if the user is not an administrator', async function () { | ||
135 | await makeDeleteRequest({ | ||
136 | url: server.url, | ||
137 | path, | ||
138 | token: userAccessToken, | ||
139 | statusCodeExpected: 403 | ||
140 | }) | ||
141 | }) | ||
142 | }) | ||
143 | |||
144 | after(async function () { | ||
145 | killallServers([ server ]) | ||
146 | |||
147 | // Keep the logs if the test failed | ||
148 | if (this['ok']) { | ||
149 | await flushTests() | ||
150 | } | ||
151 | }) | ||
152 | }) | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index e8846c8db..8c1389e7f 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -2,13 +2,14 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { deleteCustomConfig, killallServers, reRunServer } from '../../utils' | ||
5 | const expect = chai.expect | 6 | const expect = chai.expect |
6 | 7 | ||
7 | import { | 8 | import { |
8 | getConfig, | 9 | getConfig, |
9 | flushTests, | 10 | flushTests, |
10 | runServer, | 11 | runServer, |
11 | registerUser | 12 | registerUser, getCustomConfig, setAccessTokensToServers, updateCustomConfig |
12 | } from '../../utils/index' | 13 | } from '../../utils/index' |
13 | 14 | ||
14 | describe('Test config', function () { | 15 | describe('Test config', function () { |
@@ -19,6 +20,7 @@ describe('Test config', function () { | |||
19 | 20 | ||
20 | await flushTests() | 21 | await flushTests() |
21 | server = await runServer(1) | 22 | server = await runServer(1) |
23 | await setAccessTokensToServers([ server ]) | ||
22 | }) | 24 | }) |
23 | 25 | ||
24 | it('Should have a correct config on a server with registration enabled', async function () { | 26 | it('Should have a correct config on a server with registration enabled', async function () { |
@@ -43,6 +45,114 @@ describe('Test config', function () { | |||
43 | expect(data.signup.allowed).to.be.false | 45 | expect(data.signup.allowed).to.be.false |
44 | }) | 46 | }) |
45 | 47 | ||
48 | it('Should get the customized configuration', async function () { | ||
49 | const res = await getCustomConfig(server.url, server.accessToken) | ||
50 | const data = res.body | ||
51 | |||
52 | expect(data.cache.previews.size).to.equal(1) | ||
53 | expect(data.signup.enabled).to.be.true | ||
54 | expect(data.signup.limit).to.equal(4) | ||
55 | expect(data.admin.email).to.equal('admin1@example.com') | ||
56 | expect(data.user.videoQuota).to.equal(5242880) | ||
57 | expect(data.transcoding.enabled).to.be.false | ||
58 | expect(data.transcoding.threads).to.equal(2) | ||
59 | expect(data.transcoding.resolutions['240p']).to.be.true | ||
60 | expect(data.transcoding.resolutions['360p']).to.be.true | ||
61 | expect(data.transcoding.resolutions['480p']).to.be.true | ||
62 | expect(data.transcoding.resolutions['720p']).to.be.true | ||
63 | expect(data.transcoding.resolutions['1080p']).to.be.true | ||
64 | }) | ||
65 | |||
66 | it('Should update the customized configuration', async function () { | ||
67 | const newCustomConfig = { | ||
68 | cache: { | ||
69 | previews: { | ||
70 | size: 2 | ||
71 | } | ||
72 | }, | ||
73 | signup: { | ||
74 | enabled: false, | ||
75 | limit: 5 | ||
76 | }, | ||
77 | admin: { | ||
78 | email: 'superadmin1@example.com' | ||
79 | }, | ||
80 | user: { | ||
81 | videoQuota: 5242881 | ||
82 | }, | ||
83 | transcoding: { | ||
84 | enabled: true, | ||
85 | threads: 1, | ||
86 | resolutions: { | ||
87 | '240p': false, | ||
88 | '360p': true, | ||
89 | '480p': true, | ||
90 | '720p': false, | ||
91 | '1080p': false | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) | ||
96 | |||
97 | const res = await getCustomConfig(server.url, server.accessToken) | ||
98 | const data = res.body | ||
99 | |||
100 | expect(data.cache.previews.size).to.equal(2) | ||
101 | expect(data.signup.enabled).to.be.false | ||
102 | expect(data.signup.limit).to.equal(5) | ||
103 | expect(data.admin.email).to.equal('superadmin1@example.com') | ||
104 | expect(data.user.videoQuota).to.equal(5242881) | ||
105 | expect(data.transcoding.enabled).to.be.true | ||
106 | expect(data.transcoding.threads).to.equal(1) | ||
107 | expect(data.transcoding.resolutions['240p']).to.be.false | ||
108 | expect(data.transcoding.resolutions['360p']).to.be.true | ||
109 | expect(data.transcoding.resolutions['480p']).to.be.true | ||
110 | expect(data.transcoding.resolutions['720p']).to.be.false | ||
111 | expect(data.transcoding.resolutions['1080p']).to.be.false | ||
112 | }) | ||
113 | |||
114 | it('Should have the configuration updated after a restart', async function () { | ||
115 | killallServers([ server ]) | ||
116 | |||
117 | await reRunServer(server) | ||
118 | |||
119 | const res = await getCustomConfig(server.url, server.accessToken) | ||
120 | const data = res.body | ||
121 | |||
122 | expect(data.cache.previews.size).to.equal(2) | ||
123 | expect(data.signup.enabled).to.be.false | ||
124 | expect(data.signup.limit).to.equal(5) | ||
125 | expect(data.admin.email).to.equal('superadmin1@example.com') | ||
126 | expect(data.user.videoQuota).to.equal(5242881) | ||
127 | expect(data.transcoding.enabled).to.be.true | ||
128 | expect(data.transcoding.threads).to.equal(1) | ||
129 | expect(data.transcoding.resolutions['240p']).to.be.false | ||
130 | expect(data.transcoding.resolutions['360p']).to.be.true | ||
131 | expect(data.transcoding.resolutions['480p']).to.be.true | ||
132 | expect(data.transcoding.resolutions['720p']).to.be.false | ||
133 | expect(data.transcoding.resolutions['1080p']).to.be.false | ||
134 | }) | ||
135 | |||
136 | it('Should remove the custom configuration', async function () { | ||
137 | await deleteCustomConfig(server.url, server.accessToken) | ||
138 | |||
139 | const res = await getCustomConfig(server.url, server.accessToken) | ||
140 | const data = res.body | ||
141 | |||
142 | expect(data.cache.previews.size).to.equal(1) | ||
143 | expect(data.signup.enabled).to.be.true | ||
144 | expect(data.signup.limit).to.equal(4) | ||
145 | expect(data.admin.email).to.equal('admin1@example.com') | ||
146 | expect(data.user.videoQuota).to.equal(5242880) | ||
147 | expect(data.transcoding.enabled).to.be.false | ||
148 | expect(data.transcoding.threads).to.equal(2) | ||
149 | expect(data.transcoding.resolutions['240p']).to.be.true | ||
150 | expect(data.transcoding.resolutions['360p']).to.be.true | ||
151 | expect(data.transcoding.resolutions['480p']).to.be.true | ||
152 | expect(data.transcoding.resolutions['720p']).to.be.true | ||
153 | expect(data.transcoding.resolutions['1080p']).to.be.true | ||
154 | }) | ||
155 | |||
46 | after(async function () { | 156 | after(async function () { |
47 | process.kill(-server.app.pid) | 157 | process.kill(-server.app.pid) |
48 | 158 | ||
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 0a0c95750..ca20f39a0 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts | |||
@@ -5,9 +5,10 @@ import { keyBy } from 'lodash' | |||
5 | import 'mocha' | 5 | import 'mocha' |
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import { VideoPrivacy } from '../../../../shared/models/videos' | 7 | import { VideoPrivacy } from '../../../../shared/models/videos' |
8 | import { readdirPromise } from '../../../helpers/core-utils' | ||
8 | import { | 9 | import { |
9 | completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, getVideoPrivacies, | 10 | completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, getVideoPrivacies, |
10 | getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, readdirPromise, removeVideo, runServer, searchVideo, | 11 | getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer, searchVideo, |
11 | searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testVideoImage, updateVideo, uploadVideo, viewVideo | 12 | searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testVideoImage, updateVideo, uploadVideo, viewVideo |
12 | } from '../../utils' | 13 | } from '../../utils' |
13 | 14 | ||
diff --git a/server/tests/utils/miscs/miscs.ts b/server/tests/utils/miscs/miscs.ts index 2c51d1f0a..2aac37791 100644 --- a/server/tests/utils/miscs/miscs.ts +++ b/server/tests/utils/miscs/miscs.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as WebTorrent from 'webtorrent' | 1 | import * as WebTorrent from 'webtorrent' |
2 | import { readFile, readdir } from 'fs' | ||
3 | 2 | ||
4 | let webtorrent = new WebTorrent() | 3 | let webtorrent = new WebTorrent() |
5 | 4 | ||
@@ -7,26 +6,6 @@ function immutableAssign <T, U> (target: T, source: U) { | |||
7 | return Object.assign<{}, T, U>({}, target, source) | 6 | return Object.assign<{}, T, U>({}, target, source) |
8 | } | 7 | } |
9 | 8 | ||
10 | function readFilePromise (path: string) { | ||
11 | return new Promise<Buffer>((res, rej) => { | ||
12 | readFile(path, (err, data) => { | ||
13 | if (err) return rej(err) | ||
14 | |||
15 | return res(data) | ||
16 | }) | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | function readdirPromise (path: string) { | ||
21 | return new Promise<string[]>((res, rej) => { | ||
22 | readdir(path, (err, files) => { | ||
23 | if (err) return rej(err) | ||
24 | |||
25 | return res(files) | ||
26 | }) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | // Default interval -> 5 minutes | 9 | // Default interval -> 5 minutes |
31 | function dateIsValid (dateString: string, interval = 300000) { | 10 | function dateIsValid (dateString: string, interval = 300000) { |
32 | const dateToCheck = new Date(dateString) | 11 | const dateToCheck = new Date(dateString) |
@@ -48,8 +27,6 @@ function webtorrentAdd (torrent: string, refreshWebTorrent = false) { | |||
48 | // --------------------------------------------------------------------------- | 27 | // --------------------------------------------------------------------------- |
49 | 28 | ||
50 | export { | 29 | export { |
51 | readFilePromise, | ||
52 | readdirPromise, | ||
53 | dateIsValid, | 30 | dateIsValid, |
54 | wait, | 31 | wait, |
55 | webtorrentAdd, | 32 | webtorrentAdd, |
diff --git a/server/tests/utils/requests/requests.ts b/server/tests/utils/requests/requests.ts index eb02cf9e6..840072430 100644 --- a/server/tests/utils/requests/requests.ts +++ b/server/tests/utils/requests/requests.ts | |||
@@ -99,7 +99,7 @@ function makePostBodyRequest (options: { | |||
99 | function makePutBodyRequest (options: { | 99 | function makePutBodyRequest (options: { |
100 | url: string, | 100 | url: string, |
101 | path: string, | 101 | path: string, |
102 | token: string, | 102 | token?: string, |
103 | fields: { [ fieldName: string ]: any }, | 103 | fields: { [ fieldName: string ]: any }, |
104 | statusCodeExpected?: number | 104 | statusCodeExpected?: number |
105 | }) { | 105 | }) { |
diff --git a/server/tests/utils/server/config.ts b/server/tests/utils/server/config.ts index d09c19c60..b6905757a 100644 --- a/server/tests/utils/server/config.ts +++ b/server/tests/utils/server/config.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../' | ||
3 | import { CustomConfig } from '../../../../shared/models/config/custom-config.model' | ||
2 | 4 | ||
3 | function getConfig (url: string) { | 5 | function getConfig (url: string) { |
4 | const path = '/api/v1/config' | 6 | const path = '/api/v1/config' |
@@ -10,8 +12,45 @@ function getConfig (url: string) { | |||
10 | .expect('Content-Type', /json/) | 12 | .expect('Content-Type', /json/) |
11 | } | 13 | } |
12 | 14 | ||
15 | function getCustomConfig (url: string, token: string, statusCodeExpected = 200) { | ||
16 | const path = '/api/v1/config/custom' | ||
17 | |||
18 | return makeGetRequest({ | ||
19 | url, | ||
20 | token, | ||
21 | path, | ||
22 | statusCodeExpected | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | function updateCustomConfig (url: string, token: string, newCustomConfig: CustomConfig, statusCodeExpected = 200) { | ||
27 | const path = '/api/v1/config/custom' | ||
28 | |||
29 | return makePutBodyRequest({ | ||
30 | url, | ||
31 | token, | ||
32 | path, | ||
33 | fields: newCustomConfig, | ||
34 | statusCodeExpected | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | function deleteCustomConfig (url: string, token: string, statusCodeExpected = 200) { | ||
39 | const path = '/api/v1/config/custom' | ||
40 | |||
41 | return makeDeleteRequest({ | ||
42 | url, | ||
43 | token, | ||
44 | path, | ||
45 | statusCodeExpected | ||
46 | }) | ||
47 | } | ||
48 | |||
13 | // --------------------------------------------------------------------------- | 49 | // --------------------------------------------------------------------------- |
14 | 50 | ||
15 | export { | 51 | export { |
16 | getConfig | 52 | getConfig, |
53 | getCustomConfig, | ||
54 | updateCustomConfig, | ||
55 | deleteCustomConfig | ||
17 | } | 56 | } |
diff --git a/server/tests/utils/videos/videos.ts b/server/tests/utils/videos/videos.ts index dc1327215..095d4e29d 100644 --- a/server/tests/utils/videos/videos.ts +++ b/server/tests/utils/videos/videos.ts | |||
@@ -5,8 +5,9 @@ import { readFile } from 'fs' | |||
5 | import * as parseTorrent from 'parse-torrent' | 5 | import * as parseTorrent from 'parse-torrent' |
6 | import { extname, isAbsolute, join } from 'path' | 6 | import { extname, isAbsolute, join } from 'path' |
7 | import * as request from 'supertest' | 7 | import * as request from 'supertest' |
8 | import { getMyUserInformation, makeGetRequest, readFilePromise, ServerInfo } from '../' | 8 | import { getMyUserInformation, makeGetRequest, ServerInfo } from '../' |
9 | import { VideoPrivacy } from '../../../../shared/models/videos' | 9 | import { VideoPrivacy } from '../../../../shared/models/videos' |
10 | import { readFileBufferPromise } from '../../../helpers/core-utils' | ||
10 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers' | 11 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers' |
11 | import { dateIsValid, webtorrentAdd } from '../index' | 12 | import { dateIsValid, webtorrentAdd } from '../index' |
12 | 13 | ||
@@ -210,7 +211,7 @@ async function testVideoImage (url: string, imageName: string, imagePath: string | |||
210 | .get(imagePath) | 211 | .get(imagePath) |
211 | .expect(200) | 212 | .expect(200) |
212 | 213 | ||
213 | const data = await readFilePromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension)) | 214 | const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension)) |
214 | 215 | ||
215 | return data.equals(res.body) | 216 | return data.equals(res.body) |
216 | } else { | 217 | } else { |
diff --git a/shared/models/config/custom-config.model.ts b/shared/models/config/custom-config.model.ts new file mode 100644 index 000000000..73b5b6a72 --- /dev/null +++ b/shared/models/config/custom-config.model.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | export interface CustomConfig { | ||
2 | cache: { | ||
3 | previews: { | ||
4 | size: number | ||
5 | } | ||
6 | } | ||
7 | |||
8 | signup: { | ||
9 | enabled: boolean | ||
10 | limit: number | ||
11 | } | ||
12 | |||
13 | admin: { | ||
14 | email: string | ||
15 | } | ||
16 | |||
17 | user: { | ||
18 | videoQuota: number | ||
19 | } | ||
20 | |||
21 | transcoding: { | ||
22 | enabled: boolean | ||
23 | threads: number | ||
24 | resolutions: { | ||
25 | '240p': boolean | ||
26 | '360p': boolean | ||
27 | '480p': boolean | ||
28 | '720p': boolean | ||
29 | '1080p': boolean | ||
30 | } | ||
31 | } | ||
32 | } | ||
diff --git a/shared/models/server-config.model.ts b/shared/models/config/server-config.model.ts index d0b2e40de..d0b2e40de 100644 --- a/shared/models/server-config.model.ts +++ b/shared/models/config/server-config.model.ts | |||
diff --git a/shared/models/index.ts b/shared/models/index.ts index a88c01608..1b877774c 100644 --- a/shared/models/index.ts +++ b/shared/models/index.ts | |||
@@ -5,4 +5,4 @@ export * from './videos' | |||
5 | export * from './job.model' | 5 | export * from './job.model' |
6 | export * from './oauth-client-local.model' | 6 | export * from './oauth-client-local.model' |
7 | export * from './result-list.model' | 7 | export * from './result-list.model' |
8 | export * from './server-config.model' | 8 | export * from './config/server-config.model' |
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index 2e7fa1bcf..1fa149999 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts | |||
@@ -5,6 +5,7 @@ export enum UserRight { | |||
5 | MANAGE_VIDEO_ABUSES, | 5 | MANAGE_VIDEO_ABUSES, |
6 | MANAGE_VIDEO_BLACKLIST, | 6 | MANAGE_VIDEO_BLACKLIST, |
7 | MANAGE_JOBS, | 7 | MANAGE_JOBS, |
8 | MANAGE_CONFIGURATION, | ||
8 | REMOVE_ANY_VIDEO, | 9 | REMOVE_ANY_VIDEO, |
9 | REMOVE_ANY_VIDEO_CHANNEL, | 10 | REMOVE_ANY_VIDEO_CHANNEL, |
10 | REMOVE_ANY_VIDEO_COMMENT | 11 | REMOVE_ANY_VIDEO_COMMENT |
diff --git a/support/doc/production.md b/support/doc/production.md index 69af57c0e..d427489c7 100644 --- a/support/doc/production.md +++ b/support/doc/production.md | |||
@@ -31,7 +31,7 @@ $ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/la | |||
31 | cd /home/peertube && \ | 31 | cd /home/peertube && \ |
32 | sudo -u peertube mkdir config storage versions && \ | 32 | sudo -u peertube mkdir config storage versions && \ |
33 | cd versions && \ | 33 | cd versions && \ |
34 | sudo -u peertube wget "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \ | 34 | sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \ |
35 | sudo -u peertube unzip peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \ | 35 | sudo -u peertube unzip peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \ |
36 | cd ../ && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \ | 36 | cd ../ && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \ |
37 | cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile | 37 | cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile |
@@ -227,7 +227,7 @@ $ NODE_ENV=production npm run reset-password -- -u root | |||
227 | ``` | 227 | ``` |
228 | $ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/latest | grep tag_name | cut -d '"' -f 4) && \ | 228 | $ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/latest | grep tag_name | cut -d '"' -f 4) && \ |
229 | cd /home/peertube/versions && \ | 229 | cd /home/peertube/versions && \ |
230 | sudo -u peertube wget "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \ | 230 | sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \ |
231 | sudo -u peertube unzip -o peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \ | 231 | sudo -u peertube unzip -o peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \ |
232 | cd ../ && sudo rm ./peertube-latest && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \ | 232 | cd ../ && sudo rm ./peertube-latest && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \ |
233 | cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile && \ | 233 | cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile && \ |