From da8982e0e1a2b6eadcc31a453b15c5be44c95a52 Mon Sep 17 00:00:00 2001 From: brooktewabe Date: Fri, 12 Jun 2026 16:03:39 +0300 Subject: [PATCH] audit logs --- src/pages/activity-log/index.tsx | 145 +++++++++++++++--------- src/services/audit.service.ts | 182 ++++++++++++++++++------------- 2 files changed, 203 insertions(+), 124 deletions(-) diff --git a/src/pages/activity-log/index.tsx b/src/pages/activity-log/index.tsx index 4cc09ab..12a8c63 100644 --- a/src/pages/activity-log/index.tsx +++ b/src/pages/activity-log/index.tsx @@ -7,7 +7,6 @@ import { Search, ChevronLeft, ChevronRight, - Filter, Terminal, } from "lucide-react"; import { auditService, type AuditLog } from "@/services"; @@ -18,26 +17,44 @@ export default function ActivityLogPage() { const [page, setPage] = useState(1); const [limit] = useState(15); const [search, setSearch] = useState(""); + const [filter, setFilter] = useState<"action" | "userId">("action"); const { data: auditData, isLoading } = useQuery({ - queryKey: ["activity-log", page, limit, search], + queryKey: ["activity-log", page, limit, search, filter], queryFn: async () => { - const params: Record = { page, limit }; - if (search) params.search = search; - return await auditService.getAuditLogs(params); + const params: Record = { + page, + limit, + }; + + if (search.trim()) { + params.search = search.trim(); + params.filter = filter; + } + + return auditService.getAuditLogs(params); }, }); const getActionColor = (action: string) => { const act = action.toUpperCase(); - if (act.includes("CREATE")) + + if (act.includes("CREATE")) { return "text-blue-600 bg-blue-50 border-blue-100"; - if (act.includes("UPDATE")) + } + + if (act.includes("UPDATE")) { return "text-emerald-600 bg-emerald-50 border-emerald-100"; - if (act.includes("DELETE")) + } + + if (act.includes("DELETE")) { return "text-rose-600 bg-rose-50 border-rose-100"; - if (act.includes("LOGIN")) + } + + if (act.includes("LOGIN")) { return "text-purple-600 bg-purple-50 border-purple-100"; + } + return "text-slate-600 bg-slate-50 border-slate-100"; }; @@ -48,13 +65,11 @@ export default function ActivityLogPage() {

Activity Log

+

Audit trail of all administrative actions.

-
- {/* View only access: Export button removed */} -
@@ -62,25 +77,36 @@ export default function ActivityLogPage() { System Audit +
-
+ + +
+ setSearch(e.target.value)} + onChange={(e) => { + setPage(1); + setSearch(e.target.value); + }} />
-
+
@@ -89,35 +115,36 @@ export default function ActivityLogPage() { + + - + + {isLoading ? ( - ) : auditData?.data && auditData.data.length > 0 ? ( + ) : auditData?.data?.length ? ( auditData.data.map((log: AuditLog) => ( - - - - )) ) : (
Action - User ID + User - Resource - - IP Address + Detail Timestamp
Synchronizing audit records...
- {log.userId || "SYSTEM"} + + +
+

+ {log.user + ? `${log.user.firstName ?? ""} ${ + log.user.lastName ?? "" + }`.trim() || "Unknown User" + : "SYSTEM"} +

+ +

+ {log.user?.email ?? log.userId} +

+
- {log.resourceType}:{" "} - {log.resourceId.substring(0, 8)}... + + +
+ + {log.detail} +
- {log.ipAddress || "--"} - - {format(new Date(log.timestamp), "MMM dd, HH:mm:ss")} + + + {format( + new Date(log.createdAt), + "MMM dd, yyyy HH:mm:ss", + )}
No activity logs recorded. @@ -158,30 +202,31 @@ export default function ActivityLogPage() {
- {auditData && auditData.totalPages > 1 && ( + + {auditData?.meta && auditData.meta.totalPages > 1 && (

- Page {auditData.page} of {auditData.totalPages} ({auditData.total}{" "} - records) + Page {auditData.meta.page} of {auditData.meta.totalPages} ( + {auditData.meta.total} records)

+
+ @@ -191,4 +236,4 @@ export default function ActivityLogPage() {
); -} +} \ No newline at end of file diff --git a/src/services/audit.service.ts b/src/services/audit.service.ts index 1e359f8..6a20707 100644 --- a/src/services/audit.service.ts +++ b/src/services/audit.service.ts @@ -1,101 +1,135 @@ -import apiClient from './api/client' +import apiClient from "./api/client"; export interface AuditLog { - id: string - userId: string - action: string - resourceType: string - resourceId: string - changes?: Record - ipAddress: string - userAgent: string - timestamp: string + id: string; + action: string; + detail: string; + userId: string | null; + createdAt: string; + + user?: { + id: string; + email: string; + firstName: string | null; + lastName: string | null; + } | null; +} + +export interface AuditLogResponse { + data: AuditLog[]; + + meta: { + total: number; + page: number; + limit: number; + totalPages: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + }; } export interface GetAuditLogsParams { - page?: number - limit?: number - userId?: string - action?: string - resourceType?: string - resourceId?: string - startDate?: string - endDate?: string - search?: string + page?: number; + limit?: number; + + search?: string; + + // new API filter + filter?: "userId" | "action"; + + userId?: string; + action?: string; } export interface AuditStats { - totalActions: number - uniqueUsers: number - topActions: Array<{ action: string; count: number }> - topUsers: Array<{ userId: string; count: number }> + totalActions: number; + uniqueUsers: number; + topActions: Array<{ + action: string; + count: number; + }>; + topUsers: Array<{ + userId: string; + count: number; + }>; } class AuditService { - /** - * Get audit logs with pagination and filters - */ - async getAuditLogs(params?: GetAuditLogsParams): Promise<{ - data: AuditLog[] - total: number - page: number - limit: number - totalPages: number - }> { - const response = await apiClient.get('/admin/audit/logs', { params }) - return response.data + async getAuditLogs( + params?: GetAuditLogsParams, + ): Promise { + const response = await apiClient.get( + "/activity-logs", + { + params, + }, + ); + + return response.data; } - /** - * Get audit log by ID - */ async getAuditLog(id: string): Promise { - const response = await apiClient.get(`/admin/audit/logs/${id}`) - return response.data + const response = await apiClient.get( + `/activity-logs/${id}`, + ); + + return response.data; } - /** - * Get user audit activity - */ - async getUserAuditActivity(userId: string, days: number = 30): Promise { - const response = await apiClient.get(`/admin/audit/users/${userId}`, { - params: { days }, - }) - return response.data + async getUserAuditActivity( + userId: string, + days = 30, + ): Promise { + const response = await apiClient.get( + `/admin/audit/users/${userId}`, + { + params: { days }, + }, + ); + + return response.data; } - /** - * Get resource history - */ - async getResourceHistory(type: string, id: string): Promise { - const response = await apiClient.get(`/admin/audit/resource/${type}/${id}`) - return response.data + async getResourceHistory( + type: string, + id: string, + ): Promise { + const response = await apiClient.get( + `/admin/audit/resource/${type}/${id}`, + ); + + return response.data; } - /** - * Get audit statistics - */ - async getAuditStats(startDate?: string, endDate?: string): Promise { - const response = await apiClient.get('/admin/audit/stats', { - params: { startDate, endDate }, - }) - return response.data + async getAuditStats( + startDate?: string, + endDate?: string, + ): Promise { + const response = await apiClient.get( + "/admin/audit/stats", + { + params: { startDate, endDate }, + }, + ); + + return response.data; } - /** - * Export audit logs - */ async exportAuditLogs(params?: { - format?: 'csv' | 'json' - startDate?: string - endDate?: string + format?: "csv" | "json"; + startDate?: string; + endDate?: string; }): Promise { - const response = await apiClient.get('/admin/audit/export', { - params, - responseType: 'blob', - }) - return response.data + const response = await apiClient.get( + "/admin/audit/export", + { + params, + responseType: "blob", + }, + ); + + return response.data; } } -export const auditService = new AuditService() +export const auditService = new AuditService(); \ No newline at end of file