'use client'; import { useRef, useState } from 'react'; import { Database, Eye, EyeOff, RefreshCw, AlertCircle } from 'lucide-react'; import { notify } from '@/lib/notify'; import { useT } from '@/i18n/I18nProvider'; import { Select } from '@/components/ui/select'; import { ONE_GIB, minGiToFitFileBytes } from '@/lib/storage-utils'; export type DatabaseEngine = 'postgresql' | 'mysql' | 'mariadb' | 'mongodb'; export interface ManagedDatabaseFormState { databaseType: DatabaseEngine; dbVersion: string; dbUsername: string; dbPassword: string; dbStorageSize: string; } const DB_OPTIONS: { value: DatabaseEngine; label: string; versions: string[] }[] = [ { value: 'postgresql', label: 'PostgreSQL', versions: ['17', '16', '15', '14'] }, { value: 'mysql', label: 'MySQL', versions: ['9.0', '8.4', '8.0', '5.7'] }, { value: 'mariadb', label: 'MariaDB', versions: ['11.4', '11.3', '10.11', '10.6'] }, { value: 'mongodb', label: 'MongoDB', versions: ['7.0', '6.0', '5.0'] }, ]; function versionOptions(databaseType: DatabaseEngine) { const opt = DB_OPTIONS.find((o) => o.value === databaseType); return opt?.versions ?? ['16']; } function versionLabel(databaseType: DatabaseEngine, v: string) { if (databaseType === 'postgresql') return `PostgreSQL ${v}`; if (databaseType === 'mysql') return `MySQL ${v}`; if (databaseType === 'mariadb') return `MariaDB ${v}`; return `MongoDB ${v}`; } export function ManagedDatabaseConfig({ form, onChange, dbDumpFile, onDbDumpFileChange, }: { form: ManagedDatabaseFormState; onChange: (patch: Partial) => void; dbDumpFile: File | null; onDbDumpFileChange: (file: File | null) => void; }) { const c = useT().components.dbConfig; const dbDumpInputRef = useRef(null); const [showDbPassword, setShowDbPassword] = useState(false); const [isDragging, setIsDragging] = useState(false); const minDbGiFromRestoreDump = dbDumpFile ? minGiToFitFileBytes(dbDumpFile.size) : 1; const dbGi = parseInt(form.dbStorageSize || '1', 10) || 1; const acceptDump = (f: File) => { if (!f.name.endsWith('.sql') && !f.name.endsWith('.gz') && !f.name.endsWith('.dump')) { notify.error(c.allowedFormats); return; } if (f.size > 500 * 1024 * 1024) { notify.error(c.maxSize); return; } onDbDumpFileChange(f); const suggested = Math.max(minGiToFitFileBytes(f.size), Math.ceil((f.size / ONE_GIB) * 3)); const cur = parseInt(form.dbStorageSize || '1', 10) || 1; onChange({ dbStorageSize: String(Math.max(cur, suggested)) }); }; return (
{DB_OPTIONS.map((opt) => ( ))}
onChange({ dbUsername: e.target.value })} />
onChange({ dbPassword: e.target.value })} />

{c.credentialsNote}

{ e.preventDefault(); setIsDragging(false); const f = e.dataTransfer.files[0]; if (f) acceptDump(f); }} onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }} onDragLeave={() => setIsDragging(false)} onClick={() => dbDumpInputRef.current?.click()} className={`border-2 border-dashed rounded-xl p-3 text-center cursor-pointer transition-colors ${ dbDumpFile ? 'border-blue-400 bg-blue-50' : isDragging ? 'border-blue-400 bg-blue-50' : 'border-gray-300 hover:border-blue-400 hover:bg-gray-50' }`} > { const f = e.target.files?.[0]; if (f) acceptDump(f); e.target.value = ''; }} /> {dbDumpFile ? (

{dbDumpFile.name}

{(dbDumpFile.size / (1024 * 1024)).toFixed(2)} MB

) : (

{c.uploadHint}

{c.uploadConstraints}

)}
{ const val = Math.max( minDbGiFromRestoreDump, Math.min(100, parseInt(e.target.value, 10) || minDbGiFromRestoreDump), ); onChange({ dbStorageSize: String(val) }); }} className="w-16 text-center py-2 border-x border-gray-300 text-sm font-semibold focus:outline-none" />
GB {dbDumpFile && ( {c.suggestedFromDump.replace('{gi}', (dbDumpFile.size / ONE_GIB).toFixed(2))} )}

{c.minimumGb.replace('{n}', String(minDbGiFromRestoreDump))} {dbDumpFile ? c.mustFitDump : ''}{c.expansionOnly}

); } /** * Validates that the chosen DB storage fits the uploaded dump. Pass the localized * message template (components.dbConfig.storageTooSmallDetail) with {dumpGi}/{need}/{selected}. */ export function validateDbDumpStorage( dbDumpFile: File | null, dbStorageSizeGi: number, detailTemplate: string, ): string | null { if (!dbDumpFile) return null; const need = minGiToFitFileBytes(dbDumpFile.size); if (dbStorageSizeGi < need) { return detailTemplate .replace('{dumpGi}', (dbDumpFile.size / ONE_GIB).toFixed(2)) .replace('{need}', String(need)) .replace('{selected}', String(dbStorageSizeGi)); } return null; } export function RestoreStorageErrorModal({ open, message, onClose, }: { open: boolean; message: string; onClose: () => void; }) { const c = useT().components.dbConfig; if (!open) return null; return (

{c.storageTooSmallTitle}

{message}

); }