feat: v2.3.0 设置内配置 Cloudflare OAuth 客户端 + 全列表批量删除
- 设置新增「Cloudflare 接入」tab:Client ID / Client Secret(Secret AES-GCM 加密入库,
只回显末 4 位),并展示可复制的授权回调地址;settings 优先、env 变量兜底,
保存后立即生效无需重新部署;DELETE /api/settings/cf-oauth 清除
- 四个资源列表(账号/域名/邮箱/收件箱)加复选框批量选择与批量删除
- 新增 POST /api/{accounts,domains,mailboxes,emails}/batch-delete:
账号软删+撤销凭据;域名/邮箱级联删除其下邮件;邮件连 R2 原文与附件一并清理
- 抽出 deleteEmailById / deleteEmailsByMailbox / revokeAccount 供单条与批量共用
- 点击复选框不再触发行点击(避免误开详情抽屉)
- 新增 tests/batch.test.ts(6 例)与 settings OAuth 凭据用例(2 例);ui-smoke 扩到 17 步
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
} from "antd";
|
||||
import { DeleteOutlined, PlusOutlined, ReloadOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
batchDeleteAccounts,
|
||||
deleteAccount,
|
||||
getAccount,
|
||||
listAccounts,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
type AccountDetail,
|
||||
type AccountRow,
|
||||
} from "../api.ts";
|
||||
import { BatchActions, useRowSelection } from "../components/BatchActions.tsx";
|
||||
|
||||
/** # 成员变量: 账号状态 -> Tag 颜色 **/
|
||||
const STATUS_COLOR: Record<string, string> = {
|
||||
@@ -52,6 +54,8 @@ export function AccountsPage() {
|
||||
const [q, setQ] = useState("");
|
||||
const [detail, setDetail] = useState<AccountDetail | null>(null);
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const { rowSelection, ids: selectedIds, count, clear } = useRowSelection();
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -120,6 +124,26 @@ export function AccountsPage() {
|
||||
</span>
|
||||
</div>
|
||||
<Space>
|
||||
<BatchActions
|
||||
count={count}
|
||||
onClear={clear}
|
||||
loading={deleting}
|
||||
title={`删除选中的 ${count} 个账号?`}
|
||||
description="账号的 Worker 凭据会被撤销(已部署的 Email Worker 随后无法再向 Console 投递);域名与邮箱数据保留。"
|
||||
onDelete={async () => {
|
||||
setDeleting(true);
|
||||
try {
|
||||
const res = await batchDeleteAccounts(selectedIds);
|
||||
message.success(`已删除 ${res.deleted} 个账号`);
|
||||
clear();
|
||||
void load();
|
||||
} catch (err) {
|
||||
message.error(err instanceof Error ? err.message : "批量删除失败");
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void load()} />
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => void startOAuth()}>
|
||||
添加账号
|
||||
@@ -132,8 +156,16 @@ export function AccountsPage() {
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={filtered}
|
||||
rowSelection={rowSelection}
|
||||
pagination={false}
|
||||
onRow={(row) => ({ onClick: () => void openDetail(row.id), style: { cursor: "pointer" } })}
|
||||
onRow={(row) => ({
|
||||
onClick: (e) => {
|
||||
// 点击复选框不打开详情抽屉
|
||||
if ((e.target as HTMLElement).closest(".ant-table-selection-cell")) return;
|
||||
void openDetail(row.id);
|
||||
},
|
||||
style: { cursor: "pointer" },
|
||||
})}
|
||||
columns={[
|
||||
{ title: "名称", dataIndex: "name" },
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user