Merge branch 'preview'
This commit is contained in:
commit
6a98a4b719
36 changed files with 27191 additions and 64 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -10,7 +10,7 @@ dist
|
|||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
./logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@
|
|||
--vis-tooltip-backdrop-filter: none !important;
|
||||
--vis-tooltip-padding: none !important;
|
||||
|
||||
--vis-primary-color: 198 93% 60%;
|
||||
--vis-secondary-color: 158 64% 52%;
|
||||
--vis-primary-color: 158 64% 52%;
|
||||
--vis-secondary-color: 198 93% 60%;
|
||||
/* --vis-secondary-color: 160 81% 40%; */
|
||||
/* --vis-secondary-color: var(--primary); */
|
||||
--vis-text-color: var(--muted-foreground);
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ const route = useRoute()
|
|||
<TabsTrigger value="/dashboard/analysis">
|
||||
{{ $t('nav.analysis') }}
|
||||
</TabsTrigger>
|
||||
<!-- <TabsTrigger value="/dashboard/realtime">
|
||||
<TabsTrigger value="/dashboard/realtime">
|
||||
{{ $t('nav.realtime') }}
|
||||
</TabsTrigger> -->
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<slot name="left" />
|
||||
|
|
|
|||
95
app/components/dashboard/TimePicker.vue
Normal file
95
app/components/dashboard/TimePicker.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<script setup>
|
||||
import { now } from '@internationalized/date'
|
||||
import { useUrlSearchParams } from '@vueuse/core'
|
||||
|
||||
const emit = defineEmits(['update:timeRange'])
|
||||
|
||||
const timeRange = ref('last-1h')
|
||||
|
||||
watch(timeRange, (newValue) => {
|
||||
switch (newValue) {
|
||||
case 'today':
|
||||
emit('update:timeRange', [date2unix(now(), 'start'), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-5m':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ minutes: 5 })), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-10m':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ minutes: 10 })), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-30m':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ minutes: 30 })), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-1h':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ hours: 1 })), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-6h':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ hours: 6 })), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-12h':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ hours: 12 })), date2unix(now())], newValue)
|
||||
break
|
||||
case 'last-24h':
|
||||
emit('update:timeRange', [date2unix(now().subtract({ hours: 24 })), date2unix(now())], newValue)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
function restoreTimeRange() {
|
||||
try {
|
||||
const searchParams = useUrlSearchParams('history')
|
||||
if (searchParams.time) {
|
||||
timeRange.value = searchParams.time
|
||||
triggerRef(timeRange)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('restore searchParams error', error)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
restoreTimeRange,
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
restoreTimeRange()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Select v-model="timeRange">
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="last-5m">
|
||||
{{ $t('dashboard.time_picker.last_5m') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="last-10m">
|
||||
{{ $t('dashboard.time_picker.last_10m') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="last-30m">
|
||||
{{ $t('dashboard.time_picker.last_30m') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="last-1h">
|
||||
{{ $t('dashboard.time_picker.last_1h') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="last-6h">
|
||||
{{ $t('dashboard.time_picker.last_6h') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="last-12h">
|
||||
{{ $t('dashboard.time_picker.last_12h') }}
|
||||
</SelectItem>
|
||||
<SelectItem value="last-24h">
|
||||
{{ $t('dashboard.time_picker.last_24h') }}
|
||||
</SelectItem>
|
||||
<SelectSeparator />
|
||||
<SelectItem value="today">
|
||||
{{ $t('dashboard.time_picker.today') }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</template>
|
||||
|
|
@ -26,17 +26,13 @@ async function getLinkCounters() {
|
|||
counters.value = data?.[0]
|
||||
}
|
||||
|
||||
const stopWatchQueryChange = watch([time, filters], getLinkCounters, {
|
||||
watch([time, filters], getLinkCounters, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
getLinkCounters()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopWatchQueryChange()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,30 @@
|
|||
import { AreaChart } from '@/components/ui/chart-area'
|
||||
import { BarChart } from '@/components/ui/chart-bar'
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'full',
|
||||
},
|
||||
chartType: {
|
||||
type: String,
|
||||
default: 'area',
|
||||
},
|
||||
})
|
||||
|
||||
const views = ref([])
|
||||
const chart = computed(() => views.value.length > 1 ? AreaChart : BarChart)
|
||||
const chart = computed(() => (props.chartType === 'area' && views.value.length > 1) ? AreaChart : BarChart)
|
||||
|
||||
const id = inject('id')
|
||||
const time = inject('time')
|
||||
const filters = inject('filters')
|
||||
|
||||
const OneHour = 60 * 60 // 1 hour in seconds
|
||||
const OneDay = 24 * 60 * 60 // 1 day in seconds
|
||||
function getUnit(startAt, endAt) {
|
||||
if (startAt && endAt && endAt - startAt <= OneHour)
|
||||
return 'minute'
|
||||
|
||||
if (startAt && endAt && endAt - startAt <= OneDay)
|
||||
return 'hour'
|
||||
|
||||
|
|
@ -36,7 +51,7 @@ async function getLinkViews() {
|
|||
})
|
||||
}
|
||||
|
||||
const stopWatchQueryChange = watch([time, filters], getLinkViews, {
|
||||
watch([time, filters], getLinkViews, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
|
|
@ -44,10 +59,6 @@ onMounted(async () => {
|
|||
getLinkViews()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopWatchQueryChange()
|
||||
})
|
||||
|
||||
function formatTime(tick) {
|
||||
if (Number.isInteger(tick) && views.value[tick]) {
|
||||
if (getUnit(time.value.startAt, time.value.endAt) === 'hour')
|
||||
|
|
@ -61,16 +72,20 @@ function formatTime(tick) {
|
|||
|
||||
<template>
|
||||
<Card class="px-0 py-6 md:px-6">
|
||||
<CardTitle class="px-6 md:px-0">
|
||||
<CardTitle v-if="mode === 'full'" class="px-6 md:px-0">
|
||||
{{ $t('dashboard.views') }}
|
||||
</CardTitle>
|
||||
<component
|
||||
:is="chart"
|
||||
:data="views"
|
||||
v-if="views.length"
|
||||
class="w-full h-full"
|
||||
index="time"
|
||||
:categories="['visitors', 'visits']"
|
||||
:data="views"
|
||||
:categories="mode === 'full' ? ['visits', 'visitors'] : ['visits']"
|
||||
:x-formatter="formatTime"
|
||||
:y-formatter="formatNumber"
|
||||
:show-grid-line="mode === 'full'"
|
||||
:show-legend="mode === 'full'"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ async function getMapData() {
|
|||
}
|
||||
}
|
||||
|
||||
const stopWatchQueryChange = watch([time, filters], getMapData, {
|
||||
watch([time, filters], getMapData, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
|
|
@ -42,10 +42,6 @@ onMounted(() => {
|
|||
getMapData()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopWatchQueryChange()
|
||||
})
|
||||
|
||||
const valueFormatter = v => v
|
||||
const Tooltip = {
|
||||
props: ['title', 'data'],
|
||||
|
|
|
|||
|
|
@ -45,17 +45,13 @@ async function getLinkMetrics() {
|
|||
}
|
||||
}
|
||||
|
||||
const stopWatchQueryChange = watch([time, filters], getLinkMetrics, {
|
||||
watch([time, filters], getLinkMetrics, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getLinkMetrics()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopWatchQueryChange()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
52
app/components/dashboard/realtime/Chart.vue
Normal file
52
app/components/dashboard/realtime/Chart.vue
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<script setup>
|
||||
import NumberFlow from '@number-flow/vue'
|
||||
import { MousePointerClick } from 'lucide-vue-next'
|
||||
|
||||
provide('id', ref())
|
||||
|
||||
const time = inject('time')
|
||||
const filters = inject('filters')
|
||||
const stats = ref({ visits: 0 })
|
||||
|
||||
async function getRealtimeStats() {
|
||||
const { data } = await useAPI('/api/stats/counters', {
|
||||
query: {
|
||||
startAt: time.value.startAt,
|
||||
endAt: time.value.endAt,
|
||||
...filters.value,
|
||||
},
|
||||
})
|
||||
|
||||
stats.value = data?.[0] || {}
|
||||
}
|
||||
|
||||
watch([time, filters], getRealtimeStats, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
getRealtimeStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card class="md:w-80 h-72 flex flex-col p-4 md:m-2">
|
||||
<div class="h-24">
|
||||
<CardHeader v-if="stats.visits" class="flex flex-row justify-between items-center pb-2 space-y-0 px-0 pt-2">
|
||||
<CardTitle class="text-sm font-medium flex items-center gap-2">
|
||||
<span class="size-1.5 inline-flex animate-ping rounded-full bg-green-400 opacity-75" />
|
||||
{{ $t('dashboard.visits') }}
|
||||
</CardTitle>
|
||||
<MousePointerClick class="w-4 h-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent class="px-0 pb-4">
|
||||
<NumberFlow class="text-2xl font-bold" :class="{ 'blur-md opacity-60': !stats.visits }" :value="stats.visits" />
|
||||
</CardContent>
|
||||
</div>
|
||||
<DashboardAnalysisViews
|
||||
class="w-full h-40 border-none !p-0"
|
||||
mode="simple"
|
||||
chart-type="bar"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
183
app/components/dashboard/realtime/Globe.vue
Normal file
183
app/components/dashboard/realtime/Globe.vue
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<script setup>
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { scaleSequentialSqrt } from 'd3-scale'
|
||||
import { interpolateYlOrRd } from 'd3-scale-chromatic'
|
||||
import Globe from 'globe.gl'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { MeshPhongMaterial } from 'three'
|
||||
|
||||
const props = defineProps({
|
||||
minutes: {
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
})
|
||||
|
||||
const time = inject('time')
|
||||
const filters = inject('filters')
|
||||
|
||||
const countries = ref({})
|
||||
const locations = ref([])
|
||||
const colos = ref({})
|
||||
const currentLocation = ref({})
|
||||
|
||||
const el = useTemplateRef('globeEl')
|
||||
const { width } = useElementSize(el)
|
||||
const size = computed(() => ({
|
||||
width: width.value,
|
||||
height: width.value > 768 ? width.value * 0.6 : width.value,
|
||||
}))
|
||||
|
||||
const globeEl = ref()
|
||||
const hexAltitude = ref(0.001)
|
||||
const highest = computed(() => {
|
||||
return locations.value.reduce((acc, curr) => Math.max(acc, curr.count), 0) || 1
|
||||
})
|
||||
|
||||
let globe = null
|
||||
|
||||
async function getGlobeJSON() {
|
||||
const data = await $fetch('/countries.geojson')
|
||||
countries.value = data
|
||||
}
|
||||
|
||||
async function getColosJSON() {
|
||||
const data = await $fetch('/colos.json')
|
||||
colos.value = data
|
||||
}
|
||||
|
||||
async function getCurrentLocation() {
|
||||
const data = await useAPI('/api/location')
|
||||
currentLocation.value = data
|
||||
}
|
||||
|
||||
async function getLiveLocations() {
|
||||
const { data } = await useAPI('/api/logs/locations', {
|
||||
query: {
|
||||
startAt: time.value.startAt,
|
||||
endAt: time.value.endAt,
|
||||
...filters.value,
|
||||
},
|
||||
})
|
||||
locations.value = data?.map(e => ({
|
||||
lat: e.latitude,
|
||||
lng: e.longitude,
|
||||
count: Math.max(1, +e.count / highest.value),
|
||||
}))
|
||||
}
|
||||
|
||||
let cleanArcsDataTimer = null
|
||||
|
||||
function trafficEvent({ props }, { delay = 0 }) {
|
||||
const arc = {
|
||||
startLat: props.item.latitude,
|
||||
startLng: props.item.longitude,
|
||||
endLat: colos.value[props.item.COLO]?.lat,
|
||||
endLng: colos.value[props.item.COLO]?.lon,
|
||||
color: 'red',
|
||||
arcAltitude: 0.5,
|
||||
}
|
||||
console.info(`from ${props.item.city}(${props.item.latitude}, ${props.item.longitude}) to ${props.item.COLO}(${colos.value[props.item.COLO]?.lat}, ${colos.value[props.item.COLO]?.lon})`)
|
||||
const random = Math.random()
|
||||
globe.arcsData([arc])
|
||||
.arcColor('color')
|
||||
.arcDashLength(() => random + 0.2)
|
||||
.arcDashGap(() => random - 0.2)
|
||||
.arcDashAnimateTime(2000)
|
||||
|
||||
clearTimeout(cleanArcsDataTimer)
|
||||
cleanArcsDataTimer = setTimeout(() => {
|
||||
globe.arcsData([])
|
||||
}, delay + 100)
|
||||
}
|
||||
|
||||
const normalized = 5 / props.minutes
|
||||
const weightColor = scaleSequentialSqrt(interpolateYlOrRd).domain([0, highest.value * normalized * 15])
|
||||
function initGlobe() {
|
||||
globe = new Globe(globeEl.value)
|
||||
.width(size.value.width)
|
||||
.height(size.value.height)
|
||||
// .globeOffset([width.value > 768 ? -100 : 0, width.value > 768 ? 0 : 100])
|
||||
.atmosphereColor('rgba(170, 170, 200, 0.8)')
|
||||
.globeMaterial(new MeshPhongMaterial({
|
||||
color: 'rgb(228, 228, 231)',
|
||||
transparent: false,
|
||||
opacity: 1,
|
||||
}))
|
||||
.backgroundColor('rgba(0,0,0,0)')
|
||||
.hexPolygonsData(countries.value.features)
|
||||
.hexPolygonResolution(3)
|
||||
.hexPolygonMargin(0.2)
|
||||
.hexBinResolution(3)
|
||||
.hexBinPointsData(locations.value)
|
||||
.hexPolygonAltitude(() => hexAltitude.value)
|
||||
.hexBinMerge(true)
|
||||
.hexBinPointWeight('count')
|
||||
.hexPolygonColor(() => `rgba(54, 211, 153, ${Math.random() / 1.5 + 0.5})`)
|
||||
.onGlobeReady(() => {
|
||||
globe.pointOfView({ lat: currentLocation.value.latitude, lng: currentLocation.value.longitude, altitude: width.value > 768 ? 2 : 3 })
|
||||
globe.controls().autoRotate = true
|
||||
globe.controls().autoRotateSpeed = 0.3
|
||||
})
|
||||
|
||||
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))
|
||||
|
||||
globalTrafficEvent.on(trafficEvent)
|
||||
}
|
||||
|
||||
function stopRotation() {
|
||||
if (globe) {
|
||||
globe.controls().autoRotate = false
|
||||
}
|
||||
}
|
||||
|
||||
watch([time, filters], getLiveLocations, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
watch(width, () => {
|
||||
if (globe) {
|
||||
globe.width(size.value.width)
|
||||
globe.height(size.value.height)
|
||||
}
|
||||
})
|
||||
|
||||
watch(locations, () => {
|
||||
if (globe) {
|
||||
globe.hexBinPointsData(locations.value)
|
||||
globe.hexTopColor(d => weightColor(d.sumWeight))
|
||||
globe.hexSideColor(d => weightColor(d.sumWeight))
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
getLiveLocations(),
|
||||
getCurrentLocation(),
|
||||
getGlobeJSON(),
|
||||
getColosJSON(),
|
||||
])
|
||||
initGlobe()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
globalTrafficEvent.off(trafficEvent)
|
||||
if (globe) {
|
||||
globe._destructor?.()
|
||||
globe = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="globeEl" @mousedown="stopRotation" />
|
||||
</template>
|
||||
|
|
@ -1,10 +1,70 @@
|
|||
<script setup>
|
||||
import { now } from '@internationalized/date'
|
||||
import { useIntervalFn, useUrlSearchParams } from '@vueuse/core'
|
||||
import { safeDestr } from 'destr'
|
||||
|
||||
const searchParams = useUrlSearchParams('history')
|
||||
|
||||
const timePicker = ref(null)
|
||||
|
||||
const time = ref({
|
||||
startAt: date2unix(now().subtract({ hours: 1 })),
|
||||
endAt: date2unix(now()),
|
||||
})
|
||||
|
||||
provide('time', time)
|
||||
|
||||
function changeTime(timeRange, timeName) {
|
||||
console.log('changeTime', timeRange, timeName)
|
||||
time.value.startAt = timeRange[0]
|
||||
time.value.endAt = timeRange[1]
|
||||
|
||||
searchParams.time = timeName
|
||||
}
|
||||
|
||||
const filters = ref({})
|
||||
|
||||
provide('filters', filters)
|
||||
|
||||
function changeFilter(type, value) {
|
||||
console.log('changeFilter', type, value)
|
||||
filters.value[type] = value
|
||||
|
||||
searchParams.filters = JSON.stringify(filters.value)
|
||||
}
|
||||
|
||||
useIntervalFn(() => {
|
||||
timePicker.value?.restoreTimeRange()
|
||||
}, 5 * 60 * 1000)
|
||||
|
||||
function restoreSearchParams() {
|
||||
try {
|
||||
if (searchParams.filters) {
|
||||
filters.value = safeDestr(searchParams.filters)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('restore searchParams error', error)
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
restoreSearchParams()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="space-y-6">
|
||||
<div class="flex flex-col gap-6 sm:gap-2 sm:flex-row sm:justify-between">
|
||||
<DashboardNav />
|
||||
<DashboardNav class="flex-1">
|
||||
<DashboardTimePicker ref="timePicker" @update:time-range="changeTime" />
|
||||
</DashboardNav>
|
||||
<DashboardFilters @change="changeFilter" />
|
||||
</div>
|
||||
<div>
|
||||
Realtime
|
||||
<div class="relative space-y-4">
|
||||
<DashboardRealtimeChart class="md:absolute top-0 left-0 z-10" />
|
||||
<DashboardRealtimeGlobe />
|
||||
<DashboardRealtimeLogs class="md:absolute top-0 right-0 h-full z-10" />
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
|
|
|||
50
app/components/dashboard/realtime/Logs.vue
Normal file
50
app/components/dashboard/realtime/Logs.vue
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<script setup>
|
||||
import AnimatedList from '@/components/spark-ui/AnimatedList.vue'
|
||||
import Notification from '@/components/spark-ui/Notification.vue'
|
||||
|
||||
const time = inject('time')
|
||||
const filters = inject('filters')
|
||||
const logs = ref([])
|
||||
const logskey = ref(0)
|
||||
|
||||
async function getEvents() {
|
||||
const data = await useAPI('/api/logs/events', {
|
||||
query: {
|
||||
startAt: time.value.startAt,
|
||||
endAt: time.value.endAt,
|
||||
...filters.value,
|
||||
},
|
||||
})
|
||||
logs.value = data?.reverse()
|
||||
logskey.value = Date.now()
|
||||
}
|
||||
|
||||
watch([time, filters], getEvents, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
getEvents()
|
||||
})
|
||||
|
||||
function onUpdateItems(...args) {
|
||||
globalTrafficEvent.emit(...args)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AnimatedList v-if="logs.length" :key="logskey" class="md:w-72" @update:items="onUpdateItems">
|
||||
<template #default>
|
||||
<Notification
|
||||
v-for="item in logs"
|
||||
:key="item.id"
|
||||
:name="item.slug"
|
||||
:description="[item.os, item.browser].filter(Boolean).join(' ')"
|
||||
:icon="getFlag(item.country)"
|
||||
:time="item.timestamp"
|
||||
:item="item"
|
||||
class="w-full"
|
||||
/>
|
||||
</template>
|
||||
</AnimatedList>
|
||||
</template>
|
||||
99
app/components/spark-ui/AnimatedList.vue
Normal file
99
app/components/spark-ui/AnimatedList.vue
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<script lang="ts" setup>
|
||||
import { cn } from '@/lib/utils'
|
||||
import { computed, onMounted, ref, useSlots } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
class?: string
|
||||
delay?: number
|
||||
}>(), {
|
||||
delay: 1000,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:items'])
|
||||
|
||||
const slots = useSlots()
|
||||
const index = ref(0)
|
||||
const slotsArray = ref<any>([])
|
||||
const maxShowItems = 100
|
||||
|
||||
const itemsToShow = computed(() => {
|
||||
const start = index.value - maxShowItems < 0 ? 0 : index.value - maxShowItems
|
||||
return slotsArray.value.slice(start, index.value)
|
||||
})
|
||||
|
||||
watch([itemsToShow], () => {
|
||||
emit('update:items', itemsToShow.value.at(-1), props)
|
||||
})
|
||||
|
||||
async function loadComponents() {
|
||||
slotsArray.value = slots.default ? slots.default()?.[0]?.children : []
|
||||
|
||||
while (index.value < slotsArray.value.length) {
|
||||
index.value++
|
||||
await delay(props.delay)
|
||||
}
|
||||
}
|
||||
|
||||
async function delay(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function getInitial(idx: number) {
|
||||
return idx === index.value - 1
|
||||
? {
|
||||
scale: 0,
|
||||
opacity: 0,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
function getEnter(idx: number) {
|
||||
return idx === index.value - 1
|
||||
? {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
type: 'spring',
|
||||
stiffness: 250,
|
||||
damping: 40,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
function getLeave() {
|
||||
return {
|
||||
scale: 0,
|
||||
opacity: 0,
|
||||
y: 0,
|
||||
transition: {
|
||||
type: 'spring',
|
||||
stiffness: 350,
|
||||
damping: 40,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => loadComponents())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="cn('overflow-auto', $props.class)">
|
||||
<transition-group name="list" tag="div" class="flex flex-col-reverse items-center p-2" move-class="move">
|
||||
<div
|
||||
v-for="(item, idx) in itemsToShow"
|
||||
:key="item.props.key"
|
||||
v-motion :initial="getInitial(idx)" :enter="getEnter(idx)" :leave="getLeave()"
|
||||
:class="cn('mx-auto w-full')"
|
||||
>
|
||||
<component :is="item" />
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.move {
|
||||
transition: transform 0.4s ease-out;
|
||||
}
|
||||
</style>
|
||||
45
app/components/spark-ui/Notification.vue
Normal file
45
app/components/spark-ui/Notification.vue
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<script setup lang='ts'>
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
class?: string
|
||||
description: string
|
||||
icon?: string
|
||||
color?: string
|
||||
time: number
|
||||
}>()
|
||||
|
||||
const className = cn(
|
||||
'relative mx-auto min-h-fit w-full cursor-pointer border rounded-2xl my-1',
|
||||
// animation styles
|
||||
'transition-all duration-200 ease-in-out hover:scale-[103%] transform-gpu',
|
||||
// light styles
|
||||
'bg-white',
|
||||
// dark styles
|
||||
'dark:bg-transparent dark:backdrop-blur-md dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]',
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<figure :class="className">
|
||||
<div class="flex flex-row py-2 items-center px-2 gap-2">
|
||||
<div
|
||||
class="flex size-10 items-center justify-center rounded-2xl"
|
||||
:style="{ backgroundColor: props.color || 'transparent' }"
|
||||
>
|
||||
<span class="text-lg">{{ props.icon }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<div class="flex flex-row items-center whitespace-pre text-lg font-medium ">
|
||||
<span class="text-sm text-foreground sm:text-lg">{{ props.name }}</span>
|
||||
<span class="mx-1">·</span>
|
||||
<span class="text-xs text-gray-500">{{ shortTime(props.time) }}</span>
|
||||
</div>
|
||||
<p class="text-sm font-normal">
|
||||
{{ props.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
</template>
|
||||
|
|
@ -25,7 +25,7 @@ function template(d: any) {
|
|||
const omittedData = Object.entries(omit(d, [props.index])).map(([key, value]) => {
|
||||
const legendReference = props.items.find(i => i.name === key)
|
||||
return { ...legendReference, value }
|
||||
})
|
||||
}).filter(i => i.name)
|
||||
const TooltipComponent = props.customTooltip ?? ChartTooltip
|
||||
createApp(TooltipComponent, { title: d[props.index].toString(), data: omittedData }).mount(componentDiv)
|
||||
wm.set(d, componentDiv.innerHTML)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export function colorGradation(count: number) {
|
||||
return Array.from({ length: count }, (_, i) => `hsl(var(--vis-secondary-color) / ${1 - (1 / count) * i})`)
|
||||
return Array.from({ length: count }, (_, i) => `hsl(var(--vis-primary-color) / ${1 - (1 / count) * i})`)
|
||||
}
|
||||
|
|
|
|||
3
app/utils/events.ts
Normal file
3
app/utils/events.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { useEventBus } from '@vueuse/core'
|
||||
|
||||
export const globalTrafficEvent = useEventBus(Symbol('traffic'))
|
||||
|
|
@ -25,7 +25,14 @@ export function longDate(unix = 0) {
|
|||
return new Date(unix * 1000).toLocaleString()
|
||||
}
|
||||
|
||||
export function date2unix(dateValue: DateValue | Date, type: string) {
|
||||
export function shortTime(unix = 0) {
|
||||
const shortTime = new Intl.DateTimeFormat(undefined, {
|
||||
timeStyle: 'short',
|
||||
})
|
||||
return shortTime.format(unix * 1000)
|
||||
}
|
||||
|
||||
export function date2unix(dateValue: DateValue | Date, type?: string) {
|
||||
const date = dateValue instanceof Date ? dateValue : dateValue.toDate(getTimeZone())
|
||||
if (type === 'start')
|
||||
return Math.floor(date.setHours(0, 0, 0) / 1000)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
"baseColor": "zinc",
|
||||
"cssVariables": true
|
||||
},
|
||||
"framework": "nuxt",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/utils"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import withNuxt from './.nuxt/eslint.config.mjs'
|
|||
export default withNuxt(
|
||||
antfu(),
|
||||
{
|
||||
ignores: ['app/components/ui', '.data', 'public/world.json'],
|
||||
ignores: ['app/components/ui', '.data', 'public/*.json'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,16 @@
|
|||
"os": "OS",
|
||||
"browser": "Browser",
|
||||
"browserType": "Browser Type"
|
||||
},
|
||||
"time_picker": {
|
||||
"today": "Today",
|
||||
"last_5m": "Last 5 minutes",
|
||||
"last_10m": "Last 10 minutes",
|
||||
"last_30m": "Last 30 minutes",
|
||||
"last_1h": "Last 1 hour",
|
||||
"last_6h": "Last 6 hours",
|
||||
"last_12h": "Last 12 hours",
|
||||
"last_24h": "Last 24 hours"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,16 @@
|
|||
"os": "OS",
|
||||
"browser": "Navigateur",
|
||||
"browserType": "Type de navigateur"
|
||||
},
|
||||
"time_picker": {
|
||||
"today": "Aujourd'hui",
|
||||
"last_5m": "5 minutes",
|
||||
"last_10m": "10 minutes",
|
||||
"last_30m": "30 minutes",
|
||||
"last_1h": "1 heure",
|
||||
"last_6h": "6 heures",
|
||||
"last_12h": "12 heures",
|
||||
"last_24h": "24 heures"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,16 @@
|
|||
"os": "Hệ điều hành",
|
||||
"browser": "Trình duyệt",
|
||||
"browserType": "Loại trình duyệt"
|
||||
},
|
||||
"time_picker": {
|
||||
"today": "Hôm nay",
|
||||
"last_5m": "5 phút qua",
|
||||
"last_10m": "10 phút qua",
|
||||
"last_30m": "30 phút qua",
|
||||
"last_1h": "1 giờ qua",
|
||||
"last_6h": "6 giờ qua",
|
||||
"last_12h": "12 giờ qua",
|
||||
"last_24h": "24 giờ qua"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,16 @@
|
|||
"os": "操作系统",
|
||||
"browser": "浏览器",
|
||||
"browserType": "浏览器类型"
|
||||
},
|
||||
"time_picker": {
|
||||
"today": "今天",
|
||||
"last_5m": "最近5分钟",
|
||||
"last_10m": "最近10分钟",
|
||||
"last_30m": "最近30分钟",
|
||||
"last_1h": "最近1小时",
|
||||
"last_6h": "最近6小时",
|
||||
"last_12h": "最近12小时",
|
||||
"last_24h": "最近24小时"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,16 @@
|
|||
"os": "操作系統",
|
||||
"browser": "瀏覽器",
|
||||
"browserType": "瀏覽器類型"
|
||||
},
|
||||
"time_picker": {
|
||||
"today": "今天",
|
||||
"last_5m": "最近5分鐘",
|
||||
"last_10m": "最近10分鐘",
|
||||
"last_30m": "最近30分鐘",
|
||||
"last_1h": "最近1小時",
|
||||
"last_6h": "最近6小時",
|
||||
"last_12h": "最近12小時",
|
||||
"last_24h": "最近24小時"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default defineNuxtConfig({
|
|||
'shadcn-nuxt',
|
||||
'@nuxt/eslint',
|
||||
'@nuxtjs/tailwindcss',
|
||||
'@vueuse/motion/nuxt',
|
||||
'@nuxtjs/color-mode',
|
||||
'@nuxtjs/i18n',
|
||||
],
|
||||
|
|
@ -41,8 +42,10 @@ export default defineNuxtConfig({
|
|||
routeRules: {
|
||||
'/': {
|
||||
prerender: true,
|
||||
ssr: false,
|
||||
},
|
||||
'/dashboard/**': {
|
||||
prerender: true,
|
||||
ssr: false,
|
||||
},
|
||||
'/dashboard': {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"dev": "nuxt dev",
|
||||
"build": "nuxt build",
|
||||
"build:map": "node scripts/build-map.js",
|
||||
"build:colo": "node scripts/build-colo.js",
|
||||
"preview": "wrangler pages dev dist",
|
||||
"deploy": "wrangler pages deploy dist",
|
||||
"postinstall": "npm run build:map && nuxt prepare",
|
||||
|
|
@ -21,13 +22,19 @@
|
|||
"lint-staged": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "^11.1.3",
|
||||
"@number-flow/vue": "^0.3.3",
|
||||
"@unovis/ts": "^1.5.0",
|
||||
"@unovis/vue": "^1.5.0",
|
||||
"@vee-validate/zod": "^4.15.0",
|
||||
"@vueuse/core": "^12.2.0",
|
||||
"@vueuse/motion": "^3.0.3",
|
||||
"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",
|
||||
|
|
|
|||
405
pnpm-lock.yaml
405
pnpm-lock.yaml
|
|
@ -8,6 +8,9 @@ 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))
|
||||
|
|
@ -23,12 +26,27 @@ importers:
|
|||
'@vueuse/core':
|
||||
specifier: ^12.2.0
|
||||
version: 12.2.0(typescript@5.7.2)
|
||||
'@vueuse/motion':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(magicast@0.3.5)(vue@3.5.13(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':
|
||||
|
|
@ -2112,6 +2134,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==}
|
||||
|
||||
|
|
@ -2310,6 +2344,9 @@ packages:
|
|||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.18.2':
|
||||
resolution: {integrity: sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
|
@ -2578,6 +2615,11 @@ packages:
|
|||
'@vueuse/core@12.7.0':
|
||||
resolution: {integrity: sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==}
|
||||
|
||||
'@vueuse/core@13.2.0':
|
||||
resolution: {integrity: sha512-n5TZoIAxbWAQ3PqdVPDzLgIRQOujFfMlatdI+f7ditSmoEeNpPBvp7h2zamzikCmrhFIePAwdEQB6ENccHr7Rg==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/integrations@12.2.0':
|
||||
resolution: {integrity: sha512-Bc0unXiGNZ0w7xqSvzCuP7AflBRKcZX6ib7tGi7vAjOxhLd6GtN3J8qizIbSPnI62XyPy5fauOkq2i2HUPq6MQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -2628,6 +2670,14 @@ packages:
|
|||
'@vueuse/metadata@12.7.0':
|
||||
resolution: {integrity: sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==}
|
||||
|
||||
'@vueuse/metadata@13.2.0':
|
||||
resolution: {integrity: sha512-kPpzuQCU0+D8DZCzK0iPpIcXI+6ufWSgwnjJ6//GNpEn+SHViaCtR+XurzORChSgvpHO9YC8gGM97Y1kB+UabA==}
|
||||
|
||||
'@vueuse/motion@3.0.3':
|
||||
resolution: {integrity: sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==}
|
||||
peerDependencies:
|
||||
vue: '>=3.0.0'
|
||||
|
||||
'@vueuse/shared@10.11.1':
|
||||
resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==}
|
||||
|
||||
|
|
@ -2637,6 +2687,11 @@ packages:
|
|||
'@vueuse/shared@12.7.0':
|
||||
resolution: {integrity: sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==}
|
||||
|
||||
'@vueuse/shared@13.2.0':
|
||||
resolution: {integrity: sha512-vx9ZPDF5HcU9up3Jgt3G62dMUfZEdk6tLyBAHYAG4F4n73vpaA7J5hdncDI/lS9Vm7GA/FPlbOmh9TrDZROTpg==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
abbrev@1.1.1:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
|
||||
|
|
@ -2648,6 +2703,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 +3344,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 +3363,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 +3425,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 +3437,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 +3634,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 +4090,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 +4105,12 @@ packages:
|
|||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
frame-ticker@1.0.3:
|
||||
resolution: {integrity: sha512-E0X2u2JIvbEMrqEg5+4BpTqaD22OwojJI63K7MdKHdncjtAhGRbCR8nJCr2vwEt9NWBPCPcu70X9smPviEBy8Q==}
|
||||
|
||||
framesync@6.1.2:
|
||||
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
|
||||
|
||||
fresh@0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
|
@ -4181,6 +4268,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 +4286,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==}
|
||||
|
||||
|
|
@ -4235,6 +4330,9 @@ packages:
|
|||
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
|
||||
hasBin: true
|
||||
|
||||
hey-listen@1.0.8:
|
||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
||||
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
|
|
@ -4321,6 +4419,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 +4644,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 +5457,16 @@ 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'}
|
||||
|
||||
popmotion@11.0.5:
|
||||
resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
|
||||
|
||||
portfinder@1.0.32:
|
||||
resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
|
||||
engines: {node: '>= 0.12.0'}
|
||||
|
|
@ -5575,6 +5691,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 +6001,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 +6037,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'}
|
||||
|
|
@ -6048,6 +6166,9 @@ packages:
|
|||
striptags@3.2.0:
|
||||
resolution: {integrity: sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==}
|
||||
|
||||
style-value-types@5.1.2:
|
||||
resolution: {integrity: sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==}
|
||||
|
||||
stylehacks@7.0.4:
|
||||
resolution: {integrity: sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==}
|
||||
engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
|
||||
|
|
@ -6148,9 +6269,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 +6312,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==}
|
||||
|
||||
|
|
@ -6215,9 +6372,15 @@ packages:
|
|||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
tslib@2.4.0:
|
||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||
|
||||
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 +8050,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 +8082,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 +8127,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))
|
||||
|
|
@ -8900,6 +9068,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':
|
||||
|
|
@ -9129,6 +9318,8 @@ snapshots:
|
|||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.18.2(@typescript-eslint/parser@8.18.2(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.11.0
|
||||
|
|
@ -9468,7 +9659,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:
|
||||
|
|
@ -9594,6 +9785,13 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/core@13.2.0(vue@3.5.13(typescript@5.7.2))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 13.2.0
|
||||
'@vueuse/shared': 13.2.0(vue@3.5.13(typescript@5.7.2))
|
||||
vue: 3.5.13(typescript@5.7.2)
|
||||
|
||||
'@vueuse/integrations@12.2.0(fuse.js@7.0.0)(typescript@5.7.2)':
|
||||
dependencies:
|
||||
'@vueuse/core': 12.2.0(typescript@5.7.2)
|
||||
|
|
@ -9610,6 +9808,23 @@ snapshots:
|
|||
|
||||
'@vueuse/metadata@12.7.0': {}
|
||||
|
||||
'@vueuse/metadata@13.2.0': {}
|
||||
|
||||
'@vueuse/motion@3.0.3(magicast@0.3.5)(vue@3.5.13(typescript@5.7.2))':
|
||||
dependencies:
|
||||
'@vueuse/core': 13.2.0(vue@3.5.13(typescript@5.7.2))
|
||||
'@vueuse/shared': 13.2.0(vue@3.5.13(typescript@5.7.2))
|
||||
defu: 6.1.4
|
||||
framesync: 6.1.2
|
||||
popmotion: 11.0.5
|
||||
style-value-types: 5.1.2
|
||||
vue: 3.5.13(typescript@5.7.2)
|
||||
optionalDependencies:
|
||||
'@nuxt/kit': 3.15.4(magicast@0.3.5)
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
- supports-color
|
||||
|
||||
'@vueuse/shared@10.11.1(vue@3.5.13(typescript@5.7.2))':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.2))
|
||||
|
|
@ -9629,6 +9844,10 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/shared@13.2.0(vue@3.5.13(typescript@5.7.2))':
|
||||
dependencies:
|
||||
vue: 3.5.13(typescript@5.7.2)
|
||||
|
||||
abbrev@1.1.1: {}
|
||||
|
||||
abort-controller@3.0.0:
|
||||
|
|
@ -9640,6 +9859,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 +10510,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 +10529,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 +10588,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 +10634,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 +10767,8 @@ snapshots:
|
|||
|
||||
earcut@2.2.4: {}
|
||||
|
||||
earcut@3.0.1: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
|
@ -11171,6 +11412,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 +11432,14 @@ snapshots:
|
|||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
frame-ticker@1.0.3:
|
||||
dependencies:
|
||||
simplesignal: 2.1.7
|
||||
|
||||
framesync@6.1.2:
|
||||
dependencies:
|
||||
tslib: 2.4.0
|
||||
|
||||
fresh@0.5.2: {}
|
||||
|
||||
fs-extra@11.2.0:
|
||||
|
|
@ -11369,6 +11624,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 +11643,8 @@ snapshots:
|
|||
dependencies:
|
||||
duplexer: 0.1.2
|
||||
|
||||
h3-js@4.2.1: {}
|
||||
|
||||
h3@1.12.0:
|
||||
dependencies:
|
||||
cookie-es: 1.1.0
|
||||
|
|
@ -11431,6 +11697,8 @@ snapshots:
|
|||
|
||||
he@1.2.0: {}
|
||||
|
||||
hey-listen@1.0.8: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
hosted-git-info@2.8.9: {}
|
||||
|
|
@ -11515,6 +11783,8 @@ snapshots:
|
|||
|
||||
indent-string@4.0.0: {}
|
||||
|
||||
index-array-by@1.4.2: {}
|
||||
|
||||
inflight@1.0.6:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
|
@ -11695,6 +11965,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 +13202,21 @@ 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
|
||||
|
||||
popmotion@11.0.5:
|
||||
dependencies:
|
||||
framesync: 6.1.2
|
||||
hey-listen: 1.0.8
|
||||
style-value-types: 5.1.2
|
||||
tslib: 2.4.0
|
||||
|
||||
portfinder@1.0.32:
|
||||
dependencies:
|
||||
async: 2.6.4
|
||||
|
|
@ -13141,6 +13430,8 @@ snapshots:
|
|||
|
||||
potpack@1.0.2: {}
|
||||
|
||||
preact@10.26.6: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
pretty-bytes@6.1.1: {}
|
||||
|
|
@ -13517,6 +13808,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
simplesignal@2.1.7: {}
|
||||
|
||||
sirv@3.0.0:
|
||||
dependencies:
|
||||
'@polka/url': 1.0.0-next.25
|
||||
|
|
@ -13545,8 +13838,6 @@ snapshots:
|
|||
|
||||
smob@1.5.0: {}
|
||||
|
||||
source-map-js@1.2.0: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
|
|
@ -13669,6 +13960,11 @@ snapshots:
|
|||
|
||||
striptags@3.2.0: {}
|
||||
|
||||
style-value-types@5.1.2:
|
||||
dependencies:
|
||||
hey-listen: 1.0.8
|
||||
tslib: 2.4.0
|
||||
|
||||
stylehacks@7.0.4(postcss@8.4.49):
|
||||
dependencies:
|
||||
browserslist: 4.24.3
|
||||
|
|
@ -13809,12 +14105,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:
|
||||
|
|
@ -13864,8 +14219,12 @@ snapshots:
|
|||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tslib@2.4.0: {}
|
||||
|
||||
tslib@2.6.3: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsscmp@1.0.6: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
|
|
|
|||
1
public/colos.json
Normal file
1
public/colos.json
Normal file
File diff suppressed because one or more lines are too long
26017
public/countries.geojson
Normal file
26017
public/countries.geojson
Normal file
File diff suppressed because it is too large
Load diff
19
scripts/build-colo.js
Normal file
19
scripts/build-colo.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
async function main() {
|
||||
const locations = await fetch('https://raw.githubusercontent.com/Netrvin/cloudflare-colo-list/refs/heads/main/locations.json')
|
||||
if (!locations.ok) {
|
||||
throw new Error('Failed to fetch locations')
|
||||
}
|
||||
const colos = await locations.json()
|
||||
writeFileSync(join(import.meta.dirname, '../public/colos.json'), JSON.stringify(colos.reduce((acc, c) => {
|
||||
acc[c.iata] = {
|
||||
lat: c.lat,
|
||||
lon: c.lon,
|
||||
}
|
||||
return acc
|
||||
}, {}), 'utf8'))
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
7
server/api/location.ts
Normal file
7
server/api/location.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default eventHandler((event) => {
|
||||
const { request: { cf } } = event.context.cloudflare
|
||||
return {
|
||||
latitude: cf.latitude,
|
||||
longitude: cf.longitude,
|
||||
}
|
||||
})
|
||||
45
server/api/logs/events.ts
Normal file
45
server/api/logs/events.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import type { H3Event } from 'h3'
|
||||
import { QuerySchema } from '@@/schemas/query'
|
||||
import { date2unix } from '~/utils/time'
|
||||
|
||||
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).orderBy('timestamp DESC')
|
||||
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<string[]>((_, _c, i) => {
|
||||
_.push(event[`blob${i + 1}`])
|
||||
return _
|
||||
}, [])
|
||||
const doubles = Array.from({ length: Object.keys(doublesMap).length }).fill(0).reduce<number[]>((_, _c, i) => {
|
||||
_.push(+event[`double${i + 1}`])
|
||||
return _
|
||||
}, [])
|
||||
return {
|
||||
...blobs2logs(blobs),
|
||||
...doubles2logs(doubles),
|
||||
ip: undefined,
|
||||
id: crypto.randomUUID(),
|
||||
timestamp: date2unix(new Date(`${event.timestamp}Z`)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 || [])
|
||||
})
|
||||
22
server/api/logs/locations.ts
Normal file
22
server/api/logs/locations.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import type { H3Event } from 'h3'
|
||||
import { QuerySchema } from '@@/schemas/query'
|
||||
|
||||
const { select, and, notEq } = SqlBricks
|
||||
|
||||
function query2sql(query: Query, event: H3Event): string {
|
||||
const filter = query2filter(query)
|
||||
const { dataset } = useRuntimeConfig(event)
|
||||
const sql = select(`blob8 as ${blobsMap.blob8},double1 as ${doublesMap.double1},double2 as ${doublesMap.double2},count() as count`)
|
||||
.from(dataset)
|
||||
.where(and([notEq('double1', 0), notEq('double2', 0), filter]))
|
||||
.groupBy([blobsMap.blob8, doublesMap.double1, doublesMap.double2])
|
||||
appendTimeFilter(sql, query)
|
||||
return sql.toString()
|
||||
}
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const query = await getValidatedQuery(event, QuerySchema.parse)
|
||||
const sql = query2sql(query, event)
|
||||
|
||||
return useWAE(event, sql)
|
||||
})
|
||||
|
|
@ -5,6 +5,7 @@ import { z } from 'zod'
|
|||
const { select } = SqlBricks
|
||||
|
||||
const unitMap: { [x: string]: string } = {
|
||||
minute: '%H:%M',
|
||||
hour: '%Y-%m-%d %H',
|
||||
day: '%Y-%m-%d',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,13 +60,13 @@ export const logsMap = {
|
|||
...Object.entries(doublesMap).reduce((acc, [k, v]) => ({ ...acc, [v]: k }), {}),
|
||||
} as LogsMap
|
||||
|
||||
function logs2blobs(logs: LogsMap) {
|
||||
export function logs2blobs(logs: LogsMap) {
|
||||
return (Object.keys(blobsMap) as BlobsKey[])
|
||||
.sort((a, b) => toBlobNumber(a) - toBlobNumber(b))
|
||||
.map(key => String(logs[blobsMap[key] as LogsKey] || ''))
|
||||
}
|
||||
|
||||
function blobs2logs(blobs: string[]) {
|
||||
export function blobs2logs(blobs: string[]) {
|
||||
const logsList = Object.keys(blobsMap)
|
||||
|
||||
return blobs.reduce((logs, blob, i) => {
|
||||
|
|
@ -76,13 +76,13 @@ function blobs2logs(blobs: string[]) {
|
|||
}, {} as Partial<LogsMap>)
|
||||
}
|
||||
|
||||
function logs2doubles(logs: LogsMap) {
|
||||
export function logs2doubles(logs: LogsMap) {
|
||||
return (Object.keys(doublesMap) as DoublesKey[])
|
||||
.sort((a, b) => toBlobNumber(a) - toBlobNumber(b))
|
||||
.map(key => Number(logs[doublesMap[key] as LogsKey] || 0))
|
||||
}
|
||||
|
||||
function doubles2logs(doubles: number[]) {
|
||||
export function doubles2logs(doubles: number[]) {
|
||||
const logsList = Object.keys(doublesMap)
|
||||
|
||||
return doubles.reduce((logs, double, i) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue