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:
@@ -125,6 +125,7 @@ class AuditLogItem(BaseModel):
|
|||||||
action: str
|
action: str
|
||||||
resource_type: Optional[str] = None
|
resource_type: Optional[str] = None
|
||||||
resource_id: Optional[str] = None
|
resource_id: Optional[str] = None
|
||||||
|
device_id: Optional[str] = None
|
||||||
device_name: Optional[str] = None
|
device_name: Optional[str] = None
|
||||||
details: dict[str, Any] = {}
|
details: dict[str, Any] = {}
|
||||||
ip_address: Optional[str] = None
|
ip_address: Optional[str] = None
|
||||||
@@ -193,7 +194,8 @@ async def list_audit_logs(
|
|||||||
# Shared SELECT columns for data queries
|
# Shared SELECT columns for data queries
|
||||||
_data_columns = text(
|
_data_columns = text(
|
||||||
"a.id, u.email AS user_email, a.action, a.resource_type, "
|
"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"
|
"a.encrypted_details, a.ip_address, a.created_at"
|
||||||
)
|
)
|
||||||
_data_from = text(
|
_data_from = text(
|
||||||
@@ -287,6 +289,7 @@ async def list_audit_logs(
|
|||||||
action=row["action"],
|
action=row["action"],
|
||||||
resource_type=row["resource_type"],
|
resource_type=row["resource_type"],
|
||||||
resource_id=row["resource_id"],
|
resource_id=row["resource_id"],
|
||||||
|
device_id=row["device_id"],
|
||||||
device_name=row["device_name"],
|
device_name=row["device_name"],
|
||||||
details=details,
|
details=details,
|
||||||
ip_address=row["ip_address"],
|
ip_address=row["ip_address"],
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
type AuditLogParams,
|
type AuditLogParams,
|
||||||
} from '@/lib/api'
|
} from '@/lib/api'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { DeviceLink } from '@/components/ui/DeviceLink'
|
||||||
import { EmptyState } from '@/components/ui/empty-state'
|
import { EmptyState } from '@/components/ui/empty-state'
|
||||||
|
|
||||||
// Predefined action types for the filter dropdown
|
// Predefined action types for the filter dropdown
|
||||||
@@ -253,6 +254,7 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
<AuditLogRow
|
<AuditLogRow
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
|
tenantId={tenantId}
|
||||||
isExpanded={expandedId === item.id}
|
isExpanded={expandedId === item.id}
|
||||||
onToggle={() =>
|
onToggle={() =>
|
||||||
setExpandedId(expandedId === item.id ? null : item.id)
|
setExpandedId(expandedId === item.id ? null : item.id)
|
||||||
@@ -339,11 +341,12 @@ export function AuditLogTable({ tenantId }: AuditLogTableProps) {
|
|||||||
|
|
||||||
interface AuditLogRowProps {
|
interface AuditLogRowProps {
|
||||||
item: AuditLogEntry
|
item: AuditLogEntry
|
||||||
|
tenantId: string
|
||||||
isExpanded: boolean
|
isExpanded: boolean
|
||||||
onToggle: () => void
|
onToggle: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function AuditLogRow({ item, isExpanded, onToggle }: AuditLogRowProps) {
|
function AuditLogRow({ item, tenantId, isExpanded, onToggle }: AuditLogRowProps) {
|
||||||
const hasDetails =
|
const hasDetails =
|
||||||
item.details && Object.keys(item.details).length > 0
|
item.details && Object.keys(item.details).length > 0
|
||||||
|
|
||||||
@@ -408,7 +411,11 @@ function AuditLogRow({ item, isExpanded, onToggle }: AuditLogRowProps) {
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-text-secondary truncate max-w-[120px]">
|
<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>
|
||||||
<td className="px-3 py-2 text-text-muted font-mono text-xs">
|
<td className="px-3 py-2 text-text-muted font-mono text-xs">
|
||||||
{item.ip_address ?? '--'}
|
{item.ip_address ?? '--'}
|
||||||
|
|||||||
@@ -835,6 +835,7 @@ export interface AuditLogEntry {
|
|||||||
action: string
|
action: string
|
||||||
resource_type: string | null
|
resource_type: string | null
|
||||||
resource_id: string | null
|
resource_id: string | null
|
||||||
|
device_id: string | null
|
||||||
device_name: string | null
|
device_name: string | null
|
||||||
details: Record<string, unknown>
|
details: Record<string, unknown>
|
||||||
ip_address: string | null
|
ip_address: string | null
|
||||||
|
|||||||
Reference in New Issue
Block a user