Add ability to add custom css/javascript
This commit is contained in:
parent
6221f311de
commit
00b5556c18
|
@ -128,5 +128,29 @@
|
|||
</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>
|
||||
|
|
|
@ -29,3 +29,9 @@ input[type=submit] {
|
|||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@include peertube-textarea(500px, 150px);
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -49,7 +49,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
signupLimit: '',
|
||||
adminEmail: '',
|
||||
userVideoQuota: '',
|
||||
transcodingThreads: ''
|
||||
transcodingThreads: '',
|
||||
customizationJavascript: '',
|
||||
customizationCSS: ''
|
||||
}
|
||||
validationMessages = {
|
||||
instanceName: INSTANCE_NAME.MESSAGES,
|
||||
|
@ -84,7 +86,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ],
|
||||
userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ],
|
||||
transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ],
|
||||
transcodingEnabled: [ ]
|
||||
transcodingEnabled: [ ],
|
||||
customizationJavascript: [ '' ],
|
||||
customizationCSS: [ '' ]
|
||||
}
|
||||
|
||||
for (const resolution of this.resolutions) {
|
||||
|
@ -125,7 +129,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
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: {
|
||||
|
@ -183,7 +191,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
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) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<div *ngIf="customCSS" [innerHTML]="customCSS"></div>
|
||||
|
||||
<div>
|
||||
<div class="header">
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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'
|
||||
|
@ -24,10 +25,13 @@ export class AppComponent implements OnInit {
|
|||
|
||||
isMenuDisplayed = true
|
||||
|
||||
customCSS: SafeHtml
|
||||
|
||||
constructor (
|
||||
private router: Router,
|
||||
private authService: AuthService,
|
||||
private serverService: ServerService
|
||||
private serverService: ServerService,
|
||||
private domSanitizer: DomSanitizer
|
||||
) {}
|
||||
|
||||
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 () {
|
||||
|
|
|
@ -12,6 +12,7 @@ export class ServerService {
|
|||
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)
|
||||
|
@ -19,7 +20,11 @@ export class ServerService {
|
|||
|
||||
private config: ServerConfig = {
|
||||
instance: {
|
||||
name: 'PeerTube'
|
||||
name: 'PeerTube',
|
||||
customizations: {
|
||||
javascript: '',
|
||||
css: ''
|
||||
}
|
||||
},
|
||||
serverVersion: 'Unknown',
|
||||
signup: {
|
||||
|
@ -56,7 +61,11 @@ export class ServerService {
|
|||
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 () {
|
||||
|
|
|
@ -74,3 +74,6 @@ instance:
|
|||
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
|
||||
|
|
|
@ -74,3 +74,6 @@ instance:
|
|||
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
|
||||
|
|
|
@ -43,7 +43,11 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
|
|||
|
||||
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: {
|
||||
|
@ -132,7 +136,11 @@ function customConfig (): CustomConfig {
|
|||
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: {
|
||||
|
|
|
@ -158,7 +158,11 @@ const CONFIG = {
|
|||
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') }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,11 @@ describe('Test config API validators', function () {
|
|||
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: {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
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
|
||||
|
||||
|
@ -48,11 +49,13 @@ describe('Test config', function () {
|
|||
|
||||
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)
|
||||
|
@ -72,7 +75,11 @@ describe('Test config', function () {
|
|||
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: {
|
||||
|
@ -109,6 +116,8 @@ describe('Test config', function () {
|
|||
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)
|
||||
|
@ -136,6 +145,8 @@ describe('Test config', function () {
|
|||
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)
|
||||
|
@ -167,6 +178,11 @@ describe('Test config', function () {
|
|||
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)
|
||||
|
|
|
@ -3,6 +3,10 @@ export interface CustomConfig {
|
|||
name: string
|
||||
description: string
|
||||
terms: string
|
||||
customizations: {
|
||||
javascript?: string
|
||||
css?: string
|
||||
}
|
||||
}
|
||||
|
||||
cache: {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export interface Customization {
|
||||
instance: {
|
||||
customization: {
|
||||
javascript: string
|
||||
css: string
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,11 @@ export interface ServerConfig {
|
|||
serverVersion: string
|
||||
|
||||
instance: {
|
||||
name: string
|
||||
name: string;
|
||||
customizations: {
|
||||
javascript: string
|
||||
css: string
|
||||
}
|
||||
}
|
||||
|
||||
signup: {
|
||||
|
|
Loading…
Reference in New Issue