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) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-18 11:16:21 -05:00
parent 33be7a9522
commit 6713a8cf5b
3 changed files with 14 additions and 3 deletions

View File

@@ -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"],

View File

@@ -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) {
<AuditLogRow
key={item.id}
item={item}
tenantId={tenantId}
isExpanded={expandedId === item.id}
onToggle={() =>
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) {
)}
</td>
<td className="px-3 py-2 text-text-secondary truncate max-w-[120px]">
{item.device_name ?? '--'}
{item.device_name && item.device_id ? (
<DeviceLink tenantId={tenantId} deviceId={item.device_id}>
{item.device_name}
</DeviceLink>
) : (item.device_name ?? '--')}
</td>
<td className="px-3 py-2 text-text-muted font-mono text-xs">
{item.ip_address ?? '--'}

View File

@@ -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<string, unknown>
ip_address: string | null