Files
fleetforge/apps/web/src/app/audit-log/page.tsx
T

113 lines
3.8 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { api } from "@/lib/api";
import DashboardLayout from "@/components/DashboardLayout";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { ScrollText } from "lucide-react";
interface AuditEvent {
id: string;
action: string;
targetType: string;
targetId: string | null;
result: string;
ipAddress: string | null;
metadata: Record<string, unknown>;
correlationId: string;
createdAt: string;
}
export default function AuditLogPage() {
const [events, setEvents] = useState<AuditEvent[]>([]);
const [total, setTotal] = useState(0);
const [actionFilter, setActionFilter] = useState("");
useEffect(() => {
const params: Record<string, string> = {};
if (actionFilter) params.action = actionFilter;
api.get<AuditEvent[]>("/v1/audit-events", params).then((res) => {
if (res.success && res.data) {
setEvents(res.data);
setTotal(res.meta?.total || 0);
}
});
}, [actionFilter]);
return (
<DashboardLayout>
<div className="mb-8">
<h1 className="text-2xl font-bold tracking-tight">Audit Log</h1>
<p className="text-muted-foreground">{total} event(s) recorded</p>
</div>
<div className="mb-6">
<Input
placeholder="Filter by action..."
value={actionFilter}
onChange={(e) => setActionFilter(e.target.value)}
className="w-64"
/>
</div>
{events.length === 0 ? (
<Card>
<CardContent className="p-12 text-center">
<ScrollText className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">No audit events recorded yet.</p>
</CardContent>
</Card>
) : (
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead>Time</TableHead>
<TableHead>Action</TableHead>
<TableHead>Target</TableHead>
<TableHead>Result</TableHead>
<TableHead>IP</TableHead>
<TableHead>Correlation ID</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{events.map((event) => (
<TableRow key={event.id}>
<TableCell className="text-xs text-muted-foreground">
{new Date(event.createdAt).toLocaleString()}
</TableCell>
<TableCell className="text-sm font-medium">
{event.action}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{event.targetType}
{event.targetId && (
<span className="ml-1">
({event.targetId.substring(0, 8)}...)
</span>
)}
</TableCell>
<TableCell>
<Badge variant={event.result === "success" ? "success" : "destructive"}>
{event.result}
</Badge>
</TableCell>
<TableCell className="text-xs text-muted-foreground">
{event.ipAddress || "\u2014"}
</TableCell>
<TableCell className="text-xs text-muted-foreground font-mono">
{event.correlationId.substring(0, 12)}...
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
)}
</DashboardLayout>
);
}