import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { AlertTitle } from '@ui/components/shadcn/ui/alert' import { useParams } from 'common' import Link from 'next/link' import { useEffect, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { Alert, AlertDescription, Button, Card, CardContent, CardFooter, Form, FormControl, FormField, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, WarningIcon, } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageContainer } from 'ui-patterns/PageContainer' import { PageSection, PageSectionAside, PageSectionContent, PageSectionDescription, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import * as z from 'zod' import { CreateCredentialModal } from './CreateCredentialModal' import { RevokeCredentialModal } from './RevokeCredentialModal' import { StorageCredItem } from './StorageCredItem' import { getConnectionURL } from './StorageSettings.utils' import AlertError from '@/components/ui/AlertError' import { DocsButton } from '@/components/ui/DocsButton' import NoPermission from '@/components/ui/NoPermission' import { useProjectSettingsV2Query } from '@/data/config/project-settings-v2-query' import { useProjectStorageConfigQuery } from '@/data/config/project-storage-config-query' import { useProjectStorageConfigUpdateUpdateMutation } from '@/data/config/project-storage-config-update-mutation' import { useStorageCredentialsQuery } from '@/data/storage/s3-access-key-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsProjectActive, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' export const S3Connection = () => { const { ref: projectRef } = useParams() const isProjectActive = useIsProjectActive() const { data: project, isPending: projectIsLoading } = useSelectedProjectQuery() const [openCreateCred, setOpenCreateCred] = useState(false) const [openDeleteDialog, setOpenDeleteDialog] = useState(false) const [deleteCred, setDeleteCred] = useState<{ id: string; description: string }>() const { can: canReadS3Credentials, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_READ, '*' ) const { can: canUpdateStorageSettings } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_WRITE, '*' ) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: config, error: configError, isSuccess: isSuccessStorageConfig, isError: isErrorStorageConfig, } = useProjectStorageConfigQuery({ projectRef }) const { data: storageCreds, isPending: isLoadingStorageCreds } = useStorageCredentialsQuery( { projectRef }, { enabled: canReadS3Credentials } ) const { mutate: updateStorageConfig, isPending: isUpdating } = useProjectStorageConfigUpdateUpdateMutation({ onSuccess: (_, vars) => { if (vars.features?.s3Protocol) { form.reset({ s3ConnectionEnabled: vars.features.s3Protocol.enabled }) } toast.success('Successfully updated storage settings') }, }) const FormSchema = z.object({ s3ConnectionEnabled: z.boolean() }) const form = useForm>({ resolver: zodResolver(FormSchema as any), defaultValues: { s3ConnectionEnabled: false }, }) const protocol = settings?.app_config?.protocol ?? 'https' const endpoint = settings?.app_config?.storage_endpoint || settings?.app_config?.endpoint const hasStorageCreds = storageCreds?.data && storageCreds.data.length > 0 const s3connectionUrl = getConnectionURL(projectRef ?? '', protocol, endpoint) const onSubmit: SubmitHandler> = async (data) => { if (!projectRef) return console.error('Project ref is required') if (!config) return console.error('Storage config is required') updateStorageConfig({ projectRef, features: { ...config?.features, s3Protocol: { enabled: data.s3ConnectionEnabled }, }, }) } useEffect(() => { form.reset({ s3ConnectionEnabled: config?.features.s3Protocol.enabled }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccessStorageConfig]) return ( <> Connection Connect to your bucket using any S3-compatible service via the S3 protocol {isErrorStorageConfig && ( )}
{projectIsLoading ? ( ) : isProjectActive ? ( ( )} /> {!isLoadingPermissions && !canUpdateStorageSettings && (

You need additional permissions to update storage settings

)} {form.formState.isDirty && ( )}
) : ( Project is paused To connect to your S3 bucket, you need to restore your project.
)}
Access keys Manage your access keys for this project {projectIsLoading || isLoadingPermissions ? ( ) : !canReadS3Credentials ? ( ) : !isProjectActive ? ( Can't fetch S3 access keys To fetch your S3 access keys, you need to restore your project. ) : ( <> {isLoadingStorageCreds ? ( ) : ( Name Key ID Created at {hasStorageCreds ? ( storageCreds.data?.map((cred) => ( { setDeleteCred(cred) setOpenDeleteDialog(true) }} /> )) ) : (

No access keys created

There are no access keys associated with your project yet

)}
)} )}
{ setOpenDeleteDialog(false) setDeleteCred(undefined) }} /> ) }