<script setup lang="ts">
import { IconDownload } from "@tabler/icons-vue";
import { useWindowSize } from "@vueuse/core";
import EventTableFooter from "components/EventTable/EventTableFooter.vue";
import EventTableHeader from "components/EventTable/EventTableHeader.vue";
import EventTableRow from "components/EventTable/EventTableRow.vue";
import LoaderOverlay from "components/LoaderOverlay.vue";
import { buildQueryStringFromState, type FilterActions } from "src/store";
import { type HoneypotEvent } from "src/types";
import { formatLocalNumber } from "utils/formatting";
import { URL } from "utils/utils";
import { computed } from "vue";
import EventTableMobileRow from "./EventTableMobileRow.vue";
import EventTableTHead from "./EventTableTHead.vue";
const props = withDefaults(
defineProps<{
events: HoneypotEvent[];
totalEvents: number;
loading?: boolean;
columns?: string[];
filterActions: FilterActions;
}>(),
{
columns: () => ["time", "event", "remote_addr", "details"],
},
);
const state = props.filterActions.state;
const exportURL = computed(() => {
let url = `${URL()}/api/events/export/json`;
const qs = buildQueryStringFromState(state);
if (qs) {
url += `?${qs}`;
}
return url;
});
const { width } = useWindowSize();
</script>
<template>
<div class="card relative p-0">
<EventTableHeader :filter-actions="filterActions">
<template #header-right>
<div class="flex flex-wrap items-center gap-2">
<slot name="header-right">
<span class="stat-label">
{{ formatLocalNumber(totalEvents) }} entries
</span>
</slot>
<a :href="exportURL" class="btn-secondary h-8 py-0.5 text-[11px]">
<IconDownload /> JSON
</a>
</div>
</template>
</EventTableHeader>
<LoaderOverlay v-if="loading" />
<div class="">
<table class="event-table w-full table-fixed text-sm">
<EventTableTHead
v-if="width > 768"
:columns="columns"
:filter-state="filterActions.state"
/>
<tbody>
<tr v-if="events.length === 0">
<td :colspan="columns.length" class="px-3 py-8 text-center text-sm">
<slot name="empty-state"> No events found. </slot>
</td>
</tr>
<slot name="rows" :events="events">
<EventTableRow
v-if="width > 768"
v-for="ev in events"
:key="ev.id"
:evt="ev"
:columns="columns"
:filter-actions="filterActions"
:width="width"
/>
<EventTableMobileRow
v-else
v-for="ev in events"
:key="`mobile-${ev.id}`"
:evt="ev"
:columns="columns"
:filter-actions="filterActions"
:width="width"
/>
</slot>
</tbody>
</table>
</div>
<EventTableFooter
:total-events="totalEvents"
:filter-actions="filterActions"
/>
</div>
</template>
<style>
@reference "src/style.css";
.event-table {
tbody tr {
@apply overflow-x-auto border-b border-stone-800 even:bg-stone-950/50 hover:bg-stone-900;
&:last-child {
@apply border-b-0;
}
}
th {
@apply border-b border-stone-700 px-3 py-2 text-left;
}
td {
@apply px-3 py-1.5 text-sm;
}
}
</style>