v2.5.0:操作日志 + 邮件保留 + 账号弹窗精简 + 复制按钮推广

- 操作日志:operation_logs 表(0004 迁移)记录 账号/域名/邮箱/Webhook 全部写操作,
  含操作账号(Console 登录名,系统动作为 system);/logs 日志页(类型筛选+搜索+分页);
  上限 10000 条,Cron housekeeping 超限删最旧
- 邮件保留:emails.retained;POST /api/emails/retain(单条/批量);
  TTL 清理与 Webhook 投递过期判定均跳过已保留;/inboxs 操作列图钉按钮 + 批量保留
- 添加账号弹窗:OAuth 与 API Token 同窗按钮组切换,去掉说明文字与账户 ID 字段
  (账户 ID 以 Token 实际所属账户为准);账号列表去掉 Email Worker 列
- 复制按钮推广:CopyText 组件;/domains 域名列、/inboxs 发件人与收件人(原「邮箱」改名)列
- 测试:104 bun tests(+10 日志/保留)、ui-smoke 35 项(+logs 页/新弹窗/复制/保留)
This commit is contained in:
bigcode
2026-10-01 00:25:38 +08:00
parent 8c5ed7fc8b
commit 8b255ef2e0
22 changed files with 1240 additions and 228 deletions
+55 -15
View File
@@ -55,6 +55,7 @@ const pages = [
{ path: "/domains", expect: "域名", shot: "04-domains.png" },
{ path: "/mailboxes", expect: "邮箱", shot: "05-mailboxes.png" },
{ path: "/inboxs", expect: "收件箱", shot: "06-inboxs.png" },
{ path: "/logs", expect: "日志", shot: "06b-logs.png" },
];
for (const p of pages) {
await page.goto(`${BASE}${p.path}`, { waitUntil: "domcontentloaded" });
@@ -143,32 +144,39 @@ for (const path of ["/accounts", "/domains", "/mailboxes", "/inboxs"]) {
if (path === "/mailboxes") await page.screenshot({ path: `${SHOT_DIR}/11-batch-selected.png` });
}
// 6b. 添加账号模态窗口:OAuth / API Token 两种授权方式 + 各自说明
// 6b. 添加账号模态窗口:OAuth / API Token 同窗(Radio 切换),只留必要字段
await page.goto(`${BASE}/accounts`, { waitUntil: "domcontentloaded" });
await page.waitForSelector(".ant-table", { timeout: 8000 });
check(
"账号列表不再有「Email Worker」列(统一命名,无需展示)",
(await page.locator(".ant-table-thead th:has-text('Email Worker')").count()) === 0,
);
await page.click("button:has-text('添加账号')");
await page.waitForSelector(".ant-modal:has-text('添加 Cloudflare 账号')", { timeout: 8000 });
const addTabs = await page.locator(".ant-modal .ant-tabs-tab").allInnerTexts();
const modeLabels = await page.locator(".ant-modal .ant-radio-button-wrapper").allInnerTexts();
check(
"添加账号模态窗口含 OAuth / API Token 两种授权方式",
addTabs.some((t) => t.includes("OAuth")) && addTabs.some((t) => t.includes("API Token")),
addTabs.join(" | "),
"添加账号模态窗口含 OAuth / API Token 两种授权方式(同窗按钮组)",
modeLabels.some((t) => t.includes("OAuth")) && modeLabels.some((t) => t.includes("API Token")),
modeLabels.join(" | "),
);
check(
"OAuth 方式带说明(不需在本系统保存密钥)",
(await page.locator(".ant-modal :text('不需要在本机保存任何密钥')").count()) > 0,
"窗口内不再有说明文字(Alert)与账户 ID 字段",
(await page.locator(".ant-modal .ant-alert").count()) === 0 &&
(await page.locator(".ant-modal :text('账户 ID')").count()) === 0,
);
// OAuth 模式:只有「前往 Cloudflare 授权」按钮
check(
"OAuth 模式默认展示「前往 Cloudflare 授权」按钮",
(await page.locator(".ant-modal button:has-text('前往 Cloudflare 授权')").count()) === 1,
);
await page.screenshot({ path: `${SHOT_DIR}/12a-add-account-oauth.png` });
await page.click(".ant-modal .ant-tabs-tab:has-text('API Token')");
// 切到 API Token:账户名称 + API Token 两个字段
await page.click(".ant-modal .ant-radio-button-wrapper:has-text('API Token')");
await page.waitForSelector(".ant-modal :text('账户名称')", { timeout: 8000 });
check(
"API Token 方式含 账户名称 / 账户 ID / API Token 三个字段",
(await page.locator(".ant-modal :text('账户 ID')").count()) > 0 &&
(await page.locator(".ant-modal input[type=password]").count()) > 0,
);
check(
"API Token 方式带说明(会先校验再接入)",
(await page.locator(".ant-modal :text('确认可用后才建账号')").count()) > 0,
"API Token 方式只含 账户名称 / API Token 两个字段",
(await page.locator(".ant-modal input[type=password]").count()) === 1 &&
(await page.locator(".ant-modal :text('账户 ID')").count()) === 0,
);
await page.screenshot({ path: `${SHOT_DIR}/12b-add-account-token.png` });
await page.keyboard.press("Escape");
@@ -239,6 +247,38 @@ if ((await setupBtn.count()) > 0) {
check("点「设置 Email Workers」先弹确认框(说明会改 MX 与路由)", false, "找不到配置按钮");
}
// 6e. 域名列表复制按钮
await page.goto(`${BASE}/domains`, { waitUntil: "domcontentloaded" });
await page.waitForSelector(".ant-table-tbody", { timeout: 8000 });
check(
"域名列表域名右侧带复制按钮",
(await page.locator(".ant-table-tbody button.copy-btn").count()) > 0,
);
await page.waitForTimeout(400);
await page.screenshot({ path: `${SHOT_DIR}/15-domains-copy.png` });
// 6f. 收件箱:发件人/收件人复制按钮 + 「操作」列保留按钮 + 批量保留
await page.goto(`${BASE}/inboxs`, { waitUntil: "domcontentloaded" });
await page.waitForSelector(".ant-table-tbody", { timeout: 8000 });
check(
"列表表头是「收件人」(不再叫「邮箱」)",
(await page.locator(".ant-table-thead th:has-text('收件人')").count()) === 1 &&
(await page.locator(".ant-table-thead th:has-text('邮箱')").count()) === 0,
);
const inboxCopyBtns = await page.locator(".ant-table-tbody button.copy-btn").count();
check("发件人与收件人右侧都有复制按钮", inboxCopyBtns >= 2, `copy-btn x${inboxCopyBtns}`);
check(
"「操作」列有保留按钮(图钉,无边框)",
(await page.locator(".ant-table-thead th:has-text('操作')").count()) === 1 &&
(await page.locator(".ant-table-tbody button.pin-btn").count()) > 0,
);
// 勾选一行 → 工具条出现「保留选中」
await page.locator(".ant-table-tbody .ant-checkbox-input").first().click({ force: true });
await page.waitForSelector("button:has-text('保留选中')", { timeout: 6000 });
check("勾选后工具条出现「保留选中(批量保留)」按钮", true);
await page.waitForTimeout(400);
await page.screenshot({ path: `${SHOT_DIR}/16-inbox-retain.png` });
await browser.close();
const failed = results.filter((r) => !r.ok);