AzuraCast/frontend/vue/components/Admin/Stations/StationForm.vue

484 lines
14 KiB
Vue

<template>
<b-overlay variant="card" :show="loading">
<b-alert variant="danger" :show="error != null">{{ error }}</b-alert>
<b-form class="form vue-form" @submit.prevent="submit">
<b-tabs :card="!isModal" pills :content-class="tabContentClass">
<b-tab :title-link-class="getTabClass(v$.$validationGroups.profileTab)" active>
<template #title>
{{ $gettext('Profile') }}
</template>
<admin-stations-profile-form :form="v$" :timezones="timezones"
:show-advanced="showAdvanced"></admin-stations-profile-form>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.frontendTab)">
<template #title>
{{ $gettext('Broadcasting') }}
</template>
<admin-stations-frontend-form :form="v$"
:is-shoutcast-installed="isShoutcastInstalled"
:countries="countries"
:show-advanced="showAdvanced"></admin-stations-frontend-form>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.backendTab)">
<template #title>
{{ $gettext('AutoDJ') }}
</template>
<admin-stations-backend-form :form="v$" :station="station"
:is-stereo-tool-installed="isStereoToolInstalled"
:show-advanced="showAdvanced"></admin-stations-backend-form>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.hlsTab)">
<template #title>
{{ $gettext('HLS') }}
</template>
<admin-stations-hls-form :form="v$" :station="station" :show-advanced="showAdvanced">
</admin-stations-hls-form>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.requestsTab)">
<template #title>
{{ $gettext('Song Requests') }}
</template>
<admin-stations-requests-form :form="v$" :station="station" :show-advanced="showAdvanced">
</admin-stations-requests-form>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.streamersTab)">
<template #title>
{{ $gettext('Streamers/DJs') }}
</template>
<admin-stations-streamers-form :form="v$" :station="station" :show-advanced="showAdvanced">
</admin-stations-streamers-form>
</b-tab>
<b-tab v-if="showAdminTab" :title-link-class="getTabClass(v$.$validationGroups.adminTab)">
<template #title>
{{ $gettext('Administration') }}
</template>
<admin-stations-admin-form :form="v$"
:is-edit-mode="isEditMode"
:storage-location-api-url="storageLocationApiUrl"
:show-advanced="showAdvanced">
</admin-stations-admin-form>
</b-tab>
</b-tabs>
<slot name="submitButton">
<b-card-body body-class="card-padding-sm">
<b-button size="lg" type="submit" :variant="(!isValid) ? 'danger' : 'primary'">
<slot name="submitButtonText">
{{ $gettext('Save Changes') }}
</slot>
</b-button>
</b-card-body>
</slot>
</b-form>
</b-overlay>
</template>
<script>
import {defineComponent} from "vue";
export const stationFormProps = {
// Global
showAdminTab: {
type: Boolean,
default: true
},
showAdvanced: {
type: Boolean,
default: true
},
// Profile
timezones: Object,
// Frontend
isShoutcastInstalled: {
type: Boolean,
default: false
},
isStereoToolInstalled: {
type: Boolean,
default: false
},
countries: Object,
// Admin
storageLocationApiUrl: String
};
export default defineComponent({
inheritAttrs: false
});
</script>
<script setup>
import AdminStationsProfileForm from "./Form/ProfileForm.vue";
import AdminStationsFrontendForm from "./Form/FrontendForm.vue";
import AdminStationsBackendForm from "./Form/BackendForm.vue";
import AdminStationsAdminForm from "./Form/AdminForm.vue";
import AdminStationsHlsForm from "./Form/HlsForm.vue";
import AdminStationsRequestsForm from "./Form/RequestsForm.vue";
import AdminStationsStreamersForm from "./Form/StreamersForm.vue";
import {decimal, numeric, required, url} from '@vuelidate/validators';
import {AUDIO_PROCESSING_NONE, BACKEND_LIQUIDSOAP, FRONTEND_ICECAST} from "~/components/Entity/RadioAdapters";
import {computed, ref, watch} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import mergeExisting from "~/functions/mergeExisting";
import {useVuelidateOnForm} from "~/components/Form/UseVuelidateOnForm";
import {isArray, merge, mergeWith} from "lodash";
const props = defineProps({
...stationFormProps,
createUrl: String,
editUrl: String,
isEditMode: Boolean,
isModal: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['error', 'submitted', 'loadingUpdate', 'validUpdate']);
const buildForm = () => {
let validations = {
name: {required},
description: {},
genre: {},
url: {url},
timezone: {},
enable_public_page: {},
enable_on_demand: {},
enable_hls: {},
default_album_art_url: {},
enable_on_demand_download: {},
frontend_type: {required},
frontend_config: {
sc_license_id: {},
sc_user_id: {},
source_pw: {},
admin_pw: {},
},
backend_type: {required},
backend_config: {
hls_enable_on_public_player: {},
hls_is_default: {},
crossfade_type: {},
crossfade: {decimal},
audio_processing_method: {},
stereo_tool_license_key: {},
record_streams: {},
record_streams_format: {},
record_streams_bitrate: {},
dj_buffer: {numeric},
},
enable_requests: {},
request_delay: {numeric},
request_threshold: {numeric},
enable_streamers: {},
disconnect_deactivate_streamer: {},
$validationGroups: {
profileTab: [
'name', 'description', 'genre', 'url', 'timezone', 'enable_public_page',
'enable_on_demand', 'enable_on_demand_download', 'default_album_art_url'
],
frontendTab: [
'frontend_type', 'frontend_config'
],
backendTab: [
'backend_type', 'backend_config',
],
hlsTab: [
'enable_hls',
],
requestsTab: [
'enable_requests',
'request_delay',
'request_threshold'
],
streamersTab: [
'enable_streamers',
'disconnect_deactivate_streamer'
]
}
};
let blankForm = {
name: '',
description: '',
genre: '',
url: '',
timezone: 'UTC',
enable_public_page: true,
enable_on_demand: false,
enable_hls: false,
default_album_art_url: '',
enable_on_demand_download: true,
frontend_type: FRONTEND_ICECAST,
frontend_config: {
sc_license_id: '',
sc_user_id: '',
source_pw: '',
admin_pw: '',
},
backend_type: BACKEND_LIQUIDSOAP,
backend_config: {
hls_enable_on_public_player: false,
hls_is_default: false,
crossfade_type: 'normal',
crossfade: 2,
audio_processing_method: AUDIO_PROCESSING_NONE,
stereo_tool_license_key: '',
record_streams: false,
record_streams_format: 'mp3',
record_streams_bitrate: 128,
dj_buffer: 5,
},
enable_requests: false,
request_delay: 5,
request_threshold: 15,
enable_streamers: false,
disconnect_deactivate_streamer: 0,
};
function mergeCustom(objValue, srcValue) {
if (isArray(objValue)) {
return objValue.concat(srcValue);
}
}
if (props.showAdvanced) {
const advancedValidations = {
short_name: {},
api_history_items: {},
frontend_config: {
port: {numeric},
max_listeners: {},
custom_config: {},
banned_ips: {},
banned_countries: {},
allowed_ips: {},
banned_user_agents: {}
},
backend_config: {
hls_segment_length: {numeric},
hls_segments_in_playlist: {numeric},
hls_segments_overhead: {numeric},
dj_port: {numeric},
telnet_port: {numeric},
dj_mount_point: {},
enable_replaygain_metadata: {},
autodj_queue_length: {},
use_manual_autodj: {},
charset: {},
performance_mode: {},
duplicate_prevention_time_range: {},
},
$validationGroups: {
profileTab: [
'short_name', 'api_history_items'
],
}
};
mergeWith(validations, advancedValidations, mergeCustom);
const advancedForm = {
short_name: '',
api_history_items: 5,
frontend_config: {
port: '',
max_listeners: '',
custom_config: '',
banned_ips: '',
banned_countries: [],
allowed_ips: '',
banned_user_agents: '',
},
backend_config: {
hls_segment_length: 4,
hls_segments_in_playlist: 5,
hls_segments_overhead: 2,
dj_port: '',
telnet_port: '',
dj_mount_point: '/',
enable_replaygain_metadata: false,
autodj_queue_length: 3,
use_manual_autodj: false,
charset: 'UTF-8',
performance_mode: 'disabled',
duplicate_prevention_time_range: 120,
},
};
merge(blankForm, advancedForm);
}
if (props.showAdminTab) {
const adminValidations = {
media_storage_location: {},
recordings_storage_location: {},
podcasts_storage_location: {},
is_enabled: {},
$validationGroups: {
adminTab: [
'media_storage_location', 'recordings_storage_location',
'podcasts_storage_location', 'is_enabled'
]
}
};
mergeWith(validations, adminValidations, mergeCustom);
const adminForm = {
media_storage_location: '',
recordings_storage_location: '',
podcasts_storage_location: '',
is_enabled: true,
};
merge(blankForm, adminForm);
if (props.showAdvanced) {
const advancedAdminValidations = {
radio_base_dir: {},
$validationGroups: {
adminTab: [
'radio_base_dir'
]
}
}
mergeWith(validations, advancedAdminValidations, mergeCustom);
const adminAdvancedForm = {
radio_base_dir: '',
};
merge(blankForm, adminAdvancedForm);
}
}
return {blankForm, validations};
};
const {blankForm, validations} = buildForm();
const {form, resetForm, v$} = useVuelidateOnForm(validations, blankForm);
const isValid = computed(() => {
return !v$.value?.$invalid ?? true;
});
watch(isValid, (newValue) => {
emit('validUpdate', newValue);
});
const loading = ref(true);
watch(loading, (newValue) => {
emit('loadingUpdate', newValue);
});
const error = ref(null);
const blankStation = {
stereo_tool_configuration_file_path: null,
links: {
stereo_tool_configuration: null
}
};
const station = ref({...blankStation});
const tabContentClass = computed(() => {
return (props.isModal)
? 'mt-3'
: '';
});
const getTabClass = (validationGroup) => {
if (!loading.value && validationGroup.$invalid) {
return 'text-danger';
}
return null;
}
const clear = () => {
resetForm();
loading.value = false;
error.value = null;
station.value = {...blankStation};
};
const populateForm = (data) => {
form.value = mergeExisting(form.value, data);
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doLoad = () => {
loading.value = true;
wrapWithLoading(
axios.get(props.editUrl)
).then((resp) => {
populateForm(resp.data);
}).catch((err) => {
emit('error', err);
}).finally(() => {
loading.value = false;
});
};
const reset = () => {
clear();
if (props.isEditMode) {
doLoad();
}
};
const submit = () => {
v$.value.$touch();
if (v$.value.$errors.length > 0) {
return;
}
error.value = null;
wrapWithLoading(
axios({
method: (props.isEditMode)
? 'PUT'
: 'POST',
url: (props.isEditMode)
? props.editUrl
: props.createUrl,
data: form.value
})
).then(() => {
notifySuccess();
emit('submitted');
}).catch((err) => {
error.value = err.response.data.message;
});
};
defineExpose({
reset,
submit
});
</script>