<script setup lang="ts">
import { IconArrowsMaximize, IconArrowsMinimize } from "@tabler/icons-vue";
import EventDetails from "components/EventTable/EventDetails.vue";
import ScoreTag from "components/ScoreTag.vue";
import {
type FilterActions,
type GeoData,
getHoneypot,
useBlocklistStore,
useFilterActions,
useIPInfoStore,
} from "src/store";
import { type HoneypotEvent } from "src/types";
import { formatTimestamp } from "utils/formatting";
import { icons } from "utils/icons";
import { computed } from "vue";
import EventFilterMenu from "./EventFilterMenu.vue";
import EventSourceInfo from "./EventSourceInfo.vue";
const ipInfoStore = useIPInfoStore();
const { getIPInfo } = ipInfoStore;
const blocklistStore = useBlocklistStore();
const props = withDefaults(
defineProps<{
evt: HoneypotEvent;
columns?: string[];
filterActions?: FilterActions;
width?: number;
}>(),
{
columns: () => ["time", "event", "remote_addr", "details"],
},
);
const filterActions = props.filterActions || useFilterActions();
const geo = computed<GeoData | undefined>(() => {
if (!filterActions.state.resolve_ips) return undefined;
return getIPInfo(props.evt.remote_addr!).value;
});
const hasDetails = computed(() => {
return props.evt.fields && Object.keys(props.evt.fields).length > 0;
});
</script>
<template>
<tr class="group border-b border-stone-800/50">
<td colspan="100%" class="p-0">
<div class="flex flex-col gap-3 py-2">
<!-- Row 1: Header / Meta -->
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<span
class="text-muted text-sm font-medium tracking-wide uppercase"
>
{{ formatTimestamp(evt.time, true) }}
</span>
</div>
<div class="flex items-center gap-2">
<EventFilterMenu
:evt="evt"
:geo="geo"
:filter-actions="filterActions"
show-links
/>
</div>
</div>
<!-- Row 2: Event Name & Badges -->
<div class="flex flex-wrap items-center justify-between gap-4">
<div class="flex gap-1">
<span class="text-muted font-semibold">
<router-link
:to="`/honeypot/${evt.type}`"
class="hover:text-secondary-400"
>
{{ getHoneypot(evt.type)?.label || evt.type }} </router-link
>:
</span>
<span class="font-bold uppercase">{{ evt.event }}</span>
</div>
<div
class="flex shrink-0 gap-1"
v-if="blocklistStore.getTagsByIp(evt.remote_addr!).length"
>
<ScoreTag
v-for="tag in blocklistStore.getTagsByIp(evt.remote_addr!)"
:key="tag"
:tag="tag"
/>
</div>
</div>
<!-- Row 3: Source & Destination -->
<div class="flex flex-col gap-2.5 py-0.5">
<div class="flex items-center gap-2">
<component :is="icons.address" size="20" class="text-muted mt-px" />
<EventSourceInfo
:evt="evt"
:filter-actions="filterActions"
:geo="geo"
:is-mobile="true"
/>
</div>
<div v-if="evt.dst_port" class="flex items-center gap-2">
<component :is="icons.port" class="text-muted mt-px" size="20" />
<router-link
:to="`/port/${evt.dst_port}`"
class="hover:text-stone-50"
>
{{ evt.dst_port }}
</router-link>
</div>
</div>
<!-- Row 4: Expandable Details -->
<div v-if="hasDetails" class="border-stone-800 pt-4 duration-300">
<div class="mb-3 flex items-center gap-2">
<span
class="text-muted text-[10px] font-bold tracking-widest uppercase"
>
Details
</span>
</div>
<div class="relative">
<button
@click="
filterActions.state.expand_details =
!filterActions.state.expand_details
"
class="icon-button absolute -top-8 right-0 text-xs"
>
<component
:is="
filterActions.state.expand_details
? IconArrowsMinimize
: IconArrowsMaximize
"
size="16"
/>
</button>
<EventDetails
:evt="evt"
:filter-actions="filterActions"
:width="width || 1000"
/>
</div>
</div>
</div>
</td>
</tr>
</template>