import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, CardFooter, Form, FormControl, FormField, FormInputGroupInput, InputGroup, InputGroupAddon, InputGroupText, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from 'ui' import { GenericSkeletonLoader, ShimmeringLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' import { ScaffoldSection, ScaffoldSectionTitle } from '@/components/layouts/Scaffold' import AlertError from '@/components/ui/AlertError' import NoPermission from '@/components/ui/NoPermission' import { UpgradeToPro } from '@/components/ui/UpgradeToPro' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation' import { useMaxConnectionsQuery } from '@/data/database/max-connections-query' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { IS_PLATFORM } from '@/lib/constants' const FormSchema = z.object({ API_MAX_REQUEST_DURATION: z.coerce .number() .min(5, 'Must be 5 or larger') .max(30, 'Must be a value no greater than 30'), DB_MAX_POOL_SIZE: z.coerce.number().min(1), DB_MAX_POOL_SIZE_UNIT: z.enum(['percent', 'connections']), }) export const DatabaseFormSchema = z .object({ DB_MAX_POOL_SIZE: z.coerce.number().min(1), DB_MAX_POOL_SIZE_UNIT: z.enum(['percent', 'connections']), }) .superRefine((data, ctx) => { if (data.DB_MAX_POOL_SIZE_UNIT === 'percent') { if (data.DB_MAX_POOL_SIZE < 1 || data.DB_MAX_POOL_SIZE > 100) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['DB_MAX_POOL_SIZE'], message: 'Percentage must be between 1 and 100', }) } } }) export const PerformanceSettingsForm = () => { const { data: project } = useSelectedProjectQuery() const { hasAccess: hasAccessToPerformance, isLoading: isLoadingEntitlement } = useCheckEntitlements('auth.performance_settings') const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) const [isUpdatingRequestDurationForm, setIsUpdatingRequestDurationForm] = useState(false) const [isUpdatingDatabaseForm, setIsUpdatingDatabaseForm] = useState(false) const { data: authConfig, error: authConfigError, isError, isLoading: isLoadingAuthConfig, } = useAuthConfigQuery({ projectRef: project?.ref }) const { data: maxConnData, isLoading: isLoadingMaxConns } = useMaxConnectionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const maxConnectionLimit = maxConnData?.maxConnections ?? 60 const promptUpgrade = IS_PLATFORM && !isLoadingEntitlement && !hasAccessToPerformance const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() const requestDurationForm = useForm({ resolver: zodResolver( z.object({ API_MAX_REQUEST_DURATION: FormSchema.shape.API_MAX_REQUEST_DURATION }) ), defaultValues: { API_MAX_REQUEST_DURATION: 10 }, }) const databaseForm = useForm({ resolver: zodResolver(DatabaseFormSchema), defaultValues: { DB_MAX_POOL_SIZE: 10, DB_MAX_POOL_SIZE_UNIT: 'connections', }, }) const chosenUnit = databaseForm.watch('DB_MAX_POOL_SIZE_UNIT') const onSubmitRequestDurationForm = (values: any) => { if (!project?.ref) return console.error('Project ref is required') if (!hasAccessToPerformance) return setIsUpdatingRequestDurationForm(true) updateAuthConfig( { projectRef: project?.ref, config: values }, { onError: (error) => { toast.error(`Failed to update request duration settings: ${error?.message}`) setIsUpdatingRequestDurationForm(false) }, onSuccess: () => { toast.success('Successfully updated request duration settings') setIsUpdatingRequestDurationForm(false) }, } ) } const onSubmitDatabaseForm = (values: any) => { if (!project?.ref) return console.error('Project ref is required') setIsUpdatingDatabaseForm(true) const config = { DB_MAX_POOL_SIZE: values.DB_MAX_POOL_SIZE, DB_MAX_POOL_SIZE_UNIT: values.DB_MAX_POOL_SIZE_UNIT, } updateAuthConfig( { projectRef: project?.ref, config }, { onError: () => { setIsUpdatingDatabaseForm(false) }, onSuccess: () => { toast.success('Successfully updated connection settings') setIsUpdatingDatabaseForm(false) }, } ) } useEffect(() => { if (authConfig) { if (!isUpdatingRequestDurationForm) { requestDurationForm.reset({ API_MAX_REQUEST_DURATION: authConfig?.API_MAX_REQUEST_DURATION ?? 10, }) } if (!isUpdatingDatabaseForm) { databaseForm.reset({ DB_MAX_POOL_SIZE: authConfig?.DB_MAX_POOL_SIZE !== null ? (authConfig?.DB_MAX_POOL_SIZE ?? 10) : 10, DB_MAX_POOL_SIZE_UNIT: authConfig?.DB_MAX_POOL_SIZE_UNIT !== null ? authConfig?.DB_MAX_POOL_SIZE_UNIT : 'connections', }) } } }, [authConfig, isUpdatingRequestDurationForm, isUpdatingDatabaseForm]) if (isError) { return ( ) } if (!canReadConfig) { return ( ) } if (isLoadingAuthConfig || isLoadingEntitlement) { return ( ) } return ( <> {promptUpgrade && ( )} Request duration
( Requests that exceed this time limit are terminated to help manage server load.

} >
seconds

10+ seconds recommended

)} />
{requestDurationForm.formState.isDirty && ( )}
Connection management
( Choose whether to allocate a percentage or a fixed number of connections to the Auth server. We recommend a percentage, as it scales automatically with your instance size.

} >
)} />
( The maximum number of connections the Auth server can use under peak load.{' '} Connections are not reserved {' '} and are returned to Postgres after a short idle period.

} >
{chosenUnit === 'percent' ? '%' : 'connections'}
{isLoadingMaxConns ? ( ) : (

{chosenUnit === 'percent' ? Math.floor( maxConnectionLimit * (Math.min(100, field.value!) / 100) ).toString() : Math.min(maxConnectionLimit, field.value!)} {' '} / {maxConnectionLimit}

)}
)} />
{databaseForm.formState.isDirty && ( )}
) }