Add ability to update some configuration keys
This commit is contained in:
parent
9581cabc59
commit
fd206f0b2d
|
@ -7,6 +7,7 @@
|
||||||
/test6/
|
/test6/
|
||||||
/storage/
|
/storage/
|
||||||
/config/production.yaml
|
/config/production.yaml
|
||||||
|
/config/local.json
|
||||||
/ffmpeg/
|
/ffmpeg/
|
||||||
/*.sublime-project
|
/*.sublime-project
|
||||||
/*.sublime-workspace
|
/*.sublime-workspace
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
|
import { ConfigRoutes } from '@app/+admin/config'
|
||||||
|
|
||||||
import { MetaGuard } from '@ngx-meta/core'
|
import { MetaGuard } from '@ngx-meta/core'
|
||||||
|
|
||||||
import { AdminComponent } from './admin.component'
|
import { AdminComponent } from './admin.component'
|
||||||
import { FollowsRoutes } from './follows'
|
import { FollowsRoutes } from './follows'
|
||||||
|
import { JobsRoutes } from './jobs/job.routes'
|
||||||
import { UsersRoutes } from './users'
|
import { UsersRoutes } from './users'
|
||||||
import { VideoAbusesRoutes } from './video-abuses'
|
import { VideoAbusesRoutes } from './video-abuses'
|
||||||
import { VideoBlacklistRoutes } from './video-blacklist'
|
import { VideoBlacklistRoutes } from './video-blacklist'
|
||||||
import { JobsRoutes } from './jobs/job.routes'
|
|
||||||
|
|
||||||
const adminRoutes: Routes = [
|
const adminRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -26,7 +27,8 @@ const adminRoutes: Routes = [
|
||||||
...UsersRoutes,
|
...UsersRoutes,
|
||||||
...VideoAbusesRoutes,
|
...VideoAbusesRoutes,
|
||||||
...VideoBlacklistRoutes,
|
...VideoBlacklistRoutes,
|
||||||
...JobsRoutes
|
...JobsRoutes,
|
||||||
|
...ConfigRoutes
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,6 +19,10 @@
|
||||||
<a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page">
|
<a *ngIf="hasJobsRight()" routerLink="/admin/jobs" routerLinkActive="active" class="title-page">
|
||||||
Jobs
|
Jobs
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a *ngIf="hasConfigRight()" routerLink="/admin/config" routerLinkActive="active" class="title-page">
|
||||||
|
Configuration
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="margin-content">
|
<div class="margin-content">
|
||||||
|
|
|
@ -28,4 +28,8 @@ export class AdminComponent {
|
||||||
hasJobsRight () {
|
hasJobsRight () {
|
||||||
return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
|
return this.auth.getUser().hasRight(UserRight.MANAGE_JOBS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasConfigRight () {
|
||||||
|
return this.auth.getUser().hasRight(UserRight.MANAGE_CONFIGURATION)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { NgModule } from '@angular/core'
|
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 { TabsModule } from 'ngx-bootstrap/tabs'
|
||||||
import { DataTableModule } from 'primeng/components/datatable/datatable'
|
import { DataTableModule } from 'primeng/components/datatable/datatable'
|
||||||
import { SharedModule } from '../shared'
|
import { SharedModule } from '../shared'
|
||||||
|
@ -41,7 +43,10 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
|
||||||
VideoAbuseListComponent,
|
VideoAbuseListComponent,
|
||||||
|
|
||||||
JobsComponent,
|
JobsComponent,
|
||||||
JobsListComponent
|
JobsListComponent,
|
||||||
|
|
||||||
|
ConfigComponent,
|
||||||
|
EditCustomConfigComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
|
@ -51,7 +56,8 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
|
||||||
providers: [
|
providers: [
|
||||||
FollowService,
|
FollowService,
|
||||||
UserService,
|
UserService,
|
||||||
JobService
|
JobService,
|
||||||
|
ConfigService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminModule { }
|
export class AdminModule { }
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { Component } from '@angular/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: '<router-outlet></router-outlet>'
|
||||||
|
})
|
||||||
|
export class ConfigComponent {
|
||||||
|
}
|
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,97 @@
|
||||||
|
<div class="admin-sub-title">Update PeerTube configuration</div>
|
||||||
|
|
||||||
|
<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
|
||||||
|
|
||||||
|
<div class="inner-form-title">Cache</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="cachePreviewsSize">Preview cache size</label>
|
||||||
|
<input
|
||||||
|
type="text" id="cachePreviewsSize"
|
||||||
|
formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }"
|
||||||
|
>
|
||||||
|
<div *ngIf="formErrors.cachePreviewsSize" class="form-error">
|
||||||
|
{{ formErrors.cachePreviewsSize }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inner-form-title">Signup</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="signupEnabled" formControlName="signupEnabled">
|
||||||
|
|
||||||
|
<label for="signupEnabled"></label>
|
||||||
|
<label for="signupEnabled">Signup enabled</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="isSignupEnabled()" class="form-group">
|
||||||
|
<label for="signupLimit">Signup limit</label>
|
||||||
|
<input
|
||||||
|
type="text" id="signupLimit"
|
||||||
|
formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }"
|
||||||
|
>
|
||||||
|
<div *ngIf="formErrors.signupLimit" class="form-error">
|
||||||
|
{{ formErrors.signupLimit }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inner-form-title">Administrator</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="adminEmail">Admin email</label>
|
||||||
|
<input
|
||||||
|
type="text" id="adminEmail"
|
||||||
|
formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }"
|
||||||
|
>
|
||||||
|
<div *ngIf="formErrors.adminEmail" class="form-error">
|
||||||
|
{{ formErrors.adminEmail }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inner-form-title">Users</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="userVideoQuota">User default video quota</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="userVideoQuota" formControlName="userVideoQuota">
|
||||||
|
<option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
|
||||||
|
{{ videoQuotaOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inner-form-title">Transcoding</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="transcodingEnabled" formControlName="transcodingEnabled">
|
||||||
|
|
||||||
|
<label for="transcodingEnabled"></label>
|
||||||
|
<label for="transcodingEnabled">Transcoding enabled</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template [ngIf]="isTranscodingEnabled()">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="transcodingThreads">Transcoding threads</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="transcodingThreads" formControlName="transcodingThreads">
|
||||||
|
<option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value">
|
||||||
|
{{ transcodingThreadOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" *ngFor="let resolution of resolutions">
|
||||||
|
<input
|
||||||
|
type="checkbox" [id]="getResolutionKey(resolution)"
|
||||||
|
[formControlName]="getResolutionKey(resolution)"
|
||||||
|
>
|
||||||
|
<label [for]="getResolutionKey(resolution)"></label>
|
||||||
|
<label [for]="getResolutionKey(resolution)">Resolution {{ resolution }} enabled</label>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<input type="submit" value="Update configuration" [disabled]="!form.valid">
|
||||||
|
</form>
|
|
@ -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;
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './edit-custom-config.component'
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './edit-custom-config'
|
||||||
|
export * from './config.component'
|
||||||
|
export * from './config.routes'
|
|
@ -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<CustomConfig>(ConfigService.BASE_APPLICATION_URL + '/custom')
|
||||||
|
.catch(res => this.restExtractor.handleError(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCustomConfig (data: CustomConfig) {
|
||||||
|
return this.authHttp.put<CustomConfig>(ConfigService.BASE_APPLICATION_URL + '/custom', data)
|
||||||
|
.catch(res => this.restExtractor.handleError(res))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
|
|
||||||
|
|
||||||
<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
|
<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -14,8 +14,6 @@ import { FormReactive, User, UserService } from '../../../shared'
|
||||||
export class AccountDetailsComponent extends FormReactive implements OnInit {
|
export class AccountDetailsComponent extends FormReactive implements OnInit {
|
||||||
@Input() user: User = null
|
@Input() user: User = null
|
||||||
|
|
||||||
error: string = null
|
|
||||||
|
|
||||||
form: FormGroup
|
form: FormGroup
|
||||||
formErrors = {}
|
formErrors = {}
|
||||||
validationMessages = {}
|
validationMessages = {}
|
||||||
|
@ -50,7 +48,6 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
|
||||||
autoPlayVideo
|
autoPlayVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
this.error = null
|
|
||||||
this.userService.updateMyDetails(details).subscribe(
|
this.userService.updateMyDetails(details).subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.notificationsService.success('Success', 'Information updated.')
|
this.notificationsService.success('Success', 'Information updated.')
|
||||||
|
@ -58,7 +55,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
|
||||||
this.authService.refreshUserInformation()
|
this.authService.refreshUserInformation()
|
||||||
},
|
},
|
||||||
|
|
||||||
err => this.error = err.message
|
err => this.notificationsService.error('Error', err.message)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<ul *dropdownMenu class="dropdown-menu">
|
<ul *dropdownMenu class="dropdown-menu">
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="/account/settings" class="dropdown-item" title="My account">
|
<a i18n routerLink="/account/settings" class="dropdown-item" title="My account">
|
||||||
My account
|
My account
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Validators } from '@angular/forms'
|
||||||
|
|
||||||
|
export const CACHE_PREVIEWS_SIZE = {
|
||||||
|
VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': 'Preview cache size is required.',
|
||||||
|
'min': 'Preview cache size must be greater than 1.',
|
||||||
|
'pattern': 'Preview cache size must be a number.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SIGNUP_LIMIT = {
|
||||||
|
VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': 'Signup limit is required.',
|
||||||
|
'min': 'Signup limit must be greater than 1.',
|
||||||
|
'pattern': 'Preview cache size must be a number.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ADMIN_EMAIL = {
|
||||||
|
VALIDATORS: [ Validators.required, Validators.email ],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': 'Admin email is required.',
|
||||||
|
'email': 'Admin email must be valid.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TRANSCODING_THREADS = {
|
||||||
|
VALIDATORS: [ Validators.required, Validators.min(1) ],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': 'Transcoding threads is required.',
|
||||||
|
'min': 'Transcoding threads must be greater than 1.'
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,5 +3,7 @@
|
||||||
for i in $(seq 1 6); do
|
for i in $(seq 1 6); do
|
||||||
dropdb "peertube_test$i"
|
dropdb "peertube_test$i"
|
||||||
rm -rf "./test$i"
|
rm -rf "./test$i"
|
||||||
|
rm -f "./config/local-test.json"
|
||||||
|
rm -f "./config/local-test-$i.json"
|
||||||
createdb "peertube_test$i"
|
createdb "peertube_test$i"
|
||||||
done
|
done
|
||||||
|
|
|
@ -35,6 +35,7 @@ git commit package.json client/package.json -m "Bumped to version $version" || e
|
||||||
git tag -s -a "$version" -m "$version"
|
git tag -s -a "$version" -m "$version"
|
||||||
|
|
||||||
npm run build || exit -1
|
npm run build || exit -1
|
||||||
|
rm "./client/dist/stats.json" || exit -1
|
||||||
|
|
||||||
cd ../ || exit -1
|
cd ../ || exit -1
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,34 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
|
import { ServerConfig, UserRight } from '../../../shared'
|
||||||
|
import { CustomConfig } from '../../../shared/models/config/custom-config.model'
|
||||||
|
import { unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
|
||||||
import { isSignupAllowed } from '../../helpers/utils'
|
import { isSignupAllowed } from '../../helpers/utils'
|
||||||
|
import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
|
||||||
import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
|
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
|
||||||
import { asyncMiddleware } from '../../middlewares'
|
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
|
||||||
import { ServerConfig } from '../../../shared'
|
import { omit } from 'lodash'
|
||||||
|
|
||||||
const configRouter = express.Router()
|
const configRouter = express.Router()
|
||||||
|
|
||||||
configRouter.get('/',
|
configRouter.get('/',
|
||||||
asyncMiddleware(getConfig)
|
asyncMiddleware(getConfig)
|
||||||
)
|
)
|
||||||
|
configRouter.get('/custom',
|
||||||
|
authenticate,
|
||||||
|
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
||||||
|
asyncMiddleware(getCustomConfig)
|
||||||
|
)
|
||||||
|
configRouter.put('/custom',
|
||||||
|
authenticate,
|
||||||
|
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
||||||
|
asyncMiddleware(customConfigUpdateValidator),
|
||||||
|
asyncMiddleware(updateCustomConfig)
|
||||||
|
)
|
||||||
|
configRouter.delete('/custom',
|
||||||
|
authenticate,
|
||||||
|
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
||||||
|
asyncMiddleware(deleteCustomConfig)
|
||||||
|
)
|
||||||
|
|
||||||
async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function getConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const allowed = await isSignupAllowed()
|
const allowed = await isSignupAllowed()
|
||||||
|
@ -43,8 +62,72 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
|
||||||
return res.json(json)
|
return res.json(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
const data = customConfig()
|
||||||
|
|
||||||
|
return res.json(data).end()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
await unlinkPromise(CONFIG.CUSTOM_FILE)
|
||||||
|
|
||||||
|
reloadConfig()
|
||||||
|
|
||||||
|
const data = customConfig()
|
||||||
|
|
||||||
|
return res.json(data).end()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
const toUpdate: CustomConfig = req.body
|
||||||
|
|
||||||
|
// Need to change the videoQuota key a little bit
|
||||||
|
const toUpdateJSON = omit(toUpdate, 'videoQuota')
|
||||||
|
toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
|
||||||
|
|
||||||
|
await writeFilePromise(CONFIG.CUSTOM_FILE, JSON.stringify(toUpdateJSON))
|
||||||
|
|
||||||
|
reloadConfig()
|
||||||
|
|
||||||
|
const data = customConfig()
|
||||||
|
return res.json(data).end()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
configRouter
|
configRouter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function customConfig (): CustomConfig {
|
||||||
|
return {
|
||||||
|
cache: {
|
||||||
|
previews: {
|
||||||
|
size: CONFIG.CACHE.PREVIEWS.SIZE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
enabled: CONFIG.SIGNUP.ENABLED,
|
||||||
|
limit: CONFIG.SIGNUP.LIMIT
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
email: CONFIG.ADMIN.EMAIL
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
||||||
|
},
|
||||||
|
transcoding: {
|
||||||
|
enabled: CONFIG.TRANSCODING.ENABLED,
|
||||||
|
threads: CONFIG.TRANSCODING.THREADS,
|
||||||
|
resolutions: {
|
||||||
|
'240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
|
||||||
|
'360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ],
|
||||||
|
'480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
|
||||||
|
'720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
|
||||||
|
'1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const resolution of resolutions) {
|
for (const resolution of resolutions) {
|
||||||
if (configResolutions[resolution.toString()] === true && videoFileHeight > resolution) {
|
if (configResolutions[resolution + 'p'] === true && videoFileHeight > resolution) {
|
||||||
resolutionsEnabled.push(resolution)
|
resolutionsEnabled.push(resolution)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import * as config from 'config'
|
import { IConfig } from 'config'
|
||||||
import { join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
import { JobCategory, JobState, VideoRateType } from '../../shared/models'
|
import { JobCategory, JobState, VideoRateType } from '../../shared/models'
|
||||||
import { ActivityPubActorType } from '../../shared/models/activitypub'
|
import { ActivityPubActorType } from '../../shared/models/activitypub'
|
||||||
import { FollowState } from '../../shared/models/actors'
|
import { FollowState } from '../../shared/models/actors'
|
||||||
import { VideoPrivacy } from '../../shared/models/videos'
|
import { VideoPrivacy } from '../../shared/models/videos'
|
||||||
// Do not use barrels, remain constants as independent as possible
|
// Do not use barrels, remain constants as independent as possible
|
||||||
import { buildPath, isTestInstance, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
|
import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
|
||||||
|
|
||||||
|
// Use a variable to reload the configuration if we need
|
||||||
|
let config: IConfig = require('config')
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -82,6 +85,7 @@ let SCHEDULER_INTERVAL = 60000 * 60
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
|
CUSTOM_FILE: getLocalConfigFilePath(),
|
||||||
LISTEN: {
|
LISTEN: {
|
||||||
PORT: config.get<number>('listen.port')
|
PORT: config.get<number>('listen.port')
|
||||||
},
|
},
|
||||||
|
@ -110,29 +114,29 @@ const CONFIG = {
|
||||||
HOST: ''
|
HOST: ''
|
||||||
},
|
},
|
||||||
ADMIN: {
|
ADMIN: {
|
||||||
EMAIL: config.get<string>('admin.email')
|
get EMAIL () { return config.get<string>('admin.email') }
|
||||||
},
|
},
|
||||||
SIGNUP: {
|
SIGNUP: {
|
||||||
ENABLED: config.get<boolean>('signup.enabled'),
|
get ENABLED () { return config.get<boolean>('signup.enabled') },
|
||||||
LIMIT: config.get<number>('signup.limit')
|
get LIMIT () { return config.get<number>('signup.limit') }
|
||||||
},
|
},
|
||||||
USER: {
|
USER: {
|
||||||
VIDEO_QUOTA: config.get<number>('user.video_quota')
|
get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
|
||||||
},
|
},
|
||||||
TRANSCODING: {
|
TRANSCODING: {
|
||||||
ENABLED: config.get<boolean>('transcoding.enabled'),
|
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
||||||
THREADS: config.get<number>('transcoding.threads'),
|
get THREADS () { return config.get<number>('transcoding.threads') },
|
||||||
RESOLUTIONS: {
|
RESOLUTIONS: {
|
||||||
'240' : config.get<boolean>('transcoding.resolutions.240p'),
|
get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
|
||||||
'360': config.get<boolean>('transcoding.resolutions.360p'),
|
get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
|
||||||
'480': config.get<boolean>('transcoding.resolutions.480p'),
|
get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
|
||||||
'720': config.get<boolean>('transcoding.resolutions.720p'),
|
get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
|
||||||
'1080': config.get<boolean>('transcoding.resolutions.1080p')
|
get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CACHE: {
|
CACHE: {
|
||||||
PREVIEWS: {
|
PREVIEWS: {
|
||||||
SIZE: config.get<number>('cache.previews.size')
|
get SIZE () { return config.get<number>('cache.previews.size') }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,8 +365,7 @@ if (isTestInstance() === true) {
|
||||||
SCHEDULER_INTERVAL = 10000
|
SCHEDULER_INTERVAL = 10000
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
|
updateWebserverConfig()
|
||||||
CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -404,3 +407,50 @@ export {
|
||||||
AVATAR_MIMETYPE_EXT,
|
AVATAR_MIMETYPE_EXT,
|
||||||
SCHEDULER_INTERVAL
|
SCHEDULER_INTERVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function getLocalConfigFilePath () {
|
||||||
|
const configSources = config.util.getConfigSources()
|
||||||
|
if (configSources.length === 0) throw new Error('Invalid config source.')
|
||||||
|
|
||||||
|
let filename = 'local'
|
||||||
|
if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
|
||||||
|
if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
|
||||||
|
|
||||||
|
return join(dirname(configSources[ 0 ].name), filename + '.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWebserverConfig () {
|
||||||
|
CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
|
||||||
|
CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reloadConfig () {
|
||||||
|
|
||||||
|
function directory () {
|
||||||
|
if (process.env.NODE_CONFIG_DIR) {
|
||||||
|
return process.env.NODE_CONFIG_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
return join(root(), 'config')
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge () {
|
||||||
|
for (const fileName in require.cache) {
|
||||||
|
if (-1 === fileName.indexOf(directory())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete require.cache[fileName]
|
||||||
|
}
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('config')]
|
||||||
|
}
|
||||||
|
|
||||||
|
purge()
|
||||||
|
|
||||||
|
config = require('config')
|
||||||
|
|
||||||
|
updateWebserverConfig()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import * as express from 'express'
|
||||||
|
import { body } from 'express-validator/check'
|
||||||
|
import { isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
|
||||||
|
import { logger } from '../../helpers/logger'
|
||||||
|
import { areValidationErrors } from './utils'
|
||||||
|
|
||||||
|
const customConfigUpdateValidator = [
|
||||||
|
body('cache.previews.size').isInt().withMessage('Should have a valid previews size'),
|
||||||
|
body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'),
|
||||||
|
body('signup.limit').isInt().withMessage('Should have a valid signup limit'),
|
||||||
|
body('admin.email').isEmail().withMessage('Should have a valid administrator email'),
|
||||||
|
body('user.videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid video quota'),
|
||||||
|
body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'),
|
||||||
|
body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'),
|
||||||
|
body('transcoding.resolutions.240p').isBoolean().withMessage('Should have a valid transcoding 240p resolution enabled boolean'),
|
||||||
|
body('transcoding.resolutions.360p').isBoolean().withMessage('Should have a valid transcoding 360p resolution enabled boolean'),
|
||||||
|
body('transcoding.resolutions.480p').isBoolean().withMessage('Should have a valid transcoding 480p resolution enabled boolean'),
|
||||||
|
body('transcoding.resolutions.720p').isBoolean().withMessage('Should have a valid transcoding 720p resolution enabled boolean'),
|
||||||
|
body('transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'),
|
||||||
|
|
||||||
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export {
|
||||||
|
customConfigUpdateValidator
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
/* tslint:disable:no-unused-expression */
|
||||||
|
|
||||||
|
import { omit } from 'lodash'
|
||||||
|
import 'mocha'
|
||||||
|
import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
|
||||||
|
|
||||||
|
import {
|
||||||
|
createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo,
|
||||||
|
setAccessTokensToServers, userLogin
|
||||||
|
} from '../../utils'
|
||||||
|
|
||||||
|
describe('Test config API validators', function () {
|
||||||
|
const path = '/api/v1/config/custom'
|
||||||
|
let server: ServerInfo
|
||||||
|
let userAccessToken: string
|
||||||
|
const updateParams: CustomConfig = {
|
||||||
|
cache: {
|
||||||
|
previews: {
|
||||||
|
size: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
enabled: false,
|
||||||
|
limit: 5
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
email: 'superadmin1@example.com'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
videoQuota: 5242881
|
||||||
|
},
|
||||||
|
transcoding: {
|
||||||
|
enabled: true,
|
||||||
|
threads: 1,
|
||||||
|
resolutions: {
|
||||||
|
'240p': false,
|
||||||
|
'360p': true,
|
||||||
|
'480p': true,
|
||||||
|
'720p': false,
|
||||||
|
'1080p': false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
await flushTests()
|
||||||
|
server = await runServer(1)
|
||||||
|
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
|
|
||||||
|
const user = {
|
||||||
|
username: 'user1',
|
||||||
|
password: 'password'
|
||||||
|
}
|
||||||
|
await createUser(server.url, server.accessToken, user.username, user.password)
|
||||||
|
userAccessToken = await userLogin(server, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When getting the configuration', function () {
|
||||||
|
it('Should fail without token', async function () {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
statusCodeExpected: 401
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail if the user is not an administrator', async function () {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
token: userAccessToken,
|
||||||
|
statusCodeExpected: 403
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When updating the configuration', function () {
|
||||||
|
it('Should fail without token', async function () {
|
||||||
|
await makePutBodyRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
fields: updateParams,
|
||||||
|
statusCodeExpected: 401
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail if the user is not an administrator', async function () {
|
||||||
|
await makePutBodyRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
fields: updateParams,
|
||||||
|
token: userAccessToken,
|
||||||
|
statusCodeExpected: 403
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail if it misses a key', async function () {
|
||||||
|
const newUpdateParams = omit(updateParams, 'admin.email')
|
||||||
|
|
||||||
|
await makePutBodyRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
fields: newUpdateParams,
|
||||||
|
token: server.accessToken,
|
||||||
|
statusCodeExpected: 400
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should success with the correct parameters', async function () {
|
||||||
|
await makePutBodyRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
fields: updateParams,
|
||||||
|
token: server.accessToken,
|
||||||
|
statusCodeExpected: 200
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When deleting the configuration', function () {
|
||||||
|
it('Should fail without token', async function () {
|
||||||
|
await makeDeleteRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
statusCodeExpected: 401
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail if the user is not an administrator', async function () {
|
||||||
|
await makeDeleteRequest({
|
||||||
|
url: server.url,
|
||||||
|
path,
|
||||||
|
token: userAccessToken,
|
||||||
|
statusCodeExpected: 403
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
killallServers([ server ])
|
||||||
|
|
||||||
|
// Keep the logs if the test failed
|
||||||
|
if (this['ok']) {
|
||||||
|
await flushTests()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -2,13 +2,14 @@
|
||||||
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
|
import { deleteCustomConfig, killallServers, reRunServer } from '../../utils'
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getConfig,
|
getConfig,
|
||||||
flushTests,
|
flushTests,
|
||||||
runServer,
|
runServer,
|
||||||
registerUser
|
registerUser, getCustomConfig, setAccessTokensToServers, updateCustomConfig
|
||||||
} from '../../utils/index'
|
} from '../../utils/index'
|
||||||
|
|
||||||
describe('Test config', function () {
|
describe('Test config', function () {
|
||||||
|
@ -19,6 +20,7 @@ describe('Test config', function () {
|
||||||
|
|
||||||
await flushTests()
|
await flushTests()
|
||||||
server = await runServer(1)
|
server = await runServer(1)
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have a correct config on a server with registration enabled', async function () {
|
it('Should have a correct config on a server with registration enabled', async function () {
|
||||||
|
@ -43,6 +45,114 @@ describe('Test config', function () {
|
||||||
expect(data.signup.allowed).to.be.false
|
expect(data.signup.allowed).to.be.false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should get the customized configuration', async function () {
|
||||||
|
const res = await getCustomConfig(server.url, server.accessToken)
|
||||||
|
const data = res.body
|
||||||
|
|
||||||
|
expect(data.cache.previews.size).to.equal(1)
|
||||||
|
expect(data.signup.enabled).to.be.true
|
||||||
|
expect(data.signup.limit).to.equal(4)
|
||||||
|
expect(data.admin.email).to.equal('admin1@example.com')
|
||||||
|
expect(data.user.videoQuota).to.equal(5242880)
|
||||||
|
expect(data.transcoding.enabled).to.be.false
|
||||||
|
expect(data.transcoding.threads).to.equal(2)
|
||||||
|
expect(data.transcoding.resolutions['240p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['360p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['480p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['720p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['1080p']).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should update the customized configuration', async function () {
|
||||||
|
const newCustomConfig = {
|
||||||
|
cache: {
|
||||||
|
previews: {
|
||||||
|
size: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
enabled: false,
|
||||||
|
limit: 5
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
email: 'superadmin1@example.com'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
videoQuota: 5242881
|
||||||
|
},
|
||||||
|
transcoding: {
|
||||||
|
enabled: true,
|
||||||
|
threads: 1,
|
||||||
|
resolutions: {
|
||||||
|
'240p': false,
|
||||||
|
'360p': true,
|
||||||
|
'480p': true,
|
||||||
|
'720p': false,
|
||||||
|
'1080p': false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await updateCustomConfig(server.url, server.accessToken, newCustomConfig)
|
||||||
|
|
||||||
|
const res = await getCustomConfig(server.url, server.accessToken)
|
||||||
|
const data = res.body
|
||||||
|
|
||||||
|
expect(data.cache.previews.size).to.equal(2)
|
||||||
|
expect(data.signup.enabled).to.be.false
|
||||||
|
expect(data.signup.limit).to.equal(5)
|
||||||
|
expect(data.admin.email).to.equal('superadmin1@example.com')
|
||||||
|
expect(data.user.videoQuota).to.equal(5242881)
|
||||||
|
expect(data.transcoding.enabled).to.be.true
|
||||||
|
expect(data.transcoding.threads).to.equal(1)
|
||||||
|
expect(data.transcoding.resolutions['240p']).to.be.false
|
||||||
|
expect(data.transcoding.resolutions['360p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['480p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['720p']).to.be.false
|
||||||
|
expect(data.transcoding.resolutions['1080p']).to.be.false
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have the configuration updated after a restart', async function () {
|
||||||
|
killallServers([ server ])
|
||||||
|
|
||||||
|
await reRunServer(server)
|
||||||
|
|
||||||
|
const res = await getCustomConfig(server.url, server.accessToken)
|
||||||
|
const data = res.body
|
||||||
|
|
||||||
|
expect(data.cache.previews.size).to.equal(2)
|
||||||
|
expect(data.signup.enabled).to.be.false
|
||||||
|
expect(data.signup.limit).to.equal(5)
|
||||||
|
expect(data.admin.email).to.equal('superadmin1@example.com')
|
||||||
|
expect(data.user.videoQuota).to.equal(5242881)
|
||||||
|
expect(data.transcoding.enabled).to.be.true
|
||||||
|
expect(data.transcoding.threads).to.equal(1)
|
||||||
|
expect(data.transcoding.resolutions['240p']).to.be.false
|
||||||
|
expect(data.transcoding.resolutions['360p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['480p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['720p']).to.be.false
|
||||||
|
expect(data.transcoding.resolutions['1080p']).to.be.false
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should remove the custom configuration', async function () {
|
||||||
|
await deleteCustomConfig(server.url, server.accessToken)
|
||||||
|
|
||||||
|
const res = await getCustomConfig(server.url, server.accessToken)
|
||||||
|
const data = res.body
|
||||||
|
|
||||||
|
expect(data.cache.previews.size).to.equal(1)
|
||||||
|
expect(data.signup.enabled).to.be.true
|
||||||
|
expect(data.signup.limit).to.equal(4)
|
||||||
|
expect(data.admin.email).to.equal('admin1@example.com')
|
||||||
|
expect(data.user.videoQuota).to.equal(5242880)
|
||||||
|
expect(data.transcoding.enabled).to.be.false
|
||||||
|
expect(data.transcoding.threads).to.equal(2)
|
||||||
|
expect(data.transcoding.resolutions['240p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['360p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['480p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['720p']).to.be.true
|
||||||
|
expect(data.transcoding.resolutions['1080p']).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
process.kill(-server.app.pid)
|
process.kill(-server.app.pid)
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,10 @@ import { keyBy } from 'lodash'
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
|
import { readdirPromise } from '../../../helpers/core-utils'
|
||||||
import {
|
import {
|
||||||
completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, getVideoPrivacies,
|
completeVideoCheck, flushTests, getVideo, getVideoCategories, getVideoLanguages, getVideoLicences, getVideoPrivacies,
|
||||||
getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, readdirPromise, removeVideo, runServer, searchVideo,
|
getVideosList, getVideosListPagination, getVideosListSort, killallServers, rateVideo, removeVideo, runServer, searchVideo,
|
||||||
searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testVideoImage, updateVideo, uploadVideo, viewVideo
|
searchVideoWithPagination, searchVideoWithSort, ServerInfo, setAccessTokensToServers, testVideoImage, updateVideo, uploadVideo, viewVideo
|
||||||
} from '../../utils'
|
} from '../../utils'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as WebTorrent from 'webtorrent'
|
import * as WebTorrent from 'webtorrent'
|
||||||
import { readFile, readdir } from 'fs'
|
|
||||||
|
|
||||||
let webtorrent = new WebTorrent()
|
let webtorrent = new WebTorrent()
|
||||||
|
|
||||||
|
@ -7,26 +6,6 @@ function immutableAssign <T, U> (target: T, source: U) {
|
||||||
return Object.assign<{}, T, U>({}, target, source)
|
return Object.assign<{}, T, U>({}, target, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
function readFilePromise (path: string) {
|
|
||||||
return new Promise<Buffer>((res, rej) => {
|
|
||||||
readFile(path, (err, data) => {
|
|
||||||
if (err) return rej(err)
|
|
||||||
|
|
||||||
return res(data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function readdirPromise (path: string) {
|
|
||||||
return new Promise<string[]>((res, rej) => {
|
|
||||||
readdir(path, (err, files) => {
|
|
||||||
if (err) return rej(err)
|
|
||||||
|
|
||||||
return res(files)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default interval -> 5 minutes
|
// Default interval -> 5 minutes
|
||||||
function dateIsValid (dateString: string, interval = 300000) {
|
function dateIsValid (dateString: string, interval = 300000) {
|
||||||
const dateToCheck = new Date(dateString)
|
const dateToCheck = new Date(dateString)
|
||||||
|
@ -48,8 +27,6 @@ function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
readFilePromise,
|
|
||||||
readdirPromise,
|
|
||||||
dateIsValid,
|
dateIsValid,
|
||||||
wait,
|
wait,
|
||||||
webtorrentAdd,
|
webtorrentAdd,
|
||||||
|
|
|
@ -99,7 +99,7 @@ function makePostBodyRequest (options: {
|
||||||
function makePutBodyRequest (options: {
|
function makePutBodyRequest (options: {
|
||||||
url: string,
|
url: string,
|
||||||
path: string,
|
path: string,
|
||||||
token: string,
|
token?: string,
|
||||||
fields: { [ fieldName: string ]: any },
|
fields: { [ fieldName: string ]: any },
|
||||||
statusCodeExpected?: number
|
statusCodeExpected?: number
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import * as request from 'supertest'
|
import * as request from 'supertest'
|
||||||
|
import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../'
|
||||||
|
import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
|
||||||
|
|
||||||
function getConfig (url: string) {
|
function getConfig (url: string) {
|
||||||
const path = '/api/v1/config'
|
const path = '/api/v1/config'
|
||||||
|
@ -10,8 +12,45 @@ function getConfig (url: string) {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCustomConfig (url: string, token: string, statusCodeExpected = 200) {
|
||||||
|
const path = '/api/v1/config/custom'
|
||||||
|
|
||||||
|
return makeGetRequest({
|
||||||
|
url,
|
||||||
|
token,
|
||||||
|
path,
|
||||||
|
statusCodeExpected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCustomConfig (url: string, token: string, newCustomConfig: CustomConfig, statusCodeExpected = 200) {
|
||||||
|
const path = '/api/v1/config/custom'
|
||||||
|
|
||||||
|
return makePutBodyRequest({
|
||||||
|
url,
|
||||||
|
token,
|
||||||
|
path,
|
||||||
|
fields: newCustomConfig,
|
||||||
|
statusCodeExpected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteCustomConfig (url: string, token: string, statusCodeExpected = 200) {
|
||||||
|
const path = '/api/v1/config/custom'
|
||||||
|
|
||||||
|
return makeDeleteRequest({
|
||||||
|
url,
|
||||||
|
token,
|
||||||
|
path,
|
||||||
|
statusCodeExpected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getConfig
|
getConfig,
|
||||||
|
getCustomConfig,
|
||||||
|
updateCustomConfig,
|
||||||
|
deleteCustomConfig
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ import { readFile } from 'fs'
|
||||||
import * as parseTorrent from 'parse-torrent'
|
import * as parseTorrent from 'parse-torrent'
|
||||||
import { extname, isAbsolute, join } from 'path'
|
import { extname, isAbsolute, join } from 'path'
|
||||||
import * as request from 'supertest'
|
import * as request from 'supertest'
|
||||||
import { getMyUserInformation, makeGetRequest, readFilePromise, ServerInfo } from '../'
|
import { getMyUserInformation, makeGetRequest, ServerInfo } from '../'
|
||||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
|
import { readFileBufferPromise } from '../../../helpers/core-utils'
|
||||||
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
|
import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers'
|
||||||
import { dateIsValid, webtorrentAdd } from '../index'
|
import { dateIsValid, webtorrentAdd } from '../index'
|
||||||
|
|
||||||
|
@ -210,7 +211,7 @@ async function testVideoImage (url: string, imageName: string, imagePath: string
|
||||||
.get(imagePath)
|
.get(imagePath)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
const data = await readFilePromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension))
|
const data = await readFileBufferPromise(join(__dirname, '..', '..', 'api', 'fixtures', imageName + extension))
|
||||||
|
|
||||||
return data.equals(res.body)
|
return data.equals(res.body)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
export interface CustomConfig {
|
||||||
|
cache: {
|
||||||
|
previews: {
|
||||||
|
size: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signup: {
|
||||||
|
enabled: boolean
|
||||||
|
limit: number
|
||||||
|
}
|
||||||
|
|
||||||
|
admin: {
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
user: {
|
||||||
|
videoQuota: number
|
||||||
|
}
|
||||||
|
|
||||||
|
transcoding: {
|
||||||
|
enabled: boolean
|
||||||
|
threads: number
|
||||||
|
resolutions: {
|
||||||
|
'240p': boolean
|
||||||
|
'360p': boolean
|
||||||
|
'480p': boolean
|
||||||
|
'720p': boolean
|
||||||
|
'1080p': boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,4 +5,4 @@ export * from './videos'
|
||||||
export * from './job.model'
|
export * from './job.model'
|
||||||
export * from './oauth-client-local.model'
|
export * from './oauth-client-local.model'
|
||||||
export * from './result-list.model'
|
export * from './result-list.model'
|
||||||
export * from './server-config.model'
|
export * from './config/server-config.model'
|
||||||
|
|
|
@ -5,6 +5,7 @@ export enum UserRight {
|
||||||
MANAGE_VIDEO_ABUSES,
|
MANAGE_VIDEO_ABUSES,
|
||||||
MANAGE_VIDEO_BLACKLIST,
|
MANAGE_VIDEO_BLACKLIST,
|
||||||
MANAGE_JOBS,
|
MANAGE_JOBS,
|
||||||
|
MANAGE_CONFIGURATION,
|
||||||
REMOVE_ANY_VIDEO,
|
REMOVE_ANY_VIDEO,
|
||||||
REMOVE_ANY_VIDEO_CHANNEL,
|
REMOVE_ANY_VIDEO_CHANNEL,
|
||||||
REMOVE_ANY_VIDEO_COMMENT
|
REMOVE_ANY_VIDEO_COMMENT
|
||||||
|
|
|
@ -31,7 +31,7 @@ $ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/la
|
||||||
cd /home/peertube && \
|
cd /home/peertube && \
|
||||||
sudo -u peertube mkdir config storage versions && \
|
sudo -u peertube mkdir config storage versions && \
|
||||||
cd versions && \
|
cd versions && \
|
||||||
sudo -u peertube wget "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \
|
sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \
|
||||||
sudo -u peertube unzip peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \
|
sudo -u peertube unzip peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \
|
||||||
cd ../ && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \
|
cd ../ && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \
|
||||||
cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile
|
cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile
|
||||||
|
@ -227,7 +227,7 @@ $ NODE_ENV=production npm run reset-password -- -u root
|
||||||
```
|
```
|
||||||
$ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/latest | grep tag_name | cut -d '"' -f 4) && \
|
$ VERSION=$(curl -s https://api.github.com/repos/chocobozzz/peertube/releases/latest | grep tag_name | cut -d '"' -f 4) && \
|
||||||
cd /home/peertube/versions && \
|
cd /home/peertube/versions && \
|
||||||
sudo -u peertube wget "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \
|
sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/${VERSION}/peertube-${VERSION}.zip" && \
|
||||||
sudo -u peertube unzip -o peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \
|
sudo -u peertube unzip -o peertube-${VERSION}.zip && sudo -u peertube rm peertube-${VERSION}.zip && \
|
||||||
cd ../ && sudo rm ./peertube-latest && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \
|
cd ../ && sudo rm ./peertube-latest && sudo -u peertube ln -s versions/peertube-${VERSION} ./peertube-latest && \
|
||||||
cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile && \
|
cd ./peertube-latest && sudo -u peertube yarn install --production --pure-lockfile && \
|
||||||
|
|
Loading…
Reference in New Issue