diff --git a/AGENTS.md b/AGENTS.md index 030d502d8..3fb9c8ca2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -208,5 +208,5 @@ Get-ChildItem packages\ui\certd-client\src\views\certd - 后补单元测试时,应先基于对正确行为的实际预期编写测试,而不是为了迎合现有实现改写预期;如果运行后出现红灯,且通过测试需要修改已有实现,应先向用户确认这是确实的 bug,还是原本需求/既有行为就是如此;确认后再修改原始实现,避免把测试补充变成未经确认的行为改动。 - 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`;对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。 - 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`、`corepack pnpm --dir packages\core\basic test:unit`、`corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。 -- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复,例如 `corepack pnpm --dir packages\ui\certd-client exec prettier --write ` 和 `corepack pnpm --dir packages\ui\certd-client exec eslint --fix `;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。 +- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write `,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix `;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。 - 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。 diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd/cert-domain.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd/cert-domain.ts index f3da43a77..01387a475 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd/cert-domain.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd/cert-domain.ts @@ -20,6 +20,7 @@ export default { importFromProvider: "Import from Domain Provider", syncExpirationDate: "Sync Domain Expiration Time", syncTaskSubmitted: "Sync task submitted", + syncExpirationProgress: "Sync Domain Expiration Progress", expirationMonitorSetting: "Domain Expiration Monitor Settings", subdomainDnsHelper: "Note: In DNS validation mode, subdomains do not need to be maintained here, otherwise certificate application may be affected (except delegated subdomains or free second-level domains).", path: "Path", @@ -28,6 +29,9 @@ export default { progress: "Progress", operation: "Operation", total: "Total", + current: "Current", + running: "Running", + done: "Done", skipped: "Skipped", failed: "Failed", notExecuted: "Not executed", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/cert-domain.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/cert-domain.ts index 9d9ad738f..9ef745b9a 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd/cert-domain.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd/cert-domain.ts @@ -20,6 +20,7 @@ export default { importFromProvider: "从域名提供商导入", syncExpirationDate: "同步域名过期时间", syncTaskSubmitted: "同步任务已提交", + syncExpirationProgress: "同步域名过期时间进度", expirationMonitorSetting: "域名过期监控设置", subdomainDnsHelper: "注意:DNS校验方式下,子域名不需要在此处维护,否则会影响证书申请(子域名托管或免费二级域名除外)", path: "路径", @@ -28,6 +29,9 @@ export default { progress: "进度", operation: "操作", total: "总数", + current: "当前", + running: "运行中", + done: "已完成", skipped: "跳过", failed: "失败", notExecuted: "未执行", diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx b/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx index a710d58e1..e4acca9cc 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx @@ -3,7 +3,7 @@ import { Modal, notification } from "ant-design-vue"; import { Ref, ref } from "vue"; import { useRouter } from "vue-router"; import * as api from "./api"; -import { useDomainImportManage } from "./use"; +import { useDomainImportManage, useSyncExpirationProcess } from "./use"; import { Dicts } from "/@/components/plugins/lib/dicts"; import { useSettingStore } from "/@/store/settings"; import { useUserStore } from "/@/store/user"; @@ -52,6 +52,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }); const openDomainImportManageDialog = useDomainImportManage(); + const openSyncExpirationProcessDialog = useSyncExpirationProcess({ crudExpose }); const subdomainConfirmed = ref(false); return { @@ -140,13 +141,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat icon: "ion:refresh-outline", text: t("certd.domain.syncExpirationDate"), click: async () => { - await api.SyncExpirationStart(); - notification.success({ - message: t("certd.domain.syncTaskSubmitted"), - }); - setTimeout(() => { - crudExpose.doRefresh(); - }, 2000); + try { + await api.SyncExpirationStart(); + } finally { + await openSyncExpirationProcessDialog(); + } }, }, monitorSettingSave: { @@ -361,7 +360,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat - {t("certd.domain.path")}: {row.httpUploadRootDir} + + {t("certd.domain.path")}: {row.httpUploadRootDir} + ); } diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/use.tsx b/packages/ui/certd-client/src/views/certd/cert/domain/use.tsx index 77bbb3ed8..80c6b7a0e 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/use.tsx +++ b/packages/ui/certd-client/src/views/certd/cert/domain/use.tsx @@ -1,11 +1,10 @@ -import { message } from "ant-design-vue"; -import * as api from "./api"; -import { useFormDialog } from "/@/use/use-dialog"; import { compute } from "@fast-crud/fast-crud"; -import { Dicts } from "/@/components/plugins/lib/dicts"; -import { useSettingStore } from "/@/store/settings"; +import { Ref, ref } from "vue"; +import * as api from "./api"; import DomainImportTaskStatus from "./import.vue"; import { useI18n } from "/@/locales"; +import { useSettingStore } from "/@/store/settings"; +import { useFormDialog } from "/@/use/use-dialog"; export function useDomainImport() { const { openFormDialog } = useFormDialog(); const { t } = useI18n(); @@ -92,3 +91,92 @@ export function useDomainImportManage() { }); }; } + +export function useSyncExpirationProcess(opts: { crudExpose: any }) { + const { openFormDialog } = useFormDialog(); + const { t } = useI18n(); + + return async function openSyncExpirationProcessDialog() { + const taskStatus: Ref = ref({}); + const errors: Ref = ref([]); + const timerRef: Ref = ref(null); + + function stop() { + if (timerRef.value) { + clearTimeout(timerRef.value); + timerRef.value = null; + } + } + + async function loadStatus() { + const status = await api.SyncExpirationStatus(); + taskStatus.value = status || {}; + errors.value = taskStatus.value.errors || []; + + if (taskStatus.value.status === "running") { + stop(); + timerRef.value = setTimeout(async () => { + await loadStatus(); + }, 3000); + } else { + stop(); + await opts.crudExpose.doRefresh(); + } + } + + await loadStatus(); + + await openFormDialog({ + title: t("certd.domain.syncExpirationProgress"), + body: () => { + const progress = Math.min(Math.round(taskStatus.value.progress || 0), 100); + const isRunning = taskStatus.value.status === "running"; + const errorList = errors.value.map(item => { + return
{item}
; + }); + return ( +
+
+ {isRunning ? t("certd.domain.running") : t("certd.domain.done")} + + + {t("certd.domain.total")}:{taskStatus.value.total || 0} + + + {t("certd.success")}:{taskStatus.value.successCount || 0} + + + {t("certd.domain.failed")}:{taskStatus.value.errorCount || 0} + + + {t("certd.domain.current")}:{taskStatus.value.current || 0} + +
+
+ 0 ? "exception" : isRunning ? "active" : "success"} /> +
+ {errors.value.length > 0 &&
{errorList}
} +
+ ); + }, + wrapper: { + width: 600, + footer: false, + buttons: { + cancel: { + show: false, + }, + reset: { + show: false, + }, + ok: { + show: true, + }, + }, + onClosed() { + stop(); + }, + }, + }); + }; +}