<script setup lang="ts">
import DetailCard from "components/DetailView/DetailCard.vue";
import LoaderOverlay from "components/LoaderOverlay.vue";
import ScoreTag from "components/ScoreTag.vue";
import SectionCard from "components/SectionCard.vue";
import SparkChart from "components/SparkChart.vue";
import StatBarTable, { type StatBarItem } from "components/StatBarTable.vue";
import { countries } from "src/countries";
import { getHoneypot, useBlocklistStore, useDashboardStore } from "src/store";
import { splitSubnet, timeAgo } from "utils/formatting";
import { icons } from "utils/icons";
import { computed, onMounted } from "vue";
const dashboardStore = useDashboardStore();
const blocklistStore = useBlocklistStore();
const stats = computed(() => dashboardStore.stats);
const statsLoading = computed(() => dashboardStore.loading && !stats.value);
const blocklistLoading = computed(
() => blocklistStore.loading && blocklistStore.blocklist.length === 0,
);
const blocklistData = computed(() => {
return blocklistStore.blocklist.map((entry) => ({
label: entry.address,
expires: entry.expires,
tags: entry.reason.split(","),
routerLink: `/ip/${entry.address}`,
}));
});
const topRemoteAddrsData = computed<StatBarItem[]>(() => {
return (stats.value?.stats_24h?.remote_addrs || []).map((a) => ({
label: a.label,
count: a.count,
routerLink: `/ip/${a.label}`,
}));
});
const topPortsData = computed<StatBarItem[]>(() => {
return (stats.value?.stats_24h?.ports || []).map((p) => ({
label: p.label,
count: p.count,
routerLink: `/port/${p.label}`,
}));
});
const topTypesData = computed<StatBarItem[]>(() => {
return (stats.value?.stats_24h?.types || []).map((t) => ({
label: getHoneypot(t.label)?.label ?? t.label,
count: t.count,
routerLink: `/honeypot/${t.label}`,
}));
});
const topDomainsData = computed<StatBarItem[]>(() => {
return (stats.value?.stats_24h?.domains || []).map((d) => ({
label: d.label,
count: d.count,
routerLink: `/domain/${d.label}`,
}));
});
const topCountriesData = computed<StatBarItem[]>(() => {
return (stats.value?.stats_24h?.countries || []).map((c) => ({
label:
countries.get(c.label) !== undefined
? countries.get(c.label)?.[0] + " " + countries.get(c.label)?.[1]
: c.label,
count: c.count,
routerLink: `/country/${c.label}`,
}));
});
const sparkData = computed(() => {
if (!stats.value?.sparkline) return { x: [], y: [] };
return {
x: stats.value.sparkline.map((s) => new Date(s.time).getTime()),
y: stats.value.sparkline.map((s) => s.count),
};
});
onMounted(() => {
dashboardStore.fetchStats();
blocklistStore.fetchBlocklist();
});
</script>
<template>
<div class="page-container">
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<DetailCard label="24h Activity Trend" :icon="icons.activity">
<div class="relative h-32 pt-2">
<LoaderOverlay v-if="statsLoading" />
<SparkChart
:data="sparkData"
class="text-primary-500 h-full w-full"
/>
</div>
</DetailCard>
<DetailCard
label="Last 24 Hours"
:value="stats?.count_24h"
:icon="icons.time"
value-class="text-3xl font-bold"
/>
<DetailCard
label="Total Events"
:value="
stats?.stats_all?.types?.reduce((acc, curr) => acc + curr.count, 0) ??
0
"
:icon="icons.total"
value-class="text-3xl font-bold"
/>
</div>
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3">
<SectionCard
:title="`Blocked Addresses (${blocklistData.length})`"
:icon="icons.blocked"
class="relative"
>
<LoaderOverlay v-if="blocklistLoading" />
<div
class="h-full max-h-[300px] overflow-y-auto"
v-if="blocklistData.length > 0"
>
<table class="w-full text-sm">
<thead>
<tr class="text-stone-400">
<th class="w-min pb-2 text-left">Address</th>
<th class="w-full px-2 pb-2 text-left">Tags</th>
<th class="w-min pb-2 text-right whitespace-nowrap">Expires</th>
</tr>
</thead>
<tbody>
<tr v-for="s in blocklistData" :key="s.label">
<td class="w-min">
<router-link
:to="s.routerLink"
class="hover:text-secondary-400"
>
<span>
{{ splitSubnet(s.label).address
}}<span class="text-secondary-300">{{
splitSubnet(s.label).mask
}}</span>
</span>
</router-link>
</td>
<td class="w-full px-2">
<span class="inline-flex flex-wrap gap-0.5">
<ScoreTag v-for="tag in s.tags" :key="tag" :tag="tag" />
</span>
</td>
<td class="w-min text-right whitespace-nowrap">
{{ timeAgo(s.expires) }}
</td>
</tr>
</tbody>
</table>
</div>
<div
v-else
class="flex items-center justify-center py-4 text-sm text-stone-500"
>
No data available
</div>
</SectionCard>
<SectionCard title="Top Remote Addresses" :icon="icons.address">
<StatBarTable
:items="topRemoteAddrsData"
:loading="statsLoading"
class="max-h-[300px]"
/>
</SectionCard>
<SectionCard title="Top Countries" :icon="icons.country">
<StatBarTable
:items="topCountriesData"
:loading="statsLoading"
class="max-h-[300px]"
/>
</SectionCard>
<SectionCard title="Top Domains" :icon="icons.domain">
<StatBarTable
:items="topDomainsData"
:loading="statsLoading"
class="max-h-[300px]"
/>
</SectionCard>
<SectionCard title="Top Ports" :icon="icons.port">
<StatBarTable
:items="topPortsData"
:loading="statsLoading"
class="max-h-[300px]"
/>
</SectionCard>
<SectionCard title="Top Honeypots" :icon="icons.honeypotType">
<StatBarTable
:items="topTypesData"
:loading="statsLoading"
class="max-h-[300px]"
/>
</SectionCard>
</div>
</div>
</template>