Files
Website/src/components/moderator/pages/logs/AuditLog.svelte

192 lines
7.8 KiB
Svelte

<script lang="ts">
import { createSvelteTable, FlexRender } from "@components/ui/data-table";
import { columns } from "./columns";
import { getCoreRowModel, getPaginationRowModel, type PaginationState } from "@tanstack/table-core";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
import { auditLog } from "@components/repo/auditlog";
import { now, ZonedDateTime } from "@internationalized/date";
import { AuditLogEntrySchema, type AuditLogEntry } from "@components/types/auditlog";
import { Button } from "@components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
import { Input } from "@components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
import { Check } from "lucide-svelte";
import { cn } from "@components/utils";
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
import PlayerSelector from "@components/ui/PlayerSelector.svelte";
let debounceTimer: NodeJS.Timeout;
const debounce = <T,>(value: T, func: (value: T) => void) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
func(value);
}, 300);
};
let actionText = $state("");
let serverText = $state("");
let fullText = $state("");
let actors = $state<number[]>([]);
let actionTypes = $state<string[]>([]);
let timeGreater = $state<ZonedDateTime>(now("Europe/Berlin").subtract({ months: 1 }));
let timeLess = $state<ZonedDateTime>(now("Europe/Berlin"));
let serverOwner = $state<number[]>([]);
let velocity = $state(false);
let sorting = $state("DESC");
let pagination = $state<PaginationState>({
pageIndex: 0,
pageSize: 25,
});
let data = $state<AuditLogEntry[]>([]);
let rows = $state(0);
$effect(() => {
$auditLog
.get(
actionText || undefined,
serverText || undefined,
fullText || undefined,
actors.length > 0 ? actors : undefined,
actionTypes.length > 0 ? actionTypes : undefined,
timeGreater ? timeGreater.toDate().getTime() : undefined,
timeLess ? timeLess.toDate().getTime() : undefined,
serverOwner.length > 0 ? serverOwner : undefined,
velocity,
pagination.pageIndex,
pagination.pageSize,
sorting || undefined
)
.then((res) => {
data = res.entries;
rows = res.rows;
});
});
const table = createSvelteTable({
get data() {
return data;
},
columns,
state: {
get pagination() {
return pagination;
},
},
onPaginationChange: (updater) => {
if (typeof updater === "function") {
pagination = updater(pagination);
} else {
pagination = updater;
}
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
manualPagination: true,
get rowCount() {
return rows;
},
});
let playerSearch = $state("");
let ownerSearch = $state("");
</script>
<div class="p-4">
<div class="rounded border mb-4 p-2 flex lg:flex-row flex-col">
<Input
class="w-48 mr-2"
placeholder="Suchen..."
value={fullText}
onchange={(e) =>
debounce(e.currentTarget.value, (v) => {
fullText = v;
})}
oninput={(e) =>
debounce(e.currentTarget.value, (v) => {
fullText = v;
})}
/>
<Select type="multiple" value={actionTypes} onValueChange={(e) => (actionTypes = e)}>
<SelectTrigger class="w-48 mr-2" placeholder="Aktionstypen auswählen...">Aktionstypen ({actionTypes.length})</SelectTrigger>
<SelectContent>
{#each ["JOIN", "LEAVE", "COMMAND", "SENSITIVE_COMMAND", "CHAT", "GUI_OPEN", "GUI_CLOSE", "GUI_CLICK"] as option}
<SelectItem value={option}>{option}</SelectItem>
{/each}
</SelectContent>
</Select>
<div class="mr-2">
<PlayerSelector bind:value={actors} multiple placeholder="Spieler Filter" />
</div>
<div class="mr-2">
<PlayerSelector bind:value={serverOwner} multiple placeholder="Server Owner" />
</div>
<div class="mr-2">
<DateTimePicker bind:value={timeGreater} />
</div>
<div class="mr-2">
<DateTimePicker bind:value={timeLess} />
</div>
<Select type="single" value={sorting} onValueChange={(e) => (sorting = e)}>
<SelectTrigger class="w-48 mr-2">{sorting === "ASC" ? "Aufsteigend" : "Absteigend"}</SelectTrigger>
<SelectContent>
<SelectItem value="ASC">Aufsteigend</SelectItem>
<SelectItem value="DESC">Absteigend</SelectItem>
</SelectContent>
</Select>
</div>
<div class="rounded border">
<Table>
<TableHeader>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<TableRow>
{#each headerGroup.headers as header (header.id)}
<TableHead colspan={header.colSpan}>
{#if !header.isPlaceholder}
<FlexRender content={header.column.columnDef.header} context={header.getContext()} />
{/if}
</TableHead>
{/each}
</TableRow>
{/each}
</TableHeader>
<TableBody>
{#each table.getRowModel().rows as row (row.id)}
<TableRow>
{#each row.getVisibleCells() as cell (cell.id)}
<TableCell class="p-2 align-top">
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
</TableRow>
{:else}
<TableRow>
<TableCell colspan={columns.length} class="h-24 text-center">Keine Einträge gefunden.</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
</div>
<div class="flex items-center justify-end space-x-2 py-4">
<div>
<Select type="single" value={pagination.pageSize.toString()} onValueChange={(e) => (pagination = { pageSize: +e, pageIndex: 0 })}>
<SelectTrigger>{pagination.pageSize}</SelectTrigger>
<SelectContent>
<SelectItem value="5">5</SelectItem>
<SelectItem value="10">10</SelectItem>
<SelectItem value="25">25</SelectItem>
<SelectItem value="50">50</SelectItem>
<SelectItem value="100">100</SelectItem>
<SelectItem value="200">200</SelectItem>
</SelectContent>
</Select>
</div>
<Button variant="outline" size="sm" onclick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>Previous</Button>
<Button variant="outline" size="sm" onclick={() => table.nextPage()} disabled={!table.getCanNextPage()}>Next</Button>
</div>
</div>