KI-AGENT: Mandantenbezogenen IMAP-Dokumentenimport umsetzen
This commit is contained in:
219
frontend/pages/settings/document-imports/index.vue
Normal file
219
frontend/pages/settings/document-imports/index.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<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 {
|
||||
sources.value = await api("/api/document-imports")
|
||||
} 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 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>
|
||||
Reference in New Issue
Block a user