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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import LogList from "~/components/Common/LogList";
|
||||
import StreamingLogModal from "~/components/Common/StreamingLogModal";
|
||||
import {ref} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'AdminLogs',
|
||||
components: {StreamingLogModal, LogList},
|
||||
props: {
|
||||
systemLogsUrl: String,
|
||||
stationLogs: Array
|
||||
},
|
||||
methods: {
|
||||
viewLog(url) {
|
||||
this.$refs.modal.show(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
const props = defineProps({
|
||||
systemLogsUrl: String,
|
||||
stationLogs: Array
|
||||
});
|
||||
|
||||
const modal = ref(); // StreamingLogModal
|
||||
|
||||
const viewLog = (url) => {
|
||||
modal.value.show(url);
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -70,40 +70,39 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import FlowUpload from "~/components/Common/FlowUpload";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
|
||||
export default {
|
||||
name: 'AdminShoutcast',
|
||||
components: {FlowUpload},
|
||||
props: {
|
||||
apiUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
version: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
langInstalledVersion() {
|
||||
const text = this.$gettext('Shoutcast version "%{ version }" is currently installed.');
|
||||
return this.$gettextInterpolate(text, {
|
||||
version: this.version
|
||||
});
|
||||
const props = defineProps({
|
||||
apiUrl: String
|
||||
});
|
||||
|
||||
const loading = ref(true);
|
||||
const version = ref(null);
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const langInstalledVersion = computed(() => {
|
||||
return $gettext(
|
||||
'Shoutcast version "%{ version }" is currently installed.',
|
||||
{
|
||||
version: version.value
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.relist();
|
||||
},
|
||||
methods: {
|
||||
relist() {
|
||||
this.loading = true;
|
||||
this.axios.get(this.apiUrl).then((resp) => {
|
||||
this.version = resp.data.version;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const {axios} = useAxios();
|
||||
|
||||
const relist = () => {
|
||||
loading.value = true;
|
||||
axios.get(props.apiUrl).then((resp) => {
|
||||
version.value = resp.data.version;
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(relist);
|
||||
</script>
|
||||
|
|
|
@ -77,43 +77,46 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
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 {
|
||||
name: 'AdminStereoTool',
|
||||
components: {FlowUpload},
|
||||
props: {
|
||||
apiUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
version: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
langInstalledVersion() {
|
||||
const text = this.$gettext('Stereo Tool version %{ version } is currently installed.');
|
||||
return this.$gettextInterpolate(text, {
|
||||
version: this.version
|
||||
});
|
||||
const props = defineProps({
|
||||
apiUrl: String
|
||||
});
|
||||
|
||||
const loading = ref(true);
|
||||
const version = ref(null);
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const langInstalledVersion = computed(() => {
|
||||
return $gettext(
|
||||
'Stereo Tool version %{ version } is currently installed.',
|
||||
{
|
||||
version: version.value
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.relist();
|
||||
},
|
||||
methods: {
|
||||
onError(file, message) {
|
||||
this.$notifyError(message);
|
||||
},
|
||||
relist() {
|
||||
this.loading = true;
|
||||
this.axios.get(this.apiUrl).then((resp) => {
|
||||
this.version = resp.data.version;
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const {notifyError} = useNotify();
|
||||
|
||||
const onError = (file, message) => {
|
||||
notifyError(message);
|
||||
};
|
||||
|
||||
const {axios} = useAxios();
|
||||
|
||||
const relist = () => {
|
||||
loading.value = true;
|
||||
axios.get(props.apiUrl).then((resp) => {
|
||||
version.value = resp.data.version;
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(relist);
|
||||
</script>
|
||||
|
|
|
@ -13,34 +13,21 @@
|
|||
<slot name="description" v-bind="slotProps"></slot>
|
||||
</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>
|
||||
</template>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from "lodash";
|
||||
<script setup>
|
||||
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||
|
||||
export default {
|
||||
name: 'BFormMarkup',
|
||||
props: {
|
||||
id: {
|
||||
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 props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const filteredSlots = useSlotsExcept(['default', 'label', 'description']);
|
||||
</script>
|
||||
|
|
|
@ -23,56 +23,49 @@
|
|||
<slot name="description" v-bind="slotProps"></slot>
|
||||
</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>
|
||||
</template>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from "lodash";
|
||||
<script setup>
|
||||
import {has} from "lodash";
|
||||
import VuelidateError from "./VuelidateError";
|
||||
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||
import {computed} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'BWrappedFormCheckbox',
|
||||
components: {VuelidateError},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inputAttrs: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
advanced: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inputAttrs: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredScopedSlots() {
|
||||
return _.filter(this.$slots, (slot, name) => {
|
||||
return !_.includes([
|
||||
'default', 'description'
|
||||
], name);
|
||||
});
|
||||
},
|
||||
fieldState() {
|
||||
return this.field.$dirty ? !this.field.$error : null;
|
||||
},
|
||||
isRequired() {
|
||||
return _.has(this.field, 'required');
|
||||
}
|
||||
advanced: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
|
|
|
@ -29,98 +29,96 @@
|
|||
<slot v-bind="slotProps" name="description"></slot>
|
||||
</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>
|
||||
</template>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from "lodash";
|
||||
<script setup>
|
||||
import VuelidateError from "./VuelidateError";
|
||||
import {computed, ref} from "vue";
|
||||
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||
import {has} from "lodash";
|
||||
|
||||
export default {
|
||||
name: 'BWrappedFormGroup',
|
||||
components: {VuelidateError},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
inputNumber: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputTrim: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputEmptyIsNull: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputAttrs: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
advanced: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
field: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
inputNumber: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputTrim: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputEmptyIsNull: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputAttrs: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
modelValue: {
|
||||
get() {
|
||||
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";
|
||||
}
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
if (typeof this.$refs.input !== "undefined") {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
}
|
||||
advanced: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
|
|
|
@ -6,76 +6,79 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
<script setup>
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {get, map} from "lodash";
|
||||
import {computed} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'VuelidateError',
|
||||
props: {
|
||||
field: Object
|
||||
const props = defineProps({
|
||||
field: Object
|
||||
});
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const messages = {
|
||||
required: () => {
|
||||
return $gettext('This field is required.');
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
messages: {
|
||||
required: () => {
|
||||
return this.$gettext('This field is required.');
|
||||
},
|
||||
minLength: (params) => {
|
||||
let text = this.$gettext('This field must have at least %{ min } letters.');
|
||||
return this.$gettextInterpolate(text, params);
|
||||
},
|
||||
maxLength: (params) => {
|
||||
let text = this.$gettext('This field must have at most %{ max } letters.');
|
||||
return this.$gettextInterpolate(text, params);
|
||||
},
|
||||
between: (params) => {
|
||||
let text = this.$gettext('This field must be between %{ min } and %{ max }.');
|
||||
return this.$gettextInterpolate(text, params);
|
||||
},
|
||||
alpha: () => {
|
||||
return this.$gettext('This field must only contain alphabetic characters.');
|
||||
},
|
||||
alphaNum: () => {
|
||||
return this.$gettext('This field must only contain alphanumeric characters.');
|
||||
},
|
||||
numeric: () => {
|
||||
return this.$gettext('This field must only contain numeric characters.');
|
||||
},
|
||||
integer: () => {
|
||||
return this.$gettext('This field must be a valid integer.');
|
||||
},
|
||||
decimal: () => {
|
||||
return this.$gettext('This field must be a valid decimal number.');
|
||||
},
|
||||
email: () => {
|
||||
return this.$gettext('This field must be a valid e-mail address.');
|
||||
},
|
||||
ipAddress: () => {
|
||||
return this.$gettext('This field must be a valid IP address.');
|
||||
},
|
||||
url: () => {
|
||||
return this.$gettext('This field must be a valid URL.');
|
||||
},
|
||||
validatePassword: () => {
|
||||
return this.$gettext('This password is too common or insecure.');
|
||||
}
|
||||
minLength: (params) => {
|
||||
return $gettext(
|
||||
'This field must have at least %{ min } letters.',
|
||||
params
|
||||
);
|
||||
},
|
||||
maxLength: (params) => {
|
||||
return $gettext(
|
||||
'This field must have at most %{ max } letters.',
|
||||
params
|
||||
);
|
||||
},
|
||||
between: (params) => {
|
||||
return $gettext(
|
||||
'This field must be between %{ min } and %{ max }.',
|
||||
params
|
||||
);
|
||||
},
|
||||
alpha: () => {
|
||||
return $gettext('This field must only contain alphabetic characters.');
|
||||
},
|
||||
alphaNum: () => {
|
||||
return $gettext('This field must only contain alphanumeric characters.');
|
||||
},
|
||||
numeric: () => {
|
||||
return $gettext('This field must only contain numeric characters.');
|
||||
},
|
||||
integer: () => {
|
||||
return $gettext('This field must be a valid integer.');
|
||||
},
|
||||
decimal: () => {
|
||||
return $gettext('This field must be a valid decimal number.');
|
||||
},
|
||||
email: () => {
|
||||
return $gettext('This field must be a valid e-mail address.');
|
||||
},
|
||||
ipAddress: () => {
|
||||
return $gettext('This field must be a valid IP address.');
|
||||
},
|
||||
url: () => {
|
||||
return $gettext('This field must be a valid URL.');
|
||||
},
|
||||
validatePassword: () => {
|
||||
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>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</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"
|
||||
:custom-fields="customFields"></request-modal>
|
||||
</template>
|
||||
|
@ -64,12 +64,9 @@ const props = defineProps({
|
|||
}
|
||||
});
|
||||
|
||||
const history_modal = ref(); // Template ref
|
||||
const isMounted = useMounted();
|
||||
const history = ref({});
|
||||
|
||||
const onNowPlayingUpdate = (newNowPlaying) => {
|
||||
if (isMounted.value) {
|
||||
history_modal.value.updateHistory(newNowPlaying);
|
||||
}
|
||||
history.value = newNowPlaying?.song_history;
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,44 +1,33 @@
|
|||
<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"
|
||||
@submitted="doClose"></song-request>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import SongRequest from '../Requests';
|
||||
import {ref} from "vue";
|
||||
|
||||
export default {
|
||||
components: { SongRequest },
|
||||
props: {
|
||||
requestListUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
showAlbumArt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
customFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
}
|
||||
const props = defineProps({
|
||||
requestListUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: true
|
||||
};
|
||||
showAlbumArt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
computed: {
|
||||
langTitle () {
|
||||
return this.$gettext('Request a Song');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doClose () {
|
||||
this.$refs.modal.hide();
|
||||
}
|
||||
customFields: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const modal = ref(); // BModal
|
||||
|
||||
const doClose = () => {
|
||||
modal.value?.hide();
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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">
|
||||
<strong class="order">{{ history.length - index }}</strong>
|
||||
<img v-if="showAlbumArt" class="art" :src="row.song.art">
|
||||
|
@ -77,33 +77,26 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {DateTime} from 'luxon';
|
||||
<script setup>
|
||||
import {DateTime} from "luxon";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
history: Array,
|
||||
showAlbumArt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
const props = defineProps({
|
||||
history: Array,
|
||||
showAlbumArt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
computed: {
|
||||
langNoRecords () {
|
||||
return this.$gettext('No records to display.');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
unixTimestampToDate (timestamp) {
|
||||
if (!timestamp) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
return DateTime.fromSeconds(timestamp).toRelative();
|
||||
},
|
||||
albumAndArtist (song) {
|
||||
return [song.artist, song.album].filter(str => !!str).join(', ');
|
||||
}
|
||||
const unixTimestampToDate = (timestamp) => {
|
||||
if (!timestamp) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return DateTime.fromSeconds(timestamp).toRelative();
|
||||
};
|
||||
|
||||
const albumAndArtist = (song) => {
|
||||
return [song.artist, song.album].filter(str => !!str).join(', ');
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,34 +1,17 @@
|
|||
<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>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import SongHistory from './SongHistory';
|
||||
|
||||
export default {
|
||||
components: {SongHistory},
|
||||
props: {
|
||||
showAlbumArt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
const props = defineProps({
|
||||
history: Array,
|
||||
showAlbumArt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
history: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
langTitle() {
|
||||
return this.$gettext('Song History');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateHistory (np) {
|
||||
this.history = np.song_history;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
:is-stream="false"></play-button>
|
||||
<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>
|
||||
</a>
|
||||
</template>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<template #cell(media_art)="row">
|
||||
<a :href="row.item.media_art" class="album-art" target="_blank"
|
||||
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>
|
||||
</template>
|
||||
<template #cell(size)="row">
|
||||
|
@ -90,59 +90,41 @@
|
|||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import InlinePlayer from '../InlinePlayer';
|
||||
import DataTable from '~/components/Common/DataTable';
|
||||
import _ from 'lodash';
|
||||
import {forEach} from 'lodash';
|
||||
import Icon from '~/components/Common/Icon';
|
||||
import PlayButton from "~/components/Common/PlayButton";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
|
||||
export default {
|
||||
components: {PlayButton, Icon, DataTable, InlinePlayer},
|
||||
props: {
|
||||
listUrl: String,
|
||||
stationName: String,
|
||||
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}
|
||||
];
|
||||
const props = defineProps({
|
||||
listUrl: String,
|
||||
stationName: String,
|
||||
customFields: Array,
|
||||
showDownloadButton: Boolean
|
||||
});
|
||||
|
||||
_.forEach(this.customFields.slice(), (field) => {
|
||||
fields.push({
|
||||
key: field.display_key,
|
||||
label: field.label,
|
||||
sortable: true,
|
||||
selectable: true,
|
||||
visible: false
|
||||
});
|
||||
});
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
return {
|
||||
fields: fields
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
langAlbumArt () {
|
||||
return this.$gettext('Album Art');
|
||||
},
|
||||
langPlayPause () {
|
||||
return this.$gettext('Play/Pause');
|
||||
},
|
||||
langDownload () {
|
||||
return this.$gettext('Download');
|
||||
}
|
||||
}
|
||||
};
|
||||
let fields = [
|
||||
{key: 'download_url', label: ' '},
|
||||
{key: 'media_art', label: $gettext('Art')},
|
||||
{key: 'media_title', label: $gettext('Title'), sortable: true, selectable: true},
|
||||
{key: 'media_artist', label: $gettext('Artist'), sortable: true, selectable: true},
|
||||
{key: 'media_album', label: $gettext('Album'), sortable: true, selectable: true, visible: false},
|
||||
{key: 'playlist', label: $gettext('Playlist'), sortable: true, selectable: true, visible: false}
|
||||
];
|
||||
|
||||
forEach(props.customFields.slice(), (field) => {
|
||||
fields.push({
|
||||
key: field.display_key,
|
||||
label: field.label,
|
||||
sortable: true,
|
||||
selectable: true,
|
||||
visible: false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -39,15 +39,12 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import Schedule from '~/components/Common/ScheduleView';
|
||||
|
||||
export default {
|
||||
components: { Schedule },
|
||||
props: {
|
||||
scheduleUrl: String,
|
||||
stationName: String,
|
||||
stationTimeZone: String
|
||||
},
|
||||
};
|
||||
const props = defineProps({
|
||||
scheduleUrl: String,
|
||||
stationName: String,
|
||||
stationTimeZone: String
|
||||
});
|
||||
</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>
|
|
@ -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>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import DataTable from '~/components/Common/DataTable';
|
||||
import EditModal from './HlsStreams/EditModal';
|
||||
import Icon from '~/components/Common/Icon';
|
||||
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 {
|
||||
name: 'StationHlsStreams',
|
||||
components: {InfoCard, Icon, EditModal, DataTable},
|
||||
mixins: [StationMayNeedRestart],
|
||||
props: {
|
||||
listUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fields: [
|
||||
{key: 'name', isRowHeader: true, label: this.$gettext('Name'), sortable: true},
|
||||
{key: 'format', label: this.$gettext('Format'), sortable: true},
|
||||
{key: 'bitrate', label: this.$gettext('Bitrate'), sortable: true},
|
||||
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
upper(data) {
|
||||
let upper = [];
|
||||
data.split(' ').forEach((word) => {
|
||||
upper.push(word.toUpperCase());
|
||||
const props = defineProps({
|
||||
...mayNeedRestartProps,
|
||||
listUrl: String
|
||||
});
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const fields = [
|
||||
{key: 'name', isRowHeader: true, label: $gettext('Name'), sortable: true},
|
||||
{key: 'format', label: $gettext('Format'), sortable: true},
|
||||
{key: 'bitrate', label: $gettext('Bitrate'), sortable: true},
|
||||
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
|
||||
];
|
||||
|
||||
const upper = (data) => {
|
||||
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 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>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<b-overlay variant="card" :show="loading">
|
||||
<div class="card-body">
|
||||
<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"
|
||||
:input-attrs="{class: 'text-preformatted mb-3', spellcheck: 'false', 'max-rows': 20, rows: 5}">
|
||||
</b-wrapped-form-group>
|
||||
|
@ -37,7 +37,7 @@
|
|||
</b-form-markup>
|
||||
</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') }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
@ -46,83 +46,78 @@
|
|||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
<script setup>
|
||||
import BFormFieldset from "~/components/Form/BFormFieldset";
|
||||
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
|
||||
import BFormMarkup from "~/components/Form/BFormMarkup";
|
||||
import _ from "lodash";
|
||||
import {forEach} from "lodash";
|
||||
import mergeExisting from "~/functions/mergeExisting";
|
||||
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 {
|
||||
name: 'StationsLiquidsoapConfig',
|
||||
components: {InfoCard, BFormFieldset, BWrappedFormGroup, BFormMarkup},
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
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();
|
||||
const props = defineProps({
|
||||
...mayNeedRestartProps,
|
||||
settingsUrl: String,
|
||||
config: Array,
|
||||
sections: Array,
|
||||
});
|
||||
|
||||
this.loading = true;
|
||||
this.axios.get(this.settingsUrl).then((resp) => {
|
||||
this.form = mergeExisting(this.form, resp.data);
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
submit() {
|
||||
this.v$.$touch();
|
||||
if (this.v$.$errors.length > 0) {
|
||||
return;
|
||||
}
|
||||
const buildForm = () => {
|
||||
let validations = {};
|
||||
let blankForm = {};
|
||||
|
||||
this.$wrapWithLoading(
|
||||
this.axios({
|
||||
method: 'PUT',
|
||||
url: this.settingsUrl,
|
||||
data: this.form
|
||||
})
|
||||
).then(() => {
|
||||
this.$notifySuccess();
|
||||
forEach(props.sections, (section) => {
|
||||
validations[section] = {};
|
||||
blankForm[section] = null;
|
||||
});
|
||||
|
||||
this.mayNeedRestart();
|
||||
this.relist();
|
||||
});
|
||||
}
|
||||
return {validations, blankForm};
|
||||
}
|
||||
|
||||
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>
|
||||
|
|
|
@ -54,72 +54,87 @@
|
|||
</data-table>
|
||||
</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"
|
||||
@relist="relist" @needs-restart="mayNeedRestart"></edit-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import DataTable from '~/components/Common/DataTable';
|
||||
import EditModal from './Mounts/EditModal';
|
||||
import Icon from '~/components/Common/Icon';
|
||||
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 {
|
||||
name: 'StationMounts',
|
||||
components: {InfoCard, Icon, EditModal, DataTable},
|
||||
mixins: [StationMayNeedRestart],
|
||||
props: {
|
||||
listUrl: String,
|
||||
newIntroUrl: String,
|
||||
stationFrontendType: String,
|
||||
showAdvanced: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
const props = defineProps({
|
||||
...mayNeedRestartProps,
|
||||
listUrl: String,
|
||||
newIntroUrl: String,
|
||||
stationFrontendType: String,
|
||||
showAdvanced: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
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) {
|
||||
let upper = [];
|
||||
data.split(' ').forEach((word) => {
|
||||
upper.push(word.toUpperCase());
|
||||
});
|
||||
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 Mount Point?'),
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
this.$wrapWithLoading(
|
||||
this.axios.delete(url)
|
||||
).then((resp) => {
|
||||
this.$notifySuccess(resp.data.message);
|
||||
this.needsRestart();
|
||||
this.relist();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const fields = [
|
||||
{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'}
|
||||
];
|
||||
|
||||
const upper = (data) => {
|
||||
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 {needsRestart, mayNeedRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||
|
||||
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>
|
||||
|
||||
|
|
|
@ -48,71 +48,83 @@
|
|||
</data-table>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import DataTable from '~/components/Common/DataTable';
|
||||
import EditModal from './Mounts/EditModal';
|
||||
import Icon from '~/components/Common/Icon';
|
||||
import InfoCard from '~/components/Common/InfoCard';
|
||||
import RemoteEditModal from "./Remotes/EditModal";
|
||||
import StationMayNeedRestart from '~/components/Stations/Common/MayNeedRestart.vue';
|
||||
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 {
|
||||
name: 'StationMounts',
|
||||
components: {RemoteEditModal, InfoCard, Icon, EditModal, DataTable},
|
||||
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 '';
|
||||
}
|
||||
const props = defineProps({
|
||||
...mayNeedRestartProps,
|
||||
listUrl: String,
|
||||
});
|
||||
|
||||
let upper = [];
|
||||
data.split(' ').forEach((word) => {
|
||||
upper.push(word.toUpperCase());
|
||||
});
|
||||
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 Remote Relay?'),
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
this.$wrapWithLoading(
|
||||
this.axios.delete(url)
|
||||
).then((resp) => {
|
||||
this.$notifySuccess(resp.data.message);
|
||||
this.needsRestart();
|
||||
this.relist();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const fields = [
|
||||
{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'}
|
||||
];
|
||||
|
||||
const upper = (data) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
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>
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
}}
|
||||
</template>
|
||||
|
||||
<flow-upload :target-url="apiUrl" :valid-mime-types="acceptMimeTypes"
|
||||
<flow-upload :target-url="apiUrl" :valid-mime-types="['text/plain']"
|
||||
@success="onFileSuccess"></flow-upload>
|
||||
</b-form-group>
|
||||
|
||||
|
@ -56,42 +56,42 @@
|
|||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import FlowUpload from '~/components/Common/FlowUpload';
|
||||
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 {
|
||||
name: 'StationsStereoToolConfiguration',
|
||||
components: {InfoCard, FlowUpload},
|
||||
mixins: [StationMayNeedRestart],
|
||||
props: {
|
||||
recordHasStereoToolConfiguration: Boolean,
|
||||
apiUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasStereoToolConfiguration: this.recordHasStereoToolConfiguration,
|
||||
acceptMimeTypes: ['text/plain']
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onFileSuccess() {
|
||||
this.mayNeedRestart();
|
||||
this.hasStereoToolConfiguration = true;
|
||||
},
|
||||
deleteConfigurationFile() {
|
||||
this.$wrapWithLoading(
|
||||
this.axios({
|
||||
method: 'DELETE',
|
||||
url: this.apiUrl
|
||||
})
|
||||
).then(() => {
|
||||
this.mayNeedRestart();
|
||||
this.hasStereoToolConfiguration = false;
|
||||
this.$notifySuccess();
|
||||
});
|
||||
},
|
||||
}
|
||||
const props = defineProps({
|
||||
...mayNeedRestartProps,
|
||||
recordHasStereoToolConfiguration: Boolean,
|
||||
apiUrl: String
|
||||
});
|
||||
|
||||
const hasStereoToolConfiguration = ref(props.recordHasStereoToolConfiguration);
|
||||
|
||||
const {mayNeedRestart} = useMayNeedRestart(props.restartStatusUrl);
|
||||
|
||||
const onFileSuccess = () => {
|
||||
mayNeedRestart();
|
||||
hasStereoToolConfiguration.value = true;
|
||||
};
|
||||
|
||||
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||
const {axios} = useAxios();
|
||||
|
||||
const deleteConfigurationFile = () => {
|
||||
wrapWithLoading(
|
||||
axios({
|
||||
method: 'DELETE',
|
||||
url: this.apiUrl
|
||||
})
|
||||
).then(() => {
|
||||
mayNeedRestart();
|
||||
hasStereoToolConfiguration.value = false;
|
||||
notifySuccess();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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