import { Handle, Node, NodeProps, Position } from '@xyflow/react' import { useParams } from 'common' import dayjs from 'dayjs' import { Database, DatabaseBackup, HelpCircle, Loader2, MoreVertical } from 'lucide-react' import Link from 'next/link' import { parseAsBoolean, parseAsString, useQueryStates } from 'nuqs' import { toast } from 'sonner' import { Badge, Button, cn, copyToClipboard, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { TimestampInfo } from 'ui-patterns/TimestampInfo' import { ComputeMetricsFooter } from './ComputeMetricsFooter' import { ERROR_STATES, INIT_PROGRESS, LoadBalancerData, NODE_SEP, NODE_WIDTH, PrimaryNodeData, REGION_NODE_HEIGHT, ReplicaNodeData, } from './InstanceConfiguration.constants' import { formatSeconds } from './InstanceConfiguration.utils' import { getReadReplicaPath } from '@/components/interfaces/Settings/Infrastructure/Infrastructure.utils' import { REPLICA_STATUS } from '@/components/interfaces/Settings/Infrastructure/ReadReplicas/ReadReplicas.constants' import { RegionFlag } from '@/components/ui/RegionFlag' import { SparkBar } from '@/components/ui/SparkBar' import { DatabaseInitEstimations, ReplicaInitializationStatus, useReadReplicasStatusesQuery, } from '@/data/read-replicas/replicas-status-query' import { formatDatabaseID } from '@/data/read-replicas/replicas.utils' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector' export const LoadBalancerNode = ({ data }: NodeProps>) => { const { ref } = useParams() const { numDatabases } = data return ( <>

API Load Balancer

Distributes incoming API requests across{' '} {numDatabases} databases

) } export const PrimaryNode = ({ data }: NodeProps>) => { // [Joshen] Just FYI Handles cannot be conditionally rendered const { region, computeSize, numReplicas, numRegions, hasLoadBalancer } = data const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ 'project_homepage:show_instance_size', ]) return ( <>

Primary Database

{region.name}

await copyToClipboard(region.region, () => toast('Copied project region')) } > {region.region} Click to copy {projectHomepageShowInstanceSize && ( <> · {computeSize} )}

{numReplicas > 0 && (

{numReplicas} replica{numReplicas > 1 ? 's' : ''} {' '} deployed across{' '} {numRegions} region{numRegions > 1 ? 's' : ''}

)}
) } export const ReplicaNode = ({ data }: NodeProps>) => { const { ref } = useParams() const { id, region, computeSize, status, inserted_at } = data const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ 'project_homepage:show_instance_size', ]) const state = useDatabaseSelectorStateSnapshot() const [, setConnect] = useQueryStates({ showConnect: parseAsBoolean.withDefault(false), source: parseAsString, }) const { data: databaseStatuses } = useReadReplicasStatusesQuery({ projectRef: ref }) const { replicaInitializationStatus } = (databaseStatuses ?? []).find((db) => db.identifier === id) || {} const { status: initStatus, progress, estimations, error, } = (replicaInitializationStatus as { status?: string progress?: string estimations?: DatabaseInitEstimations error?: string }) ?? { status: undefined, progress: undefined, estimations: undefined, error: undefined } const created = dayjs(inserted_at).format('DD MMM YYYY') const stage = progress !== undefined ? Number(progress.split('_')[0]) : 0 const stagePercent = stage / (Object.keys(INIT_PROGRESS).length - 1) const isInTransition = ( [ REPLICA_STATUS.UNKNOWN, REPLICA_STATUS.COMING_UP, REPLICA_STATUS.GOING_DOWN, REPLICA_STATUS.RESTORING, REPLICA_STATUS.RESTARTING, REPLICA_STATUS.RESIZING, REPLICA_STATUS.INIT_READ_REPLICA, ] as string[] ).includes(status) || initStatus === ReplicaInitializationStatus.InProgress return ( <>
{isInTransition ? ( ) : ( )}

Replica {id.length > 0 && `(ID: ${formatDatabaseID(id)})`}

{initStatus === ReplicaInitializationStatus.InProgress || status === REPLICA_STATUS.COMING_UP || status === REPLICA_STATUS.UNKNOWN || status === REPLICA_STATUS.INIT_READ_REPLICA ? ( Coming up ) : initStatus === ReplicaInitializationStatus.Failed || status === REPLICA_STATUS.INIT_READ_REPLICA_FAILED ? ( <> Init failed Replica failed to initialize. Please drop this replica and spin up a new one. ) : status === REPLICA_STATUS.GOING_DOWN ? ( Going down ) : status === REPLICA_STATUS.RESTARTING ? ( Restarting ) : status === REPLICA_STATUS.RESIZING ? ( Resizing ) : status === REPLICA_STATUS.ACTIVE_HEALTHY ? ( Healthy ) : ( Unhealthy )}

{region.name}

await copyToClipboard(region.region, () => toast('Copied replica region')) } > {region.region} Click to copy {projectHomepageShowInstanceSize && !!computeSize && ( <> · {computeSize} )}

{initStatus === ReplicaInitializationStatus.InProgress && progress !== undefined ? (
{estimations !== undefined && (

Duration estimates:

{estimations.baseBackupDownloadEstimateSeconds !== undefined && (

Base backup download:{' '} {formatSeconds(estimations.baseBackupDownloadEstimateSeconds)}

)} {estimations.walArchiveReplayEstimateSeconds !== undefined && (

WAL archive replay:{' '} {formatSeconds(estimations.walArchiveReplayEstimateSeconds)}

)}
)}
) : error !== undefined ? (

Error: {ERROR_STATES[error as keyof typeof ERROR_STATES]}

) : (

Created:{' '}

)}
) } export const RegionNode = ({ data }: any) => { const { region, numReplicas } = data const regionNodeWidth = 20 + (NODE_WIDTH / 2 - 10) * numReplicas + (numReplicas - 1) * (NODE_SEP + 10) return (

{region.name}

) }