new selfhosted version

This commit is contained in:
antonio
2025-11-28 14:11:51 +01:00
parent afda29997d
commit 951860f67e
1046 changed files with 72586 additions and 574750 deletions

View File

@@ -1,7 +0,0 @@
let registered = false;
export async function registerChartComponents() {
if (registered) return;
console.log('registerChartComponents is deprecated. Plugin is now used');
registered = true;
}

View File

@@ -1,127 +0,0 @@
import type { TProjectSnapshot } from "@schema/project/ProjectSnapshot";
import * as fns from 'date-fns';
export type DefaultSnapshot = TProjectSnapshot & { default: true }
export type GenericSnapshot = TProjectSnapshot | DefaultSnapshot;
export function getDefaultSnapshots(project_id: TProjectSnapshot['project_id'], project_created_at: Date | string) {
const today: DefaultSnapshot = {
project_id,
_id: '___today' as any,
name: 'Today',
from: fns.startOfDay(Date.now()),
to: fns.endOfDay(Date.now()),
color: '#FFA600',
default: true
}
const lastDay: DefaultSnapshot = {
project_id,
_id: '___lastDay' as any,
name: 'Yesterday',
from: fns.startOfDay(fns.subDays(Date.now(), 1)),
to: fns.endOfDay(fns.subDays(Date.now(), 1)),
color: '#FF8531',
default: true
}
const lastMonth: DefaultSnapshot = {
project_id,
_id: '___lastMonth' as any,
name: 'Last Month',
from: fns.startOfMonth(fns.subMonths(Date.now(), 1)),
to: fns.endOfMonth(fns.subMonths(Date.now(), 1)),
color: '#BC5090',
default: true
}
const currentMonth: DefaultSnapshot = {
project_id,
_id: '___currentMonth' as any,
name: 'Current Month',
from: fns.startOfMonth(Date.now()),
to: fns.endOfMonth(Date.now()),
color: '#58508D',
default: true
}
const lastWeek: DefaultSnapshot = {
project_id,
_id: '___lastWeek' as any,
name: 'Last Week',
from: fns.startOfWeek(fns.subWeeks(Date.now(), 1)),
to: fns.endOfWeek(fns.subWeeks(Date.now(), 1)),
color: '#3E909D',
default: true
}
const currentWeek: DefaultSnapshot = {
project_id,
_id: '___currentWeek' as any,
name: 'Current Week',
from: fns.startOfWeek(Date.now()),
to: fns.endOfWeek(Date.now()),
color: '#007896',
default: true
}
const allTime: DefaultSnapshot = {
project_id,
_id: '___allTime' as any,
name: 'All Time',
from: fns.addMinutes(fns.startOfMonth(new Date(project_created_at.toString())), -new Date().getTimezoneOffset()),
to: fns.addMilliseconds(fns.endOfDay(Date.now()), 1),
color: '#9362FF',
default: true
}
const last30Days: DefaultSnapshot = {
project_id,
_id: '___last30days' as any,
name: 'Last 30 days',
from: fns.startOfDay(fns.subDays(Date.now(), 30)),
to: fns.endOfDay(fns.subDays(Date.now(), 0)),
color: '#606c38',
default: true
}
const last60Days: DefaultSnapshot = {
project_id,
_id: '___last60days' as any,
name: 'Last 60 days',
from: fns.startOfDay(fns.subDays(Date.now(), 60)),
to: fns.endOfDay(fns.subDays(Date.now(), 0)),
color: '#bc6c25',
default: true
}
const last90Days: DefaultSnapshot = {
project_id,
_id: '___last90days' as any,
name: 'Last 90 days',
from: fns.startOfDay(fns.subDays(Date.now(), 90)),
to: fns.endOfDay(fns.subDays(Date.now(), 0)),
color: '#fefae0',
default: true
}
const snapshotList = [
allTime,
lastDay, today,
lastWeek, currentWeek,
lastMonth, currentMonth,
last30Days,
last60Days, last90Days,
]
return snapshotList;
}

View File

