Files
supabase/apps/studio/components/interfaces/Settings/Logs/LogSelection.tsx
Ivan VasilovandJordi Enric 56de26fe22 chore: Migrate the monorepo to use Tailwind v4 (#45318)
This PR migrates the whole monorepo to use Tailwind v4:
- Removed `@tailwindcss/container-queries` plugin since it's included by
default in v4,
- Bump all instances of Tailwind to v4. Made minimal changes to the
shared config to remove non-supported features (`alpha` mentions),
- Migrate all apps to be compatible with v4 configs,
- Fix the `typography.css` import in 3 apps,
- Add missing rules which were included by default in v3,
- Run `pnpm dlx @tailwindcss/upgrade` on all apps, which renames a lot
of classes
- Rename all misnamed classes according to
https://tailwindcss.com/docs/upgrade-guide#renamed-utilities in all
apps.

---------

Co-authored-by: Jordi Enric <jordi.err@gmail.com>
2026-04-30 10:53:24 +00:00

189 lines
6.2 KiB
TypeScript

import { Check, Copy, MousePointerClick, X } from 'lucide-react'
import { useEffect, useState } from 'react'
import {
Button,
cn,
copyToClipboard,
Tabs_Shadcn_,
TabsContent_Shadcn_,
TabsList_Shadcn_,
TabsTrigger_Shadcn_,
} from 'ui'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import type { LogData, QueryType } from './Logs.types'
import { apiKey, role as extractRole, jwtAPIKey } from './Logs.utils'
import DefaultPreviewSelectionRenderer from './LogSelectionRenderers/DefaultPreviewSelectionRenderer'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
export interface LogSelectionProps {
log?: LogData
onClose: () => void
queryType?: QueryType
projectRef: string
isLoading: boolean
error?: string | object
}
const LogSelection = ({ log, onClose, queryType, isLoading, error }: LogSelectionProps) => {
const [showCopied, setShowCopied] = useState(false)
useEffect(() => {
if (!showCopied) return
const timer = setTimeout(() => setShowCopied(false), 2000)
return () => clearTimeout(timer)
}, [showCopied])
const LogDetails = () => {
if (error) return <LogErrorState error={error} />
if (!log) return <LogDetailEmptyState />
switch (queryType) {
case 'api':
const status = log?.metadata?.[0]?.response?.[0]?.status_code
const method = log?.metadata?.[0]?.request?.[0]?.method
const path = log?.metadata?.[0]?.request?.[0]?.path
const search = log?.metadata?.[0]?.request?.[0]?.search
const user_agent = log?.metadata?.[0]?.request?.[0]?.headers[0].user_agent
const error_code = log?.metadata?.[0]?.response?.[0]?.headers?.[0]?.x_sb_error_code
const apikey = jwtAPIKey(log?.metadata) ?? apiKey(log?.metadata)
const role = extractRole(log?.metadata)
const { id, metadata, timestamp, event_message, ...rest } = log
const apiLog = {
id,
status,
method,
path,
search,
user_agent,
timestamp,
event_message,
metadata,
...(apikey ? { apikey } : null),
...(error_code ? { error_code } : null),
...(role ? { role } : null),
...rest,
}
return <DefaultPreviewSelectionRenderer log={apiLog} />
case 'database':
const hint = log?.metadata?.[0]?.parsed?.[0]?.hint
const detail = log?.metadata?.[0]?.parsed?.[0]?.detail
const query = log?.metadata?.[0]?.parsed?.[0]?.query
const postgresLog = {
...(hint && { hint }),
...(detail && { detail }),
...(query && { query }),
...log,
}
return <DefaultPreviewSelectionRenderer log={postgresLog} />
default:
return <DefaultPreviewSelectionRenderer log={log} />
}
}
return (
<div className="relative flex h-full grow flex-col overflow-y-scroll bg-surface-100 border-t">
<div className="relative grow flex flex-col h-full">
<Tabs_Shadcn_ defaultValue="details" className="flex flex-col h-full">
<TabsList_Shadcn_ className="px-2 pt-2 relative">
<TabsTrigger_Shadcn_ className="px-3" value="details">
Details
</TabsTrigger_Shadcn_>
<TabsTrigger_Shadcn_ disabled={!log} className="px-3" value="raw">
Raw
</TabsTrigger_Shadcn_>
<div className="*:px-1.5 *:text-foreground-lighter ml-auto flex gap-1 absolute right-2 top-2">
<ButtonTooltip
disabled={!log || isLoading}
type="text"
tooltip={{
content: {
side: 'left',
text: isLoading ? 'Loading log...' : 'Copy as JSON',
},
}}
icon={showCopied ? <Check /> : <Copy />}
onClick={() => {
setShowCopied(true)
copyToClipboard(JSON.stringify(log, null, 2))
}}
/>
<Button type="text" onClick={onClose}>
<X size={14} strokeWidth={2} />
</Button>
</div>
</TabsList_Shadcn_>
<div className="flex-1 h-full">
{isLoading ? (
<div className="p-4">
<GenericSkeletonLoader />
</div>
) : (
<>
<TabsContent_Shadcn_ className="space-y-6 h-full" value="details">
<LogDetails />
</TabsContent_Shadcn_>
<TabsContent_Shadcn_ value="raw">
<CodeBlock
hideLineNumbers
language="json"
className="prose w-full pt-0 max-w-full border-none"
>
{JSON.stringify(log, null, 2)}
</CodeBlock>
</TabsContent_Shadcn_>
</>
)}
</div>
</Tabs_Shadcn_>
</div>
</div>
)
}
export default LogSelection
function LogDetailEmptyState({
title = 'Select an Event',
message = 'Select an Event to view the complete JSON payload',
}: {
title?: string
message?: string
}) {
return (
<div
className={cn(
'flex h-full w-full flex-col items-center justify-center gap-2 overflow-y-scroll text-center transition-all px-4'
)}
>
<div
className={cn(
'flex w-full max-w-sm flex-col items-center justify-center gap-6 text-center transition-all delay-300 duration-500'
)}
>
<div className="relative flex h-4 w-32 items-center rounded-sm border border-control px-2">
<div className="h-0.5 w-2/3 rounded-full bg-surface-300"></div>
<div className="absolute right-1 -bottom-4">
<MousePointerClick size="24" strokeWidth={1} />
</div>
</div>
<div className="flex flex-col gap-1">
<h3 className="text-sm text-foreground">{title}</h3>
<p className="text-xs text-foreground-lighter">{message}</p>
</div>
</div>
</div>
)
}
function LogErrorState({ error }: { error?: string | object }) {
return <pre>{JSON.stringify(error, null, 2)}</pre>
}