484 lines
14 KiB
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>
|
|
|
|
|