From 6713a8cf5b372bd139f6a5697369c9178c3b8663 Mon Sep 17 00:00:00 2001 From: Jason Staack Date: Wed, 18 Mar 2026 11:16:21 -0500 Subject: [PATCH] feat(audit): make device names clickable in audit log Add device_id to the audit log API response and frontend type, then use DeviceLink to make device hostnames navigable in AuditLogTable. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/routers/audit_logs.py | 5 ++++- frontend/src/components/audit/AuditLogTable.tsx | 11 +++++++++-- frontend/src/lib/api.ts | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/backend/app/routers/audit_logs.py b/backend/app/routers/audit_logs.py index 0370123..f3dd0d6 100644 --- a/backend/app/routers/audit_logs.py +++ b/backend/app/routers/audit_logs.py @@ -125,6 +125,7 @@ class AuditLogItem(BaseModel): action: str resource_type: Optional[str] = None resource_id: Optional[str] = None + device_id: Optional[str] = None device_name: Optional[str] = None details: dict[str, Any] = {} ip_address: Optional[str] = None @@ -193,7 +194,8 @@ async def list_audit_logs( # Shared SELECT columns for data queries _data_columns = text( "a.id, u.email AS user_email, a.action, a.resource_type, " - "a.resource_id, d.hostname AS device_name, a.details, " + "a.resource_id, CAST(a.device_id AS text) AS device_id, " + "d.hostname AS device_name, a.details, " "a.encrypted_details, a.ip_address, a.created_at" ) _data_from = text( @@ -287,6 +289,7 @@ async def list_audit_logs( action=row["action"], resource_type=row["resource_type"], resource_id=row["resource_id"], + device_id=row["device_id"], device_name=row["device_name"], details=details, ip_address=row["ip_address"], diff --git a/frontend/src/components/audit/AuditLogTable.tsx b/frontend/src/components/audit/AuditLogTable.tsx index 44c83fa..32d356f 100644 --- a/frontend/src/components/audit/AuditLogTable.tsx +++ b/frontend/src/components/audit/AuditLogTable.tsx @@ -22,6 +22,7 @@ import { type AuditLogParams, } from '@/lib/api' import { cn } from '@/lib/utils' +import { DeviceLink } from '@/components/ui/DeviceLink' import { EmptyState } from '@/components/ui/empty-state' // Predefined action types for the filter dropdown @@ -253,6 +254,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) { setExpandedId(expandedId === item.id ? null : item.id) @@ -339,11 +341,12 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) { interface AuditLogRowProps { item: AuditLogEntry + tenantId: string isExpanded: boolean onToggle: () => void } -function AuditLogRow({ item, isExpanded, onToggle }: AuditLogRowProps) { +function AuditLogRow({ item, tenantId, isExpanded, onToggle }: AuditLogRowProps) { const hasDetails = item.details && Object.keys(item.details).length > 0 @@ -408,7 +411,11 @@ function AuditLogRow({ item, isExpanded, onToggle }: AuditLogRowProps) { )} - {item.device_name ?? '--'} + {item.device_name && item.device_id ? ( + + {item.device_name} + + ) : (item.device_name ?? '--')} {item.ip_address ?? '--'} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index e06cc92..e0200c6 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -835,6 +835,7 @@ export interface AuditLogEntry { action: string resource_type: string | null resource_id: string | null + device_id: string | null device_name: string | null details: Record ip_address: string | null