feat(ui): allow uploading DB dump at app creation — deploy page accepts optional SQL dump and uploads it after app creation
This commit is contained in:
@@ -41,6 +41,9 @@ export default function DeployPage() {
|
|||||||
const [clusterMode, setClusterMode] = useState<'default' | 'manual' | 'pool'>('default');
|
const [clusterMode, setClusterMode] = useState<'default' | 'manual' | 'pool'>('default');
|
||||||
const [showDbPassword, setShowDbPassword] = useState(false);
|
const [showDbPassword, setShowDbPassword] = useState(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [dbDumpFile, setDbDumpFile] = useState<File | null>(null);
|
||||||
|
const dbDumpInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [dbUploadProgress, setDbUploadProgress] = useState(0);
|
||||||
|
|
||||||
const { data: clusters = [] } = useQuery<ClusterPublic[]>({
|
const { data: clusters = [] } = useQuery<ClusterPublic[]>({
|
||||||
queryKey: ['clusters-public'],
|
queryKey: ['clusters-public'],
|
||||||
@@ -71,6 +74,18 @@ export default function DeployPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upload DB dump if provided and a database was requested
|
||||||
|
if (data.databaseType && data.databaseType !== 'none' && dbDumpFile) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', dbDumpFile);
|
||||||
|
await api.post(`/applications/${appId}/db-upload`, formData, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
|
onUploadProgress: (e) => {
|
||||||
|
if (e.total) setDbUploadProgress(Math.round((e.loaded * 100) / e.total));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
@@ -448,6 +463,65 @@ export default function DeployPage() {
|
|||||||
<p className="text-xs text-gray-400">
|
<p className="text-xs text-gray-400">
|
||||||
These credentials are used for internal cluster communication only. The database is not exposed externally.
|
These credentials are used for internal cluster communication only. The database is not exposed externally.
|
||||||
</p>
|
</p>
|
||||||
|
{/* Optional DB dump upload at creation time */}
|
||||||
|
<div className="pt-2">
|
||||||
|
<label className="block text-xs text-gray-500 mb-2">Optional: Upload DB dump to restore at creation</label>
|
||||||
|
<div
|
||||||
|
onDrop={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDragging(false);
|
||||||
|
const f = e.dataTransfer.files[0];
|
||||||
|
if (f) {
|
||||||
|
if (!f.name.endsWith('.sql') && !f.name.endsWith('.gz') && !f.name.endsWith('.dump')) {
|
||||||
|
toast.error('Allowed: .sql, .gz, .dump');
|
||||||
|
} else if (f.size > 500 * 1024 * 1024) {
|
||||||
|
toast.error('Max 500MB');
|
||||||
|
} else {
|
||||||
|
setDbDumpFile(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDragOver={(e) => e.preventDefault()}
|
||||||
|
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' : 'border-gray-300 hover:border-blue-400 hover:bg-gray-50'}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={dbDumpInputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".sql,.gz,.dump"
|
||||||
|
className="hidden"
|
||||||
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0];
|
||||||
|
if (f) {
|
||||||
|
if (!f.name.endsWith('.sql') && !f.name.endsWith('.gz') && !f.name.endsWith('.dump')) {
|
||||||
|
toast.error('Allowed: .sql, .gz, .dump');
|
||||||
|
} else if (f.size > 500 * 1024 * 1024) {
|
||||||
|
toast.error('Max 500MB');
|
||||||
|
} else {
|
||||||
|
setDbDumpFile(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.target.value = '';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{dbDumpFile ? (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-sm text-left">
|
||||||
|
<p className="font-medium text-gray-800">{dbDumpFile.name}</p>
|
||||||
|
<p className="text-xs text-gray-500">{(dbDumpFile.size / (1024 * 1024)).toFixed(2)} MB</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button type="button" onClick={() => setDbDumpFile(null)} className="text-sm text-red-500">Remove</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-700">Upload a SQL dump to be restored after the database is created</p>
|
||||||
|
<p className="text-xs text-gray-400">Optional • Max 500MB</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user