104 lines
3.3 KiB
Vue
104 lines
3.3 KiB
Vue
<script setup lang="ts">
|
|||
|
|
import { Boxes, FunctionSquare } from 'lucide-vue-next'
|
||
|
|
import { computed, onMounted, ref } from 'vue'
|
||
|
|
|
||
|
|
import PageHeader from '@/components/PageHeader.vue'
|
||
|
|
import {
|
||
|
|
Badge,
|
||
|
|
Card,
|
||
|
|
CardContent,
|
||
|
|
CardDescription,
|
||
|
|
CardHeader,
|
||
|
|
CardTitle,
|
||
|
|
Skeleton,
|
||
|
|
} from '@/components/ui'
|
||
|
|
import { useLive } from '@/composables/useLive'
|
||
|
|
import { t } from '@/i18n'
|
||
|
|
import { api } from '@/lib/api'
|
||
|
|
import { compact } from '@/lib/format'
|
||
|
|
import type { MetaInfo } from '@/lib/types'
|
||
|
|
|
||
|
|
const { metrics } = useLive()
|
||
|
|
const meta = ref<MetaInfo | null>(null)
|
||
|
|
|
||
|
|
onMounted(async () => {
|
||
|
|
try {
|
||
|
|
meta.value = await api.meta()
|
||
|
|
} catch {
|
||
|
|
meta.value = { analyzers: [], modifiers: [], actions: [], functions: [] }
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
const hits = computed(() => {
|
||
|
|
const map = new Map<string, number>()
|
||
|
|
for (const item of metrics.value?.analyzers ?? []) map.set(item.name, item.count)
|
||
|
|
return map
|
||
|
|
})
|
||
|
|
|
||
|
|
const analyzers = computed(() =>
|
||
|
|
[...(meta.value?.analyzers ?? [])].sort(
|
||
|
|
(a, b) => (hits.value.get(b.name) ?? 0) - (hits.value.get(a.name) ?? 0),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div>
|
||
|
|
<PageHeader :title="t('analyzers.title')" :description="t('analyzers.subtitle')" />
|
||
|
|
|
||
|
|
<div v-if="!meta" class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
|
||
|
|
<Skeleton v-for="i in 8" :key="i" class="h-24" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<template v-else>
|
||
|
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
|
||
|
|
<Card v-for="analyzer in analyzers" :key="analyzer.name" class="p-3.5 sm:p-4">
|
||
|
|
<div class="flex items-center justify-between gap-2">
|
||
|
|
<span class="truncate font-mono text-sm font-medium">{{ analyzer.name }}</span>
|
||
|
|
<Badge :variant="analyzer.proto === 'udp' ? 'udp' : 'tcp'" class="uppercase">
|
||
|
|
{{ analyzer.proto }}
|
||
|
|
</Badge>
|
||
|
|
</div>
|
||
|
|
<p class="mt-2 text-xl font-semibold tabular-nums">
|
||
|
|
{{ compact(hits.get(analyzer.name) ?? 0) }}
|
||
|
|
</p>
|
||
|
|
<p class="text-muted-foreground text-xs">{{ t('analyzers.hits') }}</p>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="mt-3 grid gap-3 md:grid-cols-2">
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle class="flex items-center gap-2">
|
||
|
|
<Boxes class="size-4" />
|
||
|
|
{{ t('analyzers.modifiers') }}
|
||
|
|
</CardTitle>
|
||
|
|
<CardDescription>{{ t('analyzers.modifiersDesc') }}</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent class="flex flex-wrap gap-1.5">
|
||
|
|
<Badge v-for="name in meta.modifiers" :key="name" variant="secondary" class="font-mono">
|
||
|
|
{{ name }}
|
||
|
|
</Badge>
|
||
|
|
<span v-if="!meta.modifiers.length" class="text-muted-foreground text-xs">—</span>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle class="flex items-center gap-2">
|
||
|
|
<FunctionSquare class="size-4" />
|
||
|
|
{{ t('analyzers.functions') }}
|
||
|
|
</CardTitle>
|
||
|
|
<CardDescription>{{ t('analyzers.functionsDesc') }}</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent class="flex flex-wrap gap-1.5">
|
||
|
|
<Badge v-for="name in meta.functions" :key="name" variant="secondary" class="font-mono">
|
||
|
|
{{ name }}()
|
||
|
|
</Badge>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
</div>
|
||
|
|
</template>
|