Implement common methods across CRUD components.

This commit is contained in:
Buster Neece 2023-01-08 19:24:13 -06:00
parent bace4da082
commit 6a85fab94c
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
26 changed files with 963 additions and 1125 deletions

View File

@ -30,7 +30,7 @@
<data-table
id="api_keys"
ref="$dataTable"
ref="$datatable"
:fields="fields"
:api-url="apiUrl"
>
@ -52,12 +52,11 @@
<script setup>
import DataTable from "~/components/Common/DataTable.vue";
import {ref} from "vue";
import {useSweetAlert} from "~/vendor/sweetalert";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import {useTranslate} from "~/vendor/gettext";
import InfoCard from "~/components/Common/InfoCard.vue";
import Icon from "~/components/Common/Icon.vue";
import confirmAndDelete from "~/functions/confirmAndDelete";
import useHasDatatable from "~/functions/useHasDatatable";
defineProps({
apiUrl: {
@ -92,28 +91,12 @@ const fields = ref([
}
]);
const $dataTable = ref();
const $datatable = ref();
const {relist} = useHasDatatable($datatable);
const relist = () => {
$dataTable.value.relist();
};
const {confirmDelete} = useSweetAlert();
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete API Key?'),
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
}
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete API Key?'),
relist
);
</script>

View File

