feat: add realtime log and globe

This commit is contained in:
ccbikai 2025-05-11 17:48:46 +08:00
parent 622d6351c6
commit 63cd5e9503
10 changed files with 26571 additions and 32 deletions

2
.gitignore vendored
View file

@ -10,7 +10,7 @@ dist
node_modules
# Logs
logs
./logs
*.log
# Misc

View file

@ -0,0 +1,76 @@
<script setup>
import { scaleSequentialSqrt } from 'd3-scale'
import { interpolateYlOrRd } from 'd3-scale-chromatic'
import Globe from 'globe.gl'
import { debounce } from 'lodash-es'
import data from './data.json'
const countries = ref({})
async function getWorldMapJSON() {
const data = await $fetch('/countries.geojson')
countries.value = data
}
const liveSessionLocations = computed(() => {
const map = new Map()
data.forEach((item) => {
if (!item.country)
return
if (!map.has(item.country))
map.set(item.country, { lat: 37.75100, lon: -122.4194, count: 1 })
map.get(item.country).count += 1
})
return Array.from(map.values())
})
const highest = computed(() => {
return liveSessionLocations.value.reduce((acc, curr) => Math.max(acc, curr.count), 0) || 1
})
const normalized = 5 / 5
const weightColor = scaleSequentialSqrt(interpolateYlOrRd).domain([0, highest.value * normalized * 15])
const globeEl = ref()
const hexAltitude = ref(0.001)
onMounted(async () => {
await getWorldMapJSON()
const globe = new Globe(globeEl.value)
.backgroundColor('rgba(0,0,0,0)')
.hexPolygonsData(countries.value.features)
.hexPolygonResolution(3)
.hexPolygonMargin(0.2)
.hexBinResolution(3)
.hexBinPointsData(liveSessionLocations.value)
.hexPolygonAltitude(() => hexAltitude.value)
.hexBinMerge(true)
.hexBinPointWeight('count')
.hexPolygonColor(() => `rgba(120, 140, 110, ${Math.random() / 2 + 0.5})`)
.hexTopColor(d => weightColor(d.sumWeight))
.hexSideColor(d => weightColor(d.sumWeight))
.onGlobeReady(() => {
globe.pointOfView({ lat: 20, lng: -36, altitude: 2 })
globe.controls().autoRotate = true
globe.controls().autoRotateSpeed = 0.2
})
// hexAltitude
globe.controls().addEventListener('end', debounce(() => {
const distance = Math.round(globe.controls().getDistance())
let nextAlt = 0.005
if (distance <= 300)
nextAlt = 0.001
else if (distance >= 600)
nextAlt = 0.02
if (nextAlt !== hexAltitude.value)
hexAltitude.value = nextAlt
}, 200))
})
</script>
<template>
<div ref="globeEl" style="width: 100%; height: 600px;" />
</template>

View file

@ -1,10 +1,11 @@
<template>
<main class="space-y-6">
<div class="flex flex-col gap-6 sm:gap-2 sm:flex-row sm:justify-between">
<DashboardNav />
</div>
<div>
Realtime
<DashboardNav class="flex-1">
Mode & TimeRange
</DashboardNav>
<!-- add filter -->
</div>
<DashboardRealtimeGlobe />
</main>
</template>

View file

