mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 15:58:38 +01:00
implementing snapshots
This commit is contained in:
@@ -32,8 +32,8 @@ const { isOpen, close } = useMenu();
|
|||||||
const { snapshots, snapshot } = useSnapshot();
|
const { snapshots, snapshot } = useSnapshot();
|
||||||
|
|
||||||
const snapshotsItems = computed(() => {
|
const snapshotsItems = computed(() => {
|
||||||
if (!snapshots.data.value) return []
|
if (!snapshots.value) return []
|
||||||
return snapshots.data.value as any[];
|
return snapshots.value as any[];
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { BrowsersAggregated } from '~/server/api/metrics/[project_id]/data/browsers';
|
const { data: browsers, pending, refresh } = useBrowsersData(10);
|
||||||
|
|
||||||
const activeProject = await useActiveProject();
|
|
||||||
const { data: events, pending, refresh } = await useFetch<BrowsersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`, {
|
|
||||||
...signHeaders(),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||||
|
|
||||||
function showMore() {
|
function showMore() {
|
||||||
|
|
||||||
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
dialogBarData.value = [];
|
dialogBarData.value = [];
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`, signHeaders({
|
const moreRes = useBrowsersData(200);
|
||||||
'x-query-limit': '200'
|
|
||||||
})).then(data => {
|
moreRes.onResponse(data => {
|
||||||
dialogBarData.value = data;
|
dialogBarData.value = data.value || [];
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,7 +23,7 @@ function showMore() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="events || []"
|
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="browsers || []"
|
||||||
desc="The browsers most used to search your website." :dataIcons="false" :loading="pending"
|
desc="The browsers most used to search your website." :dataIcons="false" :loading="pending"
|
||||||
label="Top Browsers" sub-label="Browsers"></DashboardBarsCard>
|
label="Top Browsers" sub-label="Browsers"></DashboardBarsCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { DevicesAggregated } from '~/server/api/metrics/[project_id]/data/devices';
|
const { data: devices, pending, refresh } = useDevicesData(10);
|
||||||
|
|
||||||
const activeProject = await useActiveProject();
|
|
||||||
const { data: events, pending, refresh } = await useFetch<DevicesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/devices`, {
|
|
||||||
...signHeaders(),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||||
|
|
||||||
@@ -18,10 +11,10 @@ function showMore() {
|
|||||||
dialogBarData.value = [];
|
dialogBarData.value = [];
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/devices`, signHeaders({
|
const moreRes = useDevicesData(200);
|
||||||
'x-query-limit': '200'
|
|
||||||
})).then(data => {
|
moreRes.onResponse(data => {
|
||||||
dialogBarData.value = data;
|
dialogBarData.value = data.value || [];
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -32,7 +25,7 @@ function showMore() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="events || []" :dataIcons="false"
|
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="devices || []" :dataIcons="false"
|
||||||
desc="The devices most used to access your website." :loading="pending" label="Top Devices"
|
desc="The devices most used to access your website." :loading="pending" label="Top Devices"
|
||||||
sub-label="Devices"></DashboardBarsCard>
|
sub-label="Devices"></DashboardBarsCard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { CustomEventsAggregated } from '~/server/api/metrics/[project_id]/visits/events';
|
const { data: events, pending, refresh } = useEventsData();
|
||||||
|
|
||||||
const activeProject = await useActiveProject();
|
|
||||||
const { data: events, pending, refresh } = await useFetch<CustomEventsAggregated[]>(`/api/metrics/${activeProject.value?._id}/visits/events`, {
|
|
||||||
...signHeaders(),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -23,10 +17,10 @@ function showMore() {
|
|||||||
dialogBarData.value = [];
|
dialogBarData.value = [];
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/visits/events`, signHeaders({
|
const moreRes = useEventsData(200);
|
||||||
'x-query-limit': '200'
|
|
||||||
})).then(data => {
|
moreRes.onResponse(data => {
|
||||||
dialogBarData.value = data;
|
dialogBarData.value = data.value || [];
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { CountriesAggregated } from '~/server/api/metrics/[project_id]/data/countries';
|
|
||||||
import type { IconProvider } from './BarsCard.vue';
|
import type { IconProvider } from './BarsCard.vue';
|
||||||
|
|
||||||
const activeProject = await useActiveProject();
|
const { data: countries, pending, refresh } = useGeolocationData(10);
|
||||||
const { data: countries, pending, refresh } = await useFetch<CountriesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/countries`, {
|
|
||||||
...signHeaders(),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
|
|
||||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
function iconProvider(id: string): ReturnType<IconProvider> {
|
||||||
if (id === 'self') return ['icon', 'fas fa-link'];
|
if (id === 'self') return ['icon', 'fas fa-link'];
|
||||||
@@ -23,17 +18,18 @@ const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
|||||||
|
|
||||||
function showMore() {
|
function showMore() {
|
||||||
|
|
||||||
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
dialogBarData.value = [];
|
dialogBarData.value = [];
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/countries`, signHeaders({
|
const moreRes = useGeolocationData(200);
|
||||||
'x-query-limit': '200'
|
|
||||||
})).then(data => {
|
moreRes.onResponse(data => {
|
||||||
dialogBarData.value = data;
|
dialogBarData.value = data.value?.map(e => {
|
||||||
|
return { ...e, icon: iconProvider(e._id) }
|
||||||
|
}) || [];
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { OssAggregated } from '~/server/api/metrics/[project_id]/data/oss';
|
const { data: oss, pending, refresh } = useOssData()
|
||||||
|
|
||||||
const activeProject = await useActiveProject();
|
|
||||||
const { data: events, pending, refresh } = await useFetch<OssAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/oss`, {
|
|
||||||
...signHeaders(),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||||
|
|
||||||
function showMore() {
|
function showMore() {
|
||||||
|
|
||||||
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
dialogBarData.value = [];
|
dialogBarData.value = [];
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/oss`, signHeaders({
|
const moreRes = useOssData(200);
|
||||||
'x-query-limit': '200'
|
|
||||||
})).then(data => {
|
moreRes.onResponse(data => {
|
||||||
dialogBarData.value = data;
|
dialogBarData.value = data.value || [];
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +23,7 @@ function showMore() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="events || []"
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { ReferrersAggregated } from '~/server/api/metrics/[project_id]/data/referrers';
|
|
||||||
import type { IconProvider } from './BarsCard.vue';
|
import type { IconProvider } from './BarsCard.vue';
|
||||||
import ReferrerBarChart from '../referrer/ReferrerBarChart.vue';
|
import ReferrerBarChart from '../referrer/ReferrerBarChart.vue';
|
||||||
|
|
||||||
const activeProject = await useActiveProject();
|
const { data: events, pending, refresh } = useReferrersData(10);
|
||||||
|
|
||||||
const { safeSnapshotDates, snapshot } = useSnapshot();
|
|
||||||
|
|
||||||
const { data: events, pending, refresh } = await useFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`, {
|
|
||||||
...signHeaders({
|
|
||||||
'x-from': safeSnapshotDates.value.from,
|
|
||||||
'x-to': safeSnapshotDates.value.to
|
|
||||||
}),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(snapshot,()=>{
|
|
||||||
refresh();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
function iconProvider(id: string): ReturnType<IconProvider> {
|
||||||
@@ -37,7 +22,6 @@ const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
|||||||
const customDialog = useCustomDialog();
|
const customDialog = useCustomDialog();
|
||||||
|
|
||||||
function onShowDetails(referrer: string) {
|
function onShowDetails(referrer: string) {
|
||||||
|
|
||||||
customDialog.openDialog(ReferrerBarChart, { slice: 'day', referrer });
|
customDialog.openDialog(ReferrerBarChart, { slice: 'day', referrer });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,19 +30,19 @@ function onShowDetails(referrer: string) {
|
|||||||
|
|
||||||
function showMore() {
|
function showMore() {
|
||||||
|
|
||||||
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
dialogBarData.value = [];
|
dialogBarData.value = [];
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`, signHeaders({
|
|
||||||
'x-query-limit': '200'
|
const moreRes = useReferrersData(200);
|
||||||
})).then(data => {
|
|
||||||
dialogBarData.value = data.map(e => {
|
moreRes.onResponse(data => {
|
||||||
|
dialogBarData.value = data.value?.map(e => {
|
||||||
return { ...e, icon: iconProvider(e._id) }
|
return { ...e, icon: iconProvider(e._id) }
|
||||||
});
|
}) || [];
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
});
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,26 @@ export type CustomFetchOptions = {
|
|||||||
lazy?: boolean
|
lazy?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OnResponseCallback<TData> = (data: Ref<TData | undefined>) => any
|
||||||
|
|
||||||
export function useCustomFetch<T>(url: NitroFetchRequest, getHeaders: () => Record<string, string>, options?: CustomFetchOptions) {
|
export function useCustomFetch<T>(url: NitroFetchRequest, getHeaders: () => Record<string, string>, options?: CustomFetchOptions) {
|
||||||
|
|
||||||
const pending = ref<boolean>(false);
|
const pending = ref<boolean>(false);
|
||||||
const data = ref<T | undefined>();
|
const data = ref<T | undefined>();
|
||||||
const error = ref<Error | undefined>();
|
const error = ref<Error | undefined>();
|
||||||
|
|
||||||
|
let onResponseCallback: OnResponseCallback<T> = () => { }
|
||||||
|
|
||||||
|
const onResponse = (callback: OnResponseCallback<T>) => {
|
||||||
|
onResponseCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
const execute = async () => {
|
const execute = async () => {
|
||||||
pending.value = true;
|
pending.value = true;
|
||||||
error.value = undefined;
|
error.value = undefined;
|
||||||
try {
|
try {
|
||||||
data.value = await $fetch<T>(url, { headers: getHeaders() });
|
data.value = await $fetch<T>(url, { headers: getHeaders() });
|
||||||
|
onResponseCallback(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error.value = err as Error;
|
error.value = err as Error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -37,5 +46,7 @@ export function useCustomFetch<T>(url: NitroFetchRequest, getHeaders: () => Reco
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { pending, execute, data, error };
|
const refresh = execute;
|
||||||
|
|
||||||
|
return { pending, execute, data, error, refresh, onResponse };
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import type { Slice } from "@services/DateService";
|
import type { Slice } from "@services/DateService";
|
||||||
import DateService from "@services/DateService";
|
import DateService from "@services/DateService";
|
||||||
import type { MetricsCounts } from "~/server/api/metrics/[project_id]/counts";
|
import type { MetricsCounts } from "~/server/api/metrics/[project_id]/counts";
|
||||||
|
import type { BrowsersAggregated } from "~/server/api/metrics/[project_id]/data/browsers";
|
||||||
|
import type { CountriesAggregated } from "~/server/api/metrics/[project_id]/data/countries";
|
||||||
|
import type { DevicesAggregated } from "~/server/api/metrics/[project_id]/data/devices";
|
||||||
|
import type { CustomEventsAggregated } from "~/server/api/metrics/[project_id]/data/events";
|
||||||
|
import type { OssAggregated } from "~/server/api/metrics/[project_id]/data/oss";
|
||||||
import type { ReferrersAggregated } from "~/server/api/metrics/[project_id]/data/referrers";
|
import type { ReferrersAggregated } from "~/server/api/metrics/[project_id]/data/referrers";
|
||||||
import type { VisitsWebsiteAggregated } from "~/server/api/metrics/[project_id]/data/websites";
|
import type { VisitsWebsiteAggregated } from "~/server/api/metrics/[project_id]/data/websites";
|
||||||
import type { MetricsTimeline } from "~/server/api/metrics/[project_id]/timeline/generic";
|
import type { MetricsTimeline } from "~/server/api/metrics/[project_id]/timeline/generic";
|
||||||
@@ -97,28 +102,61 @@ const getFromToHeaders = (headers: Record<string, string> = {}) => ({
|
|||||||
...headers
|
...headers
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function useWebsitesData(limit: number = 10) {
|
export function useWebsitesData(limit: number = 10) {
|
||||||
const activeProject = useActiveProject();
|
const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`,
|
||||||
const res = useFetch<VisitsWebsiteAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`, {
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
...signHeaders({
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
'x-query-limit': limit.toString(),
|
);
|
||||||
'x-from': safeSnapshotDates.value.from,
|
|
||||||
'x-to': safeSnapshotDates.value.to
|
|
||||||
}),
|
|
||||||
key: `websites_data:${limit}:${safeSnapshotDates.value.from}:${safeSnapshotDates.value.to}`,
|
|
||||||
lazy: true,
|
|
||||||
});
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useEventsData(limit: number = 10) {
|
||||||
|
const res = useCustomFetch<CustomEventsAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/events`,
|
||||||
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export function useReferrersData(limit: number = 10) {
|
export function useReferrersData(limit: number = 10) {
|
||||||
|
|
||||||
const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`,
|
const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`,
|
||||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
{ lazy: true, watchProps: [snapshot] }
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBrowsersData(limit: number = 10) {
|
||||||
|
const res = useCustomFetch<BrowsersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`,
|
||||||
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOssData(limit: number = 10) {
|
||||||
|
const res = useCustomFetch<OssAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/oss`,
|
||||||
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGeolocationData(limit: number = 10) {
|
||||||
|
const res = useCustomFetch<CountriesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/countries`,
|
||||||
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDevicesData(limit: number = 10) {
|
||||||
|
const res = useCustomFetch<DevicesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/devices`,
|
||||||
|
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||||
|
{ lazy: false, watchProps: [snapshot] }
|
||||||
);
|
);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,62 @@
|
|||||||
import type { TProjectSnapshot } from "@schema/ProjectSnapshot";
|
import type { TProjectSnapshot } from "@schema/ProjectSnapshot";
|
||||||
|
|
||||||
const snapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
||||||
...signHeaders(),
|
...signHeaders(),
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshot = ref<TProjectSnapshot>();
|
|
||||||
|
|
||||||
watch(snapshots.data, () => {
|
const snapshots = computed(() => {
|
||||||
if (!snapshots.data.value) return;
|
|
||||||
snapshot.value = snapshots.data.value[0];
|
const activeProject = useActiveProject();
|
||||||
});
|
|
||||||
|
const getDefaultSnapshots: () => TProjectSnapshot[] = () => [
|
||||||
|
{
|
||||||
|
project_id: activeProject.value?._id as any,
|
||||||
|
_id: 'deafult0' as any,
|
||||||
|
name: 'All',
|
||||||
|
from: new Date(activeProject.value?.created_at || 0),
|
||||||
|
to: new Date(Date.now()),
|
||||||
|
color: '#CCCCCC'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
project_id: activeProject.value?._id as any,
|
||||||
|
_id: 'deafult1' as any,
|
||||||
|
name: 'Last month',
|
||||||
|
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30),
|
||||||
|
to: new Date(Date.now()),
|
||||||
|
color: '#00CC00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
project_id: activeProject.value?._id as any,
|
||||||
|
_id: 'deafult2' as any,
|
||||||
|
name: 'Last week',
|
||||||
|
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
||||||
|
to: new Date(Date.now()),
|
||||||
|
color: '#0F02D2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
project_id: activeProject.value?._id as any,
|
||||||
|
_id: 'deafult3' as any,
|
||||||
|
name: 'Last day',
|
||||||
|
from: new Date(Date.now() - 1000 * 60 * 60 * 24),
|
||||||
|
to: new Date(Date.now()),
|
||||||
|
color: '#CC11CC'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return [
|
||||||
|
...getDefaultSnapshots(),
|
||||||
|
...(remoteSnapshots.data.value || [])
|
||||||
|
];
|
||||||
|
})
|
||||||
|
|
||||||
|
const snapshot = ref<TProjectSnapshot>(snapshots.value[0]);
|
||||||
|
|
||||||
|
// 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();
|
||||||
@@ -19,8 +65,8 @@ const safeSnapshotDates = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function useSnapshot() {
|
export function useSnapshot() {
|
||||||
if (snapshots.status.value === 'idle') {
|
if (remoteSnapshots.status.value === 'idle') {
|
||||||
snapshots.execute();
|
remoteSnapshots.execute();
|
||||||
}
|
}
|
||||||
return { snapshot, snapshots, safeSnapshotDates }
|
return { snapshot, snapshots, safeSnapshotDates }
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ const selectLabels = [
|
|||||||
<DashboardTopCards></DashboardTopCards>
|
<DashboardTopCards></DashboardTopCards>
|
||||||
|
|
||||||
|
|
||||||
<!-- <div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row">
|
<div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row">
|
||||||
|
|
||||||
<CardTitled class="p-4 flex-1" title="Visits trends" sub="Shows trends in page visits.">
|
<CardTitled class="p-4 flex-1" title="Visits trends" sub="Shows trends in page visits.">
|
||||||
<template #header>
|
<template #header>
|
||||||
@@ -117,9 +117,9 @@ const selectLabels = [
|
|||||||
</div>
|
</div>
|
||||||
</CardTitled>
|
</CardTitled>
|
||||||
|
|
||||||
</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">
|
||||||
@@ -129,7 +129,7 @@ const selectLabels = [
|
|||||||
<DashboardEventsBarCard></DashboardEventsBarCard>
|
<DashboardEventsBarCard></DashboardEventsBarCard>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
||||||
@@ -143,7 +143,7 @@ const selectLabels = [
|
|||||||
</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">
|
||||||
<DashboardOssBarCard></DashboardOssBarCard>
|
<DashboardOssBarCard></DashboardOssBarCard>
|
||||||
@@ -162,7 +162,7 @@ const selectLabels = [
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,26 @@ export default defineEventHandler(async event => {
|
|||||||
const limit = getRequestHeader(event, 'x-query-limit');
|
const limit = getRequestHeader(event, 'x-query-limit');
|
||||||
const numLimit = parseInt(limit || '10');
|
const numLimit = parseInt(limit || '10');
|
||||||
|
|
||||||
|
|
||||||
|
const from = getRequestHeader(event, 'x-from');
|
||||||
|
const to = getRequestHeader(event, 'x-to');
|
||||||
|
|
||||||
|
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||||
|
|
||||||
return await Redis.useCache({
|
return await Redis.useCache({
|
||||||
key: `browsers:${project_id}:${numLimit}`,
|
key: `browsers:${project_id}:${numLimit}:${from}:${to}`,
|
||||||
exp: DATA_EXPIRE_TIME
|
exp: DATA_EXPIRE_TIME
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const browsers: BrowsersAggregated[] = await VisitModel.aggregate([
|
const browsers: BrowsersAggregated[] = await VisitModel.aggregate([
|
||||||
{ $match: { project_id: project._id }, },
|
{
|
||||||
|
$match: {
|
||||||
|
project_id: project._id,
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{ $group: { _id: "$browser", count: { $sum: 1, } } },
|
{ $group: { _id: "$browser", count: { $sum: 1, } } },
|
||||||
{ $sort: { count: -1 } },
|
{ $sort: { count: -1 } },
|
||||||
{ $limit: numLimit }
|
{ $limit: numLimit }
|
||||||
|
|||||||
@@ -21,13 +21,26 @@ export default defineEventHandler(async event => {
|
|||||||
const limit = getRequestHeader(event, 'x-query-limit');
|
const limit = getRequestHeader(event, 'x-query-limit');
|
||||||
const numLimit = parseInt(limit || '10');
|
const numLimit = parseInt(limit || '10');
|
||||||
|
|
||||||
|
const from = getRequestHeader(event, 'x-from');
|
||||||
|
const to = getRequestHeader(event, 'x-to');
|
||||||
|
|
||||||
|
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||||
|
|
||||||
return await Redis.useCache({
|
return await Redis.useCache({
|
||||||
key: `countries:${project_id}:${numLimit}`,
|
key: `countries:${project_id}:${numLimit}:${from}:${to}`,
|
||||||
exp: DATA_EXPIRE_TIME
|
exp: DATA_EXPIRE_TIME
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const countries: CountriesAggregated[] = await VisitModel.aggregate([
|
const countries: CountriesAggregated[] = await VisitModel.aggregate([
|
||||||
{ $match: { project_id: project._id, country: { $ne: null } }, },
|
{
|
||||||
|
$match: {
|
||||||
|
project_id: project._id,
|
||||||
|
country: { $ne: null },
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{ $group: { _id: "$country", count: { $sum: 1, } } },
|
{ $group: { _id: "$country", count: { $sum: 1, } } },
|
||||||
{ $sort: { count: -1 } },
|
{ $sort: { count: -1 } },
|
||||||
{ $limit: numLimit }
|
{ $limit: numLimit }
|
||||||
|
|||||||
@@ -20,13 +20,26 @@ export default defineEventHandler(async event => {
|
|||||||
const limit = getRequestHeader(event, 'x-query-limit');
|
const limit = getRequestHeader(event, 'x-query-limit');
|
||||||
const numLimit = parseInt(limit || '10');
|
const numLimit = parseInt(limit || '10');
|
||||||
|
|
||||||
|
const from = getRequestHeader(event, 'x-from');
|
||||||
|
const to = getRequestHeader(event, 'x-to');
|
||||||
|
|
||||||
|
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||||
|
|
||||||
return await Redis.useCache({
|
return await Redis.useCache({
|
||||||
key: `devices:${project_id}:${numLimit}`,
|
key: `devices:${project_id}:${numLimit}:${from}:${to}`,
|
||||||
exp: DATA_EXPIRE_TIME
|
exp: DATA_EXPIRE_TIME
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const devices: DevicesAggregated[] = await VisitModel.aggregate([
|
const devices: DevicesAggregated[] = await VisitModel.aggregate([
|
||||||
{ $match: { project_id: project._id, device: { $ne: null } }, },
|
{
|
||||||
|
$match: {
|
||||||
|
project_id: project._id,
|
||||||
|
device: { $ne: null },
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{ $group: { _id: "$device", count: { $sum: 1, } } },
|
{ $group: { _id: "$device", count: { $sum: 1, } } },
|
||||||
{ $sort: { count: -1 } },
|
{ $sort: { count: -1 } },
|
||||||
{ $limit: numLimit }
|
{ $limit: numLimit }
|
||||||
|
|||||||
49
dashboard/server/api/metrics/[project_id]/data/events.ts
Normal file
49
dashboard/server/api/metrics/[project_id]/data/events.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { DATA_EXPIRE_TIME, Redis } from "~/server/services/CacheService";
|
||||||
|
|
||||||
|
export type CustomEventsAggregated = {
|
||||||
|
_id: string,
|
||||||
|
count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
const project_id = getRequestProjectId(event);
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const from = getRequestHeader(event, 'x-from');
|
||||||
|
const to = getRequestHeader(event, 'x-to');
|
||||||
|
|
||||||
|
|
||||||
|
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||||
|
|
||||||
|
|
||||||
|
return await Redis.useCache({
|
||||||
|
key: `events:${project_id}:${from}:${to}`,
|
||||||
|
exp: DATA_EXPIRE_TIME
|
||||||
|
}, async () => {
|
||||||
|
|
||||||
|
const events: CustomEventsAggregated[] = await EventModel.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
project_id: project._id, created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ $group: { _id: "$name", count: { $sum: 1, } } },
|
||||||
|
{ $sort: { count: -1 } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
return events;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
@@ -22,13 +22,25 @@ export default defineEventHandler(async event => {
|
|||||||
const limit = getRequestHeader(event, 'x-query-limit');
|
const limit = getRequestHeader(event, 'x-query-limit');
|
||||||
const numLimit = parseInt(limit || '10');
|
const numLimit = parseInt(limit || '10');
|
||||||
|
|
||||||
|
const from = getRequestHeader(event, 'x-from');
|
||||||
|
const to = getRequestHeader(event, 'x-to');
|
||||||
|
|
||||||
|
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||||
|
|
||||||
return await Redis.useCache({
|
return await Redis.useCache({
|
||||||
key: `oss:${project_id}:${numLimit}`,
|
key: `oss:${project_id}:${numLimit}:${from}:${to}`,
|
||||||
exp: DATA_EXPIRE_TIME
|
exp: DATA_EXPIRE_TIME
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const oss: OssAggregated[] = await VisitModel.aggregate([
|
const oss: OssAggregated[] = await VisitModel.aggregate([
|
||||||
{ $match: { project_id: project._id }, },
|
{
|
||||||
|
$match: {
|
||||||
|
project_id: project._id,
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{ $group: { _id: "$os", count: { $sum: 1, } } },
|
{ $group: { _id: "$os", count: { $sum: 1, } } },
|
||||||
{ $sort: { count: -1 } },
|
{ $sort: { count: -1 } },
|
||||||
{ $limit: numLimit }
|
{ $limit: numLimit }
|
||||||
|
|||||||
@@ -22,13 +22,25 @@ export default defineEventHandler(async event => {
|
|||||||
const limit = getRequestHeader(event, 'x-query-limit');
|
const limit = getRequestHeader(event, 'x-query-limit');
|
||||||
const numLimit = parseInt(limit || '10');
|
const numLimit = parseInt(limit || '10');
|
||||||
|
|
||||||
|
const from = getRequestHeader(event, 'x-from');
|
||||||
|
const to = getRequestHeader(event, 'x-to');
|
||||||
|
|
||||||
|
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||||
|
|
||||||
return await Redis.useCache({
|
return await Redis.useCache({
|
||||||
key: `referrers:${project_id}:${numLimit}`,
|
key: `referrers:${project_id}:${numLimit}:${from}:${to}`,
|
||||||
exp: DATA_EXPIRE_TIME
|
exp: DATA_EXPIRE_TIME
|
||||||
}, async () => {
|
}, async () => {
|
||||||
const referrers: ReferrersAggregated[] = await VisitModel.aggregate([
|
const referrers: ReferrersAggregated[] = await VisitModel.aggregate([
|
||||||
{ $match: { project_id: project._id }, },
|
{
|
||||||
|
$match: {
|
||||||
|
project_id: project._id,
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{ $group: { _id: "$referrer", count: { $sum: 1, } } },
|
{ $group: { _id: "$referrer", count: { $sum: 1, } } },
|
||||||
{ $sort: { count: -1 } },
|
{ $sort: { count: -1 } },
|
||||||
{ $limit: numLimit }
|
{ $limit: numLimit }
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
|
||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { EventModel } from "@schema/metrics/EventSchema";
|
|
||||||
|
|
||||||
export type CustomEventsAggregated = {
|
|
||||||
_id: string,
|
|
||||||
count: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
|
||||||
const project_id = getRequestProjectId(event);
|
|
||||||
if (!project_id) return;
|
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
|
|
||||||
const websites: CustomEventsAggregated[] = await EventModel.aggregate([
|
|
||||||
{ $match: { project_id: project._id }, },
|
|
||||||
{ $group: { _id: "$name", count: { $sum: 1, } } },
|
|
||||||
{ $sort: { count: -1 } }
|
|
||||||
]);
|
|
||||||
|
|
||||||
return websites;
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -36,7 +36,7 @@ const VisitSchema = new Schema<TVisit>({
|
|||||||
website: { type: String, required: true },
|
website: { type: String, required: true },
|
||||||
page: { type: String, required: true },
|
page: { type: String, required: true },
|
||||||
referrer: { type: String, required: true },
|
referrer: { type: String, required: true },
|
||||||
created_at: { type: Date, default: () => Date.now() },
|
created_at: { type: Date, default: () => Date.now(), index: true },
|
||||||
})
|
})
|
||||||
|
|
||||||
export const VisitModel = model<TVisit>('visits', VisitSchema);
|
export const VisitModel = model<TVisit>('visits', VisitSchema);
|
||||||
|
|||||||
Reference in New Issue
Block a user