mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## Context Follows up from [this PR](https://github.com/supabase/supabase/pull/50799) - updates the Audit Logs UI for organization audit logs. Just differs from Account audit logs slightly with added "Actor" + "Target" column Reuses the same UI components from account audit logs so quite a bit of code clean up 🙂 <img width="1451" height="955" alt="image" src="https://github.com/user-attachments/assets/a1002cee-917f-4d9a-b977-3fab8ecd2d13" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Organization audit logs now use a sortable table with row selection and a resizable details panel. * Actor details show a member’s username when available, otherwise the actor’s email; a dash appears when neither is available. * Logs can be filtered by users and projects, with separate messages for no logs and no matching results. * A refresh control and loading indicators help show audit-log updates. * **Bug Fixes** * Organization project lists stop loading when pagination totals are unavailable. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
112 lines
4.0 KiB
TypeScript
112 lines
4.0 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import { Activity, Clock, Copy, Globe, Hash, User } from 'lucide-react'
|
|
import { Button, cn, copyToClipboard } from 'ui'
|
|
import { TimestampInfo } from 'ui-patterns/TimestampInfo'
|
|
|
|
import { getStatusLevel } from '@/components/interfaces/UnifiedLogs/UnifiedLogs.utils'
|
|
import { CollapsibleDetailSection } from '@/components/ui/DataTable/CollapsibleDetailSection'
|
|
import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
|
|
import { DetailSectionHeader } from '@/components/ui/DataTable/DetailSectionHeader'
|
|
import {
|
|
TIMESTAMP_MICROS_PER_MS,
|
|
type AuditLog,
|
|
} from '@/data/organizations/organization-audit-logs-query'
|
|
|
|
interface AuditLogOverviewProps {
|
|
selectedLog: AuditLog
|
|
}
|
|
|
|
const DetailRow = ({
|
|
label,
|
|
value,
|
|
}: {
|
|
label: string
|
|
value: string | number | undefined | null
|
|
}) => {
|
|
const isEmpty = value === null || value === undefined || value === ''
|
|
|
|
return (
|
|
<div className="flex h-9 items-center justify-between gap-x-10 px-4 pl-[38px]">
|
|
<span className="shrink-0 text-sm text-foreground-lighter">{label}</span>
|
|
<div
|
|
className={cn('flex items-center gap-x-2 min-w-0 flex-1 justify-end', isEmpty && 'pr-2')}
|
|
>
|
|
{isEmpty ? (
|
|
<span className="font-mono text-xs text-foreground-muted">—</span>
|
|
) : (
|
|
<span className="truncate font-mono text-xs text-foreground text-right">{value}</span>
|
|
)}
|
|
{!isEmpty && (
|
|
<Button
|
|
aria-label={`Copy ${label}`}
|
|
variant="text"
|
|
className="px-1"
|
|
icon={<Copy size={12} />}
|
|
onClick={() => copyToClipboard(String(value))}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const AuditLogOverview = ({ selectedLog }: AuditLogOverviewProps) => {
|
|
const hasMetadata = Object.keys(selectedLog.action.metadata ?? {}).length > 0
|
|
|
|
return (
|
|
<div>
|
|
<DetailSectionHeader
|
|
title="Request started"
|
|
icon={Clock}
|
|
className="border-b"
|
|
summary={
|
|
<TimestampInfo
|
|
className="text-xs font-mono"
|
|
utcTimestamp={dayjs(selectedLog.timestamp / TIMESTAMP_MICROS_PER_MS).toISOString()}
|
|
/>
|
|
}
|
|
/>
|
|
<DetailSectionHeader
|
|
title="Request ID"
|
|
icon={Hash}
|
|
className="border-b"
|
|
summary={
|
|
<span className="truncate text-right font-mono text-xs text-foreground">
|
|
{selectedLog.request_id}
|
|
</span>
|
|
}
|
|
/>
|
|
|
|
<CollapsibleDetailSection defaultOpen className="border-b" title="Target" icon={Globe}>
|
|
<DetailRow label="Organization slug" value={selectedLog.organization_slug} />
|
|
<DetailRow label="Project ref" value={selectedLog.project_ref} />
|
|
</CollapsibleDetailSection>
|
|
|
|
<CollapsibleDetailSection defaultOpen className="border-b" title="Actor" icon={User}>
|
|
<DetailRow label="Token type" value={selectedLog.actor.token_type} />
|
|
<DetailRow label="Email" value={selectedLog.actor.email} />
|
|
<DetailRow label="User ID" value={selectedLog.actor.user_id} />
|
|
<DetailRow label="IP address" value={selectedLog.actor.ip} />
|
|
</CollapsibleDetailSection>
|
|
|
|
<CollapsibleDetailSection defaultOpen className="border-b" title="Action" icon={Activity}>
|
|
<DetailRow label="Name" value={selectedLog.action.name} />
|
|
<DetailRow label="Method" value={selectedLog.action.method} />
|
|
<DetailRow label="Route" value={selectedLog.action.route} />
|
|
<div className="flex h-9 items-center justify-between gap-x-10 px-4 pl-[38px]">
|
|
<span className="shrink-0 text-sm text-foreground-lighter">Status</span>
|
|
<DataTableColumnStatusCode
|
|
value={selectedLog.action.status}
|
|
level={getStatusLevel(selectedLog.action.status)}
|
|
className="text-xs"
|
|
/>
|
|
</div>
|
|
<DetailRow
|
|
label="Metadata"
|
|
value={hasMetadata ? JSON.stringify(selectedLog.action.metadata) : undefined}
|
|
/>
|
|
</CollapsibleDetailSection>
|
|
</div>
|
|
)
|
|
}
|