@ -0,0 +1,111 @@
<script setup>
import {
FlexRender,
getCoreRowModel,
useVueTable,
} from '@tanstack/vue-table'
// const time = inject('time')
// const filters = inject('filters')
const logs = ref([])
async function getEvents() {
const data = await useAPI('/api/logs/events', {
query: {
startAt: 1746945961,
},
})
console.log(data)
logs.value = data
}
// const stopWatchQueryChange = watch([time, filters], getLinkViews, {
// deep: true,
// })
onMounted(async () => {
getEvents()
})
// onBeforeUnmount(() => {
// stopWatchQueryChange()
// })
const columns = [
{
accessorKey: 'slug',
header: () => h('div', { class: 'text-left' }, 'Slug'),
cell: ({ row }) => {
return h('div', { class: 'text-left font-medium' }, row.original.slug)
},
},
{
accessorKey: 'url',
header: () => h('div', { class: 'text-left' }, 'URL'),
cell: ({ row }) => {
return h('div', { class: 'text-left' }, row.original.url)
},
},
{
accessorKey: 'ip',
header: () => h('div', { class: 'text-left' }, 'IP'),
cell: ({ row }) => {
return h('div', { class: 'text-left' }, row.original.ip)
},
},
{
accessorKey: 'referer',
header: () => h('div', { class: 'text-left' }, 'Referer'),
cell: ({ row }) => {
return h('div', { class: 'text-left' }, row.original.referer)
},
},
{
accessorKey: 'country',
header: () => h('div', { class: 'text-left' }, 'Country'),
cell: ({ row }) => {
return h('div', { class: 'text-left' }, row.original.country)
},
},
]
const table = useVueTable({
get data() { return logs.value },
get columns() { return columns },
getCoreRowModel: getCoreRowModel(),
})
</script>
<template>
<Table>
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id">
<FlexRender
v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
:props="header.getContext()"
/>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="table.getRowModel().rows?.length">
<TableRow
v-for="row in table.getRowModel().rows" :key="row.id"
:data-state="row.getIsSelected() ? 'selected' : undefined"
>
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<template v-else>
<TableRow>
<TableCell :colspan="columns.length" class="h-24 text-center">
No results.
</TableCell>
</TableRow>
</template>
</TableBody>
</Table>
</template>

View file

