From 1c37936d8c3e86be6480454863f271c28e5f2d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20P=C3=B6ttker?= Date: Mon, 29 Jun 2026 16:01:27 +0200 Subject: [PATCH] feat(frontend): Webhook-Status-Tab in den Einstellungen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neuer Tab "Webhook" in der SettingsPage zeigt den letzten Paperless- Webhook-Aufruf (Zeitpunkt, Dokument-ID, Aktion, Ergebnis) über GET /api/webhook/status an. Neue API-Datei src/api/webhook.ts. Co-Authored-By: Claude Opus 4.8 --- paperless-frontend/src/api/webhook.ts | 19 ++++ paperless-frontend/src/pages/SettingsPage.tsx | 95 ++++++++++++++++++- 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 paperless-frontend/src/api/webhook.ts diff --git a/paperless-frontend/src/api/webhook.ts b/paperless-frontend/src/api/webhook.ts new file mode 100644 index 0000000..d3d971f --- /dev/null +++ b/paperless-frontend/src/api/webhook.ts @@ -0,0 +1,19 @@ +import api from './client'; + +export interface WebhookLastCall { + at: string; // ISO-Zeitstempel + documentId: number | null; + action: string | null; + status: 'processed' | 'skipped' | 'error' | 'bad-request' | string; + reason?: string; + message?: string; +} + +export interface WebhookStatus { + lastCall: WebhookLastCall | null; +} + +export const webhookApi = { + getStatus: () => + api.get('/api/webhook/status').then((r) => r.data), +}; diff --git a/paperless-frontend/src/pages/SettingsPage.tsx b/paperless-frontend/src/pages/SettingsPage.tsx index 9e67688..d26ab52 100644 --- a/paperless-frontend/src/pages/SettingsPage.tsx +++ b/paperless-frontend/src/pages/SettingsPage.tsx @@ -2,14 +2,14 @@ import { useEffect, useState, useCallback } from 'react'; import dayjs from 'dayjs'; import { Tabs, Typography, Table, Button, Modal, Form, Input, Select, - Switch, Checkbox, Popconfirm, message, Card, Tag, Space, Divider, InputNumber, Badge, Row, Col, Radio, Alert, + Switch, Checkbox, Popconfirm, message, Card, Tag, Space, Divider, InputNumber, Badge, Row, Col, Radio, Alert, Descriptions, } from 'antd'; import { UserOutlined, FileTextOutlined, ThunderboltOutlined, PlusOutlined, DeleteOutlined, EditOutlined, CloudUploadOutlined, HistoryOutlined, MinusCircleOutlined, CopyOutlined, KeyOutlined, QrcodeOutlined, UnorderedListOutlined, PrinterOutlined, GlobalOutlined, - TagsOutlined, + TagsOutlined, ApiOutlined, ReloadOutlined, } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import type { FormInstance } from 'antd'; @@ -26,6 +26,7 @@ import { } from '../api/settings'; import { clientsApi, type Client } from '../api/inbox'; import { apiKeysApi, type ApiKey } from '../api/api-keys'; +import { webhookApi, type WebhookStatus } from '../api/webhook'; import { barcodeTemplatesApi, type BarcodeTemplate, @@ -2748,6 +2749,91 @@ function SteuertagsTab() { // Settings Page // ═══════════════════════════════════════════════════════════════════ +// ═══════════════════════════════════════════════════════════════════ +// Webhook-Status Tab +// ═══════════════════════════════════════════════════════════════════ + +function WebhookStatusTab() { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(true); + + const load = useCallback(async () => { + setLoading(true); + try { + const data = await webhookApi.getStatus(); + setStatus(data); + } catch (err) { + console.error('Error loading webhook status:', err); + message.error('Webhook-Status konnte nicht geladen werden'); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { load(); }, [load]); + + const renderStatusBadge = (s: string) => { + switch (s) { + case 'processed': return ; + case 'skipped': return ; + case 'error': return ; + case 'bad-request': return ; + default: return ; + } + }; + + const lastCall = status?.lastCall ?? null; + + return ( + <> +
+ Webhook-Status + + Paperless-NGX ruft nach dem Bearbeiten eines Dokuments den Webhook{' '} + /api/webhook/paperless auf + (per API-Key authentifiziert). Hier siehst du den zuletzt + verarbeiteten Aufruf. Eine vollständige Historie steht in den + Backend-Logs. + + +
+ + + {lastCall ? ( + + + {dayjs(lastCall.at).format('DD.MM.YYYY HH:mm:ss')} + + + {lastCall.documentId ?? '—'} + + + {lastCall.action ? {lastCall.action} : '—'} + + + {renderStatusBadge(lastCall.status)} + {lastCall.reason ? ( + ({lastCall.reason}) + ) : null} + + {lastCall.message ? ( + + {lastCall.message} + + ) : null} + + ) : ( + + Noch kein Webhook-Aufruf erfolgt. + + )} + + + ); +} + export default function SettingsPage() { return (
@@ -2805,6 +2891,11 @@ export default function SettingsPage() { label: API-Keys, children: , }, + { + key: 'webhook', + label: Webhook, + children: , + }, { key: 'agrarmonitor', label: Agrarmonitor,