diff --git a/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx b/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx index f92c95ae9b9..266c01642b4 100644 --- a/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx @@ -7,10 +7,11 @@ import { CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_, DIALOG_PADDING_X, + WarningIcon, } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { ConnectionParameters } from './ConnectionParameters' -import { SessionIcon, TransactionIcon } from './pooler-icons-v2' +import { DirectConnectionIcon, SessionIcon, TransactionIcon } from './pooler-icons-v2' import { ChevronRight } from 'lucide-react' interface ConnectionPanelProps { @@ -25,7 +26,7 @@ interface ConnectionPanelProps { description?: string link?: { text: string; url: string } } - notice?: string + notice?: string[] parameters?: Array<{ key: string value: string @@ -106,140 +107,176 @@ export const ConnectionPanel = ({ fileTitle, }: ConnectionPanelProps) => { return ( -
-
-
-

{title}

-

{description}

-
- {/* {contentType === 'input' ? ( */} - {false ? ( - +
+

{title}

+

{description}

+
+ {/* {contentType === 'input' ? ( */} + {false ? ( + + ) : ( + <> + {fileTitle && } + - ) : ( - <> - {fileTitle && } - - - )} - {notice && ( -
- {notice} -
- )} - {parameters.length > 0 && } -
+ + )} + {notice && ( +
+ {notice?.map((text: string) => ( +

+ {text} +

+ ))} +
+ )} + {parameters.length > 0 && }
-
-
- {type !== 'direct' && ( - <> -
-
- {type === 'transaction' ? : } -
-
- - {type === 'transaction' - ? 'Suitable for a large number of connected clients' - : 'Not suitable if you have a large number of clients'} - -
+
+
+
+ {type !== 'session' && ( + <> +
+
+ {type === 'transaction' ? : }
-
-
- - {type === 'transaction' - ? 'Pre-warmed connection pool to the database' - : 'Connections from the pooler are not shared'} - -
+
+ + {type === 'transaction' + ? 'Suitable for a large number of connected clients' + : 'Suitable for long-lived, persistent connections'} +
- - )} +
+
+
+ + {type === 'transaction' + ? 'Pre-warmed connection pool to Postgres' + : 'Each client has a dedicated connection to Postgres'} + +
+
+ + )} -
-
- +
+
+ +
+
+ {ipv4Status.title} + {ipv4Status.description && ( + {ipv4Status.description} + )} + {/* {ipv4Status.type === 'success' && type !== 'transaction' && ( + + Use Direct Connection if on a IPv6 network + + )} */} + {ipv4Status.type === 'error' && ( + + Use Session Pooler if on a IPv4 network or purchase IPv4 addon + + )} + {ipv4Status.link && ( + + )} +
+
+ + {type === 'session' && ( +
+
+
- {ipv4Status.title} - {ipv4Status.description && ( - {ipv4Status.description} - )} - {ipv4Status.link && ( - - {ipv4Status.link.text} - - )} + Only use on a IPv4 network + + Use Direct Connection if connecting via a IPv6 network +
+ )} - {ipv4Status.type === 'error' && ( - - + + - - -
-

- A few major platforms are IPv4-only and may not work with a Direct Connection: -

-
-
Retool
-
GitHub Actions
-
Vercel
-
Render
-
-

- If you wish to use a Direct Connection with these please purchase{' '} - - IPv4 support - - . -

+ Some platforms are IPv4-only: + + + +
+

+ A few major platforms are IPv4-only and may not work with a Direct Connection: +

+
+
Vercel
+
GitHub Actions
+
Render
+
Retool
- - - )} -
+

+ If you wish to use a Direct Connection with these please purchase{' '} + + IPv4 support + + . +

+

+ You may also use the{' '} + Session Pooler or{' '} + Transaction Pooler if you are on + a IPv4 network. +

+
+
+
+ )}
diff --git a/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx b/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx index eb4cf197858..83e9a44c80f 100644 --- a/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx @@ -39,7 +39,7 @@ export const ConnectionParameters = ({ parameters }: ConnectionParametersProps)
)} -
+
{ ipv4Status={{ type: !ipv4Addon ? 'error' : 'success', title: !ipv4Addon ? 'Not IPv4 compatible' : 'IPv4 compatible', + description: ipv4Addon && 'Connections are IPv4 proxied with IPv4 addon.', link: !ipv4Addon ? { - text: 'Purchase IPv4 support', + text: 'IPv4 addon', url: `/project/${projectRef}/settings/addons?panel=ipv4`, } : { - text: 'Update IPv4 settings', + text: 'IPv4 settings', url: `/project/${projectRef}/settings/addons?panel=ipv4`, }, }} @@ -347,7 +349,7 @@ export const DatabaseConnectionString = () => { type="transaction" title="Transaction pooler" fileTitle={fileTitle} - description="Ideal for stateless applications like serverless functions where each interaction with the database is brief and isolated." + description="Ideal for stateless applications like serverless functions where each interaction with Postgres is brief and isolated." connectionString={connectionStrings['pooler'][selectedTab]} onCopy={() => handleCopy(selectedTab)} ipv4Status={{ @@ -355,7 +357,7 @@ export const DatabaseConnectionString = () => { title: 'IPv4 compatible', description: 'Transaction pooler connections are IPv4 proxied for free.', }} - notice="Transaction pooler does not support prepared statements" + notice={['Does not support PREPARE statements']} parameters={[ // prettier-ignore { ...CONNECTION_PARAMETERS.host, value: `${projectRef}.pooler.supabase.${poolerTld}` }, @@ -369,13 +371,23 @@ export const DatabaseConnectionString = () => { { ...CONNECTION_PARAMETERS.pool_mode, value: 'transaction', description: 'Each transaction uses a different connection' }, ]} /> + +

+ If you are using Session Pooler, we recommend switching to Direct Connection. +

+
+ handleCopy(selectedTab)} ipv4Status={{ diff --git a/apps/studio/components/interfaces/Connect/pooler-icons-v2.tsx b/apps/studio/components/interfaces/Connect/pooler-icons-v2.tsx index 0963917591d..1ab5554d06a 100644 --- a/apps/studio/components/interfaces/Connect/pooler-icons-v2.tsx +++ b/apps/studio/components/interfaces/Connect/pooler-icons-v2.tsx @@ -154,7 +154,7 @@ const RECT_WIDTH = ICON_WIDTH * 0.67 // ~67% of total width const CIRCLE_Y = ICON_HEIGHT * 0.13 // 13% from top const CIRCLE_SPACING = ICON_WIDTH * 0.25 // 25% of width const CIRCLE_START_X = ICON_WIDTH * 0.25 // 25% from left -const CIRCLE_RADIUS = ICON_WIDTH * 0.038 // ~3.8% of width +const CIRCLE_RADIUS = ICON_WIDTH * 0.055 // ~3.8% of width // Static circle for SessionIcon const ConnectionDot = ({ index, isActive }: { index: number; isActive: boolean }) => ( @@ -385,3 +385,102 @@ export const SessionIcon = () => {
) } + +export const DirectConnectionIcon = () => { + const [dots, setDots] = useState([false, false, false]) + const [lines, setLines] = useState([false, false, false]) + + useEffect(() => { + // Function to animate a single dot + const animateDot = (index: number) => { + setDots((prev) => { + const newState = [...prev] + newState[index] = true + return newState + }) + setLines((prev) => { + const newState = [...prev] + newState[index] = true + return newState + }) + + // Clear after 2.5s + setTimeout(() => { + setDots((prev) => { + const newState = [...prev] + newState[index] = false + return newState + }) + setLines((prev) => { + const newState = [...prev] + newState[index] = false + return newState + }) + }, 2500) + } + + // Initial staggered animation + // Start initial animations immediately with slight delays + setTimeout(() => animateDot(0), 100) + setTimeout(() => animateDot(1), 1500) + setTimeout(() => animateDot(2), 3000) + + // Set up intervals for continuous animation + const intervals = [0, 1, 2].map((index) => + setInterval(() => animateDot(index), Math.random() * 3000 + 8000) + ) + + return () => intervals.forEach(clearInterval) + }, []) + + return ( +
+ + {[0, 1, 2].map((index) => ( + + + {dots[index] && ( + + )} + + + ))} + state)} /> + + {[0, 1, 2].map((index) => ( + + ))} +
+ ) +}