fix(export): show progress while preparing database export
This commit is contained in:
parent
8841a41261
commit
a4352d6560
|
|
@ -17,7 +17,7 @@ import { buildSelectedTablesPayload } from "@/lib/export/databaseExportSelection
|
|||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Download, Square, CheckSquare, Search, X } from "@lucide/vue";
|
||||
import { Download, Square, CheckSquare, Search, X, Loader2 } from "@lucide/vue";
|
||||
import { useExportTracker } from "@/composables/useExportTracker";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
@ -230,11 +230,6 @@ async function startExport() {
|
|||
await startAllDatabasesExport();
|
||||
return;
|
||||
}
|
||||
isExporting.value = true;
|
||||
exportDone.value = false;
|
||||
exportError.value = null;
|
||||
exportCancelled.value = false;
|
||||
exportProgress.value = null;
|
||||
|
||||
exportId.value = generateDatabaseExportId();
|
||||
|
||||
|
|
@ -248,13 +243,9 @@ async function startExport() {
|
|||
defaultPath: `${safeName}.sql`,
|
||||
filters: [{ name: "SQL", extensions: ["sql"] }],
|
||||
});
|
||||
if (!path) {
|
||||
isExporting.value = false;
|
||||
return;
|
||||
}
|
||||
if (!path) return;
|
||||
filePath = path;
|
||||
} catch (e: any) {
|
||||
isExporting.value = false;
|
||||
toast(e?.message || String(e), 5000);
|
||||
return;
|
||||
}
|
||||
|
|
@ -263,6 +254,24 @@ async function startExport() {
|
|||
filePath = `__web_export_${exportId.value}.sql`;
|
||||
}
|
||||
|
||||
// Switch to the progress view only after the save dialog closes, and seed a
|
||||
// preparing state so the dialog is never a blank panel while metadata loads.
|
||||
isExporting.value = true;
|
||||
exportDone.value = false;
|
||||
exportError.value = null;
|
||||
exportCancelled.value = false;
|
||||
exportProgress.value = {
|
||||
exportId: exportId.value,
|
||||
currentObject: "",
|
||||
objectIndex: 0,
|
||||
totalObjects: 0,
|
||||
rowsExported: 0,
|
||||
totalRows: null,
|
||||
status: "Running",
|
||||
error: null,
|
||||
preparing: true,
|
||||
};
|
||||
|
||||
const request: api.DatabaseExportRequest = {
|
||||
exportId: exportId.value,
|
||||
connectionId: connectionId.value,
|
||||
|
|
@ -339,19 +348,40 @@ async function startAllDatabasesExport() {
|
|||
exportDone.value = false;
|
||||
exportError.value = null;
|
||||
exportCancelled.value = false;
|
||||
exportProgress.value = null;
|
||||
batchDatabaseIndex.value = 0;
|
||||
batchRowsExported.value = 0;
|
||||
|
||||
const dbs = [...selectedDatabases.value];
|
||||
const batchId = generateDatabaseExportId();
|
||||
exportId.value = batchId;
|
||||
exportProgress.value = {
|
||||
exportId: batchId,
|
||||
currentObject: "",
|
||||
objectIndex: 0,
|
||||
totalObjects: 0,
|
||||
rowsExported: 0,
|
||||
totalRows: null,
|
||||
status: "Running",
|
||||
error: null,
|
||||
preparing: true,
|
||||
};
|
||||
addDatabaseExportTask(batchId, t("databaseExport.allDatabasesTask", { count: dbs.length }), directoryPath);
|
||||
let exportPlan: AllDatabaseExportPlanItem[] = [];
|
||||
|
||||
try {
|
||||
exportPlan = await buildExportPlanForDatabases(dbs);
|
||||
batchDatabaseTotal.value = exportPlan.length;
|
||||
exportProgress.value = {
|
||||
exportId: batchId,
|
||||
currentObject: "",
|
||||
objectIndex: 0,
|
||||
totalObjects: exportPlan.length,
|
||||
rowsExported: 0,
|
||||
totalRows: null,
|
||||
status: "Running",
|
||||
error: null,
|
||||
preparing: true,
|
||||
};
|
||||
|
||||
for (let index = 0; index < exportPlan.length; index += 1) {
|
||||
if (exportCancelled.value) break;
|
||||
|
|
@ -490,14 +520,40 @@ const progressPercent = computed(() => {
|
|||
if (exportAllDatabases.value && batchDatabaseTotal.value > 0) {
|
||||
if (exportDone.value) return 100;
|
||||
const current = exportProgress.value;
|
||||
const currentDatabaseProgress = current && current.totalObjects > 0 ? current.objectIndex / current.totalObjects : 0;
|
||||
const currentDatabaseProgress = current && !current.preparing && current.totalObjects > 0 ? current.objectIndex / current.totalObjects : 0;
|
||||
return Math.round(Math.min(1, (Math.max(0, batchDatabaseIndex.value - 1) + currentDatabaseProgress) / batchDatabaseTotal.value) * 100);
|
||||
}
|
||||
const p = exportProgress.value;
|
||||
if (!p || p.totalObjects === 0) return 0;
|
||||
if (!p || p.preparing || p.totalObjects === 0) return 0;
|
||||
return Math.round((p.objectIndex / p.totalObjects) * 100);
|
||||
});
|
||||
|
||||
/** True while schema metadata is still loading — before objects are written. */
|
||||
const isPreparingExport = computed(() => {
|
||||
if (!isExporting.value) return false;
|
||||
if (exportDone.value || exportError.value || exportCancelled.value) return false;
|
||||
const p = exportProgress.value;
|
||||
if (!p) return true;
|
||||
return !!p.preparing || p.totalObjects <= 0;
|
||||
});
|
||||
|
||||
const progressStatusText = computed(() => {
|
||||
const p = exportProgress.value;
|
||||
if (isPreparingExport.value) {
|
||||
// Keep it as presence feedback only — no second progress counter that later resets.
|
||||
if (p?.currentObject) {
|
||||
return t("databaseExport.preparingObject", { object: p.currentObject });
|
||||
}
|
||||
return t("databaseExport.preparing");
|
||||
}
|
||||
if (!p) return t("databaseExport.exporting");
|
||||
return t("databaseExport.currentTable", {
|
||||
table: p.currentObject,
|
||||
current: p.objectIndex,
|
||||
total: p.totalObjects,
|
||||
});
|
||||
});
|
||||
|
||||
const skipConnectionWatch = ref(false);
|
||||
|
||||
watch(connectionId, (id) => {
|
||||
|
|
@ -732,22 +788,18 @@ watch(
|
|||
<div v-if="exportAllDatabases && batchDatabaseTotal" class="text-xs text-muted-foreground">
|
||||
{{ t("databaseExport.currentDatabase", { current: batchDatabaseIndex, total: batchDatabaseTotal }) }}
|
||||
</div>
|
||||
<div v-if="exportProgress" class="space-y-2">
|
||||
<div v-if="!exportAllDatabases || !exportDone" class="text-xs text-muted-foreground">
|
||||
{{
|
||||
t("databaseExport.currentTable", {
|
||||
table: exportProgress.currentObject,
|
||||
current: exportProgress.objectIndex,
|
||||
total: exportProgress.totalObjects,
|
||||
})
|
||||
}}
|
||||
<div class="space-y-2">
|
||||
<div v-if="!exportAllDatabases || !exportDone" class="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Loader2 v-if="isExporting && !exportDone && !exportError && !exportCancelled" class="h-3.5 w-3.5 shrink-0 animate-spin text-primary" />
|
||||
<span>{{ progressStatusText }}</span>
|
||||
</div>
|
||||
|
||||
<div class="w-full bg-muted rounded-full h-2 overflow-hidden">
|
||||
<div class="h-full rounded-full transition-[width] duration-300" :class="exportError ? 'bg-destructive' : exportCancelled ? 'bg-yellow-500' : 'bg-primary'" :style="{ width: `${progressPercent}%` }" />
|
||||
<div v-if="isPreparingExport" class="database-export-progress-indeterminate h-full rounded-full bg-primary" />
|
||||
<div v-else class="h-full rounded-full transition-[width] duration-300" :class="exportError ? 'bg-destructive' : exportCancelled ? 'bg-yellow-500' : exportDone ? 'bg-green-500' : 'bg-primary'" :style="{ width: `${exportDone ? 100 : progressPercent}%` }" />
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-muted-foreground">
|
||||
<div v-if="exportProgress && !isPreparingExport" class="text-xs text-muted-foreground">
|
||||
{{ exportAllDatabases ? t("databaseExport.allRowsExported", { count: exportProgress.rowsExported.toLocaleString() }) : t("databaseExport.rowsExported", { current: exportProgress.objectIndex, total: exportProgress.totalObjects, count: exportProgress.rowsExported.toLocaleString() }) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -793,3 +845,22 @@ watch(
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.database-export-progress-indeterminate {
|
||||
width: 42%;
|
||||
animation: database-export-progress-slide 1.15s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes database-export-progress-slide {
|
||||
0% {
|
||||
transform: translateX(-110%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(70%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(250%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -381,7 +381,10 @@ export function useExportTracker() {
|
|||
function updateDatabaseExportTask(exportId: string, progress: api.ExportProgress) {
|
||||
const task = taskMap.get(exportId);
|
||||
if (!task) return;
|
||||
task.tableName = progress.currentObject || task.tableName;
|
||||
// Keep the database label during metadata prefetch; only follow object names while writing.
|
||||
if (!progress.preparing && progress.currentObject) {
|
||||
task.tableName = progress.currentObject;
|
||||
}
|
||||
task.rowsExported = progress.rowsExported;
|
||||
task.totalRows = progress.totalRows;
|
||||
task.status = normalizeExportStatus(progress.status);
|
||||
|
|
|
|||
|
|
@ -4986,6 +4986,8 @@ export default {
|
|||
export: "Export",
|
||||
exportAllDatabases: "Export All Databases",
|
||||
exporting: "Exporting...",
|
||||
preparing: "Preparing export (reading tables and metadata)...",
|
||||
preparingObject: "Preparing: {object}",
|
||||
selectExportDirectory: "Select export directory",
|
||||
currentDatabase: "Database {current}/{total}",
|
||||
currentTable: "Current: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -4753,6 +4753,8 @@ export default withEnglishFallback({
|
|||
export: "Exportar",
|
||||
exportAllDatabases: "Exportar todas las bases de datos",
|
||||
exporting: "Exportando...",
|
||||
preparing: "Preparando exportación (leyendo tablas y metadatos)...",
|
||||
preparingObject: "Preparando: {object}",
|
||||
selectExportDirectory: "Seleccionar directorio de exportación",
|
||||
currentDatabase: "Base de datos {current}/{total}",
|
||||
currentTable: "Actual: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -4752,6 +4752,8 @@ export default withEnglishFallback({
|
|||
export: "Esporta",
|
||||
exportAllDatabases: "Esporta tutti i database",
|
||||
exporting: "Esportazione in corso...",
|
||||
preparing: "Preparazione esportazione (lettura tabelle e metadati)...",
|
||||
preparingObject: "Preparazione: {object}",
|
||||
selectExportDirectory: "Seleziona directory di esportazione",
|
||||
currentDatabase: "Database {current}/{total}",
|
||||
currentTable: "Corrente: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -4753,6 +4753,8 @@ export default withEnglishFallback({
|
|||
export: "エクスポート",
|
||||
exportAllDatabases: "すべてのデータベースをエクスポート",
|
||||
exporting: "エクスポート中...",
|
||||
preparing: "エクスポートを準備中(テーブルとメタデータを読み取り中)...",
|
||||
preparingObject: "準備中: {object}",
|
||||
selectExportDirectory: "エクスポート先ディレクトリを選択",
|
||||
currentDatabase: "データベース {current}/{total}",
|
||||
currentTable: "現在: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -4755,6 +4755,8 @@ export default withEnglishFallback({
|
|||
export: "Exportar",
|
||||
exportAllDatabases: "Exportar todos os bancos de dados",
|
||||
exporting: "Exportando...",
|
||||
preparing: "Preparando exportação (lendo tabelas e metadados)...",
|
||||
preparingObject: "Preparando: {object}",
|
||||
selectExportDirectory: "Selecionar diretório de exportação",
|
||||
currentDatabase: "Banco de dados {current}/{total}",
|
||||
currentTable: "Atual: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -4985,6 +4985,8 @@ export default withEnglishFallback({
|
|||
export: "导出",
|
||||
exportAllDatabases: "导出全部数据库",
|
||||
exporting: "正在导出...",
|
||||
preparing: "正在准备导出(读取表与元数据)...",
|
||||
preparingObject: "正在准备: {object}",
|
||||
selectExportDirectory: "选择导出目录",
|
||||
currentDatabase: "数据库 {current}/{total}",
|
||||
currentTable: "当前: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -4422,6 +4422,8 @@ export default withEnglishFallback({
|
|||
export: "匯出",
|
||||
exportAllDatabases: "匯出全部資料庫",
|
||||
exporting: "正在匯出……",
|
||||
preparing: "正在準備匯出(讀取資料表與中繼資料)...",
|
||||
preparingObject: "正在準備: {object}",
|
||||
selectExportDirectory: "選擇匯出目錄",
|
||||
currentDatabase: "資料庫 {current}/{total}",
|
||||
currentTable: "目前: {table} ({current}/{total})",
|
||||
|
|
|
|||
|
|
@ -2851,6 +2851,8 @@ export interface ExportProgress {
|
|||
totalRows: number | null;
|
||||
status: "Running" | "Done" | "Error" | "Cancelled";
|
||||
error: string | null;
|
||||
/** True while listing schema / prefetching metadata before objects are written. */
|
||||
preparing?: boolean;
|
||||
}
|
||||
|
||||
// --- Table Export ---
|
||||
|
|
|
|||
|
|
@ -165,6 +165,9 @@ pub struct ExportProgress {
|
|||
pub total_rows: Option<u64>,
|
||||
pub status: ExportStatus,
|
||||
pub error: Option<String>,
|
||||
/// True while listing schema / prefetching table metadata — before objects are written.
|
||||
#[serde(default)]
|
||||
pub preparing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -1294,11 +1297,36 @@ fn write_database_export_rows(
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_database_export_running(
|
||||
on_progress: &(impl Fn(ExportProgress) + Sync),
|
||||
export_id: &str,
|
||||
current_object: impl Into<String>,
|
||||
object_index: usize,
|
||||
total_objects: usize,
|
||||
rows_exported: u64,
|
||||
preparing: bool,
|
||||
) {
|
||||
on_progress(ExportProgress {
|
||||
export_id: export_id.to_string(),
|
||||
current_object: current_object.into(),
|
||||
object_index,
|
||||
total_objects,
|
||||
rows_exported,
|
||||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing,
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn export_database_sql_core(
|
||||
state: &crate::connection::AppState,
|
||||
request: &DatabaseExportRequest,
|
||||
on_progress: impl Fn(ExportProgress) + Sync,
|
||||
) -> Result<(), String> {
|
||||
// Emit immediately so the UI is never blank while we list schema metadata.
|
||||
emit_database_export_running(&on_progress, &request.export_id, "", 0, 0, 0, true);
|
||||
|
||||
// 1. Get database type
|
||||
let db_type = state
|
||||
.configs
|
||||
|
|
@ -1493,53 +1521,15 @@ pub async fn export_database_sql_core(
|
|||
|
||||
let mut object_index: usize = 0;
|
||||
let mut total_rows_exported = 0_u64;
|
||||
// total_objects is known later for the write phase; preparing updates stay
|
||||
// presence-only so the UI does not show a counter that later resets.
|
||||
emit_database_export_running(&on_progress, &request.export_id, "", 0, 0, 0, true);
|
||||
|
||||
// Export tables
|
||||
let batch_size = if request.batch_size == 0 { 1000 } else { request.batch_size };
|
||||
|
||||
for extension in &postgres_extensions {
|
||||
if is_export_cancelled(&request.export_id).await {
|
||||
return Err("Export cancelled".to_string());
|
||||
}
|
||||
on_progress(ExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
current_object: extension.name.clone(),
|
||||
object_index,
|
||||
total_objects,
|
||||
rows_exported: total_rows_exported,
|
||||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
});
|
||||
writeln!(file, "{}\n", generate_postgres_extension_ddl(extension))
|
||||
.map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
object_index += 1;
|
||||
}
|
||||
|
||||
for sequence in postgres_sequences.iter().filter(|sequence| sequence.owner_table.is_none()) {
|
||||
if is_export_cancelled(&request.export_id).await {
|
||||
return Err("Export cancelled".to_string());
|
||||
}
|
||||
|
||||
on_progress(ExportProgress {
|
||||
export_id: request.export_id.clone(),
|
||||
current_object: sequence.name.clone(),
|
||||
object_index,
|
||||
total_objects,
|
||||
rows_exported: total_rows_exported,
|
||||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
});
|
||||
|
||||
writeln!(file, "{};\n", generate_postgres_sequence_create_ddl(sequence, &request.schema))
|
||||
.map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
object_index += 1;
|
||||
}
|
||||
|
||||
// 预取各表的 DDL 与列元数据:逐表串行往返在多表数据库上是整库导出耗时的
|
||||
// 主要来源(每表 1-2 次网络往返 × 表数)。有界并发预取后,下方写出循环仍按
|
||||
// 原顺序消费,文件内容与逐表查询完全一致。
|
||||
// 原顺序消费,文件内容与逐表查询完全一致。放在写出循环之前,避免准备态与导出态来回跳。
|
||||
struct PrefetchedTableMetadata {
|
||||
ddl: Option<Result<String, String>>,
|
||||
columns: Option<Result<Vec<crate::db::ColumnInfo>, String>>,
|
||||
|
|
@ -1611,6 +1601,18 @@ pub async fn export_database_sql_core(
|
|||
.buffer_unordered(EXPORT_METADATA_PREFETCH_CONCURRENCY);
|
||||
while let Some((index, metadata)) = prefetch_stream.next().await {
|
||||
prefetched_table_metadata[index] = Some(metadata);
|
||||
if let Some(table_info) = tables.get(index) {
|
||||
// Presence-only updates: no prepare counter that later resets to 0/N.
|
||||
emit_database_export_running(
|
||||
&on_progress,
|
||||
&request.export_id,
|
||||
table_info.name.clone(),
|
||||
0,
|
||||
0,
|
||||
total_rows_exported,
|
||||
true,
|
||||
);
|
||||
}
|
||||
// 取消后不再调度新的预取任务(已在途的任务随 stream 释放而中止),
|
||||
// 写出循环入口的取消检查负责最终收尾
|
||||
if is_export_cancelled_now(&request.export_id) {
|
||||
|
|
@ -1619,6 +1621,44 @@ pub async fn export_database_sql_core(
|
|||
}
|
||||
}
|
||||
|
||||
for extension in &postgres_extensions {
|
||||
if is_export_cancelled(&request.export_id).await {
|
||||
return Err("Export cancelled".to_string());
|
||||
}
|
||||
emit_database_export_running(
|
||||
&on_progress,
|
||||
&request.export_id,
|
||||
extension.name.clone(),
|
||||
object_index,
|
||||
total_objects,
|
||||
total_rows_exported,
|
||||
false,
|
||||
);
|
||||
writeln!(file, "{}\n", generate_postgres_extension_ddl(extension))
|
||||
.map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
object_index += 1;
|
||||
}
|
||||
|
||||
for sequence in postgres_sequences.iter().filter(|sequence| sequence.owner_table.is_none()) {
|
||||
if is_export_cancelled(&request.export_id).await {
|
||||
return Err("Export cancelled".to_string());
|
||||
}
|
||||
|
||||
emit_database_export_running(
|
||||
&on_progress,
|
||||
&request.export_id,
|
||||
sequence.name.clone(),
|
||||
object_index,
|
||||
total_objects,
|
||||
total_rows_exported,
|
||||
false,
|
||||
);
|
||||
|
||||
writeln!(file, "{};\n", generate_postgres_sequence_create_ddl(sequence, &request.schema))
|
||||
.map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
object_index += 1;
|
||||
}
|
||||
|
||||
for (table_index, table_info) in tables.iter().enumerate().filter(|_| exports_database_tables(request)) {
|
||||
// Check cancellation
|
||||
if is_export_cancelled(&request.export_id).await {
|
||||
|
|
@ -1631,6 +1671,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Cancelled,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -1647,6 +1688,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
|
||||
// Export structure
|
||||
|
|
@ -1668,6 +1710,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
|
||||
writeln!(file, "{};\n", generate_postgres_sequence_create_ddl(sequence, &request.schema))
|
||||
|
|
@ -1779,6 +1822,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
Ok(())
|
||||
},
|
||||
|
|
@ -1816,6 +1860,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows,
|
||||
status: ExportStatus::Cancelled,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -1864,6 +1909,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
if row_count < batch_size {
|
||||
break;
|
||||
|
|
@ -1907,6 +1953,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
|
||||
match crate::schema::get_object_source_core(
|
||||
|
|
@ -1958,6 +2005,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
|
||||
match crate::schema::get_object_source_core(
|
||||
|
|
@ -2013,6 +2061,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Running,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
|
||||
match crate::schema::get_object_source_core(
|
||||
|
|
@ -2067,6 +2116,7 @@ pub async fn export_database_sql_core(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Done,
|
||||
error: None,
|
||||
preparing: false,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ pub async fn start_database_export(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Error,
|
||||
error: Some(e.clone()),
|
||||
preparing: false,
|
||||
};
|
||||
if let Ok(json) = serde_json::to_string(&progress) {
|
||||
let _ = tx.send(json);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ pub async fn export_database_sql(
|
|||
total_rows: None,
|
||||
status: ExportStatus::Error,
|
||||
error: Some(e),
|
||||
preparing: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue