Restrict tenant imports to new tenants

This commit is contained in:
2026-08-06 15:19:11 +02:00
parent c66d04f0b5
commit cb37970074
12 changed files with 38 additions and 1867 deletions

View File

@@ -11,30 +11,15 @@ const tenantId = Number(route.params.id)
const loading = ref(true)
const saving = ref(false)
const exportingTenant = ref(false)
const importingTenant = ref(false)
const tenantExportProgress = ref<null | {
status: string
filesDone: number
filesTotal: number
filename: string
}>(null)
const tenantImportProgress = ref<null | {
status: string
filesDone: number
filesTotal: number
filename: string
}>(null)
const creatingUser = ref(false)
const createUserModalOpen = ref(false)
const createdUserPassword = ref("")
const importFileInput = ref<HTMLInputElement | null>(null)
const lastImportResult = ref<null | {
tenantId: number
tableCount: number
rowCount: number
restoredFiles: number
skippedFiles: number
}>(null)
const lockedOptions = [
{ label: "Aktiv", value: null },
{ label: "Tenant-Wartung", value: "maintenance_tenant" },
@@ -165,111 +150,6 @@ const downloadTenantExport = async () => {
}
}
const openImportFileDialog = () => {
importFileInput.value?.click()
}
const importTenantExport = async (event: Event) => {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (!file || importingTenant.value) return
importingTenant.value = true
tenantImportProgress.value = null
lastImportResult.value = null
try {
const targetTenantId = tenantForm.value?.id || tenantId
const isZipExport = file.name.endsWith(".zip") || file.name.endsWith(".fedeo-export.zip") || file.type === "application/zip"
let result
if (isZipExport) {
const formData = new FormData()
formData.append("file", file)
formData.append("targetTenantId", String(targetTenantId))
result = await admin.importTenant(formData)
} else {
result = await admin.importTenant({
exportData: JSON.parse(await file.text()),
targetTenantId,
})
}
const importJobId = result.importId || result.exportId
if (importJobId) {
let job = result
tenantImportProgress.value = {
status: job.status,
filesDone: job.filesDone || 0,
filesTotal: job.filesTotal || 0,
filename: job.filename || file.name,
}
while (!["ready", "review", "failed"].includes(job.status)) {
await new Promise((resolve) => setTimeout(resolve, 1500))
job = await admin.getTenantExport(importJobId)
tenantImportProgress.value = {
status: job.status,
filesDone: job.filesDone || 0,
filesTotal: job.filesTotal || 0,
filename: job.filename || file.name,
}
}
if (job.status === "failed") {
throw new Error(job.error || "Import konnte nicht abgeschlossen werden.")
}
if (job.status === "review") {
await router.push(`/administration/tenant-imports/${importJobId}`)
return
}
await fetchTenant()
await auth.fetchMe()
await auth.switchTenant(String(job.tenantId || targetTenantId))
toast.add({
title: "Mandantenimport abgeschlossen",
description: job.filename || file.name,
color: "green",
})
return
}
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
lastImportResult.value = {
tenantId: result.tenantId,
tableCount: result.tables?.length || 0,
rowCount,
restoredFiles: result.files?.restored || 0,
skippedFiles: result.files?.skipped || 0,
}
await fetchTenant()
await auth.fetchMe()
await auth.switchTenant(String(result.tenantId))
toast.add({
title: "Mandantenimport abgeschlossen",
description: `${rowCount} Datensätze und ${lastImportResult.value.restoredFiles} Dateien verarbeitet.`,
color: "green",
})
} catch (err: any) {
console.error("[administration/tenants/import]", err)
toast.add({
title: "Mandant konnte nicht importiert werden",
description: err?.data?.error || err?.message || "Unbekannter Fehler",
color: "red",
})
} finally {
importingTenant.value = false
input.value = ""
}
}
const createTenantUser = async () => {
if (!tenantForm.value || creatingUser.value) return
@@ -398,7 +278,7 @@ onMounted(async () => {
<UCard v-if="!loading && tenantForm" class="mt-3">
<USeparator label="Backup und Umzug" />
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="mt-4 max-w-2xl">
<div class="border border-gray-200 dark:border-gray-800 rounded-lg p-4">
<div class="font-medium">Full Export</div>
<p class="text-sm text-gray-500 mt-1">
@@ -424,50 +304,7 @@ onMounted(async () => {
Export herunterladen
</UButton>
</div>
<div class="border border-gray-200 dark:border-gray-800 rounded-lg p-4">
<div class="font-medium">Import</div>
<p class="text-sm text-gray-500 mt-1">
Spielt einen FEDEO-Mandantenexport auf diesem Server ein. Bestehende Datensätze mit gleicher ID werden übersprungen.
</p>
<input
ref="importFileInput"
type="file"
accept="application/json,application/zip,.json,.zip,.fedeo-export.zip"
class="hidden"
@change="importTenantExport"
>
<UButton
class="mt-4"
icon="i-heroicons-arrow-up-tray"
color="warning"
:loading="importingTenant"
@click="openImportFileDialog"
>
Export importieren
</UButton>
<div v-if="tenantImportProgress" class="mt-4 space-y-2">
<UProgress
:model-value="tenantImportProgress.filesTotal ? Math.round((tenantImportProgress.filesDone / tenantImportProgress.filesTotal) * 100) : undefined"
/>
<p class="text-xs text-gray-500">
{{ tenantImportProgress.status === 'ready' ? 'Import abgeschlossen' : 'Import wird verarbeitet' }}
<span v-if="tenantImportProgress.filesTotal">
· {{ tenantImportProgress.filesDone }} / {{ tenantImportProgress.filesTotal }} Schritte
</span>
</p>
</div>
</div>
</div>
<UAlert
v-if="lastImportResult"
class="mt-4"
title="Letzter Import"
:description="`Tenant ${lastImportResult.tenantId}: ${lastImportResult.rowCount} Datensätze aus ${lastImportResult.tableCount} Tabellen verarbeitet, ${lastImportResult.restoredFiles} Dateien wiederhergestellt, ${lastImportResult.skippedFiles} Dateien übersprungen.`"
color="green"
variant="soft"
/>
</UCard>
<UCard v-if="!loading && tenantForm" class="mt-3">