aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-02-22 10:22:53 +0100
committerChocobozzz <me@florianbigard.com>2018-02-22 10:22:53 +0100
commit00b5556c182fa70dfca17c517488b8afae6257c9 (patch)
tree853ad8d85572b5b37b1956fea96a51fed211453f
parent6221f311de0eb8f2a9e7e4a77b8cb0ecbde6dfcd (diff)
downloadPeerTube-00b5556c182fa70dfca17c517488b8afae6257c9.tar.gz
PeerTube-00b5556c182fa70dfca17c517488b8afae6257c9.tar.zst
PeerTube-00b5556c182fa70dfca17c517488b8afae6257c9.zip
Add ability to add custom css/javascript
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html24
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss6
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts18
-rw-r--r--client/src/app/app.component.html2
-rw-r--r--client/src/app/app.component.ts26
-rw-r--r--client/src/app/core/server/server.service.ts13
-rw-r--r--config/default.yaml3
-rw-r--r--config/production.yaml.example3
-rw-r--r--server/controllers/api/config.ts12
-rw-r--r--server/initializers/constants.ts6
-rw-r--r--server/tests/api/check-params/config.ts6
-rw-r--r--server/tests/api/server/config.ts20
-rw-r--r--shared/models/config/custom-config.model.ts4
-rw-r--r--shared/models/config/customization.model.ts8
-rw-r--r--shared/models/config/server-config.model.ts6
15 files changed, 143 insertions, 14 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index 0fe2aa203..8dca9bc04 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -128,5 +128,29 @@
128 </div> 128 </div>
129 </ng-template> 129 </ng-template>
130 130
131 <div class="inner-form-title">Customizations</div>
132
133 <div class="form-group">
134 <label for="customizationJavascript">JavaScript</label>
135 <textarea
136 id="customizationJavascript" formControlName="customizationJavascript"
137 [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }"
138 ></textarea>
139 <div *ngIf="formErrors.customizationJavascript" class="form-error">
140 {{ formErrors.customizationJavascript }}
141 </div>
142 </div>
143
144 <div class="form-group">
145 <label for="customizationCSS">CSS</label>
146 <textarea
147 id="customizationCSS" formControlName="customizationCSS"
148 [ngClass]="{ 'input-error': formErrors['customizationCSS'] }"
149 ></textarea>
150 <div *ngIf="formErrors.customizationCSS" class="form-error">
151 {{ formErrors.customizationCSS }}
152 </div>
153 </div>
154
131 <input type="submit" value="Update configuration" [disabled]="!form.valid"> 155 <input type="submit" value="Update configuration" [disabled]="!form.valid">
132</form> 156</form>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
index 0195f44eb..e72f30c69 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
@@ -29,3 +29,9 @@ input[type=submit] {
29 margin-top: 30px; 29 margin-top: 30px;
30 margin-bottom: 10px; 30 margin-bottom: 10px;
31} 31}
32
33textarea {
34 @include peertube-textarea(500px, 150px);
35
36 display: block;
37}
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index cd8c926f7..027268536 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -49,7 +49,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
49 signupLimit: '', 49 signupLimit: '',
50 adminEmail: '', 50 adminEmail: '',
51 userVideoQuota: '', 51 userVideoQuota: '',
52 transcodingThreads: '' 52 transcodingThreads: '',
53 customizationJavascript: '',
54 customizationCSS: ''
53 } 55 }
54 validationMessages = { 56 validationMessages = {
55 instanceName: INSTANCE_NAME.MESSAGES, 57 instanceName: INSTANCE_NAME.MESSAGES,
@@ -84,7 +86,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
84 adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ], 86 adminEmail: [ '', ADMIN_EMAIL.VALIDATORS ],
85 userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ], 87 userVideoQuota: [ '', USER_VIDEO_QUOTA.VALIDATORS ],
86 transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ], 88 transcodingThreads: [ '', TRANSCODING_THREADS.VALIDATORS ],
87 transcodingEnabled: [ ] 89 transcodingEnabled: [ ],
90 customizationJavascript: [ '' ],
91 customizationCSS: [ '' ]
88 } 92 }
89 93
90 for (const resolution of this.resolutions) { 94 for (const resolution of this.resolutions) {
@@ -125,7 +129,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
125 instance: { 129 instance: {
126 name: this.form.value['instanceName'], 130 name: this.form.value['instanceName'],
127 description: this.form.value['instanceDescription'], 131 description: this.form.value['instanceDescription'],
128 terms: this.form.value['instanceTerms'] 132 terms: this.form.value['instanceTerms'],
133 customizations: {
134 javascript: this.form.value['customizationJavascript'],
135 css: this.form.value['customizationCSS']
136 }
129 }, 137 },
130 cache: { 138 cache: {
131 previews: { 139 previews: {
@@ -183,7 +191,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
183 adminEmail: this.customConfig.admin.email, 191 adminEmail: this.customConfig.admin.email,
184 userVideoQuota: this.customConfig.user.videoQuota, 192 userVideoQuota: this.customConfig.user.videoQuota,
185 transcodingThreads: this.customConfig.transcoding.threads, 193 transcodingThreads: this.customConfig.transcoding.threads,
186 transcodingEnabled: this.customConfig.transcoding.enabled 194 transcodingEnabled: this.customConfig.transcoding.enabled,
195 customizationJavascript: this.customConfig.instance.customizations.javascript,
196 customizationCSS: this.customConfig.instance.customizations.css
187 } 197 }
188 198
189 for (const resolution of this.resolutions) { 199 for (const resolution of this.resolutions) {
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index dafc45266..0e1882ad3 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -1,3 +1,5 @@
1<div *ngIf="customCSS" [innerHTML]="customCSS"></div>
2
1<div> 3<div>
2 <div class="header"> 4 <div class="header">
3 5
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 3af33ba2b..25936146c 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,4 +1,5 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
2import { GuardsCheckStart, Router } from '@angular/router' 3import { GuardsCheckStart, Router } from '@angular/router'
3import { AuthService, ServerService } from '@app/core' 4import { AuthService, ServerService } from '@app/core'
4import { isInSmallView } from '@app/shared/misc/utils' 5import { isInSmallView } from '@app/shared/misc/utils'
@@ -24,10 +25,13 @@ export class AppComponent implements OnInit {
24 25
25 isMenuDisplayed = true 26 isMenuDisplayed = true
26 27
28 customCSS: SafeHtml
29
27 constructor ( 30 constructor (
28 private router: Router, 31 private router: Router,
29 private authService: AuthService, 32 private authService: AuthService,
30 private serverService: ServerService 33 private serverService: ServerService,
34 private domSanitizer: DomSanitizer
31 ) {} 35 ) {}
32 36
33 get serverVersion () { 37 get serverVersion () {
@@ -66,6 +70,26 @@ export class AppComponent implements OnInit {
66 } 70 }
67 } 71 }
68 ) 72 )
73
74 this.serverService.configLoaded
75 .subscribe(() => {
76 const config = this.serverService.getConfig()
77
78 // We test customCSS in case or the admin removed the css
79 if (this.customCSS || config.instance.customizations.css) {
80 const styleTag = '<style>' + config.instance.customizations.css + '</style>'
81 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
82 }
83
84 if (config.instance.customizations.javascript) {
85 try {
86 // tslint:disable:no-eval
87 eval(config.instance.customizations.javascript)
88 } catch (err) {
89 console.error('Cannot eval custom JavaScript.', err)
90 }
91 }
92 })
69 } 93 }
70 94
71 toggleMenu () { 95 toggleMenu () {
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index f54e63efd..3c94f09c6 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -12,6 +12,7 @@ export class ServerService {
12 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 12 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
13 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' 13 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
14 14
15 configLoaded = new ReplaySubject<boolean>(1)
15 videoPrivaciesLoaded = new ReplaySubject<boolean>(1) 16 videoPrivaciesLoaded = new ReplaySubject<boolean>(1)
16 videoCategoriesLoaded = new ReplaySubject<boolean>(1) 17 videoCategoriesLoaded = new ReplaySubject<boolean>(1)
17 videoLicencesLoaded = new ReplaySubject<boolean>(1) 18 videoLicencesLoaded = new ReplaySubject<boolean>(1)
@@ -19,7 +20,11 @@ export class ServerService {
19 20
20 private config: ServerConfig = { 21 private config: ServerConfig = {
21 instance: { 22 instance: {
22 name: 'PeerTube' 23 name: 'PeerTube',
24 customizations: {
25 javascript: '',
26 css: ''
27 }
23 }, 28 },
24 serverVersion: 'Unknown', 29 serverVersion: 'Unknown',
25 signup: { 30 signup: {
@@ -56,7 +61,11 @@ export class ServerService {
56 loadConfig () { 61 loadConfig () {
57 this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) 62 this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL)
58 .do(this.saveConfigLocally) 63 .do(this.saveConfigLocally)
59 .subscribe(data => this.config = data) 64 .subscribe(data => {
65 this.config = data
66
67 this.configLoaded.next(true)
68 })
60 } 69 }
61 70
62 loadVideoCategories () { 71 loadVideoCategories () {
diff --git a/config/default.yaml b/config/default.yaml
index 9c1136621..0d0e788dd 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -74,3 +74,6 @@ instance:
74 name: 'PeerTube' 74 name: 'PeerTube'
75 description: 'Welcome to this PeerTube instance!' # Support markdown 75 description: 'Welcome to this PeerTube instance!' # Support markdown
76 terms: 'No terms for now.' # Support markdown 76 terms: 'No terms for now.' # Support markdown
77 customizations:
78 javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
79 css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 9d233a847..0b15c7b96 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -74,3 +74,6 @@ instance:
74 name: 'PeerTube' 74 name: 'PeerTube'
75 description: '' # Support markdown 75 description: '' # Support markdown
76 terms: '' # Support markdown 76 terms: '' # Support markdown
77 customizations:
78 javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime
79 css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 532afb8c0..8cfaf3e29 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -43,7 +43,11 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
43 43
44 const json: ServerConfig = { 44 const json: ServerConfig = {
45 instance: { 45 instance: {
46 name: CONFIG.INSTANCE.NAME 46 name: CONFIG.INSTANCE.NAME,
47 customizations: {
48 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
49 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
50 }
47 }, 51 },
48 serverVersion: packageJSON.version, 52 serverVersion: packageJSON.version,
49 signup: { 53 signup: {
@@ -132,7 +136,11 @@ function customConfig (): CustomConfig {
132 instance: { 136 instance: {
133 name: CONFIG.INSTANCE.NAME, 137 name: CONFIG.INSTANCE.NAME,
134 description: CONFIG.INSTANCE.DESCRIPTION, 138 description: CONFIG.INSTANCE.DESCRIPTION,
135 terms: CONFIG.INSTANCE.TERMS 139 terms: CONFIG.INSTANCE.TERMS,
140 customizations: {
141 css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS,
142 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT
143 }
136 }, 144 },
137 cache: { 145 cache: {
138 previews: { 146 previews: {
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index ac001bbc7..328a3e70a 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -158,7 +158,11 @@ const CONFIG = {
158 INSTANCE: { 158 INSTANCE: {
159 get NAME () { return config.get<string>('instance.name') }, 159 get NAME () { return config.get<string>('instance.name') },
160 get DESCRIPTION () { return config.get<string>('instance.description') }, 160 get DESCRIPTION () { return config.get<string>('instance.description') },
161 get TERMS () { return config.get<string>('instance.terms') } 161 get TERMS () { return config.get<string>('instance.terms') },
162 CUSTOMIZATIONS: {
163 get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
164 get CSS () { return config.get<string>('instance.customizations.css') }
165 }
162 } 166 }
163} 167}
164 168
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index efc1e4e09..c1c0a3f59 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -17,7 +17,11 @@ describe('Test config API validators', function () {
17 instance: { 17 instance: {
18 name: 'PeerTube updated', 18 name: 'PeerTube updated',
19 description: 'my super description', 19 description: 'my super description',
20 terms: 'my super terms' 20 terms: 'my super terms',
21 customizations: {
22 javascript: 'alert("coucou")',
23 css: 'body { background-color: red; }'
24 }
21 }, 25 },
22 cache: { 26 cache: {
23 previews: { 27 previews: {
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index f1f7afef9..048135a34 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -3,6 +3,7 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { About } from '../../../../shared/models/config/about.model' 5import { About } from '../../../../shared/models/config/about.model'
6import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
6import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils' 7import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
7const expect = chai.expect 8const expect = chai.expect
8 9
@@ -48,11 +49,13 @@ describe('Test config', function () {
48 49
49 it('Should get the customized configuration', async function () { 50 it('Should get the customized configuration', async function () {
50 const res = await getCustomConfig(server.url, server.accessToken) 51 const res = await getCustomConfig(server.url, server.accessToken)
51 const data = res.body 52 const data = res.body as CustomConfig
52 53
53 expect(data.instance.name).to.equal('PeerTube') 54 expect(data.instance.name).to.equal('PeerTube')
54 expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') 55 expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
55 expect(data.instance.terms).to.equal('No terms for now.') 56 expect(data.instance.terms).to.equal('No terms for now.')
57 expect(data.instance.customizations.css).to.be.empty
58 expect(data.instance.customizations.javascript).to.be.empty
56 expect(data.cache.previews.size).to.equal(1) 59 expect(data.cache.previews.size).to.equal(1)
57 expect(data.signup.enabled).to.be.true 60 expect(data.signup.enabled).to.be.true
58 expect(data.signup.limit).to.equal(4) 61 expect(data.signup.limit).to.equal(4)
@@ -72,7 +75,11 @@ describe('Test config', function () {
72 instance: { 75 instance: {
73 name: 'PeerTube updated', 76 name: 'PeerTube updated',
74 description: 'my super description', 77 description: 'my super description',
75 terms: 'my super terms' 78 terms: 'my super terms',
79 customizations: {
80 javascript: 'alert("coucou")',
81 css: 'body { background-color: red; }'
82 }
76 }, 83 },
77 cache: { 84 cache: {
78 previews: { 85 previews: {
@@ -109,6 +116,8 @@ describe('Test config', function () {
109 expect(data.instance.name).to.equal('PeerTube updated') 116 expect(data.instance.name).to.equal('PeerTube updated')
110 expect(data.instance.description).to.equal('my super description') 117 expect(data.instance.description).to.equal('my super description')
111 expect(data.instance.terms).to.equal('my super terms') 118 expect(data.instance.terms).to.equal('my super terms')
119 expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
120 expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
112 expect(data.cache.previews.size).to.equal(2) 121 expect(data.cache.previews.size).to.equal(2)
113 expect(data.signup.enabled).to.be.false 122 expect(data.signup.enabled).to.be.false
114 expect(data.signup.limit).to.equal(5) 123 expect(data.signup.limit).to.equal(5)
@@ -136,6 +145,8 @@ describe('Test config', function () {
136 expect(data.instance.name).to.equal('PeerTube updated') 145 expect(data.instance.name).to.equal('PeerTube updated')
137 expect(data.instance.description).to.equal('my super description') 146 expect(data.instance.description).to.equal('my super description')
138 expect(data.instance.terms).to.equal('my super terms') 147 expect(data.instance.terms).to.equal('my super terms')
148 expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
149 expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
139 expect(data.cache.previews.size).to.equal(2) 150 expect(data.cache.previews.size).to.equal(2)
140 expect(data.signup.enabled).to.be.false 151 expect(data.signup.enabled).to.be.false
141 expect(data.signup.limit).to.equal(5) 152 expect(data.signup.limit).to.equal(5)
@@ -167,6 +178,11 @@ describe('Test config', function () {
167 const res = await getCustomConfig(server.url, server.accessToken) 178 const res = await getCustomConfig(server.url, server.accessToken)
168 const data = res.body 179 const data = res.body
169 180
181 expect(data.instance.name).to.equal('PeerTube')
182 expect(data.instance.description).to.equal('Welcome to this PeerTube instance!')
183 expect(data.instance.terms).to.equal('No terms for now.')
184 expect(data.instance.customizations.css).to.be.empty
185 expect(data.instance.customizations.javascript).to.be.empty
170 expect(data.cache.previews.size).to.equal(1) 186 expect(data.cache.previews.size).to.equal(1)
171 expect(data.signup.enabled).to.be.true 187 expect(data.signup.enabled).to.be.true
172 expect(data.signup.limit).to.equal(4) 188 expect(data.signup.limit).to.equal(4)
diff --git a/shared/models/config/custom-config.model.ts b/shared/models/config/custom-config.model.ts
index 6ef0fc5a2..46d0a86ef 100644
--- a/shared/models/config/custom-config.model.ts
+++ b/shared/models/config/custom-config.model.ts
@@ -3,6 +3,10 @@ export interface CustomConfig {
3 name: string 3 name: string
4 description: string 4 description: string
5 terms: string 5 terms: string
6 customizations: {
7 javascript?: string
8 css?: string
9 }
6 } 10 }
7 11
8 cache: { 12 cache: {
diff --git a/shared/models/config/customization.model.ts b/shared/models/config/customization.model.ts
new file mode 100644
index 000000000..4e4d0d193
--- /dev/null
+++ b/shared/models/config/customization.model.ts
@@ -0,0 +1,8 @@
1export interface Customization {
2 instance: {
3 customization: {
4 javascript: string
5 css: string
6 }
7 }
8}
diff --git a/shared/models/config/server-config.model.ts b/shared/models/config/server-config.model.ts
index 988dd71e3..004cf6ddb 100644
--- a/shared/models/config/server-config.model.ts
+++ b/shared/models/config/server-config.model.ts
@@ -2,7 +2,11 @@ export interface ServerConfig {
2 serverVersion: string 2 serverVersion: string
3 3
4 instance: { 4 instance: {
5 name: string 5 name: string;
6 customizations: {
7 javascript: string
8 css: string
9 }
6 } 10 }
7 11
8 signup: { 12 signup: {