mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
adjust dashboard
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
steps
|
steps
|
||||||
PROCESS_EVENT
|
PROCESS_EVENT
|
||||||
|
**/node_modules/
|
||||||
docker
|
docker
|
||||||
dev
|
dev
|
||||||
docker-compose.admin.yml
|
docker-compose.admin.yml
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
|
||||||
export type IconProvider = (id: string) => ['img' | 'icon', string] | undefined;
|
export type IconProvider = (e: { _id: string, count: string } & any) => ['img' | 'icon', string] | undefined;
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -80,7 +80,7 @@ function openExternalLink(link: string) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="h-full flex flex-col">
|
||||||
<div class="flex justify-between font-bold text-text-sub/80 text-[1.1rem] mb-4">
|
<div class="flex justify-between font-bold text-text-sub/80 text-[1.1rem] mb-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div v-if="isDetailView" class="flex items-center justify-center">
|
<div v-if="isDetailView" class="flex items-center justify-center">
|
||||||
@@ -111,13 +111,13 @@ function openExternalLink(link: string) {
|
|||||||
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
|
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
|
||||||
|
|
||||||
<div class="flex px-2 py-1 relative items-center gap-4">
|
<div class="flex px-2 py-1 relative items-center gap-4">
|
||||||
<div v-if="iconProvider && iconProvider(element._id) != undefined"
|
<div v-if="iconProvider && iconProvider(element) != undefined"
|
||||||
class="flex items-center h-[1.3rem]">
|
class="flex items-center h-[1.3rem]">
|
||||||
|
|
||||||
<img v-if="iconProvider(element._id)?.[0] == 'img'" class="h-full"
|
<img v-if="iconProvider(element)?.[0] == 'img'" class="h-full"
|
||||||
:style="customIconStyle" :src="iconProvider(element._id)?.[1]">
|
:style="customIconStyle" :src="iconProvider(element)?.[1]">
|
||||||
|
|
||||||
<i v-else :class="iconProvider(element._id)?.[1]"></i>
|
<i v-else :class="iconProvider(element)?.[1]"></i>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-text/70">
|
<span class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-text/70">
|
||||||
{{ elementTextTransformer?.(element._id) || element._id }}
|
{{ elementTextTransformer?.(element._id) || element._id }}
|
||||||
@@ -132,7 +132,7 @@ function openExternalLink(link: string) {
|
|||||||
No data yet
|
No data yet
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 ">
|
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 items-end grow">
|
||||||
<div @click="$emit('showMore')"
|
<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]">
|
class="poppins hover:bg-black cursor-pointer w-fit px-6 py-1 rounded-lg border-[1px] border-text-sub text-[.9rem]">
|
||||||
Show more
|
Show more
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import type { IconProvider } from './Base.vue';
|
||||||
|
|
||||||
|
function iconProvider(e: { _id: string, flag: string, count: number }): ReturnType<IconProvider> {
|
||||||
|
let name = e._id.toLowerCase().replace(/ /g, '-');
|
||||||
|
|
||||||
|
if (name === 'mobile-safari') name = 'safari';
|
||||||
|
if (name === 'chrome-headless') name = 'chrome'
|
||||||
|
if (name === 'chrome-webview') name = 'chrome'
|
||||||
|
|
||||||
|
if (name === 'duckduckgo') return ['icon', 'far fa-duck']
|
||||||
|
if (name === 'avast-secure-browser') return ['icon', 'far fa-bug']
|
||||||
|
if (name === 'avg-secure-browser') return ['icon', 'far fa-bug']
|
||||||
|
|
||||||
|
if (name === 'no_browser') return ['icon', 'far fa-question']
|
||||||
|
if (name === 'gsa') return ['icon', 'far fa-question']
|
||||||
|
if (name === 'miui-browser') return ['icon', 'far fa-question']
|
||||||
|
|
||||||
|
if (name === 'vivo-browser') return ['icon', 'far fa-question']
|
||||||
|
if (name === 'whale') return ['icon', 'far fa-question']
|
||||||
|
|
||||||
|
if (name === 'twitter') return ['icon', 'fab fa-twitter']
|
||||||
|
if (name === 'linkedin') return ['icon', 'fab fa-linkedin']
|
||||||
|
if (name === 'facebook') return ['icon', 'fab fa-facebook']
|
||||||
|
|
||||||
|
return [
|
||||||
|
'img',
|
||||||
|
`https://github.com/alrra/browser-logos/blob/main/src/${name}/${name}_256x256.png?raw=true`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
const browsersData = useFetch('/api/data/browsers', {
|
const browsersData = useFetch('/api/data/browsers', {
|
||||||
headers: useComputedHeaders({ limit: 10, }), lazy: true
|
headers: useComputedHeaders({ limit: 10, }), lazy: true
|
||||||
@@ -8,7 +37,7 @@ const browsersData = useFetch('/api/data/browsers', {
|
|||||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||||
|
|
||||||
async function showMore() {
|
async function showMore() {
|
||||||
dialogBarData.value=[];
|
dialogBarData.value = [];
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
@@ -16,7 +45,9 @@ async function showMore() {
|
|||||||
headers: useComputedHeaders({ limit: 1000 }).value
|
headers: useComputedHeaders({ limit: 1000 }).value
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogBarData.value = res || [];
|
dialogBarData.value = res?.map(e => {
|
||||||
|
return { ...e, icon: iconProvider(e as any) }
|
||||||
|
}) || [];
|
||||||
|
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
|
|
||||||
@@ -28,8 +59,8 @@ async function showMore() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<BarCardBase @showMore="showMore()" @dataReload="browsersData.refresh()" :data="browsersData.data.value || []"
|
<BarCardBase @showMore="showMore()" @dataReload="browsersData.refresh()" :data="browsersData.data.value || []"
|
||||||
desc="The browsers most used to search your website." :dataIcons="false"
|
desc="The browsers most used to search your website." :dataIcons="true" :iconProvider="iconProvider"
|
||||||
:loading="browsersData.pending.value" label="Top Browsers" sub-label="Browsers">
|
:loading="browsersData.pending.value" label="Browsers" sub-label="Browsers">
|
||||||
</BarCardBase>
|
</BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
|
||||||
|
import type { IconProvider } from './Base.vue';
|
||||||
|
|
||||||
|
function iconProvider(e: { _id: string, count: number }): ReturnType<IconProvider> {
|
||||||
|
if (e._id === 'desktop') return ['icon','far fa-desktop'];
|
||||||
|
if (e._id === 'tablet') return ['icon','far fa-tablet'];
|
||||||
|
if (e._id === 'mobile') return ['icon','far fa-mobile'];
|
||||||
|
if (e._id === 'smarttv') return ['icon','far fa-tv'];
|
||||||
|
if (e._id === 'console') return ['icon','far fa-game-console-handheld'];
|
||||||
|
return ['icon', 'far fa-question']
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function transform(data: { _id: string, count: number }[]) {
|
function transform(data: { _id: string, count: number }[]) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
return data.map(e => ({ ...e, _id: e._id == null ? 'unknown' : e._id }))
|
return data.map(e => ({ ...e, _id: e._id == null ? 'unknown' : e._id }))
|
||||||
@@ -34,9 +46,9 @@ async function showMore() {
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2 h-full">
|
||||||
<BarCardBase @showMore="showMore()" @dataReload="devicesData.refresh()" :data="devicesData.data.value || []"
|
<BarCardBase @showMore="showMore()" @dataReload="devicesData.refresh()" :data="devicesData.data.value || []"
|
||||||
:dataIcons="false" desc="The devices most used to access your website." :loading="devicesData.pending.value"
|
:iconProvider="iconProvider" :dataIcons="true" desc="The devices most used to access your website."
|
||||||
label="Top Devices" sub-label="Devices"></BarCardBase>
|
:loading="devicesData.pending.value" label="Devices" sub-label="Devices"></BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,34 +2,42 @@
|
|||||||
|
|
||||||
import type { IconProvider } from '../BarCard/Base.vue';
|
import type { IconProvider } from '../BarCard/Base.vue';
|
||||||
|
|
||||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
function iconProvider(e: { _id: string, flag: string, count: number }): ReturnType<IconProvider> {
|
||||||
if (id === 'self') return ['icon', 'fas fa-link'];
|
if (!e.flag) return ['icon', 'far fa-question']
|
||||||
return [
|
return [
|
||||||
'img',
|
'img',
|
||||||
`https://raw.githubusercontent.com/hampusborgos/country-flags/main/png250px/${id.toLowerCase()}.png`
|
`https://raw.githubusercontent.com/hampusborgos/country-flags/main/png250px/${e.flag.toLowerCase()}.png`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const customIconStyle = `width: 2rem; padding: 1px;`
|
const customIconStyle = `width: 2rem; padding: 1px;`
|
||||||
|
|
||||||
const geolocationData = useFetch('/api/data/countries', {
|
const geolocationData = useFetch('/api/data/countries', {
|
||||||
headers: useComputedHeaders({ limit: 10, }), lazy: true
|
headers: useComputedHeaders({ limit: 10, }), lazy: true,
|
||||||
|
transform: (e) => {
|
||||||
|
if (!e) return e;
|
||||||
|
return e.map(k => {
|
||||||
|
return { ...k, flag: k._id, _id: getCountryName(k._id) ?? k._id }
|
||||||
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||||
|
|
||||||
async function showMore() {
|
async function showMore() {
|
||||||
dialogBarData.value=[];
|
dialogBarData.value = [];
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
const res = await $fetch('/api/data/countries', {
|
const res = await $fetch('/api/data/countries', {
|
||||||
headers: useComputedHeaders({limit: 1000}).value
|
headers: useComputedHeaders({ limit: 1000 }).value
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogBarData.value = res?.map(e => {
|
dialogBarData.value = res?.map(k => {
|
||||||
return { ...e, icon: iconProvider(e._id) }
|
return { ...k, flag: k._id, _id: getCountryName(k._id) ?? k._id }
|
||||||
|
}).map(e => {
|
||||||
|
return { ...e, icon: iconProvider(e) }
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
@@ -43,7 +51,7 @@ async function showMore() {
|
|||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<BarCardBase @showMore="showMore()" @dataReload="geolocationData.refresh()"
|
<BarCardBase @showMore="showMore()" @dataReload="geolocationData.refresh()"
|
||||||
:data="geolocationData.data.value || []" :dataIcons="false" :loading="geolocationData.pending.value"
|
:data="geolocationData.data.value || []" :dataIcons="false" :loading="geolocationData.pending.value"
|
||||||
label="Top Countries" sub-label="Countries" :iconProvider="iconProvider" :customIconStyle="customIconStyle"
|
label="Countries" sub-label="Countries" :iconProvider="iconProvider" :customIconStyle="customIconStyle"
|
||||||
desc=" Lists the countries where users access your website.">
|
desc=" Lists the countries where users access your website.">
|
||||||
</BarCardBase>
|
</BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ async function showMore() {
|
|||||||
<div class="flex flex-col gap-2 h-full">
|
<div class="flex flex-col gap-2 h-full">
|
||||||
<BarCardBase @showMore="showMore()" @dataReload="ossData.refresh()" :data="ossData.data.value || []"
|
<BarCardBase @showMore="showMore()" @dataReload="ossData.refresh()" :data="ossData.data.value || []"
|
||||||
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="ossData.pending.value" label="Top OS" sub-label="OSs"></BarCardBase>
|
:loading="ossData.pending.value" label="OS" sub-label="OSs"></BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import type { IconProvider } from './Base.vue';
|
import type { IconProvider } from './Base.vue';
|
||||||
|
|
||||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
function iconProvider(e: { _id: string, count: number }): ReturnType<IconProvider> {
|
||||||
if (id === 'self') return ['icon', 'fas fa-link'];
|
if (e._id === 'self') return ['icon', 'fas fa-link'];
|
||||||
return ['img', `https://s2.googleusercontent.com/s2/favicons?domain=${id}&sz=64`]
|
return ['img', `https://s2.googleusercontent.com/s2/favicons?domain=${e._id}&sz=64`]
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementTextTransformer(element: string) {
|
function elementTextTransformer(element: string) {
|
||||||
@@ -22,18 +22,18 @@ const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
|||||||
|
|
||||||
async function showMore() {
|
async function showMore() {
|
||||||
|
|
||||||
dialogBarData.value=[];
|
dialogBarData.value = [];
|
||||||
|
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
isDataLoading.value = true;
|
isDataLoading.value = true;
|
||||||
|
|
||||||
const res = await $fetch('/api/data/referrers', {
|
const res = await $fetch('/api/data/referrers', {
|
||||||
headers: useComputedHeaders({limit: 1000}).value
|
headers: useComputedHeaders({ limit: 1000 }).value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
dialogBarData.value = res?.map(e => {
|
dialogBarData.value = res?.map(e => {
|
||||||
return { ...e, icon: iconProvider(e._id) }
|
return { ...e, icon: iconProvider(e as any) }
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
isDataLoading.value = false;
|
isDataLoading.value = false;
|
||||||
@@ -47,7 +47,7 @@ async function showMore() {
|
|||||||
<BarCardBase @showMore="showMore()" :elementTextTransformer="elementTextTransformer"
|
<BarCardBase @showMore="showMore()" :elementTextTransformer="elementTextTransformer"
|
||||||
:iconProvider="iconProvider" @dataReload="referrersData.refresh()" :showLink=true
|
:iconProvider="iconProvider" @dataReload="referrersData.refresh()" :showLink=true
|
||||||
:data="referrersData.data.value || []" :interactive="false" desc="Where users find your website."
|
:data="referrersData.data.value || []" :interactive="false" desc="Where users find your website."
|
||||||
:dataIcons="true" :loading="referrersData.pending.value" label="Top Referrers" sub-label="Referrers">
|
:dataIcons="true" :loading="referrersData.pending.value" label="Top Sources" sub-label="Referrers">
|
||||||
</BarCardBase>
|
</BarCardBase>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'highlight.js/styles/stackoverflow-dark.css';
|
|||||||
import hljs from 'highlight.js';
|
import hljs from 'highlight.js';
|
||||||
import CardTitled from './CardTitled.vue';
|
import CardTitled from './CardTitled.vue';
|
||||||
|
|
||||||
|
import { Lit } from 'litlyx-js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
firstInteraction: boolean,
|
firstInteraction: boolean,
|
||||||
refreshInteraction: () => any
|
refreshInteraction: () => any
|
||||||
@@ -19,6 +21,7 @@ onMounted(() => {
|
|||||||
function copyProjectId() {
|
function copyProjectId() {
|
||||||
if (!navigator.clipboard) alert('You can\'t copy in HTTP');
|
if (!navigator.clipboard) alert('You can\'t copy in HTTP');
|
||||||
navigator.clipboard.writeText(project.value?._id?.toString() || '');
|
navigator.clipboard.writeText(project.value?._id?.toString() || '');
|
||||||
|
Lit.event('no_visit_copy_id');
|
||||||
createAlert('Success', 'Project id copied successfully.', 'far fa-circle-check', 5000);
|
createAlert('Success', 'Project id copied successfully.', 'far fa-circle-check', 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +39,7 @@ function copyScript() {
|
|||||||
].join('')
|
].join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lit.event('no_visit_copy_script');
|
||||||
navigator.clipboard.writeText(createScriptText());
|
navigator.clipboard.writeText(createScriptText());
|
||||||
createAlert('Success', 'Script copied successfully.', 'far fa-circle-check', 5000);
|
createAlert('Success', 'Script copied successfully.', 'far fa-circle-check', 5000);
|
||||||
}
|
}
|
||||||
@@ -53,6 +57,7 @@ const scriptText = computed(() => {
|
|||||||
function reloadPage() {
|
function reloadPage() {
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -256,7 +261,9 @@ function reloadPage() {
|
|||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<LyxUiButton type="secondary" to="https://docs.litlyx.com"> Visit documentation
|
<LyxUiButton @click="Lit.event('no_visit_goto_docs')" type="secondary"
|
||||||
|
to="https://docs.litlyx.com">
|
||||||
|
Visit documentation
|
||||||
</LyxUiButton>
|
</LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
</CardTitled>
|
</CardTitled>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ const avgSessionDuration = computed(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="gap-6 px-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 m-cards-wrap:grid-cols-4">
|
<div class="gap-6 px-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-2 m-cards-wrap:grid-cols-4">
|
||||||
|
|
||||||
<DashboardCountCard :ready="!visitsData.pending.value" icon="far fa-earth" text="Total page visits"
|
<DashboardCountCard :ready="!visitsData.pending.value" icon="far fa-earth" text="Total visits"
|
||||||
:value="formatNumberK(visitsData.data.value?.data.reduce((a, e) => a + e, 0) || '...')"
|
:value="formatNumberK(visitsData.data.value?.data.reduce((a, e) => a + e, 0) || '...')"
|
||||||
:avg="formatNumberK(avgVisitDay) + '/day'" :trend="visitsData.data.value?.trend"
|
:avg="formatNumberK(avgVisitDay) + '/day'" :trend="visitsData.data.value?.trend"
|
||||||
:data="visitsData.data.value?.data" :labels="visitsData.data.value?.labels" color="#5655d7">
|
:data="visitsData.data.value?.data" :labels="visitsData.data.value?.labels" color="#5655d7">
|
||||||
@@ -116,7 +116,7 @@ const avgSessionDuration = computed(() => {
|
|||||||
</DashboardCountCard>
|
</DashboardCountCard>
|
||||||
|
|
||||||
|
|
||||||
<DashboardCountCard :ready="!sessionsData.pending.value" icon="far fa-user" text="Unique visits sessions"
|
<DashboardCountCard :ready="!sessionsData.pending.value" icon="far fa-user" text="Unique visitors"
|
||||||
:value="formatNumberK(sessionsData.data.value?.data.reduce((a, e) => a + e, 0) || '...')"
|
:value="formatNumberK(sessionsData.data.value?.data.reduce((a, e) => a + e, 0) || '...')"
|
||||||
:avg="formatNumberK(avgSessionsDay) + '/day'" :trend="sessionsData.data.value?.trend"
|
:avg="formatNumberK(avgSessionsDay) + '/day'" :trend="sessionsData.data.value?.trend"
|
||||||
:data="sessionsData.data.value?.data" :labels="sessionsData.data.value?.labels" color="#4abde8">
|
:data="sessionsData.data.value?.data" :labels="sessionsData.data.value?.labels" color="#4abde8">
|
||||||
@@ -124,7 +124,7 @@ const avgSessionDuration = computed(() => {
|
|||||||
|
|
||||||
|
|
||||||
<DashboardCountCard :ready="!sessionsDurationData.pending.value" icon="far fa-timer"
|
<DashboardCountCard :ready="!sessionsDurationData.pending.value" icon="far fa-timer"
|
||||||
text="Total avg session time" :value="avgSessionDuration" :trend="sessionsDurationData.data.value?.trend"
|
text="Visit duration" :value="avgSessionDuration" :trend="sessionsDurationData.data.value?.trend"
|
||||||
:data="sessionsDurationData.data.value?.data" :labels="sessionsDurationData.data.value?.labels"
|
:data="sessionsDurationData.data.value?.data" :labels="sessionsDurationData.data.value?.labels"
|
||||||
color="#f56523">
|
color="#f56523">
|
||||||
</DashboardCountCard>
|
</DashboardCountCard>
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const isDone = ref<boolean>(false);
|
const isDone = ref<boolean>(false);
|
||||||
|
const canDelete = ref<boolean>(false);
|
||||||
|
|
||||||
async function deleteData() {
|
async function deleteData() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (props.deleteData.isAll) {
|
if (props.deleteData.isAll) {
|
||||||
|
await $fetch('/api/settings/delete_all', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: useComputedHeaders({ useSnapshotDates: false }).value,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
await $fetch('/api/settings/delete_domain', {
|
await $fetch('/api/settings/delete_domain', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -60,9 +64,14 @@ async function deleteData() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|
||||||
|
<div v-if="!isDone">
|
||||||
|
<UCheckbox v-model="canDelete" label="Confirm data delete"></UCheckbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="!isDone" class="flex justify-end gap-2">
|
<div v-if="!isDone" class="flex justify-end gap-2">
|
||||||
<LyxUiButton type="secondary" @click="emit('cancel')"> Cancel </LyxUiButton>
|
<LyxUiButton type="secondary" @click="emit('cancel')"> Cancel </LyxUiButton>
|
||||||
<LyxUiButton @click="deleteData()" :type="buttonType"> Confirm </LyxUiButton>
|
<LyxUiButton :disabled="!canDelete" @click="canDelete ? deleteData() : () => { }" :type="buttonType"> Confirm </LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isDone" class="flex justify-end w-full">
|
<div v-if="isDone" class="flex justify-end w-full">
|
||||||
|
|||||||
42
dashboard/composables/useCountryName.ts
Normal file
42
dashboard/composables/useCountryName.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const countryMap: Record<string, string> = {
|
||||||
|
RW: "Rwanda", SO: "Somalia", YE: "Yemen", IQ: "Iraq", SA: "Saudi Arabia", IR: "Iran", CY: "Cyprus", TZ: "Tanzania",
|
||||||
|
SY: "Syria", AM: "Armenia", KE: "Kenya", CD: "Congo", DJ: "Djibouti", UG: "Uganda", CF: "Central African Republic",
|
||||||
|
SC: "Seychelles", JO: "Jordan", LB: "Lebanon", KW: "Kuwait", OM: "Oman", QA: "Qatar", BH: "Bahrain", AE: "United Arab Emirates",
|
||||||
|
IL: "Israel", TR: "Türkiye", ET: "Ethiopia", ER: "Eritrea", EG: "Egypt", SD: "Sudan", GR: "Greece", BI: "Burundi",
|
||||||
|
EE: "Estonia", LV: "Latvia", AZ: "Azerbaijan", LT: "Lithuania", SJ: "Svalbard and Jan Mayen", GE: "Georgia", MD: "Moldova",
|
||||||
|
BY: "Belarus", FI: "Finland", AX: "Åland Islands", UA: "Ukraine", MK: "North Macedonia", HU: "Hungary", BG: "Bulgaria",
|
||||||
|
AL: "Albania", PL: "Poland", RO: "Romania", XK: "Kosovo", ZW: "Zimbabwe", ZM: "Zambia", KM: "Comoros", MW: "Malawi",
|
||||||
|
LS: "Lesotho", BW: "Botswana", MU: "Mauritius", SZ: "Eswatini", RE: "Réunion", ZA: "South Africa", YT: "Mayotte",
|
||||||
|
MZ: "Mozambique", MG: "Madagascar", AF: "Afghanistan", PK: "Pakistan", BD: "Bangladesh", TM: "Turkmenistan", TJ: "Tajikistan",
|
||||||
|
LK: "Sri Lanka", BT: "Bhutan", IN: "India", MV: "Maldives", IO: "British Indian Ocean Territory", NP: "Nepal", MM: "Myanmar",
|
||||||
|
UZ: "Uzbekistan", KZ: "Kazakhstan", KG: "Kyrgyzstan", TF: "French Southern Territories", HM: "Heard and McDonald Islands",
|
||||||
|
CC: "Cocos (Keeling) Islands", PW: "Palau", VN: "Vietnam", TH: "Thailand", ID: "Indonesia", LA: "Laos", TW: "Taiwan",
|
||||||
|
PH: "Philippines", MY: "Malaysia", CN: "China", HK: "Hong Kong", BN: "Brunei", MO: "Macao", KH: "Cambodia", KR: "South Korea",
|
||||||
|
JP: "Japan", KP: "North Korea", SG: "Singapore", CK: "Cook Islands", TL: "Timor-Leste", RU: "Russia", MN: "Mongolia",
|
||||||
|
AU: "Australia", CX: "Christmas Island", MH: "Marshall Islands", FM: "Federated States of Micronesia", PG: "Papua New Guinea",
|
||||||
|
SB: "Solomon Islands", TV: "Tuvalu", NR: "Nauru", VU: "Vanuatu", NC: "New Caledonia", NF: "Norfolk Island", NZ: "New Zealand",
|
||||||
|
FJ: "Fiji", LY: "Libya", CM: "Cameroon", SN: "Senegal", CG: "Congo Republic", PT: "Portugal", LR: "Liberia", CI: "Ivory Coast", GH: "Ghana",
|
||||||
|
GQ: "Equatorial Guinea", NG: "Nigeria", BF: "Burkina Faso", TG: "Togo", GW: "Guinea-Bissau", MR: "Mauritania", BJ: "Benin", GA: "Gabon",
|
||||||
|
SL: "Sierra Leone", ST: "São Tomé and Príncipe", GI: "Gibraltar", GM: "Gambia", GN: "Guinea", TD: "Chad", NE: "Niger", ML: "Mali",
|
||||||
|
EH: "Western Sahara", TN: "Tunisia", ES: "Spain", MA: "Morocco", MT: "Malta", DZ: "Algeria", FO: "Faroe Islands", DK: "Denmark",
|
||||||
|
IS: "Iceland", GB: "United Kingdom", CH: "Switzerland", SE: "Sweden", NL: "The Netherlands", AT: "Austria", BE: "Belgium",
|
||||||
|
DE: "Germany", LU: "Luxembourg", IE: "Ireland", MC: "Monaco", FR: "France", AD: "Andorra", LI: "Liechtenstein", JE: "Jersey",
|
||||||
|
IM: "Isle of Man", GG: "Guernsey", SK: "Slovakia", CZ: "Czechia", NO: "Norway", VA: "Vatican City", SM: "San Marino",
|
||||||
|
IT: "Italy", SI: "Slovenia", ME: "Montenegro", HR: "Croatia", BA: "Bosnia and Herzegovina", AO: "Angola", NA: "Namibia",
|
||||||
|
SH: "Saint Helena", BV: "Bouvet Island", BB: "Barbados", CV: "Cabo Verde", GY: "Guyana", GF: "French Guiana", SR: "Suriname",
|
||||||
|
PM: "Saint Pierre and Miquelon", GL: "Greenland", PY: "Paraguay", UY: "Uruguay", BR: "Brazil", FK: "Falkland Islands",
|
||||||
|
GS: "South Georgia and the South Sandwich Islands", JM: "Jamaica", DO: "Dominican Republic", CU: "Cuba", MQ: "Martinique",
|
||||||
|
BS: "Bahamas", BM: "Bermuda", AI: "Anguilla", TT: "Trinidad and Tobago", KN: "St Kitts and Nevis", DM: "Dominica",
|
||||||
|
AG: "Antigua and Barbuda", LC: "Saint Lucia", TC: "Turks and Caicos Islands", AW: "Aruba", VG: "British Virgin Islands",
|
||||||
|
VC: "St Vincent and Grenadines", MS: "Montserrat", MF: "Saint Martin", BL: "Saint Barthélemy", GP: "Guadeloupe",
|
||||||
|
GD: "Grenada", KY: "Cayman Islands", BZ: "Belize", SV: "El Salvador", GT: "Guatemala", HN: "Honduras", NI: "Nicaragua",
|
||||||
|
CR: "Costa Rica", VE: "Venezuela", EC: "Ecuador", CO: "Colombia", PA: "Panama", HT: "Haiti", AR: "Argentina", CL: "Chile",
|
||||||
|
BO: "Bolivia", PE: "Peru", MX: "Mexico", PF: "French Polynesia", PN: "Pitcairn Islands", KI: "Kiribati", TK: "Tokelau",
|
||||||
|
TO: "Tonga", WF: "Wallis and Futuna", WS: "Samoa", NU: "Niue", MP: "Northern Mariana Islands", GU: "Guam", PR: "Puerto Rico",
|
||||||
|
VI: "U.S. Virgin Islands", UM: "U.S. Outlying Islands", AS: "American Samoa", CA: "Canada", US: "United States",
|
||||||
|
PS: "Palestine", RS: "Serbia", AQ: "Antarctica", SX: "Sint Maarten", CW: "Curaçao", BQ: "Bonaire", SS: "South Sudan"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCountryName(iso: string) {
|
||||||
|
return countryMap[iso] as string | undefined;
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ const sections: Section[] = [
|
|||||||
entries: [
|
entries: [
|
||||||
{ label: 'Web Analytics', to: '/', icon: 'fal fa-table-layout' },
|
{ label: 'Web Analytics', to: '/', icon: 'fal fa-table-layout' },
|
||||||
{ label: 'Custom Events', to: '/events', icon: 'fal fa-square-bolt' },
|
{ label: 'Custom Events', to: '/events', icon: 'fal fa-square-bolt' },
|
||||||
{ label: 'AI Analyst', to: '/analyst', icon: 'fal fa-sparkles' },
|
{ label: 'Ask AI', to: '/analyst', icon: 'fal fa-sparkles' },
|
||||||
{ label: 'Security', to: '/security', icon: 'fal fa-shield' },
|
{ label: 'Security', to: '/security', icon: 'fal fa-shield' },
|
||||||
// { label: 'Insights (soon)', to: '#', icon: 'fal fa-lightbulb', disabled: true },
|
// { label: 'Insights (soon)', to: '#', icon: 'fal fa-lightbulb', disabled: true },
|
||||||
// { label: 'Links (soon)', to: '#', icon: 'fal fa-globe-pointer', disabled: true },
|
// { label: 'Links (soon)', to: '#', icon: 'fal fa-globe-pointer', disabled: true },
|
||||||
|
|||||||
@@ -4,8 +4,26 @@ import type { AdminProjectsList } from '~/server/api/admin/projects';
|
|||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const timeRange = ref<number>(9);
|
||||||
|
|
||||||
|
function setTimeRange(n: number) {
|
||||||
|
timeRange.value = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeRangeTimestamp = computed(()=>{
|
||||||
|
if (timeRange.value == 1) return Date.now() - 1000 * 60 * 60 * 24;
|
||||||
|
if (timeRange.value == 2) return Date.now() - 1000 * 60 * 60 * 24 * 7;
|
||||||
|
if (timeRange.value == 3) return Date.now() - 1000 * 60 * 60 * 24 * 30;
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const { data: projectsAggregatedResponseData } = await useFetch<AdminProjectsList[]>('/api/admin/projects', signHeaders());
|
const { data: projectsAggregatedResponseData } = await useFetch<AdminProjectsList[]>('/api/admin/projects', signHeaders());
|
||||||
const { data: counts } = await useFetch('/api/admin/counts', signHeaders());
|
const { data: counts } = await useFetch(()=> `/api/admin/counts?from=${timeRangeTimestamp.value}`, signHeaders());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function onHideClicked() {
|
function onHideClicked() {
|
||||||
isAdminHidden.value = true;
|
isAdminHidden.value = true;
|
||||||
@@ -17,6 +35,8 @@ const projectsAggregated = computed(() => {
|
|||||||
const sumVisitsA = a.projects.reduce((pa, pe) => pa + (pe.counts?.visits || 0) + (pe.counts?.events || 0), 0);
|
const sumVisitsA = a.projects.reduce((pa, pe) => pa + (pe.counts?.visits || 0) + (pe.counts?.events || 0), 0);
|
||||||
const sumVisitsB = b.projects.reduce((pa, pe) => pa + (pe.counts?.visits || 0) + (pe.counts?.events || 0), 0);
|
const sumVisitsB = b.projects.reduce((pa, pe) => pa + (pe.counts?.visits || 0) + (pe.counts?.events || 0), 0);
|
||||||
return sumVisitsB - sumVisitsA;
|
return sumVisitsB - sumVisitsA;
|
||||||
|
}).filter(e=>{
|
||||||
|
return new Date(e.created_at).getTime() >= timeRangeTimestamp.value
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,6 +116,8 @@ function getLogBg(last_logged_at?: string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@@ -120,6 +142,14 @@ function getLogBg(last_logged_at?: string) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Card class="p-2 flex gap-10 items-center justify-center">
|
||||||
|
<div :class="{ 'text-red-200': timeRange == 1 }" @click="setTimeRange(1)"> Last day </div>
|
||||||
|
<div :class="{ 'text-red-200': timeRange == 2 }" @click="setTimeRange(2)"> Last week </div>
|
||||||
|
<div :class="{ 'text-red-200': timeRange == 3 }" @click="setTimeRange(3)"> Last month </div>
|
||||||
|
<div :class="{ 'text-red-200': timeRange == 9 }" @click="setTimeRange(9)"> All </div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card class="p-4">
|
<Card class="p-4">
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-1">
|
<div class="grid grid-cols-2 gap-1">
|
||||||
@@ -133,7 +163,7 @@ function getLogBg(last_logged_at?: string) {
|
|||||||
Total visits: {{ formatNumberK(totalVisits) }}
|
Total visits: {{ formatNumberK(totalVisits) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Active: {{ activeProjects }} |
|
Active: {{ activeProjects }} |
|
||||||
Dead: {{ (counts?.projects || 0) - activeProjects }}
|
Dead: {{ (counts?.projects || 0) - activeProjects }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -144,10 +174,6 @@ function getLogBg(last_logged_at?: string) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<!-- <USelectMenu></USelectMenu> -->
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<div v-for="item of projectsAggregated || []"
|
<div v-for="item of projectsAggregated || []"
|
||||||
class="bg-menu p-4 rounded-xl flex flex-col gap-2 w-full relative">
|
class="bg-menu p-4 rounded-xl flex flex-col gap-2 w-full relative">
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
|
|||||||
@@ -55,22 +55,11 @@ const showDashboard = computed(() => project.value && firstInteraction.data.valu
|
|||||||
|
|
||||||
<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">
|
|
||||||
<BarCardWebsites :key="refreshKey"></BarCardWebsites>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<BarCardReferrers :key="refreshKey"></BarCardReferrers>
|
<BarCardReferrers :key="refreshKey"></BarCardReferrers>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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-1">
|
<div class="flex-1">
|
||||||
<BarCardBrowsers :key="refreshKey"></BarCardBrowsers>
|
<BarCardWebsites :key="refreshKey"></BarCardWebsites>
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<BarCardOperatingSystems :key="refreshKey"></BarCardOperatingSystems>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,6 +75,17 @@ const showDashboard = computed(() => project.value && firstInteraction.data.valu
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<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-1">
|
||||||
|
<BarCardBrowsers :key="refreshKey"></BarCardBrowsers>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<BarCardOperatingSystems :key="refreshKey"></BarCardOperatingSystems>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
definePageMeta({ layout: 'none' });
|
definePageMeta({ layout: 'none' });
|
||||||
|
|
||||||
|
import { Lit } from 'litlyx-js';
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const isNoAuth = ref<boolean>(config.public.AUTH_MODE == 'NO_AUTH');
|
const isNoAuth = ref<boolean>(config.public.AUTH_MODE == 'NO_AUTH');
|
||||||
|
|
||||||
@@ -52,6 +54,8 @@ async function handleOnSuccess(response: any) {
|
|||||||
body: JSON.stringify({ code: response.code })
|
body: JSON.stringify({ code: response.code })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Lit.event('google_login_signup');
|
||||||
|
|
||||||
if (result.error) return alert('Error during login, please try again');
|
if (result.error) return alert('Error during login, please try again');
|
||||||
|
|
||||||
setToken(result.access_token);
|
setToken(result.access_token);
|
||||||
@@ -120,7 +124,7 @@ function goBackToEmailLogin() {
|
|||||||
async function signInWithCredentials() {
|
async function signInWithCredentials() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await $fetch<{error:true, message:string} | {error: false, access_token:string}>('/api/auth/login', {
|
const result = await $fetch<{ error: true, message: string } | { error: false, access_token: string }>('/api/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email: email.value, password: password.value })
|
body: JSON.stringify({ email: email.value, password: password.value })
|
||||||
@@ -224,7 +228,8 @@ async function signInWithCredentials() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<RouterLink tag="div" to="/register" class="mt-4 text-center text-lyx-text-dark underline cursor-pointer z-[100]">
|
<RouterLink tag="div" to="/register"
|
||||||
|
class="mt-4 text-center text-lyx-text-dark underline cursor-pointer z-[100]">
|
||||||
You don't have an account ? Sign up
|
You don't have an account ? Sign up
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
definePageMeta({ layout: 'none' });
|
definePageMeta({ layout: 'none' });
|
||||||
|
|
||||||
|
import { Lit } from 'litlyx-js';
|
||||||
|
|
||||||
const emailSended = ref<boolean>(false);
|
const emailSended = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -29,6 +30,9 @@ async function registerAccount() {
|
|||||||
body: JSON.stringify({ email: email.value, password: password.value })
|
body: JSON.stringify({ email: email.value, password: password.value })
|
||||||
});
|
});
|
||||||
if (res.error === true) return alert(res.message);
|
if (res.error === true) return alert(res.message);
|
||||||
|
|
||||||
|
Lit.event('email_signup');
|
||||||
|
|
||||||
emailSended.value = true;
|
emailSended.value = true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
alert('Something went wrong');
|
alert('Something went wrong');
|
||||||
@@ -113,7 +117,8 @@ async function registerAccount() {
|
|||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!emailSended" class="text-[.9rem] poppins mt-5 xl:mt-20 text-text-sub text-center relative z-[2]">
|
<div v-if="!emailSended"
|
||||||
|
class="text-[.9rem] poppins mt-5 xl:mt-20 text-text-sub text-center relative z-[2]">
|
||||||
By continuing you are accepting
|
By continuing you are accepting
|
||||||
<br>
|
<br>
|
||||||
our
|
our
|
||||||
|
|||||||
@@ -8,9 +8,16 @@ export default defineEventHandler(async event => {
|
|||||||
if (!userData?.logged) return;
|
if (!userData?.logged) return;
|
||||||
if (!userData.user.roles.includes('ADMIN')) return;
|
if (!userData.user.roles.includes('ADMIN')) return;
|
||||||
|
|
||||||
|
const { from } = getQuery(event);
|
||||||
|
|
||||||
const projectsCount = await ProjectModel.countDocuments({});
|
const date = new Date(parseInt(from as any));
|
||||||
const usersCount = await UserModel.countDocuments({});
|
|
||||||
|
const projectsCount = await ProjectModel.countDocuments({
|
||||||
|
created_at: { $gte: date }
|
||||||
|
});
|
||||||
|
const usersCount = await UserModel.countDocuments({
|
||||||
|
created_at: { $gte: date }
|
||||||
|
});
|
||||||
|
|
||||||
return { users: usersCount, projects: projectsCount }
|
return { users: usersCount, projects: projectsCount }
|
||||||
|
|
||||||
|
|||||||
36
dashboard/server/api/settings/delete_all.delete.ts
Normal file
36
dashboard/server/api/settings/delete_all.delete.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { SessionModel } from "@schema/metrics/SessionSchema";
|
||||||
|
import { VisitModel } from "@schema/metrics/VisitSchema";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { getRequestData } from "~/server/utils/getRequestData";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project_id } = data;
|
||||||
|
|
||||||
|
taskDeleteAll(project_id);
|
||||||
|
|
||||||
|
return { ok: true }
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function taskDeleteAll(project_id: Types.ObjectId) {
|
||||||
|
|
||||||
|
console.log('Deletation all started');
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
await VisitModel.deleteMany({ project_id });
|
||||||
|
await SessionModel.deleteMany({ project_id });
|
||||||
|
await EventModel.deleteMany({ project_id });
|
||||||
|
|
||||||
|
const s = (Date.now() - start) / 1000;
|
||||||
|
|
||||||
|
console.log(`Deletation all done in ${s.toFixed(2)} seconds`);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -85,10 +85,10 @@ async function taskDeleteDomain(project_id: Types.ObjectId, domain: string, dele
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (deleteEvents === true) {
|
if (deleteEvents === true) {
|
||||||
const sessions = data.flatMap(e => e.sessions).map(e => e._id.toString());
|
const events = data.flatMap(e => e.events).map(e => e._id.toString());
|
||||||
const batchSize = 1000;
|
const batchSize = 1000;
|
||||||
for (let i = 0; i < sessions.length; i += batchSize) {
|
for (let i = 0; i < events.length; i += batchSize) {
|
||||||
const batch = sessions.slice(i, i + batchSize);
|
const batch = events.slice(i, i + batchSize);
|
||||||
await EventModel.deleteMany({ _id: { $in: batch } });
|
await EventModel.deleteMany({ _id: { $in: batch } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user