diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx b/apps/studio/components/interfaces/Connect/ConnectTabs.tsx
similarity index 81%
rename from apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx
rename to apps/studio/components/interfaces/Connect/ConnectTabs.tsx
index f5871de3f9147..ec7b1b5a61fe8 100644
--- a/apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx
+++ b/apps/studio/components/interfaces/Connect/ConnectTabs.tsx
@@ -1,8 +1,7 @@
-import { Tabs_Shadcn_ } from 'ui'
import { FileJson2 } from 'lucide-react'
-import { TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
-import { TabsContent_Shadcn_ } from 'ui'
-import React, { ReactNode } from 'react'
+import { isValidElement, ReactNode } from 'react'
+
+import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
interface ConnectTabTriggerProps {
value: string
@@ -22,7 +21,7 @@ interface ConnectTabContentProps {
const ConnectTabs = ({ children }: ConnectFileTabProps) => {
const firstChild = children[0]
- const defaultValue = React.isValidElement(firstChild)
+ const defaultValue = isValidElement(firstChild)
? (firstChild.props as any)?.children[0]?.props?.value || ''
: null
@@ -57,4 +56,4 @@ export const ConnectTabContent = ({ value, children }: ConnectTabContentProps) =
)
}
-export { ConnectTabTrigger, ConnectTabTriggers, ConnectTabs }
+export { ConnectTabs, ConnectTabTrigger, ConnectTabTriggers }
diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx b/apps/studio/components/interfaces/Connect/ConnectionIcon.tsx
similarity index 89%
rename from apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx
rename to apps/studio/components/interfaces/Connect/ConnectionIcon.tsx
index a79acebc468bf..fd2452d29619d 100644
--- a/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx
+++ b/apps/studio/components/interfaces/Connect/ConnectionIcon.tsx
@@ -1,12 +1,13 @@
-import { BASE_PATH } from 'lib/constants'
-
import { useTheme } from 'next-themes'
import Image from 'next/image'
+
+import { BASE_PATH } from 'lib/constants'
+
interface ConnectionIconProps {
connection: any
}
-const ConnectionIcon = ({ connection }: ConnectionIconProps) => {
+export const ConnectionIcon = ({ connection }: ConnectionIconProps) => {
const { resolvedTheme } = useTheme()
const imageFolder = ['ionic-angular'].includes(connection) ? 'icons/frameworks' : 'libraries'
@@ -28,5 +29,3 @@ const ConnectionIcon = ({ connection }: ConnectionIconProps) => {
/>
)
}
-
-export default ConnectionIcon
diff --git a/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx b/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx
new file mode 100644
index 0000000000000..bce5602468da8
--- /dev/null
+++ b/apps/studio/components/interfaces/Connect/ConnectionPanel.tsx
@@ -0,0 +1,265 @@
+import { ChevronRight, FileCode, X } from 'lucide-react'
+import Link from 'next/link'
+
+import {
+ Button,
+ cn,
+ CodeBlock,
+ CodeBlockLang,
+ Collapsible_Shadcn_,
+ CollapsibleContent_Shadcn_,
+ CollapsibleTrigger_Shadcn_,
+ WarningIcon,
+} from 'ui'
+import { ConnectionParameters } from './ConnectionParameters'
+import { DirectConnectionIcon, TransactionIcon } from './PoolerIcons'
+
+interface ConnectionPanelProps {
+ type?: 'direct' | 'transaction' | 'session'
+ title: string
+ description: string
+ connectionString: string
+ ipv4Status: {
+ type: 'error' | 'success'
+ title: string
+ description?: string
+ link?: { 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 (
+
+
+
+
+
+ {!active ? (
+
+
+
+ ) : (
+
+ )}
+
+ )
+}
+
+export const CodeBlockFileHeader = ({ title }: { title: string }) => {
+ return (
+
+ )
+}
+
+export const ConnectionPanel = ({
+ type = 'direct',
+ title,
+ description,
+ connectionString,
+ ipv4Status,
+ notice,
+ parameters = [],
+ lang = 'bash',
+ fileTitle,
+ onCopyCallback,
+}: ConnectionPanelProps) => {
+ return (
+
+
+
{title}
+
{description}
+
+ {fileTitle &&
}
+
+ {notice && (
+
+ {notice?.map((text: string) => (
+
+ {text}
+
+ ))}
+
+ )}
+ {parameters.length > 0 &&
}
+
+
+
+
+ {type !== 'session' && (
+ <>
+
+
+ {type === 'transaction' ? : }
+
+
+
+ {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 === 'error' && (
+
+ Use Session Pooler if on a IPv4 network or purchase IPv4 addon
+
+ )}
+ {ipv4Status.link && (
+
+
+
+ {ipv4Status.link.text}
+
+
+
+ )}
+
+
+
+ {type === 'session' && (
+
+
+
+
+
+ Only use on a IPv4 network
+
+ Use Direct Connection if connecting via an IPv6 network
+
+
+
+ )}
+
+ {ipv4Status.type === 'error' && (
+
+
+
+ }
+ >
+ 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
new file mode 100644
index 0000000000000..6a5e4ba168717
--- /dev/null
+++ b/apps/studio/components/interfaces/Connect/ConnectionParameters.tsx
@@ -0,0 +1,91 @@
+import { Check, ChevronRight, Copy } from 'lucide-react'
+import { useState } from 'react'
+
+import { copyToClipboard } from 'lib/helpers'
+import {
+ Button,
+ cn,
+ Collapsible_Shadcn_,
+ CollapsibleContent_Shadcn_,
+ CollapsibleTrigger_Shadcn_,
+ Separator,
+} from 'ui'
+
+interface Parameter {
+ key: string
+ value: string
+ description?: string
+}
+
+interface ConnectionParametersProps {
+ parameters: Parameter[]
+}
+
+export const ConnectionParameters = ({ parameters }: ConnectionParametersProps) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const [copiedMap, setCopiedMap] = useState
>({})
+
+ return (
+
+
+
+ }
+ >
+ View parameters
+
+
+
+
+ {parameters.map((param) => (
+
+
+ {param.key}:
+ {param.value}
+ {
+ copyToClipboard(param.value, () => {
+ setCopiedMap((prev) => ({ ...prev, [param.key]: true }))
+ setTimeout(() => {
+ setCopiedMap((prev) => ({ ...prev, [param.key]: false }))
+ }, 1000)
+ })
+ }}
+ className={cn(
+ 'text-foreground-lighter',
+ 'ml-2 opacity-0 group-hover/param:opacity-100',
+ 'hover:text-foreground rounded-sm p-1',
+ copiedMap[param.key] && 'opacity-100',
+ 'transition-all'
+ )}
+ >
+ {copiedMap[param.key] ? (
+
+ ) : (
+
+ )}
+
+
+
+ ))}
+
+
+
+ For security reasons, your database password is never shown.
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx
new file mode 100644
index 0000000000000..cd489d0dc0be4
--- /dev/null
+++ b/apps/studio/components/interfaces/Connect/DatabaseConnectionString.tsx
@@ -0,0 +1,477 @@
+import { ChevronDown } from 'lucide-react'
+import { HTMLAttributes, ReactNode, useState } from 'react'
+
+import { useParams } from 'common'
+import { getAddons } from 'components/interfaces/Billing/Subscription/Subscription.utils'
+import AlertError from 'components/ui/AlertError'
+import DatabaseSelector from 'components/ui/DatabaseSelector'
+import ShimmeringLoader from 'components/ui/ShimmeringLoader'
+import { usePoolingConfigurationQuery } from 'data/database/pooling-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 { pluckObjectFields } from 'lib/helpers'
+import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
+import {
+ CodeBlock,
+ CollapsibleContent_Shadcn_,
+ CollapsibleTrigger_Shadcn_,
+ Collapsible_Shadcn_,
+ DIALOG_PADDING_X,
+ SelectContent_Shadcn_,
+ SelectItem_Shadcn_,
+ SelectTrigger_Shadcn_,
+ SelectValue_Shadcn_,
+ Select_Shadcn_,
+ Separator,
+ cn,
+} from 'ui'
+import { Admonition } from 'ui-patterns'
+import {
+ CONNECTION_PARAMETERS,
+ DATABASE_CONNECTION_TYPES,
+ DatabaseConnectionType,
+} from './Connect.constants'
+import { CodeBlockFileHeader, ConnectionPanel } from './ConnectionPanel'
+import { getConnectionStrings, getPoolerTld } from './DatabaseSettings.utils'
+import examples, { Example } from './DirectConnectionExamples'
+
+const StepLabel = ({
+ number,
+ children,
+ ...props
+}: { number: number; children: ReactNode } & HTMLAttributes) => (
+
+
+ {number}
+
+
{children}
+
+)
+
+export const DatabaseConnectionString = () => {
+ const { ref: projectRef } = useParams()
+ const state = useDatabaseSelectorStateSnapshot()
+
+ const [selectedTab, setSelectedTab] = useState('uri')
+
+ const {
+ data: poolingInfo,
+ error: poolingInfoError,
+ isLoading: isLoadingPoolingInfo,
+ isError: isErrorPoolingInfo,
+ isSuccess: isSuccessPoolingInfo,
+ } = usePoolingConfigurationQuery({
+ projectRef,
+ })
+ const poolingConfiguration = poolingInfo?.find((x) => x.identifier === state.selectedDatabaseId)
+
+ const {
+ data: databases,
+ error: readReplicasError,
+ isLoading: isLoadingReadReplicas,
+ isError: isErrorReadReplicas,
+ isSuccess: isSuccessReadReplicas,
+ } = useReadReplicasQuery({ projectRef })
+
+ const error = poolingInfoError || readReplicasError
+ const isLoading = isLoadingPoolingInfo || isLoadingReadReplicas
+ const isError = isErrorPoolingInfo || isErrorReadReplicas
+ const isSuccess = isSuccessPoolingInfo && isSuccessReadReplicas
+
+ const selectedDatabase = (databases ?? []).find(
+ (db) => db.identifier === state.selectedDatabaseId
+ )
+
+ 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 = (id: string) => {
+ const labelValue = DATABASE_CONNECTION_TYPES.find((type) => type.id === id)?.label
+ sendEvent({
+ category: 'settings',
+ action: 'copy_connection_string',
+ label: labelValue ?? '',
+ })
+ }
+
+ const connectionStrings =
+ isSuccessPoolingInfo && poolingConfiguration !== undefined
+ ? getConnectionStrings(connectionInfo, poolingConfiguration, {
+ projectRef,
+ })
+ : {
+ direct: {
+ uri: '',
+ psql: '',
+ golang: '',
+ jdbc: '',
+ dotnet: '',
+ nodejs: '',
+ php: '',
+ python: '',
+ sqlalchemy: '',
+ },
+ pooler: {
+ uri: '',
+ psql: '',
+ golang: '',
+ jdbc: '',
+ dotnet: '',
+ nodejs: '',
+ php: '',
+ python: '',
+ sqlalchemy: '',
+ },
+ }
+
+ const poolerTld =
+ isSuccessPoolingInfo && poolingConfiguration !== undefined
+ ? getPoolerTld(poolingConfiguration?.connectionString)
+ : 'com'
+
+ // @mildtomato - Possible reintroduce later
+ //
+ // const poolerConnStringSyntax =
+ // isSuccessPoolingInfo && poolingConfiguration !== undefined
+ // ? constructConnStringSyntax(poolingConfiguration?.connectionString, {
+ // selectedTab,
+ // usePoolerConnection: snap.usePoolerConnection,
+ // ref: projectRef as string,
+ // cloudProvider: isProjectLoading ? '' : project?.cloud_provider || '',
+ // region: isProjectLoading ? '' : project?.region || '',
+ // tld: snap.usePoolerConnection ? poolerTld : connectionTld,
+ // portNumber: `[5432 or 6543]`,
+ // })
+ // : []
+ // useEffect(() => {
+ // // if (poolingConfiguration?.pool_mode === 'session') {
+ // // setPoolingMode(poolingConfiguration.pool_mode)
+ // // }
+ // }, [poolingConfiguration?.pool_mode])
+
+ 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
+
+ return (
+
+
+
+
+ Type
+
+
+ setSelectedTab(connectionType)
+ }
+ >
+
+
+
+
+ {DATABASE_CONNECTION_TYPES.map((type) => (
+
+ {type.label}
+
+ ))}
+
+
+
+
+
+
+ {isLoading && (
+
+
+
+ )}
+
+ {isError && (
+
+ )}
+
+ {isSuccess && (
+
+ {/* // handle non terminal examples */}
+ {hasCodeExamples && (
+
+
+
+ Install the following
+
+ {exampleInstallCommands?.map((cmd, i) => (
+
+ {cmd}
+
+ ))}
+
+ {exampleFiles && exampleFiles?.length > 0 && (
+
+
+ Add file to project
+
+ {exampleFiles?.map((file, i) => (
+
+
+
+
+ ))}
+
+ )}
+
+ )}
+
+
+ {hasCodeExamples && (
+
+ Choose type of connection
+
+ )}
+
+
handleCopy(selectedTab)}
+ />
+ handleCopy(selectedTab)}
+ />
+ {ipv4Addon && (
+
+
+ If you are using Session Pooler, we recommend switching to Direct Connection.
+
+
+ )}
+
+ handleCopy(selectedTab)}
+ />
+
+
+ {examplePostInstallCommands && (
+
+
+
+ Add the configuration package to read the settings
+
+ {examplePostInstallCommands?.map((cmd, i) => (
+
+ {cmd}
+
+ ))}
+
+
+ )}
+
+ )}
+
+ {/* Possibly reintroduce later - @mildtomato */}
+ {/*
+
+
+
+
+ How to connect to a different database or switch to another user
+
+
+
+
+
+
+
+ You can use the following URI format to switch to a different database or user
+ {snap.usePoolerConnection ? ' when using connection pooling' : ''}.
+
+
+ {poolerConnStringSyntax.map((x, idx) => {
+ if (x.tooltip) {
+ return (
+
+
+ {x.value}
+
+ {x.tooltip}
+
+ )
+ } else {
+ return (
+
+ {x.value}
+
+ )
+ }
+ })}
+
+
+
+ */}
+
+ {selectedTab === 'python' && (
+ <>
+
+
+
+
+
+ Connecting to SQL Alchemy
+
+
+
+
+
+
+
+ Please use postgresql:// instead of postgres:// as your
+ dialect when connecting via SQLAlchemy.
+
+
+ Example:
+ create_engine("postgresql+psycopg2://...")
+
+
+
+
+
+ >
+ )}
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Connect/DatabaseSettings.utils.ts b/apps/studio/components/interfaces/Connect/DatabaseSettings.utils.ts
new file mode 100644
index 0000000000000..398789c05d7b3
--- /dev/null
+++ b/apps/studio/components/interfaces/Connect/DatabaseSettings.utils.ts
@@ -0,0 +1,388 @@
+import type { PoolingConfiguration } from 'data/database/pooling-configuration-query'
+
+type ConnectionStrings = {
+ psql: string
+ uri: string
+ golang: string
+ jdbc: string
+ dotnet: string
+ nodejs: string
+ php: string
+ python: string
+ sqlalchemy: string
+}
+
+export const getConnectionStrings = (
+ connectionInfo: {
+ db_user: string
+ db_port: number
+ db_host: string
+ db_name: string
+ },
+ poolingInfo: PoolingConfiguration,
+ metadata: {
+ projectRef?: string
+ pgVersion?: string
+ }
+): {
+ direct: ConnectionStrings
+ pooler: ConnectionStrings
+} => {
+ const isMd5 = poolingInfo.connectionString.includes('options=reference')
+ const { projectRef } = metadata
+ const password = '[YOUR-PASSWORD]'
+
+ // Direct connection variables
+ const directUser = connectionInfo.db_user
+ const directPort = connectionInfo.db_port
+ const directHost = connectionInfo.db_host
+ const directName = connectionInfo.db_name
+
+ // Pooler connection variables
+ const poolerUser = poolingInfo.db_user
+ const poolerPort = poolingInfo.db_port
+ const poolerHost = poolingInfo.db_host
+ const poolerName = poolingInfo.db_name
+
+ // Direct connection strings
+ const directPsqlString = isMd5
+ ? `psql "postgresql://${directUser}:${password}@${directHost}:${directPort}/${directName}"`
+ : `psql -h ${directHost} -p ${directPort} -d ${directName} -U ${directUser}`
+
+ const directUriString = `postgresql://${directUser}:${password}@${directHost}:${directPort}/${directName}`
+
+ const directGolangString = `DATABASE_URL=${poolingInfo.connectionString}`
+
+ const directJdbcString = `jdbc:postgresql://${directHost}:${directPort}/${directName}?user=${directUser}&password=${password}`
+
+ // User Id=${directUser};Password=${password};Server=${directHost};Port=${directPort};Database=${directName}`
+ const directDotNetString = `{
+ "ConnectionStrings": {
+ "DefaultConnection": "Host=${directHost};Database=${directName};Username=${directUser};Password=${password};SSL Mode=Require;Trust Server Certificate=true"
+ }
+}`
+
+ // `User Id=${poolerUser};Password=${password};Server=${poolerHost};Port=${poolerPort};Database=${poolerName}${isMd5 ? `;Options='reference=${projectRef}'` : ''}`
+ const poolerDotNetString = `{
+ "ConnectionStrings": {
+ "DefaultConnection": "User Id=${poolerUser};Password=${password};Server=${poolerHost};Port=${poolerPort};Database=${poolerName}${isMd5 ? `;Options='reference=${projectRef}'` : ''}"
+ }
+}`
+
+ // Pooler connection strings
+ const poolerPsqlString = isMd5
+ ? `psql "postgresql://${poolerUser}:${password}@${poolerHost}:${poolerPort}/${poolerName}?options=reference%3D${projectRef}"`
+ : `psql -h ${poolerHost} -p ${poolerPort} -d ${poolerName} -U ${poolerUser}.${projectRef}`
+
+ const poolerUriString = poolingInfo.connectionString
+
+ const nodejsPoolerUriString = `DATABASE_URL=${poolingInfo.connectionString}`
+
+ const poolerGolangString = `user=${poolerUser}
+password=${password}
+host=${poolerHost}
+port=${poolerPort}
+dbname=${poolerName}${isMd5 ? `options=reference=${projectRef}` : ''}`
+
+ const poolerJdbcString = `jdbc:postgresql://${poolerHost}:${poolerPort}/${poolerName}?user=${poolerUser}${isMd5 ? `&options=reference%3D${projectRef}` : ''}&password=${password}`
+
+ const sqlalchemyString = `user=${directUser}
+password=${password}
+host=${directHost}
+port=${directPort}
+dbname=${directName}`
+
+ const poolerSqlalchemyString = `user=${poolerUser}
+password=${password}
+host=${poolerHost}
+port=${poolerPort}
+dbname=${poolerName}`
+
+ return {
+ direct: {
+ psql: directPsqlString,
+ uri: directUriString,
+ golang: directGolangString,
+ jdbc: directJdbcString,
+ dotnet: directDotNetString,
+ nodejs: nodejsPoolerUriString,
+ php: directGolangString,
+ python: directGolangString,
+ sqlalchemy: sqlalchemyString,
+ },
+ pooler: {
+ psql: poolerPsqlString,
+ uri: poolerUriString,
+ golang: poolerGolangString,
+ jdbc: poolerJdbcString,
+ dotnet: poolerDotNetString,
+ nodejs: nodejsPoolerUriString,
+ php: poolerGolangString,
+ python: poolerGolangString,
+ sqlalchemy: poolerSqlalchemyString,
+ },
+ }
+}
+
+const DB_USER_DESC = 'Database user (e.g postgres)'
+const DB_PASS_DESC = 'Database password'
+const DB_NAME_DESC = 'Database name (e.g postgres)'
+const PROJECT_REF_DESC = "Project's reference ID"
+const PORT_NUMBER_DESC = 'Port number (Use 5432 if using prepared statements)'
+
+// [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_REF_DESC },
+ { value: `.supabase.${tld}`, tooltip: undefined },
+ ]
+
+ if (selectedTab === 'uri' || selectedTab === 'nodejs') {
+ if (isMd5) {
+ return [
+ { value: 'postgresql://', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ { value: ':', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: '@', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: `?options=reference%3D`, tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'postgresql://', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ { value: ':', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: '@', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ]
+ }
+ }
+
+ if (selectedTab === 'psql') {
+ if (isMd5) {
+ return [
+ { value: 'psql "postgresql://', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ { value: ':', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: '@', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '?options=reference%3D', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'psql -h ', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' -p ', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: ' -d ', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ { value: ' -U ', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ ]
+ }
+ }
+
+ if (selectedTab === 'golang' || selectedTab === 'php' || selectedTab === 'python') {
+ if (isMd5) {
+ return [
+ { value: 'user=', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ { value: ' password=', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: ' host=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' port=', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: ' dbname=', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: ' options=reference=', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'user=', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ { value: ' password=', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: ' host=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ' port=', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: ' dbname=', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ]
+ }
+ }
+
+ if (selectedTab === 'jdbc') {
+ if (isMd5) {
+ return [
+ { value: 'jdbc:postgresql://', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ':', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ { value: '?user=', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ { value: '&password=', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '&options=reference%3D', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'jdbc:postgresql://', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: `:`, tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: '/', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ { value: '?user=', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ { value: '&password=', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ ]
+ }
+ }
+
+ if (selectedTab === 'dotnet') {
+ if (isMd5) {
+ return [
+ { value: 'User Id=', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ { value: ';Password=', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: ';Server=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ';Port=', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: ';Database=', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: ";Options='reference=", tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ { value: "'", tooltip: undefined },
+ ]
+ : []),
+ ]
+ } else {
+ return [
+ { value: 'User Id=', tooltip: undefined },
+ { value: '[user]', tooltip: DB_USER_DESC },
+ ...(usePoolerConnection
+ ? [
+ { value: '.', tooltip: undefined },
+ { value: ref, tooltip: PROJECT_REF_DESC },
+ ]
+ : []),
+ { value: ';Password=', tooltip: undefined },
+ { value: '[password]', tooltip: DB_PASS_DESC },
+ { value: ';Server=', tooltip: undefined },
+ ...(usePoolerConnection ? poolerHostDetails : dbHostDetails),
+ { value: ';Port=', tooltip: undefined },
+ { value: portNumber, tooltip: PORT_NUMBER_DESC },
+ { value: ';Database=', tooltip: undefined },
+ { value: '[db-name]', tooltip: DB_NAME_DESC },
+ ]
+ }
+ }
+
+ 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'
+ }
+}
diff --git a/apps/studio/components/interfaces/Connect/DirectConnectionExamples.tsx b/apps/studio/components/interfaces/Connect/DirectConnectionExamples.tsx
new file mode 100644
index 0000000000000..80084d1337d57
--- /dev/null
+++ b/apps/studio/components/interfaces/Connect/DirectConnectionExamples.tsx
@@ -0,0 +1,153 @@
+export type Example = {
+ installCommands?: string[]
+ postInstallCommands?: string[]
+ files?: {
+ name: string
+ content: string
+ }[]
+}
+
+const examples = {
+ nodejs: {
+ installCommands: ['npm install postgres'],
+ files: [
+ {
+ name: 'db.js',
+ content: `import postgres from 'postgres'
+
+const connectionString = process.env.DATABASE_URL
+const sql = postgres(connectionString)
+
+export default sql`,
+ },
+ ],
+ },
+ golang: {
+ installCommands: ['go get github.com/jackc/pgx/v5'],
+ files: [
+ {
+ name: 'main.go',
+ content: `package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "github.com/jackc/pgx/v5"
+)
+
+func main() {
+ conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
+ if err != nil {
+ log.Fatalf("Failed to connect to the database: %v", err)
+ }
+ defer conn.Close(context.Background())
+
+ // Example query to test connection
+ var version string
+ if err := conn.QueryRow(context.Background(), "SELECT version()").Scan(&version); err != nil {
+ log.Fatalf("Query failed: %v", err)
+ }
+
+ log.Println("Connected to:", version)
+}`,
+ },
+ ],
+ },
+ dotnet: {
+ installCommands: [
+ 'dotnet add package Microsoft.Extensions.Configuration.Json --version YOUR_DOTNET_VERSION',
+ ],
+ postInstallCommands: [
+ 'dotnet add package Microsoft.Extensions.Configuration.Json --version YOUR_DOTNET_VERSION',
+ ],
+ },
+ python: {
+ installCommands: ['pip install python-dotenv psycopg2'],
+ files: [
+ {
+ name: 'main.py',
+ content: `import psycopg2
+from dotenv import load_dotenv
+import os
+
+# Load environment variables from .env
+load_dotenv()
+
+# Fetch variables
+USER = os.getenv("user")
+PASSWORD = os.getenv("password")
+HOST = os.getenv("host")
+PORT = os.getenv("port")
+DBNAME = os.getenv("dbname")
+
+# Connect to the database
+try:
+ connection = psycopg2.connect(
+ user=USER,
+ password=PASSWORD,
+ host=HOST,
+ port=PORT,
+ dbname=DBNAME
+ )
+ print("Connection successful!")
+
+ # Create a cursor to execute SQL queries
+ cursor = connection.cursor()
+
+ # Example query
+ cursor.execute("SELECT NOW();")
+ result = cursor.fetchone()
+ print("Current Time:", result)
+
+ # Close the cursor and connection
+ cursor.close()
+ connection.close()
+ print("Connection closed.")
+
+except Exception as e:
+ print(f"Failed to connect: {e}")`,
+ },
+ ],
+ },
+ sqlalchemy: {
+ installCommands: ['pip install python-dotenv sqlalchemy psycopg2'],
+ files: [
+ {
+ name: 'main.py',
+ content: `from sqlalchemy import create_engine
+# from sqlalchemy.pool import NullPool
+from dotenv import load_dotenv
+import os
+
+# Load environment variables from .env
+load_dotenv()
+
+# Fetch variables
+USER = os.getenv("user")
+PASSWORD = os.getenv("password")
+HOST = os.getenv("host")
+PORT = os.getenv("port")
+DBNAME = os.getenv("dbname")
+
+# Construct the SQLAlchemy connection string
+DATABASE_URL = f"postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}?sslmode=require"
+
+# Create the SQLAlchemy engine
+engine = create_engine(DATABASE_URL)
+# If using Transaction Pooler or Session Pooler, we want to ensure we disable SQLAlchemy client side pooling -
+# https://docs.sqlalchemy.org/en/20/core/pooling.html#switching-pool-implementations
+# engine = create_engine(DATABASE_URL, poolclass=NullPool)
+
+# Test the connection
+try:
+ with engine.connect() as connection:
+ print("Connection successful!")
+except Exception as e:
+ print(f"Failed to connect: {e}")`,
+ },
+ ],
+ },
+}
+
+export default examples
diff --git a/apps/studio/components/interfaces/Connect/PoolerIcons.tsx b/apps/studio/components/interfaces/Connect/PoolerIcons.tsx
new file mode 100644
index 0000000000000..945052bbeb987
--- /dev/null
+++ b/apps/studio/components/interfaces/Connect/PoolerIcons.tsx
@@ -0,0 +1,485 @@
+import { AnimatePresence, motion } from 'framer-motion'
+import { Fragment, useEffect, useState } from 'react'
+
+import { Database } from 'icons'
+import { cn } from 'ui'
+
+// Add overall icon dimension controls
+const ICON_WIDTH = 48
+const ICON_HEIGHT = 96
+
+// Add these to your existing constants section
+const LINE_WIDTH = 2 // SVG container width
+const LINE_STROKE_WIDTH = 1 // Width of the actual line
+const LINE_OFFSET = 1 // For centering the line in container
+
+const FlowingLine = ({
+ x,
+ y1,
+ y2,
+ isActive,
+}: {
+ x: number
+ y1: number
+ y2: number
+ isActive: boolean
+}) => {
+ return (
+
+ {isActive && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ )
+}
+
+const TopRect = ({ isActive }: { isActive: boolean }) => (
+
+)
+
+const BottomRect = ({ isActive }: { isActive: boolean }) => (
+
+
+
+
+)
+
+// Update existing constants to use these dimensions
+const TOP_LINE_START = ICON_HEIGHT * 0.18 // 20% from top
+const TOP_LINE_END = ICON_HEIGHT * 0.28 // 48% from top
+const BOTTOM_LINE_START = ICON_HEIGHT * 0.4 // 65% from top
+const BOTTOM_LINE_END = ICON_HEIGHT * 0.59 // 80% from top
+
+// Update rect positions and dimensions
+const TOP_RECT_Y = ICON_HEIGHT * 0.32 // 55% from top
+const BOTTOM_RECT_Y = ICON_HEIGHT * 0.64 // 85% from top
+const RECT_X = ICON_WIDTH * 0.17 // ~17% from left
+const RECT_WIDTH = ICON_WIDTH * 0.67 // ~67% of total width
+
+// Update circle positions
+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.055 // ~3.8% of width
+
+// Static circle for SessionIcon
+const ConnectionDot = ({ index, isActive }: { index: number; isActive: boolean }) => (
+
+)
+
+export const TransactionIcon = () => {
+ const [dots, setDots] = useState([false, false, false])
+ const [lines, setLines] = useState([false, false, false])
+ const [bottomLineActive, setBottomLineActive] = useState(false)
+
+ useEffect(() => {
+ // Watch lines state and update bottomLineActive accordingly
+ setBottomLineActive(lines.some(Boolean))
+ }, [lines])
+
+ useEffect(() => {
+ const animateDot = (index: number) => {
+ // Clear previous state
+ setDots((prev) => {
+ const newState = [...prev]
+ newState[index] = false
+ return newState
+ })
+ setLines((prev) => {
+ const newState = [...prev]
+ newState[index] = false
+ return newState
+ })
+
+ // Step 1: Animate dot in
+ setTimeout(() => {
+ setDots((prev) => {
+ const newState = [...prev]
+ newState[index] = true
+ return newState
+ })
+ }, 0)
+
+ // Step 2: After dot is in, wait, then show line
+ setTimeout(() => {
+ setLines((prev) => {
+ const newState = [...prev]
+ newState[index] = true
+ return newState
+ })
+ }, 400) // Wait 400ms after dot appears before showing line
+
+ // Step 3: Clear everything
+ setTimeout(() => {
+ setDots((prev) => {
+ const newState = [...prev]
+ newState[index] = false
+ return newState
+ })
+ setLines((prev) => {
+ const newState = [...prev]
+ newState[index] = false
+ return newState
+ })
+ }, 1000) // Total animation duration
+ }
+
+ // Initial staggered animation
+ setTimeout(() => animateDot(0), 0)
+ setTimeout(() => animateDot(1), 200)
+ setTimeout(() => animateDot(2), 400)
+
+ // Set up intervals for continuous animation
+ const intervals = [0, 1, 2].map((index) =>
+ setInterval(() => animateDot(index), 3000 + index * 200)
+ )
+
+ return () => intervals.forEach(clearInterval)
+ }, [])
+
+ return (
+
+
+ {[0, 1, 2].map((index) => (
+
+
+ {dots[index] && (
+
+ )}
+
+
+ ))}
+
+
+
+ {[0, 1, 2].map((index) => (
+
+ ))}
+
+
+
+ )
+}
+
+export const SessionIcon = () => {
+ const [topLineStates, setTopLineStates] = useState([false, false, false])
+ const [bottomLineStates, setBottomLineStates] = useState([false, false, false])
+
+ useEffect(() => {
+ // Function to animate a single dot
+ const animateDot = (index: number) => {
+ setTopLineStates((prev) => {
+ const newState = [...prev]
+ newState[index] = true
+ return newState
+ })
+
+ setTimeout(() => {
+ setBottomLineStates((prev) => {
+ const newState = [...prev]
+ newState[index] = true
+ return newState
+ })
+ }, 300)
+
+ setTimeout(() => {
+ setTopLineStates((prev) => {
+ const newState = [...prev]
+ newState[index] = false
+ return newState
+ })
+ setBottomLineStates((prev) => {
+ const newState = [...prev]
+ newState[index] = false
+ return newState
+ })
+ }, 5000)
+ }
+
+ // Start initial animations immediately with slight delays
+ setTimeout(() => animateDot(0), 100)
+ setTimeout(() => animateDot(1), 1500)
+ setTimeout(() => animateDot(2), 3000)
+
+ // Set up intervals for subsequent animations
+ const intervals = [0, 1, 2].map((index) =>
+ setInterval(() => animateDot(index), Math.random() * 3000 + 8000)
+ )
+
+ return () => intervals.forEach(clearInterval)
+ }, [])
+
+ return (
+
+
+ {[0, 1, 2].map((index) => (
+
+
+
+ ))}
+ state)} />
+ state)} />
+
+ {[0, 1, 2].map((index) => (
+
+
+
+
+ ))}
+
+ )
+}
+
+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) => (
+
+ ))}
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Home/Connect/content/androidkotlin/supabasekt/content.tsx b/apps/studio/components/interfaces/Connect/content/androidkotlin/supabasekt/content.tsx
similarity index 93%
rename from apps/studio/components/interfaces/Home/Connect/content/androidkotlin/supabasekt/content.tsx
rename to apps/studio/components/interfaces/Connect/content/androidkotlin/supabasekt/content.tsx
index bcb90e3c9a5e8..9e1be901e598c 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/androidkotlin/supabasekt/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/androidkotlin/supabasekt/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/astro/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/astro/supabasejs/content.tsx
similarity index 91%
rename from apps/studio/components/interfaces/Home/Connect/content/astro/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/astro/supabasejs/content.tsx
index a88f865dde21f..1ce13c63f32b0 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/astro/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/astro/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx b/apps/studio/components/interfaces/Connect/content/drizzle/content.tsx
similarity index 92%
rename from apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx
rename to apps/studio/components/interfaces/Connect/content/drizzle/content.tsx
index de06febc66033..5f33d70a75935 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/drizzle/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ connectionStringPooler }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/exporeactnative/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/exporeactnative/supabasejs/content.tsx
similarity index 94%
rename from apps/studio/components/interfaces/Home/Connect/content/exporeactnative/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/exporeactnative/supabasejs/content.tsx
index 00fc587397770..fdae1dcdba17a 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/exporeactnative/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/exporeactnative/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/flutter/supabaseflutter/content.tsx b/apps/studio/components/interfaces/Connect/content/flutter/supabaseflutter/content.tsx
similarity index 93%
rename from apps/studio/components/interfaces/Home/Connect/content/flutter/supabaseflutter/content.tsx
rename to apps/studio/components/interfaces/Connect/content/flutter/supabaseflutter/content.tsx
index 8ae2c76a4d066..2eb7d6651eea0 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/flutter/supabaseflutter/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/flutter/supabaseflutter/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/ionicangular/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/ionicangular/supabasejs/content.tsx
similarity index 96%
rename from apps/studio/components/interfaces/Home/Connect/content/ionicangular/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/ionicangular/supabasejs/content.tsx
index 4a51e5f27a1b3..3beda75cbd043 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/ionicangular/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/ionicangular/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/ionicreact/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/ionicreact/supabasejs/content.tsx
similarity index 95%
rename from apps/studio/components/interfaces/Home/Connect/content/ionicreact/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/ionicreact/supabasejs/content.tsx
index 193a6e6719d98..0971e6979bcef 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/ionicreact/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/ionicreact/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx
similarity index 96%
rename from apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx
index 2ff11fda1fcd8..e6d1be4fc26c8 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/nextjs/pages/supabasejs/content.tsx
similarity index 93%
rename from apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/nextjs/pages/supabasejs/content.tsx
index f22c1474db0b5..5ad87160931cb 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/nextjs/pages/supabasejs/content.tsx
@@ -1,12 +1,12 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
+import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
import {
+ ConnectTabContent,
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
- ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
-import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
+} from 'components/interfaces/Connect/ConnectTabs'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
return (
diff --git a/apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/nuxt/supabasejs/content.tsx
similarity index 92%
rename from apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/nuxt/supabasejs/content.tsx
index 6b0428be949f6..b07237bad6ead 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/nuxt/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx b/apps/studio/components/interfaces/Connect/content/prisma/content.tsx
similarity index 89%
rename from apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx
rename to apps/studio/components/interfaces/Connect/content/prisma/content.tsx
index 1094ad05e2465..56c5371b9dbd8 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/prisma/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ connectionStringPooler }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/react/create-react-app/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/react/create-react-app/supabasejs/content.tsx
similarity index 93%
rename from apps/studio/components/interfaces/Home/Connect/content/react/create-react-app/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/react/create-react-app/supabasejs/content.tsx
index de20afa32849d..c5b84f69027dc 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/react/create-react-app/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/react/create-react-app/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/react/vite/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/react/vite/supabasejs/content.tsx
similarity index 93%
rename from apps/studio/components/interfaces/Home/Connect/content/react/vite/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/react/vite/supabasejs/content.tsx
index f7b0c82ff92f7..465d8bf097858 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/react/vite/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/react/vite/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/refine/supabasejs/content.tsx
similarity index 95%
rename from apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/refine/supabasejs/content.tsx
index 6c5cd2ea118dd..c87fdff6bdaa7 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/refine/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/remix/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx
similarity index 94%
rename from apps/studio/components/interfaces/Home/Connect/content/remix/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx
index 4487cf53afdf4..324eac69426eb 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/remix/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/remix/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/solidjs/supabasejs/content.tsx
similarity index 92%
rename from apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/solidjs/supabasejs/content.tsx
index 9ab97360e6f8e..9cd17ddd2576e 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/solidjs/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/sveltekit/supabasejs/content.tsx
similarity index 93%
rename from apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/sveltekit/supabasejs/content.tsx
index 7b058645c0767..5f326bfb8e18a 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/sveltekit/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/swift/supabaseswift/content.tsx b/apps/studio/components/interfaces/Connect/content/swift/supabaseswift/content.tsx
similarity index 92%
rename from apps/studio/components/interfaces/Home/Connect/content/swift/supabaseswift/content.tsx
rename to apps/studio/components/interfaces/Connect/content/swift/supabaseswift/content.tsx
index 43ced097a497a..83b5c4f0a39ae 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/swift/supabaseswift/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/swift/supabaseswift/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTriggers,
ConnectTabTrigger,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/vuejs/supabasejs/content.tsx
similarity index 92%
rename from apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx
rename to apps/studio/components/interfaces/Connect/content/vuejs/supabasejs/content.tsx
index 5ab61da9c8ae7..9f10023c13557 100644
--- a/apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx
+++ b/apps/studio/components/interfaces/Connect/content/vuejs/supabasejs/content.tsx
@@ -1,11 +1,11 @@
-import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types'
+import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types'
import {
ConnectTabs,
ConnectTabTrigger,
ConnectTabTriggers,
ConnectTabContent,
-} from 'components/interfaces/Home/Connect/ConnectTabs'
+} from 'components/interfaces/Connect/ConnectTabs'
import { SimpleCodeBlock } from '@ui/components/SimpleCodeBlock'
const ContentFile = ({ projectKeys }: ContentFileProps) => {
diff --git a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx
index 24c0638a1ebb5..fdf94851a4cc3 100644
--- a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx
+++ b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx
@@ -23,6 +23,7 @@ import { BillingChangeBadge } from '../ui/BillingChangeBadge'
import FormMessage from '../ui/FormMessage'
import { NoticeBar } from '../ui/NoticeBar'
import { InstanceSpecs } from 'lib/constants'
+import { DocsButton } from 'components/ui/DocsButton'
/**
* to do: this could be a type from api-types
@@ -137,6 +138,14 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) {
Hardware resources allocated to your Postgres database
+
+
+
+
+
@@ -148,6 +151,13 @@ export function DiskSizeField({
{includedDiskGB > 0 &&
subscription?.plan.id &&
`Your plan includes ${includedDiskGB} GB of disk size for ${watchedStorageType}.`}
+
+
+
+
@@ -30,6 +30,7 @@ export default function DiskSpaceBar({ form }: DiskSpaceBarProps) {
const { resolvedTheme } = useTheme()
const { formState, watch } = form
const isDarkMode = resolvedTheme?.includes('dark')
+ const project = useSelectedProject()
const {
data: diskUtil,
@@ -38,30 +39,54 @@ export default function DiskSpaceBar({ form }: DiskSpaceBarProps) {
projectRef: ref,
})
- const usedSize = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100
- const totalSize = formState.defaultValues?.totalSize || 0
- const show = formState.dirtyFields.totalSize !== undefined && usedSize
+ const { data: diskBreakdown } = useDiskBreakdownQuery({
+ projectRef: ref,
+ connectionString: project?.connectionString,
+ })
+
+ const diskBreakdownBytes = useMemo(() => {
+ return {
+ availableBytes: diskUtil?.metrics.fs_avail_bytes ?? 0,
+ totalUsedBytes: diskUtil?.metrics.fs_used_bytes ?? 0,
+ totalDiskSizeBytes: diskUtil?.metrics.fs_size_bytes,
+ dbSizeBytes: Math.max(0, diskBreakdown?.db_size_bytes ?? 0),
+ walSizeBytes: Math.max(0, diskBreakdown?.wal_size_bytes ?? 0),
+ systemBytes: Math.max(
+ 0,
+ (diskUtil?.metrics.fs_used_bytes ?? 0) -
+ (diskBreakdown?.db_size_bytes ?? 0) -
+ (diskBreakdown?.wal_size_bytes ?? 0)
+ ),
+ }
+ }, [diskUtil, diskBreakdown])
+
+ const showNewSize = formState.dirtyFields.totalSize !== undefined && diskBreakdown
const newTotalSize = watch('totalSize')
- const usedPercentage = (usedSize / totalSize) * 100
- const resizePercentage = AUTOSCALING_THRESHOLD * 100
+ const totalSize = formState.defaultValues?.totalSize || 0
+ const usedSizeTotal = Math.round(((diskBreakdownBytes?.totalUsedBytes ?? 0) / GB) * 100) / 100
+ const usedTotalPercentage = Math.min((usedSizeTotal / totalSize) * 100, 100)
- const newUsedPercentage = (usedSize / newTotalSize) * 100
- const newResizePercentage = AUTOSCALING_THRESHOLD * 100
+ const usedSizeDatabase = Math.round(((diskBreakdownBytes?.dbSizeBytes ?? 0) / GB) * 100) / 100
+ const usedPercentageDatabase = Math.min((usedSizeDatabase / totalSize) * 100, 100)
+ const newUsedPercentageDatabase = Math.min((usedSizeDatabase / newTotalSize) * 100, 100)
- const { project } = useProjectContext()
- const { data } = useDatabaseSizeQuery({
- projectRef: project?.ref,
- connectionString: project?.connectionString,
- })
- const { remainingDuration } = useRemainingDurationForDiskAttributeUpdate({ projectRef: ref })
+ const usedSizeWAL = Math.round(((diskBreakdownBytes?.walSizeBytes ?? 0) / GB) * 100) / 100
+ const usedPercentageWAL = Math.min((usedSizeWAL / totalSize) * 100, 100)
+ const newUsedPercentageWAL = Math.min((usedSizeWAL / newTotalSize) * 100, 100)
+
+ const usedSizeSystem = Math.round(((diskBreakdownBytes?.systemBytes ?? 0) / GB) * 100) / 100
+ const usedPercentageSystem = Math.min((usedSizeSystem / totalSize) * 100, 100)
+ const newUsedPercentageSystem = Math.min((usedSizeSystem / newTotalSize) * 100, 100)
+
+ const resizePercentage = AUTOSCALING_THRESHOLD * 100
+ const newResizePercentage = AUTOSCALING_THRESHOLD * 100
- const databaseSizeBytes = data ?? 0
return (
- {usedSize.toFixed(2)}
+ {usedSizeTotal.toFixed(2)}
GB used of
@@ -73,85 +98,67 @@ export default function DiskSpaceBar({ form }: DiskSpaceBarProps) {
- {!show ? (
-
-
+
+
+
= 90 && remainingDuration > 0
- ? 'bg-destructive'
- : 'bg-foreground',
- 'relative overflow-hidden transition-all duration-500 ease-in-out'
- )}
- style={{ width: `${usedPercentage >= 100 ? 100 : usedPercentage}%` }}
- >
-
-
-
-
- ) : (
-
-
+
+
+
+
+
+ {!showNewSize && (
= 100 ? 100 : newUsedPercentage}%` }}
- >
-
-
-
-
- )}
+ className="bg-transparent-800 border-r transition-all duration-500 ease-in-out"
+ style={{
+ width: `${resizePercentage - usedTotalPercentage <= 0 ? 0 : resizePercentage - usedTotalPercentage}%`,
+ }}
+ />
+ )}
+
+
- {show && (
+ {showNewSize && (
- {!show && (
+ {!showNewSize && (
@@ -202,24 +209,75 @@ export default function DiskSpaceBar({ form }: DiskSpaceBarProps) {
)}
- {!show && (
-
-
-
+ {!showNewSize && (
+
+
+
+
+
+
+
)}
Note: Disk Size refers to the total space your
project occupies on disk, including the database itself (currently{' '}
- {formatBytes(databaseSizeBytes, 2, 'GB')} ), additional files like the
- write-ahead log (WAL), and other internal resources.
+ {formatBytes(diskBreakdownBytes?.dbSizeBytes, 2, 'GB')} ), additional files like
+ the write-ahead log (currently{' '}
+ {formatBytes(diskBreakdownBytes?.walSizeBytes, 2, 'GB')} ), and other system
+ resources (currently {formatBytes(diskBreakdownBytes?.systemBytes, 2, 'GB')} ).
+ Data can take 5 minutes to refresh.
)
}
+
+const LegendItem = ({
+ name,
+ description,
+ color,
+ size,
+}: {
+ name: string
+ description: string
+ color: string
+ size: number
+}) => (
+
+
+
+
+
+
+
+
+ {name} - {formatBytes(size, 2, 'GB')}
+
+
+ {description}
+
+
+)
diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx b/apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx
deleted file mode 100644
index 7f4217fb52a9e..0000000000000
--- a/apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { Box, Check, ChevronDown } from 'lucide-react'
-import { useState } from 'react'
-import {
- Button,
- CommandEmpty_Shadcn_,
- CommandGroup_Shadcn_,
- CommandInput_Shadcn_,
- CommandItem_Shadcn_,
- CommandList_Shadcn_,
- Command_Shadcn_,
- PopoverContent_Shadcn_,
- PopoverTrigger_Shadcn_,
- Popover_Shadcn_,
- cn,
-} from 'ui'
-import ConnectionIcon from './ConnectionIcon'
-
-interface ConnectDropdownProps {
- state: string
- updateState: (state: string) => void
- label: string
- items: any[]
-}
-
-const ConnectDropdown = ({
- state,
- updateState,
- label,
-
- items,
-}: ConnectDropdownProps) => {
- const [open, setOpen] = useState(false)
-
- function onSelectLib(key: string) {
- updateState(key)
- setOpen(false)
- }
-
- const selectedItem = items.find((item) => item.key === state)
-
- return (
- <>
-
-
-
- {label}
-
-
-
-
- {selectedItem?.icon ? (
-
- ) : (
-
- )}
- {selectedItem?.label}
-
-
-
-
-
-
-
-
-
- No results found.
-
- {items.map((item) => (
- {
- onSelectLib(item.key)
- setOpen(false)
- }}
- className="flex gap-2 items-center"
- >
- {item.icon ? : }
- {item.label}
-
-
- ))}
-
-
-
-
-
- >
- )
-}
-
-export default ConnectDropdown
diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageSection/DiskUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageSection/DiskUsage.tsx
index 5383630a19980..df7486172fe31 100644
--- a/apps/studio/components/interfaces/Organization/Usage/UsageSection/DiskUsage.tsx
+++ b/apps/studio/components/interfaces/Organization/Usage/UsageSection/DiskUsage.tsx
@@ -178,7 +178,7 @@ const DiskUsage = ({
{project.name}
-
+
Manage Disk
diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionStringMoved.tsx b/apps/studio/components/interfaces/Settings/Database/ConnectionStringMoved.tsx
new file mode 100644
index 0000000000000..3852592b86259
--- /dev/null
+++ b/apps/studio/components/interfaces/Settings/Database/ConnectionStringMoved.tsx
@@ -0,0 +1,53 @@
+import { Button } from 'ui'
+import { Plug, GitBranch, ChevronsUpDown, Pointer } from 'lucide-react'
+
+export const ConnectionStringMoved = () => {
+ return (
+
+
+
Connection string has moved
+
+ You can find Project connect details by clicking 'Connect' in the top bar
+
+
+
+
+
+
+ }
+ >
+ Project name
+
+ }
+ >
+ Connect
+
+ }
+ >
+ Enable Branching
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx
index ebfa5449d4365..14793602a1952 100644
--- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx
+++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx
@@ -56,6 +56,9 @@ interface DatabaseConnectionStringProps {
appearance: 'default' | 'minimal'
}
+/**
+ * @deprecated Will be removed once `connectDialogUpdate` flag is persisted
+ */
export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStringProps) => {
const project = useSelectedProject()
const { ref: projectRef, connectionString } = useParams()
diff --git a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx
index e870b0801225d..039c9ff5d81e8 100644
--- a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx
+++ b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx
@@ -50,14 +50,12 @@ const OrganizationDropdown = ({ isNewNav = false }: OrganizationDropdownProps) =
-
-
}>
-
-
{orgName}
- {isSuccess &&
{subscription?.plan.name} }
-
-
-
+ }>
+
+
{orgName}
+ {isSuccess &&
{subscription?.plan.name} }
+
+
diff --git a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx
index 5e477b21b4a58..e2d465bcaa6d2 100644
--- a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx
+++ b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx
@@ -117,58 +117,56 @@ const ProjectDropdown = ({ isNewNav = false }: ProjectDropdownProps) => {
}
return IS_PLATFORM ? (
-
-
-
- }>
-
-
{selectedProject?.name}
-
-
-
-
-
-
-
- No projects found
-
- 7 ? 'h-[210px]' : ''}>
- {projects?.map((project) => (
-
- ))}
-
-
- {projectCreationEnabled && (
- <>
-
-
- {
+
+
+ }>
+
+
{selectedProject?.name}
+
+
+
+
+
+
+
+ No projects found
+
+ 7 ? 'h-[210px]' : ''}>
+ {projects?.map((project) => (
+
+ ))}
+
+
+ {projectCreationEnabled && (
+ <>
+
+
+ {
+ setOpen(false)
+ router.push(`/new/${selectedOrganization?.slug}`)
+ }}
+ onClick={() => setOpen(false)}
+ >
+ {
setOpen(false)
- router.push(`/new/${selectedOrganization?.slug}`)
}}
- onClick={() => setOpen(false)}
+ className="w-full flex items-center gap-2"
>
- {
- setOpen(false)
- }}
- className="w-full flex items-center gap-2"
- >
-
- New project
-
-
-
- >
- )}
-
-
-
-
-
+
+ New project
+
+
+
+ >
+ )}
+
+
+
+
) : (
{selectedProject?.name}
diff --git a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
index 66cc136d93511..6808d0d377947 100644
--- a/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
@@ -1,6 +1,7 @@
import Link from 'next/link'
import { useMemo } from 'react'
+import Connect from 'components/interfaces/Connect/Connect'
import { useParams } from 'common'
import AssistantButton from 'components/layouts/AppLayout/AssistantButton'
import BranchDropdown from 'components/layouts/AppLayout/BranchDropdown'
@@ -12,19 +13,40 @@ import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-que
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
+import { useFlag } from 'hooks/ui/useFlag'
import { IS_PLATFORM } from 'lib/constants'
-import { Badge } from 'ui'
+import { Badge, cn } from 'ui'
import BreadcrumbsView from './BreadcrumbsView'
import { FeedbackDropdown } from './FeedbackDropdown'
import HelpPopover from './HelpPopover'
import NotificationsPopoverV2 from './NotificationsPopoverV2/NotificationsPopover'
+const LayoutHeaderDivider = () => (
+
+
+
+
+
+)
+
const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = true }: any) => {
const { ref: projectRef } = useParams()
const selectedProject = useSelectedProject()
const selectedOrganization = useSelectedOrganization()
const isBranchingEnabled = selectedProject?.is_branch_enabled === true
+ const connectDialogUpdate = useFlag('connectDialogUpdate')
+
const { data: subscription } = useOrgSubscriptionQuery({
orgSlug: selectedOrganization?.slug,
})
@@ -45,67 +67,40 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder =
return (
- {/* Organization is selected */}
{projectRef && (
<>
-
+
+
+
+
- {projectRef && (
- <>
-
-
-
-
-
+ {exceedingLimits && (
+
+
+ Exceeding usage limits
+
+
+ )}
-
+ {selectedProject && isBranchingEnabled && (
+ <>
+
+
+ >
+ )}
+
- {exceedingLimits && (
-
-
- Exceeding usage limits
-
-
- )}
- >
- )}
-
- {selectedProject && (
- <>
-
-
-
-
-
- {isBranchingEnabled ?
:
}
- >
- )}
+
+ {!isBranchingEnabled && }
+ {connectDialogUpdate && }
+
>
)}
@@ -113,16 +108,14 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder =
-
- {customHeaderComponents && customHeaderComponents}
- {IS_PLATFORM && (
- <>
-
-
-
- >
- )}
-
+ {customHeaderComponents && customHeaderComponents}
+ {IS_PLATFORM && (
+ <>
+
+
+
+ >
+ )}
{!!projectRef && (
diff --git a/apps/studio/components/ui/DatabaseSelector.tsx b/apps/studio/components/ui/DatabaseSelector.tsx
index 832bdae9b11d9..72dc0c795d968 100644
--- a/apps/studio/components/ui/DatabaseSelector.tsx
+++ b/apps/studio/components/ui/DatabaseSelector.tsx
@@ -1,11 +1,18 @@
-import { useParams } from 'common'
import { noop } from 'lodash'
import { Check, ChevronDown, Loader2, Plus } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
+
+import { useParams } from 'common'
+import { Markdown } from 'components/interfaces/Markdown'
+import { REPLICA_STATUS } from 'components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
+import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
+import { formatDatabaseID, formatDatabaseRegion } from 'data/read-replicas/replicas.utils'
+import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import {
Button,
+ ButtonProps,
CommandGroup_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
@@ -19,28 +26,27 @@ import {
Tooltip_Shadcn_,
cn,
} from 'ui'
-
-import { Markdown } from 'components/interfaces/Markdown'
-import { REPLICA_STATUS } from 'components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants'
-import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
-import { formatDatabaseID, formatDatabaseRegion } from 'data/read-replicas/replicas.utils'
-import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
+import { useAppStateSnapshot } from 'state/app-state'
interface DatabaseSelectorProps {
variant?: 'regular' | 'connected-on-right' | 'connected-on-left' | 'connected-on-both'
additionalOptions?: { id: string; name: string }[]
onSelectId?: (id: string) => void // Optional callback
+
+ buttonProps?: ButtonProps
}
const DatabaseSelector = ({
variant = 'regular',
additionalOptions = [],
onSelectId = noop,
+ buttonProps,
}: DatabaseSelectorProps) => {
const router = useRouter()
const { ref: projectRef } = useParams()
const [open, setOpen] = useState(false)
+ const appState = useAppStateSnapshot()
const state = useDatabaseSelectorStateSnapshot()
const selectedDatabaseId = state.selectedDatabaseId
@@ -59,19 +65,23 @@ const DatabaseSelector = ({
return (
-
+
+
+ Source
+
}
+ iconRight={
}
+ {...buttonProps}
className={cn(
- 'pr-2',
+ 'pr-2 rounded-l-none',
variant === 'connected-on-right' && 'rounded-r-none',
variant === 'connected-on-left' && 'rounded-l-none border-l-0',
- variant === 'connected-on-both' && 'rounded-none border-x-0'
+ variant === 'connected-on-both' && 'rounded-none border-x-0',
+ buttonProps?.className
)}
- icon={isLoading &&
}
- iconRight={
}
>
-
source
{selectedAdditionalOption ? (
{selectedAdditionalOption.name}
) : (
@@ -193,7 +203,11 @@ const DatabaseSelector = ({
>
setOpen(false)}
+ onClick={() => {
+ setOpen(false)
+ // [Joshen] This is used in the Connect UI which is available across all pages
+ appState.setShowConnectDialog(false)
+ }}
className="w-full flex items-center gap-2"
>
diff --git a/apps/studio/components/ui/Panel.tsx b/apps/studio/components/ui/Panel.tsx
index 0c8f51213ab2e..028fb3f5ba796 100644
--- a/apps/studio/components/ui/Panel.tsx
+++ b/apps/studio/components/ui/Panel.tsx
@@ -117,7 +117,9 @@ const PanelNotice = forwardRef<
{href && (
)}
diff --git a/apps/studio/data/config/disk-breakdown-query.ts b/apps/studio/data/config/disk-breakdown-query.ts
new file mode 100644
index 0000000000000..5218d40d51f90
--- /dev/null
+++ b/apps/studio/data/config/disk-breakdown-query.ts
@@ -0,0 +1,59 @@
+import { useQuery, UseQueryOptions } from '@tanstack/react-query'
+
+import type { ResponseError } from 'types'
+import { configKeys } from './keys'
+import { executeSql } from 'data/sql/execute-sql-query'
+
+export type DiskBreakdownVariables = {
+ projectRef?: string
+ connectionString?: string
+}
+
+type DiskBreakdownResult = {
+ db_size_bytes: number
+ wal_size_bytes: number
+}
+
+export async function getDiskBreakdown(
+ { projectRef, connectionString }: DiskBreakdownVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) throw new Error('Project ref is required')
+ if (!connectionString) throw new Error('Connection string is required')
+
+ const { result } = await executeSql(
+ {
+ projectRef,
+ connectionString,
+ sql: `
+ SELECT
+ (
+ SELECT
+ SUM(pg_database_size(pg_database.datname)) AS db_size_bytes
+ FROM
+ pg_database
+ ),
+ (
+ SELECT SUM(size)
+ FROM
+ pg_ls_waldir()
+ ) AS wal_size_bytes`,
+ },
+ signal
+ )
+
+ return result[0] as DiskBreakdownResult
+}
+
+export type DiskBreakdownData = Awaited
>
+export type DiskBreakdownError = ResponseError
+
+export const useDiskBreakdownQuery = (
+ { projectRef, connectionString }: DiskBreakdownVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ configKeys.diskBreakdown(projectRef),
+ ({ signal }) => getDiskBreakdown({ projectRef, connectionString }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
+ )
diff --git a/apps/studio/data/config/keys.ts b/apps/studio/data/config/keys.ts
index a2aab7748f18a..e154ed6548159 100644
--- a/apps/studio/data/config/keys.ts
+++ b/apps/studio/data/config/keys.ts
@@ -14,6 +14,8 @@ export const configKeys = {
['projects', projectRef, 'upgrade-status'] as const,
diskAttributes: (projectRef: string | undefined) =>
['projects', projectRef, 'disk-attributes'] as const,
+ diskBreakdown: (projectRef: string | undefined) =>
+ ['projects', projectRef, 'disk-breakdown'] as const,
diskUtilization: (projectRef: string | undefined) =>
['projects', projectRef, 'disk-utilization'] as const,
projectCreationPostgresVersions: (
diff --git a/apps/studio/lib/constants/telemetry.ts b/apps/studio/lib/constants/telemetry.ts
index 87f03cf95b915..2030c342b8d24 100644
--- a/apps/studio/lib/constants/telemetry.ts
+++ b/apps/studio/lib/constants/telemetry.ts
@@ -4,6 +4,7 @@
export enum TELEMETRY_EVENTS {
FEATURE_PREVIEWS = 'Dashboard UI Feature Previews',
AI_ASSISTANT_V2 = 'AI Assistant V2',
+ CONNECT_UI = 'Connect UI',
CRON_JOBS = 'Cron Jobs',
}
@@ -48,4 +49,5 @@ export enum TELEMETRY_VALUES {
CRON_JOB_UPDATE_CLICKED = 'cron-job-update-clicked',
CRON_JOB_CREATE_CLICKED = 'cron-job-create-clicked',
CRON_JOBS_VIEW_PREVIOUS_RUNS = 'view-previous-runs-clicked',
+ COPY_CONNECTION_STRING = 'copy-connection-string',
}
diff --git a/apps/studio/pages/api/ai/sql/generate-v3.ts b/apps/studio/pages/api/ai/sql/generate-v3.ts
index 55517ba1c0b3a..4218aa062d60d 100644
--- a/apps/studio/pages/api/ai/sql/generate-v3.ts
+++ b/apps/studio/pages/api/ai/sql/generate-v3.ts
@@ -1,25 +1,20 @@
import { openai } from '@ai-sdk/openai'
-import { streamText } from 'ai'
-import { getTools } from './tools'
import pgMeta from '@supabase/pg-meta'
-import { executeSql } from 'data/sql/execute-sql-query'
+import { streamText } from 'ai'
import { NextApiRequest, NextApiResponse } from 'next'
+import { executeSql } from 'data/sql/execute-sql-query'
+import { getTools } from './tools'
+
export const maxDuration = 30
const openAiKey = process.env.OPENAI_API_KEY
const pgMetaSchemasList = pgMeta.schemas.list()
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!openAiKey) {
- return new Response(
- JSON.stringify({
- error: 'No OPENAI_API_KEY set. Create this environment variable to use AI features.',
- }),
- {
- status: 500,
- headers: { 'Content-Type': 'application/json' },
- }
- )
+ return res.status(400).json({
+ error: 'No OPENAI_API_KEY set. Create this environment variable to use AI features.',
+ })
}
const { method } = req
@@ -28,13 +23,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
case 'POST':
return handlePost(req, res)
default:
- return new Response(
- JSON.stringify({ data: null, error: { message: `Method ${method} Not Allowed` } }),
- {
- status: 405,
- headers: { 'Content-Type': 'application/json', Allow: 'POST' },
- }
- )
+ res.setHeader('Allow', ['POST'])
+ res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
}
}
diff --git a/apps/studio/pages/project/[ref]/index.tsx b/apps/studio/pages/project/[ref]/index.tsx
index 836b11b3f7a71..b30d2e4dce7af 100644
--- a/apps/studio/pages/project/[ref]/index.tsx
+++ b/apps/studio/pages/project/[ref]/index.tsx
@@ -1,8 +1,8 @@
import { useEffect, useRef } from 'react'
import { useParams } from 'common'
+import Connect from 'components/interfaces/Connect/Connect'
import { ClientLibrary, ExampleProject } from 'components/interfaces/Home'
-import Connect from 'components/interfaces/Home/Connect/Connect'
import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants'
import ProjectUsageSection from 'components/interfaces/Home/ProjectUsageSection'
import { SecurityStatus } from 'components/interfaces/Home/SecurityStatus'
@@ -13,6 +13,7 @@ import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
import ProjectUpgradeFailedBanner from 'components/ui/ProjectUpgradeFailedBanner'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useIsOrioleDb, useSelectedProject } from 'hooks/misc/useSelectedProject'
+import { useFlag } from 'hooks/ui/useFlag'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import type { NextPageWithLayout } from 'types'
@@ -28,6 +29,8 @@ import {
} from 'ui'
const Home: NextPageWithLayout = () => {
+ const connectDialogUpdate = useFlag('connectDialogUpdate')
+
const organization = useSelectedOrganization()
const project = useSelectedProject()
@@ -87,7 +90,9 @@ const Home: NextPageWithLayout = () => {
{project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && }
{IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && }
- {IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && }
+ {IS_PLATFORM &&
+ project?.status === PROJECT_STATUS.ACTIVE_HEALTHY &&
+ !connectDialogUpdate && }
diff --git a/apps/studio/pages/project/[ref]/reports/database.tsx b/apps/studio/pages/project/[ref]/reports/database.tsx
index 6584073bb36c6..0bc73dd479566 100644
--- a/apps/studio/pages/project/[ref]/reports/database.tsx
+++ b/apps/studio/pages/project/[ref]/reports/database.tsx
@@ -22,7 +22,6 @@ import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mu
import { useDatabaseSizeQuery } from 'data/database/database-size-query'
import { useDatabaseReport } from 'data/reports/database-report-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
-import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useFlag } from 'hooks/ui/useFlag'
import { TIME_PERIODS_INFRA } from 'lib/constants/metrics'
import { formatBytes } from 'lib/helpers'
@@ -46,7 +45,6 @@ const DatabaseUsage = () => {
const { project } = useProjectContext()
const diskManagementV2 = useFlag('diskManagementV2')
- const org = useSelectedOrganization()
const state = useDatabaseSelectorStateSnapshot()
const [dateRange, setDateRange] = useState
(undefined)
@@ -220,23 +218,20 @@ const DatabaseUsage = () => {
renderer={(props) => {
return (
-
- The data refreshes every 24 hours.
-
Space used
{formatBytes(databaseSizeBytes, 2, 'GB')}
-
Total size
+ Provisioned disk size
{currentDiskSize} GB
{showNewDiskManagementUI ? (
-
+
Increase disk size
@@ -309,11 +304,11 @@ const DatabaseUsage = () => {
}>
- Read about database space
+ Read about database size
diff --git a/apps/studio/pages/project/[ref]/settings/database.tsx b/apps/studio/pages/project/[ref]/settings/database.tsx
index 902109f8a29ea..1ed247a0807ac 100644
--- a/apps/studio/pages/project/[ref]/settings/database.tsx
+++ b/apps/studio/pages/project/[ref]/settings/database.tsx
@@ -1,25 +1,25 @@
+import { DiskManagementPanelForm } from 'components/interfaces/DiskManagement/DiskManagementPanelForm'
import {
ConnectionPooling,
DatabaseSettings,
NetworkRestrictions,
} from 'components/interfaces/Settings/Database'
-import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
-import type { NextPageWithLayout } from 'types'
-
-import { DiskManagementPanelForm } from 'components/interfaces/DiskManagement/DiskManagementPanelForm'
import BannedIPs from 'components/interfaces/Settings/Database/BannedIPs'
+import { ConnectionStringMoved } from 'components/interfaces/Settings/Database/ConnectionStringMoved'
import { DatabaseReadOnlyAlert } from 'components/interfaces/Settings/Database/DatabaseReadOnlyAlert'
import { DatabaseConnectionString } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString'
+import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
import { PoolingModesModal } from 'components/interfaces/Settings/Database/PoolingModesModal'
import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration'
+import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout'
import { ScaffoldContainer, ScaffoldHeader, ScaffoldTitle } from 'components/layouts/Scaffold'
-import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration'
-import { useFlag } from 'hooks/ui/useFlag'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
+import { useFlag } from 'hooks/ui/useFlag'
+import type { NextPageWithLayout } from 'types'
const ProjectSettings: NextPageWithLayout = () => {
const diskManagementV2 = useFlag('diskManagementV2')
- const showDiskAndComputeForm = useFlag('diskAndComputeForm')
+ const connectDialogUpdate = useFlag('connectDialogUpdate')
const project = useSelectedProject()
const showNewDiskManagementUI = diskManagementV2 && project?.cloud_provider === 'AWS'
@@ -35,8 +35,14 @@ const ProjectSettings: NextPageWithLayout = () => {
-
-
+ {connectDialogUpdate ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
diff --git a/apps/studio/state/app-state.ts b/apps/studio/state/app-state.ts
index 23efeaa44da21..9a9f4450a3b4b 100644
--- a/apps/studio/state/app-state.ts
+++ b/apps/studio/state/app-state.ts
@@ -68,6 +68,7 @@ const getInitialState = () => {
showGenerateSqlModal: false,
navigationPanelOpen: false,
navigationPanelJustClosed: false,
+ showConnectDialog: false,
}
}
@@ -111,6 +112,7 @@ const getInitialState = () => {
showGenerateSqlModal: false,
navigationPanelOpen: false,
navigationPanelJustClosed: false,
+ showConnectDialog: false,
}
}
@@ -210,6 +212,11 @@ export const appState = proxy({
...value,
}
},
+
+ showConnectDialog: false,
+ setShowConnectDialog: (value: boolean) => {
+ appState.showConnectDialog = value
+ },
})
// Set up localStorage subscription
diff --git a/apps/studio/styles/main.scss b/apps/studio/styles/main.scss
index ad2f81cd43fcc..a4ed805f793c4 100644
--- a/apps/studio/styles/main.scss
+++ b/apps/studio/styles/main.scss
@@ -198,11 +198,6 @@ input[type='number'] {
display: none;
}
-code {
- // use supabase-ui code style
- @apply text-code;
-}
-
div[data-radix-portal]:not(.portal--toast) {
z-index: 2147483646 !important;
}
diff --git a/packages/ui/src/components/CodeBlock/CodeBlock.tsx b/packages/ui/src/components/CodeBlock/CodeBlock.tsx
index 2013320d5bafc..ccf629e76c114 100644
--- a/packages/ui/src/components/CodeBlock/CodeBlock.tsx
+++ b/packages/ui/src/components/CodeBlock/CodeBlock.tsx
@@ -1,10 +1,12 @@
'use client'
+import { noop } from 'lodash'
import { Check, Copy } from 'lucide-react'
import { useTheme } from 'next-themes'
import { Children, ReactNode, useState } from 'react'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import { Light as SyntaxHighlighter, SyntaxHighlighterProps } from 'react-syntax-highlighter'
+
import { cn } from '../../lib/utils/cn'
import { Button } from '../Button/Button'
import { monokaiCustomTheme } from './CodeBlock.utils'
@@ -13,29 +15,38 @@ import curl from 'highlightjs-curl'
import bash from 'react-syntax-highlighter/dist/cjs/languages/hljs/bash'
import csharp from 'react-syntax-highlighter/dist/cjs/languages/hljs/csharp'
import dart from 'react-syntax-highlighter/dist/cjs/languages/hljs/dart'
+import go from 'react-syntax-highlighter/dist/cjs/languages/hljs/go'
import http from 'react-syntax-highlighter/dist/cjs/languages/hljs/http'
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
import json from 'react-syntax-highlighter/dist/cjs/languages/hljs/json'
import kotlin from 'react-syntax-highlighter/dist/cjs/languages/hljs/kotlin'
-import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
+import php from 'react-syntax-highlighter/dist/cjs/languages/hljs/php'
+import {
+ default as py,
+ default as python,
+} from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript'
+export type CodeBlockLang =
+ | 'js'
+ | 'jsx'
+ | 'sql'
+ | 'py'
+ | 'bash'
+ | 'ts'
+ | 'dart'
+ | 'json'
+ | 'csharp'
+ | 'kotlin'
+ | 'curl'
+ | 'http'
+ | 'php'
+ | 'python'
+ | 'go'
export interface CodeBlockProps {
title?: ReactNode
- language?:
- | 'js'
- | 'jsx'
- | 'sql'
- | 'py'
- | 'bash'
- | 'ts'
- | 'dart'
- | 'json'
- | 'csharp'
- | 'kotlin'
- | 'curl'
- | 'http'
+ language?: CodeBlockLang
linesToHighlight?: number[]
highlightBorder?: boolean
styleConfig?: {
@@ -52,6 +63,7 @@ export interface CodeBlockProps {
children?: string
renderer?: SyntaxHighlighterProps['renderer']
focusable?: boolean
+ onCopyCallback?: () => void
wrapLines?: boolean
}
@@ -88,6 +100,7 @@ export const CodeBlock = ({
wrapLines = true,
renderer,
focusable = true,
+ onCopyCallback = noop,
}: CodeBlockProps) => {
const { resolvedTheme } = useTheme()
const isDarkTheme = resolvedTheme?.includes('dark')!
@@ -97,6 +110,7 @@ export const CodeBlock = ({
const handleCopy = () => {
setCopied(true)
+ onCopyCallback()
setTimeout(() => {
setCopied(false)
}, 1000)
@@ -129,6 +143,9 @@ export const CodeBlock = ({
SyntaxHighlighter.registerLanguage('kotlin', kotlin)
SyntaxHighlighter.registerLanguage('curl', curl)
SyntaxHighlighter.registerLanguage('http', http)
+ SyntaxHighlighter.registerLanguage('php', php)
+ SyntaxHighlighter.registerLanguage('python', python)
+ SyntaxHighlighter.registerLanguage('go', go)
const large = false
// don't show line numbers if bash == lang
@@ -157,7 +174,7 @@ export const CodeBlock = ({
style={monokaiTheme}
className={cn(
'code-block border border-surface p-4 w-full !my-0 !bg-surface-100 outline-none focus:border-foreground-lighter/50',
- `${!title ? '!rounded-md' : '!rounded-t-none !rounded-b-md'}`,
+ `${!title ? 'rounded-md' : 'rounded-t-none rounded-b-md'}`,
`${!showLineNumbers ? 'pl-6' : ''}`,
className
)}
diff --git a/packages/ui/src/components/shadcn/ui/select.tsx b/packages/ui/src/components/shadcn/ui/select.tsx
index 8fd409dbcaebf..8b519980d132f 100644
--- a/packages/ui/src/components/shadcn/ui/select.tsx
+++ b/packages/ui/src/components/shadcn/ui/select.tsx
@@ -49,6 +49,7 @@ const SelectTrigger = React.forwardRef<
className={cn(
'flex w-full items-center justify-between rounded-md border border-strong hover:border-stronger bg-alternative dark:bg-muted hover:bg-selection text-xs ring-offset-background-control data-[placeholder]:text-foreground-lighter focus:outline-none ring-border-control focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-200',
'data-[state=open]:bg-selection data-[state=open]:border-stronger',
+ 'gap-2',
SelectTriggerVariants({ size }),
className
)}
@@ -56,7 +57,7 @@ const SelectTrigger = React.forwardRef<
>
{children}
-
+
))