mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
rewrite
This commit is contained in:
2
TODO
2
TODO
@@ -4,4 +4,4 @@
|
|||||||
- Show more on Dashboard cards
|
- Show more on Dashboard cards
|
||||||
- Fix devices Dashboard card, replace empty with "unkwnwn"
|
- Fix devices Dashboard card, replace empty with "unkwnwn"
|
||||||
- Component first interaction must make the request inside
|
- Component first interaction must make the request inside
|
||||||
- Reactivity on project delete
|
- Reactivity on project delete (update dropdown)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
import type { TProject } from '@schema/ProjectSchema';
|
|
||||||
import CreateSnapshot from './dialog/CreateSnapshot.vue';
|
import CreateSnapshot from './dialog/CreateSnapshot.vue';
|
||||||
|
|
||||||
export type Entry = {
|
export type Entry = {
|
||||||
@@ -27,7 +26,8 @@ type Props = {
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const { user, userRoles, setLoggedUser } = useLoggedUser()
|
const { userRoles, setLoggedUser } = useLoggedUser();
|
||||||
|
const { projectList } = useProject();
|
||||||
|
|
||||||
const debugMode = process.dev;
|
const debugMode = process.dev;
|
||||||
|
|
||||||
@@ -100,9 +100,6 @@ function onLogout() {
|
|||||||
router.push('/login');
|
router.push('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { projects } = useProjectsList();
|
|
||||||
const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
const { data: maxProjects } = useFetch("/api/user/max_projects", {
|
const { data: maxProjects } = useFetch("/api/user/max_projects", {
|
||||||
headers: computed(() => {
|
headers: computed(() => {
|
||||||
return {
|
return {
|
||||||
@@ -111,9 +108,6 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPremium = computed(() => {
|
|
||||||
return activeProject.value?.premium;
|
|
||||||
})
|
|
||||||
|
|
||||||
const pricingDrawer = usePricingDrawer();
|
const pricingDrawer = usePricingDrawer();
|
||||||
|
|
||||||
@@ -147,7 +141,7 @@ const pricingDrawer = usePricingDrawer();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<NuxtLink to="/project_creation" v-if="projects && (projects.length < (maxProjects || 1))"
|
<NuxtLink to="/project_creation" v-if="projectList && (projectList.length < (maxProjects || 1))"
|
||||||
class="flex items-center text-[.8rem] gap-1 justify-end pt-2 pr-2 text-lyx-text-dark hover:text-lyx-text cursor-pointer">
|
class="flex items-center text-[.8rem] gap-1 justify-end pt-2 pr-2 text-lyx-text-dark hover:text-lyx-text cursor-pointer">
|
||||||
<div><i class="fas fa-plus"></i></div>
|
<div><i class="fas fa-plus"></i></div>
|
||||||
<div> Create new project </div>
|
<div> Create new project </div>
|
||||||
@@ -256,7 +250,7 @@ const pricingDrawer = usePricingDrawer();
|
|||||||
<div class="manrope grow">
|
<div class="manrope grow">
|
||||||
{{ entry.label }}
|
{{ entry.label }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="entry.premiumOnly && !isPremium" class="flex items-center">
|
<div v-if="entry.premiumOnly && !userRoles.isPremium" class="flex items-center">
|
||||||
<i class="fal fa-lock"></i>
|
<i class="fal fa-lock"></i>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const { project } = useProject();
|
||||||
const { createAlert } = useAlert();
|
const { createAlert } = useAlert();
|
||||||
|
|
||||||
import 'highlight.js/styles/stackoverflow-dark.css';
|
import 'highlight.js/styles/stackoverflow-dark.css';
|
||||||
@@ -18,7 +18,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(activeProject.value?._id?.toString() || '');
|
navigator.clipboard.writeText(project.value?._id?.toString() || '');
|
||||||
createAlert('Success', 'Project id copied successfully.', 'far fa-circle-check', 5000);
|
createAlert('Success', 'Project id copied successfully.', 'far fa-circle-check', 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ function copyScript() {
|
|||||||
const createScriptText = () => {
|
const createScriptText = () => {
|
||||||
return [
|
return [
|
||||||
'<script defer ',
|
'<script defer ',
|
||||||
`data-project="${activeProject.value?._id}" `,
|
`data-project="${project.value?._id}" `,
|
||||||
'src="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js"></',
|
'src="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js"></',
|
||||||
'script>'
|
'script>'
|
||||||
].join('')
|
].join('')
|
||||||
@@ -43,7 +43,7 @@ function copyScript() {
|
|||||||
|
|
||||||
const scriptText = computed(() => {
|
const scriptText = computed(() => {
|
||||||
return [
|
return [
|
||||||
`<script defer data-project="${activeProject.value?._id.toString()}"`,
|
`<script defer data-project="${project.value?._id.toString()}"`,
|
||||||
`\nsrc="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js">\n<`,
|
`\nsrc="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js">\n<`,
|
||||||
`/script>`
|
`/script>`
|
||||||
].join('');
|
].join('');
|
||||||
@@ -57,7 +57,7 @@ function reloadPage() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div v-if="!firstInteraction && activeProject" class="mt-[5vh] flex flex-col">
|
<div v-if="!firstInteraction && project" class="mt-[5vh] flex flex-col">
|
||||||
|
|
||||||
<div class="flex gap-4 items-center justify-center">
|
<div class="flex gap-4 items-center justify-center">
|
||||||
<div class="animate-pulse w-[1.5rem] h-[1.5rem] bg-accent rounded-full"> </div>
|
<div class="animate-pulse w-[1.5rem] h-[1.5rem] bg-accent rounded-full"> </div>
|
||||||
@@ -109,7 +109,7 @@ function reloadPage() {
|
|||||||
<CardTitled class="h-full w-full" title="Project id"
|
<CardTitled class="h-full w-full" title="Project id"
|
||||||
sub="This is the identifier for this project, used to forward data">
|
sub="This is the identifier for this project, used to forward data">
|
||||||
<div class="flex flex-col items-end">
|
<div class="flex flex-col items-end">
|
||||||
<div class="w-full text-[.9rem] text-[#acacac]"> {{ activeProject?._id }} </div>
|
<div class="w-full text-[.9rem] text-[#acacac]"> {{ project?._id }} </div>
|
||||||
<LyxUiButton type="secondary" @click="copyProjectId()"> Copy </LyxUiButton>
|
<LyxUiButton type="secondary" @click="copyProjectId()"> Copy </LyxUiButton>
|
||||||
</div>
|
</div>
|
||||||
</CardTitled>
|
</CardTitled>
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import type { TProject } from '@schema/ProjectSchema';
|
|||||||
|
|
||||||
const { user } = useLoggedUser()
|
const { user } = useLoggedUser()
|
||||||
|
|
||||||
const { projectList, actions, project } = useProject();
|
const { projectList, guestProjectList, actions, project } = useProject();
|
||||||
const { data: guestProjects } = useGuestProjectsList()
|
|
||||||
|
|
||||||
const selectorProjects = computed(() => {
|
const selectorProjects = computed(() => {
|
||||||
const result: TProject[] = [];
|
const result: TProject[] = [];
|
||||||
if (projectList.value) result.push(...projectList.value);
|
if (projectList.value) result.push(...projectList.value);
|
||||||
if (guestProjects.value) result.push(...guestProjects.value);
|
if (guestProjectList.value) result.push(...guestProjectList.value);
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ const chartData = ref<ChartData<'doughnut'>>({
|
|||||||
|
|
||||||
const { doughnutChartProps, doughnutChartRef } = useDoughnutChart({ chartData: chartData, options: chartOptions });
|
const { doughnutChartProps, doughnutChartRef } = useDoughnutChart({ chartData: chartData, options: chartOptions });
|
||||||
|
|
||||||
const activeProjectId = useActiveProjectId();
|
const { projectId } = useProject();
|
||||||
|
|
||||||
const { safeSnapshotDates } = useSnapshot();
|
const { safeSnapshotDates } = useSnapshot();
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ const headers = computed(() => {
|
|||||||
'Authorization': authorizationHeaderComputed.value,
|
'Authorization': authorizationHeaderComputed.value,
|
||||||
'x-schema': 'events',
|
'x-schema': 'events',
|
||||||
'x-limit': "6",
|
'x-limit': "6",
|
||||||
'x-pid': activeProjectId.data.value || ''
|
'x-pid': projectId.value || ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function showAnomalyInfoAlert() {
|
|||||||
|
|
||||||
<div class="flex gap-2 items-center text-text/90 justify-center md:justify-start">
|
<div class="flex gap-2 items-center text-text/90 justify-center md:justify-start">
|
||||||
<div class="animate-pulse w-[1rem] h-[1rem] bg-green-400 rounded-full"> </div>
|
<div class="animate-pulse w-[1rem] h-[1rem] bg-green-400 rounded-full"> </div>
|
||||||
<div class="poppins font-medium text-[1.2rem]"> {{ onlineUsers }} Online users</div>
|
<div class="poppins font-medium text-[1.2rem]"> {{ onlineUsers.data }} Online users</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ChartData, ChartOptions } from 'chart.js';
|
import type { ChartData, ChartOptions } from 'chart.js';
|
||||||
import { defineChartComponent } from 'vue-chart-3';
|
import { defineChartComponent } from 'vue-chart-3';
|
||||||
registerChartComponents();
|
|
||||||
|
|
||||||
const FunnelChart = defineChartComponent('funnel', 'funnel');
|
const FunnelChart = defineChartComponent('funnel', 'funnel');
|
||||||
|
|
||||||
@@ -90,17 +89,13 @@ onMounted(async () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeProjectId = useActiveProjectId();
|
|
||||||
const { safeSnapshotDates } = useSnapshot();
|
|
||||||
|
|
||||||
const eventsCount = await useFetch<{ _id: string, count: number }[]>(`/api/data/query`, {
|
const eventsCount = await useFetch<{ _id: string, count: number }[]>(`/api/data/query`, {
|
||||||
...signHeaders({
|
lazy: true, headers: useComputedHeaders({
|
||||||
'x-pid': activeProjectId.data.value || '',
|
limit: 1000,
|
||||||
'x-schema': 'events',
|
custom: {
|
||||||
'x-from': safeSnapshotDates.value.from,
|
'schema': 'events'
|
||||||
'x-to': safeSnapshotDates.value.to,
|
}
|
||||||
'x-query-limit': '1000'
|
})
|
||||||
}), lazy: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
const eventNames = ref<string[]>([]);
|
const eventNames = await useFetch<string[]>(`/api/data/events_data/names`, {
|
||||||
|
headers: useComputedHeaders()
|
||||||
|
});
|
||||||
|
|
||||||
const selectedEventName = ref<string>();
|
const selectedEventName = ref<string>();
|
||||||
const metadataFields = ref<string[]>([]);
|
const metadataFields = ref<string[]>([]);
|
||||||
const selectedMetadataField = ref<string>();
|
const selectedMetadataField = ref<string>();
|
||||||
const metadataFieldGrouped = ref<any[]>([]);
|
const metadataFieldGrouped = ref<any[]>([]);
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
eventNames.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/names`, signHeaders());
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(selectedEventName, () => {
|
watch(selectedEventName, () => {
|
||||||
getMetadataFields();
|
getMetadataFields();
|
||||||
@@ -21,7 +20,9 @@ watch(selectedMetadataField, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function getMetadataFields() {
|
async function getMetadataFields() {
|
||||||
metadataFields.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/metadata_fields?name=${selectedEventName.value}`, signHeaders());
|
metadataFields.value = await $fetch<string[]>(`/api/metrics/events_data/metadata_fields?name=${selectedEventName.value}`, {
|
||||||
|
headers: useComputedHeaders().value
|
||||||
|
});
|
||||||
selectedMetadataField.value = undefined;
|
selectedMetadataField.value = undefined;
|
||||||
currentSearchText.value = "";
|
currentSearchText.value = "";
|
||||||
}
|
}
|
||||||
@@ -38,10 +39,12 @@ async function getMetadataFieldGrouped() {
|
|||||||
name: selectedEventName.value,
|
name: selectedEventName.value,
|
||||||
field: selectedMetadataField.value
|
field: selectedMetadataField.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
|
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
|
||||||
|
|
||||||
metadataFieldGrouped.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/metadata_field_group?${queryParamsString}`, signHeaders());
|
metadataFieldGrouped.value = await $fetch<string[]>(`/api/metrics/events_data/metadata_field_group?${queryParamsString}`, {
|
||||||
|
headers: useComputedHeaders().value
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ const canSearch = computed(() => {
|
|||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
||||||
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
|
placeholder="Select an event" :options="eventNames.data.value || []" v-model="selectedEventName">
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
|
|
||||||
<USelectMenu v-if="metadataFields.length > 0" searchable searchable-placeholder="Search a field..."
|
<USelectMenu v-if="metadataFields.length > 0" searchable searchable-placeholder="Search a field..."
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import DateService, { type Slice } from '@services/DateService';
|
|||||||
const props = defineProps<{ slice: Slice }>();
|
const props = defineProps<{ slice: Slice }>();
|
||||||
const slice = computed(() => props.slice);
|
const slice = computed(() => props.slice);
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
|
||||||
const { safeSnapshotDates } = useSnapshot()
|
const { safeSnapshotDates } = useSnapshot()
|
||||||
|
|
||||||
const body = computed(() => {
|
const body = computed(() => {
|
||||||
@@ -90,8 +89,10 @@ function onResponse(e: any) {
|
|||||||
if (e.response.status != 500) errorData.value = { errored: false, text: '' }
|
if (e.response.status != 500) errorData.value = { errored: false, text: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventsStackedData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events_stacked`, {
|
const eventsStackedData = useFetch(`/api/timeline/events_stacked`, {
|
||||||
method: 'POST', body, lazy: true, immediate: false, transform: transformResponse, ...signHeaders(),
|
method: 'POST', body, lazy: true, immediate: false,
|
||||||
|
transform: transformResponse,
|
||||||
|
headers: useComputedHeaders(),
|
||||||
onResponseError,
|
onResponseError,
|
||||||
onResponse
|
onResponse
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const eventNames = await useFetch<string[]>(`/api/data/events_data/names`, {
|
||||||
|
headers: useComputedHeaders()
|
||||||
|
});
|
||||||
|
|
||||||
const eventNames = ref<string[]>([]);
|
|
||||||
const selectedEventName = ref<string>();
|
const selectedEventName = ref<string>();
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
eventNames.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/names`, signHeaders());
|
|
||||||
});
|
|
||||||
|
|
||||||
const userFlowData = ref<any>();
|
const userFlowData = ref<any>();
|
||||||
const analyzing = ref<boolean>(false);
|
const analyzing = ref<boolean>(false);
|
||||||
@@ -26,7 +24,10 @@ async function getUserFlowData() {
|
|||||||
|
|
||||||
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
|
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
|
||||||
|
|
||||||
userFlowData.value = await $fetch(`/api/metrics/${activeProject.value?._id.toString()}/events/flow_from_name?${queryParamsString}`, signHeaders());
|
userFlowData.value = await $fetch(`/api/data/events_data/flow_from_name?${queryParamsString}`, {
|
||||||
|
headers: useComputedHeaders().value
|
||||||
|
});
|
||||||
|
|
||||||
analyzing.value = false;
|
analyzing.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,11 +39,12 @@ async function analyzeEvent() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CardTitled title="Event User Flow"
|
<CardTitled title="Event User Flow"
|
||||||
sub="Track your user's journey from external links to in-app events, maintaining a complete view of their path from entry to engagement." class="w-full p-4">
|
sub="Track your user's journey from external links to in-app events, maintaining a complete view of their path from entry to engagement."
|
||||||
|
class="w-full p-4">
|
||||||
|
|
||||||
<div class="p-2 flex flex-col gap-3">
|
<div class="p-2 flex flex-col gap-3">
|
||||||
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
|
||||||
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
|
placeholder="Select an event" :options="eventNames.data.value || []" v-model="selectedEventName">
|
||||||
</USelectMenu>
|
</USelectMenu>
|
||||||
<div v-if="selectedEventName && !analyzing" class="flex justify-center">
|
<div v-if="selectedEventName && !analyzing" class="flex justify-center">
|
||||||
<div @click="analyzeEvent()"
|
<div @click="analyzeEvent()"
|
||||||
|
|||||||
@@ -4,17 +4,9 @@ import type { PricingCardProp } from './PricingCardGeneric.vue';
|
|||||||
|
|
||||||
|
|
||||||
const { data: planData, refresh: refreshPlanData } = useFetch('/api/project/plan', {
|
const { data: planData, refresh: refreshPlanData } = useFetch('/api/project/plan', {
|
||||||
...signHeaders(),
|
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
lazy: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
watch(activeProject, () => {
|
|
||||||
refreshPlanData();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function getPricingsData() {
|
function getPricingsData() {
|
||||||
|
|
||||||
const freePricing: PricingCardProp[] = [
|
const freePricing: PricingCardProp[] = [
|
||||||
@@ -190,15 +182,18 @@ function getPricingsData() {
|
|||||||
return { freePricing, customPricing, slidePricings }
|
return { freePricing, customPricing, slidePricings }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { projectId } = useProject();
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(evt: 'onCloseClick'): void
|
(evt: 'onCloseClick'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
async function onLifetimeUpgradeClick() {
|
async function onLifetimeUpgradeClick() {
|
||||||
const res = await $fetch<string>(`/api/pay/${activeProject.value?._id.toString()}/create-onetime`, {
|
const res = await $fetch<string>(`/api/pay/create-onetime`, {
|
||||||
...signHeaders({ 'content-type': 'application/json' }),
|
...signHeaders({
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'x-pid': projectId.value ?? ''
|
||||||
|
}),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ planId: 2001 })
|
body: JSON.stringify({ planId: 2001 })
|
||||||
})
|
})
|
||||||
@@ -218,7 +213,8 @@ async function onLifetimeUpgradeClick() {
|
|||||||
|
|
||||||
<div class="flex gap-8 mt-10 h-max xl:flex-row flex-col">
|
<div class="flex gap-8 mt-10 h-max xl:flex-row flex-col">
|
||||||
<PricingCardGeneric class="flex-1" :datas="getPricingsData().freePricing"></PricingCardGeneric>
|
<PricingCardGeneric class="flex-1" :datas="getPricingsData().freePricing"></PricingCardGeneric>
|
||||||
<PricingCardGeneric class="flex-1" :datas="getPricingsData().slidePricings" :default-index="2"></PricingCardGeneric>
|
<PricingCardGeneric class="flex-1" :datas="getPricingsData().slidePricings" :default-index="2">
|
||||||
|
</PricingCardGeneric>
|
||||||
<PricingCardGeneric class="flex-1" :datas="getPricingsData().customPricing"></PricingCardGeneric>
|
<PricingCardGeneric class="flex-1" :datas="getPricingsData().customPricing"></PricingCardGeneric>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { TApiSettings } from '@schema/ApiSettingsSchema';
|
import type { TApiSettings } from '@schema/ApiSettingsSchema';
|
||||||
import type { SettingsTemplateEntry } from './Template.vue';
|
import type { SettingsTemplateEntry } from './Template.vue';
|
||||||
|
|
||||||
const { project } = useProject();
|
const { project, actions, projectList } = useProject();
|
||||||
|
|
||||||
const entries: SettingsTemplateEntry[] = [
|
const entries: SettingsTemplateEntry[] = [
|
||||||
{ id: 'pname', title: 'Name', text: 'Project name' },
|
{ id: 'pname', title: 'Name', text: 'Project name' },
|
||||||
@@ -21,7 +21,7 @@ const newApiKeyName = ref<string>('');
|
|||||||
async function updateApiKeys() {
|
async function updateApiKeys() {
|
||||||
newApiKeyName.value = '';
|
newApiKeyName.value = '';
|
||||||
apiKeys.value = await $fetch<TApiSettings[]>('/api/keys/get_all', signHeaders({
|
apiKeys.value = await $fetch<TApiSettings[]>('/api/keys/get_all', signHeaders({
|
||||||
'x-pid': project.value?._id.toString() ?? ''
|
'x-pid': project.value?._id.toString() ?? ''
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,10 @@ const canChange = computed(() => {
|
|||||||
async function changeProjectName() {
|
async function changeProjectName() {
|
||||||
await $fetch("/api/project/change_name", {
|
await $fetch("/api/project/change_name", {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
...signHeaders({ 'Content-Type': 'application/json' }),
|
...signHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-pid': project.value?._id.toString() ?? ''
|
||||||
|
}),
|
||||||
body: JSON.stringify({ name: projectNameInputVal.value })
|
body: JSON.stringify({ name: projectNameInputVal.value })
|
||||||
});
|
});
|
||||||
location.reload();
|
location.reload();
|
||||||
@@ -92,14 +95,17 @@ async function deleteProject() {
|
|||||||
|
|
||||||
await $fetch('/api/project/delete', {
|
await $fetch('/api/project/delete', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
...signHeaders({ 'Content-Type': 'application/json' }),
|
...signHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-pid': project.value?._id.toString() ?? ''
|
||||||
|
}),
|
||||||
body: JSON.stringify({ project_id: project.value._id.toString() })
|
body: JSON.stringify({ project_id: project.value._id.toString() })
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectsList = useProjectsList()
|
|
||||||
await projectsList.refresh();
|
|
||||||
|
|
||||||
const firstProjectId = projectsList.data.value?.[0]?._id.toString();
|
await actions.refreshProjectsList()
|
||||||
|
|
||||||
|
const firstProjectId = projectList.value?.[0]?._id.toString();
|
||||||
if (firstProjectId) {
|
if (firstProjectId) {
|
||||||
await setActiveProject(firstProjectId);
|
await setActiveProject(firstProjectId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import dayjs from 'dayjs';
|
|||||||
import type { SettingsTemplateEntry } from './Template.vue';
|
import type { SettingsTemplateEntry } from './Template.vue';
|
||||||
import { getPlanFromId, PREMIUM_PLAN, type PREMIUM_TAG } from '@data/PREMIUM';
|
import { getPlanFromId, PREMIUM_PLAN, type PREMIUM_TAG } from '@data/PREMIUM';
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const { projectId, isGuest } = useProject();
|
||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
const { data: planData, refresh: planRefresh, pending: planPending } = useFetch('/api/project/plan', {
|
const { data: planData, pending: planPending } = useFetch('/api/project/plan', {
|
||||||
...signHeaders(), lazy: true
|
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: customerAddress, refresh: refreshCustomerAddress } = useFetch(`/api/pay/${activeProject.value?._id.toString()}/customer_info`, {
|
const { data: customerAddress } = useFetch(`/api/pay/customer_info`, {
|
||||||
...signHeaders(), lazy: true
|
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
const percent = computed(() => {
|
const percent = computed(() => {
|
||||||
@@ -45,8 +45,8 @@ const prettyExpireDate = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const { data: invoices, refresh: invoicesRefresh, pending: invoicesPending } = useFetch(`/api/pay/${activeProject.value?._id.toString()}/invoices`, {
|
const { data: invoices, pending: invoicesPending } = useFetch(`/api/pay/invoices`, {
|
||||||
...signHeaders(), lazy: true
|
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
function openInvoice(link: string) {
|
function openInvoice(link: string) {
|
||||||
@@ -75,7 +75,7 @@ const entries: SettingsTemplateEntry[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
watch(customerAddress, () => {
|
watch(customerAddress, () => {
|
||||||
console.log('UPDATE',customerAddress.value)
|
console.log('UPDATE', customerAddress.value)
|
||||||
if (!customerAddress.value) return;
|
if (!customerAddress.value) return;
|
||||||
currentBillingInfo.value = customerAddress.value;
|
currentBillingInfo.value = customerAddress.value;
|
||||||
});
|
});
|
||||||
@@ -92,21 +92,22 @@ const currentBillingInfo = ref<any>({
|
|||||||
const { createAlert } = useAlert()
|
const { createAlert } = useAlert()
|
||||||
|
|
||||||
async function saveBillingInfo() {
|
async function saveBillingInfo() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await $fetch(`/api/pay/${activeProject.value?._id.toString()}/update_customer`, {
|
const res = await $fetch(`/api/pay/update_customer`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
...signHeaders({
|
...signHeaders({
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
}),
|
'x-pid': projectId.value ?? ''
|
||||||
body: JSON.stringify(currentBillingInfo.value)
|
}),
|
||||||
});
|
body: JSON.stringify(currentBillingInfo.value)
|
||||||
|
});
|
||||||
|
|
||||||
createAlert('Customer updated','Customer updated successfully', 'far fa-check', 5000);
|
createAlert('Customer updated', 'Customer updated successfully', 'far fa-check', 5000);
|
||||||
|
|
||||||
} catch(ex) {
|
} catch (ex) {
|
||||||
createAlert('Error updating customer','An error occurred while updating the customer', 'far fa-error', 8000);
|
createAlert('Error updating customer', 'An error occurred while updating the customer', 'far fa-error', 8000);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SettingsTemplateEntry } from './Template.vue';
|
import type { SettingsTemplateEntry } from './Template.vue';
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const { projectId } = useProject();
|
||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: 'me', label: '' },
|
{ key: 'me', label: '' },
|
||||||
{ key: 'email', label: 'Email' },
|
{ key: 'email', label: 'Email' },
|
||||||
@@ -15,7 +14,9 @@ const columns = [
|
|||||||
// { key: 'pending', label: 'Pending' },
|
// { key: 'pending', label: 'Pending' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const { data: members, refresh: refreshMembers, pending: pendingMembers } = useFetch('/api/project/members/list', signHeaders());
|
const { data: members, refresh: refreshMembers } = useFetch('/api/project/members/list', {
|
||||||
|
headers: useComputedHeaders({ useSnapshotDates: false })
|
||||||
|
});
|
||||||
|
|
||||||
const showAddMember = ref<boolean>(false);
|
const showAddMember = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -30,7 +31,10 @@ async function kickMember(email: string) {
|
|||||||
|
|
||||||
await $fetch('/api/project/members/kick', {
|
await $fetch('/api/project/members/kick', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
...signHeaders({ 'Content-Type': 'application/json' }),
|
...signHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-pid': projectId.value ?? ''
|
||||||
|
}),
|
||||||
body: JSON.stringify({ email }),
|
body: JSON.stringify({ email }),
|
||||||
onResponseError({ request, response, options }) {
|
onResponseError({ request, response, options }) {
|
||||||
alert(response.statusText);
|
alert(response.statusText);
|
||||||
@@ -55,7 +59,10 @@ async function addMember() {
|
|||||||
|
|
||||||
await $fetch('/api/project/members/add', {
|
await $fetch('/api/project/members/add', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
...signHeaders({ 'Content-Type': 'application/json' }),
|
...signHeaders({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-pid': projectId.value ?? ''
|
||||||
|
}),
|
||||||
body: JSON.stringify({ email: addMemberEmail.value }),
|
body: JSON.stringify({ email: addMemberEmail.value }),
|
||||||
onResponseError({ request, response, options }) {
|
onResponseError({ request, response, options }) {
|
||||||
alert(response.statusText);
|
alert(response.statusText);
|
||||||
@@ -71,10 +78,6 @@ async function addMember() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(activeProject, () => {
|
|
||||||
refreshMembers();
|
|
||||||
})
|
|
||||||
|
|
||||||
const entries: SettingsTemplateEntry[] = [
|
const entries: SettingsTemplateEntry[] = [
|
||||||
{ id: 'add', title: 'Add member', text: 'Add new member to project' },
|
{ id: 'add', title: 'Add member', text: 'Add new member to project' },
|
||||||
{ id: 'members', title: 'Members', text: 'Manage members of current project' },
|
{ id: 'members', title: 'Members', text: 'Manage members of current project' },
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import type { TProject } from "@schema/ProjectSchema";
|
|
||||||
|
|
||||||
const projects = useFetch<TProject[]>('/api/project/list', {
|
|
||||||
key: 'projectslist', ...signHeaders()
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useProjectsList() {
|
|
||||||
return { ...projects, projects: projects.data }
|
|
||||||
}
|
|
||||||
|
|
||||||
const guestProjects = useFetch<TProject[]>('/api/project/list_guest', {
|
|
||||||
key: 'guestProjectslist', ...signHeaders()
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useGuestProjectsList() {
|
|
||||||
return { ...guestProjects, guestProjects: guestProjects.data }
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeProjectId = useFetch<string>(`/api/user/active_project`, {
|
|
||||||
key: 'activeProjectId', ...signHeaders(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const isGuest = computed(() => {
|
|
||||||
if (!guestProjects.data.value) return false;
|
|
||||||
const guestTarget = guestProjects.data.value.find(e => e._id.toString() == activeProjectId.data.value);
|
|
||||||
if (guestTarget) return true;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useActiveProjectId() {
|
|
||||||
return { ...activeProjectId, pid: activeProjectId.data }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useActiveProject() {
|
|
||||||
if (isLiveDemo()) {
|
|
||||||
const { data: liveDemoProject } = useLiveDemo();
|
|
||||||
return liveDemoProject;
|
|
||||||
}
|
|
||||||
return computed(() => {
|
|
||||||
if (!projects.data.value) return;
|
|
||||||
if (!activeProjectId.data.value) return;
|
|
||||||
const target = projects.data.value.find(e => e._id.toString() == activeProjectId.data.value);
|
|
||||||
if (target) return target;
|
|
||||||
if (!guestProjects.data.value) return;
|
|
||||||
const guestTarget = guestProjects.data.value.find(e => e._id.toString() == activeProjectId.data.value);
|
|
||||||
return guestTarget;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function setActiveProject(project_id: string) {
|
|
||||||
changingProject.value = true;
|
|
||||||
await new Promise(e => setTimeout(e, 500));
|
|
||||||
await $fetch<string>(`/api/user/set_active_project?project_id=${project_id}`, signHeaders());
|
|
||||||
await activeProjectId.refresh();
|
|
||||||
changingProject.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const changingProject = ref<boolean>(false);
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export function createRequestOptions(method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', sign: boolean, body?: Record<string, any>, headers: Record<string, string> = {}) {
|
|
||||||
|
|
||||||
|
|
||||||
const requestHeaders = sign ? signHeaders(headers) : headers;
|
|
||||||
let requestBody;
|
|
||||||
|
|
||||||
if (method === 'POST' || method == 'PUT' || method == 'PATCH') {
|
|
||||||
requestBody = body ? JSON.stringify(body) : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
method,
|
|
||||||
headers: requestHeaders,
|
|
||||||
body: requestBody
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
import type { Slice } from "@services/DateService";
|
|
||||||
import DateService from "@services/DateService";
|
|
||||||
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 { VisitsWebsiteAggregated } from "~/server/api/metrics/[project_id]/data/websites";
|
|
||||||
import type { MetricsTimeline } from "~/server/api/metrics/[project_id]/timeline/generic";
|
|
||||||
|
|
||||||
export function useMetricsData() {
|
|
||||||
const activeProject = useActiveProject();
|
|
||||||
const metricsInfo = useFetch<MetricsCounts>(`/api/metrics/${activeProject.value?._id}/counts`, {
|
|
||||||
...signHeaders(),
|
|
||||||
lazy: true
|
|
||||||
});
|
|
||||||
return metricsInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// const { safeSnapshotDates, snapshot } = useSnapshot()
|
|
||||||
// const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
// const createFromToHeaders = (headers: Record<string, string> = {}) => ({
|
|
||||||
// 'x-from': safeSnapshotDates.value.from,
|
|
||||||
// 'x-to': safeSnapshotDates.value.to,
|
|
||||||
// ...headers
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const createFromToBody = (body: Record<string, any> = {}) => ({
|
|
||||||
// from: safeSnapshotDates.value.from,
|
|
||||||
// to: safeSnapshotDates.value.to,
|
|
||||||
// ...body
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export function useFirstInteractionData() {
|
|
||||||
// const activeProject = useActiveProject();
|
|
||||||
// const metricsInfo = useFetch<boolean>(`/api/metrics/${activeProject.value?._id}/first_interaction`, signHeaders());
|
|
||||||
// return metricsInfo;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// export function useTimelineAdvanced<T>(endpoint: string, slice: Ref<Slice>, customBody: Object = {}) {
|
|
||||||
// const response = useCustomFetch<T>(
|
|
||||||
// `/api/metrics/${activeProject.value?._id}/timeline/${endpoint}`,
|
|
||||||
// () => signHeaders({ 'Content-Type': 'application/json' }).headers, {
|
|
||||||
// method: 'POST',
|
|
||||||
// getBody: () => createFromToBody({ slice: slice.value, ...customBody }),
|
|
||||||
// lazy: true,
|
|
||||||
// watchProps: [snapshot, slice]
|
|
||||||
// });
|
|
||||||
// return response;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// export function useTimeline(endpoint: 'visits' | 'sessions' | 'referrers' | 'events_stacked', slice: Ref<Slice>) {
|
|
||||||
// return useTimelineAdvanced<{ _id: string, count: number }[]>(endpoint, slice);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export async function useReferrersTimeline(referrer: string, slice: Ref<Slice>) {
|
|
||||||
// return await useTimelineAdvanced<{ _id: string, count: number }[]>('referrers', slice, { referrer });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function useEventsStackedTimeline(slice: Ref<Slice>) {
|
|
||||||
// return useTimelineAdvanced<{ _id: string, name: string, count: number }[]>('events_stacked', slice);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export async function useTimelineDataRaw(timelineEndpointName: string, slice: SliceName) {
|
|
||||||
// const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
// const response = await $fetch<{ data: MetricsTimeline[], from: string, to: string }>(
|
|
||||||
// `/api/metrics/${activeProject.value?._id}/timeline/${timelineEndpointName}`, {
|
|
||||||
// method: 'POST',
|
|
||||||
// ...signHeaders({ 'Content-Type': 'application/json' }),
|
|
||||||
// body: JSON.stringify({ slice }),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return response;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export async function useTimelineData(timelineEndpointName: string, slice: SliceName) {
|
|
||||||
// const response = await useTimelineDataRaw(timelineEndpointName, slice);
|
|
||||||
// if (!response) return;
|
|
||||||
// const fixed = fixMetrics(response, slice);
|
|
||||||
// return fixed;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function usePagesData(website: string, limit: number = 10) {
|
|
||||||
// const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
// const res = useFetch<VisitsWebsiteAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/pages`, {
|
|
||||||
// ...signHeaders({
|
|
||||||
// 'x-query-limit': limit.toString(),
|
|
||||||
// 'x-website-name': website
|
|
||||||
// }),
|
|
||||||
// key: `pages_data:${website}:${limit}`,
|
|
||||||
// lazy: true
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return res;
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export function useWebsitesData(limit: number = 10) {
|
|
||||||
// const res = useCustomFetch<VisitsWebsiteAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`,
|
|
||||||
// () => signHeaders(createFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
|
||||||
// { lazy: false, watchProps: [snapshot] }
|
|
||||||
// );
|
|
||||||
// return res;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function useEventsData(limit: number = 10) {
|
|
||||||
// const res = useCustomFetch<CustomEventsAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/events`,
|
|
||||||
// () => signHeaders(createFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
|
||||||
// { lazy: false, watchProps: [snapshot] }
|
|
||||||
// );
|
|
||||||
// return res;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function useReferrersData(limit: number = 10) {
|
|
||||||
// const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`,
|
|
||||||
// () => signHeaders(createFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
|
||||||
// { lazy: false, watchProps: [snapshot] }
|
|
||||||
// );
|
|
||||||
// return res;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function useBrowsersData(limit: number = 10) {
|
|
||||||
// const res = useCustomFetch<BrowsersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`,
|
|
||||||
// () => signHeaders(createFromToHeaders({ '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(createFromToHeaders({ '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(createFromToHeaders({ '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(createFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
|
||||||
// { lazy: false, watchProps: [snapshot] }
|
|
||||||
// );
|
|
||||||
// return res;
|
|
||||||
// }
|
|
||||||
@@ -7,6 +7,9 @@ const setLoggedUser = (authContext?: AuthContext) => {
|
|||||||
loggedUser.value = authContext;
|
loggedUser.value = authContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isLogged = computed(() => {
|
||||||
|
return loggedUser.value?.logged;
|
||||||
|
})
|
||||||
|
|
||||||
function getUserRoles() {
|
function getUserRoles() {
|
||||||
const isPremium = computed(() => {
|
const isPremium = computed(() => {
|
||||||
@@ -17,6 +20,7 @@ function getUserRoles() {
|
|||||||
if (!loggedUser.value?.logged) return false;
|
if (!loggedUser.value?.logged) return false;
|
||||||
return loggedUser.value.user.roles.includes('ADMIN');
|
return loggedUser.value.user.roles.includes('ADMIN');
|
||||||
});
|
});
|
||||||
|
|
||||||
return { isPremium, isAdmin }
|
return { isPremium, isAdmin }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +28,7 @@ export const isAdminHidden = ref<boolean>(false);
|
|||||||
|
|
||||||
export function useLoggedUser() {
|
export function useLoggedUser() {
|
||||||
return {
|
return {
|
||||||
|
isLogged,
|
||||||
user: loggedUser,
|
user: loggedUser,
|
||||||
userRoles: getUserRoles(),
|
userRoles: getUserRoles(),
|
||||||
setLoggedUser
|
setLoggedUser
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
const onlineUsers = ref<number>(-1);
|
const onlineUsers = await useFetch<number>(`/api/data/live_users`, {
|
||||||
|
headers: useComputedHeaders({ useSnapshotDates: false }), immediate: false
|
||||||
async function getOnlineUsers() {
|
});
|
||||||
const activeProject = useActiveProject();
|
|
||||||
if (!activeProject.value) return onlineUsers.value = -1;
|
|
||||||
const online = await $fetch<number>(`/api/metrics/${activeProject.value._id}/live_users`, signHeaders());
|
|
||||||
onlineUsers.value = online;
|
|
||||||
}
|
|
||||||
|
|
||||||
let watching: any;
|
let watching: any;
|
||||||
|
|
||||||
function startWatching(instant: boolean = true) {
|
function startWatching(instant: boolean = true) {
|
||||||
if (instant) getOnlineUsers();
|
if (instant) onlineUsers.execute();
|
||||||
watching = setInterval(async () => {
|
watching = setInterval(async () => {
|
||||||
await getOnlineUsers();
|
onlineUsers.refresh();
|
||||||
}, 20000);
|
}, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +21,6 @@ export function useOnlineUsers() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
onlineUsers,
|
onlineUsers,
|
||||||
getOnlineUsers,
|
|
||||||
startWatching,
|
startWatching,
|
||||||
stopWatching
|
stopWatching
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,26 @@ const projectsRequest = useFetch<TProject[]>('/api/project/list', {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectList = computed(() => projectsRequest.data.value);
|
const guestProjectsRequest = useFetch<TProject[]>('/api/project/list_guest', {
|
||||||
|
headers: computed(() => {
|
||||||
|
return {
|
||||||
|
'Authorization': `Bearer ${token.value}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectList = computed(() => {
|
||||||
|
return projectsRequest.data.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const allProjectList = computed(() => {
|
||||||
|
return [...(projectsRequest.data.value || []), ...(guestProjectsRequest.data.value || [])]
|
||||||
|
})
|
||||||
|
|
||||||
|
const guestProjectList = computed(() => {
|
||||||
|
return guestProjectsRequest.data.value;
|
||||||
|
})
|
||||||
|
|
||||||
const refreshProjectsList = () => projectsRequest.refresh();
|
const refreshProjectsList = () => projectsRequest.refresh();
|
||||||
|
|
||||||
const activeProjectId = ref<string | undefined>();
|
const activeProjectId = ref<string | undefined>();
|
||||||
@@ -42,6 +61,11 @@ const project = computed(() => {
|
|||||||
return projectList.value[0];
|
return projectList.value[0];
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const isGuest = computed(() => {
|
||||||
|
return (projectList.value || []).find(e => e._id.toString() === activeProjectId.value);
|
||||||
|
})
|
||||||
|
|
||||||
export function useProject() {
|
export function useProject() {
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@@ -49,5 +73,5 @@ export function useProject() {
|
|||||||
setActiveProject
|
setActiveProject
|
||||||
}
|
}
|
||||||
|
|
||||||
return { project, projectList, actions, projectId: activeProjectId }
|
return { project, allProjectList, guestProjectList, projectList, actions, projectId: activeProjectId, isGuest }
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,37 @@
|
|||||||
import type { TProjectSnapshot } from "@schema/ProjectSnapshot";
|
import type { TProjectSnapshot } from "@schema/ProjectSnapshot";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { projectId, project } = useProject();
|
||||||
|
|
||||||
|
const headers = computed(() => {
|
||||||
|
return {
|
||||||
|
'Authorization': signHeaders().headers.Authorization,
|
||||||
|
'x-pid': projectId.value ?? ''
|
||||||
|
}
|
||||||
|
});
|
||||||
const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
||||||
...signHeaders(),
|
headers
|
||||||
immediate: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
watch(project, async () => {
|
||||||
watch(activeProject, async () => {
|
|
||||||
await remoteSnapshots.refresh();
|
await remoteSnapshots.refresh();
|
||||||
snapshot.value = isLiveDemo() ? snapshots.value[0] : snapshots.value[1];
|
snapshot.value = isLiveDemo() ? snapshots.value[0] : snapshots.value[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshots = computed(() => {
|
const snapshots = computed(() => {
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
|
||||||
|
|
||||||
const getDefaultSnapshots: () => TProjectSnapshot[] = () => [
|
const getDefaultSnapshots: () => TProjectSnapshot[] = () => [
|
||||||
{
|
{
|
||||||
project_id: activeProject.value?._id as any,
|
project_id: project.value?._id as any,
|
||||||
_id: 'default0' as any,
|
_id: 'default0' as any,
|
||||||
name: 'All',
|
name: 'All',
|
||||||
from: new Date(activeProject.value?.created_at || 0),
|
from: new Date(project.value?.created_at || 0),
|
||||||
to: new Date(Date.now()),
|
to: new Date(Date.now()),
|
||||||
color: '#CCCCCC'
|
color: '#CCCCCC'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
project_id: activeProject.value?._id as any,
|
project_id: project.value?._id as any,
|
||||||
_id: 'default1' as any,
|
_id: 'default1' as any,
|
||||||
name: 'Last month',
|
name: 'Last month',
|
||||||
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30),
|
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30),
|
||||||
@@ -33,7 +39,7 @@ const snapshots = computed(() => {
|
|||||||
color: '#00CC00'
|
color: '#00CC00'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
project_id: activeProject.value?._id as any,
|
project_id: project.value?._id as any,
|
||||||
_id: 'default2' as any,
|
_id: 'default2' as any,
|
||||||
name: 'Last week',
|
name: 'Last week',
|
||||||
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
||||||
@@ -41,7 +47,7 @@ const snapshots = computed(() => {
|
|||||||
color: '#0F02D2'
|
color: '#0F02D2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
project_id: activeProject.value?._id as any,
|
project_id: project.value?._id as any,
|
||||||
_id: 'default3' as any,
|
_id: 'default3' as any,
|
||||||
name: 'Last day',
|
name: 'Last day',
|
||||||
from: new Date(Date.now() - 1000 * 60 * 60 * 24),
|
from: new Date(Date.now() - 1000 * 60 * 60 * 24),
|
||||||
@@ -75,8 +81,5 @@ const snapshotDuration = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function useSnapshot() {
|
export function useSnapshot() {
|
||||||
if (remoteSnapshots.status.value === 'idle') {
|
|
||||||
remoteSnapshots.execute();
|
|
||||||
}
|
|
||||||
return { snapshot, snapshots, safeSnapshotDates, updateSnapshots, snapshotDuration }
|
return { snapshot, snapshots, safeSnapshotDates, updateSnapshots, snapshotDuration }
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,8 @@ import type { Section } from '~/components/CVerticalNavigation.vue';
|
|||||||
|
|
||||||
import { Lit } from 'litlyx-js';
|
import { Lit } from 'litlyx-js';
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const { userRoles, isLogged } = useLoggedUser();
|
||||||
const isPremium = computed(() => {
|
const { project } = useProject();
|
||||||
return activeProject.value?.premium;
|
|
||||||
});
|
|
||||||
|
|
||||||
const pricingDrawer = usePricingDrawer();
|
const pricingDrawer = usePricingDrawer();
|
||||||
|
|
||||||
@@ -19,9 +17,9 @@ const sections: Section[] = [
|
|||||||
{ label: 'Events', to: '/events', icon: 'fal fa-square-bolt' },
|
{ label: 'Events', to: '/events', icon: 'fal fa-square-bolt' },
|
||||||
{ label: 'AI Analyst', to: '/analyst', icon: 'fal fa-sparkles' },
|
{ label: 'AI Analyst', 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 },
|
||||||
{ label: 'Integrations (soon)', to: '/integrations', icon: 'fal fa-cube', disabled: true },
|
// { label: 'Integrations (soon)', to: '/integrations', icon: 'fal fa-cube', disabled: true },
|
||||||
{ label: 'Settings', to: '/settings', icon: 'fal fa-gear' },
|
{ label: 'Settings', to: '/settings', icon: 'fal fa-gear' },
|
||||||
{
|
{
|
||||||
grow: true,
|
grow: true,
|
||||||
@@ -33,8 +31,8 @@ const sections: Section[] = [
|
|||||||
to: '#',
|
to: '#',
|
||||||
premiumOnly: true,
|
premiumOnly: true,
|
||||||
action() {
|
action() {
|
||||||
if (isGuest.value === true) return;
|
if (isLogged.value === true) return;
|
||||||
if (isPremium.value === true) {
|
if (userRoles.isPremium.value === true) {
|
||||||
window.open('https://join.slack.com/t/litlyx/shared_invite/zt-2q3oawn29-hZlu_fBUBlc4052Ooe3FZg', '_blank');
|
window.open('https://join.slack.com/t/litlyx/shared_invite/zt-2q3oawn29-hZlu_fBUBlc4052Ooe3FZg', '_blank');
|
||||||
} else {
|
} else {
|
||||||
pricingDrawer.visible.value = true;
|
pricingDrawer.visible.value = true;
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import VueMarkdown from 'vue-markdown-render';
|
|||||||
|
|
||||||
definePageMeta({ layout: 'dashboard' });
|
definePageMeta({ layout: 'dashboard' });
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const { project, isGuest } = useProject();
|
||||||
|
|
||||||
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${activeProject.value?._id}/chats_list`, {
|
const { data: chatsList, refresh: reloadChatsList } = useFetch(`/api/ai/${project.value?._id}/chats_list`, {
|
||||||
...signHeaders()
|
...signHeaders()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const viewChatsList = computed(() => (chatsList.value || []).toReversed());
|
const viewChatsList = computed(() => (chatsList.value || []).toReversed());
|
||||||
|
|
||||||
const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/${activeProject.value?._id}/chats_remaining`, signHeaders());
|
const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/ai/${project.value?._id}/chats_remaining`, signHeaders());
|
||||||
|
|
||||||
const currentText = ref<string>("");
|
const currentText = ref<string>("");
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
@@ -27,7 +26,7 @@ const scroller = ref<HTMLDivElement | null>(null);
|
|||||||
async function sendMessage() {
|
async function sendMessage() {
|
||||||
|
|
||||||
if (loading.value) return;
|
if (loading.value) return;
|
||||||
if (!activeProject.value) return;
|
if (!project.value) return;
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ async function sendMessage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const res = await $fetch(`/api/ai/${activeProject.value._id.toString()}/send_message`, {
|
const res = await $fetch(`/api/ai/${project.value._id.toString()}/send_message`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
...signHeaders({ 'Content-Type': 'application/json' })
|
...signHeaders({ 'Content-Type': 'application/json' })
|
||||||
@@ -74,7 +73,7 @@ async function sendMessage() {
|
|||||||
|
|
||||||
async function openChat(chat_id?: string) {
|
async function openChat(chat_id?: string) {
|
||||||
menuOpen.value = false;
|
menuOpen.value = false;
|
||||||
if (!activeProject.value) return;
|
if (!project.value) return;
|
||||||
|
|
||||||
currentChatMessages.value = [];
|
currentChatMessages.value = [];
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ async function openChat(chat_id?: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentChatId.value = chat_id;
|
currentChatId.value = chat_id;
|
||||||
const messages = await $fetch(`/api/ai/${activeProject.value._id}/${chat_id}/get_messages`, signHeaders());
|
const messages = await $fetch(`/api/ai/${project.value._id}/${chat_id}/get_messages`, signHeaders());
|
||||||
if (!messages) return;
|
if (!messages) return;
|
||||||
|
|
||||||
currentChatMessages.value = messages.map(e => ({ ...e, charts: e.charts.map(k => JSON.parse(k)) })) as any;
|
currentChatMessages.value = messages.map(e => ({ ...e, charts: e.charts.map(k => JSON.parse(k)) })) as any;
|
||||||
@@ -117,14 +116,14 @@ const defaultPrompts = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
async function deleteChat(chat_id: string) {
|
async function deleteChat(chat_id: string) {
|
||||||
if (!activeProject.value) return;
|
if (!project.value) return;
|
||||||
const sure = confirm("Are you sure to delete the chat ?");
|
const sure = confirm("Are you sure to delete the chat ?");
|
||||||
if (!sure) return;
|
if (!sure) return;
|
||||||
if (currentChatId.value === chat_id) {
|
if (currentChatId.value === chat_id) {
|
||||||
currentChatId.value = "";
|
currentChatId.value = "";
|
||||||
currentChatMessages.value = [];
|
currentChatMessages.value = [];
|
||||||
}
|
}
|
||||||
await $fetch(`/api/ai/${activeProject.value._id}/${chat_id}/delete`, signHeaders());
|
await $fetch(`/api/ai/${project.value._id}/${chat_id}/delete`, signHeaders());
|
||||||
await reloadChatsList();
|
await reloadChatsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +298,7 @@ const { visible: pricingDrawerVisible } = usePricingDrawer()
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|||||||
@@ -10,12 +10,9 @@ const selectLabelsEvents = [
|
|||||||
];
|
];
|
||||||
const eventsStackedSelectIndex = ref<number>(0);
|
const eventsStackedSelectIndex = ref<number>(0);
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const { projectId } = useProject();
|
||||||
const { snapshot, safeSnapshotDates } = useSnapshot();
|
const { snapshot, safeSnapshotDates } = useSnapshot();
|
||||||
|
|
||||||
const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProject.value?._id.toString()}`);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const headers = computed(() => {
|
const headers = computed(() => {
|
||||||
return {
|
return {
|
||||||
@@ -23,7 +20,7 @@ const headers = computed(() => {
|
|||||||
'x-to': safeSnapshotDates.value.to,
|
'x-to': safeSnapshotDates.value.to,
|
||||||
'Authorization': authorizationHeaderComputed.value,
|
'Authorization': authorizationHeaderComputed.value,
|
||||||
'x-schema': 'events',
|
'x-schema': 'events',
|
||||||
'x-pid': activeProject.value?._id.toString() || ''
|
'x-pid': projectId.value ?? ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
92
dashboard/server/api/data/events_data/flow_from_name.ts
Normal file
92
dashboard/server/api/data/events_data/flow_from_name.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { VisitModel } from "@schema/metrics/VisitSchema";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project_id, from, to } = data;
|
||||||
|
|
||||||
|
const { name: eventName } = getQuery(event);
|
||||||
|
|
||||||
|
if (!eventName) return setResponseStatus(event, 400, 'name is required');
|
||||||
|
|
||||||
|
const allEvents = await EventModel.find({
|
||||||
|
project_id: project_id,
|
||||||
|
name: eventName,
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from),
|
||||||
|
$lte: new Date(to),
|
||||||
|
}
|
||||||
|
}, { flowHash: 1 });
|
||||||
|
|
||||||
|
|
||||||
|
const allFlowHashes = new Map<string, number>();
|
||||||
|
|
||||||
|
allEvents.forEach(e => {
|
||||||
|
if (!e.flowHash) return;
|
||||||
|
if (e.flowHash.length == 0) return;
|
||||||
|
if (allFlowHashes.has(e.flowHash)) {
|
||||||
|
const count = allFlowHashes.get(e.flowHash) as number;
|
||||||
|
allFlowHashes.set(e.flowHash, count + 1);
|
||||||
|
} else {
|
||||||
|
allFlowHashes.set(e.flowHash, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const flowHashIds = Array.from(allFlowHashes.keys());
|
||||||
|
|
||||||
|
const allReferrers: { referrer: string, flowHash: string }[] = [];
|
||||||
|
|
||||||
|
const promises: any[] = [];
|
||||||
|
while (flowHashIds.length > 0) {
|
||||||
|
promises.push(new Promise<void>(async resolve => {
|
||||||
|
const flowHashIdsChunk = flowHashIds.splice(0, 10);
|
||||||
|
const visits = await VisitModel.find({ project_id, flowHash: { $in: flowHashIdsChunk } }, { referrer: 1, flowHash: 1 });
|
||||||
|
allReferrers.push(...visits.map(e => { return { referrer: e.referrer, flowHash: e.flowHash } }));
|
||||||
|
resolve();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
const groupedFlows: Record<string, { referrers: string[] }> = {};
|
||||||
|
|
||||||
|
flowHashIds.forEach(flowHash => {
|
||||||
|
if (!groupedFlows[flowHash]) groupedFlows[flowHash] = { referrers: [] };
|
||||||
|
const target = groupedFlows[flowHash];
|
||||||
|
if (!target) return;
|
||||||
|
const referrers = allReferrers.filter(e => e.flowHash === flowHash).map(e => e.referrer);
|
||||||
|
for (const referrer of referrers) {
|
||||||
|
if (target.referrers.includes(referrer)) continue;
|
||||||
|
target.referrers.push(referrer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const grouped: Record<string, number> = {};
|
||||||
|
|
||||||
|
for (const referrerPlusHash of allReferrers) {
|
||||||
|
const referrer = referrerPlusHash.referrer;
|
||||||
|
if (!grouped[referrer]) grouped[referrer] = 0
|
||||||
|
grouped[referrer]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const eventsCount = allEvents.length;
|
||||||
|
|
||||||
|
const allGroupedValue = Object.keys(grouped)
|
||||||
|
.map(key => grouped[key])
|
||||||
|
.reduce((a, e) => a + e, 0);
|
||||||
|
|
||||||
|
for (const key in grouped) {
|
||||||
|
grouped[key] = 100 / allGroupedValue * grouped[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { EVENT_METADATA_FIELDS_EXPIRE_TIME, Redis } from "~/server/services/CacheService";
|
||||||
|
import { PipelineStage } from "mongoose";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project_id } = data;
|
||||||
|
|
||||||
|
|
||||||
|
const { name: eventName, field, from, to } = getQuery(event);
|
||||||
|
|
||||||
|
if (!from) return setResponseStatus(event, 400, 'from is required');
|
||||||
|
if (!to) return setResponseStatus(event, 400, 'to is required');
|
||||||
|
if (!eventName) return setResponseStatus(event, 400, 'name is required');
|
||||||
|
if (!field) return setResponseStatus(event, 400, 'field is required');
|
||||||
|
|
||||||
|
|
||||||
|
const aggregation: PipelineStage[] = [
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
project_id, name: eventName,
|
||||||
|
created_at: {
|
||||||
|
$gte: new Date(from.toString()),
|
||||||
|
$lte: new Date(to.toString()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $group: { _id: `$metadata.${field}`, count: { $sum: 1 } } },
|
||||||
|
{ $sort: { count: -1 } }
|
||||||
|
]
|
||||||
|
|
||||||
|
const metadataGrouped = await EventModel.aggregate(aggregation);
|
||||||
|
|
||||||
|
return metadataGrouped;
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
32
dashboard/server/api/data/events_data/metadata_fields.ts
Normal file
32
dashboard/server/api/data/events_data/metadata_fields.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { Redis } from "~/server/services/CacheService";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project_id } = data;
|
||||||
|
|
||||||
|
const { name: eventName } = getQuery(event);
|
||||||
|
if (!eventName) return [];
|
||||||
|
|
||||||
|
const fields: string[] = await Redis.useCache({
|
||||||
|
key: `metadata_fields:${project_id}:${eventName}`,
|
||||||
|
exp: 60
|
||||||
|
}, async () => {
|
||||||
|
const eventsWithName = await EventModel.find({ project_id, name: eventName }, { metadata: 1 }, { limit: 10, sort: { created_at: -1 } });
|
||||||
|
const allMetadata = eventsWithName.map(e => e.metadata);
|
||||||
|
const allFields = new Set<string>();
|
||||||
|
for (const metadata of allMetadata) {
|
||||||
|
const keys = Object.keys(metadata || {});
|
||||||
|
keys.forEach(key => allFields.add(key));
|
||||||
|
}
|
||||||
|
return Array.from(allFields.values());
|
||||||
|
});
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
|
||||||
|
});
|
||||||
26
dashboard/server/api/data/events_data/names.ts
Normal file
26
dashboard/server/api/data/events_data/names.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { Redis } from "~/server/services/CacheService";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project_id } = data;
|
||||||
|
|
||||||
|
const names: string[] = await Redis.useCache({
|
||||||
|
key: `event_names:${project_id}`,
|
||||||
|
exp: 60
|
||||||
|
}, async () => {
|
||||||
|
const namesAggregation = await EventModel.aggregate([
|
||||||
|
{ $match: { project_id } },
|
||||||
|
{ $group: { _id: "$name" } }
|
||||||
|
]);
|
||||||
|
return namesAggregation.map(e => e._id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return names;
|
||||||
|
|
||||||
|
});
|
||||||
25
dashboard/server/api/data/live_users.ts
Normal file
25
dashboard/server/api/data/live_users.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import { SessionModel } from "@schema/metrics/SessionSchema";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project_id } = data;
|
||||||
|
|
||||||
|
const online_users = await SessionModel.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
project_id,
|
||||||
|
updated_at: { $gt: new Date(Date.now() - 1000 * 60 * 5) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ $count: 'count' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!online_users[0]) return 0;
|
||||||
|
|
||||||
|
return online_users[0].count;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
|
||||||
import StripeService from '~/server/services/StripeService';
|
|
||||||
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
if (!project.customer_id) return;
|
|
||||||
|
|
||||||
const customer = await StripeService.getCustomer(project.customer_id);
|
|
||||||
if (customer?.deleted) return;
|
|
||||||
|
|
||||||
return customer?.address;
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -5,16 +5,10 @@ import StripeService from '~/server/services/StripeService';
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
if (!project_id) return;
|
if (!data) return;
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
const { project, pid } = data;
|
||||||
if (!user?.logged) return setResponseStatus(event, 400, 'User need to be logged');
|
|
||||||
|
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
if (project.owner.toString() != user.id) return setResponseStatus(event, 400, 'You cannot upgrade a project as guest');
|
|
||||||
|
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
|
|
||||||
@@ -30,7 +24,7 @@ export default defineEventHandler(async event => {
|
|||||||
const intent = await StripeService.createOnetimePayment(
|
const intent = await StripeService.createOnetimePayment(
|
||||||
StripeService.testMode ? PLAN.PRICE_TEST : PLAN.PRICE,
|
StripeService.testMode ? PLAN.PRICE_TEST : PLAN.PRICE,
|
||||||
'https://dashboard.litlyx.com/payment_ok',
|
'https://dashboard.litlyx.com/payment_ok',
|
||||||
project_id,
|
pid,
|
||||||
project.customer_id
|
project.customer_id
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
import { getPlanFromId } from "@data/PREMIUM";
|
import { getPlanFromId } from "@data/PREMIUM";
|
||||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
|
||||||
import StripeService from '~/server/services/StripeService';
|
import StripeService from '~/server/services/StripeService';
|
||||||
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
if (!project_id) return;
|
if (!data) return;
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
const { project, pid } = data;
|
||||||
if (!user?.logged) return setResponseStatus(event, 400, 'User need to be logged');
|
|
||||||
|
|
||||||
const project = await getUserProjectFromId(project_id, user);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
if (project.owner.toString() != user.id) return setResponseStatus(event, 400, 'You cannot upgrade a project as guest');
|
|
||||||
|
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
|
|
||||||
@@ -30,7 +23,7 @@ export default defineEventHandler(async event => {
|
|||||||
const checkout = await StripeService.createPayment(
|
const checkout = await StripeService.createPayment(
|
||||||
StripeService.testMode ? PLAN.PRICE_TEST : PLAN.PRICE,
|
StripeService.testMode ? PLAN.PRICE_TEST : PLAN.PRICE,
|
||||||
'https://dashboard.litlyx.com/payment_ok',
|
'https://dashboard.litlyx.com/payment_ok',
|
||||||
project_id,
|
pid,
|
||||||
project.customer_id
|
project.customer_id
|
||||||
);
|
);
|
||||||
|
|
||||||
16
dashboard/server/api/pay/customer_info.ts
Normal file
16
dashboard/server/api/pay/customer_info.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import StripeService from '~/server/services/StripeService';
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false, allowLitlyx: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { project } = data;
|
||||||
|
|
||||||
|
const customer = await StripeService.getCustomer(project.customer_id);
|
||||||
|
if (customer?.deleted) return;
|
||||||
|
|
||||||
|
return customer?.address;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -12,17 +12,14 @@ export type InvoiceData = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
const data = await getRequestData(event, { requireSchema: false, allowLitlyx: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
const { project, pid } = data;
|
||||||
if (!project_id) return;
|
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
|
||||||
const project = await getUserProjectFromId(project_id, user, false);
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
if (!project.customer_id) return [];
|
if (!project.customer_id) return [];
|
||||||
|
|
||||||
return await Redis.useCache({ key: `invoices:${project_id}`, exp: 10 }, async () => {
|
return await Redis.useCache({ key: `invoices:${pid}`, exp: 10 }, async () => {
|
||||||
|
|
||||||
const invoices = await StripeService.getInvoices(project.customer_id);
|
const invoices = await StripeService.getInvoices(project.customer_id);
|
||||||
if (!invoices) return [];
|
if (!invoices) return [];
|
||||||
@@ -4,13 +4,11 @@ import StripeService from '~/server/services/StripeService';
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const project_id = getRequestProjectId(event);
|
const data = await getRequestData(event, { requireSchema: false, allowLitlyx: false });
|
||||||
if (!project_id) return setResponseStatus(event, 400, 'Cannot get project_id');
|
if (!data) return;
|
||||||
|
|
||||||
const user = getRequestUser(event);
|
|
||||||
const project = await getUserProjectFromId(project_id, user, false);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Cannot get user from project_id');
|
|
||||||
|
|
||||||
|
const { project } = data;
|
||||||
|
|
||||||
if (!project.customer_id) return setResponseStatus(event, 400, 'Project has no customer_id');
|
if (!project.customer_id) return setResponseStatus(event, 400, 'Project has no customer_id');
|
||||||
|
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
@@ -1,27 +1,13 @@
|
|||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
|
||||||
import { UserModel } from "@schema/UserSchema";
|
|
||||||
import { UserSettingsModel } from "@schema/UserSettings";
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
if (!data) return;
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
const { project } = data;
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
|
||||||
|
|
||||||
if (project.owner.toString() != userData.id) {
|
|
||||||
return setResponseStatus(event, 400, 'You are not the owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name } = await readBody(event);
|
const { name } = await readBody(event);
|
||||||
|
|
||||||
if (name.length == 0) return setResponseStatus(event, 400, 'name is required');
|
if (name.length == 0) return setResponseStatus(event, 400, 'name is required');
|
||||||
|
|
||||||
project.name = name;
|
project.name = name;
|
||||||
|
|||||||
@@ -8,19 +8,12 @@ import { AiChatModel } from "@schema/ai/AiChatSchema";
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const body = await readBody(event);
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
const project_id = body.project_id;
|
const { project, user, project_id } = data;
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const projects = await ProjectModel.countDocuments({ owner: user.id });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not exist');
|
|
||||||
|
|
||||||
if (userData.id != project.owner.toString()) return setResponseStatus(event, 400, 'You cannot delete a project as guest');
|
|
||||||
|
|
||||||
const projects = await ProjectModel.countDocuments({ owner: userData.id });
|
|
||||||
if (projects == 1) return setResponseStatus(event, 400, 'Cannot delete last project');
|
if (projects == 1) return setResponseStatus(event, 400, 'Cannot delete last project');
|
||||||
|
|
||||||
if (project.premium === true) return setResponseStatus(event, 400, 'Cannot delete premium project');
|
if (project.premium === true) return setResponseStatus(event, 400, 'Cannot delete premium project');
|
||||||
@@ -29,26 +22,22 @@ export default defineEventHandler(async event => {
|
|||||||
await StripeService.deleteCustomer(project.customer_id);
|
await StripeService.deleteCustomer(project.customer_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectDeletation = await ProjectModel.deleteOne({ _id: project_id });
|
const projectDeletation = ProjectModel.deleteOne({ _id: project_id });
|
||||||
const countDeletation = await ProjectCountModel.deleteMany({ project_id });
|
const countDeletation = ProjectCountModel.deleteMany({ project_id });
|
||||||
const limitdeletation = await ProjectLimitModel.deleteMany({ project_id });
|
const limitdeletation = ProjectLimitModel.deleteMany({ project_id });
|
||||||
const sessionsDeletation = await SessionModel.deleteMany({ project_id });
|
const sessionsDeletation = SessionModel.deleteMany({ project_id });
|
||||||
const notifiesDeletation = await LimitNotifyModel.deleteMany({ project_id });
|
const notifiesDeletation = LimitNotifyModel.deleteMany({ project_id });
|
||||||
const aiChatsDeletation = await AiChatModel.deleteMany({ project_id });
|
const aiChatsDeletation = AiChatModel.deleteMany({ project_id });
|
||||||
|
|
||||||
const ok = countDeletation.acknowledged && limitdeletation.acknowledged &&
|
const results = await Promise.all([
|
||||||
projectDeletation.acknowledged && sessionsDeletation.acknowledged && notifiesDeletation.acknowledged && aiChatsDeletation.acknowledged
|
projectDeletation,
|
||||||
|
countDeletation,
|
||||||
|
limitdeletation,
|
||||||
|
sessionsDeletation,
|
||||||
|
notifiesDeletation,
|
||||||
|
aiChatsDeletation
|
||||||
|
])
|
||||||
|
|
||||||
return {
|
return { data: results };
|
||||||
ok,
|
|
||||||
data: [
|
|
||||||
countDeletation.acknowledged,
|
|
||||||
limitdeletation.acknowledged,
|
|
||||||
projectDeletation.acknowledged,
|
|
||||||
sessionsDeletation.acknowledged,
|
|
||||||
notifiesDeletation.acknowledged,
|
|
||||||
aiChatsDeletation.acknowledged
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -1,24 +1,14 @@
|
|||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||||
import { UserModel } from "@schema/UserSchema";
|
import { UserModel } from "@schema/UserSchema";
|
||||||
import { UserSettingsModel } from "@schema/UserSettings";
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
if (!data) return;
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
const { project_id } = data;
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
|
||||||
|
|
||||||
if (project.owner.toString() != userData.id) {
|
|
||||||
return setResponseStatus(event, 400, 'You are not the owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email } = await readBody(event);
|
const { email } = await readBody(event);
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,14 @@
|
|||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||||
import { UserModel } from "@schema/UserSchema";
|
import { UserModel } from "@schema/UserSchema";
|
||||||
import { UserSettingsModel } from "@schema/UserSettings";
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
if (!data) return;
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
const { project_id } = data;
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
|
||||||
|
|
||||||
if (project.owner.toString() != userData.id) {
|
|
||||||
return setResponseStatus(event, 400, 'You are not the owner');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { email } = await readBody(event);
|
const { email } = await readBody(event);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
import { TeamMemberModel } from "@schema/TeamMemberSchema";
|
||||||
import { UserModel } from "@schema/UserSchema";
|
|
||||||
import { UserSettingsModel } from "@schema/UserSettings";
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const data = await getRequestData(event, { requireSchema: false, allowGuests: false, allowLitlyx: false });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
if (!data) return;
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
const { project_id, user } = data;
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
await TeamMemberModel.deleteOne({ project_id, user_id: user.id });
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
|
||||||
|
|
||||||
await TeamMemberModel.deleteOne({ project_id, user_id: userData.id });
|
|
||||||
|
|
||||||
return { ok: true }
|
return { ok: true }
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ import StripeService from '~/server/services/StripeService';
|
|||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const data = await getRequestData(event, { requireSchema: false });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
if (!data) return;
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
const { project_id, project, user } = data;
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
|
||||||
|
|
||||||
const owner = await UserModel.findById(project.owner);
|
const owner = await UserModel.findById(project.owner);
|
||||||
if (!owner) return setResponseStatus(event, 400, 'No owner');
|
if (!owner) return setResponseStatus(event, 400, 'No owner');
|
||||||
@@ -29,7 +23,7 @@ export default defineEventHandler(async event => {
|
|||||||
name: owner.name,
|
name: owner.name,
|
||||||
role: 'OWNER',
|
role: 'OWNER',
|
||||||
pending: false,
|
pending: false,
|
||||||
me: userData.id === owner.id
|
me: user.id === owner.id
|
||||||
})
|
})
|
||||||
|
|
||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
@@ -40,7 +34,7 @@ export default defineEventHandler(async event => {
|
|||||||
name: userMember.name,
|
name: userMember.name,
|
||||||
role: member.role,
|
role: member.role,
|
||||||
pending: member.pending,
|
pending: member.pending,
|
||||||
me: userData.id === userMember.id
|
me: user.id === userMember.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
import { ProjectModel } from "@schema/ProjectSchema";
|
|
||||||
import { ProjectLimitModel } from "@schema/ProjectsLimits";
|
import { ProjectLimitModel } from "@schema/ProjectsLimits";
|
||||||
import { UserSettingsModel } from "@schema/UserSettings";
|
|
||||||
import StripeService from '~/server/services/StripeService';
|
import StripeService from '~/server/services/StripeService';
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const data = await getRequestData(event, { requireSchema: false, allowLitlyx: false });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
if (!data) return;
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
|
||||||
|
|
||||||
const project = await ProjectModel.findById(project_id);
|
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
|
||||||
|
|
||||||
|
const { project, project_id } = data;
|
||||||
|
|
||||||
if (project.subscription_id === 'onetime') {
|
if (project.subscription_id === 'onetime') {
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
|
|
||||||
import { ProjectSnapshotModel, TProjectSnapshot } from "@schema/ProjectSnapshot";
|
import { ProjectSnapshotModel, TProjectSnapshot } from "@schema/ProjectSnapshot";
|
||||||
import { UserSettingsModel } from "@schema/UserSettings";
|
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
const userData = getRequestUser(event);
|
const data = await getRequestData(event, { requireSchema: false, allowLitlyx: false });
|
||||||
if (!userData?.logged) return setResponseStatus(event, 400, 'NotLogged');
|
if (!data) return;
|
||||||
|
|
||||||
const currentActiveProject = await UserSettingsModel.findOne({ user_id: userData.id });
|
const { project_id } = data;
|
||||||
if (!currentActiveProject) return setResponseStatus(event, 400, 'You need to select a project');
|
|
||||||
|
|
||||||
const project_id = currentActiveProject.active_project_id;
|
|
||||||
|
|
||||||
const snapshots = await ProjectSnapshotModel.find({ project_id });
|
const snapshots = await ProjectSnapshotModel.find({ project_id });
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default defineEventHandler(async event => {
|
|||||||
const { pid, from, to, slice, project_id } = data;
|
const { pid, from, to, slice, project_id } = data;
|
||||||
|
|
||||||
|
|
||||||
const cacheKey = `timeline:bouncing_rate:${pid}:${from}:${to}`;
|
const cacheKey = `timeline:bouncing_rate:${pid}:${slice}:${from}:${to}`;
|
||||||
const cacheExp = 60 * 60; //1 hour
|
const cacheExp = 60 * 60; //1 hour
|
||||||
|
|
||||||
return await Redis.useCacheV2(cacheKey, cacheExp, async (noStore, updateExp) => {
|
return await Redis.useCacheV2(cacheKey, cacheExp, async (noStore, updateExp) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const { pid, from, to, slice, project_id } = data;
|
const { pid, from, to, slice, project_id } = data;
|
||||||
|
|
||||||
const cacheKey = `timeline:events:${pid}:${from}:${to}`;
|
const cacheKey = `timeline:events:${pid}:${slice}:${from}:${to}`;
|
||||||
const cacheExp = 60;
|
const cacheExp = 60;
|
||||||
|
|
||||||
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
||||||
|
|||||||
28
dashboard/server/api/timeline/events_stacked.post.ts
Normal file
28
dashboard/server/api/timeline/events_stacked.post.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { EventModel } from "@schema/metrics/EventSchema";
|
||||||
|
import { Redis, TIMELINE_EXPIRE_TIME } from "~/server/services/CacheService";
|
||||||
|
import { executeAdvancedTimelineAggregation} from "~/server/services/TimelineService";
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const data = await getRequestData(event, { requireSchema: false, requireSlice: true });
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
const { from, to, slice, project_id } = data;
|
||||||
|
|
||||||
|
return await Redis.useCache({ key: `timeline:events_stacked:${project_id}:${slice}:${from || 'none'}:${to || 'none'}`, exp: TIMELINE_EXPIRE_TIME }, async () => {
|
||||||
|
|
||||||
|
const timelineStackedEvents = await executeAdvancedTimelineAggregation<{ name: String }>({
|
||||||
|
model: EventModel,
|
||||||
|
projectId: project_id,
|
||||||
|
from, to, slice,
|
||||||
|
customProjection: { name: "$_id.name" },
|
||||||
|
customIdGroup: { name: '$name' },
|
||||||
|
})
|
||||||
|
|
||||||
|
// const filledDates = DateService.createBetweenDates(from, to, slice);
|
||||||
|
// const merged = DateService.mergeFilledDates(filledDates.dates, timelineStackedEvents, '_id', slice, { count: 0, name: '' });
|
||||||
|
|
||||||
|
return timelineStackedEvents;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -9,7 +9,7 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const { pid, from, to, slice, project_id } = data;
|
const { pid, from, to, slice, project_id } = data;
|
||||||
|
|
||||||
const cacheKey = `timeline:sessions:${pid}:${from}:${to}`;
|
const cacheKey = `timeline:sessions:${pid}:${slice}:${from}:${to}`;
|
||||||
const cacheExp = 60;
|
const cacheExp = 60;
|
||||||
|
|
||||||
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const { pid, from, to, slice, project_id } = data;
|
const { pid, from, to, slice, project_id } = data;
|
||||||
|
|
||||||
const cacheKey = `timeline:sessions_duration:${pid}:${from}:${to}`;
|
const cacheKey = `timeline:sessions_duration:${pid}:${slice}:${from}:${to}`;
|
||||||
const cacheExp = 60;
|
const cacheExp = 60;
|
||||||
|
|
||||||
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const { pid, from, to, slice, project_id } = data;
|
const { pid, from, to, slice, project_id } = data;
|
||||||
|
|
||||||
const cacheKey = `timeline:visits:${pid}:${from}:${to}`;
|
const cacheKey = `timeline:visits:${pid}:${slice}:${from}:${to}`;
|
||||||
const cacheExp = 60;
|
const cacheExp = 60;
|
||||||
|
|
||||||
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ export async function getRequestData(event: H3Event<EventHandlerRequest>, option
|
|||||||
if (!allowLitlyx) return setResponseStatus(event, 400, 'no access to project');
|
if (!allowLitlyx) return setResponseStatus(event, 400, 'no access to project');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { from, to, pid, project_id, project, user, limit, slice, schemaName, model }
|
return {
|
||||||
|
from: from as string,
|
||||||
|
to: to as string,
|
||||||
|
pid, project_id, project, user, limit, slice, schemaName, model
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user