Allow configuration to be static/readonly (#4315)
* Allow configuration to be static/readonly * Make all components disableable * Improve disabled component styling * Rename edits allowed field in configuration * Fix CI
This commit is contained in:
parent
badacdbb4a
commit
8d8a037e3f
|
@ -63,7 +63,7 @@
|
||||||
<div class="col-md-7 col-xl-5"></div>
|
<div class="col-md-7 col-xl-5"></div>
|
||||||
<div class="col-md-5 col-xl-5">
|
<div class="col-md-5 col-xl-5">
|
||||||
|
|
||||||
<div class="form-error submit-error" i18n *ngIf="!form.valid">
|
<div class="form-error submit-error" i18n *ngIf="!form.valid && serverConfig.allowEdits">
|
||||||
There are errors in the form:
|
There are errors in the form:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -77,7 +77,11 @@
|
||||||
You cannot allow live replay if you don't enable transcoding.
|
You cannot allow live replay if you don't enable transcoding.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid || !hasConsistentOptions()">
|
<span i18n *ngIf="!serverConfig.allowEdits">
|
||||||
|
You cannot change the server configuration because it's managed externally.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid || !hasConsistentOptions() || !serverConfig.allowEdits">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -33,6 +33,11 @@ input[type=number] {
|
||||||
top: 5px;
|
top: 5px;
|
||||||
right: 2.5rem;
|
right: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[disabled] {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox] {
|
input[type=checkbox] {
|
||||||
|
@ -93,6 +98,11 @@ textarea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-group-right {
|
.form-group-right {
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,6 +258,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
|
|
||||||
this.loadConfigAndUpdateForm()
|
this.loadConfigAndUpdateForm()
|
||||||
this.loadCategoriesAndLanguages()
|
this.loadCategoriesAndLanguages()
|
||||||
|
if (!this.serverConfig.allowEdits) {
|
||||||
|
this.form.disable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formValidated () {
|
formValidated () {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<textarea #textarea
|
<textarea #textarea
|
||||||
[(ngModel)]="content" (ngModelChange)="onModelChange()"
|
[(ngModel)]="content" (ngModelChange)="onModelChange()"
|
||||||
class="form-control" [ngClass]="classes"
|
class="form-control" [ngClass]="classes"
|
||||||
|
[attr.disabled]="disabled"
|
||||||
[ngStyle]="{ height: textareaHeight }"
|
[ngStyle]="{ height: textareaHeight }"
|
||||||
[id]="name" [name]="name">
|
[id]="name" [name]="name">
|
||||||
</textarea>
|
</textarea>
|
||||||
|
@ -25,11 +26,11 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<my-button
|
<my-button
|
||||||
*ngIf="!isMaximized" [title]="maximizeInText" className="maximize-button" icon="fullscreen" (click)="onMaximizeClick()"
|
*ngIf="!isMaximized" [title]="maximizeInText" className="maximize-button" icon="fullscreen" (click)="onMaximizeClick()" [disabled]="disabled"
|
||||||
></my-button>
|
></my-button>
|
||||||
|
|
||||||
<my-button
|
<my-button
|
||||||
*ngIf="isMaximized" [title]="maximizeOutText" className="maximize-button" icon="exit-fullscreen" (click)="onMaximizeClick()"
|
*ngIf="isMaximized" [title]="maximizeOutText" className="maximize-button" icon="exit-fullscreen" (click)="onMaximizeClick()" [disabled]="disabled"
|
||||||
></my-button>
|
></my-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
|
||||||
previewHTML: SafeHtml | string = ''
|
previewHTML: SafeHtml | string = ''
|
||||||
|
|
||||||
isMaximized = false
|
isMaximized = false
|
||||||
|
disabled = false
|
||||||
|
|
||||||
maximizeInText = $localize`Maximize editor`
|
maximizeInText = $localize`Maximize editor`
|
||||||
maximizeOutText = $localize`Exit maximized editor`
|
maximizeOutText = $localize`Exit maximized editor`
|
||||||
|
@ -108,6 +109,10 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDisabledState (isDisabled: boolean) {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
|
|
||||||
private lockBodyScroll () {
|
private lockBodyScroll () {
|
||||||
this.scrollPosition = this.viewportScroller.getScrollPosition()
|
this.scrollPosition = this.viewportScroller.getScrollPosition()
|
||||||
document.getElementById('content').classList.add('lock-scroll')
|
document.getElementById('content').classList.add('lock-scroll')
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
[searchable]="true"
|
[searchable]="true"
|
||||||
[closeOnSelect]="false"
|
[closeOnSelect]="false"
|
||||||
|
[disabled]="disabled"
|
||||||
|
|
||||||
bindValue="id"
|
bindValue="id"
|
||||||
bindLabel="label"
|
bindLabel="label"
|
||||||
|
|
|
@ -23,6 +23,8 @@ export class SelectCheckboxComponent implements OnInit, ControlValueAccessor {
|
||||||
@Input() selectableGroupAsModel: boolean
|
@Input() selectableGroupAsModel: boolean
|
||||||
@Input() placeholder: string
|
@Input() placeholder: string
|
||||||
|
|
||||||
|
disabled = false
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
if (!this.placeholder) this.placeholder = $localize`Add a new option`
|
if (!this.placeholder) this.placeholder = $localize`Add a new option`
|
||||||
}
|
}
|
||||||
|
@ -59,6 +61,10 @@ export class SelectCheckboxComponent implements OnInit, ControlValueAccessor {
|
||||||
this.propagateChange(this.selectedItems)
|
this.propagateChange(this.selectedItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDisabledState (isDisabled: boolean) {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
|
|
||||||
compareFn (item: SelectOptionsItem, selected: ItemSelectCheckboxValue) {
|
compareFn (item: SelectOptionsItem, selected: ItemSelectCheckboxValue) {
|
||||||
if (typeof selected === 'string' || typeof selected === 'number') {
|
if (typeof selected === 'string' || typeof selected === 'number') {
|
||||||
return item.id === selected
|
return item.id === selected
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
[searchable]="searchable"
|
[searchable]="searchable"
|
||||||
[groupBy]="groupBy"
|
[groupBy]="groupBy"
|
||||||
[labelForId]="labelForId"
|
[labelForId]="labelForId"
|
||||||
|
[disabled]="disabled"
|
||||||
|
|
||||||
[(ngModel)]="selectedId"
|
[(ngModel)]="selectedId"
|
||||||
(ngModelChange)="onModelChange()"
|
(ngModelChange)="onModelChange()"
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class SelectCustomValueComponent implements ControlValueAccessor, OnChang
|
||||||
|
|
||||||
customValue: number | string = ''
|
customValue: number | string = ''
|
||||||
selectedId: number | string
|
selectedId: number | string
|
||||||
|
disabled = false
|
||||||
|
|
||||||
itemsWithCustom: SelectOptionsItem[] = []
|
itemsWithCustom: SelectOptionsItem[] = []
|
||||||
|
|
||||||
|
@ -75,4 +76,8 @@ export class SelectCustomValueComponent implements ControlValueAccessor, OnChang
|
||||||
isCustomValue () {
|
isCustomValue () {
|
||||||
return this.selectedId === 'other'
|
return this.selectedId === 'other'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDisabledState (isDisabled: boolean) {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[labelForId]="labelForId"
|
[labelForId]="labelForId"
|
||||||
[searchable]="searchable"
|
[searchable]="searchable"
|
||||||
[searchFn]="searchFn"
|
[searchFn]="searchFn"
|
||||||
|
[disabled]="disabled"
|
||||||
|
|
||||||
bindLabel="label"
|
bindLabel="label"
|
||||||
bindValue="id"
|
bindValue="id"
|
||||||
|
|
|
@ -23,6 +23,7 @@ export class SelectOptionsComponent implements ControlValueAccessor {
|
||||||
@Input() searchFn: any
|
@Input() searchFn: any
|
||||||
|
|
||||||
selectedId: number | string
|
selectedId: number | string
|
||||||
|
disabled = false
|
||||||
|
|
||||||
propagateChange = (_: any) => { /* empty */ }
|
propagateChange = (_: any) => { /* empty */ }
|
||||||
|
|
||||||
|
@ -48,4 +49,8 @@ export class SelectOptionsComponent implements ControlValueAccessor {
|
||||||
onModelChange () {
|
onModelChange () {
|
||||||
this.propagateChange(this.selectedId)
|
this.propagateChange(this.selectedId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDisabledState (isDisabled: boolean) {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,6 +364,9 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
select[disabled] {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $width) {
|
@media screen and (max-width: $width) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -243,6 +243,11 @@ peertube:
|
||||||
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
|
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
|
||||||
url: 'https://joinpeertube.org/api/v1/versions.json'
|
url: 'https://joinpeertube.org/api/v1/versions.json'
|
||||||
|
|
||||||
|
webadmin:
|
||||||
|
configuration:
|
||||||
|
edit:
|
||||||
|
allowed: true
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
previews:
|
previews:
|
||||||
size: 500 # Max number of previews you want to cache
|
size: 500 # Max number of previews you want to cache
|
||||||
|
|
|
@ -241,6 +241,11 @@ peertube:
|
||||||
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
|
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
|
||||||
url: 'https://joinpeertube.org/api/v1/versions.json'
|
url: 'https://joinpeertube.org/api/v1/versions.json'
|
||||||
|
|
||||||
|
webadmin:
|
||||||
|
configuration:
|
||||||
|
# Set to false if you want the config to be readonly
|
||||||
|
allow_edits: true
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# From this point, all the following keys can be overridden by the web interface
|
# From this point, all the following keys can be overridden by the web interface
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { objectConverter } from '../../helpers/core-utils'
|
||||||
import { CONFIG, reloadConfig } from '../../initializers/config'
|
import { CONFIG, reloadConfig } from '../../initializers/config'
|
||||||
import { ClientHtml } from '../../lib/client-html'
|
import { ClientHtml } from '../../lib/client-html'
|
||||||
import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares'
|
import { asyncMiddleware, authenticate, ensureUserHasRight, openapiOperationDoc } from '../../middlewares'
|
||||||
import { customConfigUpdateValidator } from '../../middlewares/validators/config'
|
import { customConfigUpdateValidator, ensureConfigIsEditable } from '../../middlewares/validators/config'
|
||||||
|
|
||||||
const configRouter = express.Router()
|
const configRouter = express.Router()
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ configRouter.put('/custom',
|
||||||
openapiOperationDoc({ operationId: 'putCustomConfig' }),
|
openapiOperationDoc({ operationId: 'putCustomConfig' }),
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
||||||
|
ensureConfigIsEditable,
|
||||||
customConfigUpdateValidator,
|
customConfigUpdateValidator,
|
||||||
asyncMiddleware(updateCustomConfig)
|
asyncMiddleware(updateCustomConfig)
|
||||||
)
|
)
|
||||||
|
@ -46,6 +47,7 @@ configRouter.delete('/custom',
|
||||||
openapiOperationDoc({ operationId: 'delCustomConfig' }),
|
openapiOperationDoc({ operationId: 'delCustomConfig' }),
|
||||||
authenticate,
|
authenticate,
|
||||||
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
|
||||||
|
ensureConfigIsEditable,
|
||||||
asyncMiddleware(deleteCustomConfig)
|
asyncMiddleware(deleteCustomConfig)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -195,6 +195,13 @@ const CONFIG = {
|
||||||
URL: config.get<string>('peertube.check_latest_version.url')
|
URL: config.get<string>('peertube.check_latest_version.url')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
WEBADMIN: {
|
||||||
|
CONFIGURATION: {
|
||||||
|
EDITS: {
|
||||||
|
ALLOWED: config.get<boolean>('webadmin.configuration.edit.allowed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
ADMIN: {
|
ADMIN: {
|
||||||
get EMAIL () { return config.get<string>('admin.email') }
|
get EMAIL () { return config.get<string>('admin.email') }
|
||||||
},
|
},
|
||||||
|
@ -411,14 +418,22 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function getLocalConfigFilePath () {
|
function getLocalConfigFilePath () {
|
||||||
const configSources = config.util.getConfigSources()
|
const localConfigDir = getLocalConfigDir()
|
||||||
if (configSources.length === 0) throw new Error('Invalid config source.')
|
|
||||||
|
|
||||||
let filename = 'local'
|
let filename = 'local'
|
||||||
if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
|
if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
|
||||||
if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
|
if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
|
||||||
|
|
||||||
return join(dirname(configSources[0].name), filename + '.json')
|
return join(localConfigDir, filename + '.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalConfigDir () {
|
||||||
|
if (process.env.PEERTUBE_LOCAL_CONFIG) return process.env.PEERTUBE_LOCAL_CONFIG
|
||||||
|
|
||||||
|
const configSources = config.util.getConfigSources()
|
||||||
|
if (configSources.length === 0) throw new Error('Invalid config source.')
|
||||||
|
|
||||||
|
return dirname(configSources[0].name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
|
function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
|
||||||
|
@ -437,19 +452,19 @@ function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
|
||||||
|
|
||||||
export function reloadConfig () {
|
export function reloadConfig () {
|
||||||
|
|
||||||
function getConfigDirectory () {
|
function getConfigDirectories () {
|
||||||
if (process.env.NODE_CONFIG_DIR) {
|
if (process.env.NODE_CONFIG_DIR) {
|
||||||
return process.env.NODE_CONFIG_DIR
|
return process.env.NODE_CONFIG_DIR.split(":")
|
||||||
}
|
}
|
||||||
|
|
||||||
return join(root(), 'config')
|
return [ join(root(), 'config') ]
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge () {
|
function purge () {
|
||||||
const directory = getConfigDirectory()
|
const directories = getConfigDirectories()
|
||||||
|
|
||||||
for (const fileName in require.cache) {
|
for (const fileName in require.cache) {
|
||||||
if (fileName.includes(directory) === false) {
|
if (directories.some((dir) => fileName.includes(dir)) === false) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ class ServerConfigManager {
|
||||||
const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
|
const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
allowEdits: CONFIG.WEBADMIN.CONFIGURATION.EDITS.ALLOWED,
|
||||||
instance: {
|
instance: {
|
||||||
name: CONFIG.INSTANCE.NAME,
|
name: CONFIG.INSTANCE.NAME,
|
||||||
shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
|
shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { body } from 'express-validator'
|
import { body } from 'express-validator'
|
||||||
import { isIntOrNull } from '@server/helpers/custom-validators/misc'
|
import { isIntOrNull } from '@server/helpers/custom-validators/misc'
|
||||||
import { isEmailEnabled } from '@server/initializers/config'
|
import { CONFIG, isEmailEnabled } from '@server/initializers/config'
|
||||||
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
import { CustomConfig } from '../../../shared/models/server/custom-config.model'
|
||||||
import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
|
import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
|
||||||
import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
|
import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { isThemeRegistered } from '../../lib/plugins/theme-utils'
|
import { isThemeRegistered } from '../../lib/plugins/theme-utils'
|
||||||
import { areValidationErrors } from './shared'
|
import { areValidationErrors } from './shared'
|
||||||
|
import { HttpStatusCode } from '@shared/models/http/http-error-codes'
|
||||||
|
|
||||||
const customConfigUpdateValidator = [
|
const customConfigUpdateValidator = [
|
||||||
body('instance.name').exists().withMessage('Should have a valid instance name'),
|
body('instance.name').exists().withMessage('Should have a valid instance name'),
|
||||||
|
@ -104,10 +105,21 @@ const customConfigUpdateValidator = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function ensureConfigIsEditable (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
if (!CONFIG.WEBADMIN.CONFIGURATION.EDITS.ALLOWED) {
|
||||||
|
return res.fail({
|
||||||
|
status: HttpStatusCode.METHOD_NOT_ALLOWED_405,
|
||||||
|
message: 'Server configuration is static and cannot be edited'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
customConfigUpdateValidator
|
customConfigUpdateValidator,
|
||||||
|
ensureConfigIsEditable
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) {
|
function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) {
|
||||||
|
|
|
@ -201,6 +201,199 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.broadcastMessage.dismissable).to.be.true
|
expect(data.broadcastMessage.dismissable).to.be.true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newCustomConfig: CustomConfig = {
|
||||||
|
instance: {
|
||||||
|
name: 'PeerTube updated',
|
||||||
|
shortDescription: 'my short description',
|
||||||
|
description: 'my super description',
|
||||||
|
terms: 'my super terms',
|
||||||
|
codeOfConduct: 'my super coc',
|
||||||
|
|
||||||
|
creationReason: 'my super creation reason',
|
||||||
|
moderationInformation: 'my super moderation information',
|
||||||
|
administrator: 'Kuja',
|
||||||
|
maintenanceLifetime: 'forever',
|
||||||
|
businessModel: 'my super business model',
|
||||||
|
hardwareInformation: '2vCore 3GB RAM',
|
||||||
|
|
||||||
|
languages: [ 'en', 'es' ],
|
||||||
|
categories: [ 1, 2 ],
|
||||||
|
|
||||||
|
isNSFW: true,
|
||||||
|
defaultNSFWPolicy: 'blur' as 'blur',
|
||||||
|
|
||||||
|
defaultClientRoute: '/videos/recently-added',
|
||||||
|
|
||||||
|
customizations: {
|
||||||
|
javascript: 'alert("coucou")',
|
||||||
|
css: 'body { background-color: red; }'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
default: 'default'
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
twitter: {
|
||||||
|
username: '@Kuja',
|
||||||
|
whitelisted: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
previews: {
|
||||||
|
size: 2
|
||||||
|
},
|
||||||
|
captions: {
|
||||||
|
size: 3
|
||||||
|
},
|
||||||
|
torrents: {
|
||||||
|
size: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
enabled: false,
|
||||||
|
limit: 5,
|
||||||
|
requiresEmailVerification: false,
|
||||||
|
minimumAge: 10
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
email: 'superadmin1@example.com'
|
||||||
|
},
|
||||||
|
contactForm: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
videoQuota: 5242881,
|
||||||
|
videoQuotaDaily: 318742
|
||||||
|
},
|
||||||
|
transcoding: {
|
||||||
|
enabled: true,
|
||||||
|
allowAdditionalExtensions: true,
|
||||||
|
allowAudioFiles: true,
|
||||||
|
threads: 1,
|
||||||
|
concurrency: 3,
|
||||||
|
profile: 'vod_profile',
|
||||||
|
resolutions: {
|
||||||
|
'0p': false,
|
||||||
|
'240p': false,
|
||||||
|
'360p': true,
|
||||||
|
'480p': true,
|
||||||
|
'720p': false,
|
||||||
|
'1080p': false,
|
||||||
|
'1440p': false,
|
||||||
|
'2160p': false
|
||||||
|
},
|
||||||
|
webtorrent: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
hls: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
live: {
|
||||||
|
enabled: true,
|
||||||
|
allowReplay: true,
|
||||||
|
maxDuration: 5000,
|
||||||
|
maxInstanceLives: -1,
|
||||||
|
maxUserLives: 10,
|
||||||
|
transcoding: {
|
||||||
|
enabled: true,
|
||||||
|
threads: 4,
|
||||||
|
profile: 'live_profile',
|
||||||
|
resolutions: {
|
||||||
|
'240p': true,
|
||||||
|
'360p': true,
|
||||||
|
'480p': true,
|
||||||
|
'720p': true,
|
||||||
|
'1080p': true,
|
||||||
|
'1440p': true,
|
||||||
|
'2160p': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
import: {
|
||||||
|
videos: {
|
||||||
|
concurrency: 4,
|
||||||
|
http: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
torrent: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trending: {
|
||||||
|
videos: {
|
||||||
|
algorithms: {
|
||||||
|
enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ],
|
||||||
|
default: 'hot'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoBlacklist: {
|
||||||
|
videos: {
|
||||||
|
ofUsers: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
followers: {
|
||||||
|
instance: {
|
||||||
|
enabled: false,
|
||||||
|
manualApproval: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
followings: {
|
||||||
|
instance: {
|
||||||
|
autoFollowBack: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
autoFollowIndex: {
|
||||||
|
enabled: true,
|
||||||
|
indexUrl: 'https://updated.example.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
broadcastMessage: {
|
||||||
|
enabled: true,
|
||||||
|
level: 'error',
|
||||||
|
message: 'super bad message',
|
||||||
|
dismissable: true
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
remoteUri: {
|
||||||
|
anonymous: true,
|
||||||
|
users: true
|
||||||
|
},
|
||||||
|
searchIndex: {
|
||||||
|
enabled: true,
|
||||||
|
url: 'https://search.joinpeertube.org',
|
||||||
|
disableLocalSearch: true,
|
||||||
|
isDefaultSearch: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Test static config', function () {
|
||||||
|
let server: PeerTubeServer = null
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
server = await createSingleServer(1, { webadmin: { configuration: { edit: { allowed: false } } } })
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should tell the client that edits are not allowed', async function () {
|
||||||
|
const data = await server.config.getConfig()
|
||||||
|
|
||||||
|
expect(data.allowEdits).to.be.false
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should error when client tries to update', async function () {
|
||||||
|
await server.config.updateCustomConfig({ newCustomConfig, expectedStatus: 405 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Test config', function () {
|
describe('Test config', function () {
|
||||||
let server: PeerTubeServer = null
|
let server: PeerTubeServer = null
|
||||||
|
|
||||||
|
@ -252,177 +445,6 @@ describe('Test config', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should update the customized configuration', async function () {
|
it('Should update the customized configuration', async function () {
|
||||||
const newCustomConfig: CustomConfig = {
|
|
||||||
instance: {
|
|
||||||
name: 'PeerTube updated',
|
|
||||||
shortDescription: 'my short description',
|
|
||||||
description: 'my super description',
|
|
||||||
terms: 'my super terms',
|
|
||||||
codeOfConduct: 'my super coc',
|
|
||||||
|
|
||||||
creationReason: 'my super creation reason',
|
|
||||||
moderationInformation: 'my super moderation information',
|
|
||||||
administrator: 'Kuja',
|
|
||||||
maintenanceLifetime: 'forever',
|
|
||||||
businessModel: 'my super business model',
|
|
||||||
hardwareInformation: '2vCore 3GB RAM',
|
|
||||||
|
|
||||||
languages: [ 'en', 'es' ],
|
|
||||||
categories: [ 1, 2 ],
|
|
||||||
|
|
||||||
isNSFW: true,
|
|
||||||
defaultNSFWPolicy: 'blur' as 'blur',
|
|
||||||
|
|
||||||
defaultClientRoute: '/videos/recently-added',
|
|
||||||
|
|
||||||
customizations: {
|
|
||||||
javascript: 'alert("coucou")',
|
|
||||||
css: 'body { background-color: red; }'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
default: 'default'
|
|
||||||
},
|
|
||||||
services: {
|
|
||||||
twitter: {
|
|
||||||
username: '@Kuja',
|
|
||||||
whitelisted: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cache: {
|
|
||||||
previews: {
|
|
||||||
size: 2
|
|
||||||
},
|
|
||||||
captions: {
|
|
||||||
size: 3
|
|
||||||
},
|
|
||||||
torrents: {
|
|
||||||
size: 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
signup: {
|
|
||||||
enabled: false,
|
|
||||||
limit: 5,
|
|
||||||
requiresEmailVerification: false,
|
|
||||||
minimumAge: 10
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
email: 'superadmin1@example.com'
|
|
||||||
},
|
|
||||||
contactForm: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
videoQuota: 5242881,
|
|
||||||
videoQuotaDaily: 318742
|
|
||||||
},
|
|
||||||
transcoding: {
|
|
||||||
enabled: true,
|
|
||||||
allowAdditionalExtensions: true,
|
|
||||||
allowAudioFiles: true,
|
|
||||||
threads: 1,
|
|
||||||
concurrency: 3,
|
|
||||||
profile: 'vod_profile',
|
|
||||||
resolutions: {
|
|
||||||
'0p': false,
|
|
||||||
'240p': false,
|
|
||||||
'360p': true,
|
|
||||||
'480p': true,
|
|
||||||
'720p': false,
|
|
||||||
'1080p': false,
|
|
||||||
'1440p': false,
|
|
||||||
'2160p': false
|
|
||||||
},
|
|
||||||
webtorrent: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
hls: {
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
live: {
|
|
||||||
enabled: true,
|
|
||||||
allowReplay: true,
|
|
||||||
maxDuration: 5000,
|
|
||||||
maxInstanceLives: -1,
|
|
||||||
maxUserLives: 10,
|
|
||||||
transcoding: {
|
|
||||||
enabled: true,
|
|
||||||
threads: 4,
|
|
||||||
profile: 'live_profile',
|
|
||||||
resolutions: {
|
|
||||||
'240p': true,
|
|
||||||
'360p': true,
|
|
||||||
'480p': true,
|
|
||||||
'720p': true,
|
|
||||||
'1080p': true,
|
|
||||||
'1440p': true,
|
|
||||||
'2160p': true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
import: {
|
|
||||||
videos: {
|
|
||||||
concurrency: 4,
|
|
||||||
http: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
torrent: {
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trending: {
|
|
||||||
videos: {
|
|
||||||
algorithms: {
|
|
||||||
enabled: [ 'best', 'hot', 'most-viewed', 'most-liked' ],
|
|
||||||
default: 'hot'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autoBlacklist: {
|
|
||||||
videos: {
|
|
||||||
ofUsers: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
followers: {
|
|
||||||
instance: {
|
|
||||||
enabled: false,
|
|
||||||
manualApproval: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
followings: {
|
|
||||||
instance: {
|
|
||||||
autoFollowBack: {
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
autoFollowIndex: {
|
|
||||||
enabled: true,
|
|
||||||
indexUrl: 'https://updated.example.com'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
broadcastMessage: {
|
|
||||||
enabled: true,
|
|
||||||
level: 'error',
|
|
||||||
message: 'super bad message',
|
|
||||||
dismissable: true
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
remoteUri: {
|
|
||||||
anonymous: true,
|
|
||||||
users: true
|
|
||||||
},
|
|
||||||
searchIndex: {
|
|
||||||
enabled: true,
|
|
||||||
url: 'https://search.joinpeertube.org',
|
|
||||||
disableLocalSearch: true,
|
|
||||||
isDefaultSearch: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await server.config.updateCustomConfig({ newCustomConfig })
|
await server.config.updateCustomConfig({ newCustomConfig })
|
||||||
|
|
||||||
const data = await server.config.getCustomConfig()
|
const data = await server.config.getCustomConfig()
|
||||||
|
|
|
@ -30,6 +30,7 @@ export interface RegisteredIdAndPassAuthConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerConfig {
|
export interface ServerConfig {
|
||||||
|
allowEdits: boolean
|
||||||
serverVersion: string
|
serverVersion: string
|
||||||
serverCommit?: string
|
serverCommit?: string
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ RUN mkdir /data /config
|
||||||
RUN chown -R peertube:peertube /data /config
|
RUN chown -R peertube:peertube /data /config
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ENV NODE_CONFIG_DIR /config
|
ENV NODE_CONFIG_DIR /app/config:/app/support/docker/production/config:/config
|
||||||
|
ENV PEERTUBE_LOCAL_CONFIG /config
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
VOLUME /config
|
VOLUME /config
|
||||||
|
|
|
@ -68,6 +68,13 @@ object_storage:
|
||||||
prefix: "PEERTUBE_OBJECT_STORAGE_VIDEOS_PREFIX"
|
prefix: "PEERTUBE_OBJECT_STORAGE_VIDEOS_PREFIX"
|
||||||
base_url: "PEERTUBE_OBJECT_STORAGE_VIDEOS_BASE_URL"
|
base_url: "PEERTUBE_OBJECT_STORAGE_VIDEOS_BASE_URL"
|
||||||
|
|
||||||
|
webadmin:
|
||||||
|
configuration:
|
||||||
|
edit:
|
||||||
|
allowed:
|
||||||
|
__name: "PEERTUBE_ALLOW_WEBADMIN_CONFIG"
|
||||||
|
__format: "json"
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: "PEERTUBE_LOG_LEVEL"
|
level: "PEERTUBE_LOG_LEVEL"
|
||||||
log_ping_requests:
|
log_ping_requests:
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Populate config directory
|
|
||||||
if [ -z "$(ls -A /config)" ]; then
|
|
||||||
cp /app/support/docker/production/config/* /config
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Always copy default and custom env configuration file, in cases where new keys were added
|
find /config ! -user peertube -exec chown peertube:peertube {} \; || true
|
||||||
cp /app/config/default.yaml /config
|
|
||||||
cp /app/support/docker/production/config/custom-environment-variables.yaml /config
|
|
||||||
find /config ! -user peertube -exec chown peertube:peertube {} \;
|
|
||||||
|
|
||||||
# first arg is `-f` or `--some-option`
|
# first arg is `-f` or `--some-option`
|
||||||
# or first arg is `something.conf`
|
# or first arg is `something.conf`
|
||||||
|
|
Loading…
Reference in New Issue