PeerTube/client/src/app/+admin/config/edit-custom-config/edit-custom-config.componen...

423 lines
11 KiB
TypeScript
Raw Normal View History

2021-02-10 11:06:32 +01:00
import omit from 'lodash-es/omit'
2021-02-10 11:42:10 +01:00
import { forkJoin } from 'rxjs'
import { SelectOptionsItem } from 'src/types/select-options-item.model'
2021-02-10 11:06:32 +01:00
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
2020-06-23 14:10:17 +02:00
import { ConfigService } from '@app/+admin/config/shared/config.service'
import { Notifier } from '@app/core'
import { ServerService } from '@app/core/server/server.service'
2020-08-11 16:07:53 +02:00
import {
ADMIN_EMAIL_VALIDATOR,
CACHE_CAPTIONS_SIZE_VALIDATOR,
CACHE_PREVIEWS_SIZE_VALIDATOR,
CONCURRENCY_VALIDATOR,
INDEX_URL_VALIDATOR,
INSTANCE_NAME_VALIDATOR,
INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
2021-02-10 11:42:10 +01:00
MAX_INSTANCE_LIVES_VALIDATOR,
MAX_LIVE_DURATION_VALIDATOR,
MAX_USER_LIVES_VALIDATOR,
2022-10-07 15:26:53 +02:00
MAX_VIDEO_CHANNELS_PER_USER_VALIDATOR,
SEARCH_INDEX_URL_VALIDATOR,
SERVICES_TWITTER_USERNAME_VALIDATOR,
SIGNUP_LIMIT_VALIDATOR,
SIGNUP_MINIMUM_AGE_VALIDATOR,
2022-10-07 15:26:53 +02:00
TRANSCODING_THREADS_VALIDATOR
} from '@app/shared/form-validators/custom-config-validators'
import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators'
2022-10-07 15:26:53 +02:00
import { FormReactive, FormReactiveService } from '@app/shared/shared-forms'
import { CustomPageService } from '@app/shared/shared-main/custom-page'
2021-06-04 13:31:41 +02:00
import { CustomConfig, CustomPage, HTMLServerConfig } from '@shared/models'
2021-02-10 11:06:32 +01:00
import { EditConfigurationService } from './edit-configuration.service'
type ComponentCustomConfig = CustomConfig & {
instanceCustomHomepage: CustomPage
}
@Component({
selector: 'my-edit-custom-config',
templateUrl: './edit-custom-config.component.html',
styleUrls: [ './edit-custom-config.component.scss' ]
})
2021-02-10 11:06:32 +01:00
export class EditCustomConfigComponent extends FormReactive implements OnInit {
activeNav: string
customConfig: ComponentCustomConfig
2021-06-04 13:31:41 +02:00
serverConfig: HTMLServerConfig
homepage: CustomPage
2020-08-11 16:07:53 +02:00
languageItems: SelectOptionsItem[] = []
categoryItems: SelectOptionsItem[] = []
2019-08-23 15:23:27 +02:00
constructor (
2022-10-07 15:26:53 +02:00
protected formReactiveService: FormReactiveService,
private router: Router,
private route: ActivatedRoute,
private notifier: Notifier,
private configService: ConfigService,
private customPage: CustomPageService,
2021-02-10 11:06:32 +01:00
private serverService: ServerService,
private editConfigurationService: EditConfigurationService
) {
super()
}
2018-06-05 10:58:45 +02:00
ngOnInit () {
2021-06-04 13:31:41 +02:00
this.serverConfig = this.serverService.getHTMLConfig()
2019-12-18 15:31:54 +01:00
const formGroupData: { [key in keyof ComponentCustomConfig ]: any } = {
instance: {
name: INSTANCE_NAME_VALIDATOR,
shortDescription: INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
description: null,
2019-08-23 15:23:27 +02:00
2019-02-20 15:36:43 +01:00
isNSFW: false,
defaultNSFWPolicy: null,
2019-08-23 15:23:27 +02:00
terms: null,
codeOfConduct: null,
2019-09-03 09:49:04 +02:00
creationReason: null,
2019-08-23 15:23:27 +02:00
moderationInformation: null,
administrator: null,
maintenanceLifetime: null,
businessModel: null,
hardwareInformation: null,
2019-08-23 15:23:27 +02:00
categories: null,
languages: null,
defaultClientRoute: null,
customizations: {
javascript: null,
css: null
}
},
2019-07-09 11:45:19 +02:00
theme: {
default: null
},
services: {
twitter: {
username: SERVICES_TWITTER_USERNAME_VALIDATOR,
whitelisted: null
}
},
client: {
videos: {
miniature: {
preferAuthorDisplayName: null
}
},
menu: {
login: {
redirectOnSingleExternalAuth: null
}
}
},
cache: {
previews: {
size: CACHE_PREVIEWS_SIZE_VALIDATOR
},
captions: {
size: CACHE_CAPTIONS_SIZE_VALIDATOR
},
torrents: {
size: CACHE_CAPTIONS_SIZE_VALIDATOR
}
},
signup: {
enabled: null,
limit: SIGNUP_LIMIT_VALIDATOR,
2023-01-19 09:29:47 +01:00
requiresApproval: null,
requiresEmailVerification: null,
minimumAge: SIGNUP_MINIMUM_AGE_VALIDATOR
},
import: {
videos: {
concurrency: CONCURRENCY_VALIDATOR,
http: {
enabled: null
},
torrent: {
enabled: null
}
Channel sync (#5135) * Add external channel URL for channel update / creation (#754) * Disallow synchronisation if user has no video quota (#754) * More constraints serverside (#754) * Disable sync if server configuration does not allow HTTP import (#754) * Working version synchronizing videos with a job (#754) TODO: refactoring, too much code duplication * More logs and try/catch (#754) * Fix eslint error (#754) * WIP: support synchronization time change (#754) * New frontend #754 * WIP: Create sync front (#754) * Enhance UI, sync creation form (#754) * Warning message when HTTP upload is disallowed * More consistent names (#754) * Binding Front with API (#754) * Add a /me API (#754) * Improve list UI (#754) * Implement creation and deletion routes (#754) * Lint (#754) * Lint again (#754) * WIP: UI for triggering import existing videos (#754) * Implement jobs for syncing and importing channels * Don't sync videos before sync creation + avoid concurrency issue (#754) * Cleanup (#754) * Cleanup: OpenAPI + API rework (#754) * Remove dead code (#754) * Eslint (#754) * Revert the mess with whitespaces in constants.ts (#754) * Some fixes after rebase (#754) * Several fixes after PR remarks (#754) * Front + API: Rename video-channels-sync to video-channel-syncs (#754) * Allow enabling channel sync through UI (#754) * getChannelInfo (#754) * Minor fixes: openapi + model + sql (#754) * Simplified API validators (#754) * Rename MChannelSync to MChannelSyncChannel (#754) * Add command for VideoChannelSync (#754) * Use synchronization.enabled config (#754) * Check parameters test + some fixes (#754) * Fix conflict mistake (#754) * Restrict access to video channel sync list API (#754) * Start adding unit test for synchronization (#754) * Continue testing (#754) * Tests finished + convertion of job to scheduler (#754) * Add lastSyncAt field (#754) * Fix externalRemoteUrl sort + creation date not well formatted (#754) * Small fix (#754) * Factorize addYoutubeDLImport and buildVideo (#754) * Check duplicates on channel not on users (#754) * factorize thumbnail generation (#754) * Fetch error should return status 400 (#754) * Separate video-channel-import and video-channel-sync-latest (#754) * Bump DB migration version after rebase (#754) * Prettier states in UI table (#754) * Add DefaultScope in VideoChannelSyncModel (#754) * Fix audit logs (#754) * Ensure user can upload when importing channel + minor fixes (#754) * Mark synchronization as failed on exception + typos (#754) * Change REST API for importing videos into channel (#754) * Add option for fully synchronize a chnanel (#754) * Return a whole sync object on creation to avoid tricks in Front (#754) * Various remarks (#754) * Single quotes by default (#754) * Rename synchronization to video_channel_synchronization * Add check.latest_videos_count and max_per_user options (#754) * Better channel rendering in list #754 * Allow sorting with channel name and state (#754) * Add missing tests for channel imports (#754) * Prefer using a parent job for channel sync * Styling * Client styling Co-authored-by: Chocobozzz <me@florianbigard.com>
2022-08-10 09:53:39 +02:00
},
videoChannelSynchronization: {
enabled: null
}
},
2021-01-27 17:15:21 +01:00
trending: {
videos: {
algorithms: {
enabled: null,
default: null
}
}
},
admin: {
email: ADMIN_EMAIL_VALIDATOR
},
contactForm: {
enabled: null
},
user: {
history: {
videos: {
enabled: null
}
},
videoQuota: USER_VIDEO_QUOTA_VALIDATOR,
videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR
},
videoChannels: {
maxPerUser: MAX_VIDEO_CHANNELS_PER_USER_VALIDATOR
},
transcoding: {
enabled: null,
threads: TRANSCODING_THREADS_VALIDATOR,
allowAdditionalExtensions: null,
2019-05-16 16:55:34 +02:00
allowAudioFiles: null,
profile: null,
concurrency: CONCURRENCY_VALIDATOR,
resolutions: {},
alwaysTranscodeOriginalResolution: null,
hls: {
enabled: null
2019-11-21 17:01:24 +01:00
},
webtorrent: {
enabled: null
2023-04-21 15:04:52 +02:00
},
remoteRunners: {
enabled: null
}
},
live: {
enabled: null,
2021-02-10 11:42:10 +01:00
maxDuration: MAX_LIVE_DURATION_VALIDATOR,
maxInstanceLives: MAX_INSTANCE_LIVES_VALIDATOR,
maxUserLives: MAX_USER_LIVES_VALIDATOR,
2020-09-25 16:19:35 +02:00
allowReplay: null,
2022-03-04 13:40:02 +01:00
latencySetting: {
enabled: null
},
2020-09-25 16:19:35 +02:00
transcoding: {
enabled: null,
threads: TRANSCODING_THREADS_VALIDATOR,
profile: null,
resolutions: {},
2023-04-21 15:04:52 +02:00
alwaysTranscodeOriginalResolution: null,
remoteRunners: {
enabled: null
}
}
},
2022-03-22 16:58:49 +01:00
videoStudio: {
enabled: null,
remoteRunners: {
enabled: null
}
2022-02-11 10:51:33 +01:00
},
autoBlacklist: {
videos: {
ofUsers: {
enabled: null
}
}
},
followers: {
instance: {
enabled: null,
manualApproval: null
}
2019-09-04 14:30:34 +02:00
},
followings: {
instance: {
autoFollowBack: {
enabled: null
},
autoFollowIndex: {
enabled: null,
indexUrl: INDEX_URL_VALIDATOR
2019-09-04 14:30:34 +02:00
}
}
2020-05-28 11:15:38 +02:00
},
broadcastMessage: {
enabled: null,
level: null,
dismissable: null,
message: null
2020-05-29 16:16:24 +02:00
},
search: {
remoteUri: {
users: null,
anonymous: null
},
searchIndex: {
enabled: null,
url: SEARCH_INDEX_URL_VALIDATOR,
2020-05-29 16:16:24 +02:00
disableLocalSearch: null,
isDefaultSearch: null
}
},
instanceCustomHomepage: {
content: null
}
}
const defaultValues = {
transcoding: {
resolutions: {} as { [id: string]: string }
},
live: {
transcoding: {
resolutions: {} as { [id: string]: string }
}
}
}
2021-02-10 11:06:32 +01:00
for (const resolution of this.editConfigurationService.getVODResolutions()) {
defaultValues.transcoding.resolutions[resolution.id] = 'false'
formGroupData.transcoding.resolutions[resolution.id] = null
}
2021-02-10 11:06:32 +01:00
for (const resolution of this.editConfigurationService.getLiveResolutions()) {
defaultValues.live.transcoding.resolutions[resolution.id] = 'false'
formGroupData.live.transcoding.resolutions[resolution.id] = null
}
2018-06-05 10:58:45 +02:00
this.buildForm(formGroupData)
2021-02-10 11:06:32 +01:00
if (this.route.snapshot.fragment) {
this.onNavChange(this.route.snapshot.fragment)
}
2020-05-29 16:16:24 +02:00
2021-02-10 11:06:32 +01:00
this.loadConfigAndUpdateForm()
this.loadCategoriesAndLanguages()
2021-10-14 11:35:43 +02:00
if (!this.isUpdateAllowed()) {
this.form.disable()
}
}
2021-08-25 16:14:11 +02:00
formValidated () {
this.forceCheck()
if (!this.form.valid) return
const value: ComponentCustomConfig = this.form.getRawValue()
2020-11-10 14:15:59 +01:00
forkJoin([
this.configService.updateCustomConfig(omit(value, 'instanceCustomHomepage')),
this.customPage.updateInstanceHomepage(value.instanceCustomHomepage.content)
])
2021-08-17 11:27:47 +02:00
.subscribe({
next: ([ resConfig ]) => {
const instanceCustomHomepage = {
content: value.instanceCustomHomepage.content
}
this.customConfig = { ...resConfig, instanceCustomHomepage }
// Reload general configuration
2019-12-18 15:31:54 +01:00
this.serverService.resetConfig()
2021-08-17 14:42:53 +02:00
.subscribe(config => {
this.serverConfig = config
})
this.updateForm()
this.notifier.success($localize`Configuration updated.`)
},
2021-08-17 11:27:47 +02:00
error: err => this.notifier.error(err.message)
})
}
2021-10-14 11:35:43 +02:00
isUpdateAllowed () {
return this.serverConfig.webadmin.configuration.edition.allowed === true
}
2020-09-25 16:19:35 +02:00
hasConsistentOptions () {
if (this.hasLiveAllowReplayConsistentOptions()) return true
return false
}
hasLiveAllowReplayConsistentOptions () {
2021-02-10 11:06:32 +01:00
if (
this.editConfigurationService.isTranscodingEnabled(this.form) === false &&
this.editConfigurationService.isLiveEnabled(this.form) &&
this.form.value['live']['allowReplay'] === true
) {
2020-09-25 16:19:35 +02:00
return false
}
return true
}
onNavChange (newActiveNav: string) {
this.activeNav = newActiveNav
this.router.navigate([], { fragment: this.activeNav })
}
2021-02-10 12:06:26 +01:00
grabAllErrors (errorObjectArg?: any) {
const errorObject = errorObjectArg || this.formErrors
let acc: string[] = []
for (const key of Object.keys(errorObject)) {
const value = errorObject[key]
if (!value) continue
if (typeof value === 'string') {
acc.push(value)
} else {
acc = acc.concat(this.grabAllErrors(value))
}
}
return acc
}
private updateForm () {
this.form.patchValue(this.customConfig)
}
2021-02-10 11:06:32 +01:00
private loadConfigAndUpdateForm () {
forkJoin([
this.configService.getCustomConfig(),
this.customPage.getInstanceHomepage()
2021-08-17 11:27:47 +02:00
]).subscribe({
next: ([ config, homepage ]) => {
this.customConfig = { ...config, instanceCustomHomepage: homepage }
this.updateForm()
this.markAllAsDirty()
},
2021-08-17 11:27:47 +02:00
error: err => this.notifier.error(err.message)
})
}
2021-02-10 11:06:32 +01:00
private loadCategoriesAndLanguages () {
forkJoin([
this.serverService.getVideoLanguages(),
this.serverService.getVideoCategories()
2021-08-17 11:27:47 +02:00
]).subscribe({
next: ([ languages, categories ]) => {
2021-02-10 11:06:32 +01:00
this.languageItems = languages.map(l => ({ label: l.label, id: l.id }))
this.categoryItems = categories.map(l => ({ label: l.label, id: l.id + '' }))
},
2021-08-17 11:27:47 +02:00
error: err => this.notifier.error(err.message)
})
}
}