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 (
10+ seconds recommended