mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Compare commits
28 commits
13ab84d50e
...
9fbcec46af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fbcec46af | ||
|
|
d98cc35208 | ||
|
|
97ebdc1bab | ||
|
|
8a66603d32 | ||
|
|
e13362bfec | ||
|
|
371ff47325 | ||
|
|
3aa09572f5 | ||
|
|
a56746ce6d | ||
|
|
678a2ccdf3 | ||
|
|
bf498d9239 | ||
|
|
30781430c5 | ||
|
|
14f5babea7 | ||
|
|
14f3db550b | ||
|
|
3d8402d2f1 | ||
|
|
7ac5913c86 | ||
|
|
a6e130ab2e | ||
|
|
4fe4bb99b7 | ||
|
|
592f7c0ae7 | ||
|
|
8787764e0e | ||
|
|
839bf3898f | ||
|
|
b9e90268d1 | ||
|
|
03c892aac0 | ||
|
|
cf31ca20b9 | ||
|
|
c3f3a7db3f | ||
|
|
e49624a6da | ||
|
|
9ad17e0f62 | ||
|
|
6d16642a6c | ||
|
|
b200346fd3 |
35 changed files with 490 additions and 334 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,6 +5,7 @@ node_modules
|
||||||
.pnp
|
.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ docker pull docker.umami.is/umami-software/umami:latest
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔄 Getting Updates
|
## 🔄 Getting Updates
|
||||||
|
> [!WARNING]
|
||||||
|
> If you are updating from Umami V2, image "postgresql-latest" is deprecated. You must change it to "latest".
|
||||||
|
> e.g., rename `docker.umami.is/umami-software/umami:postgresql-latest` to `docker.umami.is/umami-software/umami:latest`.
|
||||||
|
|
||||||
To get the latest features, simply do a pull, install any new dependencies, and rebuild:
|
To get the latest features, simply do a pull, install any new dependencies, and rebuild:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ services:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://umami:umami@db:5432/umami
|
DATABASE_URL: postgresql://umami:umami@db:5432/umami
|
||||||
DATABASE_TYPE: postgresql
|
|
||||||
APP_SECRET: replace-me-with-a-random-string
|
APP_SECRET: replace-me-with-a-random-string
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@
|
||||||
"@react-spring/web": "^10.0.3",
|
"@react-spring/web": "^10.0.3",
|
||||||
"@svgr/cli": "^8.1.0",
|
"@svgr/cli": "^8.1.0",
|
||||||
"@tanstack/react-query": "^5.90.5",
|
"@tanstack/react-query": "^5.90.5",
|
||||||
"@umami/react-zen": "^0.203.0",
|
"@umami/react-zen": "^0.206.0",
|
||||||
"@umami/redis-client": "^0.29.0",
|
"@umami/redis-client": "^0.29.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
|
|
|
||||||
259
pnpm-lock.yaml
generated
259
pnpm-lock.yaml
generated
|
|
@ -45,8 +45,8 @@ importers:
|
||||||
specifier: ^5.90.5
|
specifier: ^5.90.5
|
||||||
version: 5.90.5(react@19.2.0)
|
version: 5.90.5(react@19.2.0)
|
||||||
'@umami/react-zen':
|
'@umami/react-zen':
|
||||||
specifier: ^0.203.0
|
specifier: ^0.206.0
|
||||||
version: 0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
version: 0.206.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
||||||
'@umami/redis-client':
|
'@umami/redis-client':
|
||||||
specifier: ^0.29.0
|
specifier: ^0.29.0
|
||||||
version: 0.29.0
|
version: 0.29.0
|
||||||
|
|
@ -878,6 +878,9 @@ packages:
|
||||||
'@emnapi/runtime@1.5.0':
|
'@emnapi/runtime@1.5.0':
|
||||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.7.0':
|
||||||
|
resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==}
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.0.4':
|
'@emnapi/wasi-threads@1.0.4':
|
||||||
resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
|
resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
|
||||||
|
|
||||||
|
|
@ -1163,8 +1166,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/sharp-darwin-arm64@0.34.4':
|
'@img/sharp-darwin-arm64@0.34.5':
|
||||||
resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==}
|
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
@ -1175,8 +1178,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/sharp-darwin-x64@0.34.4':
|
'@img/sharp-darwin-x64@0.34.5':
|
||||||
resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==}
|
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
@ -1186,8 +1189,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/sharp-libvips-darwin-arm64@1.2.3':
|
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||||
resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==}
|
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
|
@ -1196,8 +1199,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@img/sharp-libvips-darwin-x64@1.2.3':
|
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||||
resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==}
|
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
|
@ -1206,8 +1209,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm64@1.2.3':
|
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||||
resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
|
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
|
@ -1216,8 +1219,8 @@ packages:
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm@1.2.3':
|
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||||
resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
|
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
|
@ -1226,18 +1229,23 @@ packages:
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-ppc64@1.2.3':
|
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||||
resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
|
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||||
|
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-s390x@1.2.0':
|
'@img/sharp-libvips-linux-s390x@1.2.0':
|
||||||
resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==}
|
resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-s390x@1.2.3':
|
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||||
resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
|
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
|
@ -1246,8 +1254,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-x64@1.2.3':
|
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||||
resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
|
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
|
@ -1256,8 +1264,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
|
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||||
resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
|
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
|
@ -1266,8 +1274,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
|
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||||
resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
|
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
|
@ -1277,8 +1285,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linux-arm64@0.34.4':
|
'@img/sharp-linux-arm64@0.34.5':
|
||||||
resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
|
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
@ -1289,8 +1297,8 @@ packages:
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linux-arm@0.34.4':
|
'@img/sharp-linux-arm@0.34.5':
|
||||||
resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
|
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
@ -1301,20 +1309,26 @@ packages:
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linux-ppc64@0.34.4':
|
'@img/sharp-linux-ppc64@0.34.5':
|
||||||
resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
|
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
|
'@img/sharp-linux-riscv64@0.34.5':
|
||||||
|
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||||
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linux-s390x@0.34.3':
|
'@img/sharp-linux-s390x@0.34.3':
|
||||||
resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==}
|
resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linux-s390x@0.34.4':
|
'@img/sharp-linux-s390x@0.34.5':
|
||||||
resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
|
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
@ -1325,8 +1339,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linux-x64@0.34.4':
|
'@img/sharp-linux-x64@0.34.5':
|
||||||
resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
|
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
@ -1337,8 +1351,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-arm64@0.34.4':
|
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||||
resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
|
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
@ -1349,8 +1363,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-x64@0.34.4':
|
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||||
resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
|
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
@ -1360,8 +1374,8 @@ packages:
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [wasm32]
|
cpu: [wasm32]
|
||||||
|
|
||||||
'@img/sharp-wasm32@0.34.4':
|
'@img/sharp-wasm32@0.34.5':
|
||||||
resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
|
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [wasm32]
|
cpu: [wasm32]
|
||||||
|
|
||||||
|
|
@ -1371,8 +1385,8 @@ packages:
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@img/sharp-win32-arm64@0.34.4':
|
'@img/sharp-win32-arm64@0.34.5':
|
||||||
resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==}
|
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
@ -1383,8 +1397,8 @@ packages:
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@img/sharp-win32-ia32@0.34.4':
|
'@img/sharp-win32-ia32@0.34.5':
|
||||||
resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==}
|
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
@ -1395,8 +1409,8 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@img/sharp-win32-x64@0.34.4':
|
'@img/sharp-win32-x64@0.34.5':
|
||||||
resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==}
|
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
@ -2921,8 +2935,8 @@ packages:
|
||||||
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@umami/react-zen@0.203.0':
|
'@umami/react-zen@0.206.0':
|
||||||
resolution: {integrity: sha512-lgGUapA0zDbLu63GINaEPndIsT8ry85vE316AWU/EEH3qYDBNscetcBfZFr+DTD/c5eLKy9OxmMwIpbs7k+/UA==}
|
resolution: {integrity: sha512-9XM3Oj1akdyuwkMT1SldrJOyrMACP9TLJApZ/9ocmPuET4B7vpPxRoxv8OpEVlBaDw5nmlJfIvefsNMBLt1OQg==}
|
||||||
|
|
||||||
'@umami/redis-client@0.29.0':
|
'@umami/redis-client@0.29.0':
|
||||||
resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==}
|
resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==}
|
||||||
|
|
@ -3393,8 +3407,8 @@ packages:
|
||||||
caniuse-lite@1.0.30001741:
|
caniuse-lite@1.0.30001741:
|
||||||
resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==}
|
resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001751:
|
caniuse-lite@1.0.30001754:
|
||||||
resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==}
|
resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==}
|
||||||
|
|
||||||
caseless@0.12.0:
|
caseless@0.12.0:
|
||||||
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
|
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
|
||||||
|
|
@ -6540,8 +6554,8 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16.13.1'
|
react: '>=16.13.1'
|
||||||
|
|
||||||
react-hook-form@7.65.0:
|
react-hook-form@7.66.0:
|
||||||
resolution: {integrity: sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==}
|
resolution: {integrity: sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||||
|
|
@ -6846,8 +6860,8 @@ packages:
|
||||||
resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==}
|
resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
|
|
||||||
sharp@0.34.4:
|
sharp@0.34.5:
|
||||||
resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
|
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
|
||||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||||
|
|
||||||
shebang-command@1.2.0:
|
shebang-command@1.2.0:
|
||||||
|
|
@ -8184,6 +8198,11 @@ snapshots:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.7.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.0.4':
|
'@emnapi/wasi-threads@1.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
@ -8443,9 +8462,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-darwin-arm64': 1.2.0
|
'@img/sharp-libvips-darwin-arm64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-darwin-arm64@0.34.4':
|
'@img/sharp-darwin-arm64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-darwin-arm64': 1.2.3
|
'@img/sharp-libvips-darwin-arm64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-darwin-x64@0.34.3':
|
'@img/sharp-darwin-x64@0.34.3':
|
||||||
|
|
@ -8453,63 +8472,66 @@ snapshots:
|
||||||
'@img/sharp-libvips-darwin-x64': 1.2.0
|
'@img/sharp-libvips-darwin-x64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-darwin-x64@0.34.4':
|
'@img/sharp-darwin-x64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-darwin-x64': 1.2.3
|
'@img/sharp-libvips-darwin-x64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-darwin-arm64@1.2.0':
|
'@img/sharp-libvips-darwin-arm64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-darwin-arm64@1.2.3':
|
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-darwin-x64@1.2.0':
|
'@img/sharp-libvips-darwin-x64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-darwin-x64@1.2.3':
|
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm64@1.2.0':
|
'@img/sharp-libvips-linux-arm64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm64@1.2.3':
|
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm@1.2.0':
|
'@img/sharp-libvips-linux-arm@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-arm@1.2.3':
|
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-ppc64@1.2.0':
|
'@img/sharp-libvips-linux-ppc64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-ppc64@1.2.3':
|
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-s390x@1.2.0':
|
'@img/sharp-libvips-linux-s390x@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-s390x@1.2.3':
|
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-x64@1.2.0':
|
'@img/sharp-libvips-linux-x64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linux-x64@1.2.3':
|
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.0':
|
'@img/sharp-libvips-linuxmusl-arm64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.3':
|
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-x64@1.2.0':
|
'@img/sharp-libvips-linuxmusl-x64@1.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-libvips-linuxmusl-x64@1.2.3':
|
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-arm64@0.34.3':
|
'@img/sharp-linux-arm64@0.34.3':
|
||||||
|
|
@ -8517,9 +8539,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-linux-arm64': 1.2.0
|
'@img/sharp-libvips-linux-arm64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-arm64@0.34.4':
|
'@img/sharp-linux-arm64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-arm64': 1.2.3
|
'@img/sharp-libvips-linux-arm64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-arm@0.34.3':
|
'@img/sharp-linux-arm@0.34.3':
|
||||||
|
|
@ -8527,9 +8549,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-linux-arm': 1.2.0
|
'@img/sharp-libvips-linux-arm': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-arm@0.34.4':
|
'@img/sharp-linux-arm@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-arm': 1.2.3
|
'@img/sharp-libvips-linux-arm': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-ppc64@0.34.3':
|
'@img/sharp-linux-ppc64@0.34.3':
|
||||||
|
|
@ -8537,9 +8559,14 @@ snapshots:
|
||||||
'@img/sharp-libvips-linux-ppc64': 1.2.0
|
'@img/sharp-libvips-linux-ppc64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-ppc64@0.34.4':
|
'@img/sharp-linux-ppc64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-ppc64': 1.2.3
|
'@img/sharp-libvips-linux-ppc64': 1.2.4
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@img/sharp-linux-riscv64@0.34.5':
|
||||||
|
optionalDependencies:
|
||||||
|
'@img/sharp-libvips-linux-riscv64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-s390x@0.34.3':
|
'@img/sharp-linux-s390x@0.34.3':
|
||||||
|
|
@ -8547,9 +8574,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-linux-s390x': 1.2.0
|
'@img/sharp-libvips-linux-s390x': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-s390x@0.34.4':
|
'@img/sharp-linux-s390x@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-s390x': 1.2.3
|
'@img/sharp-libvips-linux-s390x': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-x64@0.34.3':
|
'@img/sharp-linux-x64@0.34.3':
|
||||||
|
|
@ -8557,9 +8584,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-linux-x64': 1.2.0
|
'@img/sharp-libvips-linux-x64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linux-x64@0.34.4':
|
'@img/sharp-linux-x64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linux-x64': 1.2.3
|
'@img/sharp-libvips-linux-x64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-arm64@0.34.3':
|
'@img/sharp-linuxmusl-arm64@0.34.3':
|
||||||
|
|
@ -8567,9 +8594,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.0
|
'@img/sharp-libvips-linuxmusl-arm64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-arm64@0.34.4':
|
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.3
|
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-x64@0.34.3':
|
'@img/sharp-linuxmusl-x64@0.34.3':
|
||||||
|
|
@ -8577,9 +8604,9 @@ snapshots:
|
||||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.0
|
'@img/sharp-libvips-linuxmusl-x64': 1.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-linuxmusl-x64@0.34.4':
|
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.3
|
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-wasm32@0.34.3':
|
'@img/sharp-wasm32@0.34.3':
|
||||||
|
|
@ -8587,27 +8614,27 @@ snapshots:
|
||||||
'@emnapi/runtime': 1.5.0
|
'@emnapi/runtime': 1.5.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-wasm32@0.34.4':
|
'@img/sharp-wasm32@0.34.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/runtime': 1.5.0
|
'@emnapi/runtime': 1.7.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-arm64@0.34.3':
|
'@img/sharp-win32-arm64@0.34.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-arm64@0.34.4':
|
'@img/sharp-win32-arm64@0.34.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-ia32@0.34.3':
|
'@img/sharp-win32-ia32@0.34.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-ia32@0.34.4':
|
'@img/sharp-win32-ia32@0.34.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-x64@0.34.3':
|
'@img/sharp-win32-x64@0.34.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@img/sharp-win32-x64@0.34.4':
|
'@img/sharp-win32-x64@0.34.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@internationalized/date@3.10.0':
|
'@internationalized/date@3.10.0':
|
||||||
|
|
@ -10670,7 +10697,7 @@ snapshots:
|
||||||
'@typescript-eslint/types': 8.46.2
|
'@typescript-eslint/types': 8.46.2
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
'@umami/react-zen@0.203.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))':
|
'@umami/react-zen@0.206.0(@babel/core@7.28.3)(@types/react@19.2.2)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.2.0)(use-sync-external-store@1.6.0(react@19.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/jetbrains-mono': 5.2.8
|
'@fontsource/jetbrains-mono': 5.2.8
|
||||||
'@internationalized/date': 3.10.0
|
'@internationalized/date': 3.10.0
|
||||||
|
|
@ -10684,7 +10711,7 @@ snapshots:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-aria-components: 1.13.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
react-aria-components: 1.13.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
react-hook-form: 7.65.0(react@19.2.0)
|
react-hook-form: 7.66.0(react@19.2.0)
|
||||||
react-icons: 5.5.0(react@19.2.0)
|
react-icons: 5.5.0(react@19.2.0)
|
||||||
thenby: 1.3.4
|
thenby: 1.3.4
|
||||||
zustand: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
zustand: 5.0.8(@types/react@19.2.2)(immer@10.2.0)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0))
|
||||||
|
|
@ -11215,7 +11242,7 @@ snapshots:
|
||||||
|
|
||||||
caniuse-lite@1.0.30001741: {}
|
caniuse-lite@1.0.30001741: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001751: {}
|
caniuse-lite@1.0.30001754: {}
|
||||||
|
|
||||||
caseless@0.12.0: {}
|
caseless@0.12.0: {}
|
||||||
|
|
||||||
|
|
@ -13902,7 +13929,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 15.5.6
|
'@next/env': 15.5.6
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
caniuse-lite: 1.0.30001751
|
caniuse-lite: 1.0.30001754
|
||||||
postcss: 8.4.31
|
postcss: 8.4.31
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-dom: 19.2.0(react@19.2.0)
|
react-dom: 19.2.0(react@19.2.0)
|
||||||
|
|
@ -13917,7 +13944,7 @@ snapshots:
|
||||||
'@next/swc-win32-arm64-msvc': 15.5.6
|
'@next/swc-win32-arm64-msvc': 15.5.6
|
||||||
'@next/swc-win32-x64-msvc': 15.5.6
|
'@next/swc-win32-x64-msvc': 15.5.6
|
||||||
babel-plugin-react-compiler: 19.1.0-rc.2
|
babel-plugin-react-compiler: 19.1.0-rc.2
|
||||||
sharp: 0.34.4
|
sharp: 0.34.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
|
@ -14839,7 +14866,7 @@ snapshots:
|
||||||
'@babel/runtime': 7.28.3
|
'@babel/runtime': 7.28.3
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
|
|
||||||
react-hook-form@7.65.0(react@19.2.0):
|
react-hook-form@7.66.0(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
|
|
||||||
|
|
@ -15266,34 +15293,36 @@ snapshots:
|
||||||
'@img/sharp-win32-x64': 0.34.3
|
'@img/sharp-win32-x64': 0.34.3
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
sharp@0.34.4:
|
sharp@0.34.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@img/colour': 1.0.0
|
'@img/colour': 1.0.0
|
||||||
detect-libc: 2.1.2
|
detect-libc: 2.1.2
|
||||||
semver: 7.7.3
|
semver: 7.7.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@img/sharp-darwin-arm64': 0.34.4
|
'@img/sharp-darwin-arm64': 0.34.5
|
||||||
'@img/sharp-darwin-x64': 0.34.4
|
'@img/sharp-darwin-x64': 0.34.5
|
||||||
'@img/sharp-libvips-darwin-arm64': 1.2.3
|
'@img/sharp-libvips-darwin-arm64': 1.2.4
|
||||||
'@img/sharp-libvips-darwin-x64': 1.2.3
|
'@img/sharp-libvips-darwin-x64': 1.2.4
|
||||||
'@img/sharp-libvips-linux-arm': 1.2.3
|
'@img/sharp-libvips-linux-arm': 1.2.4
|
||||||
'@img/sharp-libvips-linux-arm64': 1.2.3
|
'@img/sharp-libvips-linux-arm64': 1.2.4
|
||||||
'@img/sharp-libvips-linux-ppc64': 1.2.3
|
'@img/sharp-libvips-linux-ppc64': 1.2.4
|
||||||
'@img/sharp-libvips-linux-s390x': 1.2.3
|
'@img/sharp-libvips-linux-riscv64': 1.2.4
|
||||||
'@img/sharp-libvips-linux-x64': 1.2.3
|
'@img/sharp-libvips-linux-s390x': 1.2.4
|
||||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.3
|
'@img/sharp-libvips-linux-x64': 1.2.4
|
||||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.3
|
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
|
||||||
'@img/sharp-linux-arm': 0.34.4
|
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
|
||||||
'@img/sharp-linux-arm64': 0.34.4
|
'@img/sharp-linux-arm': 0.34.5
|
||||||
'@img/sharp-linux-ppc64': 0.34.4
|
'@img/sharp-linux-arm64': 0.34.5
|
||||||
'@img/sharp-linux-s390x': 0.34.4
|
'@img/sharp-linux-ppc64': 0.34.5
|
||||||
'@img/sharp-linux-x64': 0.34.4
|
'@img/sharp-linux-riscv64': 0.34.5
|
||||||
'@img/sharp-linuxmusl-arm64': 0.34.4
|
'@img/sharp-linux-s390x': 0.34.5
|
||||||
'@img/sharp-linuxmusl-x64': 0.34.4
|
'@img/sharp-linux-x64': 0.34.5
|
||||||
'@img/sharp-wasm32': 0.34.4
|
'@img/sharp-linuxmusl-arm64': 0.34.5
|
||||||
'@img/sharp-win32-arm64': 0.34.4
|
'@img/sharp-linuxmusl-x64': 0.34.5
|
||||||
'@img/sharp-win32-ia32': 0.34.4
|
'@img/sharp-wasm32': 0.34.5
|
||||||
'@img/sharp-win32-x64': 0.34.4
|
'@img/sharp-win32-arm64': 0.34.5
|
||||||
|
'@img/sharp-win32-ia32': 0.34.5
|
||||||
|
'@img/sharp-win32-x64': 0.34.5
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
shebang-command@1.2.0:
|
shebang-command@1.2.0:
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,18 @@ if (process.env.VERCEL && !process.env.BUILD_GEO) {
|
||||||
|
|
||||||
const db = 'GeoLite2-City';
|
const db = 'GeoLite2-City';
|
||||||
|
|
||||||
let url = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${db}.tar.gz`;
|
// Support custom URL via environment variable
|
||||||
|
let url = process.env.GEO_DATABASE_URL;
|
||||||
|
|
||||||
if (process.env.MAXMIND_LICENSE_KEY) {
|
// Fallback to default URLs if not provided
|
||||||
url =
|
if (!url) {
|
||||||
`https://download.maxmind.com/app/geoip_download` +
|
if (process.env.MAXMIND_LICENSE_KEY) {
|
||||||
`?edition_id=${db}&license_key=${process.env.MAXMIND_LICENSE_KEY}&suffix=tar.gz`;
|
url =
|
||||||
|
`https://download.maxmind.com/app/geoip_download` +
|
||||||
|
`?edition_id=${db}&license_key=${process.env.MAXMIND_LICENSE_KEY}&suffix=tar.gz`;
|
||||||
|
} else {
|
||||||
|
url = `https://raw.githubusercontent.com/GitSquared/node-geolite2-redist/master/redist/${db}.tar.gz`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dest = path.resolve(process.cwd(), 'geo');
|
const dest = path.resolve(process.cwd(), 'geo');
|
||||||
|
|
@ -27,30 +33,72 @@ if (!fs.existsSync(dest)) {
|
||||||
fs.mkdirSync(dest);
|
fs.mkdirSync(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
const download = url =>
|
// Check if URL points to a direct .mmdb file (already extracted)
|
||||||
|
const isDirectMmdb = url.endsWith('.mmdb');
|
||||||
|
|
||||||
|
// Download handler for compressed tar.gz files
|
||||||
|
const downloadCompressed = url =>
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
https.get(url, res => {
|
https.get(url, res => {
|
||||||
resolve(res.pipe(zlib.createGunzip({})).pipe(tar.t()));
|
resolve(res.pipe(zlib.createGunzip({})).pipe(tar.t()));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
download(url).then(
|
// Download handler for direct .mmdb files
|
||||||
res =>
|
const downloadDirect = (url, originalUrl) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
res.on('entry', entry => {
|
https.get(url, res => {
|
||||||
if (entry.path.endsWith('.mmdb')) {
|
// Follow redirects
|
||||||
const filename = path.join(dest, path.basename(entry.path));
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||||
entry.pipe(fs.createWriteStream(filename));
|
downloadDirect(res.headers.location, originalUrl || url).then(resolve).catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Saved geo database:', filename);
|
const filename = path.join(dest, path.basename(originalUrl || url));
|
||||||
}
|
const fileStream = fs.createWriteStream(filename);
|
||||||
});
|
|
||||||
|
|
||||||
res.on('error', e => {
|
res.pipe(fileStream);
|
||||||
reject(e);
|
|
||||||
});
|
fileStream.on('finish', () => {
|
||||||
res.on('finish', () => {
|
fileStream.close();
|
||||||
|
console.log('Saved geo database:', filename);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}),
|
|
||||||
);
|
fileStream.on('error', e => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Execute download based on file type
|
||||||
|
if (isDirectMmdb) {
|
||||||
|
downloadDirect(url).catch(e => {
|
||||||
|
console.error('Failed to download geo database:', e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
downloadCompressed(url).then(
|
||||||
|
res =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
res.on('entry', entry => {
|
||||||
|
if (entry.path.endsWith('.mmdb')) {
|
||||||
|
const filename = path.join(dest, path.basename(entry.path));
|
||||||
|
entry.pipe(fs.createWriteStream(filename));
|
||||||
|
|
||||||
|
console.log('Saved geo database:', filename);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('error', e => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
res.on('finish', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
).catch(e => {
|
||||||
|
console.error('Failed to download geo database:', e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ async function checkEnv() {
|
||||||
} else {
|
} else {
|
||||||
success('DATABASE_URL is defined.');
|
success('DATABASE_URL is defined.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.REDIS_URL) {
|
||||||
|
success('REDIS_URL is defined.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkConnection() {
|
async function checkConnection() {
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export function Attribution({
|
||||||
})}
|
})}
|
||||||
</MetricsBar>
|
</MetricsBar>
|
||||||
<SectionHeader title={formatMessage(labels.sources)} />
|
<SectionHeader title={formatMessage(labels.sources)} />
|
||||||
<Grid columns="1fr 1fr" gap>
|
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
|
||||||
<Panel>
|
<Panel>
|
||||||
<AttributionTable data={data?.['referrer']} title={formatMessage(labels.referrer)} />
|
<AttributionTable data={data?.['referrer']} title={formatMessage(labels.referrer)} />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
@ -104,7 +104,7 @@ export function Attribution({
|
||||||
</Panel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<SectionHeader title="UTM" />
|
<SectionHeader title="UTM" />
|
||||||
<Grid columns="1fr 1fr" gap>
|
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
|
||||||
<Panel>
|
<Panel>
|
||||||
<AttributionTable data={data?.['utm_source']} title={formatMessage(labels.sources)} />
|
<AttributionTable data={data?.['utm_source']} title={formatMessage(labels.sources)} />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
||||||
return (
|
return (
|
||||||
<Column gap="6">
|
<Column gap="6">
|
||||||
<WebsiteControls websiteId={websiteId} />
|
<WebsiteControls websiteId={websiteId} />
|
||||||
<Grid columns="1fr 1fr 1fr" gap>
|
<Grid columns={{ xs: '1fr', md: '1fr 1fr 1fr' }} gap>
|
||||||
<Column>
|
<Column>
|
||||||
<Select
|
<Select
|
||||||
label={formatMessage(labels.model)}
|
label={formatMessage(labels.model)}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Text, DataTable, DataColumn } from '@umami/react-zen';
|
import { Text, DataTable, DataColumn, Column } from '@umami/react-zen';
|
||||||
import { useMessages, useResultQuery, useFormat, useFields } from '@/components/hooks';
|
import { useMessages, useResultQuery, useFormat, useFields } from '@/components/hooks';
|
||||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import { formatShortTime } from '@/lib/format';
|
import { formatShortTime } from '@/lib/format';
|
||||||
|
|
@ -27,43 +27,65 @@ export function Breakdown({ websiteId, selectedFields = [], startDate, endDate }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||||
<DataTable data={data}>
|
<Column overflow="auto" minHeight="0" height="100%">
|
||||||
{selectedFields.map(field => {
|
<DataTable data={data} style={{ tableLayout: 'fixed' }}>
|
||||||
return (
|
{selectedFields.map(field => {
|
||||||
<DataColumn key={field} id={field} label={fields.find(f => f.name === field)?.label}>
|
return (
|
||||||
{row => {
|
<DataColumn
|
||||||
const value = formatValue(row[field], field);
|
key={field}
|
||||||
return (
|
id={field}
|
||||||
<Text truncate title={value}>
|
label={fields.find(f => f.name === field)?.label}
|
||||||
{value}
|
width="minmax(120px, 1fr)"
|
||||||
</Text>
|
>
|
||||||
);
|
{row => {
|
||||||
}}
|
const value = formatValue(row[field], field);
|
||||||
</DataColumn>
|
return (
|
||||||
);
|
<Text truncate title={value}>
|
||||||
})}
|
{value}
|
||||||
<DataColumn id="visitors" label={formatMessage(labels.visitors)} align="end">
|
</Text>
|
||||||
{row => row?.['visitors']?.toLocaleString()}
|
);
|
||||||
</DataColumn>
|
}}
|
||||||
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end">
|
</DataColumn>
|
||||||
{row => row?.['visits']?.toLocaleString()}
|
);
|
||||||
</DataColumn>
|
})}
|
||||||
<DataColumn id="views" label={formatMessage(labels.views)} align="end">
|
<DataColumn
|
||||||
{row => row?.['views']?.toLocaleString()}
|
id="visitors"
|
||||||
</DataColumn>
|
label={formatMessage(labels.visitors)}
|
||||||
<DataColumn id="bounceRate" label={formatMessage(labels.bounceRate)} align="end">
|
align="end"
|
||||||
{row => {
|
width="120px"
|
||||||
const n = (Math.min(row?.['visits'], row?.['bounces']) / row?.['visits']) * 100;
|
>
|
||||||
return Math.round(+n) + '%';
|
{row => row?.['visitors']?.toLocaleString()}
|
||||||
}}
|
</DataColumn>
|
||||||
</DataColumn>
|
<DataColumn id="visits" label={formatMessage(labels.visits)} align="end" width="120px">
|
||||||
<DataColumn id="visitDuration" label={formatMessage(labels.visitDuration)} align="end">
|
{row => row?.['visits']?.toLocaleString()}
|
||||||
{row => {
|
</DataColumn>
|
||||||
const n = row?.['totaltime'] / row?.['visits'];
|
<DataColumn id="views" label={formatMessage(labels.views)} align="end" width="120px">
|
||||||
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
|
{row => row?.['views']?.toLocaleString()}
|
||||||
}}
|
</DataColumn>
|
||||||
</DataColumn>
|
<DataColumn
|
||||||
</DataTable>
|
id="bounceRate"
|
||||||
|
label={formatMessage(labels.bounceRate)}
|
||||||
|
align="end"
|
||||||
|
width="120px"
|
||||||
|
>
|
||||||
|
{row => {
|
||||||
|
const n = (Math.min(row?.['visits'], row?.['bounces']) / row?.['visits']) * 100;
|
||||||
|
return Math.round(+n) + '%';
|
||||||
|
}}
|
||||||
|
</DataColumn>
|
||||||
|
<DataColumn
|
||||||
|
id="visitDuration"
|
||||||
|
label={formatMessage(labels.visitDuration)}
|
||||||
|
align="end"
|
||||||
|
width="120px"
|
||||||
|
>
|
||||||
|
{row => {
|
||||||
|
const n = row?.['totaltime'] / row?.['visits'];
|
||||||
|
return `${+n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`;
|
||||||
|
}}
|
||||||
|
</DataColumn>
|
||||||
|
</DataTable>
|
||||||
|
</Column>
|
||||||
</LoadingPanel>
|
</LoadingPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { FieldSelectForm } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/FieldSelectForm';
|
import { FieldSelectForm } from '@/app/(main)/websites/[websiteId]/(reports)/breakdown/FieldSelectForm';
|
||||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { useDateRange, useMessages, useMobile } from '@/components/hooks';
|
import { useDateRange, useMessages } from '@/components/hooks';
|
||||||
import { ListCheck } from '@/components/icons';
|
import { ListCheck } from '@/components/icons';
|
||||||
import { DialogButton } from '@/components/input/DialogButton';
|
import { DialogButton } from '@/components/input/DialogButton';
|
||||||
import { Column, Row } from '@umami/react-zen';
|
import { Column, Row } from '@umami/react-zen';
|
||||||
|
|
@ -14,12 +14,10 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||||
dateRange: { startDate, endDate },
|
dateRange: { startDate, endDate },
|
||||||
} = useDateRange();
|
} = useDateRange();
|
||||||
const [fields, setFields] = useState(['path']);
|
const [fields, setFields] = useState(['path']);
|
||||||
const { isMobile } = useMobile();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<WebsiteControls websiteId={websiteId} />
|
<WebsiteControls websiteId={websiteId} />
|
||||||
<Row alignItems="center" justifyContent={isMobile ? 'flex-end' : 'flex-start'}>
|
<Row alignItems="center" justifyContent="flex-start">
|
||||||
<FieldsButton value={fields} onChange={setFields} />
|
<FieldsButton value={fields} onChange={setFields} />
|
||||||
</Row>
|
</Row>
|
||||||
<Panel height="900px" overflow="auto" allowFullscreen>
|
<Panel height="900px" overflow="auto" allowFullscreen>
|
||||||
|
|
@ -41,8 +39,9 @@ const FieldsButton = ({ value, onChange }) => {
|
||||||
<DialogButton
|
<DialogButton
|
||||||
icon={<ListCheck />}
|
icon={<ListCheck />}
|
||||||
label={formatMessage(labels.fields)}
|
label={formatMessage(labels.fields)}
|
||||||
width="800px"
|
width="400px"
|
||||||
minHeight="300px"
|
minHeight="300px"
|
||||||
|
variant="outline"
|
||||||
>
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return <FieldSelectForm selectedFields={value} onChange={onChange} onClose={close} />;
|
return <FieldSelectForm selectedFields={value} onChange={onChange} onClose={close} />;
|
||||||
|
|
|
||||||
|
|
@ -51,51 +51,72 @@ export function Retention({ websiteId, days = DAYS, startDate, endDate }: Retent
|
||||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||||
{data && (
|
{data && (
|
||||||
<Panel allowFullscreen height="900px">
|
<Panel allowFullscreen height="900px">
|
||||||
<Column gap="1" width="100%" overflow="auto">
|
<Column
|
||||||
<Grid
|
paddingY="6"
|
||||||
columns="120px repeat(10, 100px)"
|
paddingX={{ xs: '3', md: '6' }}
|
||||||
alignItems="center"
|
position="absolute"
|
||||||
gap="1"
|
top="40px"
|
||||||
height="50px"
|
left="0"
|
||||||
autoFlow="column"
|
right="0"
|
||||||
>
|
bottom="0"
|
||||||
<Column>
|
>
|
||||||
<Text weight="bold" align="center">
|
<Column gap="1" overflow="auto">
|
||||||
{formatMessage(labels.cohort)}
|
<Grid
|
||||||
</Text>
|
columns="120px repeat(10, 100px)"
|
||||||
</Column>
|
alignItems="center"
|
||||||
{days.map(n => (
|
gap="1"
|
||||||
<Column key={n}>
|
height="50px"
|
||||||
<Text weight="bold" align="center" wrap="nowrap">
|
width="max-content"
|
||||||
{formatMessage(labels.day)} {n}
|
minWidth="100%"
|
||||||
|
autoFlow="column"
|
||||||
|
>
|
||||||
|
<Column>
|
||||||
|
<Text weight="bold" align="center">
|
||||||
|
{formatMessage(labels.cohort)}
|
||||||
</Text>
|
</Text>
|
||||||
</Column>
|
</Column>
|
||||||
))}
|
{days.map(n => (
|
||||||
</Grid>
|
<Column key={n}>
|
||||||
{rows.map(({ date, visitors, records }: any, rowIndex: number) => {
|
<Text weight="bold" align="center" wrap="nowrap">
|
||||||
return (
|
{formatMessage(labels.day)} {n}
|
||||||
<Grid key={rowIndex} columns="120px repeat(10, 100px)" gap="1" autoFlow="column">
|
</Text>
|
||||||
<Column justifyContent="center" gap="1">
|
|
||||||
<Text weight="bold">{formatDate(date, 'PP', locale)}</Text>
|
|
||||||
<Row alignItems="center" gap>
|
|
||||||
<Icon>
|
|
||||||
<Users />
|
|
||||||
</Icon>
|
|
||||||
<Text>{formatLongNumber(visitors)}</Text>
|
|
||||||
</Row>
|
|
||||||
</Column>
|
</Column>
|
||||||
{days.map(day => {
|
))}
|
||||||
if (totalDays - rowIndex < day) {
|
</Grid>
|
||||||
return null;
|
{rows.map(({ date, visitors, records }: any, rowIndex: number) => {
|
||||||
}
|
return (
|
||||||
const percentage = records.filter(a => a.day === day)[0]?.percentage;
|
<Grid
|
||||||
return (
|
key={rowIndex}
|
||||||
<Cell key={day}>{percentage ? `${Number(percentage).toFixed(2)}%` : ''}</Cell>
|
columns="120px repeat(10, 100px)"
|
||||||
);
|
gap="1"
|
||||||
})}
|
autoFlow="column"
|
||||||
</Grid>
|
width="max-content"
|
||||||
);
|
minWidth="100%"
|
||||||
})}
|
>
|
||||||
|
<Column justifyContent="center" gap="1">
|
||||||
|
<Text weight="bold">{formatDate(date, 'PP', locale)}</Text>
|
||||||
|
<Row alignItems="center" gap>
|
||||||
|
<Icon>
|
||||||
|
<Users />
|
||||||
|
</Icon>
|
||||||
|
<Text>{formatLongNumber(visitors)}</Text>
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
{days.map(day => {
|
||||||
|
if (totalDays - rowIndex < day) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const percentage = records.filter(a => a.day === day)[0]?.percentage;
|
||||||
|
return (
|
||||||
|
<Cell key={day}>
|
||||||
|
{percentage ? `${Number(percentage).toFixed(2)}%` : ''}
|
||||||
|
</Cell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Column>
|
||||||
</Column>
|
</Column>
|
||||||
</Panel>
|
</Panel>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel key={param}>
|
<Panel key={param}>
|
||||||
<Grid columns="1fr 1fr">
|
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap="6">
|
||||||
<Column>
|
<Column>
|
||||||
<Heading>
|
<Heading>
|
||||||
<Text transform="capitalize">{param.replace(/^utm_/, '')}</Text>
|
<Text transform="capitalize">{param.replace(/^utm_/, '')}</Text>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export function ExpandedViewModal({
|
||||||
maxWidth: 1320,
|
maxWidth: 1320,
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
height: isMobile ? '100dvh' : 'calc(100dvh - 40px)',
|
height: isMobile ? '100dvh' : 'calc(100dvh - 40px)',
|
||||||
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ export function WebsiteControls({
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
<Grid columns={{ xs: '1fr', md: 'auto 1fr' }} gap>
|
<Grid columns={{ xs: '1fr', md: 'auto 1fr' }} gap>
|
||||||
<Row alignItems="center" justifyContent="flex-end">
|
<Row alignItems="center" justifyContent="flex-start">
|
||||||
{allowFilter ? <WebsiteFilterButton websiteId={websiteId} /> : <div />}
|
{allowFilter ? <WebsiteFilterButton websiteId={websiteId} /> : <div />}
|
||||||
</Row>
|
</Row>
|
||||||
<Row alignItems="center" justifyContent="flex-end">
|
<Row alignItems="center" justifyContent={{ xs: 'flex-start', md: 'flex-end' }}>
|
||||||
{allowDateFilter && (
|
{allowDateFilter && (
|
||||||
<WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />
|
<WebsiteDateFilter websiteId={websiteId} allowCompare={allowCompare} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,23 @@ export function WebsiteExpandedView({
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column height="100%" overflow="hidden" gap>
|
||||||
<Row display={{ xs: 'flex', md: 'none' }}>
|
<Row id="expanded-mobile-menu-button" display={{ xs: 'flex', md: 'none' }}>
|
||||||
<MobileMenuButton>
|
<MobileMenuButton>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return <WebsiteExpandedMenu excludedIds={excludedIds} onItemClick={close} />;
|
return (
|
||||||
|
<Column padding="3">
|
||||||
|
<WebsiteExpandedMenu excludedIds={excludedIds} onItemClick={close} />
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
</MobileMenuButton>
|
</MobileMenuButton>
|
||||||
</Row>
|
</Row>
|
||||||
<Grid columns={{ xs: '1fr', md: 'auto 1fr' }} gap="6" height="100%" overflow="hidden">
|
<Grid columns={{ xs: '1fr', md: 'auto 1fr' }} gap="6" overflow="hidden">
|
||||||
<Column
|
<Column
|
||||||
|
id="metrics-expanded-menu"
|
||||||
display={{ xs: 'none', md: 'flex' }}
|
display={{ xs: 'none', md: 'flex' }}
|
||||||
|
width="240px"
|
||||||
gap="6"
|
gap="6"
|
||||||
border="right"
|
border="right"
|
||||||
paddingRight="3"
|
paddingRight="3"
|
||||||
|
|
@ -37,7 +43,7 @@ export function WebsiteExpandedView({
|
||||||
>
|
>
|
||||||
<WebsiteExpandedMenu excludedIds={excludedIds} />
|
<WebsiteExpandedMenu excludedIds={excludedIds} />
|
||||||
</Column>
|
</Column>
|
||||||
<Column overflow="hidden">
|
<Column id="metrics-expanded-table" overflow="hidden">
|
||||||
<MetricsExpandedTable
|
<MetricsExpandedTable
|
||||||
title={formatMessage(labels[view])}
|
title={formatMessage(labels[view])}
|
||||||
type={view}
|
type={view}
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,11 @@ export function WebsiteHeader({ showActions }: { showActions?: boolean }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} marginBottom="3">
|
<PageHeader title={website.name} icon={<Favicon domain={website.domain} />} marginBottom="3">
|
||||||
<Row alignItems="center" gap="6">
|
<Row alignItems="center" gap="6" wrap="wrap">
|
||||||
<ActiveUsers websiteId={website.id} />
|
<ActiveUsers websiteId={website.id} />
|
||||||
|
|
||||||
{showActions && (
|
{showActions && (
|
||||||
<Row
|
<Row alignItems="center" gap>
|
||||||
display={{ xs: 'none', sm: 'none', md: 'none', lg: 'flex', xl: 'flex' }}
|
|
||||||
alignItems="center"
|
|
||||||
gap
|
|
||||||
>
|
|
||||||
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
<ShareButton websiteId={website.id} shareId={website.shareId} />
|
||||||
<LinkButton href={renderUrl(`/websites/${website.id}/settings`, false)}>
|
<LinkButton href={renderUrl(`/websites/${website.id}/settings`, false)}>
|
||||||
<Icon>
|
<Icon>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,15 @@
|
||||||
import { REALTIME_RANGE } from '@/lib/constants';
|
import { REALTIME_RANGE } from '@/lib/constants';
|
||||||
import { getQueryFilters, parseRequest } from '@/lib/request';
|
import { getQueryFilters, parseRequest } from '@/lib/request';
|
||||||
import { json, unauthorized } from '@/lib/response';
|
import { json, unauthorized } from '@/lib/response';
|
||||||
import { timezoneParam } from '@/lib/schema';
|
|
||||||
import { canViewWebsite } from '@/permissions';
|
import { canViewWebsite } from '@/permissions';
|
||||||
import { getRealtimeData } from '@/queries/sql';
|
import { getRealtimeData } from '@/queries/sql';
|
||||||
import { startOfMinute, subMinutes } from 'date-fns';
|
import { startOfMinute, subMinutes } from 'date-fns';
|
||||||
import z from 'zod';
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: Promise<{ websiteId: string }> },
|
{ params }: { params: Promise<{ websiteId: string }> },
|
||||||
) {
|
) {
|
||||||
const schema = z.object({
|
const { auth, query, error } = await parseRequest(request);
|
||||||
timezone: timezoneParam,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { auth, query, error } = await parseRequest(request, schema);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return error();
|
return error();
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ export async function POST(request: Request) {
|
||||||
region,
|
region,
|
||||||
city,
|
city,
|
||||||
distinctId: id,
|
distinctId: id,
|
||||||
|
createdAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,15 +44,16 @@ export function LoginForm() {
|
||||||
name="username"
|
name="username"
|
||||||
rules={{ required: formatMessage(labels.required) }}
|
rules={{ required: formatMessage(labels.required) }}
|
||||||
>
|
>
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="username" />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
label={formatMessage(labels.password)}
|
label={formatMessage(labels.password)}
|
||||||
data-test="input-password"
|
data-test="input-password"
|
||||||
name="password"
|
name="password"
|
||||||
rules={{ required: formatMessage(labels.required) }}
|
rules={{ required: formatMessage(labels.required) }}
|
||||||
>
|
>
|
||||||
<PasswordField />
|
<PasswordField autoComplete="current-password" />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<FormSubmitButton
|
<FormSubmitButton
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Heading, Icon, Row, RowProps, Text, Column } from '@umami/react-zen';
|
import { Heading, Icon, Row, Text, Column, Grid } from '@umami/react-zen';
|
||||||
|
|
||||||
export function PageHeader({
|
export function PageHeader({
|
||||||
title,
|
title,
|
||||||
|
|
@ -8,7 +8,6 @@ export function PageHeader({
|
||||||
icon,
|
icon,
|
||||||
showBorder = true,
|
showBorder = true,
|
||||||
children,
|
children,
|
||||||
...props
|
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
@ -18,16 +17,13 @@ export function PageHeader({
|
||||||
allowEdit?: boolean;
|
allowEdit?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
} & RowProps) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Row
|
<Grid
|
||||||
justifyContent="space-between"
|
columns={{ xs: '1fr', md: '1fr 1fr' }}
|
||||||
alignItems="center"
|
|
||||||
paddingY="6"
|
paddingY="6"
|
||||||
marginBottom="6"
|
marginBottom="6"
|
||||||
border={showBorder ? 'bottom' : undefined}
|
border={showBorder ? 'bottom' : undefined}
|
||||||
width="100%"
|
|
||||||
{...props}
|
|
||||||
>
|
>
|
||||||
<Column gap="2">
|
<Column gap="2">
|
||||||
{label}
|
{label}
|
||||||
|
|
@ -46,6 +42,6 @@ export function PageHeader({
|
||||||
)}
|
)}
|
||||||
</Column>
|
</Column>
|
||||||
<Row justifyContent="flex-end">{children}</Row>
|
<Row justifyContent="flex-end">{children}</Row>
|
||||||
</Row>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,13 @@
|
||||||
import { useTimezone } from '@/components/hooks/useTimezone';
|
|
||||||
import { REALTIME_INTERVAL } from '@/lib/constants';
|
import { REALTIME_INTERVAL } from '@/lib/constants';
|
||||||
import { useApi } from '../useApi';
|
import { useApi } from '../useApi';
|
||||||
|
import { RealtimeData } from '@/lib/types';
|
||||||
export interface RealtimeData {
|
|
||||||
countries: Record<string, number>;
|
|
||||||
events: any[];
|
|
||||||
pageviews: any[];
|
|
||||||
referrers: Record<string, number>;
|
|
||||||
timestamp: number;
|
|
||||||
series: {
|
|
||||||
views: any[];
|
|
||||||
visitors: any[];
|
|
||||||
};
|
|
||||||
totals: {
|
|
||||||
views: number;
|
|
||||||
visitors: number;
|
|
||||||
events: number;
|
|
||||||
countries: number;
|
|
||||||
};
|
|
||||||
urls: Record<string, number>;
|
|
||||||
visitors: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useRealtimeQuery(websiteId: string) {
|
export function useRealtimeQuery(websiteId: string) {
|
||||||
const { get, useQuery } = useApi();
|
const { get, useQuery } = useApi();
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const { data, isLoading, error } = useQuery<RealtimeData>({
|
const { data, isLoading, error } = useQuery<RealtimeData>({
|
||||||
queryKey: ['realtime', { websiteId, timezone }],
|
queryKey: ['realtime', { websiteId }],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return get(`/realtime/${websiteId}`, { timezone });
|
return get(`/realtime/${websiteId}`);
|
||||||
},
|
},
|
||||||
enabled: !!websiteId,
|
enabled: !!websiteId,
|
||||||
refetchInterval: REALTIME_INTERVAL,
|
refetchInterval: REALTIME_INTERVAL,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export function useDateParameters() {
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange();
|
} = useDateRange();
|
||||||
const { timezone, toUtc } = useTimezone();
|
const { timezone, toUtc, canonicalizeTimezone } = useTimezone();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startAt: +toUtc(startDate),
|
startAt: +toUtc(startDate),
|
||||||
|
|
@ -13,6 +13,6 @@ export function useDateParameters() {
|
||||||
startDate: toUtc(startDate).toISOString(),
|
startDate: toUtc(startDate).toISOString(),
|
||||||
endDate: toUtc(endDate).toISOString(),
|
endDate: toUtc(endDate).toISOString(),
|
||||||
unit,
|
unit,
|
||||||
timezone,
|
timezone: canonicalizeTimezone(timezone),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { setItem } from '@/lib/storage';
|
import { setItem } from '@/lib/storage';
|
||||||
import { TIMEZONE_CONFIG } from '@/lib/constants';
|
import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants';
|
||||||
import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
|
import { formatInTimeZone, zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
|
||||||
import { useApp, setTimezone } from '@/store/app';
|
import { useApp, setTimezone } from '@/store/app';
|
||||||
import { useLocale } from './useLocale';
|
import { useLocale } from './useLocale';
|
||||||
|
|
@ -34,5 +34,16 @@ export function useTimezone() {
|
||||||
return utcToZonedTime(date, timezone);
|
return utcToZonedTime(date, timezone);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { timezone, saveTimezone, formatTimezoneDate, toUtc, fromUtc };
|
const canonicalizeTimezone = (timezone: string): string => {
|
||||||
|
return TIMEZONE_LEGACY[timezone] ?? timezone;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
timezone,
|
||||||
|
saveTimezone,
|
||||||
|
formatTimezoneDate,
|
||||||
|
toUtc,
|
||||||
|
fromUtc,
|
||||||
|
canonicalizeTimezone,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export function DownloadButton({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipTrigger delay={0}>
|
<TooltipTrigger delay={0}>
|
||||||
<Button variant="quiet" onClick={handleClick} isDisabled={!data}>
|
<Button variant="quiet" onClick={handleClick} isDisabled={!data || data.length === 0}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Download />
|
<Download />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ export function WebsiteDateFilter({
|
||||||
query: { compare = 'prev', offset = 0 },
|
query: { compare = 'prev', offset = 0 },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const disableForward = isAllTime || isAfter(dateRange.endDate, new Date());
|
const disableForward = isAllTime || isAfter(dateRange.endDate, new Date());
|
||||||
|
const showCompare = allowCompare && !isAllTime;
|
||||||
|
|
||||||
const websiteDateRange = useDateRangeQuery(websiteId);
|
const websiteDateRange = useDateRangeQuery(websiteId);
|
||||||
|
|
||||||
|
|
@ -62,7 +63,7 @@ export function WebsiteDateFilter({
|
||||||
}, [dateRange]);
|
}, [dateRange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gap>
|
<Row wrap="wrap" gap>
|
||||||
{showButtons && !isAllTime && !isCustomRange && (
|
{showButtons && !isAllTime && !isCustomRange && (
|
||||||
<Row gap="1">
|
<Row gap="1">
|
||||||
<Button onPress={() => handleIncrement(-1)} variant="outline">
|
<Button onPress={() => handleIncrement(-1)} variant="outline">
|
||||||
|
|
@ -85,7 +86,7 @@ export function WebsiteDateFilter({
|
||||||
renderDate={+offset !== 0}
|
renderDate={+offset !== 0}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
{allowCompare && !isAllTime && (
|
{showCompare && (
|
||||||
<Row alignItems="center" gap>
|
<Row alignItems="center" gap>
|
||||||
<Text weight="bold">VS</Text>
|
<Text weight="bold">VS</Text>
|
||||||
<Row width="200px">
|
<Row width="200px">
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export interface MetricsBarProps extends GridProps {
|
||||||
|
|
||||||
export function MetricsBar({ children, ...props }: MetricsBarProps) {
|
export function MetricsBar({ children, ...props }: MetricsBarProps) {
|
||||||
return (
|
return (
|
||||||
<Grid columns="repeat(auto-fit, minmax(140px, 1fr))" gap {...props}>
|
<Grid columns="repeat(auto-fit, minmax(160px, 1fr))" gap {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ function getDateStringSQL(data: any, unit: string = 'utc', timezone?: string) {
|
||||||
|
|
||||||
function getDateSQL(field: string, unit: string, timezone?: string) {
|
function getDateSQL(field: string, unit: string, timezone?: string) {
|
||||||
if (timezone) {
|
if (timezone) {
|
||||||
return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'), '${timezone}')`;
|
return `toDateTime(date_trunc('${unit}', ${field}, '${timezone}'))`;
|
||||||
}
|
}
|
||||||
return `toDateTime(date_trunc('${unit}', ${field}))`;
|
return `toDateTime(date_trunc('${unit}', ${field}))`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -658,3 +658,24 @@ export const CURRENCIES = [
|
||||||
{ id: 'OMR', name: 'Omani Rial' },
|
{ id: 'OMR', name: 'Omani Rial' },
|
||||||
{ id: 'GHS', name: 'Ghanaian Cedi' },
|
{ id: 'GHS', name: 'Ghanaian Cedi' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const TIMEZONE_LEGACY: Record<string, string> = {
|
||||||
|
'Asia/Batavia': 'Asia/Jakarta',
|
||||||
|
'Asia/Calcutta': 'Asia/Kolkata',
|
||||||
|
'Asia/Chongqing': 'Asia/Shanghai',
|
||||||
|
'Asia/Harbin': 'Asia/Shanghai',
|
||||||
|
'Asia/Jayapura': 'Asia/Pontianak',
|
||||||
|
'Asia/Katmandu': 'Asia/Kathmandu',
|
||||||
|
'Asia/Macao': 'Asia/Macau',
|
||||||
|
'Asia/Rangoon': 'Asia/Yangon',
|
||||||
|
'Asia/Saigon': 'Asia/Ho_Chi_Minh',
|
||||||
|
'Europe/Kiev': 'Europe/Kyiv',
|
||||||
|
'Europe/Zaporozhye': 'Europe/Kyiv',
|
||||||
|
'Etc/UTC': 'UTC',
|
||||||
|
'US/Arizona': 'America/Phoenix',
|
||||||
|
'US/Central': 'America/Chicago',
|
||||||
|
'US/Eastern': 'America/New_York',
|
||||||
|
'US/Mountain': 'America/Denver',
|
||||||
|
'US/Pacific': 'America/Los_Angeles',
|
||||||
|
'US/Samoa': 'Pacific/Pago_Pago',
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export const IP_ADDRESS_HEADERS = [
|
||||||
'fastly-client-ip', // Fastly
|
'fastly-client-ip', // Fastly
|
||||||
'x-nf-client-connection-ip', // Netlify
|
'x-nf-client-connection-ip', // Netlify
|
||||||
'do-connecting-ip', // Digital Ocean
|
'do-connecting-ip', // Digital Ocean
|
||||||
'x-appengine-user-ip', // Google App Ending
|
'x-appengine-user-ip', // Google App Engine
|
||||||
'x-client-ip',
|
'x-client-ip',
|
||||||
'x-cluster-client-ip',
|
'x-cluster-client-ip',
|
||||||
'x-forwarded',
|
'x-forwarded',
|
||||||
|
|
|
||||||
|
|
@ -116,3 +116,23 @@ export interface PageResult<T> {
|
||||||
sortDescending?: boolean;
|
sortDescending?: boolean;
|
||||||
search?: string;
|
search?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RealtimeData {
|
||||||
|
countries: Record<string, number>;
|
||||||
|
events: any[];
|
||||||
|
pageviews: any[];
|
||||||
|
referrers: Record<string, number>;
|
||||||
|
timestamp: number;
|
||||||
|
series: {
|
||||||
|
views: any[];
|
||||||
|
visitors: any[];
|
||||||
|
};
|
||||||
|
totals: {
|
||||||
|
views: number;
|
||||||
|
visitors: number;
|
||||||
|
events: number;
|
||||||
|
countries: number;
|
||||||
|
};
|
||||||
|
urls: Record<string, number>;
|
||||||
|
visitors: any[];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
||||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,44 @@
|
||||||
import { Prisma } from '@/generated/prisma/client';
|
import { Prisma } from '@/generated/prisma/client';
|
||||||
import prisma from '@/lib/prisma';
|
import prisma from '@/lib/prisma';
|
||||||
|
|
||||||
export async function createSession(data: Prisma.SessionCreateInput) {
|
const FUNCTION_NAME = 'createSession';
|
||||||
const {
|
|
||||||
id,
|
|
||||||
websiteId,
|
|
||||||
browser,
|
|
||||||
os,
|
|
||||||
device,
|
|
||||||
screen,
|
|
||||||
language,
|
|
||||||
country,
|
|
||||||
region,
|
|
||||||
city,
|
|
||||||
distinctId,
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
try {
|
export async function createSession(data: Prisma.SessionCreateInput) {
|
||||||
return await prisma.client.session.create({
|
const { rawQuery } = prisma;
|
||||||
data: {
|
|
||||||
id,
|
await rawQuery(
|
||||||
websiteId,
|
`
|
||||||
browser,
|
insert into session (
|
||||||
os,
|
session_id,
|
||||||
device,
|
website_id,
|
||||||
screen,
|
browser,
|
||||||
language,
|
os,
|
||||||
country,
|
device,
|
||||||
region,
|
screen,
|
||||||
city,
|
language,
|
||||||
distinctId,
|
country,
|
||||||
},
|
region,
|
||||||
});
|
city,
|
||||||
} catch (e: any) {
|
distinct_id,
|
||||||
if (e.message.toLowerCase().includes('unique constraint')) {
|
created_at
|
||||||
return null;
|
)
|
||||||
}
|
values (
|
||||||
throw e;
|
{{id}},
|
||||||
}
|
{{websiteId}},
|
||||||
|
{{browser}},
|
||||||
|
{{os}},
|
||||||
|
{{device}},
|
||||||
|
{{screen}},
|
||||||
|
{{language}},
|
||||||
|
{{country}},
|
||||||
|
{{region}},
|
||||||
|
{{city}},
|
||||||
|
{{distinctId}},
|
||||||
|
{{createdAt}}
|
||||||
|
)
|
||||||
|
on conflict (session_id) do nothing
|
||||||
|
`,
|
||||||
|
data,
|
||||||
|
FUNCTION_NAME,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ async function clickhouseQuery(
|
||||||
websiteId: string,
|
websiteId: string,
|
||||||
filters: QueryFilters,
|
filters: QueryFilters,
|
||||||
): Promise<{ x: string; y: number }[]> {
|
): Promise<{ x: string; y: number }[]> {
|
||||||
const { timezone = 'utc', unit = 'day' } = filters;
|
const { timezone = 'UTC', unit = 'day' } = filters;
|
||||||
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
const { parseFilters, rawQuery, getDateSQL } = clickhouse;
|
||||||
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
const { filterQuery, cohortQuery, queryParams } = parseFilters({
|
||||||
...filters,
|
...filters,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
if (excludeSearch) u.search = '';
|
if (excludeSearch) u.search = '';
|
||||||
if (excludeHash) u.hash = '';
|
if (excludeHash) u.hash = '';
|
||||||
return u.toString();
|
return u.toString();
|
||||||
} catch (e) {
|
} catch {
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue