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' import { ERROR_STATES, INIT_PROGRESS, LoadBalancerData, NODE_SEP, NODE_WIDTH, PrimaryNodeData, REGION_NODE_HEIGHT, REPLICA_STATUS, ReplicaNodeData, } from './InstanceConfiguration.constants' import { formatSeconds } from './InstanceConfiguration.utils' import { metricColor } from './InstanceNode.utils' 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 { useComputeMetrics } from '@/hooks/analytics/useComputeMetrics' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from '@/lib/constants' 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 { ref } = useParams() const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ 'project_homepage:show_instance_size', ]) const { cpu, disk, memory, connections, isLoading: metricsLoading, isError: metricsError, } = useComputeMetrics({ projectRef: ref, }) const observabilityUrl = `/project/${ref}/observability/database` return ( <>

Primary Database

{region.name}

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

region icon
{numReplicas > 0 && (

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

)} {metricsLoading ? (
) : metricsError ? ( Metrics unavailable ) : ( <> CPU {cpu.toFixed(0)}% · Disk {disk.toFixed(0)}% · RAM {memory.toFixed(0)}% {connections.max > 0 && ( <> · {connections.peak}/{connections.max} conns )} )} Go to Database Report
) } 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 icon

{region.name}

) }