feat(frontend): Webhook-Status-Tab in den Einstellungen
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<WebhookStatus>('/api/webhook/status').then((r) => r.data),
|
||||
};
|
||||
@@ -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<WebhookStatus | null>(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 <Badge status="success" text="Verarbeitet" />;
|
||||
case 'skipped': return <Badge status="warning" text="Übersprungen" />;
|
||||
case 'error': return <Badge status="error" text="Fehler" />;
|
||||
case 'bad-request': return <Badge status="error" text="Ungültige Anfrage" />;
|
||||
default: return <Badge status="default" text={s} />;
|
||||
}
|
||||
};
|
||||
|
||||
const lastCall = status?.lastCall ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Title level={4}>Webhook-Status</Title>
|
||||
<Typography.Paragraph type="secondary">
|
||||
Paperless-NGX ruft nach dem Bearbeiten eines Dokuments den Webhook{' '}
|
||||
<Typography.Text code>/api/webhook/paperless</Typography.Text> auf
|
||||
(per API-Key authentifiziert). Hier siehst du den zuletzt
|
||||
verarbeiteten Aufruf. Eine vollständige Historie steht in den
|
||||
Backend-Logs.
|
||||
</Typography.Paragraph>
|
||||
<Button icon={<ReloadOutlined />} loading={loading} onClick={load}>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card size="small" title="Letzter Webhook-Aufruf" loading={loading}>
|
||||
{lastCall ? (
|
||||
<Descriptions column={1} size="small" bordered>
|
||||
<Descriptions.Item label="Zeitpunkt">
|
||||
{dayjs(lastCall.at).format('DD.MM.YYYY HH:mm:ss')}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Dokument-ID">
|
||||
{lastCall.documentId ?? '—'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Aktion">
|
||||
{lastCall.action ? <Tag>{lastCall.action}</Tag> : '—'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Ergebnis">
|
||||
{renderStatusBadge(lastCall.status)}
|
||||
{lastCall.reason ? (
|
||||
<Typography.Text type="secondary"> ({lastCall.reason})</Typography.Text>
|
||||
) : null}
|
||||
</Descriptions.Item>
|
||||
{lastCall.message ? (
|
||||
<Descriptions.Item label="Meldung">
|
||||
<Typography.Text type="danger">{lastCall.message}</Typography.Text>
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
</Descriptions>
|
||||
) : (
|
||||
<Typography.Text type="secondary">
|
||||
Noch kein Webhook-Aufruf erfolgt.
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SettingsPage() {
|
||||
return (
|
||||
<div>
|
||||
@@ -2805,6 +2891,11 @@ export default function SettingsPage() {
|
||||
label: <span><KeyOutlined /> API-Keys</span>,
|
||||
children: <ApiKeysTab />,
|
||||
},
|
||||
{
|
||||
key: 'webhook',
|
||||
label: <span><ApiOutlined /> Webhook</span>,
|
||||
children: <WebhookStatusTab />,
|
||||
},
|
||||
{
|
||||
key: 'agrarmonitor',
|
||||
label: <span><GlobalOutlined /> Agrarmonitor</span>,
|
||||
|
||||
Reference in New Issue
Block a user