import { CopyOutlined, DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons'; import { Button, Card, Descriptions, Form, Input, InputNumber, Modal, Popconfirm, Space, Switch, Table, Tag, Typography } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { useState } from 'react'; import { useI18n } from '../frontend/i18n/react'; import type { RuntimeConfig, SmtpCredential, SmtpRelay, SmtpRelayPayload } from '../frontend/types'; interface SmtpCredentialsProps { config: RuntimeConfig | null; credential: SmtpCredential | null; credentials: SmtpCredential[]; relays: SmtpRelay[]; loading?: boolean; onCopy: (value: string) => void; onLoadCredential: (id: number) => Promise; onSaveCredential: (values: { username: string; password?: string }, id?: number) => Promise; onDeleteCredential: (credential: SmtpCredential) => Promise; onLoadRelay: (id: number) => Promise; onSaveRelay: (values: SmtpRelayPayload, id?: number) => Promise; onDeleteRelay: (relay: SmtpRelay) => Promise; } interface CredentialFormValues { username: string; password?: string; } export default function SmtpCredentials({ config, credential, credentials, relays, loading, onCopy, onLoadCredential, onSaveCredential, onDeleteCredential, onLoadRelay, onSaveRelay, onDeleteRelay }: SmtpCredentialsProps) { const { t } = useI18n(); const [credentialForm] = Form.useForm(); const [relayForm] = Form.useForm(); const [credentialOpen, setCredentialOpen] = useState(false); const [credentialLoading, setCredentialLoading] = useState(false); const [editingCredential, setEditingCredential] = useState(null); const [relayOpen, setRelayOpen] = useState(false); const [relayLoading, setRelayLoading] = useState(false); const [editingRelay, setEditingRelay] = useState(null); function generateCredentialPassword() { credentialForm.setFieldValue('password', randomPassword()); } function generateRelayPassword() { relayForm.setFieldValue('password', randomPassword()); } function openCreateCredential() { setEditingCredential(null); credentialForm.setFieldsValue({ username: credentials.length === 0 ? credential?.username || config?.submission?.username || '' : '', password: '' }); setCredentialOpen(true); } async function openEditCredential(item: SmtpCredential) { if (!item.id) return; setEditingCredential(item); setCredentialOpen(true); setCredentialLoading(true); try { const detail = await onLoadCredential(item.id); if (!detail) { setCredentialOpen(false); return; } credentialForm.setFieldsValue({ username: detail.username, password: detail.password || '' }); } finally { setCredentialLoading(false); } } async function saveCredential() { const values = await credentialForm.validateFields(); setCredentialLoading(true); try { const saved = await onSaveCredential(values, editingCredential?.id); if (!saved) return; setCredentialOpen(false); credentialForm.resetFields(); } finally { setCredentialLoading(false); } } function closeCredentialModal() { setCredentialOpen(false); setEditingCredential(null); credentialForm.resetFields(); } function openCreateRelay() { setEditingRelay(null); relayForm.setFieldsValue({ name: '', host: '', port: 587, secure: false, username: '', password: '', helo: '', isDefault: relays.length === 0 }); setRelayOpen(true); } async function openEditRelay(relay: SmtpRelay) { setEditingRelay(relay); setRelayOpen(true); setRelayLoading(true); try { const detail = await onLoadRelay(relay.id); if (!detail) { setRelayOpen(false); return; } relayForm.setFieldsValue({ name: detail.name, host: detail.host, port: detail.port, secure: detail.secure, username: detail.username, password: detail.password || '', helo: detail.helo, isDefault: detail.isDefault }); } finally { setRelayLoading(false); } } async function saveRelay() { const values = await relayForm.validateFields(); setRelayLoading(true); try { const saved = await onSaveRelay(values, editingRelay?.id); if (!saved) return; setRelayOpen(false); relayForm.resetFields(); } finally { setRelayLoading(false); } } const credentialColumns: ColumnsType = [ { title: t('smtp.username'), dataIndex: 'username', width: 240, render: (value: string) => copyable(value, onCopy) }, { title: t('smtp.password'), dataIndex: 'password', width: 280, render: (value: string | undefined) => ( value ? copyable(value, onCopy) : {t('smtp.passwordUnavailable')} ) }, { title: t('common.status'), dataIndex: 'passwordSet', width: 120, render: (value: boolean) => {value ? t('smtp.passwordSet') : t('smtp.passwordEmpty')} }, { title: t('tokens.createdAt'), dataIndex: 'createdAt', width: 190, render: formatDate }, { title: t('domains.actions'), fixed: 'right', width: 150, render: (_, item) => ( } > item.id || item.username} columns={credentialColumns} dataSource={credentials} scroll={{ x: 980 }} pagination={credentials.length > 10 ? { pageSize: 10 } : false} /> } onClick={openCreateRelay}> {t('smtpRelay.create')} } >
setRelayOpen(false)} onOk={saveRelay} width={640} destroyOnHidden >
); } function randomPassword() { const bytes = new Uint8Array(24); crypto.getRandomValues(bytes); return btoa(String.fromCharCode(...bytes)).replace(/[+/=]/g, '').slice(0, 28); } function copyable(value: string, onCopy: (value: string) => void) { return ( {value}