feat: add debug logging for SMTP configuration and detailed mail delivery status
Build and Push Multi-Platform Images / build-and-push (push) Failing after 34s
Build and Push Multi-Platform Images / build-and-push (push) Failing after 34s
This commit is contained in:
@@ -13,6 +13,7 @@ import ManuellBearbeitenPage from './pages/ManuellBearbeitenPage';
|
||||
import MailpostfachPage from './pages/MailpostfachPage';
|
||||
import MailDetailPage from './pages/MailDetailPage';
|
||||
import SettingsPage from './pages/SettingsPage';
|
||||
import UserSettingsPage from './pages/UserSettingsPage';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import DashboardPage from './pages/DashboardPage';
|
||||
import { Spin, Result, Button } from 'antd';
|
||||
@@ -127,6 +128,7 @@ function ThemedApp() {
|
||||
<Route path="/mailpostfach" element={<PermissionRoute permission={Permission.VIEW_MAIL}><MailpostfachPage /></PermissionRoute>} />
|
||||
<Route path="/mailpostfach/:id" element={<PermissionRoute permission={Permission.VIEW_MAIL}><MailDetailPage /></PermissionRoute>} />
|
||||
<Route path="/settings" element={<PermissionRoute permission={Permission.MANAGE_SETTINGS}><SettingsPage /></PermissionRoute>} />
|
||||
<Route path="/user-settings" element={<UserSettingsPage />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import api from './client';
|
||||
|
||||
export interface UserSettingsData {
|
||||
smtpHost: string | null;
|
||||
smtpPort: number | null;
|
||||
smtpSecure: boolean;
|
||||
smtpUser: string | null;
|
||||
smtpPassSet: boolean;
|
||||
smtpFrom: string | null;
|
||||
mailSignatureHtml: string | null;
|
||||
}
|
||||
|
||||
export const userSettingsApi = {
|
||||
get: () => api.get<UserSettingsData>('/api/user-settings').then((r) => r.data),
|
||||
|
||||
update: (data: Partial<UserSettingsData> & { smtpPass?: string }) =>
|
||||
api.put<UserSettingsData>('/api/user-settings', data).then((r) => r.data),
|
||||
|
||||
testSmtp: (cfg: { host: string; port: number; secure: boolean; user: string; pass: string }) =>
|
||||
api
|
||||
.post<{ ok: boolean; error?: string }>('/api/user-settings/test-smtp', cfg)
|
||||
.then((r) => r.data),
|
||||
};
|
||||
@@ -204,6 +204,12 @@ export default function AppLayout() {
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'user-settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: 'Benutzereinstellungen',
|
||||
onClick: () => navigate('/user-settings'),
|
||||
},
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
|
||||
@@ -26,6 +26,7 @@ import StarterKit from '@tiptap/starter-kit';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import { inboxApi, type InboxBarcode, type InboxFile, type PostprocessActionResult } from '../api/inbox';
|
||||
import { paperlessApi } from '../api/paperless';
|
||||
import { userSettingsApi } from '../api/userSettings';
|
||||
|
||||
const ZOOM_MIN = 0.5;
|
||||
const ZOOM_MAX = 3;
|
||||
@@ -678,13 +679,17 @@ function SendEmailDialog({ open, fileId, fileName, documents, onClose }: SendEma
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
form.resetFields();
|
||||
editor?.commands.clearContent();
|
||||
const base = fileName.replace(/\.pdf$/i, '');
|
||||
setFilenames(
|
||||
documents.map((doc) =>
|
||||
doc.belegname || (documents.length === 1 ? base : `${base}_${doc.index + 1}`),
|
||||
),
|
||||
);
|
||||
userSettingsApi.get().then((settings) => {
|
||||
editor?.commands.setContent(settings.mailSignatureHtml ?? '');
|
||||
}).catch(() => {
|
||||
editor?.commands.clearContent();
|
||||
});
|
||||
}, [open, documents, fileName, form, editor]);
|
||||
|
||||
const handleOk = async () => {
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Card, Form, Input, InputNumber, Space, Switch, Tabs, Typography, message } from 'antd';
|
||||
import { CheckCircleOutlined, CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import { userSettingsApi, type UserSettingsData } from '../api/userSettings';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function TiptapToolbar({ editor }: { editor: ReturnType<typeof useEditor> }) {
|
||||
if (!editor) return null;
|
||||
const btn = (active: boolean): React.CSSProperties => ({
|
||||
padding: '2px 8px',
|
||||
border: '1px solid #d9d9d9',
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
background: active ? '#e6f4ff' : '#fff',
|
||||
fontWeight: active ? 600 : 400,
|
||||
});
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 4, padding: '4px 0', borderBottom: '1px solid #f0f0f0', marginBottom: 6, flexWrap: 'wrap' }}>
|
||||
<button style={btn(editor.isActive('bold'))} onMouseDown={(e) => { e.preventDefault(); editor.chain().focus().toggleBold().run(); }}>F</button>
|
||||
<button style={{ ...btn(editor.isActive('italic')), fontStyle: 'italic' }} onMouseDown={(e) => { e.preventDefault(); editor.chain().focus().toggleItalic().run(); }}>K</button>
|
||||
<button style={{ ...btn(editor.isActive('underline')), textDecoration: 'underline' }} onMouseDown={(e) => { e.preventDefault(); editor.chain().focus().toggleUnderline().run(); }}>U</button>
|
||||
<button style={btn(editor.isActive('bulletList'))} onMouseDown={(e) => { e.preventDefault(); editor.chain().focus().toggleBulletList().run(); }}>• Liste</button>
|
||||
<button style={btn(editor.isActive('orderedList'))} onMouseDown={(e) => { e.preventDefault(); editor.chain().focus().toggleOrderedList().run(); }}>1. Liste</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MailSettingsTab() {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState<{ ok: boolean; error?: string } | null>(null);
|
||||
const [passSet, setPassSet] = useState(false);
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [StarterKit, Underline],
|
||||
content: '',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
userSettingsApi.get().then((data) => {
|
||||
form.setFieldsValue({
|
||||
smtpHost: data.smtpHost ?? '',
|
||||
smtpPort: data.smtpPort ?? 587,
|
||||
smtpSecure: data.smtpSecure,
|
||||
smtpUser: data.smtpUser ?? '',
|
||||
smtpFrom: data.smtpFrom ?? '',
|
||||
});
|
||||
setPassSet(data.smtpPassSet);
|
||||
if (data.mailSignatureHtml) {
|
||||
editor?.commands.setContent(data.mailSignatureHtml);
|
||||
}
|
||||
}).catch(() => {
|
||||
message.error('Einstellungen konnten nicht geladen werden');
|
||||
}).finally(() => setLoading(false));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleSave = async () => {
|
||||
const values = await form.validateFields();
|
||||
setSaving(true);
|
||||
try {
|
||||
const updated = await userSettingsApi.update({
|
||||
smtpHost: values.smtpHost || null,
|
||||
smtpPort: values.smtpPort || null,
|
||||
smtpSecure: values.smtpSecure ?? false,
|
||||
smtpUser: values.smtpUser || null,
|
||||
smtpPass: values.smtpPass || undefined,
|
||||
smtpFrom: values.smtpFrom || null,
|
||||
mailSignatureHtml: editor?.getHTML() ?? null,
|
||||
});
|
||||
setPassSet(updated.smtpPassSet);
|
||||
form.setFieldValue('smtpPass', '');
|
||||
message.success('Einstellungen gespeichert');
|
||||
} catch {
|
||||
message.error('Speichern fehlgeschlagen');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTest = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
if (!values.smtpHost) { message.warning('Bitte SMTP-Server angeben'); return; }
|
||||
setTesting(true);
|
||||
setTestResult(null);
|
||||
try {
|
||||
const result = await userSettingsApi.testSmtp({
|
||||
host: values.smtpHost,
|
||||
port: values.smtpPort ?? 587,
|
||||
secure: values.smtpSecure ?? false,
|
||||
user: values.smtpUser ?? '',
|
||||
pass: values.smtpPass || '',
|
||||
});
|
||||
setTestResult(result);
|
||||
} catch {
|
||||
setTestResult({ ok: false, error: 'Verbindung fehlgeschlagen' });
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
return (
|
||||
<Form form={form} layout="vertical" style={{ maxWidth: 600 }}>
|
||||
<Form.Item name="smtpHost" label="SMTP Server">
|
||||
<Input placeholder="smtp.beispiel.de" />
|
||||
</Form.Item>
|
||||
<Space style={{ width: '100%' }} align="start">
|
||||
<Form.Item name="smtpPort" label="Port" style={{ width: 120 }}>
|
||||
<InputNumber min={1} max={65535} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="smtpSecure" label="TLS/SSL" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Form.Item name="smtpUser" label="Benutzername">
|
||||
<Input autoComplete="off" />
|
||||
</Form.Item>
|
||||
<Form.Item name="smtpPass" label="Passwort">
|
||||
<Input.Password placeholder={passSet ? '(bereits gesetzt — leer lassen zum Beibehalten)' : ''} autoComplete="new-password" />
|
||||
</Form.Item>
|
||||
<Form.Item name="smtpFrom" label="Absender-Adresse (From)">
|
||||
<Input placeholder="mein.name@beispiel.de" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Signatur">
|
||||
<div style={{ border: '1px solid #d9d9d9', borderRadius: 6, padding: '6px 10px', minHeight: 160 }}>
|
||||
<TiptapToolbar editor={editor} />
|
||||
<EditorContent editor={editor} style={{ minHeight: 120, outline: 'none' }} />
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" loading={saving} onClick={handleSave}>
|
||||
Speichern
|
||||
</Button>
|
||||
<Button
|
||||
loading={testing}
|
||||
icon={testing ? <LoadingOutlined /> : undefined}
|
||||
onClick={handleTest}
|
||||
>
|
||||
Verbindung testen
|
||||
</Button>
|
||||
{testResult && (
|
||||
<Space>
|
||||
{testResult.ok
|
||||
? <CheckCircleOutlined style={{ color: '#52c41a' }} />
|
||||
: <CloseCircleOutlined style={{ color: '#ff4d4f' }} />}
|
||||
<Typography.Text type={testResult.ok ? 'success' : 'danger'}>
|
||||
{testResult.ok ? 'Verbindung erfolgreich' : (testResult.error ?? 'Fehler')}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
)}
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UserSettingsPage() {
|
||||
return (
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 24 }}>Benutzereinstellungen</Title>
|
||||
<Card>
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
key: 'mail',
|
||||
label: 'Maileinstellungen',
|
||||
children: <MailSettingsTab />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user