@@ -1,13 +0,0 @@
export function useSelectMenuStyle() {
return {
uiMenu: {
select: 'bg-lyx-lightmode-widget-light !ring-lyx-lightmode-widget dark:!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter dark:!ring-lyx-widget-lighter',
base: '!bg-lyx-lightmode-widget dark:!bg-lyx-widget',
option: {
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}
}
}

View File

@@ -1,25 +0,0 @@
const ACCESS_TOKEN_COOKIE_KEY = 'access_token';
const tokenCookie = useCookie(ACCESS_TOKEN_COOKIE_KEY, { expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30) });
const token = ref<string | undefined>();
export function signHeaders(headers?: Record<string, string>) {
const { token } = useAccessToken()
return { headers: { ...(headers || {}), 'Authorization': 'Bearer ' + token.value } }
}
export const authorizationHeaderComputed = computed(() => {
const { token } = useAccessToken()
return token.value ? 'Bearer ' + token.value : '';
});
function setToken(value: string) {
tokenCookie.value = value;
token.value = value;
}
export function useAccessToken() {
if (!token.value) token.value = tokenCookie.value as any;
return { setToken, token }
}

View File

@@ -1,52 +0,0 @@
export type Alert = {
title: string,
text: string,
icon: string,
ms: number,
id: number,
remaining: number,
transitionStyle: string
}
const alerts = ref<Alert[]>([]);
const idPool = {
id: 0,
getId() {
return idPool.id++;
}
}
function createAlert(title: string, text: string, icon: string, ms: number) {
const alert = reactive<Alert>({
title, text, icon, ms, id: idPool.getId(), remaining: ms,
transitionStyle: 'transition: all 250ms linear;'
});
alerts.value.push(alert);
const timeout = setInterval(() => {
alert.remaining -= 250;
if (alert.remaining <= 0) {
closeAlert(alert.id);
clearInterval(timeout);
}
}, 250)
}
function createSuccessAlert(title: string, text: string, ms?: number) {
return createAlert(title, text, 'far fa-circle-check', ms ?? 5000);
}
function createErrorAlert(title: string, text: string, ms?: number) {
return createAlert(title, text, 'far fa-triangle-exclamation', ms ?? 5000);
}
function closeAlert(id: number) {
alerts.value = alerts.value.filter(e => e.id != id);
}
export function useAlert() {
return { alerts, createAlert, closeAlert, createSuccessAlert, createErrorAlert }
}

View File

@@ -0,0 +1,34 @@
import { toast } from 'vue-sonner';
async function loadData() {
const { loggedIn } = useUserSession();
if (!loggedIn.value) return;
const router = useRouter();
const projectStore = useProjectStore();
const snapshotStore = useSnapshotStore();
const domainStore = useDomainStore();
const premiumStore = usePremiumStore();
try {
await projectStore.fetchProjects();
if (projectStore.projects.length == 0) {
router.push('/create_project?first=true');
return false;
}
await projectStore.fetchActivePermissions();
await projectStore.fetchPendingInvites();
await snapshotStore.fetchSnapshots();
await domainStore.fetchDomains();
await premiumStore.fetchPremium();
return true;
} catch (ex: any) {
console.error(ex);
toast('Error', { description: ex.message, position: 'top-right' });
}
}
export function useAppStart() {
return { loadData }
}

View File

