mirror of https://github.com/Chocobozzz/PeerTube
Add ability to add custom css/javascript
parent
6221f311de
commit
00b5556c18
|
@ -128,5 +128,29 @@
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</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">
|
<input type="submit" value="Update configuration" [disabled]="!form.valid">
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -29,3 +29,9 @@ input[type=submit] {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
@include peertube-textarea(500px, 150px);
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
signupLimit: '',
|
signupLimit: '',
|
||||||
adminEmail: '',
|
adminEmail: '',
|
||||||
userVideoQuota: '',
|
userVideoQuota: '',
|
||||||
transcodingThreads: ''
|
transcodingThreads: '',
|
||||||
|
customizationJavascript: '',
|
||||||
|
customizationCSS: ''
|
||||||
}
|
}
|
||||||
validationMessages = {
|
validationMessages = {
|
||||||
instanceName: INSTANCE_NAME.MESSAGES,
|
instanceName: INSTANCE_NAME.MESSAGES,
|
||||||
|
@ -84,7 +86,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ],
|
adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ],
|
||||||
userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ],
|
userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ],
|
||||||
transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ],
|
transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ],
|
||||||
transcodingEnabled: [ ]
|
transcodingEnabled: [ ],
|
||||||
|
customizationJavascript: [ '' ],
|
||||||
|
customizationCSS: [ '' ]
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const resolution of this.resolutions) {
|
for (const resolution of this.resolutions) {
|
||||||
|
@ -125,7 +129,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
instance: {
|
instance: {
|
||||||
name: this.form.value['instanceName'],
|
name: this.form.value['instanceName'],
|
||||||
description: this.form.value['instanceDescription'],
|
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: {
|
cache: {
|
||||||
previews: {
|
previews: {
|
||||||
|
@ -183,7 +191,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
adminEmail: this.customConfig.admin.email,
|
adminEmail: this.customConfig.admin.email,
|
||||||
userVideoQuota: this.customConfig.user.videoQuota,
|
userVideoQuota: this.customConfig.user.videoQuota,
|
||||||
transcodingThreads: this.customConfig.transcoding.threads,
|
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) {
|
for (const resolution of this.resolutions) {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
<div *ngIf="customCSS" [innerHTML]="customCSS"></div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core'
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||||
import { GuardsCheckStart, Router } from '@angular/router'
|
import { GuardsCheckStart, Router } from '@angular/router'
|
||||||
import { AuthService, ServerService } from '@app/core'
|
import { AuthService, ServerService } from '@app/core'
|
||||||
import { isInSmallView } from '@app/shared/misc/utils'
|
import { isInSmallView } from '@app/shared/misc/utils'
|
||||||
|
@ -24,10 +25,13 @@ export class AppComponent implements OnInit {
|
||||||
|
|
||||||
isMenuDisplayed = true
|
isMenuDisplayed = true
|
||||||
|
|
||||||
|
customCSS: SafeHtml
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private serverService: ServerService
|
private serverService: ServerService,
|
||||||
|
private domSanitizer: DomSanitizer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get serverVersion () {
|
get serverVersion () {
|
||||||
|
@ -66,6 +70,26 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 () {
|
toggleMenu () {
|
||||||
|
|
|
@ -12,6 +12,7 @@ export class ServerService {
|
||||||
private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
|
private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
|
||||||
private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
|
private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
|
||||||
|
|
||||||
|
configLoaded = new ReplaySubject<boolean>(1)
|
||||||
videoPrivaciesLoaded = new ReplaySubject<boolean>(1)
|
videoPrivaciesLoaded = new ReplaySubject<boolean>(1)
|
||||||
videoCategoriesLoaded = new ReplaySubject<boolean>(1)
|
videoCategoriesLoaded = new ReplaySubject<boolean>(1)
|
||||||
videoLicencesLoaded = new ReplaySubject<boolean>(1)
|
videoLicencesLoaded = new ReplaySubject<boolean>(1)
|
||||||
|
@ -19,7 +20,11 @@ export class ServerService {
|
||||||
|
|
||||||
private config: ServerConfig = {
|
private config: ServerConfig = {
|
||||||
instance: {
|
instance: {
|
||||||
name: 'PeerTube'
|
name: 'PeerTube',
|
||||||
|
customizations: {
|
||||||
|
javascript: '',
|
||||||
|
css: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
serverVersion: 'Unknown',
|
serverVersion: 'Unknown',
|
||||||
signup: {
|
signup: {
|
||||||
|
@ -56,7 +61,11 @@ export class ServerService {
|
||||||
loadConfig () {
|
loadConfig () {
|
||||||
this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
|
this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
|
||||||
.do(this.saveConfigLocally)
|
.do(this.saveConfigLocally)
|
||||||
.subscribe(data => this.config = data)
|
.subscribe(data => {
|
||||||
|
this.config = data
|
||||||
|
|
||||||
|
this.configLoaded.next(true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadVideoCategories () {
|
loadVideoCategories () {
|
||||||
|
|
|
@ -74,3 +74,6 @@ instance:
|
||||||
name: 'PeerTube'
|
name: 'PeerTube'
|
||||||
description: 'Welcome to this PeerTube instance!' # Support markdown
|
description: 'Welcome to this PeerTube instance!' # Support markdown
|
||||||
terms: 'No terms for now.' # 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
|
||||||
|
|
|
@ -74,3 +74,6 @@ instance:
|
||||||
name: 'PeerTube'
|
name: 'PeerTube'
|
||||||
description: '' # Support markdown
|
description: '' # Support markdown
|
||||||
terms: '' # 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
|
||||||
|
|
|
@ -43,7 +43,11 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
|
||||||
|
|
||||||
const json: ServerConfig = {
|
const json: ServerConfig = {
|
||||||
instance: {
|
instance: {
|
||||||
name: CONFIG.INSTANCE.NAME
|
name: CONFIG.INSTANCE.NAME,
|
||||||
|
customizations: {
|
||||||
|
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
|
||||||
|
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
|
||||||
|
}
|
||||||
},
|
},
|
||||||
serverVersion: packageJSON.version,
|
serverVersion: packageJSON.version,
|
||||||
signup: {
|
signup: {
|
||||||
|
@ -132,7 +136,11 @@ function customConfig (): CustomConfig {
|
||||||
instance: {
|
instance: {
|
||||||
name: CONFIG.INSTANCE.NAME,
|
name: CONFIG.INSTANCE.NAME,
|
||||||
description: CONFIG.INSTANCE.DESCRIPTION,
|
description: CONFIG.INSTANCE.DESCRIPTION,
|
||||||
terms: CONFIG.INSTANCE.TERMS
|
terms: CONFIG.INSTANCE.TERMS,
|
||||||
|
customizations: {
|
||||||
|
css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
|
||||||
|
javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
previews: {
|
previews: {
|
||||||
|
|
|
@ -158,7 +158,11 @@ const CONFIG = {
|
||||||
INSTANCE: {
|
INSTANCE: {
|
||||||
get NAME () { return config.get<string>('instance.name') },
|
get NAME () { return config.get<string>('instance.name') },
|
||||||
get DESCRIPTION () { return config.get<string>('instance.description') },
|
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') }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,11 @@ describe('Test config API validators', function () {
|
||||||
instance: {
|
instance: {
|
||||||
name: 'PeerTube updated',
|
name: 'PeerTube updated',
|
||||||
description: 'my super description',
|
description: 'my super description',
|
||||||
terms: 'my super terms'
|
terms: 'my super terms',
|
||||||
|
customizations: {
|
||||||
|
javascript: 'alert("coucou")',
|
||||||
|
css: 'body { background-color: red; }'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
previews: {
|
previews: {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import { About } from '../../../../shared/models/config/about.model'
|
import { About } from '../../../../shared/models/config/about.model'
|
||||||
|
import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
|
||||||
import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
|
import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
|
@ -48,11 +49,13 @@ describe('Test config', function () {
|
||||||
|
|
||||||
it('Should get the customized configuration', async function () {
|
it('Should get the customized configuration', async function () {
|
||||||
const res = await getCustomConfig(server.url, server.accessToken)
|
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.name).to.equal('PeerTube')
|
||||||
expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
|
expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
|
||||||
expect(data.instance.terms).to.equal('No terms for now.')
|
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.cache.previews.size).to.equal(1)
|
||||||
expect(data.signup.enabled).to.be.true
|
expect(data.signup.enabled).to.be.true
|
||||||
expect(data.signup.limit).to.equal(4)
|
expect(data.signup.limit).to.equal(4)
|
||||||
|
@ -72,7 +75,11 @@ describe('Test config', function () {
|
||||||
instance: {
|
instance: {
|
||||||
name: 'PeerTube updated',
|
name: 'PeerTube updated',
|
||||||
description: 'my super description',
|
description: 'my super description',
|
||||||
terms: 'my super terms'
|
terms: 'my super terms',
|
||||||
|
customizations: {
|
||||||
|
javascript: 'alert("coucou")',
|
||||||
|
css: 'body { background-color: red; }'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
previews: {
|
previews: {
|
||||||
|
@ -109,6 +116,8 @@ describe('Test config', function () {
|
||||||
expect(data.instance.name).to.equal('PeerTube updated')
|
expect(data.instance.name).to.equal('PeerTube updated')
|
||||||
expect(data.instance.description).to.equal('my super description')
|
expect(data.instance.description).to.equal('my super description')
|
||||||
expect(data.instance.terms).to.equal('my super terms')
|
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.cache.previews.size).to.equal(2)
|
||||||
expect(data.signup.enabled).to.be.false
|
expect(data.signup.enabled).to.be.false
|
||||||
expect(data.signup.limit).to.equal(5)
|
expect(data.signup.limit).to.equal(5)
|
||||||
|
@ -136,6 +145,8 @@ describe('Test config', function () {
|
||||||
expect(data.instance.name).to.equal('PeerTube updated')
|
expect(data.instance.name).to.equal('PeerTube updated')
|
||||||
expect(data.instance.description).to.equal('my super description')
|
expect(data.instance.description).to.equal('my super description')
|
||||||
expect(data.instance.terms).to.equal('my super terms')
|
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.cache.previews.size).to.equal(2)
|
||||||
expect(data.signup.enabled).to.be.false
|
expect(data.signup.enabled).to.be.false
|
||||||
expect(data.signup.limit).to.equal(5)
|
expect(data.signup.limit).to.equal(5)
|
||||||
|
@ -167,6 +178,11 @@ describe('Test config', function () {
|
||||||
const res = await getCustomConfig(server.url, server.accessToken)
|
const res = await getCustomConfig(server.url, server.accessToken)
|
||||||
const data = res.body
|
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.cache.previews.size).to.equal(1)
|
||||||
expect(data.signup.enabled).to.be.true
|
expect(data.signup.enabled).to.be.true
|
||||||
expect(data.signup.limit).to.equal(4)
|
expect(data.signup.limit).to.equal(4)
|
||||||
|
|
|
@ -3,6 +3,10 @@ export interface CustomConfig {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
terms: string
|
terms: string
|
||||||
|
customizations: {
|
||||||
|
javascript?: string
|
||||||
|
css?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cache: {
|
cache: {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export interface Customization {
|
||||||
|
instance: {
|
||||||
|
customization: {
|
||||||
|
javascript: string
|
||||||
|
css: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,11 @@ export interface ServerConfig {
|
||||||
serverVersion: string
|
serverVersion: string
|
||||||
|
|
||||||
instance: {
|
instance: {
|
||||||
name: string
|
name: string;
|
||||||
|
customizations: {
|
||||||
|
javascript: string
|
||||||
|
css: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signup: {
|
signup: {
|
||||||
|
|
Loading…
Reference in New Issue