fix: continue transfer after table failure

This commit is contained in:
t8y2
2026-05-28 16:11:29 +08:00
parent 1c03917609
commit d35aeaa08c
10 changed files with 138 additions and 41 deletions
@@ -279,9 +279,11 @@ async function startTransfer() {
try {
await api.startTransfer(request, (progress) => {
transferProgress.value.set(progress.table, progress);
transferProgress.value = new Map(transferProgress.value);
currentTable.value = progress.table;
if (progress.table) {
transferProgress.value.set(progress.table, progress);
transferProgress.value = new Map(transferProgress.value);
currentTable.value = progress.table;
}
const nextState = nextTransferTerminalState(
{
@@ -317,13 +319,38 @@ function getConnectionType(id: string): DatabaseType {
return store.connections.find((c) => c.id === id)?.db_type ?? "mysql";
}
const processedStatuses = new Set<TransferProgress["status"]>(["tableDone", "done", "error", "cancelled"]);
function formatRowCount(count: number) {
return count.toLocaleString();
}
function formatTableRows(progress: TransferProgress) {
if (typeof progress.totalRows === "number") {
return `${formatRowCount(progress.rowsTransferred)} / ${formatRowCount(progress.totalRows)}`;
}
return formatRowCount(progress.rowsTransferred);
}
const completedTables = computed(
() => [...transferProgress.value.values()].filter((p) => p.status === "tableDone" || p.status === "done").length,
() => [...transferProgress.value.values()].filter((p) => processedStatuses.has(p.status)).length,
);
const failedTables = computed(() => [...transferProgress.value.values()].filter((p) => p.status === "error").length);
const totalTransferred = computed(() =>
[...transferProgress.value.values()].reduce((sum, p) => sum + p.rowsTransferred, 0),
);
const knownTotalRows = computed(() =>
[...transferProgress.value.values()].reduce((sum, p) => sum + (typeof p.totalRows === "number" ? p.totalRows : 0), 0),
);
const overallRowsLabel = computed(() =>
knownTotalRows.value > 0
? `${formatRowCount(totalTransferred.value)} / ${formatRowCount(knownTotalRows.value)}`
: formatRowCount(totalTransferred.value),
);
</script>
<template>
@@ -532,10 +559,15 @@ const totalTransferred = computed(() =>
<div class="flex items-center justify-between text-xs text-muted-foreground">
<span>
{{ t("transfer.overallProgress") }}: {{ completedTables }} / {{ selectedTables.size }}
{{ t("transfer.tables").toLowerCase() }} · {{ totalTransferred.toLocaleString() }}
{{ t("transfer.tables").toLowerCase() }} · {{ overallRowsLabel }}
{{ t("grid.rows", { count: "" }).trim() }}
</span>
<span v-if="overallDone" class="text-green-600 font-medium">{{ t("transfer.completed") }}</span>
<span v-if="overallDone && !failedTables" class="text-green-600 font-medium">{{
t("transfer.completed")
}}</span>
<span v-else-if="overallDone && failedTables" class="text-amber-600 font-medium">
{{ t("transfer.completedWithErrors", { count: failedTables }) }}
</span>
<span v-else-if="overallCancelled" class="text-yellow-600 font-medium">{{ t("transfer.cancelled") }}</span>
<span v-else-if="overallError" class="text-destructive font-medium">{{ t("transfer.failed") }}</span>
</div>
@@ -543,7 +575,15 @@ const totalTransferred = computed(() =>
<div class="w-full bg-muted rounded-full h-2 overflow-hidden">
<div
class="h-full rounded-full transition-all duration-300"
:class="overallError ? 'bg-destructive' : overallCancelled ? 'bg-yellow-500' : 'bg-primary'"
:class="
overallError
? 'bg-destructive'
: overallCancelled
? 'bg-yellow-500'
: overallDone && failedTables
? 'bg-amber-500'
: 'bg-primary'
"
:style="{
width: `${selectedTables.size ? (completedTables / selectedTables.size) * 100 : 0}%`,
}"
@@ -561,7 +601,7 @@ const totalTransferred = computed(() =>
<template v-if="transferProgress.get(table)">
<template v-if="transferProgress.get(table)!.status === 'running'">
<Loader2 class="w-3 h-3 animate-spin text-primary" />
<span>{{ transferProgress.get(table)!.rowsTransferred.toLocaleString() }}</span>
<span>{{ formatTableRows(transferProgress.get(table)!) }}</span>
</template>
<template
v-else-if="
@@ -570,10 +610,11 @@ const totalTransferred = computed(() =>
"
>
<Check class="w-3 h-3 text-green-500" />
<span>{{ transferProgress.get(table)!.rowsTransferred.toLocaleString() }}</span>
<span>{{ formatTableRows(transferProgress.get(table)!) }}</span>
</template>
<template v-else-if="transferProgress.get(table)!.status === 'error'">
<X class="w-3 h-3 text-destructive" />
<span>{{ formatTableRows(transferProgress.get(table)!) }}</span>
<span
class="text-destructive truncate max-w-[160px]"
:title="transferProgress.get(table)!.error ?? ''"
+1
View File
@@ -1226,6 +1226,7 @@ export default {
progress: "Transferring...",
transferring: "Transferring {table}...",
completed: "Transfer completed",
completedWithErrors: "Completed with {count} failed table | Completed with {count} failed tables",
failed: "Transfer failed",
cancelled: "Transfer cancelled",
rowsTransferred: "{count} rows transferred",
+1
View File
@@ -1118,6 +1118,7 @@ export default {
progress: "Transfiriendo...",
transferring: "Transfiriendo {table}...",
completed: "Transferencia completada",
completedWithErrors: "Completada con {count} tabla con error | Completada con {count} tablas con error",
failed: "Error en la transferencia",
cancelled: "Transferencia cancelada",
rowsTransferred: "{count} filas transferidas",
+1
View File
@@ -1202,6 +1202,7 @@ export default {
progress: "传输中...",
transferring: "正在传输 {table}...",
completed: "传输完成",
completedWithErrors: "传输完成,{count} 个表失败",
failed: "传输失败",
cancelled: "传输已取消",
rowsTransferred: "已传输 {count} 行",
+1 -1
View File
@@ -929,7 +929,7 @@ export async function startTransfer(
es.onmessage = (e) => {
const progress: TransferProgress = JSON.parse(e.data);
onProgress(progress);
if (progress.status === "done" || progress.status === "error" || progress.status === "cancelled") {
if (progress.status === "done" || progress.status === "cancelled") {
es.close();
resolve();
}
+16 -12
View File
@@ -1246,20 +1246,24 @@ export async function startTransfer(
request: TransferRequest,
onProgress: (progress: TransferProgress) => void,
): Promise<void> {
const unlisten: UnlistenFn = await listen<TransferProgress>("transfer-progress", (event) => {
if (event.payload.transferId === request.transferId) {
onProgress(event.payload);
if (event.payload.status === "done" || event.payload.status === "error" || event.payload.status === "cancelled") {
unlisten();
}
return new Promise(async (resolve, reject) => {
let unlisten: UnlistenFn | null = null;
try {
unlisten = await listen<TransferProgress>("transfer-progress", (event) => {
if (event.payload.transferId !== request.transferId) return;
onProgress(event.payload);
if (event.payload.status === "done" || event.payload.status === "cancelled") {
unlisten?.();
resolve();
}
});
await invoke("start_transfer", { request });
} catch (e) {
unlisten?.();
reject(e);
}
});
try {
await invoke("start_transfer", { request });
} catch (e) {
unlisten();
throw e;
}
}
export async function cancelTransfer(transferId: string): Promise<void> {
@@ -14,6 +14,5 @@ export function nextTransferTerminalState(
): TransferTerminalState {
if (progress.status === "done") return { ...state, done: true };
if (progress.status === "cancelled") return { ...state, cancelled: true };
if (progress.status === "error") return { ...state, error: true };
return state;
}