mirror of
https://github.com/AzuraCast/AzuraCast.git
synced 2024-06-14 13:16:37 +00:00
More composition, make MayNeedRestart composable.
This commit is contained in:
parent
48bf6352f4
commit
d2cb74095a
|
@ -26,21 +26,19 @@
|
||||||
<streaming-log-modal ref="modal"></streaming-log-modal>
|
<streaming-log-modal ref="modal"></streaming-log-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import LogList from "~/components/Common/LogList";
|
import LogList from "~/components/Common/LogList";
|
||||||
import StreamingLogModal from "~/components/Common/StreamingLogModal";
|
import StreamingLogModal from "~/components/Common/StreamingLogModal";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'AdminLogs',
|
systemLogsUrl: String,
|
||||||
components: {StreamingLogModal, LogList},
|
stationLogs: Array
|
||||||
props: {
|
});
|
||||||
systemLogsUrl: String,
|
|
||||||
stationLogs: Array
|
const modal = ref(); // StreamingLogModal
|
||||||
},
|
|
||||||
methods: {
|
const viewLog = (url) => {
|
||||||
viewLog(url) {
|
modal.value.show(url);
|
||||||
this.$refs.modal.show(url);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -70,40 +70,39 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import FlowUpload from "~/components/Common/FlowUpload";
|
import FlowUpload from "~/components/Common/FlowUpload";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'AdminShoutcast',
|
apiUrl: String
|
||||||
components: {FlowUpload},
|
});
|
||||||
props: {
|
|
||||||
apiUrl: String
|
const loading = ref(true);
|
||||||
},
|
const version = ref(null);
|
||||||
data() {
|
|
||||||
return {
|
const {$gettext} = useTranslate();
|
||||||
loading: true,
|
|
||||||
version: null,
|
const langInstalledVersion = computed(() => {
|
||||||
};
|
return $gettext(
|
||||||
},
|
'Shoutcast version "%{ version }" is currently installed.',
|
||||||
computed: {
|
{
|
||||||
langInstalledVersion() {
|
version: version.value
|
||||||
const text = this.$gettext('Shoutcast version "%{ version }" is currently installed.');
|
|
||||||
return this.$gettextInterpolate(text, {
|
|
||||||
version: this.version
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
mounted() {
|
});
|
||||||
this.relist();
|
|
||||||
},
|
const {axios} = useAxios();
|
||||||
methods: {
|
|
||||||
relist() {
|
const relist = () => {
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
this.axios.get(this.apiUrl).then((resp) => {
|
axios.get(props.apiUrl).then((resp) => {
|
||||||
this.version = resp.data.version;
|
version.value = resp.data.version;
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
onMounted(relist);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -77,43 +77,46 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import FlowUpload from "~/components/Common/FlowUpload";
|
import FlowUpload from "~/components/Common/FlowUpload";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'AdminStereoTool',
|
apiUrl: String
|
||||||
components: {FlowUpload},
|
});
|
||||||
props: {
|
|
||||||
apiUrl: String
|
const loading = ref(true);
|
||||||
},
|
const version = ref(null);
|
||||||
data() {
|
|
||||||
return {
|
const {$gettext} = useTranslate();
|
||||||
loading: true,
|
|
||||||
version: null,
|
const langInstalledVersion = computed(() => {
|
||||||
};
|
return $gettext(
|
||||||
},
|
'Stereo Tool version %{ version } is currently installed.',
|
||||||
computed: {
|
{
|
||||||
langInstalledVersion() {
|
version: version.value
|
||||||
const text = this.$gettext('Stereo Tool version %{ version } is currently installed.');
|
|
||||||
return this.$gettextInterpolate(text, {
|
|
||||||
version: this.version
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
mounted() {
|
});
|
||||||
this.relist();
|
|
||||||
},
|
const {notifyError} = useNotify();
|
||||||
methods: {
|
|
||||||
onError(file, message) {
|
const onError = (file, message) => {
|
||||||
this.$notifyError(message);
|
notifyError(message);
|
||||||
},
|
};
|
||||||
relist() {
|
|
||||||
this.loading = true;
|
const {axios} = useAxios();
|
||||||
this.axios.get(this.apiUrl).then((resp) => {
|
|
||||||
this.version = resp.data.version;
|
const relist = () => {
|
||||||
this.loading = false;
|
loading.value = true;
|
||||||
});
|
axios.get(props.apiUrl).then((resp) => {
|
||||||
}
|
version.value = resp.data.version;
|
||||||
}
|
loading.value = false;
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(relist);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,34 +13,21 @@
|
||||||
<slot name="description" v-bind="slotProps"></slot>
|
<slot name="description" v-bind="slotProps"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
<template v-for="(_, slot) of filteredSlots" v-slot:[slot]="scope">
|
||||||
<slot :name="slot" v-bind="scope"></slot>
|
<slot :name="slot" v-bind="scope"></slot>
|
||||||
</template>
|
</template>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import _ from "lodash";
|
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'BFormMarkup',
|
id: {
|
||||||
props: {
|
type: String,
|
||||||
id: {
|
required: true
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
filteredScopedSlots() {
|
|
||||||
return _.filter(this.$slots, (slot, name) => {
|
|
||||||
return !_.includes([
|
|
||||||
'default', 'label', 'description'
|
|
||||||
], name);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
isRequired() {
|
|
||||||
return _.has(this.field, 'required');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const filteredSlots = useSlotsExcept(['default', 'label', 'description']);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -23,56 +23,49 @@
|
||||||
<slot name="description" v-bind="slotProps"></slot>
|
<slot name="description" v-bind="slotProps"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
<template v-for="(_, slot) of filteredSlots" v-slot:[slot]="scope">
|
||||||
<slot :name="slot" v-bind="scope"></slot>
|
<slot :name="slot" v-bind="scope"></slot>
|
||||||
</template>
|
</template>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import _ from "lodash";
|
import {has} from "lodash";
|
||||||
import VuelidateError from "./VuelidateError";
|
import VuelidateError from "./VuelidateError";
|
||||||
|
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'BWrappedFormCheckbox',
|
id: {
|
||||||
components: {VuelidateError},
|
type: String,
|
||||||
props: {
|
required: true
|
||||||
id: {
|
},
|
||||||
type: String,
|
name: {
|
||||||
required: true
|
type: String,
|
||||||
},
|
},
|
||||||
name: {
|
field: {
|
||||||
type: String,
|
type: Object,
|
||||||
},
|
required: true
|
||||||
field: {
|
},
|
||||||
type: Object,
|
inputAttrs: {
|
||||||
required: true
|
type: Object,
|
||||||
},
|
default() {
|
||||||
inputAttrs: {
|
return {};
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
advanced: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
advanced: {
|
||||||
filteredScopedSlots() {
|
type: Boolean,
|
||||||
return _.filter(this.$slots, (slot, name) => {
|
default: false
|
||||||
return !_.includes([
|
|
||||||
'default', 'description'
|
|
||||||
], name);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fieldState() {
|
|
||||||
return this.field.$dirty ? !this.field.$error : null;
|
|
||||||
},
|
|
||||||
isRequired() {
|
|
||||||
return _.has(this.field, 'required');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const filteredSlots = useSlotsExcept(['default', 'description']);
|
||||||
|
|
||||||
|
const fieldState = computed(() => {
|
||||||
|
return props.field.$dirty ? !props.field.$error : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isRequired = computed(() => {
|
||||||
|
return has(props.field, 'required');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -29,98 +29,96 @@
|
||||||
<slot v-bind="slotProps" name="description"></slot>
|
<slot v-bind="slotProps" name="description"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
<template v-for="(_, slot) of filteredSlots" v-slot:[slot]="scope">
|
||||||
<slot :name="slot" v-bind="scope"></slot>
|
<slot :name="slot" v-bind="scope"></slot>
|
||||||
</template>
|
</template>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import _ from "lodash";
|
|
||||||
import VuelidateError from "./VuelidateError";
|
import VuelidateError from "./VuelidateError";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||||
|
import {has} from "lodash";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'BWrappedFormGroup',
|
id: {
|
||||||
components: {VuelidateError},
|
type: String,
|
||||||
props: {
|
required: true
|
||||||
id: {
|
},
|
||||||
type: String,
|
name: {
|
||||||
required: true
|
type: String,
|
||||||
},
|
},
|
||||||
name: {
|
field: {
|
||||||
type: String,
|
type: Object,
|
||||||
},
|
required: true
|
||||||
field: {
|
},
|
||||||
type: Object,
|
inputType: {
|
||||||
required: true
|
type: String,
|
||||||
},
|
default: 'text'
|
||||||
inputType: {
|
},
|
||||||
type: String,
|
inputNumber: {
|
||||||
default: 'text'
|
type: Boolean,
|
||||||
},
|
default: false
|
||||||
inputNumber: {
|
},
|
||||||
type: Boolean,
|
inputTrim: {
|
||||||
default: false
|
type: Boolean,
|
||||||
},
|
default: false
|
||||||
inputTrim: {
|
},
|
||||||
type: Boolean,
|
inputEmptyIsNull: {
|
||||||
default: false
|
type: Boolean,
|
||||||
},
|
default: false
|
||||||
inputEmptyIsNull: {
|
},
|
||||||
type: Boolean,
|
inputAttrs: {
|
||||||
default: false
|
type: Object,
|
||||||
},
|
default() {
|
||||||
inputAttrs: {
|
return {};
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autofocus: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
advanced: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
autofocus: {
|
||||||
modelValue: {
|
type: Boolean,
|
||||||
get() {
|
default: false
|
||||||
return this.field.$model;
|
|
||||||
},
|
|
||||||
set(value) {
|
|
||||||
if ((this.isNumeric || this.inputEmptyIsNull) && '' === value) {
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.field.$model = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
filteredScopedSlots() {
|
|
||||||
return _.filter(this.$slots, (slot, name) => {
|
|
||||||
return !_.includes([
|
|
||||||
'default', 'label', 'description'
|
|
||||||
], name);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fieldState() {
|
|
||||||
return this.field.$dirty ? !this.field.$error : null;
|
|
||||||
},
|
|
||||||
isRequired() {
|
|
||||||
return _.has(this.field, 'required');
|
|
||||||
},
|
|
||||||
isNumeric() {
|
|
||||||
return this.inputNumber || this.inputType === "number";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
advanced: {
|
||||||
focus() {
|
type: Boolean,
|
||||||
if (typeof this.$refs.input !== "undefined") {
|
default: false
|
||||||
this.$refs.input.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
const modelValue = computed({
|
||||||
|
get() {
|
||||||
|
return props.field.$model;
|
||||||
|
},
|
||||||
|
set(newValue) {
|
||||||
|
if ((props.isNumeric || props.inputEmptyIsNull) && '' === newValue) {
|
||||||
|
newValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.field.$model = newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredSlots = useSlotsExcept(['default', 'label', 'description']);
|
||||||
|
|
||||||
|
const fieldState = computed(() => {
|
||||||
|
return props.field.$dirty ? !props.field.$error : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isRequired = computed(() => {
|
||||||
|
return has(props.field, 'required');
|
||||||
|
});
|
||||||
|
|
||||||
|
const isNumeric = computed(() => {
|
||||||
|
return props.inputNumber || props.inputType === "number";
|
||||||
|
});
|
||||||
|
|
||||||
|
const input = ref(); // Input
|
||||||
|
|
||||||
|
const focus = () => {
|
||||||
|
input.value?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,76 +6,79 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import _ from 'lodash';
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {get, map} from "lodash";
|
||||||
|
import {computed} from "vue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'VuelidateError',
|
field: Object
|
||||||
props: {
|
});
|
||||||
field: Object
|
|
||||||
|
const {$gettext} = useTranslate();
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
required: () => {
|
||||||
|
return $gettext('This field is required.');
|
||||||
},
|
},
|
||||||
data() {
|
minLength: (params) => {
|
||||||
return {
|
return $gettext(
|
||||||
messages: {
|
'This field must have at least %{ min } letters.',
|
||||||
required: () => {
|
params
|
||||||
return this.$gettext('This field is required.');
|
);
|
||||||
},
|
},
|
||||||
minLength: (params) => {
|
maxLength: (params) => {
|
||||||
let text = this.$gettext('This field must have at least %{ min } letters.');
|
return $gettext(
|
||||||
return this.$gettextInterpolate(text, params);
|
'This field must have at most %{ max } letters.',
|
||||||
},
|
params
|
||||||
maxLength: (params) => {
|
);
|
||||||
let text = this.$gettext('This field must have at most %{ max } letters.');
|
},
|
||||||
return this.$gettextInterpolate(text, params);
|
between: (params) => {
|
||||||
},
|
return $gettext(
|
||||||
between: (params) => {
|
'This field must be between %{ min } and %{ max }.',
|
||||||
let text = this.$gettext('This field must be between %{ min } and %{ max }.');
|
params
|
||||||
return this.$gettextInterpolate(text, params);
|
);
|
||||||
},
|
},
|
||||||
alpha: () => {
|
alpha: () => {
|
||||||
return this.$gettext('This field must only contain alphabetic characters.');
|
return $gettext('This field must only contain alphabetic characters.');
|
||||||
},
|
},
|
||||||
alphaNum: () => {
|
alphaNum: () => {
|
||||||
return this.$gettext('This field must only contain alphanumeric characters.');
|
return $gettext('This field must only contain alphanumeric characters.');
|
||||||
},
|
},
|
||||||
numeric: () => {
|
numeric: () => {
|
||||||
return this.$gettext('This field must only contain numeric characters.');
|
return $gettext('This field must only contain numeric characters.');
|
||||||
},
|
},
|
||||||
integer: () => {
|
integer: () => {
|
||||||
return this.$gettext('This field must be a valid integer.');
|
return $gettext('This field must be a valid integer.');
|
||||||
},
|
},
|
||||||
decimal: () => {
|
decimal: () => {
|
||||||
return this.$gettext('This field must be a valid decimal number.');
|
return $gettext('This field must be a valid decimal number.');
|
||||||
},
|
},
|
||||||
email: () => {
|
email: () => {
|
||||||
return this.$gettext('This field must be a valid e-mail address.');
|
return $gettext('This field must be a valid e-mail address.');
|
||||||
},
|
},
|
||||||
ipAddress: () => {
|
ipAddress: () => {
|
||||||
return this.$gettext('This field must be a valid IP address.');
|
return $gettext('This field must be a valid IP address.');
|
||||||
},
|
},
|
||||||
url: () => {
|
url: () => {
|
||||||
return this.$gettext('This field must be a valid URL.');
|
return $gettext('This field must be a valid URL.');
|
||||||
},
|
},
|
||||||
validatePassword: () => {
|
validatePassword: () => {
|
||||||
return this.$gettext('This password is too common or insecure.');
|
return $gettext('This password is too common or insecure.');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorMessages = computed(() => {
|
||||||
|
return map(
|
||||||
|
props.field.$errors,
|
||||||
|
(error) => {
|
||||||
|
const message = get(messages, error.$validator, null);
|
||||||
|
if (null !== message) {
|
||||||
|
return message(error.$params);
|
||||||
|
} else {
|
||||||
|
return error.$message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
computed: {
|
});
|
||||||
errorMessages() {
|
|
||||||
let errors = [];
|
|
||||||
_.forEach(this.field.$errors, (error) => {
|
|
||||||
const message = _.get(this.messages, error.$validator, null);
|
|
||||||
if (null !== message) {
|
|
||||||
errors.push(message(error.$params));
|
|
||||||
} else {
|
|
||||||
errors.push(error.$message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<song-history-modal :show-album-art="showAlbumArt" ref="history_modal"></song-history-modal>
|
<song-history-modal :show-album-art="showAlbumArt" :history="history"></song-history-modal>
|
||||||
<request-modal :show-album-art="showAlbumArt" :request-list-uri="requestListUri"
|
<request-modal :show-album-art="showAlbumArt" :request-list-uri="requestListUri"
|
||||||
:custom-fields="customFields"></request-modal>
|
:custom-fields="customFields"></request-modal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -64,12 +64,9 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const history_modal = ref(); // Template ref
|
const history = ref({});
|
||||||
const isMounted = useMounted();
|
|
||||||
|
|
||||||
const onNowPlayingUpdate = (newNowPlaying) => {
|
const onNowPlayingUpdate = (newNowPlaying) => {
|
||||||
if (isMounted.value) {
|
history.value = newNowPlaying?.song_history;
|
||||||
history_modal.value.updateHistory(newNowPlaying);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,44 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<b-modal size="lg" id="request_modal" ref="modal" :title="langTitle" hide-footer>
|
<b-modal size="lg" id="request_modal" ref="modal" :title="$gettext('Request a Song')" hide-footer>
|
||||||
<song-request :show-album-art="showAlbumArt" :request-list-uri="requestListUri" :custom-fields="customFields"
|
<song-request :show-album-art="showAlbumArt" :request-list-uri="requestListUri" :custom-fields="customFields"
|
||||||
@submitted="doClose"></song-request>
|
@submitted="doClose"></song-request>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import SongRequest from '../Requests';
|
import SongRequest from '../Requests';
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
components: { SongRequest },
|
requestListUri: {
|
||||||
props: {
|
type: String,
|
||||||
requestListUri: {
|
required: true
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
showAlbumArt: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
customFields: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data () {
|
showAlbumArt: {
|
||||||
return {
|
type: Boolean,
|
||||||
loading: true
|
default: true
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
customFields: {
|
||||||
langTitle () {
|
type: Array,
|
||||||
return this.$gettext('Request a Song');
|
required: false,
|
||||||
}
|
default: () => []
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
doClose () {
|
|
||||||
this.$refs.modal.hide();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const modal = ref(); // BModal
|
||||||
|
|
||||||
|
const doClose = () => {
|
||||||
|
modal.value?.hide();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="station-history">
|
<div id="station-history">
|
||||||
<p v-if="history.length <= 0">{{ langNoRecords }}</p>
|
<p v-if="history.length <= 0">{{ $gettext('No records to display.') }}</p>
|
||||||
<div class="song" v-for="(row, index) in history">
|
<div class="song" v-for="(row, index) in history">
|
||||||
<strong class="order">{{ history.length - index }}</strong>
|
<strong class="order">{{ history.length - index }}</strong>
|
||||||
<img v-if="showAlbumArt" class="art" :src="row.song.art">
|
<img v-if="showAlbumArt" class="art" :src="row.song.art">
|
||||||
|
@ -77,33 +77,26 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import {DateTime} from 'luxon';
|
import {DateTime} from "luxon";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
props: {
|
history: Array,
|
||||||
history: Array,
|
showAlbumArt: {
|
||||||
showAlbumArt: {
|
type: Boolean,
|
||||||
type: Boolean,
|
default: true
|
||||||
default: true
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
});
|
||||||
langNoRecords () {
|
|
||||||
return this.$gettext('No records to display.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
unixTimestampToDate (timestamp) {
|
|
||||||
if (!timestamp) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateTime.fromSeconds(timestamp).toRelative();
|
const unixTimestampToDate = (timestamp) => {
|
||||||
},
|
if (!timestamp) {
|
||||||
albumAndArtist (song) {
|
return '';
|
||||||
return [song.artist, song.album].filter(str => !!str).join(', ');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return DateTime.fromSeconds(timestamp).toRelative();
|
||||||
|
};
|
||||||
|
|
||||||
|
const albumAndArtist = (song) => {
|
||||||
|
return [song.artist, song.album].filter(str => !!str).join(', ');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,34 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<b-modal size="md" id="song_history_modal" ref="modal" :title="langTitle" centered hide-footer>
|
<b-modal size="md" id="song_history_modal" ref="modal" :title="$gettext('Song History')" centered hide-footer>
|
||||||
<song-history :show-album-art="showAlbumArt" :history="history"></song-history>
|
<song-history :show-album-art="showAlbumArt" :history="history"></song-history>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import SongHistory from './SongHistory';
|
import SongHistory from './SongHistory';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
components: {SongHistory},
|
history: Array,
|
||||||
props: {
|
showAlbumArt: {
|
||||||
showAlbumArt: {
|
type: Boolean,
|
||||||
type: Boolean,
|
default: true
|
||||||
default: true
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
});
|
||||||
return {
|
|
||||||
history: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
langTitle() {
|
|
||||||
return this.$gettext('Song History');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateHistory (np) {
|
|
||||||
this.history = np.song_history;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
:is-stream="false"></play-button>
|
:is-stream="false"></play-button>
|
||||||
<template v-if="showDownloadButton">
|
<template v-if="showDownloadButton">
|
||||||
|
|
||||||
<a class="name" :href="row.item.download_url" target="_blank" :title="langDownload">
|
<a class="name" :href="row.item.download_url" target="_blank" :title="$gettext('Download')">
|
||||||
<icon icon="cloud_download"></icon>
|
<icon icon="cloud_download"></icon>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<template #cell(media_art)="row">
|
<template #cell(media_art)="row">
|
||||||
<a :href="row.item.media_art" class="album-art" target="_blank"
|
<a :href="row.item.media_art" class="album-art" target="_blank"
|
||||||
data-fancybox="gallery">
|
data-fancybox="gallery">
|
||||||
<img class="media_manager_album_art" :alt="langAlbumArt" :src="row.item.media_art">
|
<img class="media_manager_album_art" :alt="$gettext('Album Art')" :src="row.item.media_art">
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template #cell(size)="row">
|
<template #cell(size)="row">
|
||||||
|
@ -90,59 +90,41 @@
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import InlinePlayer from '../InlinePlayer';
|
import InlinePlayer from '../InlinePlayer';
|
||||||
import DataTable from '~/components/Common/DataTable';
|
import DataTable from '~/components/Common/DataTable';
|
||||||
import _ from 'lodash';
|
import {forEach} from 'lodash';
|
||||||
import Icon from '~/components/Common/Icon';
|
import Icon from '~/components/Common/Icon';
|
||||||
import PlayButton from "~/components/Common/PlayButton";
|
import PlayButton from "~/components/Common/PlayButton";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
components: {PlayButton, Icon, DataTable, InlinePlayer},
|
listUrl: String,
|
||||||
props: {
|
stationName: String,
|
||||||
listUrl: String,
|
customFields: Array,
|
||||||
stationName: String,
|
showDownloadButton: Boolean
|
||||||
customFields: Array,
|
});
|
||||||
showDownloadButton: Boolean
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
let fields = [
|
|
||||||
{key: 'download_url', label: ' '},
|
|
||||||
{key: 'media_art', label: this.$gettext('Art')},
|
|
||||||
{key: 'media_title', label: this.$gettext('Title'), sortable: true, selectable: true},
|
|
||||||
{key: 'media_artist', label: this.$gettext('Artist'), sortable: true, selectable: true},
|
|
||||||
{key: 'media_album', label: this.$gettext('Album'), sortable: true, selectable: true, visible: false},
|
|
||||||
{key: 'playlist', label: this.$gettext('Playlist'), sortable: true, selectable: true, visible: false}
|
|
||||||
];
|
|
||||||
|
|
||||||
_.forEach(this.customFields.slice(), (field) => {
|
const {$gettext} = useTranslate();
|
||||||
fields.push({
|
|
||||||
key: field.display_key,
|
|
||||||
label: field.label,
|
|
||||||
sortable: true,
|
|
||||||
selectable: true,
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
let fields = [
|
||||||
fields: fields
|
{key: 'download_url', label: ' '},
|
||||||
};
|
{key: 'media_art', label: $gettext('Art')},
|
||||||
},
|
{key: 'media_title', label: $gettext('Title'), sortable: true, selectable: true},
|
||||||
computed: {
|
{key: 'media_artist', label: $gettext('Artist'), sortable: true, selectable: true},
|
||||||
langAlbumArt () {
|
{key: 'media_album', label: $gettext('Album'), sortable: true, selectable: true, visible: false},
|
||||||
return this.$gettext('Album Art');
|
{key: 'playlist', label: $gettext('Playlist'), sortable: true, selectable: true, visible: false}
|
||||||
},
|
];
|
||||||
langPlayPause () {
|
|
||||||
return this.$gettext('Play/Pause');
|
forEach(props.customFields.slice(), (field) => {
|
||||||
},
|
fields.push({
|
||||||
langDownload () {
|
key: field.display_key,
|
||||||
return this.$gettext('Download');
|
label: field.label,
|
||||||
}
|
sortable: true,
|
||||||
}
|
selectable: true,
|
||||||
};
|
visible: false
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -39,15 +39,12 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Schedule from '~/components/Common/ScheduleView';
|
import Schedule from '~/components/Common/ScheduleView';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
components: { Schedule },
|
scheduleUrl: String,
|
||||||
props: {
|
stationName: String,
|
||||||
scheduleUrl: String,
|
stationTimeZone: String
|
||||||
stationName: String,
|
});
|
||||||
stationTimeZone: String
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
<template>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'StationMayNeedRestart',
|
|
||||||
props: {
|
|
||||||
restartStatusUrl: String
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
mayNeedRestart() {
|
|
||||||
this.axios.get(this.restartStatusUrl).then((resp) => {
|
|
||||||
if (resp.data.needs_restart) {
|
|
||||||
this.needsRestart();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
needsRestart() {
|
|
||||||
document.dispatchEvent(new CustomEvent("station-needs-restart"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
;
|
|
||||||
</script>
|
|
35
frontend/vue/components/Stations/Common/useMayNeedRestart.js
Normal file
35
frontend/vue/components/Stations/Common/useMayNeedRestart.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
|
export const mayNeedRestartProps = {
|
||||||
|
restartStatusUrl: String
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useNeedsRestart() {
|
||||||
|
const needsRestart = () => {
|
||||||
|
document.dispatchEvent(new CustomEvent("station-needs-restart"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsRestart
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMayNeedRestart(restartStatusUrl) {
|
||||||
|
const {needsRestart} = useNeedsRestart();
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const mayNeedRestart = () => {
|
||||||
|
axios.get(restartStatusUrl).then((resp) => {
|
||||||
|
if (resp.data.needs_restart) {
|
||||||
|
needsRestart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsRestart,
|
||||||
|
mayNeedRestart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,65 +43,77 @@
|
||||||
</data-table>
|
</data-table>
|
||||||
</b-card>
|
</b-card>
|
||||||
|
|
||||||
<edit-modal ref="editModal" :create-url="listUrl" @relist="relist" @needs-restart="mayNeedRestart"></edit-modal>
|
<edit-modal ref="editmodal" :create-url="listUrl" @relist="relist" @needs-restart="mayNeedRestart"></edit-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import DataTable from '~/components/Common/DataTable';
|
import DataTable from '~/components/Common/DataTable';
|
||||||
import EditModal from './HlsStreams/EditModal';
|
import EditModal from './HlsStreams/EditModal';
|
||||||
import Icon from '~/components/Common/Icon';
|
import Icon from '~/components/Common/Icon';
|
||||||
import InfoCard from '~/components/Common/InfoCard';
|
import InfoCard from '~/components/Common/InfoCard';
|
||||||
import StationMayNeedRestart from '~/components/Stations/Common/MayNeedRestart.vue';
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {mayNeedRestartProps, useMayNeedRestart} from "~/components/Stations/Common/useMayNeedRestart";
|
||||||
|
import {useSweetAlert} from "~/vendor/sweetalert";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'StationHlsStreams',
|
...mayNeedRestartProps,
|
||||||
components: {InfoCard, Icon, EditModal, DataTable},
|
listUrl: String
|
||||||
mixins: [StationMayNeedRestart],
|
});
|
||||||
props: {
|
|
||||||
listUrl: String
|
const {$gettext} = useTranslate();
|
||||||
},
|
|
||||||
data() {
|
const fields = [
|
||||||
return {
|
{key: 'name', isRowHeader: true, label: $gettext('Name'), sortable: true},
|
||||||
fields: [
|
{key: 'format', label: $gettext('Format'), sortable: true},
|
||||||
{key: 'name', isRowHeader: true, label: this.$gettext('Name'), sortable: true},
|
{key: 'bitrate', label: $gettext('Bitrate'), sortable: true},
|
||||||
{key: 'format', label: this.$gettext('Format'), sortable: true},
|
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
|
||||||
{key: 'bitrate', label: this.$gettext('Bitrate'), sortable: true},
|
];
|
||||||
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
|
|
||||||
]
|
const upper = (data) => {
|
||||||
};
|
let upper = [];
|
||||||
},
|
data.split(' ').forEach((word) => {
|
||||||
methods: {
|
upper.push(word.toUpperCase());
|
||||||
upper(data) {
|
});
|
||||||
let upper = [];
|
return upper.join(' ');
|
||||||
data.split(' ').forEach((word) => {
|
};
|
||||||
upper.push(word.toUpperCase());
|
|
||||||
|
const datatable = ref(); // DataTable
|
||||||
|
|
||||||
|
const relist = () => {
|
||||||
|
datatable.value?.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const editmodal = ref(); // EditModal
|
||||||
|
|
||||||
|
const doCreate = () => {
|
||||||
|
editmodal.value?.create();
|
||||||
|
};
|
||||||
|
|
||||||
|
const doEdit = (url) => {
|
||||||
|
editmodal.value?.edit(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {mayNeedRestart, needsRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||||
|
const {confirmDelete} = useSweetAlert();
|
||||||
|
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const doDelete = (url) => {
|
||||||
|
confirmDelete({
|
||||||
|
title: $gettext('Delete HLS Stream?'),
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.value) {
|
||||||
|
wrapWithLoading(
|
||||||
|
axios.delete(url)
|
||||||
|
).then((resp) => {
|
||||||
|
notifySuccess(resp.data.message);
|
||||||
|
needsRestart();
|
||||||
|
relist();
|
||||||
});
|
});
|
||||||
return upper.join(' ');
|
}
|
||||||
},
|
});
|
||||||
relist() {
|
|
||||||
this.$refs.datatable.refresh();
|
|
||||||
},
|
|
||||||
doCreate() {
|
|
||||||
this.$refs.editModal.create();
|
|
||||||
},
|
|
||||||
doEdit(url) {
|
|
||||||
this.$refs.editModal.edit(url);
|
|
||||||
},
|
|
||||||
doDelete(url) {
|
|
||||||
this.$confirmDelete({
|
|
||||||
title: this.$gettext('Delete HLS Stream?'),
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.value) {
|
|
||||||
this.$wrapWithLoading(
|
|
||||||
this.axios.delete(url)
|
|
||||||
).then((resp) => {
|
|
||||||
this.$notifySuccess(resp.data.message);
|
|
||||||
this.needsRestart();
|
|
||||||
this.relist();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<b-overlay variant="card" :show="loading">
|
<b-overlay variant="card" :show="loading">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<b-form-fieldset v-for="(row, index) in config" :key="index" class="mb-0">
|
<b-form-fieldset v-for="(row, index) in config" :key="index" class="mb-0">
|
||||||
<b-wrapped-form-group v-if="row.is_field" :field="v$.form[row.field_name]"
|
<b-wrapped-form-group v-if="row.is_field" :field="v$[row.field_name]"
|
||||||
:id="'form_edit_'+row.field_name" input-type="textarea"
|
:id="'form_edit_'+row.field_name" input-type="textarea"
|
||||||
:input-attrs="{class: 'text-preformatted mb-3', spellcheck: 'false', 'max-rows': 20, rows: 5}">
|
:input-attrs="{class: 'text-preformatted mb-3', spellcheck: 'false', 'max-rows': 20, rows: 5}">
|
||||||
</b-wrapped-form-group>
|
</b-wrapped-form-group>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</b-form-markup>
|
</b-form-markup>
|
||||||
</b-form-fieldset>
|
</b-form-fieldset>
|
||||||
|
|
||||||
<b-button size="lg" type="submit" :variant="(v$.form.$invalid) ? 'danger' : 'primary'">
|
<b-button size="lg" type="submit" :variant="(v$.$invalid) ? 'danger' : 'primary'">
|
||||||
{{ $gettext('Save Changes') }}
|
{{ $gettext('Save Changes') }}
|
||||||
</b-button>
|
</b-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,83 +46,78 @@
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import useVuelidate from "@vuelidate/core";
|
|
||||||
import BFormFieldset from "~/components/Form/BFormFieldset";
|
import BFormFieldset from "~/components/Form/BFormFieldset";
|
||||||
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
|
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
|
||||||
import BFormMarkup from "~/components/Form/BFormMarkup";
|
import BFormMarkup from "~/components/Form/BFormMarkup";
|
||||||
import _ from "lodash";
|
import {forEach} from "lodash";
|
||||||
import mergeExisting from "~/functions/mergeExisting";
|
import mergeExisting from "~/functions/mergeExisting";
|
||||||
import InfoCard from "~/components/Common/InfoCard";
|
import InfoCard from "~/components/Common/InfoCard";
|
||||||
import StationMayNeedRestart from '~/components/Stations/Common/MayNeedRestart.vue';
|
import {useVuelidateOnForm} from "~/components/Form/UseVuelidateOnForm";
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
import {mayNeedRestartProps, useMayNeedRestart} from "~/components/Stations/Common/useMayNeedRestart";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'StationsLiquidsoapConfig',
|
...mayNeedRestartProps,
|
||||||
components: {InfoCard, BFormFieldset, BWrappedFormGroup, BFormMarkup},
|
settingsUrl: String,
|
||||||
setup() {
|
config: Array,
|
||||||
return {v$: useVuelidate()}
|
sections: Array,
|
||||||
},
|
});
|
||||||
props: {
|
|
||||||
settingsUrl: String,
|
|
||||||
config: Array,
|
|
||||||
sections: Array,
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
StationMayNeedRestart,
|
|
||||||
],
|
|
||||||
validations() {
|
|
||||||
let validations = {form: {}};
|
|
||||||
_.forEach(this.sections, (section) => {
|
|
||||||
validations.form[section] = {};
|
|
||||||
});
|
|
||||||
return validations;
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: true,
|
|
||||||
form: {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.relist();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resetForm() {
|
|
||||||
let form = {};
|
|
||||||
_.forEach(this.sections, (section) => {
|
|
||||||
form[section] = null;
|
|
||||||
});
|
|
||||||
this.form = form;
|
|
||||||
},
|
|
||||||
relist() {
|
|
||||||
this.resetForm();
|
|
||||||
this.v$.$reset();
|
|
||||||
|
|
||||||
this.loading = true;
|
const buildForm = () => {
|
||||||
this.axios.get(this.settingsUrl).then((resp) => {
|
let validations = {};
|
||||||
this.form = mergeExisting(this.form, resp.data);
|
let blankForm = {};
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
this.v$.$touch();
|
|
||||||
if (this.v$.$errors.length > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$wrapWithLoading(
|
forEach(props.sections, (section) => {
|
||||||
this.axios({
|
validations[section] = {};
|
||||||
method: 'PUT',
|
blankForm[section] = null;
|
||||||
url: this.settingsUrl,
|
});
|
||||||
data: this.form
|
|
||||||
})
|
|
||||||
).then(() => {
|
|
||||||
this.$notifySuccess();
|
|
||||||
|
|
||||||
this.mayNeedRestart();
|
return {validations, blankForm};
|
||||||
this.relist();
|
}
|
||||||
});
|
|
||||||
}
|
const {validations, blankForm} = buildForm();
|
||||||
|
const {form, resetForm, v$} = useVuelidateOnForm(validations, blankForm);
|
||||||
|
|
||||||
|
const loading = ref();
|
||||||
|
|
||||||
|
const {mayNeedRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||||
|
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const relist = () => {
|
||||||
|
resetForm();
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
axios.get(props.settingsUrl).then((resp) => {
|
||||||
|
form.value = mergeExisting(form.value, resp.data);
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(relist);
|
||||||
|
|
||||||
|
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
v$.value.$touch();
|
||||||
|
if (v$.value.$errors.length > 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wrapWithLoading(
|
||||||
|
axios({
|
||||||
|
method: 'PUT',
|
||||||
|
url: props.settingsUrl,
|
||||||
|
data: form.value,
|
||||||
|
})
|
||||||
|
).then(() => {
|
||||||
|
notifySuccess();
|
||||||
|
|
||||||
|
mayNeedRestart();
|
||||||
|
relist();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -54,72 +54,87 @@
|
||||||
</data-table>
|
</data-table>
|
||||||
</b-card>
|
</b-card>
|
||||||
|
|
||||||
<edit-modal ref="editModal" :create-url="listUrl" :new-intro-url="newIntroUrl"
|
<edit-modal ref="editmodal" :create-url="listUrl" :new-intro-url="newIntroUrl"
|
||||||
:show-advanced="showAdvanced" :station-frontend-type="stationFrontendType"
|
:show-advanced="showAdvanced" :station-frontend-type="stationFrontendType"
|
||||||
@relist="relist" @needs-restart="mayNeedRestart"></edit-modal>
|
@relist="relist" @needs-restart="mayNeedRestart"></edit-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import DataTable from '~/components/Common/DataTable';
|
import DataTable from '~/components/Common/DataTable';
|
||||||
import EditModal from './Mounts/EditModal';
|
import EditModal from './Mounts/EditModal';
|
||||||
import Icon from '~/components/Common/Icon';
|
import Icon from '~/components/Common/Icon';
|
||||||
import InfoCard from '~/components/Common/InfoCard';
|
import InfoCard from '~/components/Common/InfoCard';
|
||||||
import StationMayNeedRestart from '~/components/Stations/Common/MayNeedRestart.vue';
|
import {mayNeedRestartProps, useMayNeedRestart} from "~/components/Stations/Common/useMayNeedRestart";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {useSweetAlert} from "~/vendor/sweetalert";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'StationMounts',
|
...mayNeedRestartProps,
|
||||||
components: {InfoCard, Icon, EditModal, DataTable},
|
listUrl: String,
|
||||||
mixins: [StationMayNeedRestart],
|
newIntroUrl: String,
|
||||||
props: {
|
stationFrontendType: String,
|
||||||
listUrl: String,
|
showAdvanced: {
|
||||||
newIntroUrl: String,
|
type: Boolean,
|
||||||
stationFrontendType: String,
|
default: true
|
||||||
showAdvanced: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
});
|
||||||
return {
|
|
||||||
fields: [
|
const {$gettext} = useTranslate();
|
||||||
{key: 'display_name', isRowHeader: true, label: this.$gettext('Name'), sortable: true},
|
|
||||||
{key: 'enable_autodj', label: this.$gettext('AutoDJ'), sortable: true},
|
const fields = [
|
||||||
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
|
{key: 'display_name', isRowHeader: true, label: $gettext('Name'), sortable: true},
|
||||||
]
|
{key: 'enable_autodj', label: $gettext('AutoDJ'), sortable: true},
|
||||||
};
|
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
|
||||||
},
|
];
|
||||||
methods: {
|
|
||||||
upper(data) {
|
const upper = (data) => {
|
||||||
let upper = [];
|
let upper = [];
|
||||||
data.split(' ').forEach((word) => {
|
data.split(' ').forEach((word) => {
|
||||||
upper.push(word.toUpperCase());
|
upper.push(word.toUpperCase());
|
||||||
});
|
});
|
||||||
return upper.join(' ');
|
return upper.join(' ');
|
||||||
},
|
};
|
||||||
relist() {
|
|
||||||
this.$refs.datatable.refresh();
|
const datatable = ref(); // DataTable
|
||||||
},
|
|
||||||
doCreate() {
|
const relist = () => {
|
||||||
this.$refs.editModal.create();
|
datatable.value.refresh();
|
||||||
},
|
};
|
||||||
doEdit(url) {
|
|
||||||
this.$refs.editModal.edit(url);
|
const editmodal = ref(); // EditModal
|
||||||
},
|
|
||||||
doDelete(url) {
|
const doCreate = () => {
|
||||||
this.$confirmDelete({
|
editmodal.value.create();
|
||||||
title: this.$gettext('Delete Mount Point?'),
|
};
|
||||||
}).then((result) => {
|
|
||||||
if (result.value) {
|
const doEdit = (url) => {
|
||||||
this.$wrapWithLoading(
|
editmodal.value.edit(url);
|
||||||
this.axios.delete(url)
|
};
|
||||||
).then((resp) => {
|
|
||||||
this.$notifySuccess(resp.data.message);
|
const {needsRestart, mayNeedRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||||
this.needsRestart();
|
|
||||||
this.relist();
|
const {confirmDelete} = useSweetAlert();
|
||||||
});
|
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||||
}
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const doDelete = (url) => {
|
||||||
|
confirmDelete({
|
||||||
|
title: $gettext('Delete Mount Point?'),
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.value) {
|
||||||
|
wrapWithLoading(
|
||||||
|
axios.delete(url)
|
||||||
|
).then((resp) => {
|
||||||
|
notifySuccess(resp.data.message);
|
||||||
|
needsRestart();
|
||||||
|
relist();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -48,71 +48,83 @@
|
||||||
</data-table>
|
</data-table>
|
||||||
</b-card>
|
</b-card>
|
||||||
|
|
||||||
<remote-edit-modal ref="editModal" :create-url="listUrl"
|
<remote-edit-modal ref="editmodal" :create-url="listUrl"
|
||||||
@relist="relist" @needs-restart="mayNeedRestart"></remote-edit-modal>
|
@relist="relist" @needs-restart="mayNeedRestart"></remote-edit-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import DataTable from '~/components/Common/DataTable';
|
import DataTable from '~/components/Common/DataTable';
|
||||||
import EditModal from './Mounts/EditModal';
|
|
||||||
import Icon from '~/components/Common/Icon';
|
import Icon from '~/components/Common/Icon';
|
||||||
import InfoCard from '~/components/Common/InfoCard';
|
import InfoCard from '~/components/Common/InfoCard';
|
||||||
import RemoteEditModal from "./Remotes/EditModal";
|
import RemoteEditModal from "./Remotes/EditModal";
|
||||||
import StationMayNeedRestart from '~/components/Stations/Common/MayNeedRestart.vue';
|
|
||||||
import '~/vendor/sweetalert';
|
import '~/vendor/sweetalert';
|
||||||
|
import {mayNeedRestartProps, useMayNeedRestart} from "~/components/Stations/Common/useMayNeedRestart";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {useSweetAlert} from "~/vendor/sweetalert";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'StationMounts',
|
...mayNeedRestartProps,
|
||||||
components: {RemoteEditModal, InfoCard, Icon, EditModal, DataTable},
|
listUrl: String,
|
||||||
mixins: [StationMayNeedRestart],
|
});
|
||||||
props: {
|
|
||||||
listUrl: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fields: [
|
|
||||||
{key: 'display_name', isRowHeader: true, label: this.$gettext('Name'), sortable: true},
|
|
||||||
{key: 'enable_autodj', label: this.$gettext('AutoDJ'), sortable: true},
|
|
||||||
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
upper(data) {
|
|
||||||
if (!data) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let upper = [];
|
const {$gettext} = useTranslate();
|
||||||
data.split(' ').forEach((word) => {
|
|
||||||
upper.push(word.toUpperCase());
|
const fields = [
|
||||||
});
|
{key: 'display_name', isRowHeader: true, label: $gettext('Name'), sortable: true},
|
||||||
return upper.join(' ');
|
{key: 'enable_autodj', label: $gettext('AutoDJ'), sortable: true},
|
||||||
},
|
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
|
||||||
relist() {
|
];
|
||||||
this.$refs.datatable.refresh();
|
|
||||||
},
|
const upper = (data) => {
|
||||||
doCreate() {
|
if (!data) {
|
||||||
this.$refs.editModal.create();
|
return '';
|
||||||
},
|
|
||||||
doEdit(url) {
|
|
||||||
this.$refs.editModal.edit(url);
|
|
||||||
},
|
|
||||||
doDelete(url) {
|
|
||||||
this.$confirmDelete({
|
|
||||||
title: this.$gettext('Delete Remote Relay?'),
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.value) {
|
|
||||||
this.$wrapWithLoading(
|
|
||||||
this.axios.delete(url)
|
|
||||||
).then((resp) => {
|
|
||||||
this.$notifySuccess(resp.data.message);
|
|
||||||
this.needsRestart();
|
|
||||||
this.relist();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let upper = [];
|
||||||
|
data.split(' ').forEach((word) => {
|
||||||
|
upper.push(word.toUpperCase());
|
||||||
|
});
|
||||||
|
return upper.join(' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const datatable = ref(); // DataTable
|
||||||
|
|
||||||
|
const relist = () => {
|
||||||
|
datatable.value?.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const editmodal = ref(); // EditModal
|
||||||
|
|
||||||
|
const doCreate = () => {
|
||||||
|
editmodal.value?.create();
|
||||||
|
};
|
||||||
|
|
||||||
|
const doEdit = (url) => {
|
||||||
|
editmodal.value?.edit(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {mayNeedRestart, needsRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||||
|
|
||||||
|
const {confirmDelete} = useSweetAlert();
|
||||||
|
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const doDelete = (url) => {
|
||||||
|
confirmDelete({
|
||||||
|
title: $gettext('Delete Remote Relay?'),
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.value) {
|
||||||
|
wrapWithLoading(
|
||||||
|
axios.delete(url)
|
||||||
|
).then((resp) => {
|
||||||
|
notifySuccess(resp.data.message);
|
||||||
|
needsRestart();
|
||||||
|
relist();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
}}
|
}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<flow-upload :target-url="apiUrl" :valid-mime-types="acceptMimeTypes"
|
<flow-upload :target-url="apiUrl" :valid-mime-types="['text/plain']"
|
||||||
@success="onFileSuccess"></flow-upload>
|
@success="onFileSuccess"></flow-upload>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
|
||||||
|
@ -56,42 +56,42 @@
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import FlowUpload from '~/components/Common/FlowUpload';
|
import FlowUpload from '~/components/Common/FlowUpload';
|
||||||
import InfoCard from "~/components/Common/InfoCard";
|
import InfoCard from "~/components/Common/InfoCard";
|
||||||
import StationMayNeedRestart from '~/components/Stations/Common/MayNeedRestart.vue';
|
import {ref} from "vue";
|
||||||
|
import {mayNeedRestartProps, useMayNeedRestart} from "~/components/Stations/Common/useMayNeedRestart";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'StationsStereoToolConfiguration',
|
...mayNeedRestartProps,
|
||||||
components: {InfoCard, FlowUpload},
|
recordHasStereoToolConfiguration: Boolean,
|
||||||
mixins: [StationMayNeedRestart],
|
apiUrl: String
|
||||||
props: {
|
});
|
||||||
recordHasStereoToolConfiguration: Boolean,
|
|
||||||
apiUrl: String
|
const hasStereoToolConfiguration = ref(props.recordHasStereoToolConfiguration);
|
||||||
},
|
|
||||||
data() {
|
const {mayNeedRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||||
return {
|
|
||||||
hasStereoToolConfiguration: this.recordHasStereoToolConfiguration,
|
const onFileSuccess = () => {
|
||||||
acceptMimeTypes: ['text/plain']
|
mayNeedRestart();
|
||||||
};
|
hasStereoToolConfiguration.value = true;
|
||||||
},
|
};
|
||||||
methods: {
|
|
||||||
onFileSuccess() {
|
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||||
this.mayNeedRestart();
|
const {axios} = useAxios();
|
||||||
this.hasStereoToolConfiguration = true;
|
|
||||||
},
|
const deleteConfigurationFile = () => {
|
||||||
deleteConfigurationFile() {
|
wrapWithLoading(
|
||||||
this.$wrapWithLoading(
|
axios({
|
||||||
this.axios({
|
method: 'DELETE',
|
||||||
method: 'DELETE',
|
url: this.apiUrl
|
||||||
url: this.apiUrl
|
})
|
||||||
})
|
).then(() => {
|
||||||
).then(() => {
|
mayNeedRestart();
|
||||||
this.mayNeedRestart();
|
hasStereoToolConfiguration.value = false;
|
||||||
this.hasStereoToolConfiguration = false;
|
notifySuccess();
|
||||||
this.$notifySuccess();
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
12
frontend/vue/functions/useSlotsExcept.js
Normal file
12
frontend/vue/functions/useSlotsExcept.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {filter, includes} from "lodash";
|
||||||
|
import {computed, useSlots} from "vue";
|
||||||
|
|
||||||
|
export default function useSlotsExcept(except) {
|
||||||
|
const slots = useSlots();
|
||||||
|
|
||||||
|
return computed(() => {
|
||||||
|
return filter(slots, (slot, name) => {
|
||||||
|
return !includes(except, name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user