Chore/update database settings (#20575)
* Add connection string syntax * Add more description to pooling modes * Fix * Test * Fix * Sm wording change * Update apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx * prettier --------- Co-authored-by: Terry Sutton <saltcod@gmail.com> Co-authored-by: Kevin Grüneberg <k.grueneberg1994@gmail.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useParams } from 'common'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
@@ -32,7 +31,6 @@ import { usePoolingConfigurationUpdateMutation } from 'data/database/pooling-con
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useCheckPermissions, useStore } from 'hooks'
|
||||
import { POOLING_OPTIMIZATIONS } from './ConnectionPooling.constants'
|
||||
import { constructConnStringSyntax, getPoolerTld } from './ConnectionPooling.utils'
|
||||
|
||||
const formId = 'connection-pooling-form'
|
||||
|
||||
@@ -73,21 +71,9 @@ export const ConnectionPooling = () => {
|
||||
isSuccess,
|
||||
} = usePoolingConfigurationQuery({ projectRef: projectRef })
|
||||
|
||||
const poolerTld = isSuccess ? getPoolerTld(poolingInfo.connectionString) : 'com'
|
||||
|
||||
const connectionPoolingUnavailable =
|
||||
!poolingInfo?.pgbouncer_enabled && poolingInfo?.pool_mode === null
|
||||
|
||||
const poolerConnStringSyntax = isSuccess
|
||||
? constructConnStringSyntax(poolingInfo?.connectionString, {
|
||||
ref: projectRef as string,
|
||||
cloudProvider: projectIsLoading ? '' : project?.cloud_provider || '',
|
||||
region: projectIsLoading ? '' : project?.region || '',
|
||||
tld: poolerTld,
|
||||
portNumber: poolingInfo.db_port.toString(),
|
||||
})
|
||||
: []
|
||||
|
||||
// [Joshen] TODO this needs to be obtained from BE as 26th Jan is when we'll start - projects will be affected at different rates
|
||||
const resolvesToIpV6 = !poolingInfo?.supavisor_enabled && false // Number(new Date()) > Number(dayjs.utc('01-26-2024', 'MM-DD-YYYY').toDate())
|
||||
|
||||
@@ -166,21 +152,22 @@ export const ConnectionPooling = () => {
|
||||
<p>
|
||||
{connectionPoolingUnavailable
|
||||
? 'Connection Pooling is not available for this project'
|
||||
: 'Connect to your database via connection pooling'}
|
||||
: 'Connection pooling configuration'}
|
||||
</p>
|
||||
{isSuccess && (
|
||||
<div className="flex items-center gap-x-1">
|
||||
<Badge color={poolingInfo?.supavisor_enabled ? 'green' : 'scale'}>
|
||||
With {poolingInfo?.supavisor_enabled ? 'Supavisor' : 'PGBouncer'}
|
||||
</Badge>
|
||||
<Badge color={resolvesToIpV6 ? 'amber' : 'scale'}>
|
||||
{resolvesToIpV6 ? 'Resolves to IPv6' : 'Resolves to IPv4'}
|
||||
<Badge color="scale">
|
||||
{poolingInfo?.supavisor_enabled ? 'Supavisor' : 'PGBouncer'}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
@@ -256,10 +243,20 @@ export const ConnectionPooling = () => {
|
||||
label="Transaction"
|
||||
value="transaction"
|
||||
>
|
||||
Transaction
|
||||
<p>Transaction mode</p>
|
||||
<p className="text-xs text-foreground-lighter">
|
||||
Connection is assigned to the client for the duration of a
|
||||
transaction. Some session-based Postgres features such as prepared
|
||||
statements are not available with this option.
|
||||
</p>
|
||||
</Listbox.Option>
|
||||
<Listbox.Option key="session" label="Session" value="session">
|
||||
Session
|
||||
<p>Session mode</p>
|
||||
<p className="text-xs text-foreground-lighter">
|
||||
When a new client connects, a connection is assigned to the client
|
||||
until it disconnects. All Postgres features can be used with this
|
||||
option.
|
||||
</p>
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</FormControl_Shadcn_>
|
||||
@@ -406,70 +403,6 @@ export const ConnectionPooling = () => {
|
||||
<div className="border-muted border-t"></div>
|
||||
</>
|
||||
)}
|
||||
<Input
|
||||
className="input-mono w-full px-8 py-8 flex items-center"
|
||||
layout="horizontal"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
value={poolingInfo?.db_port}
|
||||
label="Port Number"
|
||||
/>
|
||||
|
||||
<div className="border-muted border-t"></div>
|
||||
|
||||
<Input
|
||||
className="input-mono w-full px-8 py-8"
|
||||
layout="vertical"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
label="Connection string"
|
||||
value={poolingInfo?.connectionString}
|
||||
descriptionText={
|
||||
poolingInfo.supavisor_enabled && (
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-sm">
|
||||
You may also connect to another database or with another user via Supavisor
|
||||
with the following URI format:
|
||||
</p>
|
||||
|
||||
{poolerConnStringSyntax.length > 0 && (
|
||||
<p className="text-sm font-mono tracking-tighter">
|
||||
{poolerConnStringSyntax.map((x, idx) => {
|
||||
if (x.tooltip) {
|
||||
return (
|
||||
<Tooltip.Root key={`syntax-${idx}`} delayDuration={0}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="text-foreground">{x.value}</span>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">{x.tooltip}</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
} else {
|
||||
return x.value
|
||||
}
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Panel>
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// [Joshen] This is to the best of interpreting the syntax from the API response
|
||||
// // There's different format for PG13 (depending on authentication method being md5) and PG14
|
||||
export const constructConnStringSyntax = (
|
||||
connString: string,
|
||||
{
|
||||
ref,
|
||||
cloudProvider,
|
||||
region,
|
||||
tld,
|
||||
portNumber,
|
||||
}: { ref: string; cloudProvider: string; region: string; tld: string; portNumber: string }
|
||||
) => {
|
||||
if (connString.includes('postgres:[YOUR-PASSWORD]')) {
|
||||
// PG 13 + Authentication MD5
|
||||
return [
|
||||
{ value: 'postgres://', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: '@', tooltip: undefined },
|
||||
{ value: cloudProvider.toLocaleLowerCase(), tooltip: 'Cloud provider' },
|
||||
{ value: '-0-', tooltip: undefined },
|
||||
{ value: region, tooltip: "Project's region" },
|
||||
{ value: `.pooler.supabase.${tld}:`, tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
{ value: `?options=reference%3D`, tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'postgres://', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: '@', tooltip: undefined },
|
||||
{ value: cloudProvider.toLocaleLowerCase(), tooltip: 'Cloud provider' },
|
||||
{ value: '-0-', tooltip: undefined },
|
||||
{ value: region, tooltip: "Project's region" },
|
||||
{ value: `.pooler.supabase.${tld}:`, tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export const getPoolerTld = (connString: string) => {
|
||||
try {
|
||||
const segment = connString.split('pooler.supabase.')[1]
|
||||
const tld = segment.split(':6543')[0]
|
||||
return tld
|
||||
} catch {
|
||||
return 'com'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
Alert_Shadcn_,
|
||||
IconAlertTriangle,
|
||||
AlertTitle_Shadcn_,
|
||||
AlertDescription_Shadcn_,
|
||||
Button,
|
||||
IconExternalLink,
|
||||
} from 'ui'
|
||||
|
||||
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useState } from 'react'
|
||||
import ConfirmDisableReadOnlyModeModal from './DatabaseSettings/ConfirmDisableReadOnlyModal'
|
||||
|
||||
export const DatabaseReadOnlyAlert = () => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const organization = useSelectedOrganization()
|
||||
const [showConfirmationModal, setShowConfirmationModal] = useState(false)
|
||||
|
||||
const { data: resourceWarnings } = useResourceWarningsQuery()
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
|
||||
|
||||
const isReadOnlyMode =
|
||||
(resourceWarnings ?? [])?.find((warning) => warning.project === projectRef)
|
||||
?.is_readonly_mode_enabled ?? false
|
||||
|
||||
return (
|
||||
<>
|
||||
{isReadOnlyMode && (
|
||||
<Alert_Shadcn_ variant="destructive">
|
||||
<IconAlertTriangle />
|
||||
<AlertTitle_Shadcn_>
|
||||
Project is in read-only mode and database is no longer accepting write requests
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
You have reached 95% of your project's disk space, and read-only mode has been enabled
|
||||
to preserve your database's stability and prevent your project from exceeding its
|
||||
current billing plan. To resolve this, you may:
|
||||
<ul className="list-disc pl-6 mt-1">
|
||||
<li>
|
||||
Temporarily disable read-only mode to free up space and reduce your database size
|
||||
</li>
|
||||
{subscription?.plan.id === 'free' ? (
|
||||
<li>
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
<a className="text underline">Upgrade to the Pro plan</a>
|
||||
</Link>{' '}
|
||||
to increase your database size limit to 8GB.
|
||||
</li>
|
||||
) : subscription?.plan.id === 'pro' && subscription?.usage_billing_enabled ? (
|
||||
<li>
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
<a className="text-foreground underline">Disable your Spend Cap</a>
|
||||
</Link>{' '}
|
||||
to allow your project to auto-scale and expand beyond the 8GB database size limit
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</AlertDescription_Shadcn_>
|
||||
<div className="mt-4 flex items-center space-x-2">
|
||||
<Button type="default" onClick={() => setShowConfirmationModal(true)}>
|
||||
Disable read-only mode
|
||||
</Button>
|
||||
<Button asChild type="default" icon={<IconExternalLink />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
<ConfirmDisableReadOnlyModeModal
|
||||
visible={showConfirmationModal}
|
||||
onClose={() => setShowConfirmationModal(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +1,32 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { useParams, useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Input, Separator, Tabs } from 'ui'
|
||||
import { Button, IconExternalLink, Input, Separator, Tabs } from 'ui'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import DatabaseSelector from 'components/ui/DatabaseSelector'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useFlag } from 'hooks'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { getConnectionStrings } from './DatabaseSettings.utils'
|
||||
import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice'
|
||||
import { UsePoolerCheckbox } from '../UsePoolerCheckbox'
|
||||
import {
|
||||
constructConnStringSyntax,
|
||||
getConnectionStrings,
|
||||
getPoolerTld,
|
||||
} from './DatabaseSettings.utils'
|
||||
|
||||
const CONNECTION_TYPES = [
|
||||
{ id: 'psql', label: 'PSQL' },
|
||||
{ id: 'uri', label: 'URI' },
|
||||
{ id: 'psql', label: 'PSQL' },
|
||||
{ id: 'golang', label: 'Golang' },
|
||||
{ id: 'jdbc', label: 'JDBC' },
|
||||
{ id: 'dotnet', label: '.NET' },
|
||||
@@ -28,16 +37,22 @@ const CONNECTION_TYPES = [
|
||||
|
||||
export const DatabaseConnectionString = () => {
|
||||
const router = useRouter()
|
||||
const { project: projectDetails } = useProjectContext()
|
||||
const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext()
|
||||
const { ref: projectRef, connectionString } = useParams()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled
|
||||
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled
|
||||
const connectionStringsRef = useRef<HTMLDivElement>(null)
|
||||
const [usePoolerConnection, setUsePoolerConnection] = useState(true)
|
||||
const [selectedTab, setSelectedTab] = useState<
|
||||
'psql' | 'uri' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python'
|
||||
>('psql')
|
||||
'uri' | 'psql' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python'
|
||||
>('uri')
|
||||
|
||||
const { data: poolingInfo, isSuccess: isSuccessPoolingInfo } = usePoolingConfigurationQuery({
|
||||
projectRef,
|
||||
})
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -70,6 +85,10 @@ export const DatabaseConnectionString = () => {
|
||||
const connectionInfo = readReplicasEnabled
|
||||
? pluckObjectFields(selectedDatabase || emptyState, DB_FIELDS)
|
||||
: pluckObjectFields(project || emptyState, DB_FIELDS)
|
||||
const connectionTld =
|
||||
projectDetails?.restUrl !== undefined
|
||||
? new URL(projectDetails?.restUrl ?? '').hostname.split('.').pop() ?? 'co'
|
||||
: 'co'
|
||||
|
||||
const handleCopy = (id: string) => {
|
||||
const labelValue = CONNECTION_TYPES.find((type) => type.id === id)?.label
|
||||
@@ -84,7 +103,26 @@ export const DatabaseConnectionString = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const connectionStrings = getConnectionStrings(connectionInfo)
|
||||
const connectionStrings = isSuccessPoolingInfo
|
||||
? getConnectionStrings(connectionInfo, poolingInfo, {
|
||||
projectRef,
|
||||
usePoolerConnection,
|
||||
})
|
||||
: { uri: '', psql: '', golang: '', jdbc: '', dotnet: '', nodejs: '', php: '', python: '' }
|
||||
const poolerTld = isSuccessPoolingInfo ? getPoolerTld(poolingInfo.connectionString) : 'com'
|
||||
const poolerConnStringSyntax = isSuccessPoolingInfo
|
||||
? constructConnStringSyntax(poolingInfo.connectionString, {
|
||||
selectedTab,
|
||||
usePoolerConnection,
|
||||
ref: projectRef as string,
|
||||
cloudProvider: isProjectLoading ? '' : project?.cloud_provider || '',
|
||||
region: isProjectLoading ? '' : project?.region || '',
|
||||
tld: usePoolerConnection ? poolerTld : connectionTld,
|
||||
portNumber: usePoolerConnection
|
||||
? poolingInfo.db_port.toString()
|
||||
: connectionInfo.db_port.toString(),
|
||||
})
|
||||
: []
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -99,40 +137,103 @@ export const DatabaseConnectionString = () => {
|
||||
|
||||
return (
|
||||
<div id="connection-string">
|
||||
<div ref={connectionStringsRef} className="flex flex-col py-4">
|
||||
<div className="px-6 mb-2">
|
||||
<h5 key="panel-title" className="mb-0">
|
||||
Connection string
|
||||
</h5>
|
||||
{readReplicasEnabled && <DatabaseSelector />}
|
||||
</div>
|
||||
<Tabs
|
||||
type="underlined"
|
||||
size="tiny"
|
||||
activeId={selectedTab}
|
||||
baseClassNames="!space-y-0 -mb-[1px] px-6"
|
||||
onChange={setSelectedTab}
|
||||
>
|
||||
{CONNECTION_TYPES.map((type) => (
|
||||
<Tabs.Panel key={type.id} id={type.id} label={type.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
<Separator />
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-4">
|
||||
{isLoading && <ShimmeringLoader className="h-8 w-full" />}
|
||||
{isError && <AlertError error={error} subject="Failed to retrieve database settings" />}
|
||||
{isSuccess && (
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
value={connectionStrings[selectedTab]}
|
||||
onCopy={() => handleCopy(selectedTab)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Panel
|
||||
className="!m-0 [&>div:nth-child(1)]:!border-0 [&>div:nth-child(1)>div]:!p-0"
|
||||
title={
|
||||
<div ref={connectionStringsRef} className="w-full flex flex-col pt-4">
|
||||
<div className="flex items-center justify-between px-6 mb-2">
|
||||
<h5 key="panel-title" className="mb-0">
|
||||
Connection string
|
||||
</h5>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{readReplicasEnabled && <DatabaseSelector />}
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a href="https://supabase.com/docs/guides/database/connecting-to-postgres">
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs
|
||||
type="underlined"
|
||||
size="tiny"
|
||||
activeId={selectedTab}
|
||||
baseClassNames="!space-y-0 px-6 -mb-[1px]"
|
||||
onChange={setSelectedTab}
|
||||
>
|
||||
{CONNECTION_TYPES.map((type) => (
|
||||
<Tabs.Panel key={type.id} id={type.id} label={type.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
<Separator />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Panel.Content>
|
||||
{isLoading && <ShimmeringLoader className="h-8 w-full" />}
|
||||
{isError && <AlertError error={error} subject="Failed to retrieve database settings" />}
|
||||
{isSuccess && (
|
||||
<div className="flex flex-col gap-y-4 pt-2">
|
||||
<UsePoolerCheckbox
|
||||
id="connection-string"
|
||||
checked={usePoolerConnection}
|
||||
onCheckedChange={setUsePoolerConnection}
|
||||
/>
|
||||
{!usePoolerConnection && <IPv4DeprecationNotice />}
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
className="input-mono [&>div>div>div>input]:text-xs [&>div>div>div>input]:opacity-100"
|
||||
value={connectionStrings[selectedTab]}
|
||||
onCopy={() => handleCopy(selectedTab)}
|
||||
/>
|
||||
{poolerConnStringSyntax.length > 0 && poolingInfo?.supavisor_enabled && (
|
||||
<div className="flex flex-col gap-y-1 text-foreground-light">
|
||||
<p className="text-sm">
|
||||
You can use the following URI format to switch to a different database or user
|
||||
when using connection pooling.
|
||||
</p>
|
||||
<p className="text-sm font-mono tracking-tight text-foreground-lighter">
|
||||
{poolerConnStringSyntax.map((x, idx) => {
|
||||
if (x.tooltip) {
|
||||
return (
|
||||
<Tooltip.Root key={`syntax-${idx}`} delayDuration={0}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className="text-foreground text-xs">{x.value}</span>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">{x.tooltip}</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<span key={`syntax-${idx}`} className="text-xs">
|
||||
{x.value}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useParams, useTelemetryProps } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -9,9 +8,7 @@ import Panel from 'components/ui/Panel'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
|
||||
import { useFlag, useSelectedOrganization, useSelectedProject } from 'hooks'
|
||||
import { useFlag, useSelectedProject } from 'hooks'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
@@ -19,16 +16,14 @@ import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Badge,
|
||||
Button,
|
||||
IconAlertTriangle,
|
||||
IconExternalLink,
|
||||
IconAlertCircle,
|
||||
Input,
|
||||
Separator,
|
||||
} from 'ui'
|
||||
import ConfirmDisableReadOnlyModeModal from './ConfirmDisableReadOnlyModal'
|
||||
import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice'
|
||||
import { UsePoolerCheckbox } from '../UsePoolerCheckbox'
|
||||
import ResetDbPassword from './ResetDbPassword'
|
||||
import { DatabaseConnectionString } from './DatabaseConnectionString'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
import { getHostFromConnectionString } from './DatabaseSettings.utils'
|
||||
|
||||
const DatabaseSettings = () => {
|
||||
const router = useRouter()
|
||||
@@ -36,17 +31,21 @@ const DatabaseSettings = () => {
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const selectedProject = useSelectedProject()
|
||||
const organization = useSelectedOrganization()
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const showReadReplicasUI = readReplicasEnabled && selectedProject?.is_read_replicas_enabled
|
||||
const connectionStringsRef = useRef<HTMLDivElement>(null)
|
||||
const [showConfirmationModal, setShowConfirmationModal] = useState(false)
|
||||
const [usePoolerConnection, setUsePoolerConnection] = useState(true)
|
||||
|
||||
// [Joshen] TODO this needs to be obtained from BE as 26th Jan is when we'll start - projects will be affected at different rates
|
||||
const resolvesToIpV6 = false // Number(new Date()) > Number(dayjs.utc('01-26-2024', 'MM-DD-YYYY').toDate())
|
||||
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
|
||||
const {
|
||||
data: poolingInfo,
|
||||
error: poolingInfoError,
|
||||
isLoading: isLoadingPoolingInfo,
|
||||
isError: isErrorPoolingInfo,
|
||||
isSuccess: isSuccessPoolingInfo,
|
||||
} = usePoolingConfigurationQuery({
|
||||
projectRef,
|
||||
})
|
||||
const {
|
||||
data,
|
||||
error: projectSettingsError,
|
||||
@@ -54,7 +53,6 @@ const DatabaseSettings = () => {
|
||||
isError: isErrorProjectSettings,
|
||||
isSuccess: isSuccessProjectSettings,
|
||||
} = useProjectSettingsQuery({ projectRef })
|
||||
const { data: resourceWarnings } = useResourceWarningsQuery()
|
||||
const {
|
||||
data: databases,
|
||||
error: readReplicasError,
|
||||
@@ -62,26 +60,40 @@ const DatabaseSettings = () => {
|
||||
isError: isErrorReadReplicas,
|
||||
isSuccess: isSuccessReadReplicas,
|
||||
} = useReadReplicasQuery({ projectRef })
|
||||
const error = showReadReplicasUI ? readReplicasError : projectSettingsError
|
||||
const isLoading = showReadReplicasUI ? isLoadingReadReplicas : isLoadingProjectSettings
|
||||
const isError = showReadReplicasUI ? isErrorReadReplicas : isErrorProjectSettings
|
||||
const isSuccess = showReadReplicasUI ? isSuccessReadReplicas : isSuccessProjectSettings
|
||||
const error = showReadReplicasUI ? readReplicasError : projectSettingsError || poolingInfoError
|
||||
const isLoading = showReadReplicasUI
|
||||
? isLoadingReadReplicas
|
||||
: isLoadingProjectSettings || isLoadingPoolingInfo
|
||||
const isError = showReadReplicasUI
|
||||
? isErrorReadReplicas
|
||||
: isErrorProjectSettings || isErrorPoolingInfo
|
||||
const isSuccess = showReadReplicasUI
|
||||
? isSuccessReadReplicas
|
||||
: isSuccessProjectSettings || isSuccessPoolingInfo
|
||||
|
||||
const selectedDatabase = (databases ?? []).find(
|
||||
(db) => db.identifier === state.selectedDatabaseId
|
||||
)
|
||||
|
||||
const isReadOnlyMode =
|
||||
(resourceWarnings ?? [])?.find((warning) => warning.project === projectRef)
|
||||
?.is_readonly_mode_enabled ?? false
|
||||
const isMd5 = poolingInfo?.connectionString.includes('?options=reference')
|
||||
|
||||
const { project } = data ?? {}
|
||||
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
|
||||
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user']
|
||||
const emptyState = { db_user: '', db_host: '', db_port: '', db_name: '' }
|
||||
const connectionInfo = showReadReplicasUI
|
||||
const dbConnectionInfo = showReadReplicasUI
|
||||
? pluckObjectFields(selectedDatabase || emptyState, DB_FIELDS)
|
||||
: pluckObjectFields(project || emptyState, DB_FIELDS)
|
||||
|
||||
const connectionInfo = usePoolerConnection
|
||||
? {
|
||||
db_host: isSuccessPoolingInfo
|
||||
? getHostFromConnectionString(poolingInfo?.connectionString)
|
||||
: '',
|
||||
db_name: poolingInfo?.db_name,
|
||||
db_port: poolingInfo?.db_port,
|
||||
db_user: `postgres.${projectRef}`,
|
||||
}
|
||||
: dbConnectionInfo
|
||||
|
||||
const handleCopy = (labelValue?: string) =>
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
@@ -103,68 +115,16 @@ const DatabaseSettings = () => {
|
||||
return (
|
||||
<>
|
||||
<section id="direct-connection">
|
||||
{isReadOnlyMode && (
|
||||
<Alert_Shadcn_ variant="destructive">
|
||||
<IconAlertTriangle />
|
||||
<AlertTitle_Shadcn_>
|
||||
Project is in read-only mode and database is no longer accepting write requests
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
You have reached 95% of your project's disk space, and read-only mode has been enabled
|
||||
to preserve your database's stability and prevent your project from exceeding its
|
||||
current billing plan. To resolve this, you may:
|
||||
<ul className="list-disc pl-6 mt-1">
|
||||
<li>
|
||||
Temporarily disable read-only mode to free up space and reduce your database size
|
||||
</li>
|
||||
{subscription?.plan.id === 'free' ? (
|
||||
<li>
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
<a className="text underline">Upgrade to the Pro plan</a>
|
||||
</Link>{' '}
|
||||
to increase your database size limit to 8GB.
|
||||
</li>
|
||||
) : subscription?.plan.id === 'pro' && subscription?.usage_billing_enabled ? (
|
||||
<li>
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
<a className="text-foreground underline">Disable your Spend Cap</a>
|
||||
</Link>{' '}
|
||||
to allow your project to auto-scale and expand beyond the 8GB database size
|
||||
limit
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</AlertDescription_Shadcn_>
|
||||
<div className="mt-4 flex items-center space-x-2">
|
||||
<Button type="default" onClick={() => setShowConfirmationModal(true)}>
|
||||
Disable read-only mode
|
||||
</Button>
|
||||
<Button asChild type="default" icon={<IconExternalLink />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/database-size#disabling-read-only-mode"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
<Panel
|
||||
className="!m-0"
|
||||
title={
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<h5 className="mb-0">Connect to your database directly</h5>
|
||||
<Badge color={resolvesToIpV6 ? 'amber' : 'scale'}>
|
||||
{resolvesToIpV6 ? 'Resolves to IPv6' : 'Resolves to IPv4'}
|
||||
</Badge>
|
||||
<h5 className="mb-0">Connection parameters</h5>
|
||||
</div>
|
||||
{showReadReplicasUI && <DatabaseSelector />}
|
||||
</div>
|
||||
}
|
||||
className="!m-0"
|
||||
>
|
||||
<Panel.Content className="space-y-6">
|
||||
{isLoading &&
|
||||
@@ -180,51 +140,27 @@ const DatabaseSettings = () => {
|
||||
{isError && <AlertError error={error} subject="Failed to retrieve databases" />}
|
||||
{isSuccess && (
|
||||
<>
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Direct database access via IPv4 and pgBouncer will be removed from January 26th
|
||||
2024
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="space-y-3">
|
||||
<p>
|
||||
We strongly recommend using{' '}
|
||||
<span
|
||||
tabIndex={0}
|
||||
className="cursor-pointer text-foreground underline underline-offset-[4px] decoration-brand-500 hover:decoration-foreground"
|
||||
onClick={() => {
|
||||
const connectionPooler = document.getElementById('connection-pooler')
|
||||
connectionPooler?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
}}
|
||||
>
|
||||
connection pooling
|
||||
</span>{' '}
|
||||
to connect to your database. You'll only need to change the connection string
|
||||
that you're using in your application to the pooler's connection string which
|
||||
can be found in the{' '}
|
||||
<span
|
||||
tabIndex={0}
|
||||
className="cursor-pointer text-foreground underline underline-offset-[4px] decoration-brand-500 hover:decoration-foreground"
|
||||
onClick={() => {
|
||||
const connectionPooler = document.getElementById('connection-pooler')
|
||||
connectionPooler?.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||
}}
|
||||
>
|
||||
connection pooling settings
|
||||
</span>
|
||||
.
|
||||
</p>
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a
|
||||
href="https://github.com/orgs/supabase/discussions/17817"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<div className="space-y-4">
|
||||
<UsePoolerCheckbox
|
||||
id="connection-params"
|
||||
checked={usePoolerConnection}
|
||||
onCheckedChange={setUsePoolerConnection}
|
||||
/>
|
||||
{!usePoolerConnection && <IPv4DeprecationNotice />}
|
||||
{isMd5 && (
|
||||
<Alert_Shadcn_>
|
||||
<IconAlertCircle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
If you are connecting to your database via a GUI client, use the{' '}
|
||||
<span tabIndex={0}>connection string</span> above instead
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
GUI clients only support database connections for Postgres 13 via a
|
||||
connection string.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
@@ -237,7 +173,6 @@ const DatabaseSettings = () => {
|
||||
handleCopy('Host')
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
@@ -247,17 +182,26 @@ const DatabaseSettings = () => {
|
||||
value={connectionInfo.db_name}
|
||||
label="Database name"
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
value={connectionInfo.db_port.toString()}
|
||||
value={connectionInfo.db_port}
|
||||
label="Port"
|
||||
/>
|
||||
|
||||
{isMd5 && (
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
value={`reference=${projectRef}`}
|
||||
label="Options"
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
layout="horizontal"
|
||||
className="input-mono table-input-cell text-base"
|
||||
@@ -267,7 +211,6 @@ const DatabaseSettings = () => {
|
||||
value={connectionInfo.db_user}
|
||||
label="User"
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
@@ -283,17 +226,10 @@ const DatabaseSettings = () => {
|
||||
</>
|
||||
)}
|
||||
</Panel.Content>
|
||||
<Separator />
|
||||
<DatabaseConnectionString />
|
||||
</Panel>
|
||||
</section>
|
||||
|
||||
<ResetDbPassword disabled={isLoading || isError} />
|
||||
|
||||
<ConfirmDisableReadOnlyModeModal
|
||||
visible={showConfirmationModal}
|
||||
onClose={() => setShowConfirmationModal(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
export const getConnectionStrings = (connectionInfo: {
|
||||
db_user: string
|
||||
db_port: number
|
||||
db_host: string
|
||||
db_name: string
|
||||
}) => {
|
||||
const uriConnString =
|
||||
`postgresql://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
|
||||
`${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
|
||||
`/${connectionInfo.db_name}`
|
||||
import { PoolingConfiguration } from 'data/database/pooling-configuration-query'
|
||||
|
||||
export const getHostFromConnectionString = (str: string) => {
|
||||
const segment = str.split('[YOUR-PASSWORD]@')
|
||||
const [output] = segment[1].split(':')
|
||||
return output
|
||||
}
|
||||
|
||||
export const getConnectionStrings = (
|
||||
connectionInfo: {
|
||||
db_user: string
|
||||
db_port: number
|
||||
db_host: string
|
||||
db_name: string
|
||||
},
|
||||
poolingInfo: PoolingConfiguration,
|
||||
metadata: {
|
||||
usePoolerConnection: boolean
|
||||
projectRef?: string
|
||||
pgVersion?: string
|
||||
}
|
||||
) => {
|
||||
const { usePoolerConnection, projectRef } = metadata
|
||||
|
||||
// Pooler: user, host port
|
||||
const user = usePoolerConnection ? `postgres.${projectRef}` : connectionInfo.db_user
|
||||
const port = usePoolerConnection ? poolingInfo?.db_port : connectionInfo.db_port
|
||||
// [Joshen] Temp FE: extract host from pooler connection string
|
||||
const host = usePoolerConnection
|
||||
? getHostFromConnectionString(poolingInfo.connectionString)
|
||||
: connectionInfo.db_host
|
||||
const name = usePoolerConnection ? poolingInfo?.db_name : connectionInfo.db_name
|
||||
|
||||
const uriConnString = usePoolerConnection
|
||||
? poolingInfo?.connectionString
|
||||
: `postgresql://${user}:[YOUR-PASSWORD]@` + `${host}:${port}` + `/${name}`
|
||||
const golangConnString =
|
||||
`user=${connectionInfo.db_user} password=[YOUR-PASSWORD] ` +
|
||||
`host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
|
||||
` dbname=${connectionInfo.db_name}`
|
||||
const psqlConnString =
|
||||
`psql -h ${connectionInfo.db_host} -p ` +
|
||||
`${connectionInfo.db_port.toString()} -d ${connectionInfo.db_name} ` +
|
||||
`-U ${connectionInfo.db_user}`
|
||||
`user=${user} password=[YOUR-PASSWORD] ` + `host=${host} port=${port}` + ` dbname=${name}`
|
||||
const psqlConnString = `psql -h ${host} -p ` + `${port} -d ${name} ` + `-U ${user}`
|
||||
const jdbcConnString =
|
||||
`jdbc:postgresql://${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
|
||||
`/${connectionInfo.db_name}?user=${connectionInfo.db_user}&password=[YOUR-PASSWORD]`
|
||||
`jdbc:postgresql://${host}:${port}` + `/${name}?user=${user}&password=[YOUR-PASSWORD]`
|
||||
const dotNetConnString =
|
||||
`User Id=${connectionInfo.db_user};Password=[YOUR-PASSWORD];` +
|
||||
`Server=${connectionInfo.db_host};Port=${connectionInfo.db_port.toString()};` +
|
||||
`Database=${connectionInfo.db_name}`
|
||||
`User Id=${user};Password=[YOUR-PASSWORD];` +
|
||||
`Server=${host};Port=${port};` +
|
||||
`Database=${name}`
|
||||
const pythonConnString =
|
||||
`user=${connectionInfo.db_user} password=[YOUR-PASSWORD]` +
|
||||
` host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
|
||||
` database=${connectionInfo.db_name}`
|
||||
`user=${user} password=[YOUR-PASSWORD]` + ` host=${host} port=${port}` + ` database=${name}`
|
||||
|
||||
return {
|
||||
psql: psqlConnString,
|
||||
@@ -39,3 +57,302 @@ export const getConnectionStrings = (connectionInfo: {
|
||||
python: pythonConnString,
|
||||
}
|
||||
}
|
||||
|
||||
// [Joshen] This is to the best of interpreting the syntax from the API response
|
||||
// // There's different format for PG13 (depending on authentication method being md5) and PG14
|
||||
export const constructConnStringSyntax = (
|
||||
connString: string,
|
||||
{
|
||||
selectedTab,
|
||||
usePoolerConnection,
|
||||
ref,
|
||||
cloudProvider,
|
||||
region,
|
||||
tld,
|
||||
portNumber,
|
||||
}: {
|
||||
selectedTab: 'uri' | 'psql' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python'
|
||||
usePoolerConnection: boolean
|
||||
ref: string
|
||||
cloudProvider: string
|
||||
region: string
|
||||
tld: string
|
||||
portNumber: string
|
||||
}
|
||||
) => {
|
||||
const isMd5 = connString.includes('?options=reference')
|
||||
const poolerHostDetails = [
|
||||
{ value: cloudProvider.toLocaleLowerCase(), tooltip: 'Cloud provider' },
|
||||
{ value: '-0-', tooltip: undefined },
|
||||
{ value: region, tooltip: "Project's region" },
|
||||
{ value: `.pooler.supabase.${tld}`, tooltip: undefined },
|
||||
]
|
||||
const dbHostDetails = [
|
||||
{ value: 'db.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
{ value: `.supabase.${tld}`, tooltip: undefined },
|
||||
]
|
||||
|
||||
if (selectedTab === 'uri' || selectedTab === 'nodejs') {
|
||||
if (isMd5) {
|
||||
return [
|
||||
{ value: 'postgres://', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: '@', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: `?options=reference%3D`, tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'postgres://', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: '@', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab === 'psql') {
|
||||
if (isMd5) {
|
||||
return [
|
||||
{ value: 'psql "postgresql://', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: '@', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '?options=reference%3D', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'psql -h ', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ' -p ', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ' -d ', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
{ value: ' -U ', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab === 'golang' || selectedTab === 'php') {
|
||||
if (isMd5) {
|
||||
return [
|
||||
{ value: 'user=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: ' password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: ' host=', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ' port=', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ' dbname=', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: ' options=reference=', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'user=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
{ value: ' password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: ' host=', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ' port=', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ' dbname=', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab === 'jdbc') {
|
||||
if (isMd5) {
|
||||
return [
|
||||
{ value: 'jdbc:postgresql://', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ':', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
{ value: '?user=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: '&password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '&options=reference%3D', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'jdbc:postgresql://', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: `:`, tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: '/', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
{ value: '?user=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
{ value: '&password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab === 'dotnet') {
|
||||
if (isMd5) {
|
||||
return [
|
||||
{ value: 'User Id=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: ';Password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: ';Server=', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ';Port=', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ';Database=', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: ";Options='reference=", tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
{ value: "'", tooltip: undefined },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'User Id=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
{ value: ';Password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: ';Server=', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ';Port=', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ';Database=', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if ('python') {
|
||||
if (isMd5) {
|
||||
return [
|
||||
{ value: 'user=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
{ value: ' password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: ' host=', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ' port=', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ' database=', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: ' options=reference=', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ value: 'user=', tooltip: undefined },
|
||||
{ value: '[user]', tooltip: 'Database user (e.g postgres)' },
|
||||
...(usePoolerConnection
|
||||
? [
|
||||
{ value: '.', tooltip: undefined },
|
||||
{ value: ref, tooltip: "Project's reference ID" },
|
||||
]
|
||||
: []),
|
||||
{ value: ' password=', tooltip: undefined },
|
||||
{ value: '[password]', tooltip: 'Database password' },
|
||||
{ value: ' host=', tooltip: undefined },
|
||||
...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
|
||||
{ value: ' port=', tooltip: undefined },
|
||||
{ value: portNumber, tooltip: 'Port number (Use 5432 if using prepared statements)' },
|
||||
{ value: ' database=', tooltip: undefined },
|
||||
{ value: '[db-name]', tooltip: 'Database name (e.g postgres)' },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export const getPoolerTld = (connString: string) => {
|
||||
try {
|
||||
const segment = connString.split('pooler.supabase.')[1]
|
||||
const tld = segment.split(':6543')[0]
|
||||
return tld
|
||||
} catch {
|
||||
return 'com'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Alert_Shadcn_,
|
||||
IconAlertTriangle,
|
||||
AlertTitle_Shadcn_,
|
||||
AlertDescription_Shadcn_,
|
||||
Button,
|
||||
IconExternalLink,
|
||||
} from 'ui'
|
||||
|
||||
export const IPv4DeprecationNotice = () => {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Direct database access via IPv4 and pgBouncer will be removed from January 26th 2024
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="space-y-3">
|
||||
<p>
|
||||
We strongly recommend using <span className="text-foreground">connection pooling</span> to
|
||||
connect to your database. You'll only need to change the connection string that you're
|
||||
using in your application to the pooler's connection string.
|
||||
</p>
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a
|
||||
href="https://github.com/orgs/supabase/discussions/17817"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useParams } from 'common'
|
||||
import { Badge, Checkbox_Shadcn_ } from 'ui'
|
||||
|
||||
import { Markdown } from 'components/interfaces/Markdown'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
|
||||
interface UsePoolerCheckboxInterface {
|
||||
id: string
|
||||
checked: boolean
|
||||
onCheckedChange: (value: boolean) => void
|
||||
}
|
||||
|
||||
export const UsePoolerCheckbox = ({ id, checked, onCheckedChange }: UsePoolerCheckboxInterface) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const { data, isSuccess } = usePoolingConfigurationQuery({ projectRef })
|
||||
|
||||
// [Joshen] TODO this needs to be obtained from BE as 26th Jan is when we'll start - projects will be affected at different rates
|
||||
const resolvesToIpV6 = !data?.supavisor_enabled && false // Number(new Date()) > Number(dayjs.utc('01-26-2024', 'MM-DD-YYYY').toDate())
|
||||
|
||||
return (
|
||||
<div className="flex gap-x-3">
|
||||
<Checkbox_Shadcn_
|
||||
id={`use-pooler-${id}`}
|
||||
checked={checked}
|
||||
onCheckedChange={() => onCheckedChange(!checked)}
|
||||
/>
|
||||
<div className="-mt-[2px] flex flex-col gap-y-1 w-full">
|
||||
<label htmlFor={`use-pooler-${id}`} className="text-sm cursor-pointer">
|
||||
Use connection pooling
|
||||
{isSuccess && checked && data.supavisor_enabled && (
|
||||
<Badge color="scale" className="ml-2">
|
||||
Supavisor
|
||||
</Badge>
|
||||
)}
|
||||
<Badge color="scale" className="ml-2">
|
||||
{checked
|
||||
? 'Resolves to IPv4'
|
||||
: resolvesToIpV6
|
||||
? 'Resolves to IPv6'
|
||||
: 'Resolves to IPv4'}
|
||||
</Badge>
|
||||
</label>
|
||||
<Markdown
|
||||
extLinks
|
||||
className="[&>p]:m-0 space-y-1 text-foreground-lighter max-w-full"
|
||||
content={`
|
||||
IPv4 and IPv6 connections will resolve while using connection pooling\n
|
||||
A connection pooler is useful for managing a large number of temporary connections. [Learn more](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pooler)`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,11 +2,14 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'data/fetchers'
|
||||
import { ResponseError } from 'types'
|
||||
import { databaseKeys } from './keys'
|
||||
import { components } from 'data/api'
|
||||
|
||||
export type PoolingConfigurationVariables = {
|
||||
projectRef?: string
|
||||
}
|
||||
|
||||
export type PoolingConfiguration = components['schemas']['PgbouncerConfigResponse']
|
||||
|
||||
export async function getPoolingConfiguration(
|
||||
{ projectRef }: PoolingConfigurationVariables,
|
||||
signal?: AbortSignal
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { SettingsLayout } from 'components/layouts'
|
||||
import {
|
||||
ConnectionPooling,
|
||||
DatabaseSettings,
|
||||
NetworkRestrictions,
|
||||
} from 'components/interfaces/Settings/Database'
|
||||
import { SettingsLayout } from 'components/layouts'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
|
||||
import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration'
|
||||
import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
|
||||
import BannedIPs from 'components/interfaces/Settings/Database/BannedIPs'
|
||||
import { DatabaseConnectionString } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString'
|
||||
import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
|
||||
import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration'
|
||||
import { DatabaseReadOnlyAlert } from 'components/interfaces/Settings/Database/DatabaseReadOnlyAlert'
|
||||
|
||||
const ProjectSettings: NextPageWithLayout = () => {
|
||||
return (
|
||||
<div className="1xl:px-28 mx-auto flex flex-col px-5 pt-6 pb-14 lg:px-16 xl:px-24 2xl:px-32">
|
||||
<div className="content h-full w-full overflow-y-auto space-y-10">
|
||||
<div className="content h-full w-full overflow-y-auto space-y-6">
|
||||
<h3 className="text-foreground text-xl">Database Settings</h3>
|
||||
<div className="flex flex-col gap-y-4 !mt-6">
|
||||
<ConnectionPooling />
|
||||
<DatabaseSettings />
|
||||
<div className="space-y-10">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<DatabaseReadOnlyAlert />
|
||||
<DatabaseConnectionString />
|
||||
<DatabaseSettings />
|
||||
<ConnectionPooling />
|
||||
</div>
|
||||
|
||||
<SSLConfiguration />
|
||||
<DiskSizeConfiguration />
|
||||
<NetworkRestrictions />
|
||||
<BannedIPs />
|
||||
</div>
|
||||
<SSLConfiguration />
|
||||
<DiskSizeConfiguration />
|
||||
<NetworkRestrictions />
|
||||
<BannedIPs />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user