</div>
</ng-template>
+ <div class="inner-form-title">Customizations</div>
+
+ <div class="form-group">
+ <label for="customizationJavascript">JavaScript</label>
+ <textarea
+ id="customizationJavascript" formControlName="customizationJavascript"
+ [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }"
+ ></textarea>
+ <div *ngIf="formErrors.customizationJavascript" class="form-error">
+ {{ formErrors.customizationJavascript }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="customizationCSS">CSS</label>
+ <textarea
+ id="customizationCSS" formControlName="customizationCSS"
+ [ngClass]="{ 'input-error': formErrors['customizationCSS'] }"
+ ></textarea>
+ <div *ngIf="formErrors.customizationCSS" class="form-error">
+ {{ formErrors.customizationCSS }}
+ </div>
+ </div>
+
<input type="submit" value="Update configuration" [disabled]="!form.valid">
</form>
margin-top: 30px;
margin-bottom: 10px;
}
+
+textarea {
+ @include peertube-textarea(500px, 150px);
+
+ display: block;
+}
signupLimit: '',
adminEmail: '',
userVideoQuota: '',
- transcodingThreads: ''
+ transcodingThreads: '',
+ customizationJavascript: '',
+ customizationCSS: ''
}
validationMessages = {
instanceName: INSTANCE_NAME.MESSAGES,
adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ],
userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ],
transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ],
- transcodingEnabled: [ ]
+ transcodingEnabled: [ ],
+ customizationJavascript: [ '' ],
+ customizationCSS: [ '' ]
}
for (const resolution of this.resolutions) {
instance: {
name: this.form.value['instanceName'],
description: this.form.value['instanceDescription'],
- terms: this.form.value['instanceTerms']
+ terms: this.form.value['instanceTerms'],
+ customizations: {
+ javascript: this.form.value['customizationJavascript'],
+ css: this.form.value['customizationCSS']
+ }
},
cache: {
previews: {
adminEmail: this.customConfig.admin.email,
userVideoQuota: this.customConfig.user.videoQuota,
transcodingThreads: this.customConfig.transcoding.threads,
- transcodingEnabled: this.customConfig.transcoding.enabled
+ transcodingEnabled: this.customConfig.transcoding.enabled,
+ customizationJavascript: this.customConfig.instance.customizations.javascript,
+ customizationCSS: this.customConfig.instance.customizations.css
}
for (const resolution of this.resolutions) {
+<div *ngIf="customCSS" [innerHTML]="customCSS"></div>
+
<div>
<div class="header">
import { Component, OnInit } from '@angular/core'
+import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
import { GuardsCheckStart, Router } from '@angular/router'
import { AuthService, ServerService } from '@app/core'
import { isInSmallView } from '@app/shared/misc/utils'
isMenuDisplayed = true
+ customCSS: SafeHtml
+
constructor (
private router: Router,
private authService: AuthService,
- private serverService: ServerService
+ private serverService: ServerService,
+ private domSanitizer: DomSanitizer
) {}
get serverVersion () {
}
}
)
+
+ this.serverService.configLoaded
+ .subscribe(() => {
+ const config = this.serverService.getConfig()
+
+ // We test customCSS in case or the admin removed the css
+ if (this.customCSS || config.instance.customizations.css) {
+ const styleTag = '<style>' + config.instance.customizations.css + '</style>'
+ this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
+ }
+
+ if (config.instance.customizations.javascript) {
+ try {
+ // tslint:disable:no-eval
+ eval(config.instance.customizations.javascript)
+ } catch (err) {
+ console.error('Cannot eval custom JavaScript.', err)
+ }
+ }
+ })
}
toggleMenu () {
private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
+ configLoaded = new ReplaySubject<boolean>(1)
videoPrivaciesLoaded = new ReplaySubject<boolean>(1)
videoCategoriesLoaded = new ReplaySubject<boolean>(1)
videoLicencesLoaded = new ReplaySubject<boolean>(1)
private config: ServerConfig = {
instance: {
- name: 'PeerTube'
+ name: 'PeerTube',
+ customizations: {
+ javascript: '',
+ css: ''
+ }
},
serverVersion: 'Unknown',
signup: {
loadConfig () {
this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
.do(this.saveConfigLocally)
- .subscribe(data => this.config = data)
+ .subscribe(data => {
+ this.config = data
+
+ this.configLoaded.next(true)
+ })
}
loadVideoCategories () {
name: 'PeerTube'
description: 'Welcome to this PeerTube instance!' # Support markdown
terms: 'No terms for now.' # Support markdown
+ customizations:
+ javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
+ css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
name: 'PeerTube'
description: '' # Support markdown
terms: '' # Support markdown
+ customizations:
+ javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
+ css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
const json: ServerConfig = {
instance: {
- name: CONFIG.INSTANCE.NAME
+ name: CONFIG.INSTANCE.NAME,
+ customizations: {
+ javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
+ css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
+ }
},
serverVersion: packageJSON.version,
signup: {
instance: {
name: CONFIG.INSTANCE.NAME,
description: CONFIG.INSTANCE.DESCRIPTION,
- terms: CONFIG.INSTANCE.TERMS
+ terms: CONFIG.INSTANCE.TERMS,
+ customizations: {
+ css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
+ javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
+ }
},
cache: {
previews: {
INSTANCE: {
get NAME () { return config.get<string>('instance.name') },
get DESCRIPTION () { return config.get<string>('instance.description') },
- get TERMS () { return config.get<string>('instance.terms') }
+ get TERMS () { return config.get<string>('instance.terms') },
+ CUSTOMIZATIONS: {
+ get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
+ get CSS () { return config.get<string>('instance.customizations.css') }
+ }
}
}
instance: {
name: 'PeerTube updated',
description: 'my super description',
- terms: 'my super terms'
+ terms: 'my super terms',
+ customizations: {
+ javascript: 'alert("coucou")',
+ css: 'body { background-color: red; }'
+ }
},
cache: {
previews: {
import 'mocha'
import * as chai from 'chai'
import { About } from '../../../../shared/models/config/about.model'
+import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
const expect = chai.expect
it('Should get the customized configuration', async function () {
const res = await getCustomConfig(server.url, server.accessToken)
- const data = res.body
+ const data = res.body as CustomConfig
expect(data.instance.name).to.equal('PeerTube')
expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
expect(data.instance.terms).to.equal('No terms for now.')
+ expect(data.instance.customizations.css).to.be.empty
+ expect(data.instance.customizations.javascript).to.be.empty
expect(data.cache.previews.size).to.equal(1)
expect(data.signup.enabled).to.be.true
expect(data.signup.limit).to.equal(4)
instance: {
name: 'PeerTube updated',
description: 'my super description',
- terms: 'my super terms'
+ terms: 'my super terms',
+ customizations: {
+ javascript: 'alert("coucou")',
+ css: 'body { background-color: red; }'
+ }
},
cache: {
previews: {
expect(data.instance.name).to.equal('PeerTube updated')
expect(data.instance.description).to.equal('my super description')
expect(data.instance.terms).to.equal('my super terms')
+ expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
+ expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
expect(data.cache.previews.size).to.equal(2)
expect(data.signup.enabled).to.be.false
expect(data.signup.limit).to.equal(5)
expect(data.instance.name).to.equal('PeerTube updated')
expect(data.instance.description).to.equal('my super description')
expect(data.instance.terms).to.equal('my super terms')
+ expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
+ expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
expect(data.cache.previews.size).to.equal(2)
expect(data.signup.enabled).to.be.false
expect(data.signup.limit).to.equal(5)
const res = await getCustomConfig(server.url, server.accessToken)
const data = res.body
+ expect(data.instance.name).to.equal('PeerTube')
+ expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
+ expect(data.instance.terms).to.equal('No terms for now.')
+ expect(data.instance.customizations.css).to.be.empty
+ expect(data.instance.customizations.javascript).to.be.empty
expect(data.cache.previews.size).to.equal(1)
expect(data.signup.enabled).to.be.true
expect(data.signup.limit).to.equal(4)
name: string
description: string
terms: string
+ customizations: {
+ javascript?: string
+ css?: string
+ }
}
cache: {
--- /dev/null
+export interface Customization {
+ instance: {
+ customization: {
+ javascript: string
+ css: string
+ }
+ }
+}
serverVersion: string
instance: {
- name: string
+ name: string;
+ customizations: {
+ javascript: string
+ css: string
+ }
}
signup: {