SmtpCredentials.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { CopyOutlined, ReloadOutlined } from '@ant-design/icons';
  2. import { Button, Card, Descriptions, Form, Input, Space, Tag, Typography } from 'antd';
  3. import { useI18n } from '../frontend/i18n/react';
  4. import type { RuntimeConfig, SmtpCredential } from '../frontend/types';
  5. interface SmtpCredentialsProps {
  6. config: RuntimeConfig | null;
  7. credential: SmtpCredential | null;
  8. loading?: boolean;
  9. onCopy: (value: string) => void;
  10. onSave: (values: { username: string; password?: string }) => Promise<void>;
  11. }
  12. export default function SmtpCredentials({ config, credential, loading, onCopy, onSave }: SmtpCredentialsProps) {
  13. const { t } = useI18n();
  14. const [form] = Form.useForm();
  15. function generatePassword() {
  16. const bytes = new Uint8Array(24);
  17. crypto.getRandomValues(bytes);
  18. const password = btoa(String.fromCharCode(...bytes)).replace(/[+/=]/g, '').slice(0, 28);
  19. form.setFieldValue('password', password);
  20. }
  21. return (
  22. <Space direction="vertical" size={16} className="full-width">
  23. <Card title={t('smtp.connectionTitle')}>
  24. <Descriptions column={1}>
  25. <Descriptions.Item label="SMTP Host">{copyable(config?.submission?.host || '-', onCopy)}</Descriptions.Item>
  26. <Descriptions.Item label="SMTP Port">
  27. {(config?.submission?.ports || []).map((item) => <Tag key={item.port}>{item.port} · {item.protocol}</Tag>)}
  28. </Descriptions.Item>
  29. <Descriptions.Item label="TLS / SSL">{config?.submission?.tls ? 'TLS' : 'STARTTLS'}</Descriptions.Item>
  30. <Descriptions.Item label="Username">{copyable(credential?.username || config?.submission?.username || '-', onCopy)}</Descriptions.Item>
  31. <Descriptions.Item label="Password">
  32. {credential?.password ? copyable(credential.password, onCopy) : <Typography.Text type="secondary">{t('smtp.resetToCopy')}</Typography.Text>}
  33. </Descriptions.Item>
  34. </Descriptions>
  35. </Card>
  36. <Card title={t('smtp.updateTitle')}>
  37. <Form
  38. form={form}
  39. layout="vertical"
  40. onFinish={onSave}
  41. initialValues={{ username: credential?.username || config?.submission?.username || '' }}
  42. >
  43. <Form.Item name="username" label="Username" rules={[{ required: true, message: t('smtp.usernameRequired') }]}>
  44. <Input autoComplete="off" />
  45. </Form.Item>
  46. <Form.Item name="password" label="Password" extra={t('smtp.passwordExtra')}>
  47. <Input.Password autoComplete="new-password" />
  48. </Form.Item>
  49. <Space wrap>
  50. <Button icon={<ReloadOutlined />} onClick={generatePassword}>
  51. {t('smtp.regenerate')}
  52. </Button>
  53. <Button type="primary" htmlType="submit" loading={loading}>
  54. {t('smtp.save')}
  55. </Button>
  56. </Space>
  57. </Form>
  58. </Card>
  59. </Space>
  60. );
  61. }
  62. function copyable(value: string, onCopy: (value: string) => void) {
  63. return (
  64. <Space>
  65. <Typography.Text code>{value}</Typography.Text>
  66. <Button size="small" icon={<CopyOutlined />} onClick={() => onCopy(value)} />
  67. </Space>
  68. );
  69. }