mirror of
https://github.com/certd/certd.git
synced 2026-05-13 19:47:55 +08:00
perf(domain): 添加域名过期时间同步进度显示功能
添加同步进度对话框,展示同步任务的实时进度和状态 更新中英文翻译文件,添加相关文案 修改AGENTS.md文档中的格式化命令说明
This commit is contained in:
@@ -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 <files>` 和 `corepack pnpm --dir packages\ui\certd-client exec eslint --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`,ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||
- 优先对改动包运行聚焦的测试或类型检查;只有跨包影响明显时再考虑全 monorepo 构建。
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "未执行",
|
||||
|
||||
@@ -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
|
||||
<fs-values-format modelValue={row.challengeType} dict={Dicts.challengeTypeDict} color={"auto"}></fs-values-format>
|
||||
<fs-values-format modelValue={row.httpUploaderType} dict={httpUploaderTypeDict} color={"auto"}></fs-values-format>
|
||||
<fs-values-format class={"ml-5"} modelValue={row.httpUploaderAccess} dict={accessDict} color={"auto"}></fs-values-format>
|
||||
<a-tag class={"ml-5 flex items-center"}>{t("certd.domain.path")}: {row.httpUploadRootDir}</a-tag>
|
||||
<a-tag class={"ml-5 flex items-center"}>
|
||||
{t("certd.domain.path")}: {row.httpUploadRootDir}
|
||||
</a-tag>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<any> = ref({});
|
||||
const errors: Ref<string[]> = ref([]);
|
||||
const timerRef: Ref<any> = 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 <div>{item}</div>;
|
||||
});
|
||||
return (
|
||||
<div class={"w-full"}>
|
||||
<div class={"mt-4 flex flex-wrap gap-2"}>
|
||||
<a-tag color={isRunning ? "processing" : "success"}>{isRunning ? t("certd.domain.running") : t("certd.domain.done")}</a-tag>
|
||||
|
||||
<a-tag class={"m-0"} color={"blue"}>
|
||||
{t("certd.domain.total")}:{taskStatus.value.total || 0}
|
||||
</a-tag>
|
||||
<a-tag class={"m-0"} color={"green"}>
|
||||
{t("certd.success")}:{taskStatus.value.successCount || 0}
|
||||
</a-tag>
|
||||
<a-tag class={"m-0"} color={"red"}>
|
||||
{t("certd.domain.failed")}:{taskStatus.value.errorCount || 0}
|
||||
</a-tag>
|
||||
<a-tag class={"m-0"} color={"cyan"}>
|
||||
{t("certd.domain.current")}:{taskStatus.value.current || 0}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class={"mt-4 pr-4"}>
|
||||
<a-progress percent={progress} status={errors.value.length > 0 ? "exception" : isRunning ? "active" : "success"} />
|
||||
</div>
|
||||
{errors.value.length > 0 && <div class={"mt-2 break-words text-red-500 mb-4"}>{errorList}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
wrapper: {
|
||||
width: 600,
|
||||
footer: false,
|
||||
buttons: {
|
||||
cancel: {
|
||||
show: false,
|
||||
},
|
||||
reset: {
|
||||
show: false,
|
||||
},
|
||||
ok: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
onClosed() {
|
||||
stop();
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user