This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api, AuditEvent } from "@/lib/api";
|
||||
import { Card } from "@/components/ui";
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from "@/components/ui";
|
||||
|
||||
const EVENT_LABELS: Record<string, string> = {
|
||||
"server.created": "Server Created",
|
||||
"server.deleted": "Server Deleted",
|
||||
"server.offline": "Server Offline",
|
||||
"key.uploaded": "Key Uploaded",
|
||||
"key.deleted": "Key Deleted",
|
||||
"key.assigned": "Key Assigned",
|
||||
"key.revoked": "Key Revoked",
|
||||
"key.generation_dispatched": "Key Generation",
|
||||
"agent.update_dispatched": "Agent Updated",
|
||||
"updates.applied": "Updates Applied",
|
||||
"settings.updated": "Settings Updated",
|
||||
};
|
||||
|
||||
const EVENT_COLOURS: Record<string, string> = {
|
||||
"server.offline": "text-danger",
|
||||
"server.deleted": "text-danger",
|
||||
"key.deleted": "text-danger",
|
||||
"key.revoked": "text-warning",
|
||||
"server.created": "text-success",
|
||||
"key.uploaded": "text-success",
|
||||
"key.assigned": "text-success",
|
||||
};
|
||||
|
||||
function formatDate(dateStr: string) {
|
||||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
|
||||
function EventTypeBadge({ type }: { type: string }) {
|
||||
const label = EVENT_LABELS[type] ?? type;
|
||||
const colour = EVENT_COLOURS[type] ?? "text-text-secondary";
|
||||
return (
|
||||
<span className={`font-mono text-xs font-medium ${colour}`}>{label}</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AuditPage() {
|
||||
const { data: events, isLoading, error } = useQuery({
|
||||
queryKey: ["audit"],
|
||||
queryFn: () => api.listAuditEvents(200),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-text-primary">Audit Log</h1>
|
||||
<p className="mt-1 text-sm text-text-secondary">
|
||||
All administrative actions and server status changes
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card padding={false}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-accent" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="py-20 text-center text-danger">Failed to load audit log.</div>
|
||||
) : events && events.length > 0 ? (
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Time</Th>
|
||||
<Th>Event</Th>
|
||||
<Th>Actor</Th>
|
||||
<Th>Details</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{events.map((e: AuditEvent) => (
|
||||
<Tr key={e.id}>
|
||||
<Td>
|
||||
<span className="whitespace-nowrap font-mono text-xs text-text-secondary">
|
||||
{formatDate(e.created_at)}
|
||||
</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<EventTypeBadge type={e.event_type} />
|
||||
</Td>
|
||||
<Td>
|
||||
<span className="text-sm text-text-primary">{e.actor}</span>
|
||||
</Td>
|
||||
<Td>
|
||||
<span className="text-sm text-text-secondary">{e.details}</span>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="py-20 text-center">
|
||||
<p className="text-text-secondary text-sm">No audit events recorded yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user