import * as React from "react" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { CheckCircle2, Copy, Globe2, Mailbox, Plus, RefreshCcw, ShieldCheck, Users } from "lucide-react" import { api, DNSRecord, Domain } from "@/lib/api" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { useToast } from "@/hooks/use-toast" export function AdminPage() { const domains = useQuery({ queryKey: ["admin", "domains"], queryFn: api.domains }) const mailboxes = useQuery({ queryKey: ["admin", "mailboxes"], queryFn: api.mailboxes }) const aliases = useQuery({ queryKey: ["admin", "aliases"], queryFn: api.aliases }) const [selectedDomain, setSelectedDomain] = React.useState(null) React.useEffect(() => { if (!selectedDomain && domains.data?.items?.[0]) setSelectedDomain(domains.data.items[0].id) }, [domains.data, selectedDomain]) const domain = domains.data?.items.find((d) => d.id === selectedDomain) return (

系统管理

} label="域名" value={domains.data?.items.length || 0} /> } label="邮箱账号" value={mailboxes.data?.items.length || 0} /> } label="别名" value={aliases.data?.items.length || 0} /> } label="DNS 正常" value={(domains.data?.items || []).filter((d) => d.dnsStatus === "ok").length} />
域名
{domains.data?.items.map((d) => ( ))}
邮箱账号 地址 归属用户 名称 配额 状态 {mailboxes.data?.items.map((m) => ( {m.address} {m.userEmail || m.userId} {m.displayName} {m.quotaMb} MB {m.status} ))}
别名/转发 来源 目标 状态 {aliases.data?.items.map((a) => ( {a.source} {a.destination} {a.enabled ? "启用" : "停用"} ))}
) } function Stat({ icon, label, value }: { icon: React.ReactNode; label: string; value: number }) { return (
{icon}
{value}
{label}
) } function DNSPanel({ domain }: { domain?: Domain }) { const { toast } = useToast() const qc = useQueryClient() const records = useQuery({ queryKey: ["dns-records", domain?.id], queryFn: () => api.dnsRecords(domain!.id), enabled: !!domain }) const check = useMutation({ mutationFn: () => api.checkDns(domain!.id), onSuccess: (res) => { qc.invalidateQueries({ queryKey: ["admin", "domains"] }) toast({ title: res.status === "ok" ? "DNS 检测通过" : "DNS 检测未通过", description: Object.values(res.checks).map((c) => c.message).join(";") }) }, }) if (!domain) return 请选择域名 return (
DNS 记录
{records.data?.items.map((r) => )}
{check.data && ( <>
{Object.entries(check.data.checks).map(([k, v]) => (
{k.toUpperCase()}: {v.message}
))}
)}
) } function DNSRecordRow({ record }: { record: DNSRecord }) { const { toast } = useToast() const text = `${record.type} ${record.name} ${record.value}` return (
{record.type}
Name: {record.name}
Value: {record.value}
TTL: {record.ttl}s
) } function CreateDomainDialog() { const qc = useQueryClient() const { toast } = useToast() const [open, setOpen] = React.useState(false) const mut = useMutation({ mutationFn: (form: FormData) => api.createDomain(String(form.get("name"))), onSuccess: () => { qc.invalidateQueries({ queryKey: ["admin", "domains"] }); setOpen(false); toast({ title: "域名已创建" }) }, onError: (e) => toast({ title: "创建失败", description: e.message }), }) return ( 添加域名
{ e.preventDefault(); mut.mutate(new FormData(e.currentTarget)) }}>
) } function CreateMailboxDialog({ domains }: { domains: Domain[] }) { const qc = useQueryClient() const { toast } = useToast() const [open, setOpen] = React.useState(false) const [domainId, setDomainId] = React.useState("") const [role, setRole] = React.useState("user") React.useEffect(() => { if (!domainId && domains[0]) setDomainId(domains[0].id) }, [domains, domainId]) const mut = useMutation({ mutationFn: (form: FormData) => api.createMailbox({ domainId, localPart: String(form.get("localPart")), displayName: String(form.get("displayName")), password: String(form.get("password")), quotaMb: Number(form.get("quotaMb") || 1024), role: role as "admin" | "user", ownerEmail: String(form.get("ownerEmail") || ""), }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["admin", "mailboxes"] }); setOpen(false); toast({ title: "邮箱已创建" }) }, onError: (e) => toast({ title: "创建失败", description: e.message }), }) return ( 创建邮箱账号
{ e.preventDefault(); mut.mutate(new FormData(e.currentTarget)) }}>
) } function CreateAliasDialog({ domains }: { domains: Domain[] }) { const qc = useQueryClient() const { toast } = useToast() const [open, setOpen] = React.useState(false) const [domainId, setDomainId] = React.useState("") React.useEffect(() => { if (!domainId && domains[0]) setDomainId(domains[0].id) }, [domains, domainId]) const mut = useMutation({ mutationFn: (form: FormData) => api.createAlias({ domainId, source: String(form.get("source")), destination: String(form.get("destination")), enabled: true }), onSuccess: () => { qc.invalidateQueries({ queryKey: ["admin", "aliases"] }); setOpen(false); toast({ title: "别名已创建" }) }, onError: (e) => toast({ title: "创建失败", description: e.message }), }) return ( 创建别名/转发
{ e.preventDefault(); mut.mutate(new FormData(e.currentTarget)) }}>
) } function Field({ label, required = true, ...props }: React.InputHTMLAttributes & { label: string }) { return
} function DomainSelect({ domains, value, onChange }: { domains: Domain[]; value: string; onChange: (value: string) => void }) { return
}