@@ -0,0 +1,84 @@
import type { FetchError } from 'ofetch';
import type { FetchResult, UseFetchOptions, AsyncData } from "#app";
import type { NitroFetchRequest, AvailableRouterMethod } from 'nitropack';
type PickFrom<T, K extends Array<string>> = T extends Array<any> ? T : T extends Record<string, any> ? keyof T extends K[number] ? T : K[number] extends never ? T : Pick<T, K[number]> : T;
type KeysOf<T> = Array<T extends T ? keyof T extends string ? keyof T : never : never>;
//@ts-ignore
export function useAuthFetch<
ResT = void,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
Method extends AvailableRouterMethod<ReqT> = ResT extends void ? 'get' extends AvailableRouterMethod<ReqT> ? 'get' : AvailableRouterMethod<ReqT> : AvailableRouterMethod<ReqT>,
_ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
DataT = _ResT,
PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
DefaultT = null
>(
request: Ref<ReqT> | ReqT | (() => ReqT),
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null> {
const projectStore = useProjectStore();
const snapshotStore = useSnapshotStore();
const domainStore = useDomainStore();
const { sharedLink, sharedFrom, sharedTo, sharedSlice, sharedPassword } = useShared();
const isDev = useDev();
opts = opts ?? {}
opts.lazy = opts.lazy ?? true;
opts.headers = opts.headers ?? {}
opts.headers = {
...opts.headers,
'x-pid': computed(() => projectStore.pid ?? ''),
'x-domain': computed(() => domainStore.activeDomain?._id ?? ''),
'x-from': computed(() => snapshotStore.from?.toString() ?? ''),
'x-to': computed(() => snapshotStore.to?.toString() ?? ''),
'x-dev': computed(() => isDev ? 'true' : 'false'),
'x-shared-link': computed(() => sharedLink.value ?? ''),
'x-shared-from': computed(() => sharedFrom.value ?? ''),
'x-shared-to': computed(() => sharedTo.value ?? ''),
'x-shared-slice': computed(() => sharedSlice.value ?? ''),
'x-shared-pass': computed(() => sharedPassword.value ?? ''),
}
return useFetch(request, opts);
}
export async function useAuthFetchSync<T>(url: string, options?: any) {
const projectStore = useProjectStore();
const snapshotStore = useSnapshotStore();
const domainStore = useDomainStore();
const { sharedLink, sharedFrom, sharedTo, sharedSlice, sharedPassword } = useShared();
const isDev = useDev();
options = options ?? {}
options.headers = options.headers ?? {}
options.headers = {
...options.headers,
'x-pid': projectStore.pid ?? '',
'x-domain': domainStore.activeDomain?._id ?? '',
'x-from': snapshotStore.from?.toString() ?? '',
'x-to': snapshotStore.to?.toString() ?? '',
'x-dev': isDev ? 'true' : 'false',
'x-shared-link': sharedLink.value ?? '',
'x-shared-from': sharedFrom.value ?? '',
'x-shared-to': sharedTo.value ?? '',
'x-shared-slice': sharedSlice.value ?? '',
'x-shared-pass': sharedPassword.value ?? ''
}
return await $fetch<T>(url, options);
}

View File

@@ -1,12 +0,0 @@
export function useBarCardDialog() {
const showDialog = useState('show-bar-card-dialog', () => false);
const dialogBarData = useState<any[]>('bar-card-dialog-data', () => []);
const isDataLoading = useState('bar-card-dialog-data-loading', () => false);
function closeDialog() {
showDialog.value = false;
}
return { showDialog, dialogBarData, closeDialog, isDataLoading };
}

View File

@@ -0,0 +1,51 @@
import { toast, type ExternalToast } from "vue-sonner";
import { FetchError } from 'ofetch'
const showToastFunction = (message: string, data: ExternalToast) => toast.success(message, { position: 'top-right', ...data });
export type UseCatchOptions<T> = {
toast?: boolean,
toastTitle?: string,
action: () => T,
onError?: (ex: FetchError | Error) => any,
onGenericError?: (ex: Error) => any,
onFetchError?: (ex: FetchError) => any,
onSuccess?: (data: Awaited<T>, showToast: typeof showToastFunction) => any
}
export const ERRORS_MAP: Record<string, string> = {
'WORKSPACE_LIMIT_REACHED': 'Workspace limit reached. Please upgrade your plan.',
'MEMBERS_LIMIT_REACHED': 'Members limit reached. Please upgrade your plan.'
}
export async function useCatch<T>(options: UseCatchOptions<T>): Promise<T | void> {
try {
const result = await options.action();
await options?.onSuccess?.(result, showToastFunction);
return result;
} catch (ex) {
console.dir(ex);
if (ex instanceof FetchError) {
if (options?.toast) {
const targetError = ERRORS_MAP[ex.data.statusMessage as string];
toast.error(options?.toastTitle ?? 'Error', { description: targetError ?? ex.data.message ?? 'An error occurred. Please contact support.', position: 'top-right' });
}
options?.onError?.(ex);
options?.onFetchError?.(ex);
} else if (ex instanceof Error) {
if (options?.toast) {
toast.error('Error', { description: 'An error occurred. Please contact support.', position: 'top-right' });
}
options?.onError?.(ex);
options?.onGenericError?.(ex);
} else {
console.error('NOT HANDLED');
}
}
}

View File

@@ -0,0 +1,9 @@
export function useChartColor() {
return {
visits: '#5655d7',
sessions: '#4abde8',
events: '#ffff00'
}
}

View File

@@ -1,42 +0,0 @@
const countryMap: Record<string, string> = {
RW: "Rwanda", SO: "Somalia", YE: "Yemen", IQ: "Iraq", SA: "Saudi Arabia", IR: "Iran", CY: "Cyprus", TZ: "Tanzania",
SY: "Syria", AM: "Armenia", KE: "Kenya", CD: "Congo", DJ: "Djibouti", UG: "Uganda", CF: "Central African Republic",
SC: "Seychelles", JO: "Jordan", LB: "Lebanon", KW: "Kuwait", OM: "Oman", QA: "Qatar", BH: "Bahrain", AE: "United Arab Emirates",
IL: "Israel", TR: "Türkiye", ET: "Ethiopia", ER: "Eritrea", EG: "Egypt", SD: "Sudan", GR: "Greece", BI: "Burundi",
EE: "Estonia", LV: "Latvia", AZ: "Azerbaijan", LT: "Lithuania", SJ: "Svalbard and Jan Mayen", GE: "Georgia", MD: "Moldova",
BY: "Belarus", FI: "Finland", AX: "Åland Islands", UA: "Ukraine", MK: "North Macedonia", HU: "Hungary", BG: "Bulgaria",
AL: "Albania", PL: "Poland", RO: "Romania", XK: "Kosovo", ZW: "Zimbabwe", ZM: "Zambia", KM: "Comoros", MW: "Malawi",
LS: "Lesotho", BW: "Botswana", MU: "Mauritius", SZ: "Eswatini", RE: "Réunion", ZA: "South Africa", YT: "Mayotte",
MZ: "Mozambique", MG: "Madagascar", AF: "Afghanistan", PK: "Pakistan", BD: "Bangladesh", TM: "Turkmenistan", TJ: "Tajikistan",
LK: "Sri Lanka", BT: "Bhutan", IN: "India", MV: "Maldives", IO: "British Indian Ocean Territory", NP: "Nepal", MM: "Myanmar",
UZ: "Uzbekistan", KZ: "Kazakhstan", KG: "Kyrgyzstan", TF: "French Southern Territories", HM: "Heard and McDonald Islands",
CC: "Cocos (Keeling) Islands", PW: "Palau", VN: "Vietnam", TH: "Thailand", ID: "Indonesia", LA: "Laos", TW: "Taiwan",
PH: "Philippines", MY: "Malaysia", CN: "China", HK: "Hong Kong", BN: "Brunei", MO: "Macao", KH: "Cambodia", KR: "South Korea",
JP: "Japan", KP: "North Korea", SG: "Singapore", CK: "Cook Islands", TL: "Timor-Leste", RU: "Russia", MN: "Mongolia",
AU: "Australia", CX: "Christmas Island", MH: "Marshall Islands", FM: "Federated States of Micronesia", PG: "Papua New Guinea",
SB: "Solomon Islands", TV: "Tuvalu", NR: "Nauru", VU: "Vanuatu", NC: "New Caledonia", NF: "Norfolk Island", NZ: "New Zealand",
FJ: "Fiji", LY: "Libya", CM: "Cameroon", SN: "Senegal", CG: "Congo Republic", PT: "Portugal", LR: "Liberia", CI: "Ivory Coast", GH: "Ghana",
GQ: "Equatorial Guinea", NG: "Nigeria", BF: "Burkina Faso", TG: "Togo", GW: "Guinea-Bissau", MR: "Mauritania", BJ: "Benin", GA: "Gabon",
SL: "Sierra Leone", ST: "São Tomé and Príncipe", GI: "Gibraltar", GM: "Gambia", GN: "Guinea", TD: "Chad", NE: "Niger", ML: "Mali",
EH: "Western Sahara", TN: "Tunisia", ES: "Spain", MA: "Morocco", MT: "Malta", DZ: "Algeria", FO: "Faroe Islands", DK: "Denmark",
IS: "Iceland", GB: "United Kingdom", CH: "Switzerland", SE: "Sweden", NL: "The Netherlands", AT: "Austria", BE: "Belgium",
DE: "Germany", LU: "Luxembourg", IE: "Ireland", MC: "Monaco", FR: "France", AD: "Andorra", LI: "Liechtenstein", JE: "Jersey",
IM: "Isle of Man", GG: "Guernsey", SK: "Slovakia", CZ: "Czechia", NO: "Norway", VA: "Vatican City", SM: "San Marino",
IT: "Italy", SI: "Slovenia", ME: "Montenegro", HR: "Croatia", BA: "Bosnia and Herzegovina", AO: "Angola", NA: "Namibia",
SH: "Saint Helena", BV: "Bouvet Island", BB: "Barbados", CV: "Cabo Verde", GY: "Guyana", GF: "French Guiana", SR: "Suriname",
PM: "Saint Pierre and Miquelon", GL: "Greenland", PY: "Paraguay", UY: "Uruguay", BR: "Brazil", FK: "Falkland Islands",
GS: "South Georgia and the South Sandwich Islands", JM: "Jamaica", DO: "Dominican Republic", CU: "Cuba", MQ: "Martinique",
BS: "Bahamas", BM: "Bermuda", AI: "Anguilla", TT: "Trinidad and Tobago", KN: "St Kitts and Nevis", DM: "Dominica",
AG: "Antigua and Barbuda", LC: "Saint Lucia", TC: "Turks and Caicos Islands", AW: "Aruba", VG: "British Virgin Islands",
VC: "St Vincent and Grenadines", MS: "Montserrat", MF: "Saint Martin", BL: "Saint Barthélemy", GP: "Guadeloupe",
GD: "Grenada", KY: "Cayman Islands", BZ: "Belize", SV: "El Salvador", GT: "Guatemala", HN: "Honduras", NI: "Nicaragua",
CR: "Costa Rica", VE: "Venezuela", EC: "Ecuador", CO: "Colombia", PA: "Panama", HT: "Haiti", AR: "Argentina", CL: "Chile",
BO: "Bolivia", PE: "Peru", MX: "Mexico", PF: "French Polynesia", PN: "Pitcairn Islands", KI: "Kiribati", TK: "Tokelau",
TO: "Tonga", WF: "Wallis and Futuna", WS: "Samoa", NU: "Niue", MP: "Northern Mariana Islands", GU: "Guam", PR: "Puerto Rico",
VI: "U.S. Virgin Islands", UM: "U.S. Outlying Islands", AS: "American Samoa", CA: "Canada", US: "United States",
PS: "Palestine", RS: "Serbia", AQ: "Antarctica", SX: "Sint Maarten", CW: "Curaçao", BQ: "Bonaire", SS: "South Sudan"
}
export function getCountryName(iso: string) {
return countryMap[iso] as string | undefined;
}

View File

@@ -1,45 +0,0 @@
import type { Component } from "vue";
const showDialog = ref<boolean>(false);
const dialogParams = ref<any>({});
const dialogComponent = ref<Component>();
const dialogWidth = ref<string>("100%");
const dialogHeight = ref<string>("100%");
const dialogClosable = ref<boolean>(true);
function closeDialog() {
showDialog.value = false;
}
export type CustomDialogOptions = {
params?: any,
width?: string,
height?: string,
closable?: boolean,
}
function openDialogEx(component: Component, options?: CustomDialogOptions) {
dialogComponent.value = component;
dialogParams.value = options?.params || {};
showDialog.value = true;
dialogWidth.value = options?.width || '100%';
dialogHeight.value = options?.height || '100%';
dialogClosable.value = options?.closable ?? true;
}
function openDialog(component: Component, params: any) {
dialogComponent.value = component;
dialogParams.value = params;
showDialog.value = true;
dialogWidth.value = '100%';
dialogHeight.value = '100%';
}
const dialogStyle = computed(() => {
return `width: ${dialogWidth.value}; height: ${dialogHeight.value}`;
});
export function useCustomDialog() {
return { showDialog, openDialogEx, closeDialog, openDialog, dialogParams, dialogComponent, dialogStyle, dialogClosable };
}

View File

@@ -1,55 +0,0 @@
type RefOrPrimitive<T> = T | Ref<T> | ComputedRef<T>
export type CustomOptions = {
useSnapshotDates?: boolean,
useActiveDomain?: boolean,
useActivePid?: boolean,
useTimeOffset?: boolean,
slice?: RefOrPrimitive<string>,
limit?: RefOrPrimitive<number | string>,
custom?: Record<string, RefOrPrimitive<string>>,
}
const { token } = useAccessToken();
const { projectId } = useProject();
const { safeSnapshotDates } = useSnapshot()
const { domain } = useDomain();
function getValueFromRefOrPrimitive<T>(data?: T | Ref<T> | ComputedRef<T>) {
if (!data) return;
if (isRef(data)) return data.value;
return data;
}
export function useComputedHeaders(customOptions?: CustomOptions) {
const useSnapshotDates = customOptions?.useSnapshotDates || true;
const useActivePid = customOptions?.useActivePid || true;
const useTimeOffset = customOptions?.useTimeOffset || true;
const useActiveDomain = customOptions?.useActiveDomain || true;
const headers = computed<Record<string, string>>(() => {
// console.trace('Computed recalculated');
const parsedCustom: Record<string, string> = {}
const customKeys = Object.keys(customOptions?.custom || {});
for (const key of customKeys) {
parsedCustom[key] = getValueFromRefOrPrimitive((customOptions?.custom || {})[key]) ?? ''
}
return {
'Authorization': `Bearer ${token.value}`,
'x-pid': useActivePid ? (projectId.value ?? '') : '',
'x-from': useSnapshotDates ? (safeSnapshotDates.value.from ?? '') : '',
'x-to': useSnapshotDates ? (safeSnapshotDates.value.to ?? '') : '',
'x-time-offset': useTimeOffset ? (new Date().getTimezoneOffset().toString()) : '',
'x-slice': getValueFromRefOrPrimitive(customOptions?.slice) ?? '',
'x-limit': getValueFromRefOrPrimitive(customOptions?.limit)?.toString() ?? '',
'x-domain': useActiveDomain ? (domain.value ?? '') : '',
...parsedCustom
}
})
return headers;
}

View File

@@ -0,0 +1,17 @@
const data = ref<any>({});
function setData(name: string, value: any) {
data.value[name] = value;
}
function getData(name: string, prefix: string = '', suffix: string = '') {
return `${prefix} ${data.value[name]} ${suffix}`;
}
export function useDev() {
const route = useRoute();
const isDev = computed(() => route.query['dev'] === "1");
return { isDev, setData, getData }
}

View File

@@ -0,0 +1,42 @@
type InferEmitPayload<E, K extends string> =
E extends Record<K, (...args: infer A) => any> ? A[0] : never
export type EmitOf<T, K extends string> =
T extends { __emitOptions?: infer E }
? InferEmitPayload<E, K>
: T extends new (...args: any) => { $emit: (event: K, ...args: infer A) => any }
? A[0]
: never
type ExtractProp<T, K extends string> =
T extends { new(...args: any): { $props: infer P } }
? K extends keyof P
? P[K]
: never
: never;
export type GlobalDialogPropsData<T extends Component> = {
body: T,
footer?: Component,
title?: string,
description?: string,
props?: ExtractProp<T, 'data'>
onSuccess?: (data: EmitOf<T, 'confirm'>, close: () => any) => any,
}
const currentDialogData = shallowRef<GlobalDialogPropsData<any>>();
const dialogOpen = ref<boolean>(false);
export function useDialog() {
const open = <T extends Component>(data: GlobalDialogPropsData<T>) => {
currentDialogData.value = data;
dialogOpen.value = true;
}
const close = () => {
dialogOpen.value = false;
}
return { open, close, isOpen: dialogOpen, data: currentDialogData }
}

View File

@@ -1,43 +0,0 @@
const { token } = useAccessToken();
const { projectId } = useProject();
const domainsRequest = useFetch<{ _id: string, visits: number }[]>('/api/domains/list', {
headers: computed(() => {
return {
'Authorization': `Bearer ${token.value}`,
'x-pid': projectId.value || ''
}
})
});
function refreshDomains() {
domainsRequest.refresh();
}
watch(domainsRequest.data, () => {
if (!domainsRequest.data.value) return;
setActiveDomain(domainList.value[0]._id);
});
const refreshingDomains = computed(() => domainsRequest.pending.value);
const domainList = computed(() => {
return (domainsRequest.data.value?.sort((a, b) => b.visits - a.visits) || []);
})
const activeDomain = ref<string>();
const domain = computed(() => {
return activeDomain.value;
})
function setActiveDomain(domain: string) {
activeDomain.value = domain;
}
export function useDomain() {
return { domainList, domain, setActiveDomain, refreshDomains, refreshingDomains }
}

View File

@@ -1,34 +0,0 @@
const drawerVisible = ref<boolean>(false);
const drawerComponent = ref<Component>();
const drawerClasses = ref<string>('')
type ComponentType = "DOCS" | "PRICING";
async function loadComponent(component: ComponentType): Promise<Component> {
switch (component) {
case "DOCS":
const DrawerDocs = await import("../components/drawer/Docs.vue");
return DrawerDocs.default;
case "PRICING":
const DrawerPricing = await import("../components/drawer/Pricing.vue");
return DrawerPricing.default;
default:
throw new Error("Unknown component type");
}
}
async function showDrawer(component: ComponentType, classes: string = "") {
drawerComponent.value = await loadComponent(component);
drawerVisible.value = true;
drawerClasses.value = classes;
}
function hideDrawer() {
drawerVisible.value = false;
}
export function useDrawer() {
return { drawerClasses, drawerVisible, drawerComponent, showDrawer, hideDrawer };
}

View File

@@ -0,0 +1,17 @@
const insight = ref<string>("");
const insightStatus = ref<'success' | 'pending'>('pending');
async function insightRefresh() {
if (isSelfhosted()) return;
insightStatus.value = 'pending';
insight.value = await useAuthFetchSync('/api/ai/insight');
insightStatus.value = 'success';
}
export function useInsight() {
if (insightStatus.value !== 'success') insightRefresh();
return { insight, insightRefresh, insightStatus }
}

View File

@@ -1,11 +0,0 @@
const route = useRoute();
export const isLiveDemo = computed(() => {
return route.path == '/live_demo';
})
const liveDemoData = useFetch('/api/live_demo');
export function useLiveDemo() { return liveDemoData; }

View File

@@ -1,39 +0,0 @@
import type { AuthContext } from "~/server/middleware/01-authorization";
const loggedUser = ref<AuthContext | undefined>();
const setLoggedUser = (authContext?: AuthContext) => {
loggedUser.value = authContext;
};
const isLogged = computed(() => {
return loggedUser.value?.logged;
})
function getUserRoles() {
const isPremium = computed(() => {
if (!loggedUser.value?.logged) return false;
return loggedUser.value.user.roles.includes('PREMIUM');
});
const isAdmin = computed(() => {
if (!loggedUser.value?.logged) return false;
return loggedUser.value.user.roles.includes('ADMIN');
});
return { isPremium, isAdmin }
}
export const isAdminHidden = ref<boolean>(false);
export function useLoggedUser() {
return {
isLogged,
user: loggedUser,
userRoles: getUserRoles(),
setLoggedUser
}
}

View File

@@ -1,12 +0,0 @@
const isOpen = ref<boolean>(false);
function open() { isOpen.value = true; }
function close() { isOpen.value = false; return true; }
function toggle() { isOpen.value = !isOpen.value; }
function set(value: boolean) { isOpen.value = value; }
export function useMenu() {
return { isOpen, open, close, toggle, set }
}

View File

@@ -1,28 +0,0 @@
const onlineUsers = useFetch<number>(`/api/data/live_users`, {
headers: useComputedHeaders({ useSnapshotDates: false }), immediate: false
});
let watching: any;
function startWatching(instant: boolean = true) {
if (instant) onlineUsers.execute();
watching = setInterval(async () => {
onlineUsers.refresh();
}, 20000);
}
function stopWatching() {
if (watching) clearInterval(watching);
}
export function useOnlineUsers() {
return {
onlineUsers,
startWatching,
stopWatching
}
}

View File

@@ -1,14 +0,0 @@
const { data: permission } = useFetch('/api/project/members/me', {
headers: useComputedHeaders({})
});
const canSeeWeb = computed(() => permission.value?.webAnalytics || false);
const canSeeEvents = computed(() => permission.value?.events || false);
const canSeeAi = computed(() => permission.value?.ai || false);
export function usePermission() {
return { permission, canSeeWeb, canSeeEvents, canSeeAi };
}

View File

@@ -1,87 +0,0 @@
import type { TProject } from "@schema/project/ProjectSchema";
import { ProjectSnapshotModel } from "@schema/project/ProjectSnapshot";
const { token } = useAccessToken();
const projectsRequest = useFetch<TProject[]>('/api/project/list', {
headers: computed(() => {
return {
'Authorization': `Bearer ${token.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 = async () => {
await projectsRequest.refresh();
await guestProjectsRequest.refresh();
}
const activeProjectId = ref<string | undefined>();
const setActiveProject = (project_id: string) => {
activeProjectId.value = project_id;
localStorage.setItem('active_pid', project_id);
}
const project = computed(() => {
if (isLiveDemo.value) return useLiveDemo().data.value;
if (!allProjectList.value) return;
if (allProjectList.value.length == 0) return;
if (activeProjectId.value) {
const target = allProjectList.value.find(e => e._id.toString() == activeProjectId.value);
if (target) return target;
}
const savedActive = localStorage.getItem('active_pid');
if (savedActive) {
const target = allProjectList.value.find(e => e._id.toString() == savedActive);
if (target) {
activeProjectId.value = savedActive;
return target;
}
}
activeProjectId.value = allProjectList.value[0]._id.toString();
return allProjectList.value[0];
})
const projectId = computed(() => project.value?._id.toString())
const isGuest = computed(() => {
return (projectList.value || []).find(e => e._id.toString() === activeProjectId.value) == undefined;
})
export function useProject() {
const actions = {
refreshProjectsList,
setActiveProject
}
return { project, allProjectList, guestProjectList, projectList, actions, projectId, isGuest }
}

View File

@@ -1,8 +0,0 @@
const { project } = useProject()
export const refreshKey = computed(() => {
if (!project.value) return 'null';
return project.value._id.toString();
})

View File

@@ -1,7 +0,0 @@
const app = useRuntimeConfig();
export function useSelfhosted() {
return app.public.SELFHOSTED.toString() === 'TRUE' || app.public.SELFHOSTED.toString() === 'true';
}

View File

@@ -0,0 +1,37 @@
import type { DateRange } from 'reka-ui';
import { CalendarDate } from '@internationalized/date'
import type { Slice } from '~/shared/services/DateService';
const sharedLink = ref<string>('');
const sharedPassword = ref<string>('');
const needPassword = ref<boolean>();
const timeValue = ref<DateRange>({
start: new CalendarDate(new Date().getFullYear(), new Date().getUTCMonth() + 1, 1),
end: new CalendarDate(new Date().getFullYear(), new Date().getUTCMonth() + 1, new Date().getDate())
}) as Ref<DateRange>;
const sharedFrom = computed(() => {
if (!timeValue.value.start) return '0';
return new Date(timeValue.value.start.toString()).getTime().toString();
});
const sharedTo = computed(() => {
if (!timeValue.value.end) return '0';
return new Date(timeValue.value.end.toString()).getTime().toString();
});
const sharedSlice = computed<Slice>(() => {
const dayDiff = (parseInt(sharedTo.value) - parseInt(sharedFrom.value)) / (1000 * 60 * 60 * 24);
if (dayDiff <= 3) return 'hour';
if (dayDiff <= 31 * 2) return 'day';
return 'month';
});
export function useShared() {
const route = useRoute();
const isShared = ref<boolean>(false);
if (route.fullPath.includes('/shared/')) isShared.value = true;
return { needPassword, sharedPassword, timeValue, sharedLink, isShared, sharedSlice, sharedFrom, sharedTo }
}

View File

@@ -1,46 +0,0 @@
import type { TProjectSnapshot } from "@schema/project/ProjectSnapshot";
import { getDefaultSnapshots, type GenericSnapshot } from "./snapshots/BaseSnapshots";
import * as fns from 'date-fns';
const { projectId, project } = useProject();
const headers = computed(() => {
return {
'Authorization': signHeaders().headers.Authorization,
'x-pid': projectId.value ?? ''
}
});
const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', { headers });
watch(project, async () => {
await remoteSnapshots.refresh();
snapshot.value = isLiveDemo.value ? snapshots.value[7] : snapshots.value[7];
});
const snapshots = computed<GenericSnapshot[]>(() => {
const defaultSnapshots: GenericSnapshot[] = project.value?._id ? getDefaultSnapshots(project.value._id as any, project.value.created_at) : [];
return [...defaultSnapshots, ...(remoteSnapshots.data.value || [])];
})
const snapshot = ref<GenericSnapshot>(snapshots.value[3]);
const safeSnapshotDates = computed(() => {
const from = new Date(snapshot.value?.from || 0).toISOString();
const to = new Date(snapshot.value?.to || Date.now()).toISOString();
return { from, to }
})
async function updateSnapshots() {
await remoteSnapshots.refresh();
}
const snapshotDuration = computed(() => {
const from = new Date(snapshot.value?.from || 0).getTime();
const to = new Date(snapshot.value?.to || 0).getTime() + 1000;
return fns.differenceInDays(to, from);
});
export function useSnapshot() {
return { snapshot, snapshots, safeSnapshotDates, updateSnapshots, snapshotDuration }
}

View File

@@ -1,36 +0,0 @@
export function useTextType(options: { ms: number, increase: number }, onTickAction?: () => any) {
let interval: any;
const index = ref<number>(0);
function onTick() {
index.value += options.increase;
onTickAction?.();
}
function pause() {
if (interval) clearInterval(interval);
}
function resume() {
if (interval) clearInterval(interval);
interval = setInterval(() => onTick(), options.ms);
}
function stop() {
if (interval) clearTimeout(interval);
}
function start() {
index.value = 0;
if (interval) clearInterval(interval);
interval = setInterval(() => onTick(), options.ms);
}
return { start, stop, resume, pause, index, interval }
}