internal/dashboard/frontend/src/components/EventTable/EventTableRow.vue

<script setup lang="ts">
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 { computed, ref } 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 showLinks = ref(false);
</script>

<template>
  <tr
    @mouseenter="showLinks = true"
    @mouseleave="showLinks = false"
    @click="showLinks = !showLinks"
  >
    <td v-if="columns.includes('time')" class="md:whitespace-nowrap">
      {{ formatTimestamp(evt.time, true) }}
    </td>
    <td v-if="columns.includes('event')">
      <div class="max-w-48 md:whitespace-nowrap">
        <span class="text-xs font-semibold">
          <router-link
            :to="`/honeypot/${evt.type}`"
            class="text-muted hover:text-secondary-400"
            v-if="getHoneypot(evt.type)"
          >
            {{ getHoneypot(evt.type)?.label }}:
          </router-link>
          <span class="uppercase">{{ evt.event || "—" }}</span>
        </span>
      </div>
    </td>
    <td v-if="columns.includes('remote_addr')">
      <div
        v-if="evt.remote_addr"
        class="group flex items-center justify-between gap-2"
      >
        <EventSourceInfo
          :evt="evt"
          :filter-actions="filterActions"
          :geo="geo"
        />
        <div class="flex items-center gap-2">
          <div
            class="flex gap-1"
            v-if="blocklistStore.getTagsByIp(evt.remote_addr!).length"
          >
            <ScoreTag
              v-for="tag in blocklistStore.getTagsByIp(evt.remote_addr!)"
              :key="tag"
              :tag="tag"
              no-label
            />
          </div>
          <EventFilterMenu
            :evt="evt"
            :geo="geo"
            :filter-actions="filterActions"
            :show-links="showLinks"
          />
        </div>
      </div>
      <span v-else class="text-muted"></span>
    </td>
    <td v-if="columns.includes('details')" class="align-top text-xs">
      <EventDetails
        :evt="evt"
        :filter-actions="filterActions"
        :width="width || 1000"
      />
    </td>
  </tr>
</template>

<style scoped>
@reference "src/style.css";

a {
  @apply hover:text-secondary-400;
}
</style>