rephrase warning for self-hostable services
[oweals/peertube.git] / client / src / app / +admin / config / edit-custom-config / edit-custom-config.component.ts
1 import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core'
2 import { ConfigService } from '@app/+admin/config/shared/config.service'
3 import { ServerService } from '@app/core/server/server.service'
4 import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared'
5 import { Notifier } from '@app/core'
6 import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model'
7 import { I18n } from '@ngx-translate/i18n-polyfill'
8 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
9 import { SelectItem } from 'primeng/api'
10 import { forkJoin } from 'rxjs'
11 import { ServerConfig } from '@shared/models'
12 import { ViewportScroller } from '@angular/common'
13 import { NgbNav } from '@ng-bootstrap/ng-bootstrap'
14
15 @Component({
16   selector: 'my-edit-custom-config',
17   templateUrl: './edit-custom-config.component.html',
18   styleUrls: [ './edit-custom-config.component.scss' ]
19 })
20 export class EditCustomConfigComponent extends FormReactive implements OnInit, AfterViewChecked {
21   // FIXME: use built-in router
22   @ViewChild('nav') nav: NgbNav
23
24   initDone = false
25   customConfig: CustomConfig
26
27   resolutions: { id: string, label: string, description?: string }[] = []
28   transcodingThreadOptions: { label: string, value: number }[] = []
29
30   languageItems: SelectItem[] = []
31   categoryItems: SelectItem[] = []
32
33   private serverConfig: ServerConfig
34
35   constructor (
36     private viewportScroller: ViewportScroller,
37     protected formValidatorService: FormValidatorService,
38     private customConfigValidatorsService: CustomConfigValidatorsService,
39     private userValidatorsService: UserValidatorsService,
40     private notifier: Notifier,
41     private configService: ConfigService,
42     private serverService: ServerService,
43     private i18n: I18n
44   ) {
45     super()
46
47     this.resolutions = [
48       {
49         id: '0p',
50         label: this.i18n('Audio-only'),
51         description: this.i18n('A <code>.mp4</code> that keeps the original audio track, with no video')
52       },
53       {
54         id: '240p',
55         label: this.i18n('240p')
56       },
57       {
58         id: '360p',
59         label: this.i18n('360p')
60       },
61       {
62         id: '480p',
63         label: this.i18n('480p')
64       },
65       {
66         id: '720p',
67         label: this.i18n('720p')
68       },
69       {
70         id: '1080p',
71         label: this.i18n('1080p')
72       },
73       {
74         id: '2160p',
75         label: this.i18n('2160p')
76       }
77     ]
78
79     this.transcodingThreadOptions = [
80       { value: 0, label: this.i18n('Auto (via ffmpeg)') },
81       { value: 1, label: '1' },
82       { value: 2, label: '2' },
83       { value: 4, label: '4' },
84       { value: 8, label: '8' }
85     ]
86   }
87
88   get videoQuotaOptions () {
89     return this.configService.videoQuotaOptions
90   }
91
92   get videoQuotaDailyOptions () {
93     return this.configService.videoQuotaDailyOptions
94   }
95
96   get availableThemes () {
97     return this.serverConfig.theme.registered
98       .map(t => t.name)
99   }
100
101   getResolutionKey (resolution: string) {
102     return 'transcoding.resolutions.' + resolution
103   }
104
105   ngOnInit () {
106     this.serverConfig = this.serverService.getTmpConfig()
107     this.serverService.getConfig()
108         .subscribe(config => this.serverConfig = config)
109
110     const formGroupData: { [key in keyof CustomConfig ]: any } = {
111       instance: {
112         name: this.customConfigValidatorsService.INSTANCE_NAME,
113         shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION,
114         description: null,
115
116         isNSFW: false,
117         defaultNSFWPolicy: null,
118
119         terms: null,
120         codeOfConduct: null,
121
122         creationReason: null,
123         moderationInformation: null,
124         administrator: null,
125         maintenanceLifetime: null,
126         businessModel: null,
127
128         hardwareInformation: null,
129
130         categories: null,
131         languages: null,
132
133         defaultClientRoute: null,
134
135         customizations: {
136           javascript: null,
137           css: null
138         }
139       },
140       theme: {
141         default: null
142       },
143       services: {
144         twitter: {
145           username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME,
146           whitelisted: null
147         }
148       },
149       cache: {
150         previews: {
151           size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE
152         },
153         captions: {
154           size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE
155         }
156       },
157       signup: {
158         enabled: null,
159         limit: this.customConfigValidatorsService.SIGNUP_LIMIT,
160         requiresEmailVerification: null
161       },
162       import: {
163         videos: {
164           http: {
165             enabled: null
166           },
167           torrent: {
168             enabled: null
169           }
170         }
171       },
172       admin: {
173         email: this.customConfigValidatorsService.ADMIN_EMAIL
174       },
175       contactForm: {
176         enabled: null
177       },
178       user: {
179         videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
180         videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY
181       },
182       transcoding: {
183         enabled: null,
184         threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
185         allowAdditionalExtensions: null,
186         allowAudioFiles: null,
187         resolutions: {},
188         hls: {
189           enabled: null
190         },
191         webtorrent: {
192           enabled: null
193         }
194       },
195       autoBlacklist: {
196         videos: {
197           ofUsers: {
198             enabled: null
199           }
200         }
201       },
202       followers: {
203         instance: {
204           enabled: null,
205           manualApproval: null
206         }
207       },
208       followings: {
209         instance: {
210           autoFollowBack: {
211             enabled: null
212           },
213           autoFollowIndex: {
214             enabled: null,
215             indexUrl: this.customConfigValidatorsService.INDEX_URL
216           }
217         }
218       },
219       broadcastMessage: {
220         enabled: null,
221         level: null,
222         dismissable: null,
223         message: null
224       },
225       search: {
226         remoteUri: {
227           users: null,
228           anonymous: null
229         },
230         searchIndex: {
231           enabled: null,
232           url: this.customConfigValidatorsService.SEARCH_INDEX_URL,
233           disableLocalSearch: null,
234           isDefaultSearch: null
235         }
236       }
237     }
238
239     const defaultValues = {
240       transcoding: {
241         resolutions: {}
242       }
243     }
244     for (const resolution of this.resolutions) {
245       defaultValues.transcoding.resolutions[resolution.id] = 'false'
246       formGroupData.transcoding.resolutions[resolution.id] = null
247     }
248
249     this.buildForm(formGroupData)
250     this.loadForm()
251     this.checkTranscodingFields()
252   }
253
254   ngAfterViewChecked () {
255     if (!this.initDone) {
256       this.initDone = true
257       this.gotoAnchor()
258     }
259   }
260
261   isTranscodingEnabled () {
262     return this.form.value['transcoding']['enabled'] === true
263   }
264
265   isSignupEnabled () {
266     return this.form.value['signup']['enabled'] === true
267   }
268
269   isSearchIndexEnabled () {
270     return this.form.value['search']['searchIndex']['enabled'] === true
271   }
272
273   isAutoFollowIndexEnabled () {
274     return this.form.value['followings']['instance']['autoFollowIndex']['enabled'] === true
275   }
276
277   async formValidated () {
278     this.configService.updateCustomConfig(this.form.getRawValue())
279       .subscribe(
280         res => {
281           this.customConfig = res
282
283           // Reload general configuration
284           this.serverService.resetConfig()
285
286           this.updateForm()
287
288           this.notifier.success(this.i18n('Configuration updated.'))
289         },
290
291         err => this.notifier.error(err.message)
292       )
293   }
294
295   getSelectedLanguageLabel () {
296     return this.i18n('{{\'{0} languages selected')
297   }
298
299   getDefaultLanguageLabel () {
300     return this.i18n('No language')
301   }
302
303   getSelectedCategoryLabel () {
304     return this.i18n('{{\'{0} categories selected')
305   }
306
307   getDefaultCategoryLabel () {
308     return this.i18n('No category')
309   }
310
311   gotoAnchor () {
312     const hashToNav = {
313       'customizations': 'advanced-configuration'
314     }
315     const hash = window.location.hash.replace('#', '')
316
317     if (hash && Object.keys(hashToNav).includes(hash)) {
318       this.nav.select(hashToNav[hash])
319       setTimeout(() => this.viewportScroller.scrollToAnchor(hash), 100)
320     }
321   }
322
323   private updateForm () {
324     this.form.patchValue(this.customConfig)
325   }
326
327   private loadForm () {
328     forkJoin([
329       this.configService.getCustomConfig(),
330       this.serverService.getVideoLanguages(),
331       this.serverService.getVideoCategories()
332     ]).subscribe(
333       ([ config, languages, categories ]) => {
334         this.customConfig = config
335
336         this.languageItems = languages.map(l => ({ label: l.label, value: l.id }))
337         this.categoryItems = categories.map(l => ({ label: l.label, value: l.id }))
338
339         this.updateForm()
340         // Force form validation
341         this.forceCheck()
342       },
343
344       err => this.notifier.error(err.message)
345     )
346   }
347
348   private checkTranscodingFields () {
349     const hlsControl = this.form.get('transcoding.hls.enabled')
350     const webtorrentControl = this.form.get('transcoding.webtorrent.enabled')
351
352     webtorrentControl.valueChanges
353                      .subscribe(newValue => {
354                        if (newValue === false && !hlsControl.disabled) {
355                          hlsControl.disable()
356                        }
357
358                        if (newValue === true && !hlsControl.enabled) {
359                          hlsControl.enable()
360                        }
361                      })
362
363     hlsControl.valueChanges
364               .subscribe(newValue => {
365                 if (newValue === false && !webtorrentControl.disabled) {
366                   webtorrentControl.disable()
367                 }
368
369                 if (newValue === true && !webtorrentControl.enabled) {
370                   webtorrentControl.enable()
371                 }
372               })
373   }
374 }