Fix datetime issues.
This commit is contained in:
parent
4ceb167f5e
commit
10c8069719
|
@ -20,9 +20,6 @@
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:api-url="apiUrl"
|
:api-url="apiUrl"
|
||||||
>
|
>
|
||||||
<template #cell(date_time)="row">
|
|
||||||
{{ formatTimestamp(row.item.timestamp) }}
|
|
||||||
</template>
|
|
||||||
<template #cell(operation)="row">
|
<template #cell(operation)="row">
|
||||||
<span
|
<span
|
||||||
v-if="row.item.operation_text === 'insert'"
|
v-if="row.item.operation_text === 'insert'"
|
||||||
|
@ -135,9 +132,21 @@ const dateRange = ref({
|
||||||
});
|
});
|
||||||
|
|
||||||
const {$gettext} = useTranslate();
|
const {$gettext} = useTranslate();
|
||||||
|
const {timeConfig} = useAzuraCast();
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{key: 'date_time', label: $gettext('Date/Time'), sortable: false},
|
{
|
||||||
|
key: 'timestamp',
|
||||||
|
label: $gettext('Date/Time'),
|
||||||
|
sortable: false,
|
||||||
|
formatter: (value) => {
|
||||||
|
return DateTime.fromSeconds(value).toLocaleString(
|
||||||
|
{
|
||||||
|
...DateTime.DATETIME_SHORT, ...timeConfig
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{key: 'user', label: $gettext('User'), sortable: false},
|
{key: 'user', label: $gettext('User'), sortable: false},
|
||||||
{key: 'operation', isRowHeader: true, label: $gettext('Operation'), sortable: false},
|
{key: 'operation', isRowHeader: true, label: $gettext('Operation'), sortable: false},
|
||||||
{key: 'identifier', label: $gettext('Identifier'), sortable: false},
|
{key: 'identifier', label: $gettext('Identifier'), sortable: false},
|
||||||
|
@ -160,16 +169,6 @@ const $dataTable = ref(); // DataTable Template Ref
|
||||||
const relist = () => {
|
const relist = () => {
|
||||||
$dataTable.value.relist();
|
$dataTable.value.relist();
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimestamp = (unix_timestamp) => {
|
|
||||||
const {timeConfig} = useAzuraCast();
|
|
||||||
|
|
||||||
return DateTime.fromSeconds(unix_timestamp).toLocaleString(
|
|
||||||
{
|
|
||||||
...DateTime.DATETIME_SHORT, ...timeConfig
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<h2 class="card-title flex-fill my-0">
|
<h2 class="card-title flex-fill my-0">
|
||||||
{{ $gettext('Song Playback Timeline') }}
|
{{ $gettext('Song Playback Timeline') }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex-shrink">
|
<div class="flex-shrink buttons">
|
||||||
<a
|
<a
|
||||||
id="btn-export"
|
id="btn-export"
|
||||||
class="btn btn-bg"
|
class="btn btn-bg"
|
||||||
|
@ -26,22 +26,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<data-table
|
<data-table
|
||||||
ref="datatable"
|
ref="$datatable"
|
||||||
responsive
|
responsive
|
||||||
paginated
|
paginated
|
||||||
select-fields
|
select-fields
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:api-url="apiUrl"
|
:api-url="apiUrl"
|
||||||
>
|
>
|
||||||
<template #cell(datetime)="row">
|
|
||||||
{{ formatTimestamp(row.item.played_at) }}
|
|
||||||
</template>
|
|
||||||
<template #cell(datetime_station)="row">
|
|
||||||
{{ formatTimestampStation(row.item.played_at) }}
|
|
||||||
</template>
|
|
||||||
<template #cell(listeners_start)="row">
|
|
||||||
{{ row.item.listeners_start }}
|
|
||||||
</template>
|
|
||||||
<template #cell(delta)="row">
|
<template #cell(delta)="row">
|
||||||
<span class="typography-subheading">
|
<span class="typography-subheading">
|
||||||
<template v-if="row.item.delta_total > 0">
|
<template v-if="row.item.delta_total > 0">
|
||||||
|
@ -92,116 +83,121 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import Icon from "~/components/Common/Icon";
|
import Icon from "~/components/Common/Icon";
|
||||||
import DataTable from "~/components/Common/DataTable";
|
import DataTable from "~/components/Common/DataTable";
|
||||||
import DateRangeDropdown from "~/components/Common/DateRangeDropdown";
|
import DateRangeDropdown from "~/components/Common/DateRangeDropdown";
|
||||||
import {DateTime} from 'luxon';
|
import {DateTime} from 'luxon';
|
||||||
import {useAzuraCast} from "~/vendor/azuracast";
|
import {useAzuraCast} from "~/vendor/azuracast";
|
||||||
|
import {computed, ref} from "vue";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'StationsReportsTimeline',
|
baseApiUrl: {
|
||||||
components: {DateRangeDropdown, DataTable, Icon},
|
type: String,
|
||||||
props: {
|
required: true
|
||||||
baseApiUrl: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
stationTimeZone: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
data() {
|
stationTimeZone: {
|
||||||
let nowTz = DateTime.now().setZone(this.stationTimeZone);
|
type: String,
|
||||||
|
required: true
|
||||||
return {
|
|
||||||
dateRange: {
|
|
||||||
startDate: nowTz.minus({days: 13}).toJSDate(),
|
|
||||||
endDate: nowTz.toJSDate(),
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'datetime',
|
|
||||||
label: this.$gettext('Date/Time (Browser)'),
|
|
||||||
selectable: true,
|
|
||||||
sortable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'datetime_station',
|
|
||||||
label: this.$gettext('Date/Time (Station)'),
|
|
||||||
sortable: false,
|
|
||||||
selectable: true,
|
|
||||||
visible: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'listeners_start',
|
|
||||||
label: this.$gettext('Listeners'),
|
|
||||||
selectable: true,
|
|
||||||
sortable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'delta',
|
|
||||||
label: this.$gettext('Change'),
|
|
||||||
selectable: true,
|
|
||||||
sortable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'song',
|
|
||||||
isRowHeader: true,
|
|
||||||
label: this.$gettext('Song Title'),
|
|
||||||
selectable: true,
|
|
||||||
sortable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'source',
|
|
||||||
label: this.$gettext('Source'),
|
|
||||||
selectable: true,
|
|
||||||
sortable: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
apiUrl() {
|
|
||||||
let apiUrl = new URL(this.baseApiUrl, document.location);
|
|
||||||
|
|
||||||
let apiUrlParams = apiUrl.searchParams;
|
|
||||||
apiUrlParams.set('start', DateTime.fromJSDate(this.dateRange.startDate).toISO());
|
|
||||||
apiUrlParams.set('end', DateTime.fromJSDate(this.dateRange.endDate).toISO());
|
|
||||||
|
|
||||||
return apiUrl.toString();
|
|
||||||
},
|
|
||||||
exportUrl() {
|
|
||||||
let exportUrl = new URL(this.apiUrl, document.location);
|
|
||||||
let exportUrlParams = exportUrl.searchParams;
|
|
||||||
|
|
||||||
exportUrlParams.set('format', 'csv');
|
|
||||||
|
|
||||||
return exportUrl.toString();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
relist() {
|
|
||||||
this.$refs.datatable.relist();
|
|
||||||
},
|
|
||||||
abs(val) {
|
|
||||||
return Math.abs(val);
|
|
||||||
},
|
|
||||||
formatTimestamp(unix_timestamp) {
|
|
||||||
const {timeConfig} = useAzuraCast();
|
|
||||||
|
|
||||||
return DateTime.fromSeconds(unix_timestamp).toLocaleString(
|
|
||||||
{...DateTime.DATETIME_SHORT, ...timeConfig}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
formatTimestampStation(unix_timestamp) {
|
|
||||||
const {timeConfig} = useAzuraCast();
|
|
||||||
|
|
||||||
return DateTime.fromSeconds(unix_timestamp).setZone(this.stationTimeZone).toLocaleString(
|
|
||||||
{...DateTime.DATETIME_SHORT, ...timeConfig}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const nowTz = DateTime.now().setZone(props.stationTimeZone);
|
||||||
|
|
||||||
|
const dateRange = ref(
|
||||||
|
{
|
||||||
|
startDate: nowTz.minus({days: 13}).toJSDate(),
|
||||||
|
endDate: nowTz.toJSDate(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const {$gettext} = useTranslate();
|
||||||
|
const {timeConfig} = useAzuraCast();
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
key: 'played_at',
|
||||||
|
label: $gettext('Date/Time (Browser)'),
|
||||||
|
selectable: true,
|
||||||
|
sortable: false,
|
||||||
|
formatter: (value) => {
|
||||||
|
return DateTime.fromSeconds(
|
||||||
|
value,
|
||||||
|
{zone: 'system'}
|
||||||
|
).toLocaleString(
|
||||||
|
{...DateTime.DATETIME_SHORT, ...timeConfig}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'played_at_station',
|
||||||
|
label: $gettext('Date/Time (Station)'),
|
||||||
|
sortable: false,
|
||||||
|
selectable: true,
|
||||||
|
visible: false,
|
||||||
|
formatter: (value, key, item) => {
|
||||||
|
return DateTime.fromSeconds(
|
||||||
|
item.played_at,
|
||||||
|
{zone: props.stationTimeZone}
|
||||||
|
).toLocaleString(
|
||||||
|
{...DateTime.DATETIME_SHORT, ...timeConfig}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'listeners_start',
|
||||||
|
label: $gettext('Listeners'),
|
||||||
|
selectable: true,
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delta',
|
||||||
|
label: $gettext('Change'),
|
||||||
|
selectable: true,
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'song',
|
||||||
|
isRowHeader: true,
|
||||||
|
label: $gettext('Song Title'),
|
||||||
|
selectable: true,
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'source',
|
||||||
|
label: $gettext('Source'),
|
||||||
|
selectable: true,
|
||||||
|
sortable: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const apiUrl = computed(() => {
|
||||||
|
let apiUrl = new URL(props.baseApiUrl, document.location);
|
||||||
|
|
||||||
|
let apiUrlParams = apiUrl.searchParams;
|
||||||
|
apiUrlParams.set('start', DateTime.fromJSDate(dateRange.value.startDate).toISO());
|
||||||
|
apiUrlParams.set('end', DateTime.fromJSDate(dateRange.value.endDate).toISO());
|
||||||
|
|
||||||
|
return apiUrl.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportUrl = computed(() => {
|
||||||
|
let exportUrl = new URL(apiUrl.value, document.location);
|
||||||
|
let exportUrlParams = exportUrl.searchParams;
|
||||||
|
|
||||||
|
exportUrlParams.set('format', 'csv');
|
||||||
|
|
||||||
|
return exportUrl.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const abs = (val) => {
|
||||||
|
return Math.abs(val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const $datatable = ref(); // Template Ref
|
||||||
|
|
||||||
|
const relist = () => {
|
||||||
|
$datatable.value.relist();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<b-modal
|
<b-modal
|
||||||
id="streamer_broadcasts"
|
id="streamer_broadcasts"
|
||||||
ref="modal"
|
ref="$modal"
|
||||||
size="lg"
|
size="lg"
|
||||||
centered
|
centered
|
||||||
:title="$gettext('Streamer Broadcasts')"
|
:title="$gettext('Streamer Broadcasts')"
|
||||||
|
@ -11,12 +11,12 @@
|
||||||
style="min-height: 40px;"
|
style="min-height: 40px;"
|
||||||
class="flex-fill text-left bg-primary rounded mb-2"
|
class="flex-fill text-left bg-primary rounded mb-2"
|
||||||
>
|
>
|
||||||
<inline-player ref="player" />
|
<inline-player ref="$player" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<data-table
|
<data-table
|
||||||
id="station_streamer_broadcasts"
|
id="station_streamer_broadcasts"
|
||||||
ref="datatable"
|
ref="$datatable"
|
||||||
:show-toolbar="false"
|
:show-toolbar="false"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:api-url="listUrl"
|
:api-url="listUrl"
|
||||||
|
@ -65,7 +65,8 @@
|
||||||
</template>
|
</template>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
|
<script setup>
|
||||||
import DataTable from '~/components/Common/DataTable.vue';
|
import DataTable from '~/components/Common/DataTable.vue';
|
||||||
import formatFileSize from '~/functions/formatFileSize.js';
|
import formatFileSize from '~/functions/formatFileSize.js';
|
||||||
import InlinePlayer from '~/components/InlinePlayer';
|
import InlinePlayer from '~/components/InlinePlayer';
|
||||||
|
@ -74,95 +75,108 @@ import PlayButton from "~/components/Common/PlayButton";
|
||||||
import {DateTime} from 'luxon';
|
import {DateTime} from 'luxon';
|
||||||
import '~/vendor/sweetalert';
|
import '~/vendor/sweetalert';
|
||||||
import {useAzuraCast} from "~/vendor/azuracast";
|
import {useAzuraCast} from "~/vendor/azuracast";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {useTranslate} from "~/vendor/gettext";
|
||||||
|
import {useSweetAlert} from "~/vendor/sweetalert";
|
||||||
|
import {useNotify} from "~/vendor/bootstrapVue";
|
||||||
|
import {useAxios} from "~/vendor/axios";
|
||||||
|
|
||||||
export default {
|
const listUrl = ref(null);
|
||||||
name: 'StreamerBroadcastsModal',
|
|
||||||
components: {PlayButton, Icon, InlinePlayer, DataTable},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
listUrl: null,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'download',
|
|
||||||
label: ' ',
|
|
||||||
sortable: false,
|
|
||||||
class: 'shrink pr-3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'timestampStart',
|
|
||||||
label: this.$gettext('Start Time'),
|
|
||||||
sortable: false,
|
|
||||||
formatter: (value) => {
|
|
||||||
const {timeConfig} = useAzuraCast();
|
|
||||||
|
|
||||||
return DateTime.fromSeconds(value).toLocaleString(
|
const {$gettext} = useTranslate();
|
||||||
{...DateTime.DATETIME_MED, ...timeConfig}
|
const {timeConfig} = useAzuraCast();
|
||||||
);
|
|
||||||
},
|
|
||||||
class: 'pl-3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'timestampEnd',
|
|
||||||
label: this.$gettext('End Time'),
|
|
||||||
sortable: false,
|
|
||||||
formatter: (value) => {
|
|
||||||
if (value === 0) {
|
|
||||||
return this.$gettext('Live');
|
|
||||||
}
|
|
||||||
|
|
||||||
const {timeConfig} = useAzuraCast();
|
const fields = [
|
||||||
|
{
|
||||||
return DateTime.fromSeconds(value).toLocaleString(
|
key: 'download',
|
||||||
{...DateTime.DATETIME_MED, ...timeConfig}
|
label: ' ',
|
||||||
);
|
sortable: false,
|
||||||
}
|
class: 'shrink pr-3'
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'size',
|
|
||||||
label: this.$gettext('Size'),
|
|
||||||
sortable: false,
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
if (!item.recording?.size) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatFileSize(item.recording.size);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'actions',
|
|
||||||
label: this.$gettext('Actions'),
|
|
||||||
sortable: false,
|
|
||||||
class: 'shrink'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
methods: {
|
{
|
||||||
doDelete (url) {
|
key: 'timestampStart',
|
||||||
this.$confirmDelete({
|
label: $gettext('Start Time'),
|
||||||
title: this.$gettext('Delete Broadcast?')
|
sortable: false,
|
||||||
}).then((result) => {
|
formatter: (value) => {
|
||||||
if (result.value) {
|
return DateTime.fromSeconds(value).toLocaleString(
|
||||||
this.axios.delete(url).then((resp) => {
|
{...DateTime.DATETIME_MED, ...timeConfig}
|
||||||
this.$notifySuccess(resp.data.message);
|
);
|
||||||
this.$refs.datatable.refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$refs.datatable.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
open (listUrl) {
|
class: 'pl-3'
|
||||||
this.listUrl = listUrl;
|
},
|
||||||
this.$refs.modal.show();
|
{
|
||||||
},
|
key: 'timestampEnd',
|
||||||
close () {
|
label: $gettext('End Time'),
|
||||||
this.$refs.player.stop();
|
sortable: false,
|
||||||
|
formatter: (value) => {
|
||||||
|
if (value === 0) {
|
||||||
|
return $gettext('Live');
|
||||||
|
}
|
||||||
|
|
||||||
this.listUrl = null;
|
return DateTime.fromSeconds(value).toLocaleString(
|
||||||
this.$refs.modal.hide();
|
{...DateTime.DATETIME_MED, ...timeConfig}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'size',
|
||||||
|
label: $gettext('Size'),
|
||||||
|
sortable: false,
|
||||||
|
formatter: (value, key, item) => {
|
||||||
|
if (!item.recording?.size) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatFileSize(item.recording.size);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'actions',
|
||||||
|
label: $gettext('Actions'),
|
||||||
|
sortable: false,
|
||||||
|
class: 'shrink'
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const {confirmDelete} = useSweetAlert();
|
||||||
|
const {notifySuccess} = useNotify();
|
||||||
|
const {axios} = useAxios();
|
||||||
|
|
||||||
|
const $datatable = ref(); // Template Ref
|
||||||
|
|
||||||
|
const doDelete = (url) => {
|
||||||
|
confirmDelete({
|
||||||
|
title: $gettext('Delete Broadcast?')
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.value) {
|
||||||
|
axios.delete(url).then((resp) => {
|
||||||
|
notifySuccess(resp.data.message);
|
||||||
|
$datatable.value.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
$datatable.value.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $modal = ref(); // Template Ref
|
||||||
|
|
||||||
|
const open = (newListUrl) => {
|
||||||
|
listUrl.value = newListUrl;
|
||||||
|
$modal.value.show();
|
||||||
|
};
|
||||||
|
|
||||||
|
const $player = ref(); // Template Ref
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
$player.value.stop();
|
||||||
|
|
||||||
|
listUrl.value = null;
|
||||||
|
|
||||||
|
$modal.value.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,5 +5,4 @@ const {localeWithDashes} = useAzuraCast();
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
Settings.defaultLocale = localeWithDashes;
|
Settings.defaultLocale = localeWithDashes;
|
||||||
Settings.defaultZone = 'UTC';
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue