| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import { useParams } from 'common'
- import { ChevronRight, FileCode, X } from 'lucide-react'
- import Link from 'next/link'
- import { PropsWithChildren, ReactNode } from 'react'
- import {
- Badge,
- Button,
- cn,
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
- WarningIcon,
- } from 'ui'
- import { Admonition } from 'ui-patterns/admonition'
- import { CodeBlock, type CodeBlockLang } from 'ui-patterns/CodeBlock'
- import { ConnectionParameters } from './ConnectionParameters'
- import { useSupavisorConfigurationQuery } from '@/data/database/supavisor-configuration-query'
- import { IS_PLATFORM } from '@/lib/constants'
- import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
- interface ConnectionPanelProps {
- type?: 'direct' | 'transaction' | 'session'
- badge?: string
- title: string
- description: string
- contentFooter?: ReactNode
- connectionString: string
- ipv4Status: {
- type: 'error' | 'success'
- title: string
- description?: string | ReactNode
- links?: { text: string; url: string }[]
- }
- notice?: string[]
- parameters?: Array<{
- key: string
- value: string
- description?: string
- }>
- contentType?: 'input' | 'code'
- lang?: CodeBlockLang
- fileTitle?: string
- onCopyCallback: () => void
- }
- const IPv4StatusIcon = ({ className, active }: { className?: string; active: boolean }) => {
- return (
- <div className={cn('relative inline-flex', className)}>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth="1"
- stroke="currentColor"
- className="size-6 stroke-foreground-lighter"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
- />
- </svg>
- {!active ? (
- <div className="absolute -right-1.5 -top-1.5 bg-destructive rounded-sm w-4 h-4 flex items-center justify-center">
- <X size={10} strokeWidth={4} className="text-white rounded-full" />
- </div>
- ) : (
- <div className="absolute -right-1.5 -top-1.5 bg-brand-500 rounded-sm w-4 h-4 flex items-center justify-center">
- <svg
- width="10"
- height="10"
- viewBox="0 0 10 10"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M8.33325 2.5L3.74992 7.08333L1.66659 5"
- stroke="white"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- />
- </svg>
- </div>
- )}
- </div>
- )
- }
- export const CodeBlockFileHeader = ({ title }: { title: string }) => {
- return (
- <div className="flex items-center justify-between px-4 py-1 bg-surface-100/50 border border-b-0 border-surface rounded-t">
- <div className="flex items-center gap-2">
- <FileCode size={12} className="text-foreground-muted" strokeWidth={1.5} />
- <span className="text-xs text-foreground-light">{title}</span>
- </div>
- </div>
- )
- }
- export const ConnectionPanel = ({
- type = 'direct',
- badge,
- title,
- description,
- contentFooter,
- connectionString,
- ipv4Status,
- notice,
- parameters = [],
- lang = 'bash',
- fileTitle,
- children,
- onCopyCallback,
- }: PropsWithChildren<ConnectionPanelProps>) => {
- const { ref: projectRef } = useParams()
- const state = useDatabaseSelectorStateSnapshot()
- const { data: poolingInfo } = useSupavisorConfigurationQuery({ projectRef })
- const poolingConfiguration = poolingInfo?.find((x) => x.identifier === state.selectedDatabaseId)
- const isSessionMode = poolingConfiguration?.pool_mode === 'session'
- const links = ipv4Status.links ?? []
- const isTransactionDedicatedPooler = type === 'transaction' && badge === 'Dedicated Pooler'
- return (
- <div className="relative text-sm flex flex-col gap-5 lg:grid lg:grid-cols-12 w-full">
- <div className="col-span-4 flex flex-col">
- <div className="flex items-center gap-x-2 mb-2">
- <h1 className="text-sm">{title}</h1>
- {!!badge && !isTransactionDedicatedPooler && <Badge>{badge}</Badge>}
- </div>
- <p className="text-sm text-foreground-light mb-4">{description}</p>
- {contentFooter}
- </div>
- <div className="col-span-8 flex flex-col gap-2">
- {isTransactionDedicatedPooler && (
- <div className="text-xs flex items-center text-foreground-light">
- Using the Dedicated Pooler:
- </div>
- )}
- <div className="flex flex-col -space-y-px">
- {fileTitle && <CodeBlockFileHeader title={fileTitle} />}
- {type === 'transaction' && isSessionMode ? (
- <Admonition
- showIcon={false}
- type="default"
- className="[&>h5]:text-xs [&>div]:text-xs"
- title="Transaction pooler is unavailable as pool mode is set to Session"
- description="If you'd like to use transaction mode, update your pool mode to Transaction for the connection pooler in your project's Database Settings."
- >
- <Button asChild type="default" className="mt-2">
- <Link
- href={`/project/${projectRef}/database/settings#connection-pooler`}
- className="text-xs text-light hover:text-foreground"
- >
- Database Settings
- </Link>
- </Button>
- </Admonition>
- ) : (
- <>
- <CodeBlock
- wrapperClassName={cn(
- '[&_pre]:rounded-b-none [&_pre]:px-4 [&_pre]:py-3',
- fileTitle && '[&_pre]:rounded-t-none'
- )}
- language={lang}
- value={connectionString}
- className="[&_code]:text-[12px] [&_code]:text-foreground [&_code]:whitespace-normal!"
- hideLineNumbers
- onCopyCallback={onCopyCallback}
- />
- {notice && (
- <div className="border px-4 py-1 w-full justify-start rounded-t-none !last:rounded-b group-data-open:rounded-b-none">
- {notice?.map((text: string) => (
- <p key={text} className="text-xs text-foreground-lighter">
- {text}
- </p>
- ))}
- </div>
- )}
- {parameters.length > 0 && <ConnectionParameters parameters={parameters} />}
- </>
- )}
- </div>
- <div className="flex flex-col -space-y-px w-full">
- {IS_PLATFORM && (
- <div className="border border-muted px-5 flex gap-7 items-center py-3 first:rounded-t last:rounded-b">
- <div className="flex items-center gap-2">
- <IPv4StatusIcon active={ipv4Status.type === 'success'} />
- </div>
- <div className="flex flex-col">
- <span className="text-xs text-foreground">{ipv4Status.title}</span>
- {ipv4Status.description &&
- (typeof ipv4Status.description === 'string' ? (
- <span className="text-xs text-foreground-lighter">
- {ipv4Status.description}
- </span>
- ) : (
- ipv4Status.description
- ))}
- {links.length > 0 && (
- <div className="flex items-center gap-x-2 mt-2">
- {links.map((link) => (
- <Button key={link.text} asChild type="default" size="tiny">
- <Link href={link.url} className="text-xs text-light hover:text-foreground">
- {link.text}
- </Link>
- </Button>
- ))}
- </div>
- )}
- </div>
- </div>
- )}
- {type === 'session' && (
- <div className="border border-muted px-5 flex gap-7 items-center py-3 first:rounded-t last:rounded-b bg-alternative/50">
- <div className="flex w-6 h-6 rounded-sm items-center justify-center gap-2 shrink-0 bg-surface-100">
- <WarningIcon />
- </div>
- <div className="flex flex-col">
- <span className="text-xs text-foreground">Only use on a IPv4 network</span>
- <div className="flex flex-col text-xs text-foreground-lighter">
- <p>Session pooler connections are IPv4 proxied for free.</p>
- <p>Use Direct Connection if connecting via an IPv6 network.</p>
- </div>
- </div>
- </div>
- )}
- {IS_PLATFORM && ipv4Status.type === 'error' && (
- <Collapsible className="group -space-y-px">
- <CollapsibleTrigger
- asChild
- className="group/collapse w-full justify-start rounded-t-none !last:rounded-b group-data-open:rounded-b-none border-muted"
- >
- <Button
- type="default"
- size="tiny"
- className="text-foreground-lighter bg-dash-sidebar!"
- icon={
- <ChevronRight
- className={cn(
- 'group-data-open/collapse:rotate-90 text-foreground-muted transition-transform'
- )}
- />
- }
- >
- Some platforms are IPv4-only:
- </Button>
- </CollapsibleTrigger>
- <CollapsibleContent className="bg-dash-sidebar rounded-b border px-3 py-2">
- <div className="flex flex-col gap-2">
- <p className="text-xs text-foreground-light max-w-xs">
- A few major platforms are IPv4-only and may not work with a Direct Connection:
- </p>
- <div className="flex gap-4">
- <div className="text-foreground text-xs">Vercel</div>
- <div className="text-foreground text-xs">GitHub Actions</div>
- <div className="text-foreground text-xs">Render</div>
- <div className="text-foreground text-xs">Retool</div>
- </div>
- <p className="text-xs text-foreground-lighter max-w-xs">
- If you wish to use a Direct Connection with these, please purchase{' '}
- <Link
- href={`/project/${projectRef}/settings/addons?panel=ipv4`}
- className="text-xs text-light hover:text-foreground"
- >
- IPv4 support
- </Link>
- .
- </p>
- <p className="text-xs text-foreground-lighter max-w-xs">
- You may also use the{' '}
- <span className="text-foreground-light">Session Pooler</span> or{' '}
- <span className="text-foreground-light">Transaction Pooler</span> if you are on
- a IPv4 network.
- </p>
- </div>
- </CollapsibleContent>
- </Collapsible>
- )}
- </div>
- {children}
- </div>
- </div>
- )
- }
|