From f748c5dbe43e9bc5702034d985359d0d87b74530 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 22 Oct 2025 12:23:48 -0700 Subject: [PATCH] Basic request log working --- messages/en-US.json | 20 +- .../routers/auditLogs/exportActionAuditLog.ts | 32 +- .../routers/auditLogs/queryActionAuditLog.ts | 14 +- server/private/routers/external.ts | 4 +- .../routers/auditLogs/exportRequstAuditLog.ts | 73 ++++ server/routers/auditLogs/generateCSV.ts | 16 + server/routers/auditLogs/index.ts | 2 + .../routers/auditLogs/queryRequstAuditLog.ts | 165 ++++++++ server/routers/auditLogs/types.ts | 31 ++ server/routers/badger/logRequestAudit.ts | 24 +- server/routers/external.ts | 11 + src/app/[orgId]/settings/logs/action/page.tsx | 6 +- src/app/[orgId]/settings/logs/layout.tsx | 8 +- .../[orgId]/settings/logs/request/page.tsx | 400 ++++++++++++++++++ src/app/navigation.tsx | 2 +- src/components/LogDataTable.tsx | 37 +- 16 files changed, 793 insertions(+), 52 deletions(-) create mode 100644 server/routers/auditLogs/exportRequstAuditLog.ts create mode 100644 server/routers/auditLogs/generateCSV.ts create mode 100644 server/routers/auditLogs/index.ts create mode 100644 server/routers/auditLogs/queryRequstAuditLog.ts create mode 100644 src/app/[orgId]/settings/logs/request/page.tsx diff --git a/messages/en-US.json b/messages/en-US.json index 521c4a39..d1041ea1 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1902,5 +1902,23 @@ "timestamp": "Timestamp", "accessLogs": "Access Logs", "exportCsv": "Export CSV", - "actorId": "Actor ID" + "actorId": "Actor ID", + "allowedByRule": "Allowed by Rule", + "allowedNoAuth": "Allowed No Auth", + "validAccessToken": "Valid Access Token", + "validHeaderAuth": "Valid header auth", + "validPincode": "Valid Pincode", + "validPassword": "Valid Password", + "validEmail": "Valid email", + "validSSO": "Valid SSO", + "resourceBlocked": "Resource Blocked", + "droppedByRule": "Dropped by Rule", + "noSessions": "No Sessions", + "temporaryRequestToken": "Temporary Request Token", + "noMoreAuthMethods": "No More Auth Methods", + "ip": "IP Address", + "reason": "Reason", + "requestLogs": "Request Logs", + "host": "Host", + "location": "Location" } diff --git a/server/private/routers/auditLogs/exportActionAuditLog.ts b/server/private/routers/auditLogs/exportActionAuditLog.ts index de6cf298..4d15dd55 100644 --- a/server/private/routers/auditLogs/exportActionAuditLog.ts +++ b/server/private/routers/auditLogs/exportActionAuditLog.ts @@ -24,24 +24,8 @@ import { fromError } from "zod-validation-error"; import { QueryActionAuditLogResponse } from "@server/routers/auditLogs/types"; import response from "@server/lib/response"; import logger from "@server/logger"; -import { queryAccessAuditLogsParams, queryAccessAuditLogsQuery, querySites } from "./queryActionAuditLog"; - -function generateCSV(data: any[]): string { - if (data.length === 0) { - return "orgId,action,actorType,timestamp,actor\n"; - } - - const headers = Object.keys(data[0]).join(","); - const rows = data.map(row => - Object.values(row).map(value => - typeof value === 'string' && value.includes(',') - ? `"${value.replace(/"/g, '""')}"` - : value - ).join(",") - ); - - return [headers, ...rows].join("\n"); -} +import { queryActionAuditLogsParams, queryActionAuditLogsQuery, querySites } from "./queryActionAuditLog"; +import { generateCSV } from "@server/routers/auditLogs/generateCSV"; registry.registerPath({ method: "get", @@ -49,19 +33,19 @@ registry.registerPath({ description: "Export the action audit log for an organization as CSV", tags: [OpenAPITags.Org], request: { - query: queryAccessAuditLogsQuery, - params: queryAccessAuditLogsParams + query: queryActionAuditLogsQuery, + params: queryActionAuditLogsParams }, responses: {} }); -export async function exportAccessAuditLogs( +export async function exportActionAuditLogs( req: Request, res: Response, next: NextFunction ): Promise { try { - const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query); + const parsedQuery = queryActionAuditLogsQuery.safeParse(req.query); if (!parsedQuery.success) { return next( createHttpError( @@ -72,7 +56,7 @@ export async function exportAccessAuditLogs( } const { timeStart, timeEnd, limit, offset } = parsedQuery.data; - const parsedParams = queryAccessAuditLogsParams.safeParse(req.params); + const parsedParams = queryActionAuditLogsParams.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( @@ -90,7 +74,7 @@ export async function exportAccessAuditLogs( const csvData = generateCSV(log); res.setHeader('Content-Type', 'text/csv'); - res.setHeader('Content-Disposition', `attachment; filename="audit-logs-${orgId}-${Date.now()}.csv"`); + res.setHeader('Content-Disposition', `attachment; filename="action-audit-logs-${orgId}-${Date.now()}.csv"`); return res.send(csvData); } catch (error) { diff --git a/server/private/routers/auditLogs/queryActionAuditLog.ts b/server/private/routers/auditLogs/queryActionAuditLog.ts index 379266ae..dd82e21f 100644 --- a/server/private/routers/auditLogs/queryActionAuditLog.ts +++ b/server/private/routers/auditLogs/queryActionAuditLog.ts @@ -25,7 +25,7 @@ import { QueryActionAuditLogResponse } from "@server/routers/auditLogs/types"; import response from "@server/lib/response"; import logger from "@server/logger"; -export const queryAccessAuditLogsQuery = z.object({ +export const queryActionAuditLogsQuery = z.object({ // iso string just validate its a parseable date timeStart: z .string() @@ -55,7 +55,7 @@ export const queryAccessAuditLogsQuery = z.object({ .pipe(z.number().int().nonnegative()) }); -export const queryAccessAuditLogsParams = z.object({ +export const queryActionAuditLogsParams = z.object({ orgId: z.string() }); @@ -100,19 +100,19 @@ registry.registerPath({ description: "Query the action audit log for an organization", tags: [OpenAPITags.Org], request: { - query: queryAccessAuditLogsQuery, - params: queryAccessAuditLogsParams + query: queryActionAuditLogsQuery, + params: queryActionAuditLogsParams }, responses: {} }); -export async function queryAccessAuditLogs( +export async function queryActionAuditLogs( req: Request, res: Response, next: NextFunction ): Promise { try { - const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query); + const parsedQuery = queryActionAuditLogsQuery.safeParse(req.query); if (!parsedQuery.success) { return next( createHttpError( @@ -123,7 +123,7 @@ export async function queryAccessAuditLogs( } const { timeStart, timeEnd, limit, offset } = parsedQuery.data; - const parsedParams = queryAccessAuditLogsParams.safeParse(req.params); + const parsedParams = queryActionAuditLogsParams.safeParse(req.params); if (!parsedParams.success) { return next( createHttpError( diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index 566c1c55..d89b3294 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -348,11 +348,11 @@ authenticated.post( authenticated.get( "/org/:orgId/logs/action", - logs.queryAccessAuditLogs + logs.queryActionAuditLogs ) authenticated.get( "/org/:orgId/logs/action/export", - logs.exportAccessAuditLogs + logs.exportActionAuditLogs ) \ No newline at end of file diff --git a/server/routers/auditLogs/exportRequstAuditLog.ts b/server/routers/auditLogs/exportRequstAuditLog.ts new file mode 100644 index 00000000..d8bf7916 --- /dev/null +++ b/server/routers/auditLogs/exportRequstAuditLog.ts @@ -0,0 +1,73 @@ +import { db, requestAuditLog } from "@server/db"; +import { registry } from "@server/openApi"; +import { NextFunction } from "express"; +import { Request, Response } from "express"; +import { eq, gt, lt, and, count } from "drizzle-orm"; +import { OpenAPITags } from "@server/openApi"; +import { z } from "zod"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; +import { fromError } from "zod-validation-error"; +import { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types"; +import response from "@server/lib/response"; +import logger from "@server/logger"; +import { queryAccessAuditLogsQuery, queryRequestAuditLogsParams, querySites } from "./queryRequstAuditLog"; +import { generateCSV } from "./generateCSV"; + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/logs/request", + description: "Query the request audit log for an organization", + tags: [OpenAPITags.Org], + request: { + query: queryAccessAuditLogsQuery, + params: queryRequestAuditLogsParams + }, + responses: {} +}); + +export async function exportRequestAuditLogs( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedQuery.error) + ) + ); + } + const { timeStart, timeEnd, limit, offset } = parsedQuery.data; + + const parsedParams = queryRequestAuditLogsParams.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error) + ) + ); + } + const { orgId } = parsedParams.data; + + const baseQuery = querySites(timeStart, timeEnd, orgId); + + const log = await baseQuery.limit(limit).offset(offset); + + const csvData = generateCSV(log); + + res.setHeader('Content-Type', 'text/csv'); + res.setHeader('Content-Disposition', `attachment; filename="request-audit-logs-${orgId}-${Date.now()}.csv"`); + + return res.send(csvData); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/auditLogs/generateCSV.ts b/server/routers/auditLogs/generateCSV.ts new file mode 100644 index 00000000..8a067069 --- /dev/null +++ b/server/routers/auditLogs/generateCSV.ts @@ -0,0 +1,16 @@ +export function generateCSV(data: any[]): string { + if (data.length === 0) { + return "orgId,action,actorType,timestamp,actor\n"; + } + + const headers = Object.keys(data[0]).join(","); + const rows = data.map(row => + Object.values(row).map(value => + typeof value === 'string' && value.includes(',') + ? `"${value.replace(/"/g, '""')}"` + : value + ).join(",") + ); + + return [headers, ...rows].join("\n"); +} \ No newline at end of file diff --git a/server/routers/auditLogs/index.ts b/server/routers/auditLogs/index.ts new file mode 100644 index 00000000..4823831d --- /dev/null +++ b/server/routers/auditLogs/index.ts @@ -0,0 +1,2 @@ +export * from "./queryRequstAuditLog"; +export * from "./exportRequstAuditLog"; \ No newline at end of file diff --git a/server/routers/auditLogs/queryRequstAuditLog.ts b/server/routers/auditLogs/queryRequstAuditLog.ts new file mode 100644 index 00000000..3f5d7f43 --- /dev/null +++ b/server/routers/auditLogs/queryRequstAuditLog.ts @@ -0,0 +1,165 @@ +import { db, requestAuditLog } from "@server/db"; +import { registry } from "@server/openApi"; +import { NextFunction } from "express"; +import { Request, Response } from "express"; +import { eq, gt, lt, and, count } from "drizzle-orm"; +import { OpenAPITags } from "@server/openApi"; +import { z } from "zod"; +import createHttpError from "http-errors"; +import HttpCode from "@server/types/HttpCode"; +import { fromError } from "zod-validation-error"; +import { QueryRequestAuditLogResponse } from "@server/routers/auditLogs/types"; +import response from "@server/lib/response"; +import logger from "@server/logger"; + +export const queryAccessAuditLogsQuery = z.object({ + // iso string just validate its a parseable date + timeStart: z + .string() + .refine((val) => !isNaN(Date.parse(val)), { + message: "timeStart must be a valid ISO date string" + }) + .transform((val) => Math.floor(new Date(val).getTime() / 1000)), + timeEnd: z + .string() + .refine((val) => !isNaN(Date.parse(val)), { + message: "timeEnd must be a valid ISO date string" + }) + .transform((val) => Math.floor(new Date(val).getTime() / 1000)) + .optional() + .default(new Date().toISOString()), + limit: z + .string() + .optional() + .default("1000") + .transform(Number) + .pipe(z.number().int().positive()), + offset: z + .string() + .optional() + .default("0") + .transform(Number) + .pipe(z.number().int().nonnegative()) +}); + +export const queryRequestAuditLogsParams = z.object({ + orgId: z.string() +}); + +export function querySites(timeStart: number, timeEnd: number, orgId: string) { + return db + .select({ + timestamp: requestAuditLog.timestamp, + orgId: requestAuditLog.orgId, + action: requestAuditLog.action, + reason: requestAuditLog.reason, + actorType: requestAuditLog.actorType, + actor: requestAuditLog.actor, + actorId: requestAuditLog.actorId, + resourceId: requestAuditLog.resourceId, + ip: requestAuditLog.ip, + location: requestAuditLog.location, + userAgent: requestAuditLog.userAgent, + metadata: requestAuditLog.metadata, + headers: requestAuditLog.headers, + query: requestAuditLog.query, + originalRequestURL: requestAuditLog.originalRequestURL, + scheme: requestAuditLog.scheme, + host: requestAuditLog.host, + path: requestAuditLog.path, + method: requestAuditLog.method, + tls: requestAuditLog.tls, + }) + .from(requestAuditLog) + .where( + and( + gt(requestAuditLog.timestamp, timeStart), + lt(requestAuditLog.timestamp, timeEnd), + eq(requestAuditLog.orgId, orgId) + ) + ) + .orderBy(requestAuditLog.timestamp); +} + +export function countQuery(timeStart: number, timeEnd: number, orgId: string) { + const countQuery = db + .select({ count: count() }) + .from(requestAuditLog) + .where( + and( + gt(requestAuditLog.timestamp, timeStart), + lt(requestAuditLog.timestamp, timeEnd), + eq(requestAuditLog.orgId, orgId) + ) + ); + return countQuery; +} + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/logs/request", + description: "Query the request audit log for an organization", + tags: [OpenAPITags.Org], + request: { + query: queryAccessAuditLogsQuery, + params: queryRequestAuditLogsParams + }, + responses: {} +}); + +export async function queryRequestAuditLogs( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = queryAccessAuditLogsQuery.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedQuery.error) + ) + ); + } + const { timeStart, timeEnd, limit, offset } = parsedQuery.data; + + const parsedParams = queryRequestAuditLogsParams.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error) + ) + ); + } + const { orgId } = parsedParams.data; + + const baseQuery = querySites(timeStart, timeEnd, orgId); + + const log = await baseQuery.limit(limit).offset(offset); + + const totalCountResult = await countQuery(timeStart, timeEnd, orgId); + const totalCount = totalCountResult[0].count; + + return response(res, { + data: { + log: log, + pagination: { + total: totalCount, + limit, + offset + } + }, + success: true, + error: false, + message: "Action audit logs retrieved successfully", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} diff --git a/server/routers/auditLogs/types.ts b/server/routers/auditLogs/types.ts index 4530de79..f2f6139f 100644 --- a/server/routers/auditLogs/types.ts +++ b/server/routers/auditLogs/types.ts @@ -3,6 +3,7 @@ export type QueryActionAuditLogResponse = { orgId: string; action: string; actorType: string; + actorId: string; timestamp: number; actor: string; }[]; @@ -12,3 +13,33 @@ export type QueryActionAuditLogResponse = { offset: number; }; }; + +export type QueryRequestAuditLogResponse = { + log: { + timestamp: number; + orgId: string; + action: boolean; + reason: number; + actorType: string | null; + actor: string | null; + actorId: string | null; + resourceId: number | null; + ip: string | null; + location: string | null; + userAgent: string | null; + metadata: string | null; + headers: string | null; + query: string | null; + originalRequestURL: string | null; + scheme: string | null; + host: string | null; + path: string | null; + method: string | null; + tls: boolean | null; + }[]; + pagination: { + total: number; + limit: number; + offset: number; + }; +}; \ No newline at end of file diff --git a/server/routers/badger/logRequestAudit.ts b/server/routers/badger/logRequestAudit.ts index 39f1b3cf..2c10264c 100644 --- a/server/routers/badger/logRequestAudit.ts +++ b/server/routers/badger/logRequestAudit.ts @@ -69,7 +69,7 @@ export async function logRequestAudit( if (!orgId) { logger.warn("logRequestAudit: No organization context found"); - orgId = "unknown"; + orgId = "org_7g93l5xu7p61q14"; // return; } @@ -85,6 +85,26 @@ export async function logRequestAudit( metadata = JSON.stringify(metadata); } + const clientIp = body.requestIp + ? (() => { + if (body.requestIp.startsWith("[") && body.requestIp.includes("]")) { + // if brackets are found, extract the IPv6 address from between the brackets + const ipv6Match = body.requestIp.match(/\[(.*?)\]/); + if (ipv6Match) { + return ipv6Match[1]; + } + } + + // ivp4 + // split at last colon + const lastColonIndex = body.requestIp.lastIndexOf(":"); + if (lastColonIndex !== -1) { + return body.requestIp.substring(0, lastColonIndex); + } + return body.requestIp; + })() + : undefined; + await db.insert(requestAuditLog).values({ timestamp, orgId, @@ -104,7 +124,7 @@ export async function logRequestAudit( host: body.host, path: body.path, method: body.method, - ip: body.requestIp, + ip: clientIp, tls: body.tls }); } catch (error) { diff --git a/server/routers/external.ts b/server/routers/external.ts index ecdfe352..2b8c270f 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -14,6 +14,7 @@ import * as supporterKey from "./supporterKey"; import * as accessToken from "./accessToken"; import * as idp from "./idp"; import * as apiKeys from "./apiKeys"; +import * as logs from "./auditLogs"; import HttpCode from "@server/types/HttpCode"; import { verifyAccessTokenAccess, @@ -1180,3 +1181,13 @@ authRouter.delete( }), auth.deleteSecurityKey ); + +authenticated.get( + "/org/:orgId/logs/request", + logs.queryRequestAuditLogs +) + +authenticated.get( + "/org/:orgId/logs/request/export", + logs.exportRequestAuditLogs +) \ No newline at end of file diff --git a/src/app/[orgId]/settings/logs/action/page.tsx b/src/app/[orgId]/settings/logs/action/page.tsx index a681ec64..180815d8 100644 --- a/src/app/[orgId]/settings/logs/action/page.tsx +++ b/src/app/[orgId]/settings/logs/action/page.tsx @@ -190,7 +190,7 @@ export default function GeneralPage() { const epoch = Math.floor(Date.now() / 1000); link.setAttribute( "download", - `access_audit_logs_${orgId}_${epoch}.csv` + `access-audit-logs-${orgId}-${epoch}.csv` ); document.body.appendChild(link); link.click(); @@ -274,8 +274,8 @@ export default function GeneralPage() { ([]); + const [isRefreshing, setIsRefreshing] = useState(false); + const [isExporting, setIsExporting] = useState(false); + + // Pagination state + const [totalCount, setTotalCount] = useState(0); + const [currentPage, setCurrentPage] = useState(0); + const [pageSize, setPageSize] = useState(20); + const [isLoading, setIsLoading] = useState(false); + + // Set default date range to last 24 hours + const getDefaultDateRange = () => { + const now = new Date(); + const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000); + + return { + startDate: { + date: yesterday + }, + endDate: { + date: now + } + }; + }; + + const [dateRange, setDateRange] = useState<{ + startDate: DateTimeValue; + endDate: DateTimeValue; + }>(getDefaultDateRange()); + + // Trigger search with default values on component mount + useEffect(() => { + const defaultRange = getDefaultDateRange(); + queryDateTime(defaultRange.startDate, defaultRange.endDate); + }, [orgId]); // Re-run if orgId changes + + const handleDateRangeChange = ( + startDate: DateTimeValue, + endDate: DateTimeValue + ) => { + setDateRange({ startDate, endDate }); + setCurrentPage(0); // Reset to first page when filtering + queryDateTime(startDate, endDate, 0, pageSize); + }; + + // Handle page changes + const handlePageChange = (newPage: number) => { + setCurrentPage(newPage); + queryDateTime( + dateRange.startDate, + dateRange.endDate, + newPage, + pageSize + ); + }; + + // Handle page size changes + const handlePageSizeChange = (newPageSize: number) => { + setPageSize(newPageSize); + setCurrentPage(0); // Reset to first page when changing page size + queryDateTime(dateRange.startDate, dateRange.endDate, 0, newPageSize); + }; + + const queryDateTime = async ( + startDate: DateTimeValue, + endDate: DateTimeValue, + page: number = currentPage, + size: number = pageSize + ) => { + console.log("Date range changed:", { startDate, endDate, page, size }); + setIsLoading(true); + + try { + // Convert the date/time values to API parameters + let params: any = { + limit: size, + offset: page * size + }; + + if (startDate?.date) { + const startDateTime = new Date(startDate.date); + if (startDate.time) { + const [hours, minutes, seconds] = startDate.time + .split(":") + .map(Number); + startDateTime.setHours(hours, minutes, seconds || 0); + } + params.timeStart = startDateTime.toISOString(); + } + + if (endDate?.date) { + const endDateTime = new Date(endDate.date); + if (endDate.time) { + const [hours, minutes, seconds] = endDate.time + .split(":") + .map(Number); + endDateTime.setHours(hours, minutes, seconds || 0); + } else { + // If no time is specified, set to NOW + const now = new Date(); + endDateTime.setHours( + now.getHours(), + now.getMinutes(), + now.getSeconds(), + now.getMilliseconds() + ); + } + params.timeEnd = endDateTime.toISOString(); + } + + const res = await api.get(`/org/${orgId}/logs/request`, { params }); + if (res.status === 200) { + setRows(res.data.data.log || []); + setTotalCount(res.data.data.pagination?.total || 0); + console.log("Fetched logs:", res.data); + } + } catch (error) { + toast({ + title: t("error"), + description: t("Failed to filter logs"), + variant: "destructive" + }); + } finally { + setIsLoading(false); + } + }; + + const refreshData = async () => { + console.log("Data refreshed"); + setIsRefreshing(true); + try { + // Refresh data with current date range and pagination + await queryDateTime( + dateRange.startDate, + dateRange.endDate, + currentPage, + pageSize + ); + } catch (error) { + toast({ + title: t("error"), + description: t("refreshError"), + variant: "destructive" + }); + } finally { + setIsRefreshing(false); + } + }; + + const exportData = async () => { + try { + setIsExporting(true); + const response = await api.get( + `/org/${orgId}/logs/request/export`, + { + responseType: "blob", + params: { + timeStart: dateRange.startDate?.date + ? new Date(dateRange.startDate.date).toISOString() + : undefined, + timeEnd: dateRange.endDate?.date + ? new Date(dateRange.endDate.date).toISOString() + : undefined + } + } + ); + + // Create a URL for the blob and trigger a download + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement("a"); + link.href = url; + const epoch = Math.floor(Date.now() / 1000); + link.setAttribute( + "download", + `request-audit-logs-${orgId}-${epoch}.csv` + ); + document.body.appendChild(link); + link.click(); + link.parentNode?.removeChild(link); + setIsExporting(false); + } catch (error) { + toast({ + title: t("error"), + description: t("exportError"), + variant: "destructive" + }); + } + }; + + // 100 - Allowed by Rule + // 101 - Allowed No Auth + // 102 - Valid Access Token + // 103 - Valid header auth + // 104 - Valid Pincode + // 105 - Valid Password + // 106 - Valid email + // 107 - Valid SSO + + // 201 - Resource Not Found + // 202 - Resource Blocked + // 203 - Dropped by Rule + // 204 - No Sessions + // 205 - Temporary Request Token + // 299 - No More Auth Methods + + const reasonMap: any = { + 100: t("allowedByRule"), + 101: t("allowedNoAuth"), + 102: t("validAccessToken"), + 103: t("validHeaderAuth"), + 104: t("validPincode"), + 105: t("validPassword"), + 106: t("validEmail"), + 107: t("validSSO"), + 201: t("resourceNotFound"), + 202: t("resourceBlocked"), + 203: t("droppedByRule"), + 204: t("noSessions"), + 205: t("temporaryRequestToken"), + 299: t("noMoreAuthMethods") + }; + + // resourceId: integer("resourceId"), + // userAgent: text("userAgent"), + // metadata: text("details"), + // headers: text("headers"), // JSON blob + // query: text("query"), // JSON blob + // originalRequestURL: text("originalRequestURL"), + // scheme: text("scheme"), + + const columns: ColumnDef[] = [ + { + accessorKey: "timestamp", + header: ({ column }) => { + return t("timestamp"); + }, + cell: ({ row }) => { + return ( +
+ {new Date( + row.original.timestamp * 1000 + ).toLocaleString()} +
+ ); + } + }, + + { + accessorKey: "ip", + header: ({ column }) => { + return t("ip"); + } + }, + + { + accessorKey: "location", + header: ({ column }) => { + return t("location"); + }, + cell: ({ row }) => { + return ( + + {row.original.location ? ( + + ({row.original.location}) + + ) : ( + + - + + )} + + ); + } + }, + + { + accessorKey: "host", + header: ({ column }) => { + return t("host"); + }, + cell: ({ row }) => { + return ( + + {row.original.tls ? ( + + ) : ( + + )} + {row.original.host} + + ); + } + }, + { + accessorKey: "path", + header: ({ column }) => { + return t("path"); + } + }, + + // { + // accessorKey: "scheme", + // header: ({ column }) => { + // return t("scheme"); + // }, + // }, + { + accessorKey: "method", + header: ({ column }) => { + return t("method"); + } + }, + { + accessorKey: "reason", + header: ({ column }) => { + return t("reason"); + }, + cell: ({ row }) => { + return ( + + {reasonMap[row.original.reason]} + + ); + } + }, + { + accessorKey: "actor", + header: ({ column }) => { + return t("actor"); + }, + cell: ({ row }) => { + return ( + + {row.original.actorType == "user" ? ( + + ) : ( + + )} + {row.original.actor} + + ); + } + } + ]; + + return ( + <> + + + ); +} diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 4b8cfe58..02d90f1e 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -141,7 +141,7 @@ export const orgNavSections = ( : []), { title: "sidebarLogs", - href: "/{orgId}/settings/logs/action", + href: "/{orgId}/settings/logs/request", icon: }, { diff --git a/src/components/LogDataTable.tsx b/src/components/LogDataTable.tsx index 3de4dc19..2c1a33b1 100644 --- a/src/components/LogDataTable.tsx +++ b/src/components/LogDataTable.tsx @@ -360,15 +360,36 @@ export function LogDataTable({ data-state={ row.getIsSelected() && "selected" } + className="text-xs" // made smaller > - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} + {row.getVisibleCells().map((cell) => { + const originalRow = + row.original as any; + const actionValue = + originalRow?.action; + let className = ""; + + if ( + typeof actionValue === "boolean" + ) { + className = actionValue + ? "bg-green-100 dark:bg-green-900" + : "bg-red-100 dark:bg-red-900"; + } + + return ( + + {flexRender( + cell.column.columnDef + .cell, + cell.getContext() + )} + + ); + })} )) ) : (