fix(data-transfer): handle missing owner role policy and align web sync behavior
* Feat: Add PostgreSQL transfer ownership preview and resolution - Add ownership policy support for data transfers: preserve, skip, and reassign missing owners - Preview missing PostgreSQL roles before creating target schema objects - Show ownership confirmation dialog when target roles are missing - Allow users to skip ownership changes or reassign missing owners to the target connection user - Add Tauri and HTTP API support for ownership preview - Apply ownership statements according to selected policy during PostgreSQL transfers - Add Chinese localization for ownership confirmation UI * Feat: Add ownership confirmation localization - Add ownership confirmation messages for supported desktop locales - Include skip and confirm labels for ownership handling - Update transfer test requests to use Preserve ownership policy
This commit is contained in:
18 files changed
+508
-33
No files matched your search
@@ -55,6 +55,11 @@ const transferMode = ref<TransferMode>("append");
|
||||
const targetTableNameCase = ref<TransferTableNameCase>("preserve");
|
||||
const batchSize = ref(1000);
|
||||
const isSubmitting = ref(false);
|
||||
const ownershipDialogOpen = ref(false);
|
||||
const ownershipMissingOwners = ref<string[]>([]);
|
||||
const ownershipTargetOwner = ref("");
|
||||
const pendingOwnershipRequest = ref<api.TransferRequest | null>(null);
|
||||
const pendingOwnershipRefresh = ref<{ targetConnection: string; targetDatabase: string; targetSchema: string; shouldRefreshTargetTree: boolean } | null>(null);
|
||||
|
||||
const filteredTables = computed(() => {
|
||||
const q = tableSearch.value.toLowerCase();
|
||||
@@ -247,6 +252,11 @@ function resetState() {
|
||||
targetTableNameCase.value = "preserve";
|
||||
batchSize.value = 1000;
|
||||
isSubmitting.value = false;
|
||||
ownershipDialogOpen.value = false;
|
||||
ownershipMissingOwners.value = [];
|
||||
ownershipTargetOwner.value = "";
|
||||
pendingOwnershipRequest.value = null;
|
||||
pendingOwnershipRefresh.value = null;
|
||||
}
|
||||
|
||||
async function startTransfer() {
|
||||
@@ -272,10 +282,39 @@ async function startTransfer() {
|
||||
createTable: createTable.value,
|
||||
mode: transferMode.value,
|
||||
targetTableNameCase: targetTableNameCase.value,
|
||||
ownershipPolicy: "preserve",
|
||||
batchSize: batchSize.value,
|
||||
};
|
||||
|
||||
startDataTransferTask(request, `${sourceDatabaseName} → ${targetDatabaseName}`, {
|
||||
if (createTable.value) {
|
||||
try {
|
||||
const preview = await api.previewTransferOwnership(request);
|
||||
if (preview.missingOwners.length > 0) {
|
||||
ownershipMissingOwners.value = preview.missingOwners;
|
||||
ownershipTargetOwner.value = preview.targetOwner;
|
||||
pendingOwnershipRequest.value = request;
|
||||
pendingOwnershipRefresh.value = {
|
||||
targetConnection,
|
||||
targetDatabase: targetDatabaseName,
|
||||
targetSchema: effectiveTargetSchema,
|
||||
shouldRefreshTargetTree,
|
||||
};
|
||||
ownershipDialogOpen.value = true;
|
||||
isSubmitting.value = false;
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
isSubmitting.value = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
runTransfer(request, targetConnection, targetDatabaseName, effectiveTargetSchema, shouldRefreshTargetTree);
|
||||
}
|
||||
|
||||
function runTransfer(request: api.TransferRequest, targetConnection: string, targetDatabaseName: string, effectiveTargetSchema: string, shouldRefreshTargetTree: boolean) {
|
||||
isSubmitting.value = true;
|
||||
startDataTransferTask(request, `${request.sourceDatabase} → ${targetDatabaseName}`, {
|
||||
formatOverlapError: (tables) => t("transfer.targetTableBusy", { tables: tables.join(", ") }),
|
||||
onDone: async () => {
|
||||
if (shouldRefreshTargetTree) {
|
||||
@@ -287,6 +326,21 @@ async function startTransfer() {
|
||||
resetState();
|
||||
}
|
||||
|
||||
function resolveOwnershipDecision(policy: api.TransferOwnershipPolicy | null) {
|
||||
const request = pendingOwnershipRequest.value;
|
||||
const refresh = pendingOwnershipRefresh.value;
|
||||
pendingOwnershipRequest.value = null;
|
||||
pendingOwnershipRefresh.value = null;
|
||||
ownershipDialogOpen.value = false;
|
||||
ownershipMissingOwners.value = [];
|
||||
ownershipTargetOwner.value = "";
|
||||
if (!policy || !request || !refresh) {
|
||||
isSubmitting.value = false;
|
||||
return;
|
||||
}
|
||||
runTransfer({ ...request, ownershipPolicy: policy }, refresh.targetConnection, refresh.targetDatabase, refresh.targetSchema, refresh.shouldRefreshTargetTree);
|
||||
}
|
||||
|
||||
function getConnectionName(id: string) {
|
||||
return store.connections.find((c) => c.id === id)?.name ?? id;
|
||||
}
|
||||
@@ -514,4 +568,34 @@ function getConnectionName(id: string) {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="ownershipDialogOpen">
|
||||
<DialogContent class="sm:max-w-[520px]" @interact-outside.prevent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("transfer.ownershipTitle") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="space-y-3 text-sm">
|
||||
<p class="text-muted-foreground">
|
||||
{{ t("transfer.ownershipMessage", { owners: ownershipMissingOwners.join(", ") }) }}
|
||||
</p>
|
||||
<div class="rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
|
||||
{{ t("transfer.ownershipSkipDetails") }}
|
||||
</div>
|
||||
<div class="rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
|
||||
{{ t("transfer.ownershipTargetOwner", { owner: ownershipTargetOwner }) }}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter class="gap-2">
|
||||
<Button variant="outline" size="sm" @click="resolveOwnershipDecision(null)">
|
||||
{{ t("transfer.cancel") }}
|
||||
</Button>
|
||||
<Button variant="secondary" size="sm" @click="resolveOwnershipDecision('skip')">
|
||||
{{ t("transfer.ownershipSkip") }}
|
||||
</Button>
|
||||
<Button size="sm" @click="resolveOwnershipDecision('reassignMissing')">
|
||||
{{ t("transfer.ownershipConfirm") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in new issue
Block a user