import { CopyOutlined, DownOutlined, InfoCircleOutlined, UpOutlined } from '@ant-design/icons'; import { App, Button, Checkbox, Input, InputNumber, Select, Switch, Tooltip } from 'antd'; import { useEffect, useState } from 'react'; import { IS_CLOUD } from '../../../../constants'; import { type Database, PostgresBackupType, databaseApi } from '../../../../entity/databases'; import { ConnectionStringParser } from '../../../../entity/databases/model/postgresql/ConnectionStringParser'; import { ClipboardHelper } from '../../../../shared/lib/ClipboardHelper'; import { ToastHelper } from '../../../../shared/toast'; import { ClipboardPasteModalComponent } from '../../../../shared/ui'; interface Props { database: Database; isShowCancelButton?: boolean; onCancel: () => void; isShowBackButton: boolean; onBack: () => void; saveButtonText?: string; isSaveToApi: boolean; onSaved: (database: Database) => void; isShowDbName?: boolean; isRestoreMode?: boolean; } export const EditPostgreSqlSpecificDataComponent = ({ database, isShowCancelButton, onCancel, isShowBackButton, onBack, saveButtonText, isSaveToApi, onSaved, isShowDbName = true, isRestoreMode = false, }: Props) => { const { message } = App.useApp(); const [editingDatabase, setEditingDatabase] = useState(); const [isSaving, setIsSaving] = useState(false); const [isConnectionTested, setIsConnectionTested] = useState(false); const [isTestingConnection, setIsTestingConnection] = useState(false); const [isConnectionFailed, setIsConnectionFailed] = useState(false); const hasAdvancedValues = !!database.postgresql?.includeSchemas?.length || !!database.postgresql?.isExcludeExtensions; const [isShowAdvanced, setShowAdvanced] = useState(hasAdvancedValues); const [hasAutoAddedPublicSchema, setHasAutoAddedPublicSchema] = useState(false); const [isShowPasteModal, setIsShowPasteModal] = useState(false); const applyConnectionString = (text: string) => { const trimmedText = text.trim(); if (!trimmedText) { message.error('Clipboard is empty'); return; } const result = ConnectionStringParser.parse(trimmedText); if ('error' in result) { message.error(result.error); return; } if (!editingDatabase?.postgresql) return; const updatedDatabase: Database = { ...editingDatabase, postgresql: { ...editingDatabase.postgresql, host: result.host, port: result.port, username: result.username, password: result.password, database: result.database, isHttps: result.isHttps, cpuCount: 1, }, }; setEditingDatabase(autoAddPublicSchemaForSupabase(updatedDatabase)); setIsConnectionTested(false); message.success('Connection string parsed successfully'); }; const parseFromClipboard = async () => { if (!ClipboardHelper.isClipboardApiAvailable()) { setIsShowPasteModal(true); return; } try { const text = await ClipboardHelper.readFromClipboard(); applyConnectionString(text); } catch { message.error('Failed to read clipboard. Please check browser permissions.'); } }; const autoAddPublicSchemaForSupabase = (updatedDatabase: Database): Database => { if (hasAutoAddedPublicSchema) return updatedDatabase; const host = updatedDatabase.postgresql?.host || ''; const username = updatedDatabase.postgresql?.username || ''; const isSupabase = host.includes('supabase') || username.includes('supabase'); if (isSupabase && updatedDatabase.postgresql) { setHasAutoAddedPublicSchema(true); const currentSchemas = updatedDatabase.postgresql.includeSchemas || []; if (!currentSchemas.includes('public')) { return { ...updatedDatabase, postgresql: { ...updatedDatabase.postgresql, includeSchemas: ['public', ...currentSchemas], }, }; } } return updatedDatabase; }; const testConnection = async () => { if (!editingDatabase?.postgresql) return; setIsTestingConnection(true); setIsConnectionFailed(false); const trimmedDatabase = { ...editingDatabase, postgresql: { ...editingDatabase.postgresql, password: editingDatabase.postgresql.password?.trim(), }, }; try { await databaseApi.testDatabaseConnectionDirect(trimmedDatabase); setIsConnectionTested(true); ToastHelper.showToast({ title: 'Connection test passed', description: 'You can continue with the next step', }); } catch (e) { setIsConnectionFailed(true); alert((e as Error).message); } setIsTestingConnection(false); }; const saveDatabase = async () => { if (!editingDatabase?.postgresql) return; const trimmedDatabase = { ...editingDatabase, postgresql: { ...editingDatabase.postgresql, password: editingDatabase.postgresql.password?.trim(), }, }; if (isSaveToApi) { setIsSaving(true); try { await databaseApi.updateDatabase(trimmedDatabase); } catch (e) { alert((e as Error).message); } setIsSaving(false); } onSaved(trimmedDatabase); }; useEffect(() => { setIsSaving(false); setIsConnectionTested(false); setIsTestingConnection(false); setIsConnectionFailed(false); setEditingDatabase({ ...database }); }, [database]); if (!editingDatabase) return null; const backupType = editingDatabase.postgresql?.backupType; const renderBackupTypeSelector = () => { if (editingDatabase.id || IS_CLOUD) return null; return (
Backup type
{ if (!editingDatabase.postgresql) return; const updatedDatabase = { ...editingDatabase, postgresql: { ...editingDatabase.postgresql, host: e.target.value.trim().replace('https://', '').replace('http://', ''), }, }; setEditingDatabase(autoAddPublicSchemaForSupabase(updatedDatabase)); setIsConnectionTested(false); }} size="small" className="max-w-[200px] grow" placeholder="Enter PG host" />
{isLocalhostDb && !IS_CLOUD && (
Please{' '} read this document {' '} to study how to backup local database
)} {isSupabaseDb && (
Please{' '} read this document {' '} to study how to backup Supabase database
)}
Port
{ if (!editingDatabase.postgresql || e === null) return; setEditingDatabase({ ...editingDatabase, postgresql: { ...editingDatabase.postgresql, port: e }, }); setIsConnectionTested(false); }} size="small" className="max-w-[200px] grow" placeholder="Enter PG port" />
Username
{ if (!editingDatabase.postgresql) return; const updatedDatabase = { ...editingDatabase, postgresql: { ...editingDatabase.postgresql, username: e.target.value.trim() }, }; setEditingDatabase(autoAddPublicSchemaForSupabase(updatedDatabase)); setIsConnectionTested(false); }} size="small" className="max-w-[200px] grow" placeholder="Enter PG username" />
Password
{ if (!editingDatabase.postgresql) return; setEditingDatabase({ ...editingDatabase, postgresql: { ...editingDatabase.postgresql, password: e.target.value }, }); setIsConnectionTested(false); }} size="small" className="max-w-[200px] grow" placeholder="Enter PG password" autoComplete="off" data-1p-ignore data-lpignore="true" data-form-type="other" />
{isShowDbName && (
DB name
{ if (!editingDatabase.postgresql) return; setEditingDatabase({ ...editingDatabase, postgresql: { ...editingDatabase.postgresql, database: e.target.value.trim() }, }); setIsConnectionTested(false); }} size="small" className="max-w-[200px] grow" placeholder="Enter PG database name" />
)}
Use HTTPS
{ if (!editingDatabase.postgresql) return; setEditingDatabase({ ...editingDatabase, postgresql: { ...editingDatabase.postgresql, isHttps: checked }, }); setIsConnectionTested(false); }} size="small" />
{isRestoreMode && !IS_CLOUD && (
CPU count
{ if (!editingDatabase.postgresql) return; setEditingDatabase({ ...editingDatabase, postgresql: { ...editingDatabase.postgresql, cpuCount: value || 1 }, }); setIsConnectionTested(false); }} size="small" className="max-w-[75px] grow" />
)}
setShowAdvanced(!isShowAdvanced)} > Advanced settings {isShowAdvanced ? ( ) : ( )}
{isShowAdvanced && ( <> {!isRestoreMode && (
Include schemas