@ -8,7 +8,6 @@
"baseColor": "zinc",
"cssVariables": true
},
"framework": "nuxt",
"aliases": {
"components": "@/components",
"utils": "@/utils"

View file

@ -21,13 +21,19 @@
"lint-staged": "lint-staged"
},
"dependencies": {
"@intlify/message-compiler": "^11.1.3",
"@number-flow/vue": "^0.3.3",
"@tanstack/vue-table": "^8.21.3",
"@unovis/ts": "^1.5.0",
"@unovis/vue": "^1.5.0",
"@vee-validate/zod": "^4.15.0",
"@vueuse/core": "^12.2.0",
"d3-scale": "^4.0.2",
"d3-scale-chromatic": "^3.1.0",
"fuse.js": "^7.0.0",
"globe.gl": "^2.41.4",
"intl-parse-accept-language": "^1.0.0",
"lodash-es": "^4.17.21",
"lucide-vue-next": "^0.469.0",
"mysql-bricks": "^1.1.2",
"nanoid": "^5.0.9",

View file

@ -8,9 +8,15 @@ importers:
.:
dependencies:
'@intlify/message-compiler':
specifier: ^11.1.3
version: 11.1.3
'@number-flow/vue':
specifier: ^0.3.3
version: 0.3.3(vue@3.5.13(typescript@5.7.2))
'@tanstack/vue-table':
specifier: ^8.21.3
version: 8.21.3(vue@3.5.13(typescript@5.7.2))
'@unovis/ts':
specifier: ^1.5.0
version: 1.5.0
@ -23,12 +29,24 @@ importers:
'@vueuse/core':
specifier: ^12.2.0
version: 12.2.0(typescript@5.7.2)
d3-scale:
specifier: ^4.0.2
version: 4.0.2
d3-scale-chromatic:
specifier: ^3.1.0
version: 3.1.0
fuse.js:
specifier: ^7.0.0
version: 7.0.0
globe.gl:
specifier: ^2.41.4
version: 2.41.4
intl-parse-accept-language:
specifier: ^1.0.0
version: 1.0.0
lodash-es:
specifier: ^4.17.21
version: 4.17.21
lucide-vue-next:
specifier: ^0.469.0
version: 0.469.0(vue@3.5.13(typescript@5.7.2))
@ -1418,20 +1436,24 @@ packages:
resolution: {integrity: sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==}
engines: {node: '>= 16'}
'@intlify/message-compiler@11.0.0-rc.1':
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
'@intlify/message-compiler@11.1.3':
resolution: {integrity: sha512-7rbqqpo2f5+tIcwZTAG/Ooy9C8NDVwfDkvSeDPWUPQW+Dyzfw2o9H103N5lKBxO7wxX9dgCDjQ8Umz73uYw3hw==}
engines: {node: '>= 16'}
'@intlify/message-compiler@12.0.0-alpha.2':
resolution: {integrity: sha512-PD9C+oQbb7BF52hec0+vLnScaFkvnfX+R7zSbODYuRo/E2niAtGmHd0wPvEMsDhf9Z9b8f/qyDsVeZnD/ya9Ug==}
engines: {node: '>= 16'}
'@intlify/shared@10.0.5':
resolution: {integrity: sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==}
engines: {node: '>= 16'}
'@intlify/shared@11.0.0-rc.1':
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
'@intlify/shared@11.1.3':
resolution: {integrity: sha512-pTFBgqa/99JRA2H1qfyqv97MKWJrYngXBA/I0elZcYxvJgcCw3mApAoPW3mJ7vx3j+Ti0FyKUFZ4hWxdjKaxvA==}
engines: {node: '>= 16'}
'@intlify/shared@11.1.2':
resolution: {integrity: sha512-dF2iMMy8P9uKVHV/20LA1ulFLL+MKSbfMiixSmn6fpwqzvix38OIc7ebgnFbBqElvghZCW9ACtzKTGKsTGTWGA==}
'@intlify/shared@12.0.0-alpha.2':
resolution: {integrity: sha512-P2DULVX9nz3y8zKNqLw9Es1aAgQ1JGC+kgpx5q7yLmrnAKkPR5MybQWoEhxanefNJgUY5ehsgo+GKif59SrncA==}
engines: {node: '>= 16'}
'@intlify/unplugin-vue-i18n@6.0.3':
@ -2092,12 +2114,22 @@ packages:
'@swc/helpers@0.5.11':
resolution: {integrity: sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==}
'@tanstack/table-core@8.21.3':
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
engines: {node: '>=12'}
'@tanstack/virtual-core@3.13.2':
resolution: {integrity: sha512-Qzz4EgzMbO5gKrmqUondCjiHcuu4B1ftHb0pjCut661lXZdGoHeze9f/M8iwsK1t5LGR6aNuNGU7mxkowaW6RQ==}
'@tanstack/virtual-core@3.8.1':
resolution: {integrity: sha512-uNtAwenT276M9QYCjTBoHZ8X3MUeCRoGK59zPi92hMIxdfS9AyHjkDWJ94WroDxnv48UE+hIeo21BU84jKc8aQ==}
'@tanstack/vue-table@8.21.3':
resolution: {integrity: sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==}
engines: {node: '>=12'}
peerDependencies:
vue: '>=3.2'
'@tanstack/vue-virtual@3.13.2':
resolution: {integrity: sha512-z4swzjdhzCh95n9dw9lTvw+t3iwSkYRlVkYkra3C9mul/m5fTzHR7KmtkwH4qXMTXGJUbngtC/bz2cHQIHkO8g==}
peerDependencies:
@ -2112,6 +2144,18 @@ packages:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
'@turf/boolean-point-in-polygon@7.2.0':
resolution: {integrity: sha512-lvEOjxeXIp+wPXgl9kJA97dqzMfNexjqHou+XHVcfxQgolctoJiRYmcVCWGpiZ9CBf/CJha1KmD1qQoRIsjLaA==}
'@turf/helpers@7.2.0':
resolution: {integrity: sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw==}
'@turf/invariant@7.2.0':
resolution: {integrity: sha512-kV4u8e7Gkpq+kPbAKNC21CmyrXzlbBgFjO1PhrHPgEdNqXqDawoZ3i6ivE3ULJj2rSesCjduUaC/wyvH/sNr2Q==}
'@tweenjs/tween.js@25.0.0':
resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==}
'@types/d3-array@3.2.1':
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
@ -2648,6 +2692,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
accessor-fn@1.5.3:
resolution: {integrity: sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==}
engines: {node: '>=12'}
acorn-import-attributes@1.9.5:
resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==}
peerDependencies:
@ -3285,6 +3333,10 @@ packages:
engines: {node: '>=12'}
hasBin: true
d3-geo-voronoi@2.1.0:
resolution: {integrity: sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==}
engines: {node: '>=12'}
d3-geo@3.1.1:
resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
engines: {node: '>=12'}
@ -3300,6 +3352,9 @@ packages:
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
engines: {node: '>=12'}
d3-octree@1.1.0:
resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==}
d3-path@1.0.9:
resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
@ -3359,6 +3414,10 @@ packages:
peerDependencies:
d3-selection: 2 - 3
d3-tricontour@1.0.2:
resolution: {integrity: sha512-HIRxHzHagPtUPNabjOlfcyismJYIsc+Xlq4mlsts4e8eAcwyq9Tgk/sYdyhlBpQ0MHwVquc/8j+e29YjXnmxeA==}
engines: {node: '>=12'}
d3-zoom@3.0.0:
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
engines: {node: '>=12'}
@ -3367,6 +3426,10 @@ packages:
resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
engines: {node: '>=12'}
data-bind-mapper@1.0.3:
resolution: {integrity: sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw==}
engines: {node: '>=12'}
data-uri-to-buffer@2.0.2:
resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
@ -3560,6 +3623,9 @@ packages:
earcut@2.2.4:
resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==}
earcut@3.0.1:
resolution: {integrity: sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@ -4013,6 +4079,10 @@ packages:
flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
float-tooltip@1.7.5:
resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==}
engines: {node: '>=12'}
foreground-child@3.1.1:
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
engines: {node: '>=14'}
@ -4024,6 +4094,9 @@ packages:
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
frame-ticker@1.0.3:
resolution: {integrity: sha512-E0X2u2JIvbEMrqEg5+4BpTqaD22OwojJI63K7MdKHdncjtAhGRbCR8nJCr2vwEt9NWBPCPcu70X9smPviEBy8Q==}
fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
@ -4181,6 +4254,10 @@ packages:
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
engines: {node: '>=18'}
globe.gl@2.41.4:
resolution: {integrity: sha512-ExddewF46ncxFSG+ci62Vz7QO4NmLmTtJ386zLoZSgLdbKl9bWz6DN7DxXNkvo6M6mSV94E18uOvLbvr7YmsGw==}
engines: {node: '>=12'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@ -4195,6 +4272,10 @@ packages:
resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
h3-js@4.2.1:
resolution: {integrity: sha512-HYiUrq5qTRFqMuQu3jEHqxXLk1zsSJiby9Lja/k42wHjabZG7tN9rOuzT/PEFf+Wa7rsnHLMHRWIu0mgcJ0ewQ==}
engines: {node: '>=4', npm: '>=3', yarn: '>=1.3.0'}
h3@1.12.0:
resolution: {integrity: sha512-Zi/CcNeWBXDrFNlV0hUBJQR9F7a96RjMeAZweW/ZWkR9fuXrMcvKnSA63f/zZ9l0GgQOZDVHGvXivNN9PWOwhA==}
@ -4321,6 +4402,10 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
index-array-by@1.4.2:
resolution: {integrity: sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==}
engines: {node: '>=12'}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@ -4542,6 +4627,10 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
kapsule@1.16.3:
resolution: {integrity: sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==}
engines: {node: '>=12'}
kdbush@3.0.0:
resolution: {integrity: sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==}
@ -5351,6 +5440,13 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
point-in-polygon-hao@1.2.4:
resolution: {integrity: sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==}
polished@4.3.1:
resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==}
engines: {node: '>=10'}
portfinder@1.0.32:
resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
engines: {node: '>= 0.12.0'}
@ -5575,6 +5671,9 @@ packages:
potpack@1.0.2:
resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==}
preact@10.26.6:
resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@ -5882,6 +5981,9 @@ packages:
simple-git@3.27.0:
resolution: {integrity: sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==}
simplesignal@2.1.7:
resolution: {integrity: sha512-PEo2qWpUke7IMhlqiBxrulIFvhJRLkl1ih52Rwa+bPjzhJepcd4GIjn2RiQmFSx3dQvsEAgF0/lXMwMN7vODaA==}
sirv@3.0.0:
resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==}
engines: {node: '>=18'}
@ -5915,10 +6017,6 @@ packages:
smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@ -6148,9 +6246,42 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
three-conic-polygon-geometry@2.1.2:
resolution: {integrity: sha512-NaP3RWLJIyPGI+zyaZwd0Yj6rkoxm4FJHqAX1Enb4L64oNYLCn4bz1ESgOEYavgcUwCNYINu1AgEoUBJr1wZcA==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.72.0'
three-geojson-geometry@2.1.1:
resolution: {integrity: sha512-dC7bF3ri1goDcihYhzACHOBQqu7YNNazYLa2bSydVIiJUb3jDFojKSy+gNj2pMkqZNSVjssSmdY9zlmnhEpr1w==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.72.0'
three-globe@2.42.4:
resolution: {integrity: sha512-YWWFtl2MNT3CDDjgE4blmWaIgSjVOqJdtx9BaLIwWwVo4oTto6dU6w/tHkLKx/hCpGCQfhWJFszvereUaeknEg==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.154'
three-render-objects@1.40.0:
resolution: {integrity: sha512-Ub2IebRGrV+ctxkOe7lkLzIraJDTtz5s31Z2rvaQb7sHAXfofv02CwtEJmIPIkEy6jpGreuqJbCGEnQpvKKDFw==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.168'
three-slippy-map-globe@1.0.3:
resolution: {integrity: sha512-Y9WCA/tTL8yH8FHVSXVQss/P0V36utTNhuixzFPj0Bs0SXxO+Vui133oAQmMpx4BLXYZpWZwcqHM2i3MfFlYWw==}
engines: {node: '>=12'}
peerDependencies:
three: '>=0.154'
three@0.135.0:
resolution: {integrity: sha512-kuEpuuxRzLv0MDsXai9huCxOSQPZ4vje6y0gn80SRmQvgz6/+rI0NAvCRAw56zYaWKMGMfqKWsxF9Qa2Z9xymQ==}
three@0.176.0:
resolution: {integrity: sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==}
throttle-debounce@5.0.0:
resolution: {integrity: sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==}
engines: {node: '>=12.22'}
@ -6158,6 +6289,9 @@ packages:
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
tinyexec@0.3.1:
resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==}
@ -6218,6 +6352,9 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsscmp@1.0.6:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
engines: {node: '>=0.6.x'}
@ -7887,8 +8024,8 @@ snapshots:
'@intlify/bundle-utils@10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))':
dependencies:
'@intlify/message-compiler': 11.0.0-rc.1
'@intlify/shared': 11.0.0-rc.1
'@intlify/message-compiler': 12.0.0-alpha.2
'@intlify/shared': 12.0.0-alpha.2
acorn: 8.14.0
escodegen: 2.1.0
estree-walker: 2.0.2
@ -7919,23 +8056,28 @@ snapshots:
'@intlify/shared': 10.0.5
source-map-js: 1.2.1
'@intlify/message-compiler@11.0.0-rc.1':
'@intlify/message-compiler@11.1.3':
dependencies:
'@intlify/shared': 11.0.0-rc.1
'@intlify/shared': 11.1.3
source-map-js: 1.2.1
'@intlify/message-compiler@12.0.0-alpha.2':
dependencies:
'@intlify/shared': 12.0.0-alpha.2
source-map-js: 1.2.1
'@intlify/shared@10.0.5': {}
'@intlify/shared@11.0.0-rc.1': {}
'@intlify/shared@11.1.3': {}
'@intlify/shared@11.1.2': {}
'@intlify/shared@12.0.0-alpha.2': {}
'@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.17.0(jiti@2.4.2))(rollup@4.18.0)(typescript@5.7.2)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2))
'@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))
'@intlify/shared': 11.1.2
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
'@intlify/shared': 11.1.3
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.3)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))
'@rollup/pluginutils': 5.1.4(rollup@4.18.0)
'@typescript-eslint/scope-manager': 8.18.2
'@typescript-eslint/typescript-estree': 8.18.2(typescript@5.7.2)
@ -7959,11 +8101,11 @@ snapshots:
'@intlify/utils@0.13.0': {}
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.2)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.3)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))':
dependencies:
'@babel/parser': 7.26.3
optionalDependencies:
'@intlify/shared': 11.1.2
'@intlify/shared': 11.1.3
'@vue/compiler-dom': 3.5.13
vue: 3.5.13(typescript@5.7.2)
vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.2))
@ -8884,10 +9026,17 @@ snapshots:
dependencies:
tslib: 2.6.3
'@tanstack/table-core@8.21.3': {}
'@tanstack/virtual-core@3.13.2': {}
'@tanstack/virtual-core@3.8.1': {}
'@tanstack/vue-table@8.21.3(vue@3.5.13(typescript@5.7.2))':
dependencies:
'@tanstack/table-core': 8.21.3
vue: 3.5.13(typescript@5.7.2)
'@tanstack/vue-virtual@3.13.2(vue@3.5.13(typescript@5.7.2))':
dependencies:
'@tanstack/virtual-core': 3.13.2
@ -8900,6 +9049,27 @@ snapshots:
'@trysound/sax@0.2.0': {}
'@turf/boolean-point-in-polygon@7.2.0':
dependencies:
'@turf/helpers': 7.2.0
'@turf/invariant': 7.2.0
'@types/geojson': 7946.0.14
point-in-polygon-hao: 1.2.4
tslib: 2.8.1
'@turf/helpers@7.2.0':
dependencies:
'@types/geojson': 7946.0.14
tslib: 2.8.1
'@turf/invariant@7.2.0':
dependencies:
'@turf/helpers': 7.2.0
'@types/geojson': 7946.0.14
tslib: 2.8.1
'@tweenjs/tween.js@25.0.0': {}
'@types/d3-array@3.2.1': {}
'@types/d3-axis@3.0.6':
@ -9468,7 +9638,7 @@ snapshots:
'@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.0
source-map-js: 1.2.1
'@vue/compiler-dom@3.5.13':
dependencies:
@ -9640,6 +9810,8 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
accessor-fn@1.5.3: {}
acorn-import-attributes@1.9.5(acorn@8.12.1):
dependencies:
acorn: 8.12.1
@ -10289,6 +10461,13 @@ snapshots:
d3-array: 3.2.4
d3-geo: 3.1.1
d3-geo-voronoi@2.1.0:
dependencies:
d3-array: 3.2.4
d3-delaunay: 6.0.4
d3-geo: 3.1.1
d3-tricontour: 1.0.2
d3-geo@3.1.1:
dependencies:
d3-array: 3.2.4
@ -10301,6 +10480,8 @@ snapshots:
dependencies:
d3-color: 3.1.0
d3-octree@1.1.0: {}
d3-path@1.0.9: {}
d3-path@3.1.0: {}
@ -10358,6 +10539,11 @@ snapshots:
d3-selection: 3.0.0
d3-timer: 3.0.1
d3-tricontour@1.0.2:
dependencies:
d3-delaunay: 6.0.4
d3-scale: 4.0.2
d3-zoom@3.0.0:
dependencies:
d3-dispatch: 3.0.1
@ -10399,6 +10585,10 @@ snapshots:
d3-transition: 3.0.1(d3-selection@3.0.0)
d3-zoom: 3.0.0
data-bind-mapper@1.0.3:
dependencies:
accessor-fn: 1.5.3
data-uri-to-buffer@2.0.2: {}
date-fns@4.1.0: {}
@ -10528,6 +10718,8 @@ snapshots:
earcut@2.2.4: {}
earcut@3.0.1: {}
eastasianwidth@0.2.0: {}
ee-first@1.1.1: {}
@ -11171,6 +11363,12 @@ snapshots:
flatted@3.3.2: {}
float-tooltip@1.7.5:
dependencies:
d3-selection: 3.0.0
kapsule: 1.16.3
preact: 10.26.6
foreground-child@3.1.1:
dependencies:
cross-spawn: 7.0.3
@ -11185,6 +11383,10 @@ snapshots:
fraction.js@4.3.7: {}
frame-ticker@1.0.3:
dependencies:
simplesignal: 2.1.7
fresh@0.5.2: {}
fs-extra@11.2.0:
@ -11369,6 +11571,15 @@ snapshots:
slash: 5.1.0
unicorn-magic: 0.1.0
globe.gl@2.41.4:
dependencies:
'@tweenjs/tween.js': 25.0.0
accessor-fn: 1.5.3
kapsule: 1.16.3
three: 0.176.0
three-globe: 2.42.4(three@0.176.0)
three-render-objects: 1.40.0(three@0.176.0)
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
@ -11379,6 +11590,8 @@ snapshots:
dependencies:
duplexer: 0.1.2
h3-js@4.2.1: {}
h3@1.12.0:
dependencies:
cookie-es: 1.1.0
@ -11515,6 +11728,8 @@ snapshots:
indent-string@4.0.0: {}
index-array-by@1.4.2: {}
inflight@1.0.6:
dependencies:
once: 1.4.0
@ -11695,6 +11910,10 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
kapsule@1.16.3:
dependencies:
lodash-es: 4.17.21
kdbush@3.0.0: {}
keygrip@1.1.0:
@ -12928,6 +13147,14 @@ snapshots:
pluralize@8.0.0: {}
point-in-polygon-hao@1.2.4:
dependencies:
robust-predicates: 3.0.2
polished@4.3.1:
dependencies:
'@babel/runtime': 7.24.5
portfinder@1.0.32:
dependencies:
async: 2.6.4
@ -13141,6 +13368,8 @@ snapshots:
potpack@1.0.2: {}
preact@10.26.6: {}
prelude-ls@1.2.1: {}
pretty-bytes@6.1.1: {}
@ -13517,6 +13746,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
simplesignal@2.1.7: {}
sirv@3.0.0:
dependencies:
'@polka/url': 1.0.0-next.25
@ -13545,8 +13776,6 @@ snapshots:
smob@1.5.0: {}
source-map-js@1.2.0: {}
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@ -13809,12 +14038,71 @@ snapshots:
dependencies:
any-promise: 1.3.0
three-conic-polygon-geometry@2.1.2(three@0.176.0):
dependencies:
'@turf/boolean-point-in-polygon': 7.2.0
d3-array: 3.2.4
d3-geo: 3.1.1
d3-geo-voronoi: 2.1.0
d3-scale: 4.0.2
delaunator: 5.0.1
earcut: 3.0.1
three: 0.176.0
three-geojson-geometry@2.1.1(three@0.176.0):
dependencies:
d3-geo: 3.1.1
d3-interpolate: 3.0.1
earcut: 3.0.1
three: 0.176.0
three-globe@2.42.4(three@0.176.0):
dependencies:
'@tweenjs/tween.js': 25.0.0
accessor-fn: 1.5.3
d3-array: 3.2.4
d3-color: 3.1.0
d3-geo: 3.1.1
d3-interpolate: 3.0.1
d3-scale: 4.0.2
d3-scale-chromatic: 3.1.0
data-bind-mapper: 1.0.3
frame-ticker: 1.0.3
h3-js: 4.2.1
index-array-by: 1.4.2
kapsule: 1.16.3
three: 0.176.0
three-conic-polygon-geometry: 2.1.2(three@0.176.0)
three-geojson-geometry: 2.1.1(three@0.176.0)
three-slippy-map-globe: 1.0.3(three@0.176.0)
tinycolor2: 1.6.0
three-render-objects@1.40.0(three@0.176.0):
dependencies:
'@tweenjs/tween.js': 25.0.0
accessor-fn: 1.5.3
float-tooltip: 1.7.5
kapsule: 1.16.3
polished: 4.3.1
three: 0.176.0
three-slippy-map-globe@1.0.3(three@0.176.0):
dependencies:
d3-geo: 3.1.1
d3-octree: 1.1.0
d3-scale: 4.0.2
three: 0.176.0
three@0.135.0: {}
three@0.176.0: {}
throttle-debounce@5.0.0: {}
tiny-invariant@1.3.3: {}
tinycolor2@1.6.0: {}
tinyexec@0.3.1: {}
tinyglobby@0.2.10:
@ -13866,6 +14154,8 @@ snapshots:
tslib@2.6.3: {}
tslib@2.8.1: {}
tsscmp@1.0.6: {}
type-check@0.4.0:

