feat(web): complete audit log viewer with IP column and all event types

This commit is contained in:
SnapOtter
2026-06-13 16:42:10 +08:00
parent 3016571c2b
commit c6f9a29687
2 changed files with 33 additions and 4 deletions
+5
View File
@@ -12,6 +12,7 @@ export async function auditLogRoutes(app: FastifyInstance): Promise<void> {
page?: string; page?: string;
limit?: string; limit?: string;
action?: string; action?: string;
ip?: string;
from?: string; from?: string;
to?: string; to?: string;
}; };
@@ -30,6 +31,9 @@ export async function auditLogRoutes(app: FastifyInstance): Promise<void> {
if (request.query.action) { if (request.query.action) {
conditions.push(eq(schema.auditLog.action, request.query.action)); conditions.push(eq(schema.auditLog.action, request.query.action));
} }
if (request.query.ip) {
conditions.push(eq(schema.auditLog.ipAddress, request.query.ip));
}
if (request.query.from) { if (request.query.from) {
const fromDate = new Date(request.query.from); const fromDate = new Date(request.query.from);
if (!Number.isNaN(fromDate.getTime())) { if (!Number.isNaN(fromDate.getTime())) {
@@ -68,6 +72,7 @@ export async function auditLogRoutes(app: FastifyInstance): Promise<void> {
targetId: e.targetId, targetId: e.targetId,
details: e.details ?? null, details: e.details ?? null,
ipAddress: e.ipAddress, ipAddress: e.ipAddress,
requestId: e.requestId ?? null,
createdAt: e.createdAt.toISOString(), createdAt: e.createdAt.toISOString(),
})), })),
total: countResult?.count ?? 0, total: countResult?.count ?? 0,
@@ -2571,26 +2571,39 @@ function RolesSection() {
const AUDIT_ACTIONS = [ const AUDIT_ACTIONS = [
"LOGIN_SUCCESS", "LOGIN_SUCCESS",
"LOGIN_FAILED", "LOGIN_FAILED",
"USER_CREATED", "LOGOUT",
"USER_UPDATED",
"USER_DELETED",
"PASSWORD_CHANGED", "PASSWORD_CHANGED",
"PASSWORD_RESET", "PASSWORD_RESET",
"USER_CREATED",
"USER_DELETED",
"USER_UPDATED",
"FILE_UPLOADED",
"FILE_DELETED",
"API_KEY_CREATED", "API_KEY_CREATED",
"API_KEY_DELETED", "API_KEY_DELETED",
"ROLE_CREATED", "ROLE_CREATED",
"ROLE_UPDATED", "ROLE_UPDATED",
"ROLE_DELETED", "ROLE_DELETED",
"SETTINGS_UPDATED", "SETTINGS_UPDATED",
"OIDC_LOGIN_SUCCESS",
"OIDC_USER_CREATED",
"OIDC_USER_LINKED",
"OIDC_LOGIN_FAILED",
"TOOL_EXECUTED",
"BATCH_EXECUTED",
"PIPELINE_EXECUTED",
] as const; ] as const;
interface AuditEntry { interface AuditEntry {
id: string; id: string;
actorId: string | null;
actorUsername: string; actorUsername: string;
action: string; action: string;
targetType: string | null; targetType: string | null;
targetId: string | null; targetId: string | null;
details: Record<string, unknown> | null; details: Record<string, unknown> | null;
ipAddress: string | null;
requestId: string | null;
createdAt: string; createdAt: string;
} }
@@ -2692,6 +2705,11 @@ function AuditLogSection() {
</div> </div>
<div className="flex items-center gap-2 mt-1"> <div className="flex items-center gap-2 mt-1">
<span className="text-sm text-foreground">{entry.actorUsername}</span> <span className="text-sm text-foreground">{entry.actorUsername}</span>
{entry.ipAddress && (
<span className="font-mono text-xs text-muted-foreground tabular-nums">
{entry.ipAddress}
</span>
)}
{entry.targetType && ( {entry.targetType && (
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{entry.targetType} {entry.targetType}
@@ -2720,6 +2738,9 @@ function AuditLogSection() {
<th className="text-start px-3 py-2 font-medium text-muted-foreground"> <th className="text-start px-3 py-2 font-medium text-muted-foreground">
{t.settings.auditLog.tableHeaderUser} {t.settings.auditLog.tableHeaderUser}
</th> </th>
<th className="text-start px-3 py-2 font-medium text-muted-foreground">
IP
</th>
<th className="text-start px-3 py-2 font-medium text-muted-foreground"> <th className="text-start px-3 py-2 font-medium text-muted-foreground">
{t.settings.auditLog.tableHeaderAction} {t.settings.auditLog.tableHeaderAction}
</th> </th>
@@ -2739,6 +2760,9 @@ function AuditLogSection() {
{formatRelativeTime(entry.createdAt)} {formatRelativeTime(entry.createdAt)}
</td> </td>
<td className="px-3 py-2 text-foreground">{entry.actorUsername}</td> <td className="px-3 py-2 text-foreground">{entry.actorUsername}</td>
<td className="px-3 py-2 font-mono text-xs text-muted-foreground tabular-nums whitespace-nowrap">
{entry.ipAddress ?? "---"}
</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
<span className="font-mono text-xs bg-muted px-1.5 py-0.5 rounded"> <span className="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">
{entry.action} {entry.action}
@@ -2752,7 +2776,7 @@ function AuditLogSection() {
</tr> </tr>
{expandedId === entry.id && entry.details && ( {expandedId === entry.id && entry.details && (
<tr className="border-b border-border last:border-0"> <tr className="border-b border-border last:border-0">
<td colSpan={4} className="px-3 py-2 bg-muted/10"> <td colSpan={5} className="px-3 py-2 bg-muted/10">
<pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono overflow-x-auto"> <pre className="text-xs text-muted-foreground whitespace-pre-wrap font-mono overflow-x-auto">
{JSON.stringify(entry.details, null, 2)} {JSON.stringify(entry.details, null, 2)}
</pre> </pre>