import React, { useState, useEffect, useCallback } from 'react'; import { X, Loader, AlertCircle, ChevronLeft, ChevronRight, Search } from 'lucide-react'; const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api'; const ACTION_BADGES = { login: { bg: 'bg-green-100', text: 'text-green-800' }, logout: { bg: 'bg-gray-100', text: 'text-gray-800' }, login_failed: { bg: 'bg-red-100', text: 'text-red-800' }, cve_create: { bg: 'bg-blue-100', text: 'text-blue-800' }, cve_update_status: { bg: 'bg-yellow-100', text: 'text-yellow-800' }, document_upload: { bg: 'bg-purple-100', text: 'text-purple-800' }, document_delete: { bg: 'bg-red-100', text: 'text-red-800' }, user_create: { bg: 'bg-blue-100', text: 'text-blue-800' }, user_update: { bg: 'bg-yellow-100', text: 'text-yellow-800' }, user_delete: { bg: 'bg-red-100', text: 'text-red-800' }, cve_edit: { bg: 'bg-orange-100', text: 'text-orange-800' }, cve_delete: { bg: 'bg-red-100', text: 'text-red-800' }, cve_nvd_sync: { bg: 'bg-green-100', text: 'text-green-800' }, }; const ENTITY_TYPES = ['auth', 'cve', 'document', 'user']; export default function AuditLog({ onClose }) { const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [actions, setActions] = useState([]); const [pagination, setPagination] = useState({ page: 1, limit: 25, total: 0, totalPages: 0 }); // Filters const [userFilter, setUserFilter] = useState(''); const [actionFilter, setActionFilter] = useState(''); const [entityTypeFilter, setEntityTypeFilter] = useState(''); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const fetchLogs = useCallback(async (page = 1) => { setLoading(true); setError(null); try { const params = new URLSearchParams({ page, limit: 25 }); if (userFilter) params.append('user', userFilter); if (actionFilter) params.append('action', actionFilter); if (entityTypeFilter) params.append('entityType', entityTypeFilter); if (startDate) params.append('startDate', startDate); if (endDate) params.append('endDate', endDate); const response = await fetch(`${API_BASE}/audit-logs?${params}`, { credentials: 'include' }); if (!response.ok) throw new Error('Failed to fetch audit logs'); const data = await response.json(); setLogs(data.logs); setPagination(data.pagination); } catch (err) { setError(err.message); } finally { setLoading(false); } }, [userFilter, actionFilter, entityTypeFilter, startDate, endDate]); const fetchActions = async () => { try { const response = await fetch(`${API_BASE}/audit-logs/actions`, { credentials: 'include' }); if (response.ok) { const data = await response.json(); setActions(data); } } catch (err) { // Non-critical, ignore } }; useEffect(() => { fetchLogs(1); fetchActions(); }, [fetchLogs]); const formatDate = (dateStr) => { if (!dateStr) return '-'; return new Date(dateStr).toLocaleString(); }; const formatDetails = (details) => { if (!details) return '-'; try { const parsed = typeof details === 'string' ? JSON.parse(details) : details; return Object.entries(parsed) .map(([k, v]) => `${k}: ${v}`) .join(', '); } catch { return details; } }; const getActionBadge = (action) => { const style = ACTION_BADGES[action] || { bg: 'bg-gray-100', text: 'text-gray-800' }; return ( {action} ); }; const handleFilter = (e) => { e.preventDefault(); fetchLogs(1); }; const handleReset = () => { setUserFilter(''); setActionFilter(''); setEntityTypeFilter(''); setStartDate(''); setEndDate(''); }; return (
Track all user actions across the system
Loading audit logs...
{error}
No audit log entries found.
| Time | User | Action | Entity | Details | IP Address |
|---|---|---|---|---|---|
| {formatDate(log.created_at)} | {log.username} | {getActionBadge(log.action)} | {log.entity_type} {log.entity_id && ( {log.entity_id} )} | {formatDetails(log.details)} | {log.ip_address || '-'} |
Showing {((pagination.page - 1) * pagination.limit) + 1} - {Math.min(pagination.page * pagination.limit, pagination.total)} of {pagination.total} entries