mirror of
https://github.com/umami-software/umami.git
synced 2026-02-15 01:55:36 +01:00
Compare commits
No commits in common. "5213e04f444297b47eb3e5c7d8f5c8d1d68417f9" and "42d0594118b67d916e2c9a1d7e8e50dc7b72a5d9" have entirely different histories.
5213e04f44
...
42d0594118
16 changed files with 160 additions and 452 deletions
91
.gitignore
vendored
91
.gitignore
vendored
|
|
@ -1,46 +1,45 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store
|
||||
package-lock.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next
|
||||
/out
|
||||
|
||||
# production
|
||||
/build
|
||||
/public/script.js
|
||||
/geo
|
||||
/dist
|
||||
/generated
|
||||
/src/generated
|
||||
pm2.yml
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.idea
|
||||
.yarn
|
||||
*.iml
|
||||
*.log
|
||||
.vscode
|
||||
.tool-versions
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.*
|
||||
*.env.*
|
||||
|
||||
*.dev.yml
|
||||
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store
|
||||
package-lock.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next
|
||||
/out
|
||||
|
||||
# production
|
||||
/build
|
||||
/public/script.js
|
||||
/geo
|
||||
/dist
|
||||
/generated
|
||||
/src/generated
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.idea
|
||||
.yarn
|
||||
*.iml
|
||||
*.log
|
||||
.vscode
|
||||
.tool-versions
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.*
|
||||
*.env.*
|
||||
|
||||
*.dev.yml
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ const cloudMode = process.env.CLOUD_MODE || '';
|
|||
const cloudUrl = process.env.CLOUD_URL || '';
|
||||
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT || '';
|
||||
const corsMaxAge = process.env.CORS_MAX_AGE || '';
|
||||
const defaultCurrency = process.env.DEFAULT_CURRENCY || '';
|
||||
const defaultLocale = process.env.DEFAULT_LOCALE || '';
|
||||
const forceSSL = process.env.FORCE_SSL || '';
|
||||
const frameAncestors = process.env.ALLOWED_FRAME_URLS || '';
|
||||
|
|
@ -171,7 +170,6 @@ export default {
|
|||
cloudMode,
|
||||
cloudUrl,
|
||||
currentVersion: pkg.version,
|
||||
defaultCurrency,
|
||||
defaultLocale,
|
||||
},
|
||||
basePath,
|
||||
|
|
|
|||
|
|
@ -5,18 +5,6 @@
|
|||
"value": "访问代码"
|
||||
}
|
||||
],
|
||||
"label.account": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "账户"
|
||||
}
|
||||
],
|
||||
"label.action": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "行为"
|
||||
}
|
||||
],
|
||||
"label.actions": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -47,24 +35,12 @@
|
|||
"value": "添加描述"
|
||||
}
|
||||
],
|
||||
"label.add-link": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "添加链接"
|
||||
}
|
||||
],
|
||||
"label.add-member": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "添加成员"
|
||||
}
|
||||
],
|
||||
"label.add-pixel": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "添加像素"
|
||||
}
|
||||
],
|
||||
"label.add-step": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -107,24 +83,12 @@
|
|||
"value": "所有时间段"
|
||||
}
|
||||
],
|
||||
"label.analysis": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "分析"
|
||||
}
|
||||
],
|
||||
"label.analytics": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "分析"
|
||||
}
|
||||
],
|
||||
"label.application": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "应用"
|
||||
}
|
||||
],
|
||||
"label.apply": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -143,12 +107,6 @@
|
|||
"value": "查看用户如何与您的营销互动,以及是什么促成了转化。"
|
||||
}
|
||||
],
|
||||
"label.audience": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "受众"
|
||||
}
|
||||
],
|
||||
"label.average": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -167,12 +125,6 @@
|
|||
"value": "之前"
|
||||
}
|
||||
],
|
||||
"label.behavior": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "行为"
|
||||
}
|
||||
],
|
||||
"label.boards": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -221,24 +173,12 @@
|
|||
"value": "修改密码"
|
||||
}
|
||||
],
|
||||
"label.channel": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "渠道"
|
||||
}
|
||||
],
|
||||
"label.channels": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "渠道"
|
||||
}
|
||||
],
|
||||
"label.chart": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "图表"
|
||||
}
|
||||
],
|
||||
"label.cities": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -263,12 +203,6 @@
|
|||
"value": "队列"
|
||||
}
|
||||
],
|
||||
"label.cohorts": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "队列"
|
||||
}
|
||||
],
|
||||
"label.compare": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -383,12 +317,6 @@
|
|||
"value": "创建者"
|
||||
}
|
||||
],
|
||||
"label.criteria": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "条件"
|
||||
}
|
||||
],
|
||||
"label.currency": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -491,12 +419,6 @@
|
|||
"value": "台式机"
|
||||
}
|
||||
],
|
||||
"label.destination-url": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "目标URL"
|
||||
}
|
||||
],
|
||||
"label.details": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -533,12 +455,6 @@
|
|||
"value": "唯一ID"
|
||||
}
|
||||
],
|
||||
"label.documentation": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "文档"
|
||||
}
|
||||
],
|
||||
"label.does-not-contain": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -563,12 +479,6 @@
|
|||
"value": "域名"
|
||||
}
|
||||
],
|
||||
"label.download": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "下载"
|
||||
}
|
||||
],
|
||||
"label.dropoff": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -596,7 +506,7 @@
|
|||
"label.email": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "邮箱"
|
||||
"value": "Email"
|
||||
}
|
||||
],
|
||||
"label.enable-share-url": [
|
||||
|
|
@ -617,12 +527,6 @@
|
|||
"value": "入口 URL"
|
||||
}
|
||||
],
|
||||
"label.environment": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "环境"
|
||||
}
|
||||
],
|
||||
"label.event": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -767,12 +671,6 @@
|
|||
"value": "分组"
|
||||
}
|
||||
],
|
||||
"label.growth": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "增长"
|
||||
}
|
||||
],
|
||||
"label.hostname": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -803,12 +701,6 @@
|
|||
"value": "通过使用筛选器和划分时间段来更深入地研究数据。"
|
||||
}
|
||||
],
|
||||
"label.invalid-url": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "无效URL"
|
||||
}
|
||||
],
|
||||
"label.is": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -971,24 +863,12 @@
|
|||
"value": "少于等于"
|
||||
}
|
||||
],
|
||||
"label.link": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "链接"
|
||||
}
|
||||
],
|
||||
"label.links": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "链接"
|
||||
}
|
||||
],
|
||||
"label.location": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "位置"
|
||||
}
|
||||
],
|
||||
"label.login": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1140,7 +1020,7 @@
|
|||
"label.online": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "在线"
|
||||
"value": "Online"
|
||||
}
|
||||
],
|
||||
"label.organic-search": [
|
||||
|
|
@ -1285,12 +1165,6 @@
|
|||
"value": "路径"
|
||||
}
|
||||
],
|
||||
"label.pixel": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "像素"
|
||||
}
|
||||
],
|
||||
"label.pixels": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1311,12 +1185,6 @@
|
|||
"value": " 提供支持"
|
||||
}
|
||||
],
|
||||
"label.preferences": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "偏好"
|
||||
}
|
||||
],
|
||||
"label.previous": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1341,12 +1209,6 @@
|
|||
"value": "个人资料"
|
||||
}
|
||||
],
|
||||
"label.profiles": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "个人资料"
|
||||
}
|
||||
],
|
||||
"label.properties": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1386,7 +1248,7 @@
|
|||
"label.referral": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "来源"
|
||||
"value": "Referral"
|
||||
}
|
||||
],
|
||||
"label.referrer": [
|
||||
|
|
@ -1509,24 +1371,6 @@
|
|||
"value": "保存"
|
||||
}
|
||||
],
|
||||
"label.save-cohort": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "保存为群组"
|
||||
}
|
||||
],
|
||||
"label.save-segment": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "保存为细分"
|
||||
}
|
||||
],
|
||||
"label.screen": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "屏幕"
|
||||
}
|
||||
],
|
||||
"label.screens": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1539,18 +1383,6 @@
|
|||
"value": "搜索"
|
||||
}
|
||||
],
|
||||
"label.segment": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "细分"
|
||||
}
|
||||
],
|
||||
"label.segments": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "细分"
|
||||
}
|
||||
],
|
||||
"label.select": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1653,24 +1485,6 @@
|
|||
"value": "总和"
|
||||
}
|
||||
],
|
||||
"label.support": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "支持"
|
||||
}
|
||||
],
|
||||
"label.switch-account": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "切换账户"
|
||||
}
|
||||
],
|
||||
"label.table": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "表格"
|
||||
}
|
||||
],
|
||||
"label.tablet": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -1821,12 +1635,6 @@
|
|||
"value": "跟踪代码"
|
||||
}
|
||||
],
|
||||
"label.traffic": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "流量"
|
||||
}
|
||||
],
|
||||
"label.transactions": [
|
||||
{
|
||||
"type": 0,
|
||||
|
|
@ -2038,7 +1846,7 @@
|
|||
"message.bad-request": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "请求错误"
|
||||
"value": "Bad request"
|
||||
}
|
||||
],
|
||||
"message.collected-data": [
|
||||
|
|
@ -2138,7 +1946,7 @@
|
|||
"message.forbidden": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "禁止访问"
|
||||
"value": "Forbidden"
|
||||
}
|
||||
],
|
||||
"message.go-to-settings": [
|
||||
|
|
@ -2238,13 +2046,13 @@
|
|||
"message.not-found": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "未找到"
|
||||
"value": "Not found"
|
||||
}
|
||||
],
|
||||
"message.nothing-selected": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "未选择"
|
||||
"value": "Nothing selected."
|
||||
}
|
||||
],
|
||||
"message.page-not-found": [
|
||||
|
|
@ -2282,7 +2090,7 @@
|
|||
"message.sever-error": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "服务器错误"
|
||||
"value": "Server error"
|
||||
}
|
||||
],
|
||||
"message.share-url": [
|
||||
|
|
@ -2350,7 +2158,7 @@
|
|||
"message.unauthorized": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "未授权"
|
||||
"value": "Unauthorized"
|
||||
}
|
||||
],
|
||||
"message.user-deleted": [
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
"AD-06": "Sant Julia de Loria",
|
||||
"AD-07": "Andorra la Vella",
|
||||
"AD-08": "Escaldes-Engordany",
|
||||
"AE-AJ": "Ajman",
|
||||
"AE-AZ": "Abu Dhabi",
|
||||
"AE-DU": "Dubai",
|
||||
"AE-FU": "Al Fujairah",
|
||||
"AE-RK": "Ras al Khaimah",
|
||||
"AE-SH": "Sharjah",
|
||||
"AE-UQ": "Umm al Quwain",
|
||||
"AE-AJ": "'Ajman",
|
||||
"AE-AZ": "Abu Zaby",
|
||||
"AE-DU": "Dubayy",
|
||||
"AE-FU": "Al Fujayrah",
|
||||
"AE-RK": "Ra's al Khaymah",
|
||||
"AE-SH": "Ash Shariqah",
|
||||
"AE-UQ": "Umm al Qaywayn",
|
||||
"AF-BAL": "Balkh",
|
||||
"AF-BAM": "Bamyan",
|
||||
"AF-BDG": "Badghis",
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@ import {
|
|||
Form,
|
||||
FormField,
|
||||
FormSubmitButton,
|
||||
Grid,
|
||||
Icon,
|
||||
Label,
|
||||
Loading,
|
||||
Row,
|
||||
TextField,
|
||||
} from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useConfig, useLinkQuery, useMessages } from '@/components/hooks';
|
||||
import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
|
||||
import { RefreshCw } from '@/components/icons';
|
||||
|
|
@ -43,7 +42,7 @@ export function LinkEditForm({
|
|||
const { linksUrl } = useConfig();
|
||||
const hostUrl = linksUrl || LINKS_URL;
|
||||
const { data, isLoading } = useLinkQuery(linkId);
|
||||
const [defaultSlug] = useState(generateId());
|
||||
const [slug, setSlug] = useState(generateId());
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
await mutateAsync(data, {
|
||||
|
|
@ -56,6 +55,14 @@ export function LinkEditForm({
|
|||
});
|
||||
};
|
||||
|
||||
const handleSlug = () => {
|
||||
const slug = generateId();
|
||||
|
||||
setSlug(slug);
|
||||
|
||||
return slug;
|
||||
};
|
||||
|
||||
const checkUrl = (url: string) => {
|
||||
if (!isValidUrl(url)) {
|
||||
return formatMessage(labels.invalidUrl);
|
||||
|
|
@ -63,19 +70,19 @@ export function LinkEditForm({
|
|||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setSlug(data.slug);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (linkId && isLoading) {
|
||||
return <Loading placement="absolute" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
error={getErrorMessage(error)}
|
||||
defaultValues={{ slug: defaultSlug, ...data }}
|
||||
>
|
||||
{({ setValue, watch }) => {
|
||||
const slug = watch('slug');
|
||||
|
||||
<Form onSubmit={handleSubmit} error={getErrorMessage(error)} defaultValues={{ slug, ...data }}>
|
||||
{({ setValue }) => {
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
|
|
@ -94,25 +101,15 @@ export function LinkEditForm({
|
|||
<TextField placeholder="https://example.com" autoComplete="off" />
|
||||
</FormField>
|
||||
|
||||
<Grid columns="1fr auto" alignItems="end" gap>
|
||||
<FormField
|
||||
name="slug"
|
||||
label={formatMessage({ id: 'label.slug', defaultMessage: 'Slug' })}
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
}}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
<Button
|
||||
variant="quiet"
|
||||
onPress={() => setValue('slug', generateId(), { shouldDirty: true })}
|
||||
>
|
||||
<Icon>
|
||||
<RefreshCw />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Grid>
|
||||
<FormField
|
||||
name="slug"
|
||||
rules={{
|
||||
required: formatMessage(labels.required),
|
||||
}}
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<input type="hidden" />
|
||||
</FormField>
|
||||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.link)}</Label>
|
||||
|
|
@ -124,6 +121,14 @@ export function LinkEditForm({
|
|||
allowCopy
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
variant="quiet"
|
||||
onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}
|
||||
>
|
||||
<Icon>
|
||||
<RefreshCw />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
</Column>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ import { ListTable } from '@/components/metrics/ListTable';
|
|||
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
import { renderDateLabels } from '@/lib/charts';
|
||||
import { CHART_COLORS, CURRENCY_CONFIG, DEFAULT_CURRENCY } from '@/lib/constants';
|
||||
import { CHART_COLORS } from '@/lib/constants';
|
||||
import { generateTimeSeries } from '@/lib/date';
|
||||
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
||||
import { getItem, setItem } from '@/lib/storage';
|
||||
|
||||
export interface RevenueProps {
|
||||
websiteId: string;
|
||||
|
|
@ -25,15 +24,7 @@ export interface RevenueProps {
|
|||
}
|
||||
|
||||
export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
||||
const [currency, setCurrency] = useState(
|
||||
getItem(CURRENCY_CONFIG) || process.env.defaultCurrency || DEFAULT_CURRENCY,
|
||||
);
|
||||
|
||||
const handleCurrencyChange = (value: string) => {
|
||||
setCurrency(value);
|
||||
setItem(CURRENCY_CONFIG, value);
|
||||
};
|
||||
|
||||
const [currency, setCurrency] = useState('USD');
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { locale, dateLocale } = useLocale();
|
||||
const { countryNames } = useCountryNames(locale);
|
||||
|
|
@ -116,7 +107,7 @@ export function Revenue({ websiteId, startDate, endDate, unit }: RevenueProps) {
|
|||
return (
|
||||
<Column gap>
|
||||
<Grid columns="280px" gap>
|
||||
<CurrencySelect value={currency} onChange={handleCurrencyChange} />
|
||||
<CurrencySelect value={currency} onChange={setCurrency} />
|
||||
</Grid>
|
||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||
{data && (
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export function useEventDataValuesQuery(
|
|||
return useQuery<any>({
|
||||
queryKey: [
|
||||
'websites:event-data:values',
|
||||
{ websiteId, startAt, endAt, unit, timezone, ...filters, event, propertyName },
|
||||
{ websiteId, event, propertyName, startAt, endAt, unit, timezone, ...filters },
|
||||
],
|
||||
queryFn: () =>
|
||||
get(`/websites/${websiteId}/event-data/values`, {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export function WebsiteSelect({
|
|||
renderValue={renderValue}
|
||||
listProps={{
|
||||
renderEmptyState: () => <Empty message={formatMessage(messages.noResultsFound)} />,
|
||||
style: { maxHeight: 'calc(42vh - 65px)' },
|
||||
style: { maxHeight: '400px' },
|
||||
}}
|
||||
>
|
||||
{({ id, name }: any) => <ListItem key={id}>{name}</ListItem>}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
"label.event-name": "Име на събитие",
|
||||
"label.events": "Събития",
|
||||
"label.exists": "Съществува",
|
||||
"label.exit": "URL за изход",
|
||||
"label.exit": "Exit URL",
|
||||
"label.false": "Грешно",
|
||||
"label.field": "Поле",
|
||||
"label.fields": "Полета",
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
"label.last-days": "Последните {x} дни",
|
||||
"label.last-hours": "Последните {x} часа",
|
||||
"label.last-months": "Последните {x} месеца",
|
||||
"label.last-seen": "Последно видяно",
|
||||
"label.last-seen": "Last seen",
|
||||
"label.leave": "Напусни",
|
||||
"label.leave-team": "Напусни екип",
|
||||
"label.less-than": "По-малко от",
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
"label.none": "Няма",
|
||||
"label.number-of-records": "{x} {x, plural, one {един} other {други}}",
|
||||
"label.ok": "Добре",
|
||||
"label.online": "Онлайн",
|
||||
"label.online": "Online",
|
||||
"label.organic-search": "Органично търсене",
|
||||
"label.organic-shopping": "Органично пазаруване",
|
||||
"label.organic-social": "Органични социални мрежи",
|
||||
|
|
@ -185,9 +185,9 @@
|
|||
"label.paths": "Пътища",
|
||||
"label.pixels": "Пиксели",
|
||||
"label.powered-by": "Поддържано от {name}",
|
||||
"label.previous": "Предишен",
|
||||
"label.previous-period": "Предишен период",
|
||||
"label.previous-year": "Предишна година",
|
||||
"label.previous": "Previous",
|
||||
"label.previous-period": "Previous period",
|
||||
"label.previous-year": "Previous year",
|
||||
"label.profile": "Профил",
|
||||
"label.properties": "Свойства",
|
||||
"label.property": "Свойство",
|
||||
|
|
@ -211,8 +211,8 @@
|
|||
"label.reset-website": "Нулирай уебсайт",
|
||||
"label.retention": "Привързване",
|
||||
"label.retention-description": "Измерете привързаността към вашия уебсайт, като проследявате колко често потребителите се връщат.",
|
||||
"label.revenue": "Приходи",
|
||||
"label.revenue-description": "Прегледайте приходите си във времето.",
|
||||
"label.revenue": "Revenue",
|
||||
"label.revenue-description": "Look into your revenue across time.",
|
||||
"label.role": "Роля",
|
||||
"label.run-query": "Изпълни запитване",
|
||||
"label.save": "Запази",
|
||||
|
|
@ -260,14 +260,14 @@
|
|||
"label.total": "Общо",
|
||||
"label.total-records": "Общо записи",
|
||||
"label.tracking-code": "Код за проследяване",
|
||||
"label.transactions": "Транзакции",
|
||||
"label.transactions": "Transactions",
|
||||
"label.transfer": "Прехвърли",
|
||||
"label.transfer-website": "Прехвърляне на уебсайт",
|
||||
"label.true": "Вярно",
|
||||
"label.type": "Вид",
|
||||
"label.unique": "Уникален",
|
||||
"label.unique-visitors": "Уникални посетители",
|
||||
"label.uniqueCustomers": "Уникални клиенти",
|
||||
"label.uniqueCustomers": "Unique Customers",
|
||||
"label.unknown": "Неизвестен",
|
||||
"label.untitled": "Без заглавие",
|
||||
"label.update": "Актуализирай",
|
||||
|
|
@ -282,7 +282,7 @@
|
|||
"label.view-only": "Само за преглед",
|
||||
"label.views": "Прегледи",
|
||||
"label.views-per-visit": "Прегледи на посещение",
|
||||
"label.visit-duration": "Продължителност на посещение",
|
||||
"label.visit-duration": "Visit duration",
|
||||
"label.visitors": "Посетители",
|
||||
"label.visits": "Посещения",
|
||||
"label.website": "Уебсайт",
|
||||
|
|
@ -292,8 +292,8 @@
|
|||
"label.yesterday": "Вчера",
|
||||
"message.action-confirmation": "Въведете {confirmation} в полето по-долу, за да потвърдите.",
|
||||
"message.active-users": "{x} {x, plural, one {активен един} other {активни други}}",
|
||||
"message.bad-request": "Невалидна заявка",
|
||||
"message.collected-data": "Събрани данни",
|
||||
"message.bad-request": "Bad request",
|
||||
"message.collected-data": "Collected data",
|
||||
"message.confirm-delete": "Сигурни ли сте, че искате да изтриете {target}?",
|
||||
"message.confirm-leave": "Сигурни ли сте, че искате да напуснете {target}?",
|
||||
"message.confirm-remove": "Сигурни ли сте, че искате да премахнете {target}?",
|
||||
|
|
@ -302,7 +302,7 @@
|
|||
"message.delete-website-warning": "Всички данни за уебсайта ще бъдат изтрити.",
|
||||
"message.error": "Възникна грешка.",
|
||||
"message.event-log": "{event} на {url}",
|
||||
"message.forbidden": "Забранено",
|
||||
"message.forbidden": "Forbidden",
|
||||
"message.go-to-settings": "Отидете в настройките",
|
||||
"message.incorrect-username-password": "Неправилно потребителско име и/или парола.",
|
||||
"message.invalid-domain": "Невалиден домейн. Не включвайте http/https.",
|
||||
|
|
@ -316,13 +316,13 @@
|
|||
"message.no-teams": "Няма създадени екипи.",
|
||||
"message.no-users": "Няма потребители.",
|
||||
"message.no-websites-configured": "Нямате конфигурирани уебсайтове.",
|
||||
"message.not-found": "Не е намерено",
|
||||
"message.nothing-selected": "Няма избрано.",
|
||||
"message.not-found": "Not found",
|
||||
"message.nothing-selected": "Nothing selected.",
|
||||
"message.page-not-found": "Страницата не е намерена",
|
||||
"message.reset-website": "За да нулирате този уебсайт, въведете {confirmation} в полето по-долу, за да потвърдите.",
|
||||
"message.reset-website-warning": "Всички статистически данни за този уебсайт ще бъдат изтрити, но вашите настройки ще останат непроменени.",
|
||||
"message.saved": "Запазено.",
|
||||
"message.sever-error": "Сървърна грешка",
|
||||
"message.sever-error": "Server error",
|
||||
"message.share-url": "Статистиката за вашия уебсайт е публично достъпна на следния URL адрес:",
|
||||
"message.team-already-member": "Вече сте член на екипа.",
|
||||
"message.team-not-found": "Екипът не е намерен.",
|
||||
|
|
@ -332,7 +332,7 @@
|
|||
"message.transfer-user-website-to-team": "Изберете екипът на който да бъде прехвърлен уебсайта.",
|
||||
"message.transfer-website": "Прехвърли собствеността на уебсайта към вашия акаунт или към друг екип.",
|
||||
"message.triggered-event": "Активирано събитие",
|
||||
"message.unauthorized": "Неоторизиран достъп",
|
||||
"message.unauthorized": "Unauthorized",
|
||||
"message.user-deleted": "Потребителят е изтрит.",
|
||||
"message.viewed-page": "Страницата е видяна",
|
||||
"message.visitor-log": "Посетител от {country}, използващ {browser} на {os} {device}"
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
{
|
||||
"label.access-code": "访问代码",
|
||||
"label.account": "账户",
|
||||
"label.action": "行为",
|
||||
"label.actions": "用户行为",
|
||||
"label.activity": "活动日志",
|
||||
"label.add": "添加",
|
||||
"label.add-board": "添加看板",
|
||||
"label.add-description": "添加描述",
|
||||
"label.add-link": "添加链接",
|
||||
"label.add-member": "添加成员",
|
||||
"label.add-pixel": "添加像素",
|
||||
"label.add-step": "添加步骤",
|
||||
"label.add-website": "添加网站",
|
||||
"label.admin": "管理员",
|
||||
|
|
@ -17,13 +13,10 @@
|
|||
"label.after": "之后",
|
||||
"label.all": "所有",
|
||||
"label.all-time": "所有时间段",
|
||||
"label.analysis": "分析",
|
||||
"label.analytics": "分析",
|
||||
"label.application": "应用",
|
||||
"label.apply": "应用",
|
||||
"label.attribution": "归因",
|
||||
"label.attribution-description": "查看用户如何与您的营销互动,以及是什么促成了转化。",
|
||||
"label.audience": "受众",
|
||||
"label.average": "平均",
|
||||
"label.back": "返回",
|
||||
"label.before": "之前",
|
||||
|
|
@ -36,14 +29,11 @@
|
|||
"label.campaigns": "活动",
|
||||
"label.cancel": "取消",
|
||||
"label.change-password": "修改密码",
|
||||
"label.channel": "渠道",
|
||||
"label.channels": "渠道",
|
||||
"label.chart": "图表",
|
||||
"label.cities": "市/县",
|
||||
"label.city": "市/县",
|
||||
"label.clear-all": "清除全部",
|
||||
"label.cohort": "队列",
|
||||
"label.cohorts": "队列",
|
||||
"label.compare": "比较",
|
||||
"label.compare-dates": "比较日期",
|
||||
"label.confirm": "确认",
|
||||
|
|
@ -63,7 +53,6 @@
|
|||
"label.create-user": "创建用户",
|
||||
"label.created": "已创建",
|
||||
"label.created-by": "创建者",
|
||||
"label.criteria": "条件",
|
||||
"label.currency": "货币",
|
||||
"label.current": "当前",
|
||||
"label.current-password": "当前密码",
|
||||
|
|
@ -81,28 +70,24 @@
|
|||
"label.delete-website": "删除网站",
|
||||
"label.description": "描述",
|
||||
"label.desktop": "台式机",
|
||||
"label.destination-url": "目标URL",
|
||||
"label.details": "详细信息",
|
||||
"label.device": "设备",
|
||||
"label.devices": "设备",
|
||||
"label.direct": "直接",
|
||||
"label.dismiss": "关闭",
|
||||
"label.distinct-id": "唯一ID",
|
||||
"label.documentation": "文档",
|
||||
"label.does-not-contain": "不包含",
|
||||
"label.does-not-include": "不包括",
|
||||
"label.doest-not-exist": "不存在",
|
||||
"label.domain": "域名",
|
||||
"label.download": "下载",
|
||||
"label.dropoff": "丢弃",
|
||||
"label.edit": "编辑",
|
||||
"label.edit-dashboard": "编辑仪表盘",
|
||||
"label.edit-member": "编辑成员",
|
||||
"label.email": "邮箱",
|
||||
"label.email": "Email",
|
||||
"label.enable-share-url": "启用共享链接",
|
||||
"label.end-step": "结束步骤",
|
||||
"label.entry": "入口 URL",
|
||||
"label.environment": "环境",
|
||||
"label.event": "事件",
|
||||
"label.event-data": "事件数据",
|
||||
"label.event-name": "事件名称",
|
||||
|
|
@ -127,13 +112,11 @@
|
|||
"label.greater-than": "大于",
|
||||
"label.greater-than-equals": "大于或等于",
|
||||
"label.grouped": "分组",
|
||||
"label.growth": "增长",
|
||||
"label.hostname": "主机名",
|
||||
"label.includes": "包括",
|
||||
"label.insight": "洞察",
|
||||
"label.insights": "见解",
|
||||
"label.insights-description": "通过使用筛选器和划分时间段来更深入地研究数据。",
|
||||
"label.invalid-url": "无效URL",
|
||||
"label.is": "等于",
|
||||
"label.is-false": "否",
|
||||
"label.is-not": "不等于",
|
||||
|
|
@ -157,9 +140,7 @@
|
|||
"label.leave-team": "离开团队",
|
||||
"label.less-than": "少于",
|
||||
"label.less-than-equals": "少于等于",
|
||||
"label.link": "链接",
|
||||
"label.links": "链接",
|
||||
"label.location": "位置",
|
||||
"label.login": "登录",
|
||||
"label.logout": "退出",
|
||||
"label.manage": "管理",
|
||||
|
|
@ -180,7 +161,7 @@
|
|||
"label.none": "无",
|
||||
"label.number-of-records": "{x} {x, plural, one {record} other {records}}",
|
||||
"label.ok": "好的",
|
||||
"label.online": "在线",
|
||||
"label.online": "Online",
|
||||
"label.organic-search": "自然搜索",
|
||||
"label.organic-shopping": "自然购物",
|
||||
"label.organic-social": "自然社交",
|
||||
|
|
@ -202,22 +183,19 @@
|
|||
"label.password": "密码",
|
||||
"label.path": "路径",
|
||||
"label.paths": "路径",
|
||||
"label.pixel": "像素",
|
||||
"label.pixels": "像素",
|
||||
"label.powered-by": "由 {name} 提供支持",
|
||||
"label.preferences": "偏好",
|
||||
"label.previous": "先前",
|
||||
"label.previous-period": "上一时期",
|
||||
"label.previous-year": "上一年",
|
||||
"label.profile": "个人资料",
|
||||
"label.profiles": "个人资料",
|
||||
"label.properties": "属性",
|
||||
"label.property": "属性",
|
||||
"label.queries": "查询",
|
||||
"label.query": "查询",
|
||||
"label.query-parameters": "查询参数",
|
||||
"label.realtime": "实时",
|
||||
"label.referral": "来源",
|
||||
"label.referral": "Referral",
|
||||
"label.referrer": "来源",
|
||||
"label.referrers": "来源域名",
|
||||
"label.refresh": "刷新",
|
||||
|
|
@ -238,13 +216,8 @@
|
|||
"label.role": "角色",
|
||||
"label.run-query": "查询",
|
||||
"label.save": "保存",
|
||||
"label.save-cohort": "保存为群组",
|
||||
"label.save-segment": "保存为细分",
|
||||
"label.screen": "屏幕",
|
||||
"label.screens": "屏幕尺寸",
|
||||
"label.search": "搜索",
|
||||
"label.segment": "细分",
|
||||
"label.segments": "细分",
|
||||
"label.select": "选择",
|
||||
"label.select-date": "选择日期",
|
||||
"label.select-filter": "选择筛选器",
|
||||
|
|
@ -262,9 +235,6 @@
|
|||
"label.start-step": "开始步骤",
|
||||
"label.steps": "步骤",
|
||||
"label.sum": "总和",
|
||||
"label.support": "支持",
|
||||
"label.switch-account": "切换账户",
|
||||
"label.table": "表格",
|
||||
"label.tablet": "平板",
|
||||
"label.tag": "标签",
|
||||
"label.tags": "标签",
|
||||
|
|
@ -290,7 +260,6 @@
|
|||
"label.total": "总数",
|
||||
"label.total-records": "总记录数",
|
||||
"label.tracking-code": "跟踪代码",
|
||||
"label.traffic": "流量",
|
||||
"label.transactions": "交易",
|
||||
"label.transfer": "转移",
|
||||
"label.transfer-website": "转移网站",
|
||||
|
|
@ -323,7 +292,7 @@
|
|||
"label.yesterday": "昨天",
|
||||
"message.action-confirmation": "请在下方输入框中输入 {confirmation} 以确认操作。",
|
||||
"message.active-users": "当前在线 {x} 位访客",
|
||||
"message.bad-request": "请求错误",
|
||||
"message.bad-request": "Bad request",
|
||||
"message.collected-data": "已收集的数据",
|
||||
"message.confirm-delete": "你确定要删除 {target} 吗?",
|
||||
"message.confirm-leave": "你确定要离开 {target} 吗?",
|
||||
|
|
@ -333,7 +302,7 @@
|
|||
"message.delete-website-warning": "所有相关数据将会被删除。",
|
||||
"message.error": "发生错误。",
|
||||
"message.event-log": "{url} 上的 {event}",
|
||||
"message.forbidden": "禁止访问",
|
||||
"message.forbidden": "Forbidden",
|
||||
"message.go-to-settings": "去设置",
|
||||
"message.incorrect-username-password": "用户名或密码不正确。",
|
||||
"message.invalid-domain": "无效域名",
|
||||
|
|
@ -347,13 +316,13 @@
|
|||
"message.no-teams": "您尚未创建任何团队。",
|
||||
"message.no-users": "暂无用户。",
|
||||
"message.no-websites-configured": "你还没有设置任何网站。",
|
||||
"message.not-found": "未找到",
|
||||
"message.nothing-selected": "未选择",
|
||||
"message.not-found": "Not found",
|
||||
"message.nothing-selected": "Nothing selected.",
|
||||
"message.page-not-found": "页面未找到。",
|
||||
"message.reset-website": "如确定要重置该网站,请在下面输入 {confirmation} 以确认。",
|
||||
"message.reset-website-warning": "此网站的所有统计数据将被删除,但您的跟踪代码将保持不变。",
|
||||
"message.saved": "保存成功。",
|
||||
"message.sever-error": "服务器错误",
|
||||
"message.sever-error": "Server error",
|
||||
"message.share-url": "这是 {target} 的共享链接。",
|
||||
"message.team-already-member": "你已是该团队的成员。",
|
||||
"message.team-not-found": "未找到团队。",
|
||||
|
|
@ -363,7 +332,7 @@
|
|||
"message.transfer-user-website-to-team": "选择要转移此网站的团队。",
|
||||
"message.transfer-website": "将网站所有权转移到您的账户或其他团队。",
|
||||
"message.triggered-event": "触发事件",
|
||||
"message.unauthorized": "未授权",
|
||||
"message.unauthorized": "Unauthorized",
|
||||
"message.user-deleted": "用户已删除。",
|
||||
"message.viewed-page": "已浏览页面",
|
||||
"message.visitor-log": "来自 {country} 的访客在搭载 {os} 的 {device} 上使用 {browser} 浏览器进行访问。"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ export const LOCALE_CONFIG = 'umami.locale';
|
|||
export const TIMEZONE_CONFIG = 'umami.timezone';
|
||||
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
||||
export const THEME_CONFIG = 'umami.theme';
|
||||
export const CURRENCY_CONFIG = 'umami.currency';
|
||||
export const DASHBOARD_CONFIG = 'umami.dashboard';
|
||||
export const LAST_TEAM_CONFIG = 'umami.last-team';
|
||||
export const VERSION_CHECK = 'umami.version-check';
|
||||
|
|
@ -26,7 +25,6 @@ export const DEFAULT_WEBSITE_LIMIT = 10;
|
|||
export const DEFAULT_RESET_DATE = '2000-01-01';
|
||||
export const DEFAULT_PAGE_SIZE = 20;
|
||||
export const DEFAULT_DATE_COMPARE = 'prev';
|
||||
export const DEFAULT_CURRENCY = 'USD';
|
||||
|
||||
export const REALTIME_RANGE = 30;
|
||||
export const REALTIME_INTERVAL = 10000;
|
||||
|
|
|
|||
|
|
@ -28,12 +28,6 @@ const PROVIDER_HEADERS = [
|
|||
regionHeader: 'cloudfront-viewer-country-region',
|
||||
cityHeader: 'cloudfront-viewer-city',
|
||||
},
|
||||
// EdgeOne headers (requires custom request headers in Rule Priorities, see: https://edgeone.ai/document/46151)
|
||||
{
|
||||
countryHeader: 'eo-ipcountry',
|
||||
regionHeader: 'eo-region-code',
|
||||
cityHeader: 'eo-ipcity',
|
||||
},
|
||||
];
|
||||
|
||||
export function getDevice(userAgent: string, screen: string = '') {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { DEFAULT_CURRENCY } from './constants';
|
||||
|
||||
export function parseTime(val: number) {
|
||||
const days = ~~(val / 86400);
|
||||
const hours = ~~(val / 3600) - days * 24;
|
||||
|
|
@ -96,7 +94,7 @@ export function formatCurrency(value: number, currency: string, locale = 'en-US'
|
|||
// Fallback to default currency format if an error occurs
|
||||
formattedValue = new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency: DEFAULT_CURRENCY,
|
||||
currency: 'USD',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import ipaddr from 'ipaddr.js';
|
||||
|
||||
export const IP_ADDRESS_HEADERS = [
|
||||
'true-client-ip', // CDN
|
||||
'cf-connecting-ip', // Cloudflare
|
||||
|
|
@ -15,87 +13,35 @@ export const IP_ADDRESS_HEADERS = [
|
|||
'x-forwarded',
|
||||
];
|
||||
|
||||
/**
|
||||
* Normalize IP strings to a canonical form:
|
||||
* - strips IPv4-mapped IPv6 (e.g. ::ffff:192.0.2.1 -> 192.0.2.1)
|
||||
* - keeps valid IPv4/IPv6 as-is (canonically formatted by ipaddr.js)
|
||||
*/
|
||||
function normalizeIp(ip?: string | null) {
|
||||
if (!ip) return ip;
|
||||
|
||||
try {
|
||||
const parsed = ipaddr.parse(ip);
|
||||
|
||||
if (parsed.kind() === 'ipv6' && (parsed as ipaddr.IPv6).isIPv4MappedAddress()) {
|
||||
return (parsed as ipaddr.IPv6).toIPv4Address().toString();
|
||||
}
|
||||
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
// Fallback: return original if parsing fails
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveIp(ip?: string | null) {
|
||||
if (!ip) return ip;
|
||||
|
||||
// First, try as-is
|
||||
const normalized = normalizeIp(ip);
|
||||
try {
|
||||
ipaddr.parse(normalized);
|
||||
return normalized;
|
||||
} catch {
|
||||
// try stripping port (handles IPv4:port; leaves IPv6 intact)
|
||||
const stripped = stripPort(ip);
|
||||
if (stripped !== ip) {
|
||||
const normalizedStripped = normalizeIp(stripped);
|
||||
try {
|
||||
ipaddr.parse(normalizedStripped);
|
||||
return normalizedStripped;
|
||||
} catch {
|
||||
return normalizedStripped;
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
export function getIpAddress(headers: Headers) {
|
||||
const customHeader = process.env.CLIENT_IP_HEADER;
|
||||
|
||||
if (customHeader && headers.get(customHeader)) {
|
||||
return resolveIp(headers.get(customHeader));
|
||||
return headers.get(customHeader);
|
||||
}
|
||||
|
||||
const header = IP_ADDRESS_HEADERS.find(name => headers.get(name));
|
||||
if (!header) {
|
||||
return undefined;
|
||||
}
|
||||
const header = IP_ADDRESS_HEADERS.find(name => {
|
||||
return headers.get(name);
|
||||
});
|
||||
|
||||
const ip = headers.get(header);
|
||||
|
||||
if (header === 'x-forwarded-for') {
|
||||
return resolveIp(ip?.split(',')?.[0]?.trim());
|
||||
return ip?.split(',')?.[0]?.trim();
|
||||
}
|
||||
|
||||
if (header === 'forwarded') {
|
||||
const match = ip.match(/for=(\[?[0-9a-fA-F:.]+\]?)/);
|
||||
|
||||
if (match) {
|
||||
return resolveIp(match[1]);
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return resolveIp(ip);
|
||||
return ip;
|
||||
}
|
||||
|
||||
export function stripPort(ip?: string | null) {
|
||||
if (!ip) {
|
||||
return ip;
|
||||
}
|
||||
|
||||
export function stripPort(ip: string) {
|
||||
if (ip.startsWith('[')) {
|
||||
const endBracket = ip.indexOf(']');
|
||||
if (endBracket !== -1) {
|
||||
|
|
|
|||
|
|
@ -46,23 +46,31 @@ export async function relationalQuery({
|
|||
createdAt,
|
||||
}));
|
||||
|
||||
const existing = await client.sessionData.findMany({
|
||||
where: {
|
||||
sessionId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
sessionId: true,
|
||||
dataKey: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const data of flattenedData) {
|
||||
const { sessionId, dataKey, ...props } = data;
|
||||
const record = existing.find(e => e.sessionId === sessionId && e.dataKey === dataKey);
|
||||
|
||||
// Try to update existing record using compound where clause
|
||||
// This is safer than using id from a previous query due to race conditions
|
||||
const updateResult = await client.sessionData.updateMany({
|
||||
where: {
|
||||
sessionId,
|
||||
dataKey,
|
||||
},
|
||||
data: {
|
||||
...props,
|
||||
},
|
||||
});
|
||||
|
||||
// If no record was updated, create a new one
|
||||
if (updateResult.count === 0) {
|
||||
if (record) {
|
||||
await client.sessionData.update({
|
||||
where: {
|
||||
id: record.id,
|
||||
},
|
||||
data: {
|
||||
...props,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await client.sessionData.create({
|
||||
data,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,13 +12,7 @@
|
|||
if (!currentScript) return;
|
||||
|
||||
const { hostname, href, origin } = location;
|
||||
|
||||
let localStorage;
|
||||
try {
|
||||
localStorage = href.startsWith('data:') ? undefined : window.localStorage;
|
||||
} catch {
|
||||
/* (DOMException) SecurityError: Access is denied for this document. */
|
||||
}
|
||||
const localStorage = href.startsWith('data:') ? undefined : window.localStorage;
|
||||
|
||||
const _data = 'data-';
|
||||
const _false = 'false';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue