mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
implementing snapshots + change ui
This commit is contained in:
@@ -60,10 +60,30 @@ async function deleteSnapshot(close: () => any) {
|
|||||||
});
|
});
|
||||||
await updateSnapshots();
|
await updateSnapshots();
|
||||||
snapshot.value = snapshots.value[1];
|
snapshot.value = snapshots.value[1];
|
||||||
createAlert('Snapshot deleted','Snapshot deleted successfully', 'far fa-circle-check', 5000);
|
createAlert('Snapshot deleted', 'Snapshot deleted successfully', 'far fa-circle-check', 5000);
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generatePDF() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await $fetch<Blob>('/api/project/generate_pdf', {
|
||||||
|
...signHeaders(),
|
||||||
|
responseType: 'blob'
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = URL.createObjectURL(res);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `Report.pdf`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (ex: any) {
|
||||||
|
alert(ex.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -119,15 +139,22 @@ async function deleteSnapshot(close: () => any) {
|
|||||||
<div v-if="snapshot" class="flex flex-col text-[.8rem] mt-2 px-2">
|
<div v-if="snapshot" class="flex flex-col text-[.8rem] mt-2 px-2">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="grow poppins"> From:</div>
|
<div class="grow poppins"> From:</div>
|
||||||
<div class="poppins"> {{ new Date(snapshot.from).toLocaleString('it-IT').split(',')[0].trim() }} </div>
|
<div class="poppins"> {{ new Date(snapshot.from).toLocaleString('it-IT').split(',')[0].trim() }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="grow poppins"> To:</div>
|
<div class="grow poppins"> To:</div>
|
||||||
<div class="poppins"> {{ new Date(snapshot.to).toLocaleString('it-IT').split(',')[0].trim() }}</div>
|
<div class="poppins"> {{ new Date(snapshot.to).toLocaleString('it-IT').split(',')[0].trim() }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4" v-if="snapshot._id.toString().startsWith('default') === false">
|
|
||||||
|
<LyxUiButton @click="generatePDF()" type="primary" class="w-full text-center mt-4">
|
||||||
|
Download report
|
||||||
|
</LyxUiButton>
|
||||||
|
|
||||||
|
<div class="mt-2" v-if="snapshot._id.toString().startsWith('default') === false">
|
||||||
<UPopover placement="bottom">
|
<UPopover placement="bottom">
|
||||||
<LyxUiButton type="danger" class="w-full">
|
<LyxUiButton type="danger" class="w-full text-center">
|
||||||
Delete current snapshot
|
Delete current snapshot
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
|
|
||||||
|
|||||||
@@ -50,112 +50,99 @@ function openExternalLink(link: string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="flex h-full">
|
<LyxUiCard class="w-full h-full p-4 flex flex-col gap-8 relative">
|
||||||
|
<div class="flex justify-between mb-3">
|
||||||
<div class="text-text flex flex-col items-start gap-4 w-full relative">
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="flex gap-4 items-center">
|
||||||
<div class="w-full h-full p-4 flex flex-col bg-card rounded-xl gap-8 card-shadow">
|
<div class="poppins font-semibold text-[1.4rem] text-text">
|
||||||
|
{{ label }}
|
||||||
<div class="flex justify-between mb-3">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<div class="flex gap-4 items-center">
|
|
||||||
<div class="poppins font-semibold text-[1.4rem] text-text">
|
|
||||||
{{ label }}
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<i @click="reloadData()"
|
|
||||||
class="hover:rotate-[50deg] transition-all duration-100 fas fa-refresh text-[1.2rem] cursor-pointer"></i>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="poppins text-[1rem] text-text-sub/90">
|
|
||||||
{{ desc }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="rawButton" class="hidden lg:flex">
|
<div class="flex items-center">
|
||||||
<div @click="$emit('showRawData')"
|
<i @click="reloadData()"
|
||||||
class="cursor-pointer flex gap-1 items-center justify-center font-semibold poppins rounded-lg text-[#5680f8] hover:text-[#5681f8ce]">
|
class="hover:rotate-[50deg] transition-all duration-100 fas fa-refresh text-[1.2rem] cursor-pointer"></i>
|
||||||
<div> Raw data </div>
|
|
||||||
<div class="flex items-center"> <i class="fas fa-arrow-up-right"></i> </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="poppins text-[1rem] text-text-sub/90">
|
||||||
<div>
|
{{ desc }}
|
||||||
<div class="flex justify-between font-bold text-text-sub/80 text-[1.1rem] mb-4">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div v-if="isDetailView" class="flex items-center justify-center">
|
|
||||||
<i @click="$emit('showGeneral')"
|
|
||||||
class="fas fa-arrow-left text-[.9rem] hover:text-text cursor-pointer"></i>
|
|
||||||
</div>
|
|
||||||
<div> {{ subLabel }} </div>
|
|
||||||
</div>
|
|
||||||
<div> Count </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
|
|
||||||
<div v-if="props.data.length > 0" class="flex justify-between items-center"
|
|
||||||
v-for="element of props.data">
|
|
||||||
|
|
||||||
<div class="flex items-center gap-2 w-10/12 relative">
|
|
||||||
|
|
||||||
<div v-if="showLink">
|
|
||||||
<i @click="openExternalLink(element._id)"
|
|
||||||
class="fas fa-link text-gray-300 hover:text-gray-400 cursor-pointer"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-1 items-center" @click="showDetails(element._id)"
|
|
||||||
:class="{ 'cursor-pointer line-active': interactive }">
|
|
||||||
|
|
||||||
<div class="absolute rounded-sm w-full h-full bg-[#92abcf38]"
|
|
||||||
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
|
|
||||||
|
|
||||||
<div class="flex px-2 py-1 relative items-center gap-4">
|
|
||||||
<div v-if="iconProvider && iconProvider(element._id) != undefined"
|
|
||||||
class="flex items-center h-[1.3rem]">
|
|
||||||
|
|
||||||
<img v-if="iconProvider(element._id)?.[0] == 'img'" class="h-full"
|
|
||||||
:style="customIconStyle" :src="iconProvider(element._id)?.[1]">
|
|
||||||
|
|
||||||
<i v-else :class="iconProvider(element._id)?.[1]"></i>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-text/70">
|
|
||||||
{{ elementTextTransformer?.(element._id) || element._id }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-text font-semibold text-[.9rem] md:text-[1rem] manrope"> {{
|
|
||||||
formatNumberK(element.count) }} </div>
|
|
||||||
</div>
|
|
||||||
<div v-if="props.data.length == 0"
|
|
||||||
class="flex justify-center text-text-sub font-bold text-[1.1rem]">
|
|
||||||
No visits yet
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 ">
|
|
||||||
<div @click="$emit('showMore')"
|
|
||||||
class="poppins hover:bg-black cursor-pointer w-fit px-6 py-1 rounded-lg border-[1px] border-text-sub text-[.9rem]">
|
|
||||||
Show more
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="loading"
|
<div v-if="rawButton" class="hidden lg:flex">
|
||||||
class="backdrop-blur-[1px] z-[20] left-0 top-0 w-full h-full flex items-center justify-center font-bold rockmann absolute">
|
<div @click="$emit('showRawData')"
|
||||||
<i
|
class="cursor-pointer flex gap-1 items-center justify-center font-semibold poppins rounded-lg text-[#5680f8] hover:text-[#5681f8ce]">
|
||||||
class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
<div> Raw data </div>
|
||||||
|
<div class="flex items-center"> <i class="fas fa-arrow-up-right"></i> </div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<div>
|
||||||
|
<div class="flex justify-between font-bold text-text-sub/80 text-[1.1rem] mb-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div v-if="isDetailView" class="flex items-center justify-center">
|
||||||
|
<i @click="$emit('showGeneral')"
|
||||||
|
class="fas fa-arrow-left text-[.9rem] hover:text-text cursor-pointer"></i>
|
||||||
|
</div>
|
||||||
|
<div> {{ subLabel }} </div>
|
||||||
|
</div>
|
||||||
|
<div> Count </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
|
||||||
|
<div v-if="props.data.length > 0" class="flex justify-between items-center"
|
||||||
|
v-for="element of props.data">
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 w-10/12 relative">
|
||||||
|
|
||||||
|
<div v-if="showLink">
|
||||||
|
<i @click="openExternalLink(element._id)"
|
||||||
|
class="fas fa-link text-gray-300 hover:text-gray-400 cursor-pointer"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-1 items-center" @click="showDetails(element._id)"
|
||||||
|
:class="{ 'cursor-pointer line-active': interactive }">
|
||||||
|
|
||||||
|
<div class="absolute rounded-sm w-full h-full bg-[#92abcf38]"
|
||||||
|
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
|
||||||
|
|
||||||
|
<div class="flex px-2 py-1 relative items-center gap-4">
|
||||||
|
<div v-if="iconProvider && iconProvider(element._id) != undefined"
|
||||||
|
class="flex items-center h-[1.3rem]">
|
||||||
|
|
||||||
|
<img v-if="iconProvider(element._id)?.[0] == 'img'" class="h-full"
|
||||||
|
:style="customIconStyle" :src="iconProvider(element._id)?.[1]">
|
||||||
|
|
||||||
|
<i v-else :class="iconProvider(element._id)?.[1]"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-text/70">
|
||||||
|
{{ elementTextTransformer?.(element._id) || element._id }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-text font-semibold text-[.9rem] md:text-[1rem] manrope"> {{
|
||||||
|
formatNumberK(element.count) }} </div>
|
||||||
|
</div>
|
||||||
|
<div v-if="props.data.length == 0" class="flex justify-center text-text-sub font-bold text-[1.1rem]">
|
||||||
|
No visits yet
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 ">
|
||||||
|
<div @click="$emit('showMore')"
|
||||||
|
class="poppins hover:bg-black cursor-pointer w-fit px-6 py-1 rounded-lg border-[1px] border-text-sub text-[.9rem]">
|
||||||
|
Show more
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading"
|
||||||
|
class="backdrop-blur-[1px] z-[20] left-0 top-0 w-full h-full flex items-center justify-center font-bold rockmann absolute">
|
||||||
|
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||||
|
</div>
|
||||||
|
</LyxUiCard>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function showMore() {
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2 h-full">
|
||||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="oss || []"
|
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="oss || []"
|
||||||
desc="The operating systems most commonly used by your website's visitors." :dataIcons="false"
|
desc="The operating systems most commonly used by your website's visitors." :dataIcons="false"
|
||||||
:loading="pending" label="Top OS" sub-label="OSs"></DashboardBarsCard>
|
:loading="pending" label="Top OS" sub-label="OSs"></DashboardBarsCard>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { VisitsWebsiteAggregated } from '~/server/api/metrics/[project_id]/
|
|||||||
|
|
||||||
const { data: websites, pending, refresh } = useWebsitesData();
|
const { data: websites, pending, refresh } = useWebsitesData();
|
||||||
|
|
||||||
const currentViewData = ref<(VisitsWebsiteAggregated[] | null)>(websites.value);
|
const currentViewData = ref<(VisitsWebsiteAggregated[] | undefined)>(websites.value);
|
||||||
|
|
||||||
|
|
||||||
const isPagesView = ref<boolean>(false);
|
const isPagesView = ref<boolean>(false);
|
||||||
@@ -33,7 +33,7 @@ async function showDetails(website: string) {
|
|||||||
|
|
||||||
watch(pending, () => {
|
watch(pending, () => {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
currentViewData.value = pagesData.value;
|
currentViewData.value = pagesData.value as any;
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function usePagesData(website: string, limit: number = 10) {
|
|||||||
|
|
||||||
|
|
||||||
export function useWebsitesData(limit: number = 10) {
|
export function useWebsitesData(limit: number = 10) {
|
||||||
const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`,
|
const res = useCustomFetch<VisitsWebsiteAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`,
|
||||||
() => signHeaders(createFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
() => signHeaders(createFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
{ lazy: false, watchProps: [snapshot] }
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const activeProject = useActiveProject();
|
||||||
watch(activeProject, () => {
|
watch(activeProject, async () => {
|
||||||
remoteSnapshots.refresh();
|
await remoteSnapshots.refresh();
|
||||||
|
snapshot.value = snapshots.value[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshots = computed(() => {
|
const snapshots = computed(() => {
|
||||||
@@ -57,19 +58,14 @@ const snapshots = computed(() => {
|
|||||||
|
|
||||||
const snapshot = ref<TProjectSnapshot>(snapshots.value[1]);
|
const snapshot = ref<TProjectSnapshot>(snapshots.value[1]);
|
||||||
|
|
||||||
// watch(remoteSnapshots.data, () => {
|
|
||||||
// if (!remoteSnapshots.data.value) return;
|
|
||||||
// snapshot.value = remoteSnapshots.data.value[0];
|
|
||||||
// });
|
|
||||||
|
|
||||||
const safeSnapshotDates = computed(() => {
|
const safeSnapshotDates = computed(() => {
|
||||||
const from = new Date(snapshot.value?.from || 0).toISOString();
|
const from = new Date(snapshot.value?.from || 0).toISOString();
|
||||||
const to = new Date(snapshot.value?.to || Date.now()).toISOString();
|
const to = new Date(snapshot.value?.to || Date.now()).toISOString();
|
||||||
return { from, to }
|
return { from, to }
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateSnapshots() {
|
async function updateSnapshots() {
|
||||||
remoteSnapshots.refresh();
|
await remoteSnapshots.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSnapshot() {
|
export function useSnapshot() {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const sections: Section[] = [
|
|||||||
{ label: 'Dashboard', to: '/', icon: 'far fa-home' },
|
{ label: 'Dashboard', to: '/', icon: 'far fa-home' },
|
||||||
{ label: 'Events', to: '/events', icon: 'far fa-bolt' },
|
{ label: 'Events', to: '/events', icon: 'far fa-bolt' },
|
||||||
{ label: 'Analyst', to: '/analyst', icon: 'far fa-microchip-ai' },
|
{ label: 'Analyst', to: '/analyst', icon: 'far fa-microchip-ai' },
|
||||||
{ label: 'Report', to: '/report', icon: 'far fa-notes' },
|
// { label: 'Report', to: '/report', icon: 'far fa-notes' },
|
||||||
// { label: 'AI', to: '/dashboard/settings', icon: 'far fa-robot brightness-[.4]' },
|
// { label: 'AI', to: '/dashboard/settings', icon: 'far fa-robot brightness-[.4]' },
|
||||||
// { label: 'Visits', to: '/dashboard/visits', icon: 'far fa-eye' },
|
// { label: 'Visits', to: '/dashboard/visits', icon: 'far fa-eye' },
|
||||||
// { label: 'Events', to: '/dashboard/events', icon: 'far fa-line-chart' },
|
// { label: 'Events', to: '/dashboard/events', icon: 'far fa-line-chart' },
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ function testAlert() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex w-full justify-center mt-6 px-6">
|
<div class="flex w-full justify-center mt-6 px-6">
|
||||||
<div class="flex w-full gap-6 flex-col xl:flex-row">
|
<div class="flex w-full gap-6 flex-col xl:flex-row">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user