import { ExternalLink } from 'lucide-react' import Link from 'next/link' import { useEffect, useState } from 'react' import toast from 'react-hot-toast' import { useParams } from 'common' import { useEnablePhysicalBackupsMutation } from 'data/database/enable-physical-backups-mutation' import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { Region, useReadReplicaSetUpMutation } from 'data/read-replicas/replica-setup-mutation' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { AWS_REGIONS, AWS_REGIONS_DEFAULT, AWS_REGIONS_KEYS, BASE_PATH } from 'lib/constants' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Listbox, SidePanel, } from 'ui' import { WarningIcon } from 'ui-patterns/Icons/StatusIcons' import { AVAILABLE_REPLICA_REGIONS, AWS_REGIONS_VALUES } from './InstanceConfiguration.constants' // [Joshen] FYI this is purely for AWS only, need to update to support Fly eventually interface DeployNewReplicaPanelProps { visible: boolean selectedDefaultRegion?: AWS_REGIONS_KEYS onSuccess: () => void onClose: () => void } const DeployNewReplicaPanel = ({ visible, selectedDefaultRegion, onSuccess, onClose, }: DeployNewReplicaPanelProps) => { const { ref: projectRef } = useParams() const project = useSelectedProject() const org = useSelectedOrganization() const [refetchInterval, setRefetchInterval] = useState(false) const { data } = useReadReplicasQuery({ projectRef }) const { data: addons, isSuccess } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug }) useProjectDetailQuery( { ref: projectRef }, { refetchInterval, refetchOnWindowFocus: false, onSuccess: (data) => { if (data.is_physical_backups_enabled) setRefetchInterval(false) }, } ) const { mutate: enablePhysicalBackups, isLoading: isEnabling } = useEnablePhysicalBackupsMutation( { onSuccess: () => { toast.success( 'Physical backups are currently being enabled, please check back in a few minutes!' ) setRefetchInterval(5000) }, } ) const { mutate: setUpReplica, isLoading: isSettingUp } = useReadReplicaSetUpMutation({ onSuccess: () => { const region = AVAILABLE_REPLICA_REGIONS.find((r) => r.key === selectedRegion)?.name toast.success(`Spinning up new replica in ${region ?? ' Unknown'}...`) onSuccess() onClose() }, }) const currentPgVersion = Number( (project?.dbVersion ?? '').split('supabase-postgres-')[1]?.split('.')[0] ) const reachedMaxReplicas = (data ?? []).filter((db) => db.identifier !== projectRef).length >= 2 const isFreePlan = subscription?.plan.id === 'free' const isAWSProvider = project?.cloud_provider === 'AWS' const isWalgEnabled = project?.is_physical_backups_enabled const currentComputeAddon = addons?.selected_addons.find( (addon) => addon.type === 'compute_instance' ) const isMinimallyOnSmallCompute = currentComputeAddon?.variant.identifier !== undefined && currentComputeAddon?.variant.identifier !== 'ci_micro' const canDeployReplica = !reachedMaxReplicas && currentPgVersion >= 15 && isAWSProvider && !isFreePlan && isWalgEnabled && currentComputeAddon !== undefined const computeAddons = addons?.available_addons.find((addon) => addon.type === 'compute_instance')?.variants ?? [] // Opting for useState temporarily as Listbox doesn't seem to work with react-hook-form yet const [defaultRegion] = Object.entries(AWS_REGIONS).find( ([_, name]) => name === AWS_REGIONS_DEFAULT ) ?? ['ap-southeast-1'] // Will be following the primary's instance size for the time being const defaultCompute = addons?.selected_addons.find((addon) => addon.type === 'compute_instance')?.variant .identifier ?? 'ci_micro' const [selectedRegion, setSelectedRegion] = useState(defaultRegion) const [selectedCompute, setSelectedCompute] = useState(defaultCompute) const selectedComputeMeta = computeAddons.find((addon) => addon.identifier === selectedCompute) const availableRegions = process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' ? AVAILABLE_REPLICA_REGIONS.filter((x) => x.key === 'SOUTHEAST_ASIA') : AVAILABLE_REPLICA_REGIONS const onSubmit = async () => { const regionKey = AWS_REGIONS_VALUES[selectedRegion] if (!projectRef) return console.error('Project is required') if (!regionKey) return toast.error('Unable to deploy replica: Unsupported region selected') const primary = data?.find((db) => db.identifier === projectRef) setUpReplica({ projectRef, region: regionKey as Region, size: primary?.size ?? 't4g.small' }) } useEffect(() => { if (visible && isSuccess) { if (selectedDefaultRegion !== undefined) { setSelectedRegion(selectedDefaultRegion) } else if (defaultRegion) { setSelectedRegion(defaultRegion) } if (defaultCompute !== undefined) setSelectedCompute(defaultCompute) } }, [visible, isSuccess]) return ( onSubmit()} confirmText="Deploy replica" // [Joshen] Refer to EnablePhysicalBackupsModal as to why this is commented out for now // customFooter={ //
// // {isMinimallyOnSmallCompute && !isWalgEnabled ? ( // // ) : ( // // )} //
// } > {!isAWSProvider ? ( Read replicas are only supported for projects provisioned via AWS Projects provisioned by other cloud providers currently will not be able to use read replicas ) : currentPgVersion < 15 ? ( Read replicas can only be deployed with projects on Postgres version 15 and above If you'd like to use read replicas, please contact us via support ) : ( <> {!isMinimallyOnSmallCompute && ( Project required to at least be on a Small compute This is to ensure that read replicas can keep up with the primary databases' activities.
)} {isMinimallyOnSmallCompute && !isWalgEnabled && ( {refetchInterval !== false ? 'Physical backups are currently being enabled' : 'Physical backups are required to deploy replicas'} {refetchInterval === false && ( Physical backups are used under the hood to spin up read replicas for your project. )} {refetchInterval !== false ? 'This warning will go away once physical backups have been enabled - check back in a few minutes!' : 'Enabling physical backups will take a few minutes, after which you will be able to deploy read replicas.'} {refetchInterval !== false ? ( You may start deploying read replicas thereafter once this is completed. ) : ( )} )} {reachedMaxReplicas && ( You can only deploy up to 2 read replicas at once If you'd like to spin up another read replica, please drop an existing replica first. )} )}
{availableRegions.map((region) => ( ( region icon )} > {region.name} ))}

Read replicas will be on the same compute size as your primary database. Deploying a read replica on the {selectedComputeMeta?.name} size incurs additional{' '} {selectedComputeMeta?.price_description}.

Read more about{' '} usage-based billing {' '} for compute.

) } export default DeployNewReplicaPanel