26017
public/countries.geojson Normal file

File diff suppressed because it is too large Load diff

39
server/api/logs/events.ts Normal file
View file

@ -0,0 +1,39 @@
import type { H3Event } from 'h3'
import { QuerySchema } from '@@/schemas/query'
const { select } = SqlBricks
function query2sql(query: Query, event: H3Event): string {
const filter = query2filter(query)
const { dataset } = useRuntimeConfig(event)
const sql = select(`*`).from(dataset).where(filter)
appendTimeFilter(sql, query)
return sql.toString()
}
interface WAEEvents {
[key: string]: string
}
function events2logs(events: WAEEvents[]) {
return events.map((event) => {
const blobs = Array.from({ length: Object.keys(blobsMap).length }).fill(0).reduce((_, _c, i) => {
_.push(event[`blob${i + 1}`])
return _
}, [])
return {
...blobs2logs(blobs),
id: event.index1,
timestamp: event.timestamp,
_sample_interval: event._sample_interval,
}
})
}
export default eventHandler(async (event) => {
const query = await getValidatedQuery(event, QuerySchema.parse)
const sql = query2sql(query, event)
const logs = await useWAE(event, sql) as { data: WAEEvents[] }
return events2logs(logs?.data || [])
})

View file

@ -43,12 +43,12 @@ export type LogsMap = { [key in LogsKey]: string | undefined }
export const logsMap: LogsMap = Object.entries(blobsMap).reduce((acc, [k, v]) => ({ ...acc, [v]: k }), {}) as LogsMap
function logs2blobs(logs: LogsMap) {
export function logs2blobs(logs: LogsMap) {
// @ts-expect-error todo
return Object.keys(blobsMap).sort((a, b) => toBlobNumber(a) - toBlobNumber(b)).map(key => logs[blobsMap[key]] || '')
}
function blobs2logs(blobs: string[]) {
export function blobs2logs(blobs: string[]) {
const logsList = Object.keys(blobsMap)
// @ts-expect-error todo
@ -56,7 +56,7 @@ function blobs2logs(blobs: string[]) {
// @ts-expect-error todo
logs[blobsMap[logsList[i]]] = blob
return logs
}, {})
}, {}) as LogsMap
}
export function useAccessLog(event: H3Event) {