| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679 |
- // @ts-nocheck
- import { useParams } from 'common'
- import { BookOpen, ChevronDown, ExternalLink } from 'lucide-react'
- import { parseAsString, useQueryState } from 'nuqs'
- import { HTMLAttributes, ReactNode, useEffect, useState } from 'react'
- import {
- Badge,
- Button,
- cn,
- Collapsible,
- CollapsibleContent,
- CollapsibleTrigger,
- DIALOG_PADDING_X,
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
- Separator,
- } from 'ui'
- import { CodeBlock } from 'ui-patterns/CodeBlock'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- import {
- CONNECTION_PARAMETERS,
- connectionStringMethodOptions,
- DATABASE_CONNECTION_TYPES,
- DatabaseConnectionType,
- IPV4_ADDON_TEXT,
- PGBOUNCER_ENABLED_BUT_NO_IPV4_ADDON_TEXT,
- type ConnectionStringMethod,
- } from './Connect.constants'
- import { CodeBlockFileHeader, ConnectionPanel } from './ConnectionPanel'
- import { getConnectionStrings } from './DatabaseSettings.utils'
- import { examples, type Example } from './DirectConnectionExamples'
- import { getAddons } from '@/components/interfaces/Billing/Subscription/Subscription.utils'
- import AlertError from '@/components/ui/AlertError'
- import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
- import { InlineLink } from '@/components/ui/InlineLink'
- import { usePgbouncerConfigQuery } from '@/data/database/pgbouncer-config-query'
- import { useSupavisorConfigurationQuery } from '@/data/database/supavisor-configuration-query'
- import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
- import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query'
- import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
- import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- import { DOCS_URL, IS_PLATFORM } from '@/lib/constants'
- import { pluckObjectFields } from '@/lib/helpers'
- import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
- const StepLabel = ({
- number,
- children,
- ...props
- }: { number: number; children: ReactNode } & HTMLAttributes<HTMLDivElement>) => (
- <div {...props} className={cn('flex items-center gap-2', props.className)}>
- <div className="flex font-mono text-xs items-center justify-center min-w-6 w-6 h-6 border border-strong rounded-md bg-surface-100">
- {number}
- </div>
- <span>{children}</span>
- </div>
- )
- /**
- * [Joshen] For paid projects - Dedicated pooler is always in transaction mode
- * So session mode connection details are always using the shared pooler (Supavisor)
- */
- export const DatabaseConnectionString = () => {
- const { ref: projectRef } = useParams()
- const { data: org } = useSelectedOrganizationQuery()
- const state = useDatabaseSelectorStateSnapshot()
- const {
- hasAccess: hasDedicatedPooler,
- isLoading: isLoadingEntitlement,
- isSuccess: isSuccessEntitlement,
- } = useCheckEntitlements('dedicated_pooler')
- const sharedPoolerPreferred = !hasDedicatedPooler
- // URL state management
- const [queryType, setQueryType] = useQueryState('type', parseAsString.withDefault('uri'))
- const [querySource, setQuerySource] = useQueryState('source', parseAsString)
- const [queryMethod, setQueryMethod] = useQueryState('method', parseAsString.withDefault('direct'))
- const [selectedTab, setSelectedTab] = useState<DatabaseConnectionType>('uri')
- const [selectedMethod, setSelectedMethod] = useState<ConnectionStringMethod>('direct')
- // Sync URL state with component state on mount and when URL changes
- useEffect(() => {
- const validTypes = DATABASE_CONNECTION_TYPES.map((t) => t.id)
- if (queryType && validTypes.includes(queryType as DatabaseConnectionType)) {
- setSelectedTab(queryType as DatabaseConnectionType)
- } else if (queryType && !validTypes.includes(queryType as DatabaseConnectionType)) {
- setQueryType('uri')
- setSelectedTab('uri')
- }
- const validMethods: ConnectionStringMethod[] = ['direct', 'transaction', 'session']
- if (queryMethod && validMethods.includes(queryMethod as ConnectionStringMethod)) {
- setSelectedMethod(queryMethod as ConnectionStringMethod)
- } else if (queryMethod && !validMethods.includes(queryMethod as ConnectionStringMethod)) {
- setQueryMethod('direct')
- setSelectedMethod('direct')
- }
- if (querySource && querySource !== state.selectedDatabaseId) {
- state.setSelectedDatabaseId(querySource)
- } else if (!querySource && state.selectedDatabaseId !== projectRef) {
- state.setSelectedDatabaseId(projectRef)
- }
- }, [queryType, queryMethod, querySource, state])
- // Sync component state changes back to URL
- const handleTabChange = (connectionType: DatabaseConnectionType) => {
- setSelectedTab(connectionType)
- setQueryType(connectionType)
- }
- const handleMethodChange = (method: ConnectionStringMethod) => {
- setSelectedMethod(method)
- setQueryMethod(method)
- }
- const handleDatabaseChange = (databaseId: string) => {
- if (databaseId === projectRef) {
- setQuerySource(null)
- } else {
- setQuerySource(databaseId)
- }
- }
- // Sync database selector state changes back to URL
- useEffect(() => {
- if (state.selectedDatabaseId && state.selectedDatabaseId !== querySource) {
- // Only set source in URL if it's not the primary database
- if (state.selectedDatabaseId === projectRef) {
- setQuerySource(null)
- } else {
- setQuerySource(state.selectedDatabaseId)
- }
- }
- }, [state.selectedDatabaseId, querySource, projectRef])
- const {
- data: pgbouncerConfig,
- error: pgbouncerError,
- isPending: isLoadingPgbouncerConfig,
- isError: isErrorPgbouncerConfig,
- isSuccess: isSuccessPgBouncerConfig,
- } = usePgbouncerConfigQuery({ projectRef })
- const {
- data: supavisorConfig,
- error: supavisorConfigError,
- isPending: isLoadingSupavisorConfig,
- isError: isErrorSupavisorConfig,
- isSuccess: isSuccessSupavisorConfig,
- } = useSupavisorConfigurationQuery({ projectRef })
- const {
- data: databases,
- error: readReplicasError,
- isPending: isLoadingReadReplicas,
- isError: isErrorReadReplicas,
- isSuccess: isSuccessReadReplicas,
- } = useReadReplicasQuery({ projectRef })
- const poolerError = sharedPoolerPreferred ? pgbouncerError : supavisorConfigError
- const isLoadingPoolerConfig = !IS_PLATFORM
- ? false
- : sharedPoolerPreferred
- ? isLoadingPgbouncerConfig
- : isLoadingSupavisorConfig
- const isErrorPoolerConfig = !IS_PLATFORM
- ? undefined
- : sharedPoolerPreferred
- ? isErrorPgbouncerConfig
- : isErrorSupavisorConfig
- const isSuccessPoolerConfig = !IS_PLATFORM
- ? true
- : sharedPoolerPreferred
- ? isSuccessPgBouncerConfig
- : isSuccessSupavisorConfig
- const error = poolerError || readReplicasError
- const isLoading = isLoadingPoolerConfig || isLoadingReadReplicas || isLoadingEntitlement
- const isError = isErrorPoolerConfig || isErrorReadReplicas
- const isSuccess = isSuccessPoolerConfig && isSuccessReadReplicas && isSuccessEntitlement
- const sharedPoolerConfig = supavisorConfig?.find((x) => x.identifier === state.selectedDatabaseId)
- const poolingConfiguration = sharedPoolerPreferred ? sharedPoolerConfig : pgbouncerConfig
- const selectedDatabase = (databases ?? []).find(
- (db) => db.identifier === state.selectedDatabaseId
- )
- const isReplicaSelected = selectedDatabase?.identifier !== projectRef
- const { data: addons } = useProjectAddonsQuery({ projectRef })
- const { ipv4: ipv4Addon } = getAddons(addons?.selected_addons ?? [])
- const { mutate: sendEvent } = useSendEventMutation()
- const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
- const emptyState = { db_user: '', db_host: '', db_port: '', db_name: '' }
- const connectionInfo = pluckObjectFields(selectedDatabase || emptyState, DB_FIELDS)
- const handleCopy = (
- connectionTypeId: string,
- connectionStringMethod: 'direct' | 'transaction_pooler' | 'session_pooler'
- ) => {
- const connectionInfo = DATABASE_CONNECTION_TYPES.find((type) => type.id === connectionTypeId)
- const connectionType = connectionInfo?.label ?? 'Unknown'
- const lang = connectionInfo?.lang ?? 'Unknown'
- sendEvent({
- action: 'connection_string_copied',
- properties: {
- connectionType,
- lang,
- connectionMethod: connectionStringMethod,
- connectionTab: 'Connection String',
- },
- groups: { project: projectRef ?? 'Unknown', organization: org?.slug ?? 'Unknown' },
- })
- }
- const supavisorConnectionStrings = getConnectionStrings({
- connectionInfo,
- poolingInfo: {
- connectionString: sharedPoolerConfig?.connection_string ?? '',
- db_host: isReplicaSelected ? connectionInfo.db_host : (sharedPoolerConfig?.db_host ?? ''),
- db_name: sharedPoolerConfig?.db_name ?? '',
- db_port: sharedPoolerConfig?.db_port ?? 0,
- db_user: sharedPoolerConfig?.db_user ?? '',
- },
- metadata: { projectRef },
- })
- const connectionStrings = getConnectionStrings({
- connectionInfo,
- poolingInfo: {
- connectionString: isReplicaSelected
- ? (poolingConfiguration?.connection_string.replace(
- poolingConfiguration?.db_host,
- connectionInfo.db_host
- ) ?? '')
- : (poolingConfiguration?.connection_string ?? ''),
- db_host: isReplicaSelected ? connectionInfo.db_host : poolingConfiguration?.db_host,
- db_name: poolingConfiguration?.db_name ?? '',
- db_port: poolingConfiguration?.db_port ?? 0,
- db_user: poolingConfiguration?.db_user ?? '',
- },
- metadata: { projectRef },
- })
- const lang = DATABASE_CONNECTION_TYPES.find((type) => type.id === selectedTab)?.lang ?? 'bash'
- const contentType =
- DATABASE_CONNECTION_TYPES.find((type) => type.id === selectedTab)?.contentType ?? 'input'
- const example: Example | undefined = examples[selectedTab as keyof typeof examples]
- const exampleFiles = example?.files
- const exampleInstallCommands = example?.installCommands
- const examplePostInstallCommands = example?.postInstallCommands
- const hasCodeExamples = exampleFiles || exampleInstallCommands
- const fileTitle = DATABASE_CONNECTION_TYPES.find((type) => type.id === selectedTab)?.fileTitle
- // [Refactor] See if we can do this in an immutable way, technically not a good practice to do this
- let stepNumber = 0
- const ipv4AddOnUrl = {
- text: 'IPv4 add-on',
- url: `/project/${projectRef}/settings/addons?panel=ipv4`,
- }
- const ipv4SettingsUrl = {
- text: 'IPv4 settings',
- url: `/project/${projectRef}/settings/addons?panel=ipv4`,
- }
- const poolerSettingsUrl = {
- text: 'Pooler settings',
- url: `/project/${projectRef}/database/settings#connection-pooling`,
- }
- const buttonLinks = !ipv4Addon
- ? [ipv4AddOnUrl, ...(sharedPoolerPreferred ? [poolerSettingsUrl] : [])]
- : [ipv4SettingsUrl, ...(sharedPoolerPreferred ? [poolerSettingsUrl] : [])]
- const poolerBadge = sharedPoolerPreferred ? 'Shared Pooler' : 'Dedicated Pooler'
- return (
- <div className="flex flex-col">
- <div className={cn('w-full flex flex-col items-start gap-2 lg:gap-3', DIALOG_PADDING_X)}>
- <div className="flex w-full flex-col md:flex-row items-stretch md:items-center gap-2 md:gap-3">
- <div className="flex">
- <span className="w-1/2 md:w-auto flex items-center text-foreground-lighter px-3 rounded-lg rounded-r-none text-xs border border-button border-r-0">
- Type
- </span>
- <Select value={selectedTab} onValueChange={handleTabChange}>
- <SelectTrigger size="small" className="w-full md:w-auto rounded-l-none">
- <SelectValue />
- </SelectTrigger>
- <SelectContent>
- {DATABASE_CONNECTION_TYPES.map((type) => (
- <SelectItem key={type.id} value={type.id}>
- {type.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <DatabaseSelector
- align="start"
- buttonProps={{
- size: 'small',
- className: 'w-full pr-2.5 [&_svg]:h-4',
- }}
- className="w-full md:w-auto [&>span]:w-1/2 [&>span]:md:w-auto"
- onSelectId={handleDatabaseChange}
- />
- <div className="flex">
- <span className="w-1/2 md:w-auto flex items-center text-foreground-lighter px-3 rounded-lg rounded-r-none text-xs border border-button border-r-0">
- Method
- </span>
- <Select value={selectedMethod} onValueChange={handleMethodChange}>
- <SelectTrigger size="small" className="w-full md:w-auto rounded-l-none">
- <SelectValue size="tiny">
- {connectionStringMethodOptions[selectedMethod].label}
- </SelectValue>
- </SelectTrigger>
- <SelectContent className="max-w-sm">
- {Object.keys(connectionStringMethodOptions).map((method) => (
- <ConnectionStringMethodSelectItem
- key={method}
- method={method as ConnectionStringMethod}
- poolerBadge={method === 'transaction' ? poolerBadge : undefined}
- />
- ))}
- </SelectContent>
- </Select>
- </div>
- </div>
- <p className="text-xs inline-flex items-center gap-1 text-foreground-lighter">
- <BookOpen size={12} strokeWidth={1.5} className="-mb-px" /> Learn how to connect to your
- Postgres databases.
- <InlineLink
- title="Read docs"
- className="flex items-center gap-x-1"
- href={`${DOCS_URL}/guides/database/connecting-to-postgres`}
- >
- Read docs <ExternalLink size={12} strokeWidth={1.5} />
- </InlineLink>
- </p>
- </div>
- {isLoading && (
- <div className="p-7">
- <ShimmeringLoader className="h-8 w-full" />
- </div>
- )}
- {isError && (
- <div className="p-7">
- <AlertError error={error} subject="Failed to retrieve database settings" />
- </div>
- )}
- {isSuccess && (
- <div className="flex flex-col divide-y divide-border">
- {/* // handle non terminal examples */}
- {hasCodeExamples && (
- <div className="flex flex-col w-full">
- <div className="grid lg:grid-cols-3 gap-4 lg:gap-5 py-8 px-4 md:px-7">
- <StepLabel number={++stepNumber} className="items-start">
- Install the following
- </StepLabel>
- {exampleInstallCommands?.map((cmd) => (
- <CodeBlock
- key={`example-install-command-${cmd}`}
- className="[&_code]:text-[12px] [&_code]:text-foreground"
- wrapperClassName="lg:col-span-2"
- value={cmd}
- hideLineNumbers
- language="bash"
- >
- {cmd}
- </CodeBlock>
- ))}
- </div>
- {exampleFiles && exampleFiles?.length > 0 && (
- <div className="grid lg:grid-cols-3 gap-4 lg:gap-5 border-t py-8 px-4 md:px-7">
- <StepLabel number={++stepNumber} className="items-start">
- Add file to project
- </StepLabel>
- {exampleFiles?.map((file) => (
- <div key={`example-files-${file.name}`} className="lg:col-span-2">
- <CodeBlockFileHeader title={file.name} />
- <CodeBlock
- wrapperClassName="[&_pre]:max-h-40 [&_pre]:px-4 [&_pre]:py-3 [&_pre]:rounded-t-none"
- value={file.content}
- hideLineNumbers
- language={lang}
- className="[&_code]:text-[12px] [&_code]:text-foreground"
- />
- </div>
- ))}
- </div>
- )}
- </div>
- )}
- <div>
- {hasCodeExamples && (
- <div className="px-4 md:px-7 pt-8">
- <StepLabel number={++stepNumber}>Connect to your database</StepLabel>
- </div>
- )}
- <div className="px-4 md:px-7 py-8">
- {selectedMethod === 'direct' && (
- <ConnectionPanel
- type="direct"
- title={connectionStringMethodOptions.direct.label}
- contentType={contentType}
- lang={lang}
- fileTitle={fileTitle}
- description={connectionStringMethodOptions.direct.description}
- connectionString={connectionStrings['direct'][selectedTab]}
- ipv4Status={{
- type: !ipv4Addon ? 'error' : 'success',
- title: !ipv4Addon ? 'Not IPv4 compatible' : 'IPv4 compatible',
- description:
- !sharedPoolerPreferred && !ipv4Addon
- ? PGBOUNCER_ENABLED_BUT_NO_IPV4_ADDON_TEXT
- : sharedPoolerPreferred
- ? 'Use Session Pooler if on a IPv4 network or purchase IPv4 add-on'
- : IPV4_ADDON_TEXT,
- links: buttonLinks,
- }}
- parameters={[
- { ...CONNECTION_PARAMETERS.host, value: connectionInfo.db_host },
- { ...CONNECTION_PARAMETERS.port, value: connectionInfo.db_port },
- { ...CONNECTION_PARAMETERS.database, value: connectionInfo.db_name },
- { ...CONNECTION_PARAMETERS.user, value: connectionInfo.db_user },
- ]}
- onCopyCallback={() => handleCopy(selectedTab, 'direct')}
- />
- )}
- {selectedMethod === 'transaction' && IS_PLATFORM && (
- <ConnectionPanel
- type="transaction"
- title={connectionStringMethodOptions.transaction.label}
- contentType={contentType}
- lang={lang}
- badge={poolerBadge}
- fileTitle={fileTitle}
- description={connectionStringMethodOptions.transaction.description}
- connectionString={connectionStrings['pooler'][selectedTab]}
- ipv4Status={{
- type: !sharedPoolerPreferred && !ipv4Addon ? 'error' : 'success',
- title:
- !sharedPoolerPreferred && !ipv4Addon
- ? 'Not IPv4 compatible'
- : 'IPv4 compatible',
- description:
- !sharedPoolerPreferred && !ipv4Addon
- ? PGBOUNCER_ENABLED_BUT_NO_IPV4_ADDON_TEXT
- : sharedPoolerPreferred
- ? 'Transaction pooler connections are IPv4 proxied for free.'
- : IPV4_ADDON_TEXT,
- links: !sharedPoolerPreferred ? buttonLinks : undefined,
- }}
- notice={['Does not support PREPARE statements']}
- parameters={[
- {
- ...CONNECTION_PARAMETERS.host,
- value: isReplicaSelected
- ? connectionInfo.db_host
- : (poolingConfiguration?.db_host ?? ''),
- },
- {
- ...CONNECTION_PARAMETERS.port,
- value: poolingConfiguration?.db_port.toString() ?? '6543',
- },
- {
- ...CONNECTION_PARAMETERS.database,
- value: poolingConfiguration?.db_name ?? '',
- },
- { ...CONNECTION_PARAMETERS.user, value: poolingConfiguration?.db_user ?? '' },
- { ...CONNECTION_PARAMETERS.pool_mode, value: 'transaction' },
- ]}
- onCopyCallback={() => handleCopy(selectedTab, 'transaction_pooler')}
- >
- {!sharedPoolerPreferred && !ipv4Addon && (
- <>
- <Separator className="w-full" />
- <Collapsible className="group">
- <CollapsibleTrigger
- asChild
- className="w-full justify-start !last:rounded-b group-data-open:rounded-b-none px-3"
- >
- <Button
- type="default"
- size="large"
- iconRight={
- <ChevronDown className="transition group-data-open:rotate-180" />
- }
- className="text-foreground bg-dash-sidebar! justify-between"
- >
- <div className="text-xs flex items-center gap-x-2 py-2 px-1">
- <span>Using the Shared Pooler</span>
- <Badge variant="success">IPv4 compatible</Badge>
- </div>
- </Button>
- </CollapsibleTrigger>
- <CollapsibleContent className="bg-dash-sidebar rounded-b border text-xs">
- <CodeBlock
- wrapperClassName={cn(
- '[&_pre]:border-x-0 [&_pre]:border-t-0 [&_pre]:px-4 [&_pre]:py-3',
- '[&_pre]:rounded-t-none'
- )}
- language={lang}
- value={supavisorConnectionStrings['pooler'][selectedTab]}
- className="[&_code]:text-[12px] [&_code]:text-foreground"
- hideLineNumbers
- onCopyCallback={() => handleCopy(selectedTab, 'transaction_pooler')}
- />
- <p className="px-3 py-2 text-foreground-light">
- Only recommended when your network does not support IPv6. Added latency
- compared to dedicated pooler.
- </p>
- </CollapsibleContent>
- </Collapsible>
- </>
- )}
- </ConnectionPanel>
- )}
- {selectedMethod === 'session' && IS_PLATFORM && (
- <ConnectionPanel
- type="session"
- title={connectionStringMethodOptions.session.label}
- contentType={contentType}
- lang={lang}
- badge="Shared Pooler"
- fileTitle={fileTitle}
- description={connectionStringMethodOptions.session.description}
- connectionString={supavisorConnectionStrings['pooler'][selectedTab].replace(
- '6543',
- '5432'
- )}
- ipv4Status={{
- type: 'success',
- title: 'IPv4 compatible',
- description: 'Session pooler connections are IPv4 proxied for free',
- links: undefined,
- }}
- parameters={[
- { ...CONNECTION_PARAMETERS.host, value: sharedPoolerConfig?.db_host ?? '' },
- { ...CONNECTION_PARAMETERS.port, value: '5432' },
- {
- ...CONNECTION_PARAMETERS.database,
- value: sharedPoolerConfig?.db_name ?? '',
- },
- { ...CONNECTION_PARAMETERS.user, value: sharedPoolerConfig?.db_user ?? '' },
- { ...CONNECTION_PARAMETERS.pool_mode, value: 'session' },
- ]}
- onCopyCallback={() => handleCopy(selectedTab, 'session_pooler')}
- />
- )}
- </div>
- </div>
- {examplePostInstallCommands && (
- <div className="grid lg:grid-cols-3 gap-4 lg:gap-5 w-full px-4 md:px-7 py-8">
- <StepLabel number={++stepNumber} className="items-start">
- Add the configuration package to read the settings
- </StepLabel>
- {examplePostInstallCommands?.map((cmd) => (
- <CodeBlock
- key={`example-post-install-commands-${cmd}`}
- className="text-sm"
- wrapperClassName="lg:col-span-2"
- value={cmd}
- hideLineNumbers
- language="bash"
- >
- {cmd}
- </CodeBlock>
- ))}
- </div>
- )}
- </div>
- )}
- {selectedTab === 'python' && (
- <>
- <Separator />
- <Collapsible className="px-8 py-5">
- <CollapsibleTrigger className="group [&[data-state=open]>div>svg]:-rotate-180!">
- <div className="flex items-center gap-x-2 w-full">
- <p className="text-xs text-foreground-light group-hover:text-foreground transition">
- Connecting to SQL Alchemy
- </p>
- <ChevronDown
- className="transition-transform duration-200"
- strokeWidth={1.5}
- size={14}
- />
- </div>
- </CollapsibleTrigger>
- <CollapsibleContent className="my-2">
- <div className="text-foreground-light text-xs grid gap-2">
- <p>
- Please use <code>postgresql://</code> instead of <code>postgres://</code> as your
- dialect when connecting via SQLAlchemy.
- </p>
- <p>
- Example:
- <code>create_engine("postgresql+psycopg2://...")</code>
- </p>
- <p className="text-sm font-mono tracking-tight text-foreground-lighter"></p>
- </div>
- </CollapsibleContent>
- </Collapsible>
- </>
- )}
- <Separator />
- <div className="px-8 pt-5 flex flex-col gap-y-1">
- <p className="text-sm">Reset your database password</p>
- <p className="text-sm text-foreground-lighter">
- You may reset your database password in your project's{' '}
- <InlineLink
- href={`/project/${projectRef}/database/settings`}
- className="text-foreground-lighter hover:text-foreground"
- >
- Database Settings
- </InlineLink>
- </p>
- </div>
- </div>
- )
- }
- const ConnectionStringMethodSelectItem = ({
- method,
- poolerBadge,
- }: {
- method: ConnectionStringMethod
- poolerBadge?: string
- }) => {
- const badges: ReactNode[] = []
- if (method !== 'direct') {
- badges.push(
- <Badge key="direct" className="flex gap-x-1">
- Shared Pooler
- </Badge>
- )
- }
- if (poolerBadge === 'Dedicated Pooler') {
- badges.push(
- <Badge key="dedicated" className="flex gap-x-1">
- {poolerBadge}
- </Badge>
- )
- }
- return (
- <SelectItem value={method} className="[&>span:first-child]:top-3.5">
- <div className="flex flex-col w-full py-1">
- <div className="flex gap-x-2 items-center">
- {connectionStringMethodOptions[method].label}
- </div>
- <div className="text-foreground-lighter text-xs">
- {connectionStringMethodOptions[method].description}
- </div>
- <div className="flex items-center gap-0.5 flex-wrap mt-1.5">
- {badges.map((badge) => badge)}
- </div>
- </div>
- </SelectItem>
- )
- }
|