--- import AdminLayout from "@/layouts/AdminLayout.astro"; import { getDatabase, schema } from "@/db"; import { desc, like, eq, or, sql } from "drizzle-orm"; import type { NodePgDatabase } from "drizzle-orm/node-postgres"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { FileText, User, Trash2, Shield, GitBranch, Settings, Search, ChevronLeft, ChevronRight, ExternalLink, } from "lucide-react"; const user = Astro.locals.user; // Only admins can access if (!user?.isAdmin) { return Astro.redirect("/"); } const db = getDatabase() as NodePgDatabase; const searchQuery = Astro.url.searchParams.get("q") || ""; const actionFilter = Astro.url.searchParams.get("action") || ""; const page = parseInt(Astro.url.searchParams.get("page") || "1"); const limit = 50; const offset = (page - 1) * limit; // Build query conditions let whereConditions: any[] = []; if (searchQuery) { whereConditions.push( or( like(schema.auditLogs.action, `%${searchQuery}%`), like(schema.auditLogs.targetName, `%${searchQuery}%`), like(schema.auditLogs.actorId, `%${searchQuery}%`), ), ); } if (actionFilter) { whereConditions.push(like(schema.auditLogs.action, `%${actionFilter}%`)); } const whereClause = whereConditions.length > 0 ? whereConditions.reduce((acc, curr) => acc && curr) : undefined; // Fetch audit logs const [logs, totalResult] = await Promise.all([ db.query.auditLogs.findMany({ where: whereClause, orderBy: [desc(schema.auditLogs.createdAt)], limit, offset, with: { user: { columns: { id: true, username: true, avatarUrl: true }, }, repository: { columns: { id: true, name: true }, }, }, }), db .select({ count: sql`count(*)` }) .from(schema.auditLogs) .where(whereClause) .limit(1), ]); const total = Number(totalResult[0]?.count) || 0; const totalPages = Math.ceil(total / limit); // Action categories for icons function getActionIcon(action: string) { if (action.includes("delete")) return Trash2; if (action.includes("user") || action.includes("login")) return User; if (action.includes("admin") || action.includes("ban")) return Shield; if (action.includes("repo") || action.includes("push")) return GitBranch; if (action.includes("setting")) return Settings; return FileText; } function getActionColor(action: string) { if (action.includes("delete")) return "text-red-500 bg-red-500/10"; if (action.includes("create")) return "text-green-500 bg-green-500/10"; if (action.includes("update") || action.includes("change")) return "text-blue-500 bg-blue-500/10"; if (action.includes("ban")) return "text-orange-500 bg-orange-500/10"; if (action.includes("admin")) return "text-purple-500 bg-purple-500/10"; return "text-muted-foreground bg-muted"; } function timeAgo(date: string) { const seconds = Math.floor((Date.now() - new Date(date).getTime()) / 1000); if (seconds < 60) return `${seconds}s ago`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`; return new Date(date).toLocaleDateString(); } ---

Audit Log

Track all critical system actions

← Back to Dashboard

Total Events

{total}

Today

{ logs.filter( (l: typeof logs[number]) => new Date(l.createdAt).toDateString() === new Date().toDateString(), ).length }

Events { logs.length === 0 ? (

No audit events found

) : (
{logs.map((log) => { const ActionIcon = getActionIcon(log.action); const colorClass = getActionColor(log.action); const data = log.data ? JSON.parse(log.data) : null; return (
{log.action} {log.targetName && ( on )} {log.targetName && ( {log.targetName} )}
{log.user && (
{log.user.avatarUrl ? ( ) : ( )} @{log.user.username}
)} {log.actorIp && IP: {log.actorIp}} {timeAgo(log.createdAt.toISOString())}
{data && (
View Details
                              {JSON.stringify(data, null, 2)}
                            
)}
{log.repository && ( {log.repository.name} )}
); })}
) } {/* Pagination */} { totalPages > 1 && (
Showing {offset + 1} - {Math.min(offset + limit, total)} of{" "} {total}
{page > 1 && ( )} Page {page} of {totalPages} {page < totalPages && ( )}
) }