mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-23 16:04:26 +00:00
fix(ui): replace DialogScrollContent with scrollable DialogContent for transfer dialog search input
Replace DialogScrollContent with DialogContent + custom scrollable container to fix focus issue with search input in transfer dialog.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Dialog, DialogHeader, DialogTitle, DialogFooter, DialogScrollContent } from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -355,7 +355,7 @@ const overallRowsLabel = computed(() =>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogScrollContent class="sm:max-w-[560px]" :trap-focus="false" @interact-outside.prevent>
|
||||
<DialogContent class="sm:max-w-[560px] max-h-[80vh] flex flex-col overflow-hidden" @interact-outside.prevent>
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<ArrowRightLeft class="w-4 h-4" />
|
||||
@@ -363,276 +363,285 @@ const overallRowsLabel = computed(() =>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<!-- Config View -->
|
||||
<div v-if="!isTransferring" class="grid gap-4 py-3">
|
||||
<!-- Source Section -->
|
||||
<div class="space-y-3">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{{ t("transfer.source") }}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.sourceConnection") }}</Label>
|
||||
<Select v-model="sourceConnectionId">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<div v-if="sourceConnectionId" class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="getConnectionType(sourceConnectionId)" class="w-3.5 h-3.5" />
|
||||
<span class="truncate">{{ getConnectionName(sourceConnectionId) }}</span>
|
||||
</div>
|
||||
<SelectValue v-else />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="c in sqlConnections" :key="c.id" :value="c.id">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="c.db_type" class="w-3.5 h-3.5" />
|
||||
{{ c.name }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.sourceDatabase") }}</Label>
|
||||
<Select v-model="sourceDatabase" :disabled="!sourceDatabases.length">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="db in sourceDatabases" :key="db" :value="db">{{ db }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="sourceSchemas.length" class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.sourceSchema") }}</Label>
|
||||
<Select v-model="sourceSchema">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="schema in sourceSchemas" :key="schema" :value="schema">{{ schema }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Target Section -->
|
||||
<div class="space-y-3">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{{ t("transfer.target") }}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.targetConnection") }}</Label>
|
||||
<Select v-model="targetConnectionId">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<div v-if="targetConnectionId" class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="getConnectionType(targetConnectionId)" class="w-3.5 h-3.5" />
|
||||
<span class="truncate">{{ getConnectionName(targetConnectionId) }}</span>
|
||||
</div>
|
||||
<SelectValue v-else />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="c in sqlConnections" :key="c.id" :value="c.id">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="c.db_type" class="w-3.5 h-3.5" />
|
||||
{{ c.name }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.targetDatabase") }}</Label>
|
||||
<Select v-model="targetDatabase" :disabled="!targetDatabases.length">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="db in targetDatabases" :key="db" :value="db">{{ db }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="targetSchemas.length" class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.targetSchema") }}</Label>
|
||||
<Select v-model="targetSchema">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="schema in targetSchemas" :key="schema" :value="schema">{{ schema }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tables Section -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 min-h-0 overflow-auto">
|
||||
<!-- Config View -->
|
||||
<div v-if="!isTransferring" class="grid gap-4 py-3">
|
||||
<!-- Source Section -->
|
||||
<div class="space-y-3">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{{ t("transfer.tables") }}
|
||||
<span v-if="sourceTables.length" class="text-muted-foreground/60"
|
||||
>({{ selectedTables.size }}/{{ sourceTables.length }})</span
|
||||
>
|
||||
{{ t("transfer.source") }}
|
||||
</div>
|
||||
<Button
|
||||
v-if="sourceTables.length"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 text-xs px-2"
|
||||
@click="toggleSelectAll"
|
||||
>
|
||||
{{ allSelected ? t("transfer.deselectAll") : t("transfer.selectAll") }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
v-if="sourceTables.length > 5"
|
||||
v-model="tableSearch"
|
||||
:placeholder="t('transfer.searchTables')"
|
||||
class="h-7 text-xs"
|
||||
/>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.sourceConnection") }}</Label>
|
||||
<Select v-model="sourceConnectionId">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<div v-if="sourceConnectionId" class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="getConnectionType(sourceConnectionId)" class="w-3.5 h-3.5" />
|
||||
<span class="truncate">{{ getConnectionName(sourceConnectionId) }}</span>
|
||||
</div>
|
||||
<SelectValue v-else />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="c in sqlConnections" :key="c.id" :value="c.id">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="c.db_type" class="w-3.5 h-3.5" />
|
||||
{{ c.name }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div v-if="loadingTables" class="flex items-center gap-2 text-xs text-muted-foreground py-4 justify-center">
|
||||
<Loader2 class="w-3.5 h-3.5 animate-spin" />
|
||||
{{ t("common.loading") }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!sourceConnectionId || !sourceDatabase"
|
||||
class="text-xs text-muted-foreground py-4 text-center"
|
||||
>
|
||||
{{ t("transfer.selectSourceFirst") }}
|
||||
</div>
|
||||
<div v-else-if="sourceTables.length === 0" class="text-xs text-muted-foreground py-4 text-center">
|
||||
{{ t("transfer.noTables") }}
|
||||
</div>
|
||||
<div v-else class="border rounded-md max-h-[200px] overflow-y-auto">
|
||||
<div
|
||||
v-for="table in filteredTables"
|
||||
:key="table"
|
||||
class="flex items-center gap-2 px-2.5 py-1.5 hover:bg-muted/50 cursor-pointer text-xs"
|
||||
@click="toggleTable(table)"
|
||||
>
|
||||
<CheckSquare v-if="selectedTables.has(table)" class="w-3.5 h-3.5 text-primary shrink-0" />
|
||||
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
|
||||
<span class="truncate">{{ table }}</span>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.sourceDatabase") }}</Label>
|
||||
<Select v-model="sourceDatabase" :disabled="!sourceDatabases.length">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="db in sourceDatabases" :key="db" :value="db">{{ db }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="sourceSchemas.length" class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.sourceSchema") }}</Label>
|
||||
<Select v-model="sourceSchema">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="schema in sourceSchemas" :key="schema" :value="schema">{{ schema }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="space-y-2.5">
|
||||
<div class="flex items-center gap-2 cursor-pointer text-xs" @click="createTable = !createTable">
|
||||
<CheckSquare v-if="createTable" class="w-3.5 h-3.5 text-primary shrink-0" />
|
||||
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
|
||||
{{ t("transfer.createTable") }}
|
||||
<!-- Target Section -->
|
||||
<div class="space-y-3">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{{ t("transfer.target") }}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.targetConnection") }}</Label>
|
||||
<Select v-model="targetConnectionId">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<div v-if="targetConnectionId" class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="getConnectionType(targetConnectionId)" class="w-3.5 h-3.5" />
|
||||
<span class="truncate">{{ getConnectionName(targetConnectionId) }}</span>
|
||||
</div>
|
||||
<SelectValue v-else />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="c in sqlConnections" :key="c.id" :value="c.id">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<DatabaseIcon :db-type="c.db_type" class="w-3.5 h-3.5" />
|
||||
{{ c.name }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.targetDatabase") }}</Label>
|
||||
<Select v-model="targetDatabase" :disabled="!targetDatabases.length">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="db in targetDatabases" :key="db" :value="db">{{ db }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="targetSchemas.length" class="space-y-1.5">
|
||||
<Label class="text-xs">{{ t("transfer.targetSchema") }}</Label>
|
||||
<Select v-model="targetSchema">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="schema in targetSchemas" :key="schema" :value="schema">{{ schema }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Label class="text-xs shrink-0">{{ t("transfer.transferMode") }}</Label>
|
||||
<Select v-model="transferMode">
|
||||
<SelectTrigger class="h-7 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="append">{{ t("transfer.modeAppend") }}</SelectItem>
|
||||
<SelectItem value="overwrite">{{ t("transfer.modeOverwrite") }}</SelectItem>
|
||||
<SelectItem value="upsert">{{ t("transfer.modeUpsert") }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Label class="text-xs shrink-0">{{ t("transfer.batchSize") }}</Label>
|
||||
<Input v-model.number="batchSize" type="number" min="100" max="10000" step="100" class="h-7 text-xs w-24" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress View -->
|
||||
<div v-else class="py-3 space-y-3">
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>
|
||||
{{ t("transfer.overallProgress") }}: {{ completedTables }} / {{ selectedTables.size }}
|
||||
{{ t("transfer.tables").toLowerCase() }} · {{ overallRowsLabel }}
|
||||
{{ t("grid.rows", { count: "" }).trim() }}
|
||||
</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>
|
||||
|
||||
<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'
|
||||
: overallDone && failedTables
|
||||
? 'bg-amber-500'
|
||||
: 'bg-primary'
|
||||
"
|
||||
:style="{
|
||||
width: `${selectedTables.size ? (completedTables / selectedTables.size) * 100 : 0}%`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md max-h-[280px] overflow-y-auto">
|
||||
<div
|
||||
v-for="table in [...selectedTables]"
|
||||
:key="table"
|
||||
class="flex items-center justify-between px-2.5 py-1.5 text-xs border-b last:border-b-0"
|
||||
>
|
||||
<span class="truncate">{{ table }}</span>
|
||||
<div class="flex items-center gap-1.5 shrink-0 text-muted-foreground">
|
||||
<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>{{ formatTableRows(transferProgress.get(table)!) }}</span>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
transferProgress.get(table)!.status === 'tableDone' ||
|
||||
transferProgress.get(table)!.status === 'done'
|
||||
"
|
||||
<!-- Tables Section -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
{{ t("transfer.tables") }}
|
||||
<span v-if="sourceTables.length" class="text-muted-foreground/60"
|
||||
>({{ selectedTables.size }}/{{ sourceTables.length }})</span
|
||||
>
|
||||
<Check class="w-3 h-3 text-green-500" />
|
||||
<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="max-w-[520px] whitespace-normal break-words text-destructive"
|
||||
:title="transferProgress.get(table)!.error ?? ''"
|
||||
>
|
||||
{{ transferProgress.get(table)!.error }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="transferProgress.get(table)!.status === 'cancelled'">
|
||||
<X class="w-3 h-3 text-yellow-500" />
|
||||
<span>{{ t("transfer.cancelled") }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<span v-else class="text-muted-foreground/40">—</span>
|
||||
</div>
|
||||
<Button
|
||||
v-if="sourceTables.length"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 text-xs px-2"
|
||||
@click="toggleSelectAll"
|
||||
>
|
||||
{{ allSelected ? t("transfer.deselectAll") : t("transfer.selectAll") }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
v-if="sourceTables.length > 5"
|
||||
v-model="tableSearch"
|
||||
:placeholder="t('transfer.searchTables')"
|
||||
class="h-7 text-xs"
|
||||
/>
|
||||
|
||||
<div v-if="loadingTables" class="flex items-center gap-2 text-xs text-muted-foreground py-4 justify-center">
|
||||
<Loader2 class="w-3.5 h-3.5 animate-spin" />
|
||||
{{ t("common.loading") }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!sourceConnectionId || !sourceDatabase"
|
||||
class="text-xs text-muted-foreground py-4 text-center"
|
||||
>
|
||||
{{ t("transfer.selectSourceFirst") }}
|
||||
</div>
|
||||
<div v-else-if="sourceTables.length === 0" class="text-xs text-muted-foreground py-4 text-center">
|
||||
{{ t("transfer.noTables") }}
|
||||
</div>
|
||||
<div v-else class="border rounded-md max-h-[200px] overflow-y-auto">
|
||||
<div
|
||||
v-for="table in filteredTables"
|
||||
:key="table"
|
||||
class="flex items-center gap-2 px-2.5 py-1.5 hover:bg-muted/50 cursor-pointer text-xs"
|
||||
@click="toggleTable(table)"
|
||||
>
|
||||
<CheckSquare v-if="selectedTables.has(table)" class="w-3.5 h-3.5 text-primary shrink-0" />
|
||||
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
|
||||
<span class="truncate">{{ table }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="space-y-2.5">
|
||||
<div class="flex items-center gap-2 cursor-pointer text-xs" @click="createTable = !createTable">
|
||||
<CheckSquare v-if="createTable" class="w-3.5 h-3.5 text-primary shrink-0" />
|
||||
<Square v-else class="w-3.5 h-3.5 text-muted-foreground/40 shrink-0" />
|
||||
{{ t("transfer.createTable") }}
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Label class="text-xs shrink-0">{{ t("transfer.transferMode") }}</Label>
|
||||
<Select v-model="transferMode">
|
||||
<SelectTrigger class="h-7 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="append">{{ t("transfer.modeAppend") }}</SelectItem>
|
||||
<SelectItem value="overwrite">{{ t("transfer.modeOverwrite") }}</SelectItem>
|
||||
<SelectItem value="upsert">{{ t("transfer.modeUpsert") }}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Label class="text-xs shrink-0">{{ t("transfer.batchSize") }}</Label>
|
||||
<Input
|
||||
v-model.number="batchSize"
|
||||
type="number"
|
||||
min="100"
|
||||
max="10000"
|
||||
step="100"
|
||||
class="h-7 text-xs w-24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status message -->
|
||||
<!-- Progress View -->
|
||||
<div v-else class="py-3 space-y-3">
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>
|
||||
{{ t("transfer.overallProgress") }}: {{ completedTables }} / {{ selectedTables.size }}
|
||||
{{ t("transfer.tables").toLowerCase() }} · {{ overallRowsLabel }}
|
||||
{{ t("grid.rows", { count: "" }).trim() }}
|
||||
</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>
|
||||
|
||||
<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'
|
||||
: overallDone && failedTables
|
||||
? 'bg-amber-500'
|
||||
: 'bg-primary'
|
||||
"
|
||||
:style="{
|
||||
width: `${selectedTables.size ? (completedTables / selectedTables.size) * 100 : 0}%`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md max-h-[280px] overflow-y-auto">
|
||||
<div
|
||||
v-for="table in [...selectedTables]"
|
||||
:key="table"
|
||||
class="flex items-center justify-between px-2.5 py-1.5 text-xs border-b last:border-b-0"
|
||||
>
|
||||
<span class="truncate">{{ table }}</span>
|
||||
<div class="flex items-center gap-1.5 shrink-0 text-muted-foreground">
|
||||
<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>{{ formatTableRows(transferProgress.get(table)!) }}</span>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="
|
||||
transferProgress.get(table)!.status === 'tableDone' ||
|
||||
transferProgress.get(table)!.status === 'done'
|
||||
"
|
||||
>
|
||||
<Check class="w-3 h-3 text-green-500" />
|
||||
<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="max-w-[520px] whitespace-normal break-words text-destructive"
|
||||
:title="transferProgress.get(table)!.error ?? ''"
|
||||
>
|
||||
{{ transferProgress.get(table)!.error }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="transferProgress.get(table)!.status === 'cancelled'">
|
||||
<X class="w-3 h-3 text-yellow-500" />
|
||||
<span>{{ t("transfer.cancelled") }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<span v-else class="text-muted-foreground/40">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status message -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
@@ -656,6 +665,6 @@ const overallRowsLabel = computed(() =>
|
||||
</Button>
|
||||
</template>
|
||||
</DialogFooter>
|
||||
</DialogScrollContent>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user