Files
supabase/apps/studio/components/interfaces/AuditLogs/AuditLogDetailsPanel.tsx
Joshen Lim e3fec137fe Joshenlim/fe 4466 audit logs update organization logs as well (#50935)
## 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 -->
2026-09-29 17:41:54 +08:00

90 lines
3.0 KiB
TypeScript

import { Check, Copy, X } from 'lucide-react'
import { useState } from 'react'
import {
Button,
copyToClipboard,
FloatingPlate,
ResizableHandle,
ResizablePanel,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { AuditLogOverview } from './AuditLogOverview'
import type { AuditLog } from '@/data/organizations/organization-audit-logs-query'
interface AuditLogDetailsPanelProps {
selectedLog: AuditLog
onClose: () => void
}
export const AuditLogDetailsPanel = ({ selectedLog, onClose }: AuditLogDetailsPanelProps) => {
const [copied, setCopied] = useState(false)
const json = JSON.stringify(selectedLog, null, 2)
return (
<>
<ResizableHandle withHandle />
<ResizablePanel id="audit-log-details-panel" defaultSize="33%" minSize={320} maxSize="50%">
<Tabs defaultValue="overview" className="flex h-full w-full flex-col">
<div className="flex items-center justify-between px-4 border-b border-border h-[40px]">
<TabsList className="flex h-auto gap-x-4 rounded-none border-none!">
<TabsTrigger value="overview" className="border-b py-3 font-mono text-xs uppercase">
Overview
</TabsTrigger>
<TabsTrigger value="raw-json" className="border-b py-3 font-mono text-xs uppercase">
Raw JSON
</TabsTrigger>
</TabsList>
<Button
variant="text"
className="px-1"
icon={<X />}
onClick={onClose}
aria-label="Close"
/>
</div>
<TabsContent value="overview" className="mt-0 grow overflow-auto py-2">
<AuditLogOverview selectedLog={selectedLog} />
</TabsContent>
<TabsContent
value="raw-json"
className="mt-0 grow overflow-auto bg-surface-100/50 relative"
>
<div className="sticky top-2 z-10 flex justify-end px-2 -mb-9 pointer-events-none">
<FloatingPlate className="pointer-events-auto">
<Button
size="tiny"
className="px-1.5"
icon={copied ? <Check size={12} /> : <Copy size={12} />}
onClick={() => {
copyToClipboard(json, () => {
setCopied(true)
setTimeout(() => setCopied(false), 1000)
})
}}
>
{copied ? 'Copied' : ''}
</Button>
</FloatingPlate>
</div>
<CodeBlock
language="json"
hideCopy
wrapperClassName="!overflow-visible bg-surface-100/50 [&_pre]:!bg-surface-100/50"
className="rounded-none border-none !overflow-x-visible [&_code]:!leading-tight [&_pre]:!leading-tight"
>
{json}
</CodeBlock>
</TabsContent>
</Tabs>
</ResizablePanel>
</>
)
}