Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,23 @@ export const APIKeyRow = ({
</div>
</TableCell>

<TableCell className="flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger className="px-1 focus-visible:outline-none" asChild>
<Button
type="text"
size="tiny"
icon={
<MoreVertical size="14" className="text-foreground-light hover:text-foreground" />
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="max-w-40" align="end">
<APIKeyDeleteDialog apiKey={apiKey} lastSeen={lastSeen} />
</DropdownMenuContent>
</DropdownMenu>
<TableCell className="py-2">
<div className="flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger className="px-1 focus-visible:outline-none" asChild>
<Button
type="text"
size="tiny"
icon={
<MoreVertical size="14" className="text-foreground-light hover:text-foreground" />
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent className="max-w-40" align="end">
<APIKeyDeleteDialog apiKey={apiKey} lastSeen={lastSeen} />
</DropdownMenuContent>
</DropdownMenu>
</div>
</TableCell>
</MotionTableRow>
)
Expand Down
6 changes: 3 additions & 3 deletions apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function ApiKeyPill({
const {
data,
error,
isLoading,
refetch: refetchApiKey,
} = useAPIKeyIdQuery(
{
Expand Down Expand Up @@ -116,9 +117,7 @@ export function ApiKeyPill({
'w-[100px] sm:w-[140px] md:w-[180px] lg:w-[340px] gap-0 font-mono rounded-full',
isSecret ? 'overflow-hidden' : '',
show ? 'ring-1 ring-foreground-lighter ring-opacity-50' : 'ring-0 ring-opacity-0',
'transition-all',
'cursor-text',
'relative'
'transition-all cursor-text relative'
)}
style={{ userSelect: 'all' }}
>
Expand All @@ -139,6 +138,7 @@ export function ApiKeyPill({
<Button
type="outline"
className="rounded-full px-2 pointer-events-auto"
loading={show && isLoading}
icon={show ? <EyeOff strokeWidth={2} /> : <Eye strokeWidth={2} />}
onClick={onSubmitToggle}
disabled={isRestricted}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Button,
Dialog,
DialogContent,
Expand All @@ -12,18 +16,13 @@ import {
DialogSectionSeparator,
DialogTitle,
DialogTrigger,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
Form_Shadcn_,
Input_Shadcn_,
Alert,
Alert_Shadcn_,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
import { toast } from 'sonner'

import { useParams } from 'common'
import { useAPIKeyCreateMutation } from 'data/api-keys/api-key-create-mutation'
Expand Down Expand Up @@ -85,7 +84,7 @@ const CreateSecretAPIKeyDialog = () => {
<Dialog open={visible} onOpenChange={onClose}>
<DialogTrigger asChild>
<Button type="default" className="mt-2" icon={<Plus />}>
Add new secret key
New secret key
</Button>
</DialogTrigger>
<DialogContent>
Expand Down
139 changes: 46 additions & 93 deletions apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import dayjs from 'dayjs'
import { ReactNode, useMemo, useRef } from 'react'
import { useMemo, useRef } from 'react'

import { useParams } from 'common'
import AlertError from 'components/ui/AlertError'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { APIKeysData, useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import useLogsQuery from 'hooks/analytics/useLogsQuery'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { Card, CardContent, EyeOffIcon, Skeleton, cn } from 'ui'
import { Card, EyeOffIcon } from 'ui'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Expand Down Expand Up @@ -75,106 +75,59 @@ export const SecretAPIKeys = () => {

const empty = secretApiKeys?.length === 0 && !isLoadingApiKeys && !isLoadingPermissions

const RowLoading = () => (
<TableRow>
<TableCell>
<Skeleton className="max-w-12 h-4 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="max-w-60 h-4 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="max-w-60 h-4 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="w-2 h-4 rounded-full" />
</TableCell>
</TableRow>
)

const TableContainer = ({ children, className }: { children: ReactNode; className?: string }) => (
return (
<div className="pb-30">
<FormHeader
title="Secret keys"
description="These API keys allow privileged access to your project's APIs. Use in servers, functions, workers or other backend components of your application."
actions={<CreateSecretAPIKeyDialog />}
/>
<Card className={cn('w-full overflow-hidden', !empty && 'bg-surface-100', className)}>
<CardContent className="p-0">
<Table className="p-5 table-auto">
<TableHeader>
<TableRow className={cn('bg-200', empty && 'hidden')}>
<TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2">
Name
</TableHead>
<TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2 pr-0">
API Key
</TableHead>

<TableHead className="text-left font-mono uppercase text-xs text-foreground-lighter h-auto py-2 hidden lg:table-cell">
Last Seen
</TableHead>
<TableHead className="text-right font-mono uppercase text-xs text-foreground-lighter h-auto py-2" />
{isLoadingApiKeys || isLoadingPermissions ? (
<GenericSkeletonLoader />
) : !canReadAPIKeys ? (
<Card>
<div className="!rounded-b-md overflow-hidden py-12 flex flex-col gap-1 items-center justify-center">
<EyeOffIcon />
<p className="text-sm text-foreground">
You do not have permission to read API Secret Keys
</p>
<p className="text-foreground-light">
Contact your organization owner/admin to request access.
</p>
</div>
</Card>
) : isErrorApiKeys ? (
<AlertError error={error} subject="Failed to load secret API keys" />
) : empty ? (
<Card>
<div className="!rounded-b-md overflow-hidden py-12 flex flex-col gap-1 items-center justify-center">
<p className="text-sm text-foreground">No secret API keys found</p>
<p className="text-sm text-foreground-light">
Your project is not accessible via secret keys—there are no active secret keys
created.
</p>
</div>
</Card>
) : (
<Card className="bg-surface-100">
<Table>
<TableHeader>
<TableRow className="bg-200">
<TableHead>Name</TableHead>
<TableHead>API Key</TableHead>
<TableHead className="hidden lg:table-cell">Last Seen</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody className="">{children}</TableBody>
<TableBody>
{secretApiKeys.map((apiKey) => (
<APIKeyRow key={apiKey.id} apiKey={apiKey} lastSeen={lastSeen[apiKey.hash]} />
))}
</TableBody>
</Table>
</CardContent>
</Card>
</Card>
)}
</div>
)

if (isLoadingApiKeys || isLoadingPermissions) {
return (
<TableContainer>
<RowLoading />
<RowLoading />
</TableContainer>
)
}

if (!canReadAPIKeys) {
return (
<TableContainer>
<div className="!rounded-b-md overflow-hidden py-12 flex flex-col gap-1 items-center justify-center">
<EyeOffIcon />
<p className="text-sm text-foreground">
You do not have permission to read API Secret Keys
</p>
<p className="text-foreground-light">
Contact your organization owner/admin to request access.
</p>
</div>
</TableContainer>
)
}

if (isErrorApiKeys) {
return (
<TableContainer className="border-0">
<AlertError error={error} subject="Failed to load secret API keys" />
</TableContainer>
)
}

if (empty) {
return (
<TableContainer>
<div className="!rounded-b-md overflow-hidden py-12 flex flex-col gap-1 items-center justify-center">
<p className="text-sm text-foreground">No secret API keys found</p>
<p className="text-sm text-foreground-light">
Your project is not accessible via secret keys—there are no active secret keys created.
</p>
</div>
</TableContainer>
)
}

return (
<TableContainer>
{secretApiKeys.map((apiKey) => (
<APIKeyRow key={apiKey.id} apiKey={apiKey} lastSeen={lastSeen[apiKey.hash]} />
))}
</TableContainer>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const AnalyticBucketDetails = ({ bucket }: { bucket: Bucket }) => {
.find((w) => w.name === snakeCase(`${bucket.name}_fdw`))
}, [data, bucket.name])

const extensionState = useIcebergWrapperExtension()
const { state: extensionState } = useIcebergWrapperExtension()

const integration = INTEGRATIONS.find((i) => i.id === 'iceberg_wrapper' && i.type === 'wrapper')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const useIcebergWrapperExtension = () => {

if (!integration || integration.type !== 'wrapper') {
// This should never happen
return 'not-found'
return { extension: undefined, state: 'not-found' }
}

const wrapperMeta = integration.meta
Expand All @@ -23,13 +23,13 @@ export const useIcebergWrapperExtension = () => {
const hasRequiredVersion =
(wrappersExtension?.installed_version ?? '') >= (wrapperMeta?.minimumExtensionVersion ?? '')

const state = isExtensionsLoading
const state: 'loading' | 'installed' | 'needs-upgrade' | 'not-installed' = isExtensionsLoading
? 'loading'
: isWrappersExtensionInstalled
? hasRequiredVersion
? 'installed'
: 'needs-upgrade'
: ('not-installed' as const)
: 'not-installed'

return state
return { extension: wrappersExtension, state }
}
Loading
Loading