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
228 lines
8.2 KiB
Vue
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>
|