mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
## Problem After the Tailwind v4 upgrade, the 3xx/4xx HTTP status badge in the Logs explorer rendered with a saturated yellow background and washed-out text, because `bg-amber-300`/`text-amber-1100` no longer resolve as they did under v3. ## Fix Switch the 3xx/4xx case in `ResponseCodeFormatter` to the existing `bg-warning/10 text-warning` semantic tokens, matching the pattern used by the shared `Badge` warning variant. ## Before <img width="798" height="722" alt="CleanShot 2026-05-05 at 12 13 23@2x" src="https://github.com/user-attachments/assets/a2d37f37-4260-4ec6-bf1c-ff96b6f51be0" /> ## After <img width="642" height="530" alt="CleanShot 2026-05-05 at 12 12 55@2x" src="https://github.com/user-attachments/assets/7c82aee2-0d40-4213-8533-14ffb04fb5de" /> ## How to test - Run `pnpm dev:studio` - Open any project's Logs explorer (API, Edge Functions, Auth, etc.) - Find a row with a 3xx or 4xx status code - Expected: faint yellow background with a darker amber/yellow text, consistent with other warning badges in the app - Confirm 2xx (brand) and 5xx (red) badges still render as before <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Style** * Updated the visual styling of response codes in the logs display for improved consistency. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
253 lines
6.5 KiB
TypeScript
253 lines
6.5 KiB
TypeScript
/*
|
|
* Response Code
|
|
*
|
|
* for http response codes
|
|
*/
|
|
|
|
import dayjs from 'dayjs'
|
|
import { AlertCircle, Info } from 'lucide-react'
|
|
import React from 'react'
|
|
import { cn } from 'ui'
|
|
|
|
import { isUnixMicro, unixMicroToIsoTimestamp } from './Logs.utils'
|
|
import CopyButton from '@/components/ui/CopyButton'
|
|
|
|
export const RowLayout: React.FC<React.PropsWithChildren> = ({ children }) => (
|
|
<div className="flex h-full w-full items-center gap-4">{children}</div>
|
|
)
|
|
// renders a timestamp (either unix microsecond or iso timestamp)
|
|
export const SelectionDetailedTimestampRow = ({ value }: { value: string | number }) => (
|
|
<SelectionDetailedRow
|
|
label="Timestamp"
|
|
value={isUnixMicro(value) ? unixMicroToIsoTimestamp(value) : String(value)}
|
|
/>
|
|
)
|
|
export const SelectionDetailedRow = ({
|
|
label,
|
|
value,
|
|
valueRender,
|
|
}: {
|
|
label: string
|
|
value: string
|
|
valueRender?: React.ReactNode
|
|
}) => {
|
|
return (
|
|
<div className="group flex items-center gap-2 flex-wrap">
|
|
<span className="text-foreground-lighter text-sm col-span-3 whitespace-pre-wrap">
|
|
{label}
|
|
</span>
|
|
<span
|
|
title={value}
|
|
className="truncate font-mono text-foreground text-sm whitespace-pre-wrap break-all"
|
|
>
|
|
{valueRender ?? value}
|
|
</span>
|
|
<CopyButton
|
|
iconOnly
|
|
text={value}
|
|
className="group-hover:opacity-100 opacity-0 p-0 h-6 w-6"
|
|
type="text"
|
|
title="Copy to clipboard"
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// used for column renderers
|
|
export const TextFormatter: React.FC<{ className?: string; value: string }> = ({
|
|
value,
|
|
className,
|
|
}) => (
|
|
<span className={cn('font-mono text-xs truncate select-text cursor-text', className)}>
|
|
{value}
|
|
</span>
|
|
)
|
|
|
|
export const ResponseCodeFormatter = ({ value }: { value: string }) => {
|
|
if (!value) {
|
|
return (
|
|
<div>
|
|
<label className="text-xs text-border-stronger">No data</label>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const ResponseCodeItem = ({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: React.ReactNode
|
|
className?: string
|
|
}) => (
|
|
<div className="flex h-full items-center">
|
|
<div
|
|
className={cn(
|
|
`relative flex h-6 items-center rounded-md justify-center px-2 py-1 text-center`,
|
|
className
|
|
)}
|
|
>
|
|
<label className="block font-mono text-sm">{children}</label>
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
const split = value.toString().split('')[0]
|
|
|
|
switch (split) {
|
|
// 2XX || 1XX responses
|
|
case '1':
|
|
return <ResponseCodeItem>{value}</ResponseCodeItem>
|
|
case '2':
|
|
return <ResponseCodeItem className="bg-surface-100 text-brand">{value}</ResponseCodeItem>
|
|
// 5XX responses
|
|
case '5':
|
|
return <ResponseCodeItem className="bg-red-300 text-red-1100">{value}</ResponseCodeItem>
|
|
|
|
// 4XX || 3XX responses
|
|
case '4':
|
|
case '3':
|
|
return <ResponseCodeItem className="bg-warning/10 text-warning">{value}</ResponseCodeItem>
|
|
|
|
// All other responses
|
|
default:
|
|
return (
|
|
<ResponseCodeItem className="bg-surface-100 text-foreground-lighter">
|
|
{value}
|
|
</ResponseCodeItem>
|
|
)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Response Code
|
|
*
|
|
* for http response codes
|
|
*/
|
|
|
|
export const SeverityFormatter = ({
|
|
value,
|
|
uppercase = true,
|
|
}: {
|
|
value: string
|
|
uppercase?: boolean
|
|
}) => {
|
|
if (!value) {
|
|
return (
|
|
<div>
|
|
<label className="text-xs text-border-stronger">No data</label>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const uppercasedValue = value.toUpperCase()
|
|
const text = uppercase ? uppercasedValue : value
|
|
const Layout: React.FC<React.PropsWithChildren<{ className?: string }>> = ({
|
|
className,
|
|
children,
|
|
}) => <div className={`w-24 flex items-center h-full ${className}`}>{children}</div>
|
|
|
|
switch (uppercasedValue) {
|
|
case 'UNCAUGHTEXCEPTION':
|
|
case 'PANIC':
|
|
case 'FATAL':
|
|
case 'ERROR':
|
|
return (
|
|
<Layout className="gap-1">
|
|
<div className=" p-0.5 rounded-sm text-red-900!">
|
|
<AlertCircle size={14} strokeWidth={2} />
|
|
</div>
|
|
<span className="text-red-900! block! titlecase">{text}</span>
|
|
</Layout>
|
|
)
|
|
break
|
|
|
|
case 'INFO':
|
|
|
|
case 'DEBUG':
|
|
return (
|
|
<Layout className="gap-1">
|
|
<div className=" p-0.5 rounded-sm text-blue-900!">
|
|
<AlertCircle size={14} strokeWidth={2} />
|
|
</div>
|
|
<span className="text-blue-900! block! titlecase">{text}</span>
|
|
</Layout>
|
|
)
|
|
break
|
|
|
|
case 'LOG':
|
|
return (
|
|
<Layout className="gap-1">
|
|
<div className=" p-0.5 rounded-sm text-blue-900!">
|
|
<Info size={14} strokeWidth={2} />
|
|
</div>
|
|
<span className="text-blue-900! block! titlecase">{text}</span>
|
|
</Layout>
|
|
)
|
|
break
|
|
|
|
case 'WARNING':
|
|
return (
|
|
<Layout className="gap-1">
|
|
<div className=" p-0.5 rounded-sm text-amber-900!">
|
|
<AlertCircle size={14} strokeWidth={2} />
|
|
</div>
|
|
<span className="text-amber-900! block! titlecase">{text}</span>
|
|
</Layout>
|
|
)
|
|
break
|
|
|
|
// All other responses
|
|
default:
|
|
return (
|
|
<Layout>
|
|
<div className="relative rounded-sm px-2 py-1 text-center h-6 flex justify-center items-center bg-surface-100">
|
|
<label className="block font-mono text-sm text-foreground-lighter">{text}</label>
|
|
</div>
|
|
</Layout>
|
|
)
|
|
break
|
|
}
|
|
}
|
|
|
|
export const timestampLocalFormatter = (value: string | number) => {
|
|
const timestamp = isUnixMicro(value) ? unixMicroToIsoTimestamp(value) : value
|
|
return dayjs(timestamp).format('DD MMM HH:mm:ss')
|
|
}
|
|
|
|
/*
|
|
* JSON Syntax Highlighter
|
|
*
|
|
* for http response codes
|
|
*/
|
|
|
|
export function jsonSyntaxHighlight(input: Object) {
|
|
let json: string = JSON.stringify(input, null, 2)
|
|
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
|
|
const newJson = json.replace(
|
|
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
|
|
|
function (match) {
|
|
var cls = 'number text-tomato-900'
|
|
if (/^"/.test(match)) {
|
|
if (/:$/.test(match)) {
|
|
cls = 'key text-foreground'
|
|
} else {
|
|
cls = 'string text-brand-600'
|
|
}
|
|
} else if (/true|false/.test(match)) {
|
|
cls = 'boolean text-blue-900'
|
|
} else if (/null/.test(match)) {
|
|
cls = 'null text-amber-1100'
|
|
}
|
|
return '<span class="' + cls + '">' + match + '</span>'
|
|
}
|
|
)
|
|
|
|
const jsonWithLineWraps = newJson.split(`\n`).map((x) => {
|
|
return `<span class="line text-xs">${x}</span>`
|
|
})
|
|
|
|
return jsonWithLineWraps.join('\n')
|
|
}
|