]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
Add fragment support in admin conf page
[github/Chocobozzz/PeerTube.git] / client / src / app / +admin / config / edit-custom-config / edit-custom-config.component.ts
CommitLineData
ccc00cb2 1import { forkJoin } from 'rxjs'
80ac2e55 2import { pairwise } from 'rxjs/operators'
53e4e201 3import { SelectOptionsItem } from 'src/types/select-options-item.model'
45e0d669 4import { ViewportScroller } from '@angular/common'
67ed6552 5import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core'
53e4e201 6import { ActivatedRoute, Router } from '@angular/router'
67ed6552
C
7import { ConfigService } from '@app/+admin/config/shared/config.service'
8import { Notifier } from '@app/core'
9import { ServerService } from '@app/core/server/server.service'
52c4976f 10import {
7ed1edbb
C
11 ADMIN_EMAIL_VALIDATOR,
12 CACHE_CAPTIONS_SIZE_VALIDATOR,
13 CACHE_PREVIEWS_SIZE_VALIDATOR,
9129b769 14 CONCURRENCY_VALIDATOR,
7ed1edbb
C
15 INDEX_URL_VALIDATOR,
16 INSTANCE_NAME_VALIDATOR,
17 INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
18 SEARCH_INDEX_URL_VALIDATOR,
19 SERVICES_TWITTER_USERNAME_VALIDATOR,
20 SIGNUP_LIMIT_VALIDATOR,
21 TRANSCODING_THREADS_VALIDATOR
22} from '@app/shared/form-validators/custom-config-validators'
23import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators'
21e493d4 24import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
45c6bcf3 25import { NgbNav } from '@ng-bootstrap/ng-bootstrap'
67ed6552 26import { CustomConfig, ServerConfig } from '@shared/models'
fd206f0b
C
27
28@Component({
29 selector: 'my-edit-custom-config',
30 templateUrl: './edit-custom-config.component.html',
31 styleUrls: [ './edit-custom-config.component.scss' ]
32})
45e0d669 33export class EditCustomConfigComponent extends FormReactive implements OnInit, AfterViewChecked {
45c6bcf3 34 @ViewChild('nav') nav: NgbNav
45e0d669
RK
35
36 initDone = false
bee0abff 37 customConfig: CustomConfig
bee0abff 38
46db9430 39 resolutions: { id: string, label: string, description?: string }[] = []
c6c0fa6c 40 liveResolutions: { id: string, label: string, description?: string }[] = []
fd206f0b 41
ead64cdf
C
42 transcodingThreadOptions: SelectOptionsItem[] = []
43 liveMaxDurationOptions: SelectOptionsItem[] = []
80ac2e55 44
52c4976f
C
45 languageItems: SelectOptionsItem[] = []
46 categoryItems: SelectOptionsItem[] = []
ccc00cb2 47
16a173bb
C
48 signupAlertMessage: string
49
53e4e201
C
50 activeNav: string
51
ba430d75
C
52 private serverConfig: ServerConfig
53
fd206f0b 54 constructor (
53e4e201
C
55 private router: Router,
56 private route: ActivatedRoute,
45e0d669 57 private viewportScroller: ViewportScroller,
d18d6478 58 protected formValidatorService: FormValidatorService,
f8b2c1b4 59 private notifier: Notifier,
fd206f0b 60 private configService: ConfigService,
66357162 61 private serverService: ServerService
fd206f0b
C
62 ) {
63 super()
3827c3b3
C
64
65 this.resolutions = [
2fa9c40e 66 {
5c7d6508 67 id: '0p',
66357162
C
68 label: $localize`Audio-only`,
69 description: $localize`A <code>.mp4</code> that keeps the original audio track, with no video`
5c7d6508 70 },
00aa1f0d
C
71 {
72 id: '240p',
66357162 73 label: $localize`240p`
00aa1f0d
C
74 },
75 {
76 id: '360p',
66357162 77 label: $localize`360p`
00aa1f0d
C
78 },
79 {
80 id: '480p',
66357162 81 label: $localize`480p`
00aa1f0d
C
82 },
83 {
84 id: '720p',
66357162 85 label: $localize`720p`
00aa1f0d
C
86 },
87 {
88 id: '1080p',
66357162 89 label: $localize`1080p`
00aa1f0d 90 },
b7085c71
RK
91 {
92 id: '1440p',
93 label: $localize`1440p`
94 },
00aa1f0d
C
95 {
96 id: '2160p',
66357162 97 label: $localize`2160p`
00aa1f0d 98 }
3827c3b3
C
99 ]
100
c6c0fa6c
C
101 this.liveResolutions = this.resolutions.filter(r => r.id !== '0p')
102
3827c3b3 103 this.transcodingThreadOptions = [
ead64cdf
C
104 { id: 0, label: $localize`Auto (via ffmpeg)` },
105 { id: 1, label: '1' },
106 { id: 2, label: '2' },
107 { id: 4, label: '4' },
108 { id: 8, label: '8' },
109 { id: 12, label: '12' },
110 { id: 16, label: '16' },
111 { id: 32, label: '32' }
3827c3b3 112 ]
80ac2e55 113
fb719404 114 this.liveMaxDurationOptions = [
ead64cdf
C
115 { id: -1, label: $localize`No limit` },
116 { id: 1000 * 3600, label: $localize`1 hour` },
117 { id: 1000 * 3600 * 3, label: $localize`3 hours` },
118 { id: 1000 * 3600 * 5, label: $localize`5 hours` },
119 { id: 1000 * 3600 * 10, label: $localize`10 hours` }
fb719404 120 ]
fd206f0b
C
121 }
122
41a676db 123 get videoQuotaOptions () {
3827c3b3 124 return this.configService.videoQuotaOptions
41a676db
C
125 }
126
127 get videoQuotaDailyOptions () {
3827c3b3 128 return this.configService.videoQuotaDailyOptions
41a676db
C
129 }
130
7cd4d2ba 131 get availableThemes () {
ba430d75 132 return this.serverConfig.theme.registered
ffb321be 133 .map(t => t.name)
7cd4d2ba
C
134 }
135
4f20856e
C
136 get liveRTMPPort () {
137 return this.serverConfig.live.rtmp.port
138 }
139
80ac2e55 140 getAvailableTranscodingProfile (type: 'live' | 'vod') {
ead64cdf
C
141 const profiles = type === 'live'
142 ? this.serverConfig.live.transcoding.availableProfiles
143 : this.serverConfig.transcoding.availableProfiles
80ac2e55 144
ead64cdf 145 return profiles.map(p => ({ id: p, label: p }))
80ac2e55
C
146 }
147
ea5cdc11
RK
148 getTotalTranscodingThreads () {
149 const transcodingEnabled = this.form.value['transcoding']['enabled']
150 const transcodingThreads = this.form.value['transcoding']['threads']
151 const liveTranscodingEnabled = this.form.value['live']['transcoding']['enabled']
152 const liveTranscodingThreads = this.form.value['live']['transcoding']['threads']
153
154 // checks whether all enabled method are on fixed values and not on auto (= 0)
155 let noneOnAuto = !transcodingEnabled || +transcodingThreads > 0
156 noneOnAuto &&= !liveTranscodingEnabled || +liveTranscodingThreads > 0
157
158 // count total of fixed value, repalcing auto by a single thread (knowing it will display "at least")
159 let value = 0
160 if (transcodingEnabled) value += +transcodingThreads || 1
161 if (liveTranscodingEnabled) value += +liveTranscodingThreads || 1
162
163 return {
164 value,
165 atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible
166 unit: value > 1
167 ? $localize`threads`
168 : $localize`thread`
169 }
170 }
171
fd206f0b 172 getResolutionKey (resolution: string) {
3866f1a0 173 return 'transcoding.resolutions.' + resolution
fd206f0b
C
174 }
175
d18d6478 176 ngOnInit () {
ba430d75
C
177 this.serverConfig = this.serverService.getTmpConfig()
178 this.serverService.getConfig()
fb719404
C
179 .subscribe(config => {
180 this.serverConfig = config
181 })
ba430d75 182
3866f1a0
C
183 const formGroupData: { [key in keyof CustomConfig ]: any } = {
184 instance: {
7ed1edbb
C
185 name: INSTANCE_NAME_VALIDATOR,
186 shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
3866f1a0 187 description: null,
ccc00cb2 188
f8802489 189 isNSFW: false,
3866f1a0 190 defaultNSFWPolicy: null,
ccc00cb2
C
191
192 terms: null,
193 codeOfConduct: null,
8ae03c37
C
194
195 creationReason: null,
ccc00cb2
C
196 moderationInformation: null,
197 administrator: null,
198 maintenanceLifetime: null,
199 businessModel: null,
200
be04c6fd
C
201 hardwareInformation: null,
202
ccc00cb2
C
203 categories: null,
204 languages: null,
205
206 defaultClientRoute: null,
207
3866f1a0
C
208 customizations: {
209 javascript: null,
210 css: null
211 }
212 },
7cd4d2ba
C
213 theme: {
214 default: null
215 },
3866f1a0
C
216 services: {
217 twitter: {
7ed1edbb 218 username: SERVICES_TWITTER_USERNAME_VALIDATOR,
3866f1a0
C
219 whitelisted: null
220 }
221 },
222 cache: {
223 previews: {
7ed1edbb 224 size: CACHE_PREVIEWS_SIZE_VALIDATOR
3866f1a0
C
225 },
226 captions: {
7ed1edbb 227 size: CACHE_CAPTIONS_SIZE_VALIDATOR
3866f1a0
C
228 }
229 },
230 signup: {
231 enabled: null,
7ed1edbb 232 limit: SIGNUP_LIMIT_VALIDATOR,
3866f1a0
C
233 requiresEmailVerification: null
234 },
235 import: {
236 videos: {
9129b769 237 concurrency: CONCURRENCY_VALIDATOR,
3866f1a0
C
238 http: {
239 enabled: null
240 },
241 torrent: {
242 enabled: null
243 }
244 }
245 },
ba5d4a84
RK
246 trending: {
247 videos: {
248 algorithms: {
249 enabled: null,
250 default: null
251 }
252 }
253 },
3866f1a0 254 admin: {
7ed1edbb 255 email: ADMIN_EMAIL_VALIDATOR
3866f1a0
C
256 },
257 contactForm: {
258 enabled: null
259 },
260 user: {
7ed1edbb
C
261 videoQuota: USER_VIDEO_QUOTA_VALIDATOR,
262 videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR
3866f1a0
C
263 },
264 transcoding: {
265 enabled: null,
7ed1edbb 266 threads: TRANSCODING_THREADS_VALIDATOR,
3866f1a0 267 allowAdditionalExtensions: null,
536598cf 268 allowAudioFiles: null,
80ac2e55 269 profile: null,
9129b769 270 concurrency: CONCURRENCY_VALIDATOR,
5d9e4eaa
C
271 resolutions: {},
272 hls: {
273 enabled: null
5a71acd2
C
274 },
275 webtorrent: {
276 enabled: null
5d9e4eaa 277 }
7ccddd7b 278 },
c6c0fa6c
C
279 live: {
280 enabled: null,
281
fb719404 282 maxDuration: null,
a056ca48
C
283 maxInstanceLives: null,
284 maxUserLives: null,
fb719404
C
285 allowReplay: null,
286
c6c0fa6c
C
287 transcoding: {
288 enabled: null,
289 threads: TRANSCODING_THREADS_VALIDATOR,
80ac2e55 290 profile: null,
c6c0fa6c
C
291 resolutions: {}
292 }
293 },
7ccddd7b
JM
294 autoBlacklist: {
295 videos: {
296 ofUsers: {
297 enabled: null
298 }
299 }
0dc64777
C
300 },
301 followers: {
302 instance: {
303 enabled: null,
304 manualApproval: null
305 }
e1b49ee5
C
306 },
307 followings: {
308 instance: {
309 autoFollowBack: {
310 enabled: null
311 },
312 autoFollowIndex: {
313 enabled: null,
7ed1edbb 314 indexUrl: INDEX_URL_VALIDATOR
e1b49ee5
C
315 }
316 }
72c33e71
C
317 },
318 broadcastMessage: {
319 enabled: null,
320 level: null,
321 dismissable: null,
322 message: null
5fb2e288
C
323 },
324 search: {
325 remoteUri: {
326 users: null,
327 anonymous: null
328 },
329 searchIndex: {
330 enabled: null,
7ed1edbb 331 url: SEARCH_INDEX_URL_VALIDATOR,
5fb2e288
C
332 disableLocalSearch: null,
333 isDefaultSearch: null
334 }
3866f1a0 335 }
fd206f0b
C
336 }
337
3866f1a0
C
338 const defaultValues = {
339 transcoding: {
340 resolutions: {}
c6c0fa6c
C
341 },
342 live: {
343 transcoding: {
344 resolutions: {}
345 }
3866f1a0
C
346 }
347 }
c6c0fa6c 348
fd206f0b 349 for (const resolution of this.resolutions) {
00aa1f0d
C
350 defaultValues.transcoding.resolutions[resolution.id] = 'false'
351 formGroupData.transcoding.resolutions[resolution.id] = null
fd206f0b
C
352 }
353
c6c0fa6c
C
354 for (const resolution of this.liveResolutions) {
355 defaultValues.live.transcoding.resolutions[resolution.id] = 'false'
356 formGroupData.live.transcoding.resolutions[resolution.id] = null
357 }
358
53e4e201
C
359 if (this.route.snapshot.fragment) {
360 this.onNavChange(this.route.snapshot.fragment)
361 }
362
d18d6478 363 this.buildForm(formGroupData)
04cda1d7 364 this.loadForm()
16a173bb 365
04cda1d7 366 this.checkTranscodingFields()
16a173bb 367 this.checkSignupField()
fd206f0b
C
368 }
369
45e0d669
RK
370 ngAfterViewChecked () {
371 if (!this.initDone) {
372 this.initDone = true
373 this.gotoAnchor()
374 }
375 }
376
fd206f0b 377 isTranscodingEnabled () {
3866f1a0 378 return this.form.value['transcoding']['enabled'] === true
fd206f0b
C
379 }
380
c6c0fa6c
C
381 isLiveEnabled () {
382 return this.form.value['live']['enabled'] === true
383 }
384
385 isLiveTranscodingEnabled () {
386 return this.form.value['live']['transcoding']['enabled'] === true
387 }
388
fd206f0b 389 isSignupEnabled () {
3866f1a0 390 return this.form.value['signup']['enabled'] === true
fd206f0b
C
391 }
392
5fb2e288
C
393 isSearchIndexEnabled () {
394 return this.form.value['search']['searchIndex']['enabled'] === true
395 }
396
4ee6a8b1
C
397 isAutoFollowIndexEnabled () {
398 return this.form.value['followings']['instance']['autoFollowIndex']['enabled'] === true
399 }
400
ba5d4a84
RK
401 trendingVideosAlgorithmsEnabledIncludes (algorithm: string) {
402 return this.form.value['trending']['videos']['algorithms']['enabled'].find((e: string) => e === algorithm)
3da68f0a
RK
403 }
404
1f30a185 405 async formValidated () {
210856a7
C
406 const value: CustomConfig = this.form.getRawValue()
407
210856a7 408 this.configService.updateCustomConfig(value)
fd206f0b
C
409 .subscribe(
410 res => {
411 this.customConfig = res
412
413 // Reload general configuration
ba430d75 414 this.serverService.resetConfig()
fd206f0b
C
415
416 this.updateForm()
66b16caf 417
66357162 418 this.notifier.success($localize`Configuration updated.`)
fd206f0b
C
419 },
420
f8b2c1b4 421 err => this.notifier.error(err.message)
fd206f0b
C
422 )
423 }
424
45e0d669 425 gotoAnchor () {
45c6bcf3 426 const hashToNav = {
45e0d669
RK
427 'customizations': 'advanced-configuration'
428 }
429 const hash = window.location.hash.replace('#', '')
430
45c6bcf3
C
431 if (hash && Object.keys(hashToNav).includes(hash)) {
432 this.nav.select(hashToNav[hash])
45e0d669
RK
433 setTimeout(() => this.viewportScroller.scrollToAnchor(hash), 100)
434 }
435 }
436
fb719404
C
437 hasConsistentOptions () {
438 if (this.hasLiveAllowReplayConsistentOptions()) return true
439
440 return false
441 }
442
443 hasLiveAllowReplayConsistentOptions () {
444 if (this.isTranscodingEnabled() === false && this.isLiveEnabled() && this.form.value['live']['allowReplay'] === true) {
445 return false
446 }
447
448 return true
449 }
450
53e4e201
C
451 onNavChange (newActiveNav: string) {
452 this.activeNav = newActiveNav
453
454 this.router.navigate([], { fragment: this.activeNav })
455 }
456
fd206f0b 457 private updateForm () {
3866f1a0 458 this.form.patchValue(this.customConfig)
fd206f0b 459 }
04cda1d7
C
460
461 private loadForm () {
462 forkJoin([
463 this.configService.getCustomConfig(),
464 this.serverService.getVideoLanguages(),
465 this.serverService.getVideoCategories()
466 ]).subscribe(
467 ([ config, languages, categories ]) => {
468 this.customConfig = config
469
52c4976f
C
470 this.languageItems = languages.map(l => ({ label: l.label, id: l.id }))
471 this.categoryItems = categories.map(l => ({ label: l.label, id: l.id + '' }))
04cda1d7
C
472
473 this.updateForm()
474 // Force form validation
475 this.forceCheck()
476 },
477
478 err => this.notifier.error(err.message)
479 )
480 }
481
482 private checkTranscodingFields () {
483 const hlsControl = this.form.get('transcoding.hls.enabled')
484 const webtorrentControl = this.form.get('transcoding.webtorrent.enabled')
485
486 webtorrentControl.valueChanges
487 .subscribe(newValue => {
488 if (newValue === false && !hlsControl.disabled) {
489 hlsControl.disable()
490 }
491
492 if (newValue === true && !hlsControl.enabled) {
493 hlsControl.enable()
494 }
495 })
496
497 hlsControl.valueChanges
498 .subscribe(newValue => {
499 if (newValue === false && !webtorrentControl.disabled) {
500 webtorrentControl.disable()
501 }
502
503 if (newValue === true && !webtorrentControl.enabled) {
504 webtorrentControl.enable()
505 }
506 })
507 }
16a173bb
C
508
509 private checkSignupField () {
510 const signupControl = this.form.get('signup.enabled')
511
512 signupControl.valueChanges
513 .pipe(pairwise())
514 .subscribe(([ oldValue, newValue ]) => {
515 if (oldValue !== true && newValue === true) {
516 // tslint:disable:max-line-length
517 this.signupAlertMessage = $localize`You enabled signup: we automatically enabled the "Block new videos automatically" checkbox of the "Videos" section just below.`
518
519 this.form.patchValue({
520 autoBlacklist: {
521 videos: {
522 ofUsers: {
523 enabled: true
524 }
525 }
526 }
527 })
528 }
529 })
530 }
fd206f0b 531}