Files
supabase/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.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

237 lines
8.9 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events'
import { useFlag, useParams } from 'common'
import { AlertCircle, Loader2 } from 'lucide-react'
import Link from 'next/link'
import { useMemo } from 'react'
import { toast } from 'sonner'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormLayout } from 'ui-patterns/form/Layout/FormLayout'
import { getLastUsedAPIKeys, useLastUsedAPIKeysLogQuery } from './DisplayApiSettings.utils'
import Panel from '@/components/ui/Panel'
import { useJwtSecretUpdatingStatusQuery } from '@/data/config/jwt-secret-updating-status-query'
import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
export const DisplayApiSettings = ({
showTitle = true,
showNotice = true,
showLegacyText = true,
}: {
showTitle?: boolean
showNotice?: boolean
showLegacyText?: boolean
}) => {
const { ref: projectRef } = useParams()
const {
data: settings,
isError: isProjectSettingsError,
isPending: isProjectSettingsLoading,
} = useProjectSettingsV2Query({ projectRef })
const {
data,
isError: isJwtSecretUpdateStatusError,
isPending: isJwtSecretUpdateStatusLoading,
} = useJwtSecretUpdatingStatusQuery({ projectRef })
const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus
const { isLoading: isLoadingPermissions, can: canReadAPIKeys } = useAsyncCheckPermissions(
PermissionAction.READ,
'service_api_keys'
)
const isLoading = isProjectSettingsLoading || isLoadingPermissions
const isNotUpdatingJwtSecret =
jwtSecretUpdateStatus === undefined || jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updated
const apiKeys = useMemo(() => settings?.service_api_keys ?? [], [settings])
// api keys should not be empty. However it can be populated with a delay on project creation
const isApiKeysEmpty = apiKeys.length === 0
const showApiKeyLastUsed = useFlag('showApiKeysLastUsed')
const { isLoading: isLoadingLastUsed, logData: lastUsedLogData } = useLastUsedAPIKeysLogQuery({
projectRef: projectRef ?? '',
enabled: showApiKeyLastUsed,
})
const lastUsedAPIKeys = useMemo(() => {
if (
apiKeys.length < 1 ||
!lastUsedLogData ||
lastUsedLogData.length < 1 ||
!showApiKeyLastUsed
) {
return {}
}
try {
return getLastUsedAPIKeys(apiKeys, lastUsedLogData)
} catch (e: any) {
toast.error('Failed to identify when the anon and service_role keys were last used')
console.error(e)
return {}
}
}, [lastUsedLogData, apiKeys, showApiKeyLastUsed])
return (
<Panel
noMargin
title={
showTitle && (
<div className="space-y-3">
<h5 className="text-base">Project API Keys</h5>
<p className="text-sm text-foreground-light">
Your API is secured behind an API gateway which requires an API Key for every request.
<br />
You can use the keys below in the Supabase client libraries.
<br />
</p>
</div>
)
}
>
{isLoading ? (
<div className="flex items-center justify-center py-8 space-x-2">
<Loader2 className="animate-spin" size={16} strokeWidth={1.5} />
<p className="text-sm text-foreground-light">Retrieving API keys</p>
</div>
) : !canReadAPIKeys ? (
<div className="flex items-center py-8 px-8 space-x-2">
<AlertCircle size={16} strokeWidth={1.5} />
<p className="text-sm text-foreground-light">
You don't have permission to view API keys. These keys restricted to users with higher
access levels.
</p>
</div>
) : isProjectSettingsError || isJwtSecretUpdateStatusError ? (
<div className="flex items-center justify-center py-8 space-x-2">
<AlertCircle size={16} strokeWidth={1.5} />
<p className="text-sm text-foreground-light">
{isProjectSettingsError ? 'Failed to retrieve API keys' : 'Failed to update JWT secret'}
</p>
</div>
) : isApiKeysEmpty || isProjectSettingsLoading || isJwtSecretUpdateStatusLoading ? (
<div className="flex items-center justify-center py-8 space-x-2">
<Loader2 className="animate-spin" size={16} strokeWidth={1.5} />
<p className="text-sm text-foreground-light">
{isProjectSettingsLoading || isApiKeysEmpty
? 'Retrieving API keys'
: 'JWT secret is being updated'}
</p>
</div>
) : (
apiKeys.map((x, i: number) => (
<Panel.Content
key={x.api_key}
className={
i >= 1 &&
'border-t border-panel-border-interior-light in-data-[theme*=dark]:border-panel-border-interior-dark'
}
>
<FormLayout
layout="horizontal"
label={
<div className="flex items-center space-x-1">
{x.tags?.split(',').map((x, i: number) => (
<code key={`${x}${i}`} className="text-code-inline">
{x}
</code>
))}
{x.tags === 'service_role' && (
<>
<code className="text-code-inline bg-destructive! text-white! border-destructive!">
secret
</code>
</>
)}
{x.tags === 'anon' && <code className="text-code-inline">public</code>}
</div>
}
description={
x.tags === 'service_role' ? (
<>
This key has the ability to bypass Row Level Security. Never share it publicly.
If leaked, generate a new JWT secret immediately.{' '}
{showLegacyText && (
<span>
Prefer using{' '}
<Link
href={`/project/${projectRef}/settings/api-keys/new`}
className="text-link underline"
>
Secret API keys
</Link>{' '}
instead.
</span>
)}
</>
) : (
<>
This key is safe to use in a browser if you have enabled Row Level Security for
your tables and configured policies.{' '}
{showLegacyText && (
<span>
Prefer using{' '}
<Link
href={`/project/${projectRef}/settings/api-keys/new`}
className="text-link underline"
>
Publishable API keys
</Link>{' '}
instead.
</span>
)}
</>
)
}
>
<Input
readOnly
className="font-mono"
copy={canReadAPIKeys && isNotUpdatingJwtSecret}
reveal={x.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret}
value={
!canReadAPIKeys
? 'You need additional permissions to view API keys'
: jwtSecretUpdateStatus === JwtSecretUpdateStatus.Failed
? 'JWT secret update failed, new API key may have issues'
: jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating
? 'Updating JWT secret...'
: (x?.api_key ?? 'You need additional permissions to view API keys')
}
onChange={() => {}}
/>
</FormLayout>
{showApiKeyLastUsed && (
<div
className="pt-2 text-foreground-lighter w-full text-sm data-[invisible=true]:invisible"
data-invisible={isLoadingLastUsed}
>
{lastUsedAPIKeys[x.api_key]
? `Last request was ${lastUsedAPIKeys[x.api_key]} ago.`
: 'No requests in the past 24 hours.'}
</div>
)}
</Panel.Content>
))
)}
{showNotice ? (
<Panel.Notice
className="border-t"
title="API keys have moved"
badgeLabel="Changelog"
description={`
\`anon\` and \`service_role\` API keys can now be replaced with \`publishable\` and \`secret\` API keys.
`}
href="https://github.com/orgs/supabase/discussions/29260"
buttonText="Read the announcement"
/>
) : null}
</Panel>
)
}