mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
new selfhosted version
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
34
dashboard/composables/useAppStart.ts
Normal file
34
dashboard/composables/useAppStart.ts
Normal 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 }
|
||||
|
||||
}
|
||||
84
dashboard/composables/useAuthFetch.ts
Normal file
84
dashboard/composables/useAuthFetch.ts
Normal 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);
|
||||
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
51
dashboard/composables/useCatch.ts
Normal file
51
dashboard/composables/useCatch.ts
Normal 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');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
9
dashboard/composables/useChartColor.ts
Normal file
9
dashboard/composables/useChartColor.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
export function useChartColor() {
|
||||
return {
|
||||
visits: '#5655d7',
|
||||
sessions: '#4abde8',
|
||||
events: '#ffff00'
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
17
dashboard/composables/useDev.ts
Normal file
17
dashboard/composables/useDev.ts
Normal 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 }
|
||||
}
|
||||
42
dashboard/composables/useDialog.ts
Normal file
42
dashboard/composables/useDialog.ts
Normal 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 }
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
17
dashboard/composables/useInsight.ts
Normal file
17
dashboard/composables/useInsight.ts
Normal 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 }
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
const { project } = useProject()
|
||||
|
||||
export const refreshKey = computed(() => {
|
||||
if (!project.value) return 'null';
|
||||
return project.value._id.toString();
|
||||
})
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
|
||||
const app = useRuntimeConfig();
|
||||
|
||||
export function useSelfhosted() {
|
||||
return app.public.SELFHOSTED.toString() === 'TRUE' || app.public.SELFHOSTED.toString() === 'true';
|
||||
}
|
||||
37
dashboard/composables/useShared.ts
Normal file
37
dashboard/composables/useShared.ts
Normal 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 }
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user