@ -14,7 +14,7 @@
</div>
</div>
<data-table
ref="$dataTable"
ref="$datatable"
responsive
paginated
:fields="fields"
@ -118,6 +118,7 @@ import {useAzuraCast} from "~/vendor/azuracast";
import DataTable from "~/components/Common/DataTable.vue";
import DateRangeDropdown from "~/components/Common/DateRangeDropdown.vue";
import Icon from "~/components/Common/Icon.vue";
import useHasDatatable from "~/functions/useHasDatatable";
const props = defineProps({
baseApiUrl: {
@ -164,11 +165,8 @@ const apiUrl = computed(() => {
return apiUrl.toString();
});
const $dataTable = ref(); // DataTable Template Ref
const relist = () => {
$dataTable.value.relist();
};
const $datatable = ref(); // DataTable Template Ref
const {relist} = useHasDatatable($datatable);
</script>
<style lang="scss">

View File

@ -108,7 +108,7 @@
<data-table
id="api_keys"
ref="$dataTable"
ref="$datatable"
:fields="fields"
:api-url="listUrl"
>
@ -169,7 +169,7 @@ import {onMounted, ref} from "vue";
import {useTranslate} from "~/vendor/gettext";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import {useSweetAlert} from "~/vendor/sweetalert";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
listUrl: {
@ -238,9 +238,9 @@ const fields = [
}
];
const $dataTable = ref(); // DataTable
const $datatable = ref(); // DataTable
const {wrapWithLoading, notifySuccess} = useNotify();
const {wrapWithLoading} = useNotify();
const {axios} = useAxios();
const relist = () => {
@ -256,7 +256,7 @@ const relist = () => {
settingsLoading.value = false;
});
$dataTable.value.relist();
$datatable.value.relist();
};
onMounted(relist);
@ -280,20 +280,9 @@ const doRunBackup = () => {
$runBackupModal.value.open();
};
const {confirmDelete} = useSweetAlert();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete Backup?')
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Backup?'),
relist,
);
</script>

View File

@ -71,9 +71,9 @@ import InfoCard from '~/components/Common/InfoCard.vue';
import {get} from 'lodash';
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import {useSweetAlert} from "~/vendor/sweetalert";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
listUrl: {
@ -112,37 +112,14 @@ const fields = [
];
const $dataTable = ref(); // DataTable
const relist = () => {
$dataTable.value.refresh();
};
const {relist} = useHasDatatable($dataTable);
const $editModal = ref(); // EditModal
const {doCreate, doEdit} = useHasEditModal($editModal);
const doCreate = () => {
$editModal.value.create();
}
const doEdit = (url) => {
$editModal.value.edit(url);
}
const {confirmDelete} = useSweetAlert();
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete Custom Field?')
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Custom Field?'),
relist
);
</script>

View File

@ -88,9 +88,9 @@ import InfoCard from '~/components/Common/InfoCard';
import {filter, get, map} from 'lodash';
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import {useSweetAlert} from "~/vendor/sweetalert";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
listUrl: {
@ -136,37 +136,14 @@ const getStationName = (stationId) => {
};
const $datatable = ref(); // Template Ref
const relist = () => {
$datatable.value.refresh();
};
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const {doCreate, doEdit} = useHasEditModal($editModal);
const doCreate = () => {
$editModal.value.create();
};
const doEdit = (url) => {
$editModal.value.edit(url);
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {confirmDelete} = useSweetAlert();
const {axios} = useAxios();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete Role?'),
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
}
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Role?'),
relist
);
</script>

View File

@ -88,9 +88,9 @@ import stationFormProps from "./Stations/stationFormProps";
import {pickProps} from "~/functions/pickProps";
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import {useSweetAlert} from "~/vendor/sweetalert";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
...stationFormProps,
@ -142,20 +142,10 @@ const fields = [
];
const $datatable = ref(); // Template Ref
const relist = () => {
$datatable.value.refresh();
};
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const doCreate = () => {
$editModal.value.create();
};
const doEdit = (url) => {
$editModal.value.edit(url);
};
const {doCreate, doEdit} = useHasEditModal($editModal);
const $cloneModal = ref(); // Template Ref
@ -163,22 +153,9 @@ const doClone = (stationName, url) => {
$cloneModal.value.create(stationName, url);
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {confirmDelete} = useSweetAlert();
const {axios} = useAxios();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete Station?'),
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Station?'),
relist
);
</script>

View File

@ -100,9 +100,9 @@ import EditModal from './StorageLocations/EditModal';
import Icon from '~/components/Common/Icon';
import {computed, ref} from "vue";
import {useTranslate} from "~/vendor/gettext";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import {useSweetAlert} from "~/vendor/sweetalert";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
listUrl: {
@ -146,20 +146,10 @@ const tabs = [
];
const $datatable = ref(); // Template Ref
const relist = () => {
$datatable.value?.refresh();
};
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const doCreate = () => {
$editModal.value?.create();
};
const doEdit = (url) => {
$editModal.value?.edit(url);
};
const {doCreate, doEdit} = useHasEditModal($editModal);
const setType = (type) => {
activeType.value = type;
@ -198,22 +188,9 @@ const getProgressVariant = (percent) => {
}
};
const {notifySuccess, wrapWithLoading} = useNotify();
const {confirmDelete} = useSweetAlert();
const {axios} = useAxios();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete Storage Location?'),
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Storage Location?'),
relist
);
</script>

View File

@ -92,9 +92,9 @@ import EditModal from './Users/EditModal';
import Icon from '~/components/Common/Icon';
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useSweetAlert} from "~/vendor/sweetalert";
import {useAxios} from "~/vendor/axios";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
listUrl: {
@ -116,38 +116,14 @@ const fields = [
];
const $datatable = ref(); // Template Ref
const relist = () => {
$datatable.value.refresh();
};
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const {doCreate, doEdit} = useHasEditModal($editModal);
const doCreate = () => {
$editModal.value.create();
};
const doEdit = (url) => {
$editModal.value.edit(url);
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {confirmDelete} = useSweetAlert();
const {axios} = useAxios();
const doDelete = (url) => {
confirmDelete({
title: $gettext('Delete User?'),
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
}
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete User?'),
relist
);
</script>

View File

@ -1,79 +1,76 @@
<template>
<modal-form
ref="modal"
ref="$modal"
:loading="loading"
:title="langTitle"
:error="error"
:disable-save-button="v$.form.$invalid"
:disable-save-button="v$.$invalid"
@submit="doSubmit"
@hidden="clearContents"
>
<admin-users-form
:form="v$.form"
:form="v$"
:roles="roles"
:is-edit-mode="isEditMode"
/>
</modal-form>
</template>
<script>
import useVuelidate from "@vuelidate/core";
<script setup>
import {email, required} from '@vuelidate/validators';
import BaseEditModal from '~/components/Common/BaseEditModal';
import AdminUsersForm from './Form.vue';
import {map} from 'lodash';
import validatePassword from "~/functions/validatePassword";
import {computed, ref} from "vue";
import {baseEditModalProps, useBaseEditModal} from "~/functions/useBaseEditModal";
import {useTranslate} from "~/vendor/gettext";
import ModalForm from "~/components/Common/ModalForm.vue";
/* TODO Options API */
const props = defineProps({
...baseEditModalProps,
roles: {
type: Object,
required: true
}
});
export default {
name: 'AdminUsersEditModal',
components: {AdminUsersForm},
mixins: [BaseEditModal],
props: {
roles: {
type: Object,
required: true
const emit = defineEmits(['relist']);
const $modal = ref(); // Template Ref
const {
loading,
error,
isEditMode,
v$,
clearContents,
create,
edit,
doSubmit,
close
} = useBaseEditModal(
props,
emit,
$modal,
(formRef, formIsEditMode) => computed(() => {
return {
name: {},
new_password: (formIsEditMode.value)
? {validatePassword}
: {required, validatePassword},
email: {required, email},
roles: {}
}
}),
{
name: '',
email: '',
new_password: '',
roles: [],
},
setup() {
return {v$: useVuelidate()}
},
computed: {
langTitle() {
return this.isEditMode
? this.$gettext('Edit User')
: this.$gettext('Add User');
}
},
validations() {
let validations = {
form: {
name: {},
email: {required, email},
roles: {}
}
};
if (this.isEditMode) {
validations.form.new_password = {validatePassword};
} else {
validations.form.new_password = {required, validatePassword};
}
return validations;
},
methods: {
resetForm() {
this.form = {
name: '',
email: '',
new_password: '',
roles: [],
};
},
populateForm(data) {
this.form = {
{
populateForm: (data, formRef) => {
formRef.value = {
name: data.name,
email: data.email,
new_password: '',
@ -81,5 +78,19 @@ export default {
};
},
}
};
);
const {$gettext} = useTranslate();
const langTitle = computed(() => {
return isEditMode.value
? $gettext('Edit User')
: $gettext('Add User');
});
defineExpose({
create,
edit,
close
});
</script>

View File

@ -14,7 +14,7 @@
<textarea
id="log-view-contents"
ref="textarea"
ref="$textarea"
class="form-control log-viewer"
spellcheck="false"
readonly
@ -23,84 +23,85 @@
</b-overlay>
</template>
<script>
<script setup>
import {nextTick, onMounted, ref} from "vue";
import {useAxios} from "~/vendor/axios";
import {useTimeoutFn} from "@vueuse/core";
/* TODO Options API */
const props = defineProps({
logUrl: {
type: String,
required: true,
}
});
export default {
name: 'StreamingLogView',
props: {
logUrl: {
type: String,
required: true,
}
},
data() {
return {
loading: false,
logs: '',
currentLogPosition: null,
timeoutUpdateLog: null,
scrollToBottom: true,
};
},
mounted() {
this.loading = true;
const loading = ref(false);
const logs = ref('');
const currentLogPosition = ref(null);
const scrollToBottom = ref(true);
this.axios({
method: 'GET',
url: this.logUrl
}).then((resp) => {
if (resp.data.contents !== '') {
this.logs = resp.data.contents + "\n";
this.scrollTextarea();
} else {
this.logs = '';
}
const {axios} = useAxios();
this.currentLogPosition = resp.data.position;
const $textarea = ref(); // Template Ref
if (!resp.data.eof) {
this.timeoutUpdateLog = setTimeout(this.updateLogs, 2500);
}
}).finally(() => {
this.loading = false;
const scrollTextarea = () => {
if (scrollToBottom.value) {
nextTick(() => {
$textarea.value.scrollTop = $textarea.value.scrollHeight;
});
},
beforeUnmount() {
clearTimeout(this.timeoutUpdateLog);
},
methods: {
updateLogs() {
this.axios({
method: 'GET',
url: this.logUrl,
params: {
position: this.currentLogPosition
}
}).then((resp) => {
if (resp.data.contents !== '') {
this.logs = this.logs + resp.data.contents + "\n";
this.scrollTextarea();
}
this.currentLogPosition = resp.data.position;
if (!resp.data.eof) {
this.timeoutUpdateLog = setTimeout(this.updateLogs, 2500);
}
});
},
getContents() {
return this.logs;
},
scrollTextarea() {
if (this.scrollToBottom) {
this.$nextTick(() => {
const textarea = this.$refs.textarea;
textarea.scrollTop = textarea.scrollHeight;
});
}
}
}
};
const updateLogs = () => {
axios({
method: 'GET',
url: props.logUrl,
params: {
position: currentLogPosition.value
}
}).then((resp) => {
if (resp.data.contents !== '') {
logs.value = logs.value + resp.data.contents + "\n";
scrollTextarea();
}
currentLogPosition.value = resp.data.position;
if (!resp.data.eof) {
useTimeoutFn(updateLogs, 2500);
}
});
};
onMounted(() => {
loading.value = true;
axios({
method: 'GET',
url: props.logUrl
}).then((resp) => {
if (resp.data.contents !== '') {
logs.value = resp.data.contents + "\n";
scrollTextarea();
} else {
logs.value = '';
}
currentLogPosition.value = resp.data.position;
if (!resp.data.eof) {
useTimeoutFn(updateLogs, 2500);
}
}).finally(() => {
loading.value = false;
});
});
const getContents = () => {
return logs.value;
};
defineExpose({
getContents
});
</script>

View File

@ -84,7 +84,7 @@
<b-modal
id="import_modal"
ref="modal"
ref="$modal"
:title="$gettext('Import Results')"
>
<div>
@ -154,47 +154,49 @@
</div>
</template>
<script>
<script setup>
import {ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
/* TODO Options API */
export default {
name: 'StationsBulkMedia',
props: {
apiUrl: {
type: String,
required: true
}
},
data() {
return {
importFile: null,
importResults: {},
};
},
methods: {
doSubmit() {
let formData = new FormData();
formData.append('import_file', this.importFile);
this.$wrapWithLoading(
this.axios.post(this.apiUrl, formData)
).then((resp) => {
this.importFile = null;
if (resp.data.success) {
this.importResults = resp.data;
this.$notifySuccess(resp.data.message);
this.$refs.modal.show();
} else {
this.$notifyError(resp.data.message);
this.close();
}
});
},
closeModal() {
this.$refs.modal.hide();
}
const props = defineProps({
apiUrl: {
type: String,
required: true
}
});
const importFile = ref(null);
const importResults = ref(null);
const {wrapWithLoading, notifySuccess, notifyError} = useNotify();
const {axios} = useAxios();
const $modal = ref(); // Template Ref
const close = () => {
$modal.value.hide();
};
const doSubmit = () => {
let formData = new FormData();
formData.append('import_file', importFile.value);
wrapWithLoading(
axios.post(props.apiUrl, formData)
).then((resp) => {
importFile.value = null;
if (resp.data.success) {
importResults.value = resp.data;
notifySuccess(resp.data.message);
$modal.value.show();
} else {
notifyError(resp.data.message);
close();
}
});
};
</script>

View File

@ -79,9 +79,9 @@ import InfoCard from '~/components/Common/InfoCard';
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import {mayNeedRestartProps, useMayNeedRestart} from "~/functions/useMayNeedRestart";
import {useSweetAlert} from "~/vendor/sweetalert";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
...mayNeedRestartProps,
@ -109,39 +109,19 @@ const upper = (data) => {
};
const $dataTable = ref(); // DataTable
const relist = () => {
$dataTable.value?.refresh();
};
const {relist} = useHasDatatable($dataTable);
const $editModal = ref(); // EditModal
const doCreate = () => {
$editModal.value?.create();
};
const doEdit = (url) => {
$editModal.value?.edit(url);
};
const {doCreate, doEdit} = useHasEditModal($editModal);
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();
});
}
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete HLS Stream?'),
() => {
needsRestart();
relist();
}
);
</script>

View File

@ -12,65 +12,76 @@
content-class="mt-3"
pills
>
<form-basic-info :form="v$" />
<form-basic-info :form="v$"/>
</b-tabs>
</modal-form>
</template>
<script>
<script setup>
import {required} from '@vuelidate/validators';
import BaseEditModal from '~/components/Common/BaseEditModal';
import FormBasicInfo from './Form/BasicInfo';
import mergeExisting from "~/functions/mergeExisting";
import {useVuelidateOnForm} from "~/functions/useVuelidateOnForm";
import {baseEditModalProps, useBaseEditModal} from "~/functions/useBaseEditModal";
import {computed, ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useTranslate} from "~/vendor/gettext";
import ModalForm from "~/components/Common/ModalForm.vue";
/* TODO Options API */
const props = defineProps({
...baseEditModalProps,
});
export default {
name: 'EditModal',
components: {FormBasicInfo},
mixins: [BaseEditModal],
emits: ['relist', 'needs-restart'],
setup() {
const {form, resetForm, v$} = useVuelidateOnForm(
{
name: {required},
format: {required},
bitrate: {required}
},
{
name: null,
format: 'aac',
bitrate: 128
}
);
const emit = defineEmits(['relist', 'needs-restart']);
return {
form,
resetForm,
v$
}
const $modal = ref(); // Template Ref
const {notifySuccess} = useNotify();
const {
loading,
error,
isEditMode,
form,
v$,
clearContents,
create,
edit,
doSubmit,
close
} = useBaseEditModal(
props,
emit,
$modal,
{
name: {required},
format: {required},
bitrate: {required}
},
computed: {
langTitle() {
return this.isEditMode
? this.$gettext('Edit HLS Stream')
: this.$gettext('Add HLS Stream');
}
{
name: null,
format: 'aac',
bitrate: 128
},
methods: {
populateForm(d) {
this.record = d;
this.form = mergeExisting(this.form, d);
},
onSubmitSuccess() {
this.$notifySuccess();
this.$emit('needs-restart');
this.$emit('relist');
this.close();
{
onSubmitSuccess: () => {
notifySuccess();
emit('relist');
emit('needs-restart');
close();
},
}
};
);
const {$gettext} = useTranslate();
const langTitle = computed(() => {
return isEditMode.value
? $gettext('Edit HLS Stream')
: $gettext('Add HLS Stream');
});
defineExpose({
create,
edit,
close
});
</script>

View File

@ -69,10 +69,6 @@ const props = defineProps({
form: {
type: Object,
required: true
},
stationFrontendType: {
type: String,
required: true
}
});

View File

@ -1,7 +1,7 @@
<template>
<b-modal
id="move_file"
ref="modal"
ref="$modal"
size="xl"
centered
:title="langHeader"
@ -12,7 +12,7 @@
size="sm"
variant="primary"
:disabled="dirHistory.length === 0"
@click="pageBack"
@click.prevent="pageBack"
>
<icon icon="chevron_left" />
{{ $gettext('Back') }}
@ -31,7 +31,7 @@
<b-col md="12">
<data-table
id="station_media"
ref="datatable"
ref="$datatable"
:show-toolbar="false"
:selectable="false"
:fields="fields"
@ -71,108 +71,113 @@
</template>
</b-modal>
</template>
<script>
<script setup>
import DataTable from '~/components/Common/DataTable.vue';
import {forEach} from 'lodash';
import Icon from '~/components/Common/Icon';
import {computed, h, ref} from "vue";
import {useTranslate} from "~/vendor/gettext";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
/* TODO Options API */
export default {
name: 'MoveFilesModal',
components: {Icon, DataTable},
props: {
selectedItems: {
type: Object,
required: true
},
currentDirectory: {
type: String,
required: true
},
batchUrl: {
type: String,
required: true
},
listDirectoriesUrl: {
type: String,
required: true
}
const props = defineProps({
selectedItems: {
type: Object,
required: true
},
emits: ['relist'],
data() {
return {
destinationDirectory: '',
dirHistory: [],
fields: [
{key: 'directory', label: this.$gettext('Directory'), sortable: false}
]
};
currentDirectory: {
type: String,
required: true
},
computed: {
langHeader () {
return this.$gettext(
'Move %{ num } File(s) to',
{num: this.selectedItems.all.length}
);
}
batchUrl: {
type: String,
required: true
},
methods: {
close () {
this.dirHistory = [];
this.destinationDirectory = '';
this.$refs.modal.hide();
},
doMove () {
(this.selectedItems.all.length) && this.$wrapWithLoading(
this.axios.put(this.batchUrl, {
'do': 'move',
'currentDirectory': this.currentDirectory,
'directory': this.destinationDirectory,
'files': this.selectedItems.files,
'dirs': this.selectedItems.directories
})
).then(() => {
let notifyMessage = this.$gettext('Files moved:');
let itemNameNodes = [];
forEach(this.selectedItems.all, (item) => {
itemNameNodes.push(this.$createElement('div', {}, item.name));
});
this.$notifySuccess(itemNameNodes, {
title: notifyMessage
});
}).finally(() => {
this.close();
this.$emit('relist');
});
},
enterDirectory (path) {
this.dirHistory.push(path);
this.destinationDirectory = path;
this.$refs.datatable.refresh();
},
pageBack: function (e) {
e.preventDefault();
this.dirHistory.pop();
let newDirectory = this.dirHistory.slice(-1)[0];
if (typeof newDirectory === 'undefined' || null === newDirectory) {
newDirectory = '';
}
this.destinationDirectory = newDirectory;
this.$refs.datatable.refresh();
},
requestConfig (config) {
config.params.currentDirectory = this.destinationDirectory;
config.params.csrf = this.csrf;
return config;
}
listDirectoriesUrl: {
type: String,
required: true
}
});
const emit = defineEmits(['relist']);
const destinationDirectory = ref('');
const dirHistory = ref([]);
const {$gettext} = useTranslate();
const fields = [
{key: 'directory', label: $gettext('Directory'), sortable: false}
];
const langHeader = computed(() => {
return $gettext(
'Move %{ num } File(s) to',
{num: props.selectedItems.all.length}
);
});
const $modal = ref(); // Template Ref
const close = () => {
dirHistory.value = [];
destinationDirectory.value = '';
$modal.value.hide();
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doMove = () => {
(props.selectedItems.all.length) && wrapWithLoading(
axios.put(props.batchUrl, {
'do': 'move',
'currentDirectory': props.currentDirectory,
'directory': destinationDirectory.value,
'files': props.selectedItems.files,
'dirs': props.selectedItems.directories
})
).then(() => {
let notifyMessage = $gettext('Files moved:');
let itemNameNodes = [];
forEach(props.selectedItems.all, (item) => {
itemNameNodes.push(h('div', {}, item.path_short));
});
notifySuccess(itemNameNodes, {
title: notifyMessage
});
}).finally(() => {
close();
emit('relist');
});
};
const $datatable = ref(); // Template Ref
const enterDirectory = (path) => {
dirHistory.value.push(path);
destinationDirectory.value = path;
$datatable.value.refresh();
};
const pageBack = () => {
dirHistory.value.pop();
let newDirectory = dirHistory.value.slice(-1)[0];
if (typeof newDirectory === 'undefined' || null === newDirectory) {
newDirectory = '';
}
destinationDirectory.value = newDirectory;
$datatable.value.refresh();
};
const requestConfig = (config) => {
config.params.currentDirectory = destinationDirectory.value;
return config;
};
</script>

View File

@ -90,10 +90,10 @@ import InfoCard from '~/components/Common/InfoCard';
import {mayNeedRestartProps, useMayNeedRestart} from "~/functions/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";
import showFormatAndBitrate from "~/functions/showFormatAndBitrate";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
...mayNeedRestartProps,
@ -124,42 +124,20 @@ const fields = [
];
const $dataTable = ref(); // DataTable
const relist = () => {
$dataTable.value.refresh();
};
const {relist} = useHasDatatable($dataTable);
const $editModal = ref(); // EditModal
const doCreate = () => {
$editModal.value.create();
};
const doEdit = (url) => {
$editModal.value.edit(url);
};
const {doCreate, doEdit} = useHasEditModal($editModal);
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();
});
}
});
}
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Mount Point?'),
() => {
needsRestart();
relist();
}
);
</script>

View File

@ -41,7 +41,7 @@
<data-table
id="station_playlists"
ref="datatable"
ref="$datatable"
paginated
:fields="fields"
:responsive="false"
@ -185,7 +185,7 @@
no-body
>
<schedule
ref="schedule"
ref="$schedule"
:schedule-url="scheduleUrl"
:station-time-zone="stationTimeZone"
@click="doCalendarClick"
@ -195,28 +195,28 @@
</b-card>
<edit-modal
ref="editModal"
ref="$editModal"
:create-url="listUrl"
:station-time-zone="stationTimeZone"
:enable-advanced-features="enableAdvancedFeatures"
@relist="relist"
@needs-restart="mayNeedRestart"
/>
<reorder-modal ref="reorderModal" />
<queue-modal ref="queueModal" />
<reorder-modal ref="reorderModal" />
<reorder-modal ref="$reorderModal" />
<queue-modal ref="$queueModal" />
<reorder-modal ref="$reorderModal" />
<import-modal
ref="importModal"
ref="$importModal"
@relist="relist"
/>
<clone-modal
ref="cloneModal"
ref="$cloneModal"
@relist="relist"
@needs-restart="mayNeedRestart"
/>
</template>
<script>
<script setup>
import DataTable from '~/components/Common/DataTable';
import Schedule from '~/components/Common/ScheduleView';
import EditModal from './Playlists/EditModal';
@ -225,181 +225,182 @@ import ImportModal from './Playlists/ImportModal';
import QueueModal from './Playlists/QueueModal';
import Icon from '~/components/Common/Icon';
import CloneModal from './Playlists/CloneModal';
import {DateTime} from 'luxon';
import humanizeDuration from 'humanize-duration';
import {useAzuraCast} from "~/vendor/azuracast";
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import useHasEditModal from "~/functions/useHasEditModal";
import {mayNeedRestartProps, useMayNeedRestart} from "~/functions/useMayNeedRestart";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import confirmAndDelete from "~/functions/confirmAndDelete";
/* TODO Options API */
export default {
name: 'StationPlaylists',
components: {CloneModal, Icon, QueueModal, ImportModal, ReorderModal, EditModal, Schedule, DataTable},
props: {
listUrl: {
type: String,
required: true
},
scheduleUrl: {
type: String,
required: true
},
filesUrl: {
type: String,
required: true
},
restartStatusUrl: {
type: String,
required: true
},
stationTimeZone: {
type: String,
required: true
},
useManualAutoDj: {
type: Boolean,
required: true
},
enableAdvancedFeatures: {
type: Boolean,
required: true
}
const props = defineProps({
...mayNeedRestartProps,
listUrl: {
type: String,
required: true
},
data () {
return {
fields: [
{key: 'name', isRowHeader: true, label: this.$gettext('Playlist'), sortable: true},
{key: 'scheduling', label: this.$gettext('Scheduling'), sortable: false},
{key: 'num_songs', label: this.$gettext('# Songs'), sortable: false},
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
]
};
scheduleUrl: {
type: String,
required: true
},
methods: {
langToggleButton (record) {
return (record.is_enabled)
? this.$gettext('Disable')
: this.$gettext('Enable');
},
formatTime (time) {
const {timeConfig} = useAzuraCast();
filesUrl: {
type: String,
required: true
},
stationTimeZone: {
type: String,
required: true
},
useManualAutoDj: {
type: Boolean,
required: true
},
enableAdvancedFeatures: {
type: Boolean,
required: true
}
});
return DateTime.fromSeconds(time).setZone(this.stationTimeZone).toLocaleString(
{...DateTime.DATETIME_MED, ...timeConfig}
const {$gettext} = useTranslate();
const fields = [
{key: 'name', isRowHeader: true, label: $gettext('Playlist'), sortable: true},
{key: 'scheduling', label: $gettext('Scheduling'), sortable: false},
{key: 'num_songs', label: $gettext('# Songs'), sortable: false},
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
];
const langToggleButton = (record) => {
return (record.is_enabled)
? $gettext('Disable')
: $gettext('Enable');
};
const {localeShort} = useAzuraCast();
const formatLength = (length) => humanizeDuration(
length * 1000,
{
round: true,
language: localeShort,
fallbacks: ['en']
}
);
const formatType = (record) => {
if (!record.is_enabled) {
return $gettext('Disabled');
}
switch (record.type) {
case 'default':
return $gettext('General Rotation') + '<br>' + $gettext('Weight') + ': ' + record.weight;
case 'once_per_x_songs':
return $gettext(
'Once per %{songs} Songs',
{songs: record.play_per_songs}
);
},
formatLength (length) {
const {localeShort} = useAzuraCast();
return humanizeDuration(length * 1000, {
round: true,
language: localeShort,
fallbacks: ['en']
});
},
formatType (record) {
if (!record.is_enabled) {
return this.$gettext('Disabled');
}
case 'once_per_x_minutes':
return $gettext(
'Once per %{minutes} Minutes',
{minutes: record.play_per_minutes}
);
switch (record.type) {
case 'default':
return this.$gettext('General Rotation') + '<br>' + this.$gettext('Weight') + ': ' + record.weight;
case 'once_per_hour':
return $gettext(
'Once per Hour (at %{minute})',
{minute: record.play_per_hour_minute}
);
case 'once_per_x_songs':
return this.$gettext(
'Once per %{songs} Songs',
{songs: record.play_per_songs}
);
case 'once_per_x_minutes':
return this.$gettext(
'Once per %{minutes} Minutes',
{minutes: record.play_per_minutes}
);
case 'once_per_hour':
return this.$gettext(
'Once per Hour (at %{minute})',
{minute: record.play_per_hour_minute}
);
default:
return this.$gettext('Custom');
}
},
relist () {
if (this.$refs.datatable) {
this.$refs.datatable.refresh();
}
if (this.$refs.schedule) {
this.$refs.schedule.refresh();
}
},
doCreate () {
this.$refs.editModal.create();
},
doCalendarClick (event) {
this.doEdit(event.extendedProps.edit_url);
},
doEdit (url) {
this.$refs.editModal.edit(url);
},
doReorder (url) {
this.$refs.reorderModal.open(url);
},
doQueue (url) {
this.$refs.queueModal.open(url);
},
doImport (url) {
this.$refs.importModal.open(url);
},
doClone (name, url) {
this.$refs.cloneModal.open(name, url);
},
doModify (url) {
this.$wrapWithLoading(
this.axios.put(url)
).then((resp) => {
this.needsRestart();
this.$notifySuccess(resp.data.message);
this.relist();
});
},
doDelete (url) {
this.$confirmDelete({
title: this.$gettext('Delete Playlist?'),
}).then((result) => {
if (result.value) {
this.$wrapWithLoading(
this.axios.delete(url)
).then((resp) => {
this.needsRestart();
this.$notifySuccess(resp.data.message);
this.relist();
});
}
});
},
mayNeedRestart() {
if (!this.useManualAutoDj) {
return;
}
this.axios.get(this.restartStatusUrl).then((resp) => {
if (resp.data.needs_restart) {
this.needsRestart();
}
});
},
needsRestart() {
if (!this.useManualAutoDj) {
return;
}
document.dispatchEvent(new CustomEvent("station-needs-restart"));
}
default:
return $gettext('Custom');
}
};
const $datatable = ref(); // Template Ref
const $schedule = ref(); // Template Ref
const relist = () => {
$datatable.value?.refresh();
$schedule.value?.refresh();
};
const $editModal = ref(); // Template Ref
const {doCreate, doEdit} = useHasEditModal($editModal);
const doCalendarClick = (event) => {
doEdit(event.extendedProps.edit_url);
};
const $reorderModal = ref(); // Template Ref
const doReorder = (url) => {
$reorderModal.value?.open(url);
};
const $queueModal = ref(); // Template Ref
const doQueue = (url) => {
$queueModal.value?.open(url);
};
const $importModal = ref(); // Template Ref
const doImport = (url) => {
$importModal.value?.open(url);
};
const $cloneModal = ref(); // Template Ref
const doClone = (name, url) => {
$cloneModal.value?.open(name, url);
};
const {
mayNeedRestart: originalMayNeedRestart,
needsRestart: originalNeedsRestart
} = useMayNeedRestart(props.restartStatusUrl);
const mayNeedRestart = () => {
if (!props.useManualAutoDj) {
return;
}
originalMayNeedRestart();
};
const needsRestart = () => {
if (!props.useManualAutoDj) {
return;
}
originalNeedsRestart();
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doModify = (url) => {
wrapWithLoading(
axios.put(url)
).then((resp) => {
needsRestart();
notifySuccess(resp.data.message);
relist();
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Playlist?'),
() => {
relist();
needsRestart();
},
);
</script>

View File

@ -16,7 +16,7 @@
</div>
<data-table
id="station_queue"
ref="datatable"
ref="$datatable"
:fields="fields"
:api-url="listUrl"
>
@ -67,91 +67,87 @@
</data-table>
</b-card>
<queue-logs-modal ref="logs_modal" />
<queue-logs-modal ref="$logsModal" />
</template>
<script>
<script setup>
import DataTable from '../Common/DataTable';
import QueueLogsModal from './Queue/LogsModal';
import Icon from "~/components/Common/Icon";
import {DateTime} from 'luxon';
import {useAzuraCast} from "~/vendor/azuracast";
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import confirmAndDelete from "~/functions/confirmAndDelete";
import useHasDatatable from "~/functions/useHasDatatable";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
/* TODO Options API */
export default {
name: 'StationQueue',
components: {QueueLogsModal, DataTable, Icon},
props: {
listUrl: {
type: String,
required: true
},
clearUrl: {
type: String,
required: true
},
stationTimeZone: {
type: String,
required: true
}
const props = defineProps({
listUrl: {
type: String,
required: true
},
data() {
return {
fields: [
{key: 'actions', label: this.$gettext('Actions'), sortable: false},
{key: 'song_title', isRowHeader: true, label: this.$gettext('Song Title'), sortable: false},
{key: 'played_at', label: this.$gettext('Expected to Play at'), sortable: false},
{key: 'source', label: this.$gettext('Source'), sortable: false}
]
};
clearUrl: {
type: String,
required: true
},
methods: {
formatTime(time) {
const {timeConfig} = useAzuraCast();
return this.getDateTime(time).toLocaleString(
{...DateTime.TIME_WITH_SECONDS, ...timeConfig}
);
},
formatRelativeTime(time) {
return this.getDateTime(time).toRelative();
},
getDateTime(timestamp) {
return DateTime.fromSeconds(timestamp).setZone(this.stationTimeZone);
},
doShowLogs(logs) {
this.$refs.logs_modal.show(logs);
},
doDelete(url) {
this.$confirmDelete({
title: this.$gettext('Delete Queue Item?'),
}).then((result) => {
if (result.value) {
this.$wrapWithLoading(
this.axios.delete(url)
).then((resp) => {
this.$notifySuccess(resp.data.message);
this.$refs.datatable.refresh();
});
}
});
},
doClear() {
this.$confirmDelete({
title: this.$gettext('Clear Upcoming Song Queue?'),
confirmButtonText: this.$gettext('Clear'),
}).then((result) => {
if (result.value) {
this.$wrapWithLoading(
this.axios.post(this.clearUrl)
).then((resp) => {
this.$notifySuccess(resp.data.message);
this.$refs.datatable.refresh();
});
}
});
}
stationTimeZone: {
type: String,
required: true
}
});
const {$gettext} = useTranslate();
const fields = [
{key: 'actions', label: $gettext('Actions'), sortable: false},
{key: 'song_title', isRowHeader: true, label: $gettext('Song Title'), sortable: false},
{key: 'played_at', label: $gettext('Expected to Play at'), sortable: false},
{key: 'source', label: $gettext('Source'), sortable: false}
];
const getDateTime = (timestamp) =>
DateTime.fromSeconds(timestamp).setZone(props.stationTimeZone);
const {timeConfig} = useAzuraCast();
const formatTime = (time) => getDateTime(time).toLocaleString(
{...DateTime.TIME_WITH_SECONDS, ...timeConfig}
);
const formatRelativeTime = (time) => getDateTime(time).toRelative();
const $datatable = ref(); // Template Ref
const {relist} = useHasDatatable($datatable);
const $logsModal = ref(); // Template Ref
const doShowLogs = (logs) => {
$logsModal.value?.show(logs);
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Queue Item?'),
relist
);
const {wrapWithLoading, confirmDelete, notifySuccess} = useNotify();
const {axios} = useAxios();
const doClear = () => {
confirmDelete({
title: $gettext('Clear Upcoming Song Queue?'),
confirmButtonText: $gettext('Clear'),
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.post(props.clearUrl)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
}
});
}
</script>

View File

@ -89,10 +89,10 @@ import '~/vendor/sweetalert';
import {mayNeedRestartProps, useMayNeedRestart} from "~/functions/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";
import showFormatAndBitrate from "~/functions/showFormatAndBitrate";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
const props = defineProps({
...mayNeedRestartProps,
@ -111,40 +111,19 @@ const fields = [
];
const $dataTable = ref(); // DataTable
const relist = () => {
$dataTable.value?.refresh();
};
const {relist} = useHasDatatable($dataTable);
const $editModal = ref(); // EditModal
const doCreate = () => {
$editModal.value?.create();
};
const doEdit = (url) => {
$editModal.value?.edit(url);
};
const {doCreate, doEdit} = useHasEditModal($editModal);
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();
});
}
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Remote Relay?'),
() => {
needsRestart();
relist();
}
);
</script>

View File

@ -20,7 +20,7 @@
<data-table
id="station_remotes"
ref="datatable"
ref="$datatable"
:show-toolbar="false"
:fields="fields"
:api-url="listUrl"
@ -75,65 +75,50 @@
</div>
<sftp-users-edit-modal
ref="editModal"
ref="$editModal"
:create-url="listUrl"
@relist="relist"
/>
</div>
</template>
<script>
<script setup>
import DataTable from "~/components/Common/DataTable";
import SftpUsersEditModal from "./SftpUsers/EditModal";
import Icon from "~/components/Common/Icon";
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
/* TODO Options API */
export default {
name: 'SftpUsers',
components: {Icon, SftpUsersEditModal, DataTable},
props: {
listUrl: {
type: String,
required: true
},
connectionInfo: {
type: Object,
required: true
}
const props = defineProps({
listUrl: {
type: String,
required: true
},
data() {
return {
fields: [
{key: 'username', isRowHeader: true, label: this.$gettext('Username'), sortable: false},
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
]
};
},
methods: {
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 SFTP User?')
}).then((result) => {
if (result.value) {
this.$wrapWithLoading(
this.axios.delete(url)
).then((resp) => {
this.$notifySuccess(resp.data.message);
this.relist();
});
}
});
}
connectionInfo: {
type: Object,
required: true
}
}
});
const {$gettext} = useTranslate();
const fields = [
{key: 'username', isRowHeader: true, label: $gettext('Username'), sortable: false},
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
];
const $datatable = ref(); // Template Ref
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const {doCreate, doEdit} = useHasEditModal($editModal);
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete SFTP User?'),
relist
);
</script>

View File

@ -44,7 +44,7 @@
<data-table
id="station_streamers"
ref="datatable"
ref="$datatable"
:fields="fields"
:api-url="listUrl"
>
@ -94,7 +94,7 @@
no-body
>
<schedule
ref="schedule"
ref="$schedule"
:schedule-url="scheduleUrl"
:station-time-zone="stationTimeZone"
@click="doCalendarClick"
@ -108,17 +108,17 @@
</div>
<edit-modal
ref="editModal"
ref="$editModal"
:create-url="listUrl"
:station-time-zone="stationTimeZone"
:new-art-url="newArtUrl"
@relist="relist"
/>
<broadcasts-modal ref="broadcastsModal" />
<broadcasts-modal ref="$broadcastsModal" />
</div>
</template>
<script>
<script setup>
import DataTable from '~/components/Common/DataTable';
import EditModal from './Streamers/EditModal';
import BroadcastsModal from './Streamers/BroadcastsModal';
@ -126,75 +126,64 @@ import Schedule from '~/components/Common/ScheduleView';
import Icon from '~/components/Common/Icon';
import ConnectionInfo from "./Streamers/ConnectionInfo";
import AlbumArt from "~/components/Common/AlbumArt";
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import confirmAndDelete from "~/functions/confirmAndDelete";
/* TODO Options API */
export default {
name: 'StationStreamers',
components: {AlbumArt, ConnectionInfo, Icon, EditModal, BroadcastsModal, DataTable, Schedule},
props: {
listUrl: {
type: String,
required: true
},
newArtUrl: {
type: String,
required: true
},
scheduleUrl: {
type: String,
required: true
},
stationTimeZone: {
type: String,
required: true
},
connectionInfo: {
type: Object,
required: true
}
const props = defineProps({
listUrl: {
type: String,
required: true
},
data() {
return {
fields: [
{key: 'art', label: this.$gettext('Art'), sortable: false, class: 'shrink pr-0'},
{key: 'display_name', label: this.$gettext('Display Name'), sortable: true},
{key: 'streamer_username', isRowHeader: true, label: this.$gettext('Username'), sortable: true},
{key: 'comments', label: this.$gettext('Notes'), sortable: false},
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
]
};
newArtUrl: {
type: String,
required: true
},
methods: {
relist() {
this.$refs.datatable.refresh();
},
doCreate() {
this.$refs.editModal.create();
},
doCalendarClick(event) {
this.doEdit(event.extendedProps.edit_url);
},
doEdit(url) {
this.$refs.editModal.edit(url);
},
doShowBroadcasts(url) {
this.$refs.broadcastsModal.open(url);
},
doDelete(url) {
this.$confirmDelete({
title: this.$gettext('Delete Streamer?'),
}).then((result) => {
if (result.value) {
this.$wrapWithLoading(
this.axios.delete(url)
).then((resp) => {
this.$notifySuccess(resp.data.message);
this.relist();
});
}
});
}
scheduleUrl: {
type: String,
required: true
},
stationTimeZone: {
type: String,
required: true
},
connectionInfo: {
type: Object,
required: true
}
});
const {$gettext} = useTranslate();
const fields = [
{key: 'art', label: $gettext('Art'), sortable: false, class: 'shrink pr-0'},
{key: 'display_name', label: $gettext('Display Name'), sortable: true},
{key: 'streamer_username', isRowHeader: true, label: $gettext('Username'), sortable: true},
{key: 'comments', label: $gettext('Notes'), sortable: false},
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
];
const $datatable = ref(); // Template Ref
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const {doCreate, doEdit} = useHasEditModal($editModal);
const doCalendarClick = (event) => {
doEdit(event.extendedProps.edit_url);
};
const $broadcastsModal = ref(); // Template Ref
const doShowBroadcasts = (url) => {
$broadcastsModal.value.open(url);
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Streamer?'),
relist
);
</script>

View File

@ -24,7 +24,7 @@
<data-table
id="station_webhooks"
ref="datatable"
ref="$datatable"
:fields="fields"
:api-url="listUrl"
>
@ -84,9 +84,9 @@
</data-table>
</b-card>
<streaming-log-modal ref="logModal" />
<streaming-log-modal ref="$logModal" />
<edit-modal
ref="editModal"
ref="$editModal"
:create-url="listUrl"
:webhook-types="webhookTypes"
:trigger-titles="langTriggerTitles"
@ -96,125 +96,119 @@
/>
</template>
<script>
<script setup>
import DataTable from '~/components/Common/DataTable';
import EditModal from './Webhooks/EditModal';
import Icon from '~/components/Common/Icon';
import InfoCard from "~/components/Common/InfoCard";
import {get, map} from 'lodash';
import StreamingLogModal from "~/components/Common/StreamingLogModal";
import {useTranslate} from "~/vendor/gettext";
import {ref} from "vue";
import useHasDatatable from "~/functions/useHasDatatable";
import useHasEditModal from "~/functions/useHasEditModal";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import confirmAndDelete from "~/functions/confirmAndDelete";
/* TODO Options API */
export default {
name: 'StationWebhooks',
components: {StreamingLogModal, InfoCard, Icon, EditModal, DataTable},
props: {
listUrl: {
type: String,
required: true
},
nowPlayingUrl: {
type: String,
required: true
},
webhookTypes: {
type: Object,
required: true
}
const props = defineProps({
listUrl: {
type: String,
required: true
},
data() {
return {
fields: [
{key: 'name', isRowHeader: true, label: this.$gettext('Name/Type'), sortable: true},
{key: 'triggers', label: this.$gettext('Triggers'), sortable: false},
{key: 'actions', label: this.$gettext('Actions'), sortable: false, class: 'shrink'}
]
};
nowPlayingUrl: {
type: String,
required: true
},
computed: {
langTriggerTitles() {
return {
song_changed: this.$gettext('Song Change'),
song_changed_live: this.$gettext('Song Change (Live Only)'),
listener_gained: this.$gettext('Listener Gained'),
listener_lost: this.$gettext('Listener Lost'),
live_connect: this.$gettext('Live Streamer/DJ Connected'),
live_disconnect: this.$gettext('Live Streamer/DJ Disconnected'),
station_offline: this.$gettext('Station Goes Offline'),
station_online: this.$gettext('Station Goes Online'),
}
},
langTriggerDescriptions() {
return {
song_changed: this.$gettext('Any time the currently playing song changes'),
song_changed_live: this.$gettext('When the song changes and a live streamer/DJ is connected'),
listener_gained: this.$gettext('Any time the listener count increases'),
listener_lost: this.$gettext('Any time the listener count decreases'),
live_connect: this.$gettext('Any time a live streamer/DJ connects to the stream'),
live_disconnect: this.$gettext('Any time a live streamer/DJ disconnects from the stream'),
station_offline: this.$gettext('When the station broadcast goes offline'),
station_online: this.$gettext('When the station broadcast comes online'),
}
}
},
methods: {
langToggleButton(record) {
return (record.is_enabled)
? this.$gettext('Disable')
: this.$gettext('Enable');
},
getToggleVariant(record) {
return (record.is_enabled)
? 'warning'
: 'success';
},
getWebhookName(key) {
return get(this.webhookTypes, [key, 'name'], '');
},
getTriggerNames(triggers) {
return map(triggers, (trigger) => {
return get(this.langTriggerTitles, trigger, '');
});
},
relist() {
this.$refs.datatable.refresh();
},
doCreate() {
this.$refs.editModal.create();
},
doEdit(url) {
this.$refs.editModal.edit(url);
},
doToggle(url) {
this.$wrapWithLoading(
this.axios.put(url)
).then((resp) => {
this.$notifySuccess(resp.data.message);
this.relist();
});
},
doTest(url) {
this.$wrapWithLoading(
this.axios.put(url)
).then((resp) => {
this.$refs.logModal.show(resp.data.links.log);
});
},
doDelete(url) {
this.$confirmDelete({
title: this.$gettext('Delete Web Hook?'),
}).then((result) => {
if (result.value) {
this.$wrapWithLoading(
this.axios.delete(url)
).then((resp) => {
this.$notifySuccess(resp.data.message);
this.relist();
});
}
});
}
webhookTypes: {
type: Object,
required: true
}
});
const {$gettext} = useTranslate();
const fields = [
{key: 'name', isRowHeader: true, label: $gettext('Name/Type'), sortable: true},
{key: 'triggers', label: $gettext('Triggers'), sortable: false},
{key: 'actions', label: $gettext('Actions'), sortable: false, class: 'shrink'}
];
const langTriggerTitles = {
song_changed: $gettext('Song Change'),
song_changed_live: $gettext('Song Change (Live Only)'),
listener_gained: $gettext('Listener Gained'),
listener_lost: $gettext('Listener Lost'),
live_connect: $gettext('Live Streamer/DJ Connected'),
live_disconnect: $gettext('Live Streamer/DJ Disconnected'),
station_offline: $gettext('Station Goes Offline'),
station_online: $gettext('Station Goes Online'),
};
const langTriggerDescriptions = {
song_changed: $gettext('Any time the currently playing song changes'),
song_changed_live: $gettext('When the song changes and a live streamer/DJ is connected'),
listener_gained: $gettext('Any time the listener count increases'),
listener_lost: $gettext('Any time the listener count decreases'),
live_connect: $gettext('Any time a live streamer/DJ connects to the stream'),
live_disconnect: $gettext('Any time a live streamer/DJ disconnects from the stream'),
station_offline: $gettext('When the station broadcast goes offline'),
station_online: $gettext('When the station broadcast comes online'),
};
const langToggleButton = (record) => {
return (record.is_enabled)
? $gettext('Disable')
: $gettext('Enable');
};
const getToggleVariant = (record) => {
return (record.is_enabled)
? 'warning'
: 'success';
};
const getWebhookName = (key) => {
return get(props.webhookTypes, [key, 'name'], '');
};
const getTriggerNames = (triggers) => {
return map(triggers, (trigger) => {
return get(langTriggerTitles, trigger, '');
});
};
const $datatable = ref(); // Template Ref
const {relist} = useHasDatatable($datatable);
const $editModal = ref(); // Template Ref
const {doCreate, doEdit} = useHasEditModal($editModal);
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doToggle = (url) => {
wrapWithLoading(
axios.put(url)
).then((resp) => {
notifySuccess(resp.data.message);
relist();
});
};
const $logModal = ref(); // Template Ref
const doTest = (url) => {
wrapWithLoading(
axios.put(url)
).then((resp) => {
$logModal.value.show(resp.data.links.log);
});
};
const doDelete = (url) => confirmAndDelete(
url,
$gettext('Delete Web Hook?'),
relist
);
</script>

View File

@ -0,0 +1,29 @@
import {useSweetAlert} from "~/vendor/sweetalert";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
export default function confirmAndDelete(
deleteUrl,
confirmMessage,
onSuccess = null
) {
const {confirmDelete} = useSweetAlert();
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
confirmDelete({
title: confirmMessage
}).then((result) => {
if (result.value) {
wrapWithLoading(
axios.delete(deleteUrl)
).then((resp) => {
notifySuccess(resp.data.message);
if (typeof onSuccess === 'function') {
onSuccess(resp.data);
}
});
}
});
}

View File

@ -0,0 +1,9 @@
export default function useHasDatatable($datatableRef) {
const relist = () => {
return $datatableRef.value?.relist();
}
return {
relist
};
}

View File

@ -0,0 +1,15 @@
export default function useHasEditModal($modalRef) {
const doCreate = () => {
$modalRef.value?.create();
};
const doEdit = (editUrl) => {
$modalRef.value?.edit(editUrl);
};
return {
doCreate,
doEdit
}
}

View File

@ -1,7 +1,10 @@
import {useAxios} from "~/vendor/axios";
export const mayNeedRestartProps = {
restartStatusUrl: String
restartStatusUrl: {
type: String,
required: true
}
};
export function useNeedsRestart() {