mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(web): complete audit log viewer with IP column and all event types
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user