KI-AGENT: Statusanzeige für Tenant-Import ergänzen
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 45s
Build and Push Docker Images / build-frontend (push) Successful in 1m21s
Build and Push Docker Images / build-central-services-api (push) Successful in 22s
Build and Push Docker Images / build-website (push) Successful in 23s
Build and Push Docker Images / build-central-services-admin (push) Successful in 22s
Build and Push Docker Images / build-docs (push) Successful in 21s

This commit is contained in:
2026-08-27 08:50:17 +02:00
parent afb9adb444
commit 6b5d4f7f36
6 changed files with 234 additions and 19 deletions

View File

@@ -63,6 +63,24 @@ export type TenantImportResult = {
error?: string | null
}
export type TenantImportJob = {
importId: string
tenantId?: number | null
operation?: "import" | string
status: "queued" | "running" | "completed" | "failed" | string
statusMessage?: string | null
filename: string
fileSize?: number | null
filesDone?: number
filesTotal?: number
error?: string | null
result?: TenantImportResult | null
statusUrl?: string
createdAt?: string
updatedAt?: string | null
completedAt?: string | null
}
export type TenantExportJob = {
exportId: string
importId?: string
@@ -215,13 +233,17 @@ export const useAdmin = () => {
})
}
const importTenant = async (body: Record<string, any> | FormData): Promise<TenantImportResult> => {
const importTenant = async (body: Record<string, any> | FormData): Promise<TenantImportJob> => {
return await $api("/api/admin/tenant-imports", {
method: "POST",
body,
})
}
const getTenantImport = async (importId: string): Promise<TenantImportJob> => {
return await $api(`/api/admin/tenant-imports/${importId}`)
}
const getSystemStatus = async (): Promise<SystemStatus> => {
return await $api("/api/admin/system-status")
}
@@ -262,5 +284,6 @@ export const useAdmin = () => {
getTenantExport,
downloadTenantExport,
importTenant,
getTenantImport,
}
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { AdminTenant } from "~/composables/useAdmin"
import type { AdminTenant, TenantImportJob } from "~/composables/useAdmin"
const auth = useAuthStore()
const toast = useToast()
@@ -9,6 +9,7 @@ const admin = useAdmin()
const loading = ref(true)
const creatingTenant = ref(false)
const importingTenant = ref(false)
const tenantImportProgress = ref<TenantImportJob | null>(null)
const createTenantModalOpen = ref(false)
const importFileInput = ref<HTMLInputElement | null>(null)
const tenants = ref<AdminTenant[]>([])
@@ -101,19 +102,42 @@ const importTenantExport = async (event: Event) => {
if (!file || importingTenant.value) return
importingTenant.value = true
tenantImportProgress.value = {
importId: "",
status: "uploading",
statusMessage: "Exportdatei wird hochgeladen",
filename: file.name,
filesDone: 0,
filesTotal: 0,
}
try {
const isZipExport = file.name.endsWith(".zip") || file.name.endsWith(".fedeo-export.zip") || file.type === "application/zip"
let result
let job
if (isZipExport) {
const formData = new FormData()
formData.append("file", file)
result = await admin.importTenant(formData)
job = await admin.importTenant(formData)
} else {
result = await admin.importTenant(JSON.parse(await file.text()))
job = await admin.importTenant(JSON.parse(await file.text()))
}
tenantImportProgress.value = job
while (!["completed", "failed"].includes(job.status)) {
await new Promise((resolve) => setTimeout(resolve, 1500))
job = await admin.getTenantImport(job.importId)
tenantImportProgress.value = job
}
if (job.status === "failed") {
throw new Error(job.error || "Mandantenimport fehlgeschlagen")
}
const result = job.result
if (!result) throw new Error("Importergebnis fehlt")
const rowCount = (result.tables || []).reduce((sum, table) => sum + table.rows, 0)
await fetchTenants()
@@ -194,6 +218,36 @@ onMounted(async () => {
</template>
</UTable>
<UCard v-if="tenantImportProgress" class="mt-4">
<div class="space-y-2">
<div class="flex items-center justify-between gap-4">
<div>
<div class="font-medium">Mandantenimport</div>
<p class="text-sm text-gray-500">
{{ tenantImportProgress.statusMessage || 'Import wird verarbeitet' }}
</p>
</div>
<UBadge
:color="tenantImportProgress.status === 'failed' ? 'error' : tenantImportProgress.status === 'completed' ? 'success' : 'warning'"
variant="soft"
>
{{ tenantImportProgress.status === 'uploading' ? 'Upload' : tenantImportProgress.status === 'queued' ? 'Wartet' : tenantImportProgress.status === 'running' ? 'Läuft' : tenantImportProgress.status === 'completed' ? 'Abgeschlossen' : 'Fehlgeschlagen' }}
</UBadge>
</div>
<UProgress
:model-value="tenantImportProgress.filesTotal ? Math.round(((tenantImportProgress.filesDone || 0) / tenantImportProgress.filesTotal) * 100) : undefined"
:color="tenantImportProgress.status === 'failed' ? 'error' : tenantImportProgress.status === 'completed' ? 'success' : 'warning'"
:animation="tenantImportProgress.filesTotal ? undefined : 'carousel'"
/>
<p v-if="tenantImportProgress.filesTotal" class="text-xs text-gray-500">
{{ tenantImportProgress.filesDone || 0 }} / {{ tenantImportProgress.filesTotal }} Schritte
</p>
<p v-if="tenantImportProgress.error" class="text-sm text-red-600">
{{ tenantImportProgress.error }}
</p>
</div>
</UCard>
<UModal v-model:open="createTenantModalOpen">
<template #content>
<UCard>