/** * 域名页(v1.1.0):域名由 Cloudflare OAuth 自动同步(无手工添加); * 点击域名配置 Email Workers(Worker + Email Routing + catch-all)与域名级 Webhook; * 支持搜索与按账号筛选。 */ import { useCallback, useEffect, useMemo, useState } from "react"; import { Button, Card, Drawer, Input, Select, Space, Table, Tag, Tooltip, Typography, App } from "antd"; import { ReloadOutlined, SearchOutlined } from "@ant-design/icons"; import { deleteDomain, listDomainAccounts, listDomains, setupDomain, type DomainRow, } from "../api.ts"; import { WebhookForm } from "../components/WebhookForm.tsx"; /** # 成员变量: 域名状态 -> Tag **/ function StatusTag({ row }: { row: DomainRow }) { if (row.status === "active") return 已配置 Email Workers; if (row.status === "error") return ( error ); return pending; } /** # 函数 * 域名页组件 */ export function DomainsPage() { const { message, modal } = App.useApp(); const [items, setItems] = useState([]); const [accountOptions, setAccountOptions] = useState<{ id: string; name: string }[]>([]); const [loading, setLoading] = useState(false); const [q, setQ] = useState(""); const [accountId, setAccountId] = useState(); const [editing, setEditing] = useState(null); const load = useCallback(async () => { setLoading(true); try { const [d, a] = await Promise.all([listDomains(), listDomainAccounts()]); setItems(d.items); setAccountOptions(a.items); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const filtered = useMemo( () => items.filter( (row) => (!accountId || row.node_id === accountId) && row.name.toLowerCase().includes(q.trim().toLowerCase()), ), [items, q, accountId], ); /** 配置 Email Workers */ const runSetup = async (row: DomainRow) => { try { const res = await setupDomain(row.id); if (res.ok) message.success(`已为 ${row.name} 部署 Email Worker 并开启 Email Routing`); else message.error(res.error ?? "配置失败"); void load(); setEditing((cur) => (cur?.id === row.id ? { ...cur, status: res.ok ? "active" : "error" } : cur)); } catch (err) { message.error(err instanceof Error ? err.message : "配置失败"); } }; return ( } placeholder="搜索域名" style={{ width: 240 }} onChange={(e) => setQ(e.target.value)} /> ({ value: a.id, label: a.name }))} /> 域名在 Cloudflare OAuth 授权后自动同步;点击域名行设置 Email Workers 与 Webhook。 } onClick={() => void load()} /> ({ onClick: () => setEditing(row), style: { cursor: "pointer" } })} columns={[ { title: "域名", dataIndex: "name", render: (v: string) => {v} }, { title: "账号", dataIndex: "node_name", width: 170, ellipsis: true, render: (v: string | null) => v ?? "-" }, { title: "状态", dataIndex: "status", width: 170, render: (_: unknown, row: DomainRow) => }, { title: "Email Worker", dataIndex: "worker_id", width: 210, ellipsis: true, render: (v: string | null) => (v ? {v} : 未配置), }, { title: "Webhook", width: 95, render: (_: unknown, row: DomainRow) => row.webhook_url ? 已配置 : -, }, { title: "邮箱数", dataIndex: "mailbox_count", width: 80 }, { title: "操作", width: 200, render: (_: unknown, row: DomainRow) => ( e.stopPropagation()}> {row.status !== "active" ? ( void runSetup(row)}> 设置 Email Workers ) : ( void runSetup(row)}> 重新配置 )} modal.confirm({ title: `删除域名 ${row.name}?`, content: "仅在域名无邮箱时可删;删除后重新 OAuth 授权会再次同步。", onOk: async () => { try { await deleteDomain(row.id); message.success("已删除"); void load(); } catch (err) { message.error(err instanceof Error ? err.message : "删除失败"); } }, }) } > 删除 ), }, ]} /> {editing.name} {editing.node_name ?? "-"} ) : ( "域名" ) } size="large" open={Boolean(editing)} onClose={() => setEditing(null)} > {editing && ( void runSetup(editing)}> 设置 Email Workers ) } > 部署账号级 Email Worker(接收 Cloudflare 邮件事件),开启该域名的 Email Routing 并把 catch-all 规则指向 Worker;邮件经由 Node 转发回 Console。 {editing.status === "active" && editing.worker_id && ( Worker:{editing.worker_id} · Zone:{editing.zone_id} )} {editing.status === "error" && editing.setup_error && ( {editing.setup_error} )} 该域名下邮件未命中邮箱级 Webhook 时使用;再未配置则回退全局 Webhook(设置中配置)。 { const fresh = (await listDomains()).items.find((d) => d.id === editing.id) ?? null; setEditing(fresh); void load(); }} /> )} ); }