diff --git a/.gitignore b/.gitignore
index 2805af0fc..b373b368b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
/test6/
/storage/
/config/production.yaml
+/config/local.json
/ffmpeg/
/*.sublime-project
/*.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 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
+import { ConfigRoutes } from '@app/+admin/config'
import { MetaGuard } from '@ngx-meta/core'
import { AdminComponent } from './admin.component'
import { FollowsRoutes } from './follows'
+import { JobsRoutes } from './jobs/job.routes'
import { UsersRoutes } from './users'
import { VideoAbusesRoutes } from './video-abuses'
import { VideoBlacklistRoutes } from './video-blacklist'
-import { JobsRoutes } from './jobs/job.routes'
const adminRoutes: Routes = [
{
@@ -26,7 +27,8 @@ const adminRoutes: Routes = [
...UsersRoutes,
...VideoAbusesRoutes,
...VideoBlacklistRoutes,
- ...JobsRoutes
+ ...JobsRoutes,
+ ...ConfigRoutes
]
}
]
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 @@
Jobs
+
+
+ Configuration
+
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 {
hasJobsRight () {
return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
}
+
+ hasConfigRight () {
+ return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION)
+ }
}
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 @@
import { NgModule } from '@angular/core'
+import { ConfigComponent, EditCustomConfigComponent } from '@app/+admin/config'
+import { ConfigService } from '@app/+admin/config/shared/config.service'
import { TabsModule } from 'ngx-bootstrap/tabs'
import { DataTableModule } from 'primeng/components/datatable/datatable'
import { SharedModule } from '../shared'
@@ -41,7 +43,10 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
VideoAbuseListComponent,
JobsComponent,
- JobsListComponent
+ JobsListComponent,
+
+ ConfigComponent,
+ EditCustomConfigComponent
],
exports: [
@@ -51,7 +56,8 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
providers: [
FollowService,
UserService,
- JobService
+ JobService,
+ ConfigService
]
})
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 @@
+import { Component } from '@angular/core'
+
+@Component({
+ template: '
'
+})
+export class ConfigComponent {
+}
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 @@
+import { Routes } from '@angular/router'
+import { EditCustomConfigComponent } from '@app/+admin/config/edit-custom-config'
+import { UserRightGuard } from '@app/core'
+import { UserRight } from '../../../../../shared/models/users'
+import { ConfigComponent } from './config.component'
+
+export const ConfigRoutes: Routes = [
+ {
+ path: 'config',
+ component: ConfigComponent,
+ canActivate: [ UserRightGuard ],
+ data: {
+ userRight: UserRight.MANAGE_CONFIGURATION
+ },
+ children: [
+ {
+ path: '',
+ redirectTo: 'edit-custom',
+ pathMatch: 'full'
+ },
+ {
+ path: 'edit-custom',
+ component: EditCustomConfigComponent,
+ data: {
+ meta: {
+ title: 'Following list'
+ }
+ }
+ }
+ ]
+ }
+]
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 @@
+
Update PeerTube configuration
+
+
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 @@
+@import '_variables';
+@import '_mixins';
+
+input[type=text] {
+ @include peertube-input-text(340px);
+ display: block;
+}
+
+input[type=checkbox] {
+ @include peertube-checkbox(1px);
+}
+
+.peertube-select-container {
+ @include peertube-select-container(340px);
+}
+
+input[type=submit] {
+ @include peertube-button;
+ @include orange-button;
+
+ margin-top: 20px;
+}
+
+.inner-form-title {
+ text-transform: uppercase;
+ color: $orange-color;
+ font-weight: $font-bold;
+ font-size: 13px;
+ margin-top: 30px;
+ margin-bottom: 10px;
+}
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 @@
+import { Component, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { Router } from '@angular/router'
+import { ConfigService } from '@app/+admin/config/shared/config.service'
+import { ServerService } from '@app/core/server/server.service'
+import { FormReactive, USER_VIDEO_QUOTA } from '@app/shared'
+import { ADMIN_EMAIL, CACHE_PREVIEWS_SIZE, SIGNUP_LIMIT, TRANSCODING_THREADS } from '@app/shared/forms/form-validators/custom-config'
+import { NotificationsService } from 'angular2-notifications'
+import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model'
+
+@Component({
+ selector: 'my-edit-custom-config',
+ templateUrl: './edit-custom-config.component.html',
+ styleUrls: [ './edit-custom-config.component.scss' ]
+})
+export class EditCustomConfigComponent extends FormReactive implements OnInit {
+ customConfig: CustomConfig
+ resolutions = [ '240p', '360p', '480p', '720p', '1080p' ]
+
+ videoQuotaOptions = [
+ { value: -1, label: 'Unlimited' },
+ { value: 0, label: '0' },
+ { value: 100 * 1024 * 1024, label: '100MB' },
+ { value: 500 * 1024 * 1024, label: '500MB' },
+ { value: 1024 * 1024 * 1024, label: '1GB' },
+ { value: 5 * 1024 * 1024 * 1024, label: '5GB' },
+ { value: 20 * 1024 * 1024 * 1024, label: '20GB' },
+ { value: 50 * 1024 * 1024 * 1024, label: '50GB' }
+ ]
+ transcodingThreadOptions = [
+ { value: 1, label: '1' },
+ { value: 2, label: '2' },
+ { value: 4, label: '4' },
+ { value: 8, label: '8' }
+ ]
+
+ form: FormGroup
+ formErrors = {
+ cachePreviewsSize: '',
+ signupLimit: '',
+ adminEmail: '',
+ userVideoQuota: '',
+ transcodingThreads: ''
+ }
+ validationMessages = {
+ cachePreviewsSize: CACHE_PREVIEWS_SIZE.MESSAGES,
+ signupLimit: SIGNUP_LIMIT.MESSAGES,
+ adminEmail: ADMIN_EMAIL.MESSAGES,
+ userVideoQuota: USER_VIDEO_QUOTA.MESSAGES
+ }
+
+ constructor (
+ private formBuilder: FormBuilder,
+ private router: Router,
+ private notificationsService: NotificationsService,
+ private configService: ConfigService,
+ private serverService: ServerService
+ ) {
+ super()
+ }
+
+ getResolutionKey (resolution: string) {
+ return 'transcodingResolution' + resolution
+ }
+
+ buildForm () {
+ const formGroupData = {
+ cachePreviewsSize: [ '', CACHE_PREVIEWS_SIZE.VALIDATORS ],
+ signupEnabled: [ ],
+ signupLimit: [ '', SIGNUP_LIMIT.VALIDATORS ],
+ adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ],
+ userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ],
+ transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ],
+ transcodingEnabled: [ ]
+ }
+
+ for (const resolution of this.resolutions) {
+ const key = this.getResolutionKey(resolution)
+ formGroupData[key] = [ false ]
+ }
+
+ this.form = this.formBuilder.group(formGroupData)
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+ }
+
+ ngOnInit () {
+ this.buildForm()
+
+ this.configService.getCustomConfig()
+ .subscribe(
+ res => {
+ this.customConfig = res
+
+ this.updateForm()
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
+ }
+
+ isTranscodingEnabled () {
+ return this.form.value['transcodingEnabled'] === true
+ }
+
+ isSignupEnabled () {
+ return this.form.value['signupEnabled'] === true
+ }
+
+ formValidated () {
+ const data = {
+ cache: {
+ previews: {
+ size: this.form.value['cachePreviewsSize']
+ }
+ },
+ signup: {
+ enabled: this.form.value['signupEnabled'],
+ limit: this.form.value['signupLimit']
+ },
+ admin: {
+ email: this.form.value['adminEmail']
+ },
+ user: {
+ videoQuota: this.form.value['userVideoQuota']
+ },
+ transcoding: {
+ enabled: this.form.value['transcodingEnabled'],
+ threads: this.form.value['transcodingThreads'],
+ resolutions: {
+ '240p': this.form.value[this.getResolutionKey('240p')],
+ '360p': this.form.value[this.getResolutionKey('360p')],
+ '480p': this.form.value[this.getResolutionKey('480p')],
+ '720p': this.form.value[this.getResolutionKey('720p')],
+ '1080p': this.form.value[this.getResolutionKey('1080p')]
+ }
+ }
+ }
+
+ this.configService.updateCustomConfig(data)
+ .subscribe(
+ res => {
+ this.customConfig = res
+
+ // Reload general configuration
+ this.serverService.loadConfig()
+
+ this.updateForm()
+ },
+
+ err => this.notificationsService.error('Error', err.message)
+ )
+ }
+
+ private updateForm () {
+ const data = {
+ cachePreviewsSize: this.customConfig.cache.previews.size,
+ signupEnabled: this.customConfig.signup.enabled,
+ signupLimit: this.customConfig.signup.limit,
+ adminEmail: this.customConfig.admin.email,
+ userVideoQuota: this.customConfig.user.videoQuota,
+ transcodingThreads: this.customConfig.transcoding.threads,
+ transcodingEnabled: this.customConfig.transcoding.enabled
+ }
+
+ for (const resolution of this.resolutions) {
+ const key = this.getResolutionKey(resolution)
+ data[key] = this.customConfig.transcoding.resolutions[resolution]
+ }
+
+ this.form.patchValue(data)
+ }
+
+}
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 @@
+export * from './edit-custom-config'
+export * from './config.component'
+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 @@
+import { HttpClient } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model'
+import { environment } from '../../../../environments/environment'
+import { RestExtractor, RestService } from '../../../shared'
+
+@Injectable()
+export class ConfigService {
+ private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/config'
+
+ constructor (
+ private authHttp: HttpClient,
+ private restService: RestService,
+ private restExtractor: RestExtractor
+ ) {}
+
+ getCustomConfig () {
+ return this.authHttp.get
(ConfigService.BASE_APPLICATION_URL + '/custom')
+ .catch(res => this.restExtractor.handleError(res))
+ }
+
+ updateCustomConfig (data: CustomConfig) {
+ return this.authHttp.put(ConfigService.BASE_APPLICATION_URL + '/custom', data)
+ .catch(res => this.restExtractor.handleError(res))
+ }
+}
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 @@
-{{ error }}
-