Files
FEDEO/frontend/pages/settings/document-imports/index.vue
flfeders 09431a87c7
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 38s
Build and Push Docker Images / build-frontend (push) Successful in 1m58s
Build and Push Docker Images / build-central-services-api (push) Successful in 35s
Build and Push Docker Images / build-docs (push) Successful in 34s
Build and Push Docker Images / build-website (push) Successful in 37s
Build and Push Docker Images / build-central-services-admin (push) Successful in 35s
Fix document import account form submission
2026-08-09 13:48:45 +02:00

228 lines
8.2 KiB
Vue

<script setup lang="ts">
type ImportSource = {
id: string
name: string
provider: string
enabled: boolean
mailboxAddress: string
imapHost: string
imapPort: number
imapSecure: boolean
mailboxPath: string
markAsSeen: boolean
hasPassword: boolean
lastSyncedAt?: string | null
lastError?: string | null
}
const api = useNuxtApp().$api
const toast = useToast()
const sources = ref<ImportSource[]>([])
const loading = ref(true)
const saving = ref(false)
const activeAction = ref<string | null>(null)
const editingId = ref<string | null>(null)
const showForm = ref(false)
const emptyForm = () => ({
name: "",
provider: "imap",
enabled: true,
mailboxAddress: "",
password: "",
imapHost: "",
imapPort: 993,
imapSecure: true,
mailboxPath: "INBOX",
markAsSeen: true,
})
const form = ref(emptyForm())
const load = async () => {
loading.value = true
try {
const result = await api<ImportSource[]>("/api/document-imports")
sources.value = Array.isArray(result) ? result : []
} catch (error: any) {
sources.value = []
toast.add({
title: "Importquellen konnten nicht geladen werden",
description: error?.data?.error || error?.message || "Bitte versuche es später erneut.",
color: "error",
})
} finally {
loading.value = false
}
}
const create = () => {
editingId.value = null
form.value = emptyForm()
showForm.value = true
}
const edit = (source: ImportSource) => {
editingId.value = source.id
form.value = {
name: source.name,
provider: source.provider,
enabled: source.enabled,
mailboxAddress: source.mailboxAddress,
password: "",
imapHost: source.imapHost,
imapPort: source.imapPort,
imapSecure: source.imapSecure,
mailboxPath: source.mailboxPath,
markAsSeen: source.markAsSeen,
}
showForm.value = true
}
const save = async () => {
saving.value = true
try {
const result = await api(`/api/document-imports${editingId.value ? `/${editingId.value}` : ""}`, {
method: "POST",
body: { ...form.value, imapPort: Number(form.value.imapPort), password: form.value.password || undefined },
})
toast.add({ title: "Importquelle gespeichert", color: "success" })
showForm.value = false
await load()
if (!editingId.value && result.id) await runAction(result.id, "test")
} catch (error: any) {
toast.add({ title: "Speichern fehlgeschlagen", description: error?.data?.error || error?.message, color: "error" })
} finally {
saving.value = false
}
}
const runAction = async (id: string, action: "test" | "sync") => {
activeAction.value = `${id}:${action}`
try {
const result = await api(`/api/document-imports/${id}/${action}`, { method: "POST" })
toast.add({
title: action === "test" ? "Verbindung erfolgreich" : "Import abgeschlossen",
description: action === "test"
? `${result.messages} Nachrichten im Postfach`
: `${result.imported} Anhänge importiert, ${result.duplicates} Dubletten übersprungen`,
color: "success",
})
await load()
} catch (error: any) {
toast.add({
title: action === "test" ? "Verbindung fehlgeschlagen" : "Import fehlgeschlagen",
description: error?.data?.error || error?.message,
color: "error",
})
await load()
} finally {
activeAction.value = null
}
}
onMounted(load)
</script>
<template>
<UDashboardNavbar title="Dokumentenimporte">
<template #right>
<UButton icon="i-heroicons-plus" @click="create">Importquelle</UButton>
</template>
</UDashboardNavbar>
<div class="space-y-5 p-4">
<UAlert
color="neutral"
variant="soft"
icon="i-heroicons-inbox-arrow-down"
title="Dokumente aus eigenen Postfächern importieren"
description="FEDEO importiert Dateianhänge aus ungelesenen Nachrichten. Nachrichten werden erst nach erfolgreicher Verarbeitung als gelesen markiert und nicht gelöscht."
/>
<div v-if="loading" class="space-y-3">
<USkeleton v-for="index in 3" :key="index" class="h-28" />
</div>
<TableEmptyState v-else-if="sources.length === 0" label="Noch keine Importquelle eingerichtet" />
<div v-else class="space-y-3">
<div v-for="source in sources" :key="source.id" class="rounded-lg border border-(--ui-border) p-4">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<p class="font-medium">{{ source.name }}</p>
<UBadge color="neutral" variant="soft">IMAP</UBadge>
<UBadge :color="source.enabled ? 'success' : 'neutral'" variant="soft">
{{ source.enabled ? "Aktiv" : "Deaktiviert" }}
</UBadge>
</div>
<p class="mt-1 truncate text-sm text-dimmed">
{{ source.mailboxAddress }} · {{ source.imapHost }}:{{ source.imapPort }} · {{ source.mailboxPath }}
</p>
<p v-if="source.lastSyncedAt" class="mt-1 text-xs text-dimmed">
Zuletzt synchronisiert: {{ new Date(source.lastSyncedAt).toLocaleString('de-DE') }}
</p>
<p v-if="source.lastError" class="mt-1 text-sm text-error">{{ source.lastError }}</p>
</div>
<div class="flex shrink-0 flex-wrap gap-2">
<UButton
color="neutral"
variant="soft"
icon="i-heroicons-signal"
:loading="activeAction === `${source.id}:test`"
@click="runAction(source.id, 'test')"
>Testen</UButton>
<UButton
icon="i-heroicons-arrow-path"
:loading="activeAction === `${source.id}:sync`"
:disabled="!source.enabled"
@click="runAction(source.id, 'sync')"
>Jetzt importieren</UButton>
<UButton color="neutral" variant="ghost" icon="i-heroicons-pencil-square" @click="edit(source)" />
</div>
</div>
</div>
</div>
<UModal v-model:open="showForm" :title="editingId ? 'Importquelle bearbeiten' : 'Importquelle einrichten'">
<template #body>
<UForm :state="form" class="space-y-5" @submit.prevent="save">
<div class="grid gap-4 md:grid-cols-2">
<UFormField label="Bezeichnung" required>
<UInput v-model="form.name" placeholder="Rechnungseingang" class="w-full" />
</UFormField>
<UFormField label="Postfachadresse" required>
<UInput v-model="form.mailboxAddress" type="email" placeholder="rechnungen@firma.de" class="w-full" />
</UFormField>
</div>
<div class="grid gap-4 md:grid-cols-[1fr_120px]">
<UFormField label="IMAP-Host" required>
<UInput v-model="form.imapHost" placeholder="imap.example.de" class="w-full" />
</UFormField>
<UFormField label="Port" required>
<UInput v-model="form.imapPort" type="number" class="w-full" />
</UFormField>
</div>
<div class="grid gap-4 md:grid-cols-2">
<UFormField :label="editingId ? 'Neues Passwort' : 'Passwort'" :required="!editingId">
<UInput v-model="form.password" type="password" :placeholder="editingId ? 'Unverändert lassen' : ''" class="w-full" />
</UFormField>
<UFormField label="Postfachordner">
<UInput v-model="form.mailboxPath" placeholder="INBOX" class="w-full" />
</UFormField>
</div>
<div class="grid gap-3 md:grid-cols-3">
<UCheckbox v-model="form.enabled" label="Import aktiv" />
<UCheckbox v-model="form.imapSecure" label="TLS/SSL verwenden" />
<UCheckbox v-model="form.markAsSeen" label="Erfolgreiche E-Mails als gelesen markieren" />
</div>
<div class="flex justify-end gap-2">
<UButton color="neutral" variant="ghost" @click="showForm = false">Abbrechen</UButton>
<UButton type="submit" icon="i-heroicons-check" :loading="saving" :disabled="!form.name || !form.mailboxAddress || !form.imapHost || (!editingId && !form.password)">
Speichern
</UButton>
</div>
</UForm>
</template>
</UModal>
</div>
</template>