refactor: organize certd client i18n translations

This commit is contained in:
xiaojunnuo
2026-04-30 23:48:48 +08:00
parent 267243e71b
commit 028932c04a
35 changed files with 503 additions and 150 deletions
@@ -1,17 +1,18 @@
<template> <template>
<div class="flex"> <div class="flex">
<a-input :value="valueRef" placeholder="请输入图片验证码" autocomplete="off" @update:value="onChange"> <a-input :value="valueRef" :placeholder="t('certd.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
<template #prefix> <template #prefix>
<fs-icon icon="ion:image-outline"></fs-icon> <fs-icon icon="ion:image-outline"></fs-icon>
</template> </template>
</a-input> </a-input>
<div class="input-right pointer" title="点击刷新"> <div class="input-right pointer" :title="t('certd.captcha.refresh')">
<img class="image-code" :src="imageCodeSrc" @click="resetImageCode" /> <img class="image-code" :src="imageCodeSrc" @click="resetImageCode" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
const props = defineProps<{ const props = defineProps<{
modelValue: any; modelValue: any;
@@ -20,6 +21,7 @@ const props = defineProps<{
defineOptions({ defineOptions({
name: "ImageCaptcha", name: "ImageCaptcha",
}); });
const { t } = useI18n();
const emit = defineEmits(["update:modelValue", "change"]); const emit = defineEmits(["update:modelValue", "change"]);
const valueRef = ref(""); const valueRef = ref("");
@@ -4,8 +4,8 @@
<div class="sweep-animation"></div> <div class="sweep-animation"></div>
<div class="box-content"> <div class="box-content">
<div class="box-icon"></div> <div class="box-icon"></div>
<span v-if="modelValue == null" class="status-text">点击进行验证</span> <span v-if="modelValue == null" class="status-text">{{ t("certd.captcha.clickToVerify") }}</span>
<span v-else class="status-text">验证成功</span> <span v-else class="status-text">{{ t("certd.captcha.verifySuccess") }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -13,8 +13,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
import { ref, Ref, watch } from "vue"; import { ref, Ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { loadScript } from "vue-plugin-load-script"; import { loadScript } from "vue-plugin-load-script";
const { t } = useI18n();
const loaded = ref(false); const loaded = ref(false);
async function loadCaptchaScript() { async function loadCaptchaScript() {
// 加载验证码js // 加载验证码js
@@ -56,7 +58,7 @@ function callback(res: { ret: number; ticket: string; randstr: string; errorCode
if (res.errorCode && res.errorCode > 0) { if (res.errorCode && res.errorCode > 0) {
notification.error({ notification.error({
message: `验证码验证失败:${res.errorMessage || res.errorCode}`, message: t("certd.captcha.verifyFailed", { message: res.errorMessage || res.errorCode }),
}); });
} }
@@ -83,13 +85,13 @@ function loadErrorCallback(error: any) {
// errorMessage: "jsload_error", // errorMessage: "jsload_error",
// }); // });
notification.error({ notification.error({
message: `验证码加载失败:${error?.message || error}`, message: t("certd.captcha.loadFailed", { message: error?.message || error }),
}); });
} }
async function triggerCaptcha() { async function triggerCaptcha() {
if (!loaded.value) { if (!loaded.value) {
notification.error({ notification.error({
message: "验证码还未加载完成,请稍后再试", message: t("certd.captcha.notLoaded"),
}); });
return; return;
} }
@@ -4,7 +4,7 @@
<!-- <td class="domain">--> <!-- <td class="domain">-->
<!-- {{ props.domain }}--> <!-- {{ props.domain }}-->
<!-- </td>--> <!-- </td>-->
<td class="host-record" :title="'域名:' + props.domain"> <td class="host-record" :title="t('certd.verifyPlan.domainTitle', { domain: props.domain })">
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable> <fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
</td> </td>
<td style="text-align: center">CNAME</td> <td style="text-align: center">CNAME</td>
@@ -16,17 +16,17 @@
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error"> <a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon> <fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
</a-tooltip> </a-tooltip>
<a-tooltip v-if="cnameRecord.status === 'valid'" title="重置校验状态,重新校验"> <a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.verifyPlan.resetStatusTooltip')">
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon> <fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
</a-tooltip> </a-tooltip>
</td> </td>
<td class="center"> <td class="center">
<template v-if="cnameRecord.status !== 'valid'"> <template v-if="cnameRecord.status !== 'valid'">
<a-button type="primary" size="small" :loading="loading" @click="doVerify">点击验证</a-button> <a-button type="primary" size="small" :loading="loading" @click="doVerify">{{ t("certd.verifyPlan.clickToValidate") }}</a-button>
<cname-tip :record="cnameRecord"></cname-tip> <cname-tip :record="cnameRecord"></cname-tip>
</template> </template>
<div v-else class="helper" title="后续自动申请证书需要">不要删除CNAME</div> <div v-else class="helper" :title="t('certd.verifyPlan.keepCnameTitle')">{{ t("certd.verifyPlan.keepCname") }}</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -35,18 +35,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-verify-plan-editor/api"; import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { dict } from "@fast-crud/fast-crud"; import { dict } from "@fast-crud/fast-crud";
import * as api from "./api.js"; import * as api from "./api.js";
import CnameTip from "./cname-tip.vue"; import CnameTip from "./cname-tip.vue";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import { utils } from "/@/utils/index.js"; import { utils } from "/@/utils/index.js";
const { t } = useI18n();
const statusDict = dict({ const statusDict = dict({
data: [ data: [
{ label: "待设置CNAME", value: "cname", color: "warning" }, { label: t("certd.verifyPlan.status.pendingCname"), value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "blue" }, { label: t("certd.verifyPlan.status.validating"), value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" }, { label: t("certd.verifyPlan.status.valid"), value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" }, { label: t("certd.verifyPlan.status.failed"), value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" }, { label: t("certd.verifyPlan.status.timeout"), value: "timeout", color: "red" },
], ],
}); });
@@ -125,8 +127,8 @@ async function doVerify() {
async function resetStatus() { async function resetStatus() {
Modal.confirm({ Modal.confirm({
title: "重置状态", title: t("certd.verifyPlan.resetStatus"),
content: "确定要重置校验状态吗?", content: t("certd.verifyPlan.confirmResetStatus"),
onOk: async () => { onOk: async () => {
await api.ResetStatus(cnameRecord.value.id); await api.ResetStatus(cnameRecord.value.id);
await loadRecord(); await loadRecord();
@@ -2,17 +2,17 @@
<a-tooltip :overlay-style="{ maxWidth: '400px' }"> <a-tooltip :overlay-style="{ maxWidth: '400px' }">
<template #title> <template #title>
<div> <div>
<div>多试几次如果仍然无法验证通过请按如下步骤排查问题</div> <div>{{ t("certd.verifyPlan.cnameTip.intro") }}</div>
<div>1. 解析记录应该添加在{{ record.domain }}域名下</div> <div>{{ t("certd.verifyPlan.cnameTip.step1", { domain: record.domain }) }}</div>
<div>2. 要添加的是CNAME类型的记录不是TXT</div> <div>{{ t("certd.verifyPlan.cnameTip.step2") }}</div>
<div>3. 核对记录值是否是:{{ record.recordValue }}</div> <div>{{ t("certd.verifyPlan.cnameTip.step3", { value: record.recordValue }) }}</div>
<div> <div>
4. 在验证中状态下运行下面的命令,查看cname和txt解析是否正确 {{ t("certd.verifyPlan.cnameTip.step4") }}
<fs-copyable :style="{ color: '#52c41a' }" :model-value="nslookupCmd"></fs-copyable> <fs-copyable :style="{ color: '#52c41a' }" :model-value="nslookupCmd"></fs-copyable>
或者 {{ t("certd.verifyPlan.cnameTip.or") }}
<fs-copyable :style="{ color: '#52c41a' }" :model-value="digCmd"></fs-copyable> <fs-copyable :style="{ color: '#52c41a' }" :model-value="digCmd"></fs-copyable>
</div> </div>
<div>5. 如果以上检查都没有问题则可能是DNS解析生效时间比较慢某些提供商延迟可能高达几个小时</div> <div>{{ t("certd.verifyPlan.cnameTip.step5") }}</div>
</div> </div>
</template> </template>
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon> <fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
@@ -21,9 +21,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n";
const props = defineProps<{ const props = defineProps<{
record: any; record: any;
}>(); }>();
const { t } = useI18n();
const nslookupCmd = computed(() => { const nslookupCmd = computed(() => {
return `nslookup -q=txt _acme-challenge.${props.record.domain}`; return `nslookup -q=txt _acme-challenge.${props.record.domain}`;
@@ -2,11 +2,11 @@
<table class="cname-verify-plan"> <table class="cname-verify-plan">
<thead> <thead>
<tr> <tr>
<td style="width: 160px">主机记录</td> <td style="width: 160px">{{ t("certd.verifyPlan.hostRecord") }}</td>
<td style="width: 100px; text-align: center">记录类型</td> <td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.recordType") }}</td>
<td style="width: 250px">请设置CNAME记录验证成功以后不要删除</td> <td style="width: 250px">{{ t("certd.verifyPlan.setCnameRecord") }}</td>
<td style="width: 120px" class="center">状态</td> <td style="width: 120px" class="center">{{ t("certd.status") }}</td>
<td style="width: 90px" class="center">操作</td> <td style="width: 90px" class="center">{{ t("certd.verifyPlan.operation") }}</td>
</tr> </tr>
</thead> </thead>
<template v-for="key in domains" :key="key"> <template v-for="key in domains" :key="key">
@@ -19,11 +19,14 @@
import { CnameRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/api"; import { CnameRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue"; import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-editor/cname-record-info.vue";
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n";
defineOptions({ defineOptions({
name: "CnameVerifyPlan", name: "CnameVerifyPlan",
}); });
const { t } = useI18n();
const emit = defineEmits(["update:modelValue", "change"]); const emit = defineEmits(["update:modelValue", "change"]);
const props = defineProps<{ const props = defineProps<{
@@ -2,10 +2,10 @@
<table class="http-verify-plan"> <table class="http-verify-plan">
<thead> <thead>
<tr> <tr>
<td style="width: 160px">网站域名</td> <td style="width: 160px">{{ t("certd.verifyPlan.websiteDomain") }}</td>
<td style="width: 100px; text-align: center">上传方式</td> <td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.uploadMethod") }}</td>
<td style="width: 150px">上传授权</td> <td style="width: 150px">{{ t("certd.verifyPlan.uploadAccess") }}</td>
<td style="width: 200px">网站根目录路径</td> <td style="width: 200px">{{ t("certd.verifyPlan.websiteRootPath") }}</td>
</tr> </tr>
</thead> </thead>
<tbody v-if="records" class="http-record-body"> <tbody v-if="records" class="http-record-body">
@@ -21,7 +21,7 @@
<access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector> <access-selector v-model="item.httpUploaderAccess" :type="item.httpUploaderType" @change="onRecordChange"></access-selector>
</td> </td>
<td> <td>
<a-input v-model:value="item.httpUploadRootDir" placeholder="网站根目录,如:/www/wwwroot" @change="onRecordChange"></a-input> <a-input v-model:value="item.httpUploadRootDir" :placeholder="t('certd.verifyPlan.websiteRootPlaceholder')" @change="onRecordChange"></a-input>
</td> </td>
</tr> </tr>
</template> </template>
@@ -31,6 +31,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Ref, ref, watch, nextTick } from "vue"; import { Ref, ref, watch, nextTick } from "vue";
import { useI18n } from "vue-i18n";
import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type"; import { HttpRecord } from "/@/components/plugins/cert/domains-verify-plan-editor/type";
import { dict } from "@fast-crud/fast-crud"; import { dict } from "@fast-crud/fast-crud";
import { Dicts } from "/@/components/plugins/lib/dicts"; import { Dicts } from "/@/components/plugins/lib/dicts";
@@ -39,6 +40,8 @@ defineOptions({
name: "HttpVerifyPlan", name: "HttpVerifyPlan",
}); });
const { t } = useI18n();
const emit = defineEmits(["update:modelValue", "change"]); const emit = defineEmits(["update:modelValue", "change"]);
const props = defineProps<{ const props = defineProps<{
@@ -5,7 +5,7 @@
<div class="plan-box bg-white dark:bg-neutral-700"> <div class="plan-box bg-white dark:bg-neutral-700">
<div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen"> <div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen">
<span v-if="!fullscreen" style="font-size: 10px" class="flex-center"> <span v-if="!fullscreen" style="font-size: 10px" class="flex-center">
这里可以放大 {{ t("certd.verifyPlan.expandTip") }}
<fs-icon icon="ion:arrow-forward-outline"></fs-icon> <fs-icon icon="ion:arrow-forward-outline"></fs-icon>
</span> </span>
<fs-icon :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"></fs-icon> <fs-icon :icon="fullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen'"></fs-icon>
@@ -13,9 +13,9 @@
<table class="plan-table"> <table class="plan-table">
<thead> <thead>
<tr> <tr>
<th style="min-width: 100px">主域名</th> <th style="min-width: 100px">{{ t("certd.verifyPlan.mainDomain") }}</th>
<th>验证方式</th> <th>{{ t("certd.verifyPlan.challengeType") }}</th>
<th>验证计划</th> <th>{{ t("certd.verifyPlan.challengePlan") }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -30,13 +30,13 @@
<div class="plan"> <div class="plan">
<div v-if="item.type === 'dns'" class="plan-dns"> <div v-if="item.type === 'dns'" class="plan-dns">
<div class="form-item"> <div class="form-item">
<span class="label">DNS类型</span> <span class="label">{{ t("certd.verifyPlan.dnsType") }}:</span>
<span class="input"> <span class="input">
<fs-dict-select <fs-dict-select
v-model:value="item.dnsProviderType" v-model:value="item.dnsProviderType"
size="small" size="small"
:dict="dnsProviderTypeDict" :dict="dnsProviderTypeDict"
placeholder="DNS提供商" :placeholder="t('certd.verifyPlan.dnsProvider')"
@change="onPlanChanged" @change="onPlanChanged"
@selected-change="onDnsProviderChange(item, $event)" @selected-change="onDnsProviderChange(item, $event)"
></fs-dict-select> ></fs-dict-select>
@@ -44,9 +44,9 @@
</div> </div>
<a-divider type="vertical" /> <a-divider type="vertical" />
<div class="form-item"> <div class="form-item">
<span class="label">DNS授权</span> <span class="label">{{ t("certd.verifyPlan.dnsAccess") }}:</span>
<span class="input"> <span class="input">
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderAccessType || item.dnsProviderType" placeholder="请选择" @change="onPlanChanged"></access-selector> <access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderAccessType || item.dnsProviderType" :placeholder="t('certd.verifyPlan.pleaseSelect')" @change="onPlanChanged"></access-selector>
</span> </span>
</div> </div>
</div> </div>
@@ -55,7 +55,7 @@
</div> </div>
<div v-if="item.type === 'http'" class="plan-http"> <div v-if="item.type === 'http'" class="plan-http">
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" /> <http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
<div class="helper">证书颁发机构将请求 https://yourdomain/.well-known/acme-challenge/xxxxxx 来验证域名所有权。</div> <div class="helper">{{ t("certd.verifyPlan.httpHelper") }}</div>
</div> </div>
</div> </div>
</td> </td>
@@ -72,6 +72,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { dict, FsDictSelect } from "@fast-crud/fast-crud"; import { dict, FsDictSelect } from "@fast-crud/fast-crud";
import AccessSelector from "/@/views/certd/access/access-selector/index.vue"; import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
import CnameVerifyPlan from "./cname-verify-plan.vue"; import CnameVerifyPlan from "./cname-verify-plan.vue";
@@ -84,17 +85,19 @@ defineOptions({
name: "DomainsVerifyPlanEditor", name: "DomainsVerifyPlanEditor",
}); });
const { t } = useI18n();
const challengeTypeOptions = ref<any[]>([ const challengeTypeOptions = ref<any[]>([
{ {
label: "DNS验证", label: t("certd.verifyPlan.dnsChallenge"),
value: "dns", value: "dns",
}, },
{ {
label: "CNAME验证", label: t("certd.verifyPlan.cnameChallenge"),
value: "cname", value: "cname",
}, },
{ {
label: "HTTP验证", label: t("certd.verifyPlan.httpChallenge"),
value: "http", value: "http",
}, },
]); ]);
@@ -1,5 +1,6 @@
import Validator from "async-validator"; import Validator from "async-validator";
import { DomainsVerifyPlanInput } from "./type"; import { DomainsVerifyPlanInput } from "./type";
import { $t } from "/@/locales";
function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) { function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
if (value == null) { if (value == null) {
@@ -13,7 +14,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const subDomain of subDomains) { for (const subDomain of subDomains) {
const plan = value[domain].cnameVerifyPlan[subDomain]; const plan = value[domain].cnameVerifyPlan[subDomain];
if (plan.status !== "valid") { if (plan.status !== "valid") {
throw new Error(`域名${subDomain}的CNAME未验证通过,请先设置CNAME记录,点击验证按钮`); throw new Error($t("certd.verifyPlan.errors.cnameNotValid", { domain: subDomain }));
} }
} }
} }
@@ -22,7 +23,7 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const item of domains) { for (const item of domains) {
//如果有通配符域名则不允许使用http校验 //如果有通配符域名则不允许使用http校验
if (item.startsWith("*.")) { if (item.startsWith("*.")) {
throw new Error(`域名${item}为通配符域名,不支持HTTP校验`); throw new Error($t("certd.verifyPlan.errors.wildcardNotSupportHttp", { domain: item }));
} }
} }
@@ -31,19 +32,19 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
for (const subDomain of subDomains) { for (const subDomain of subDomains) {
const plan = value[domain].httpVerifyPlan[subDomain]; const plan = value[domain].httpVerifyPlan[subDomain];
if (!plan.httpUploaderType) { if (!plan.httpUploaderType) {
throw new Error(`域名${subDomain}的上传方式必须填写`); throw new Error($t("certd.verifyPlan.errors.uploadMethodRequired", { domain: subDomain }));
} }
if (!plan.httpUploaderAccess) { if (!plan.httpUploaderAccess) {
throw new Error(`域名${subDomain}的上传授权信息必须填写`); throw new Error($t("certd.verifyPlan.errors.uploadAccessRequired", { domain: subDomain }));
} }
if (!plan.httpUploadRootDir) { if (!plan.httpUploadRootDir) {
throw new Error(`域名${subDomain}的网站根路径必须填写`); throw new Error($t("certd.verifyPlan.errors.websiteRootRequired", { domain: subDomain }));
} }
} }
} }
} else if (type === "dns") { } else if (type === "dns") {
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) { if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
throw new Error(`DNS模式下,域名${domain}的DNS类型和授权信息必须填写`); throw new Error($t("certd.verifyPlan.errors.dnsProviderRequired", { domain }));
} }
} }
} }
@@ -1,7 +1,7 @@
<template> <template>
<div class="api-test"> <div class="api-test">
<div> <div>
<fs-button :loading="loading" type="primary" text="测试" icon="ion:refresh-outline" @click="doTest"></fs-button> <fs-button :loading="loading" type="primary" :text="t('certd.pluginCommon.test')" icon="ion:refresh-outline" @click="doTest"></fs-button>
</div> </div>
<div class="helper" :class="{ error: hasError }"> <div class="helper" :class="{ error: hasError }">
@@ -12,6 +12,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib"; import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { ref, inject } from "vue"; import { ref, inject } from "vue";
import { useI18n } from "vue-i18n";
import { Form } from "ant-design-vue"; import { Form } from "ant-design-vue";
import { getInputFromForm } from "./utils"; import { getInputFromForm } from "./utils";
@@ -19,6 +20,8 @@ defineOptions({
name: "ApiTest", name: "ApiTest",
}); });
const { t } = useI18n();
const fromType: any = inject("getFromType"); const fromType: any = inject("getFromType");
const getScope: any = inject("get:scope"); const getScope: any = inject("get:scope");
const getPluginType: any = inject("get:plugin:type", () => { const getPluginType: any = inject("get:plugin:type", () => {
@@ -61,14 +64,14 @@ const doTest = async () => {
{ {
onError(err: any) { onError(err: any) {
hasError.value = true; hasError.value = true;
message.value = `错误:${err.message}`; message.value = t("certd.pluginCommon.errorWithMessage", { message: err.message });
}, },
showErrorNotify: false, showErrorNotify: false,
} }
); );
message.value = "测试请求成功"; message.value = t("certd.pluginCommon.testRequestSuccess");
if (res) { if (res) {
message.value += `,返回:${JSON.stringify(res)}`; message.value += t("certd.pluginCommon.responseSuffix", { response: JSON.stringify(res) });
} }
} finally { } finally {
loading.value = false; loading.value = false;
@@ -9,6 +9,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { inject, ref, watch } from "vue"; import { inject, ref, watch } from "vue";
import { useI18n } from "/@/locales";
defineOptions({ defineOptions({
name: "CertDomainsGetter", name: "CertDomainsGetter",
@@ -24,6 +25,7 @@ const emit = defineEmits<{
}>(); }>();
const pipeline: any = inject("pipeline"); const pipeline: any = inject("pipeline");
const { t } = useI18n();
function findStepFromPipeline(targetStepId: string) { function findStepFromPipeline(targetStepId: string) {
for (const stage of pipeline.value.stages) { for (const stage of pipeline.value.stages) {
@@ -40,7 +42,7 @@ function findStepFromPipeline(targetStepId: string) {
const errorRef = ref(""); const errorRef = ref("");
function getStepIdFromInputKey(inputKey: string) { function getStepIdFromInputKey(inputKey: string) {
if (!inputKey) { if (!inputKey) {
errorRef.value = "请先选择域名证书"; errorRef.value = t("certd.pluginCommon.selectCertFirst");
return; return;
} }
return inputKey.split(".")[1]; return inputKey.split(".")[1];
@@ -49,7 +51,7 @@ function getDomainFromPipeline(inputKey: string) {
let targetStepId = getStepIdFromInputKey(inputKey); let targetStepId = getStepIdFromInputKey(inputKey);
let certStep = findStepFromPipeline(targetStepId); let certStep = findStepFromPipeline(targetStepId);
if (!certStep) { if (!certStep) {
errorRef.value = "找不到目标步骤,请先选择域名证书"; errorRef.value = t("certd.pluginCommon.targetStepNotFound");
return; return;
} }
@@ -58,7 +60,7 @@ function getDomainFromPipeline(inputKey: string) {
targetStepId = getStepIdFromInputKey(firstLevelValue); targetStepId = getStepIdFromInputKey(firstLevelValue);
certStep = findStepFromPipeline(targetStepId); certStep = findStepFromPipeline(targetStepId);
if (!certStep) { if (!certStep) {
errorRef.value = "找不到目标步骤,请先选择域名证书"; errorRef.value = t("certd.pluginCommon.targetStepNotFound");
return; return;
} }
} }
@@ -17,12 +17,12 @@
<template v-if="search"> <template v-if="search">
<div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px"> <div class="flex w-full items-center justify-between flex-wrap" style="padding: 4px 8px">
<div class="flex-1 flex flex-row items-center"> <div class="flex-1 flex flex-row items-center">
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear placeholder="这里可以搜索域名(数据来自“设置->域名管理”),您也可以直接在上面输入框输入" @keydown.enter="doSearch" /> <a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.domainSearchPlaceholder')" @keydown.enter="doSearch" />
<fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch"> 查询 </fs-button> <fs-button type="primary" class="m-1" :loading="loading" icon="mingcute:search-2-line" @click="doSearch">{{ t("certd.pluginCommon.search") }}</fs-button>
</div> </div>
<div class="manager flex flex-row items-center"> <div class="manager flex flex-row items-center">
<fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">导入域名</fs-button> <fs-button type="primary" class="m-1" icon="mingcute:vip-1-line" @click="openDomainImportDialog">{{ t("certd.pluginCommon.importDomain") }}</fs-button>
<fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">管理域名</fs-button> <fs-button class="m-1" type="primary" icon="carbon:gui-management" @click="openDomainManager">{{ t("certd.pluginCommon.manageDomain") }}</fs-button>
</div> </div>
</div> </div>
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }"> <div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
@@ -47,7 +47,7 @@
</template> </template>
</a-select> </a-select>
<div class="ml-5"> <div class="ml-5">
<fs-button :loading="loading" title="刷新我的域名列表" icon="ion:refresh-outline" @click="refreshOptions"></fs-button> <fs-button :loading="loading" :title="t('certd.pluginCommon.refreshMyDomains')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div> </div>
</div> </div>
<div class="helper" :class="{ error: hasError }"> <div class="helper" :class="{ error: hasError }">
@@ -57,6 +57,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineComponent, onMounted, ref, Ref, useAttrs } from "vue"; import { computed, defineComponent, onMounted, ref, Ref, useAttrs } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Dicts } from "../lib/dicts"; import { Dicts } from "../lib/dicts";
import { request } from "/@/api/service"; import { request } from "/@/api/service";
@@ -67,6 +68,8 @@ defineOptions({
name: "DomainSelector", name: "DomainSelector",
}); });
const { t } = useI18n();
const VNodes = defineComponent({ const VNodes = defineComponent({
props: { props: {
vnodes: { vnodes: {
@@ -4,7 +4,7 @@
<a-auto-complete class="remote-auto-complete-input" :filter-option="filterOption" :options="optionsRef" :value="value" v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> <a-auto-complete class="remote-auto-complete-input" :filter-option="filterOption" :options="optionsRef" :value="value" v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)">
</a-auto-complete> </a-auto-complete>
<div class="ml-5"> <div class="ml-5">
<fs-button :loading="loading" title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button> <fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div> </div>
</div> </div>
<div class="helper" :class="{ error: hasError }"> <div class="helper" :class="{ error: hasError }">
@@ -15,6 +15,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib"; import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue"; import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { useI18n } from "vue-i18n";
import { PluginDefine } from "@certd/pipeline"; import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils"; import { getInputFromForm } from "./utils";
@@ -22,6 +23,8 @@ defineOptions({
name: "RemoteAutoComplete", name: "RemoteAutoComplete",
}); });
const { t } = useI18n();
const props = defineProps< const props = defineProps<
{ {
watches?: string[]; watches?: string[];
@@ -93,16 +96,16 @@ const getOptions = async () => {
{ {
onError(err: any) { onError(err: any) {
hasError.value = true; hasError.value = true;
message.value = `获取选项出错:${err.message}`; message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
}, },
showErrorNotify: false, showErrorNotify: false,
} }
); );
const list = res?.list || res || []; const list = res?.list || res || [];
if (list.length > 0) { if (list.length > 0) {
message.value = "获取数据成功,请从下拉框中选择"; message.value = t("certd.pluginCommon.getDataSuccessSelect");
} else { } else {
message.value = "获取数据成功,没有数据"; message.value = t("certd.pluginCommon.getDataSuccessEmpty");
} }
optionsRef.value = list; optionsRef.value = list;
@@ -7,6 +7,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { doRequest } from "/@/components/plugins/lib"; import { doRequest } from "/@/components/plugins/lib";
import { inject, ref, useAttrs } from "vue"; import { inject, ref, useAttrs } from "vue";
import { useI18n } from "vue-i18n";
import { useFormWrapper } from "@fast-crud/fast-crud"; import { useFormWrapper } from "@fast-crud/fast-crud";
import { notification } from "ant-design-vue"; import { notification } from "ant-design-vue";
import { getInputFromForm } from "./utils"; import { getInputFromForm } from "./utils";
@@ -15,6 +16,7 @@ defineOptions({
name: "RemoteInput", name: "RemoteInput",
}); });
const { openCrudFormDialog } = useFormWrapper(); const { openCrudFormDialog } = useFormWrapper();
const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
modelValue: string; modelValue: string;
title: string; title: string;
@@ -53,7 +55,7 @@ async function openDialog() {
saveRemind: false, saveRemind: false,
}, },
afterSubmit() { afterSubmit() {
notification.success({ message: "操作成功" }); notification.success({ message: t("certd.operationSuccess") });
}, },
async doSubmit({ form }: any) { async doSubmit({ form }: any) {
return await doPluginFormSubmit(form); return await doPluginFormSubmit(form);
@@ -5,12 +5,12 @@
<template #dropdownRender="{ menuNode: menu }"> <template #dropdownRender="{ menuNode: menu }">
<template v-if="search"> <template v-if="search">
<div class="flex w-full" style="padding: 4px 8px"> <div class="flex w-full" style="padding: 4px 8px">
<a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear placeholder="查询关键字" @keydown.enter="doSearch" /> <a-input ref="inputRef" v-model:value="searchKeyRef" class="flex-1" allow-clear :placeholder="t('certd.pluginCommon.searchKeyword')" @keydown.enter="doSearch" />
<a-button class="ml-2" :loading="loading" type="text" @click="doSearch"> <a-button class="ml-2" :loading="loading" type="text" @click="doSearch">
<template #icon> <template #icon>
<search-outlined /> <search-outlined />
</template> </template>
查询 {{ t("certd.pluginCommon.search") }}
</a-button> </a-button>
</div> </div>
<div v-if="hasError" class="helper p-2" :class="{ error: hasError }"> <div v-if="hasError" class="helper p-2" :class="{ error: hasError }">
@@ -26,7 +26,7 @@
</template> </template>
</a-select> </a-select>
<div class="ml-5 flex flex-row no-wrap"> <div class="ml-5 flex flex-row no-wrap">
<fs-button :loading="loading" title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button> <fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<UploadCert v-if="uploadCert" class="ml-5" v-bind="uploadCert" @submit="refreshOptions"></UploadCert> <UploadCert v-if="uploadCert" class="ml-5" v-bind="uploadCert" @submit="refreshOptions"></UploadCert>
</div> </div>
</div> </div>
@@ -38,6 +38,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib"; import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue"; import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { useI18n } from "vue-i18n";
import { PluginDefine } from "@certd/pipeline"; import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils"; import { getInputFromForm } from "./utils";
import UploadCert from "./upload-cert.vue"; import UploadCert from "./upload-cert.vue";
@@ -47,6 +48,8 @@ defineOptions({
name: "RemoteSelect", name: "RemoteSelect",
}); });
const { t } = useI18n();
const selectRef = ref(null); const selectRef = ref(null);
const VNodes = defineComponent({ const VNodes = defineComponent({
@@ -161,7 +164,7 @@ const getOptions = async () => {
{ {
onError(err: any) { onError(err: any) {
hasError.value = true; hasError.value = true;
message.value = `获取选项出错:${err.message}`; message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
optionsRef.value = []; optionsRef.value = [];
}, },
showErrorNotify: false, showErrorNotify: false,
@@ -169,9 +172,9 @@ const getOptions = async () => {
); );
let list = res?.list || res || []; let list = res?.list || res || [];
if (list.length > 0) { if (list.length > 0) {
message.value = "获取数据成功,请从下拉框中选择"; message.value = t("certd.pluginCommon.getDataSuccessSelect");
} else { } else {
message.value = "获取数据成功,没有数据"; message.value = t("certd.pluginCommon.getDataSuccessEmpty");
} }
list = list.map((item: any) => { list = list.map((item: any) => {
return { return {
@@ -3,7 +3,7 @@
<div class="flex flex-row"> <div class="flex flex-row">
<a-tree-select class="remote-tree-select-input" :tree-data="optionsRef" :value="value" tree-checkable allow-clear v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> </a-tree-select> <a-tree-select class="remote-tree-select-input" :tree-data="optionsRef" :value="value" tree-checkable allow-clear v-bind="attrs" @click="onClick" @update:value="emit('update:value', $event)"> </a-tree-select>
<div class="ml-5"> <div class="ml-5">
<fs-button :loading="loading" title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button> <fs-button :loading="loading" :title="t('certd.pluginCommon.refreshOptions')" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
</div> </div>
</div> </div>
<div class="helper" :class="{ error: hasError }"> <div class="helper" :class="{ error: hasError }">
@@ -14,6 +14,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib"; import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue"; import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { useI18n } from "vue-i18n";
import { PluginDefine } from "@certd/pipeline"; import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils"; import { getInputFromForm } from "./utils";
@@ -21,6 +22,8 @@ defineOptions({
name: "RemoteTreeSelect", name: "RemoteTreeSelect",
}); });
const { t } = useI18n();
const props = defineProps< const props = defineProps<
{ {
watches: string[]; watches: string[];
@@ -104,16 +107,16 @@ const getOptions = async () => {
{ {
onError(err: any) { onError(err: any) {
hasError.value = true; hasError.value = true;
message.value = `获取选项出错:${err.message}`; message.value = t("certd.pluginCommon.getOptionsError", { message: err.message });
}, },
showErrorNotify: false, showErrorNotify: false,
} }
); );
const list = res?.list || res || []; const list = res?.list || res || [];
if (list.length > 0) { if (list.length > 0) {
message.value = "获取数据成功,请从下拉框中选择"; message.value = t("certd.pluginCommon.getDataSuccessSelect");
} else { } else {
message.value = "获取数据成功,没有数据"; message.value = t("certd.pluginCommon.getDataSuccessEmpty");
} }
optionsRef.value = list; optionsRef.value = list;
pagerRef.value.total = list.length; pagerRef.value.total = list.length;
@@ -1,12 +1,13 @@
<template> <template>
<div class="upload-cert"> <div class="upload-cert">
<fs-button v-model:loading="loading" type="primary" text="上传" v-bind="props.button" @click="openUploadCertDialog"></fs-button> <fs-button v-model:loading="loading" type="primary" :text="t('certd.pluginCommon.upload')" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { message } from "ant-design-vue"; import { message } from "ant-design-vue";
import { useFormDialog } from "../../../use/use-dialog"; import { useFormDialog } from "../../../use/use-dialog";
import { computed, inject, ref } from "vue"; import { computed, inject, ref } from "vue";
import { useI18n } from "vue-i18n";
import { doRequest } from "../lib"; import { doRequest } from "../lib";
import { getInputFromForm } from "./utils"; import { getInputFromForm } from "./utils";
import { UploadCertProps } from "./types"; import { UploadCertProps } from "./types";
@@ -14,6 +15,7 @@ import { merge } from "lodash-es";
const props = defineProps<UploadCertProps>(); const props = defineProps<UploadCertProps>();
const loading = ref(false); const loading = ref(false);
const { t } = useI18n();
const emit = defineEmits(["submit"]); const emit = defineEmits(["submit"]);
const { openFormDialog } = useFormDialog(); const { openFormDialog } = useFormDialog();
@@ -28,18 +30,18 @@ const getScope: any = inject("get:scope", () => {
const getPluginType: any = inject("get:plugin:type", () => { const getPluginType: any = inject("get:plugin:type", () => {
return "plugin"; return "plugin";
}); });
const title = computed(() => props.title || "上传证书"); const title = computed(() => props.title || t("certd.pluginCommon.uploadCert"));
function openUploadCertDialog() { function openUploadCertDialog() {
const columns = merge( const columns = merge(
{ {
certName: { certName: {
title: "证书名称", title: t("certd.pluginCommon.certName"),
form: { form: {
component: { component: {
name: "a-input", name: "a-input",
vModel: "value", vModel: "value",
}, },
helper: "上传后证书显示名称", helper: t("certd.pluginCommon.certNameHelper"),
}, },
}, },
}, },
@@ -49,7 +51,7 @@ function openUploadCertDialog() {
title: title.value, title: title.value,
columns: { columns: {
certName: { certName: {
title: "证书名称", title: t("certd.pluginCommon.certName"),
form: { form: {
component: { component: {
name: "a-input", name: "a-input",
@@ -84,7 +86,7 @@ function openUploadCertDialog() {
showErrorNotify: true, showErrorNotify: true,
} }
); );
message.success("上传成功"); message.success(t("certd.pluginCommon.uploadSuccess"));
emit("submit"); emit("submit");
} finally { } finally {
loading.value = false; loading.value = false;
@@ -1,17 +1,18 @@
import { dict } from "@fast-crud/fast-crud"; import { dict } from "@fast-crud/fast-crud";
import { $t } from "/@/locales";
export const Dicts = { export const Dicts = {
sslProviderDict: dict({ sslProviderDict: dict({
data: [ data: [
{ value: "letsencrypt", label: "Lets Encrypt" }, { value: "letsencrypt", label: "Let's Encrypt" },
{ value: "zerossl", label: "ZeroSSL" }, { value: "zerossl", label: "ZeroSSL" },
], ],
}), }),
challengeTypeDict: dict({ challengeTypeDict: dict({
data: [ data: [
{ value: "dns", label: "DNS校验", color: "green" }, { value: "dns", label: $t("certd.verifyPlan.dnsChallenge"), color: "green" },
{ value: "cname", label: "CNAME代理校验", color: "blue" }, { value: "cname", label: $t("certd.verifyPlan.cnameProxyChallenge"), color: "blue" },
{ value: "http", label: "HTTP校验", color: "yellow" }, { value: "http", label: $t("certd.verifyPlan.httpChallenge"), color: "yellow" },
], ],
}), }),
dnsProviderTypeDict: dict({ dnsProviderTypeDict: dict({
@@ -22,17 +23,17 @@ export const Dicts = {
{ label: "SFTP", value: "sftp" }, { label: "SFTP", value: "sftp" },
{ label: "SCP", value: "scp" }, { label: "SCP", value: "scp" },
{ label: "FTP", value: "ftp" }, { label: "FTP", value: "ftp" },
{ label: "阿里云OSS", value: "alioss" }, { label: $t("certd.verifyPlan.uploader.aliyunOss"), value: "alioss" },
{ label: "腾讯云COS", value: "tencentcos" }, { label: $t("certd.verifyPlan.uploader.tencentCos"), value: "tencentcos" },
{ label: "七牛OSS", value: "qiniuoss" }, { label: $t("certd.verifyPlan.uploader.qiniuOss"), value: "qiniuoss" },
{ label: "S3/Minio", value: "s3" }, { label: "S3/Minio", value: "s3" },
{ label: "SSH(已废弃,请选择SFTP方式)", value: "ssh", disabled: true }, { label: $t("certd.verifyPlan.uploader.sshDeprecated"), value: "ssh", disabled: true },
], ],
}), }),
domainFromTypeDict: dict({ domainFromTypeDict: dict({
data: [ data: [
{ value: "manual", label: "手动" }, { value: "manual", label: $t("certd.verifyPlan.domainFrom.manual") },
{ value: "auto", label: "自动" }, { value: "auto", label: $t("certd.verifyPlan.domainFrom.auto") },
], ],
}), }),
}; };
@@ -14,5 +14,93 @@ export default {
gotoCnameTip: "Please go to CNAME Record Page", gotoCnameTip: "Please go to CNAME Record Page",
fromType: "From Type", fromType: "From Type",
expirationDate: "Expiration Date", expirationDate: "Expiration Date",
cnameManagedInCnamePage: "For CNAME mode, please manage records on the CNAME Records page.",
subdomainConfirmTitle: "Subdomain Confirmation",
subdomainConfirmContent: "{domain} appears to be a subdomain. Only delegated subdomains and free second-level subdomains need to be maintained here. Otherwise certificate application may fail. Continue?",
importFromProvider: "Import from Domain Provider",
syncExpirationDate: "Sync Domain Expiration Time",
syncTaskSubmitted: "Sync task submitted",
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",
addImportTask: "Add Import Task",
refresh: "Refresh",
progress: "Progress",
operation: "Operation",
total: "Total",
skipped: "Skipped",
failed: "Failed",
notExecuted: "Not executed",
execute: "Run",
delete: "Delete",
confirmDelete: "Confirm deletion?",
domainProvider: "Domain Provider",
domainProviderAccessType: "Domain Provider Access Type",
domainProviderAccess: "Domain Provider Access",
}, },
verifyPlan: {
expandTip: "Click to enlarge",
mainDomain: "Main Domain",
challengeType: "Challenge Method",
challengePlan: "Challenge Plan",
dnsType: "DNS Type",
dnsProvider: "DNS Provider",
dnsAccess: "DNS Access",
pleaseSelect: "Please select",
httpHelper: "The CA will request https://yourdomain/.well-known/acme-challenge/xxxxxx to verify domain ownership.",
dnsChallenge: "DNS Challenge",
cnameChallenge: "CNAME Challenge",
cnameProxyChallenge: "CNAME Proxy Challenge",
httpChallenge: "HTTP Challenge",
domainTitle: "Domain: {domain}",
resetStatusTooltip: "Reset validation status and validate again",
clickToValidate: "Validate",
keepCnameTitle: "Required for future automatic certificate applications",
keepCname: "Do not delete CNAME",
resetStatus: "Reset Status",
confirmResetStatus: "Are you sure you want to reset the validation status?",
hostRecord: "Host Record",
recordType: "Record Type",
setCnameRecord: "Set the CNAME record. Do not delete it after validation succeeds.",
operation: "Operation",
websiteDomain: "Website Domain",
uploadMethod: "Upload Method",
uploadAccess: "Upload Access",
websiteRootPath: "Website Root Path",
websiteRootPlaceholder: "Website root path, e.g. /www/wwwroot",
status: {
pendingCname: "Pending CNAME Setup",
validating: "Validating",
valid: "Validation Successful",
failed: "Validation Failed",
timeout: "Validation Timed Out",
},
cnameTip: {
intro: "Try a few more times. If validation still fails, troubleshoot with these steps:",
step1: "1. The DNS record should be added under {domain}.",
step2: "2. Add a CNAME record, not a TXT record.",
step3: "3. Check whether the record value is: {value}",
step4: "4. While validating, run the command below to check whether CNAME and TXT resolution are correct.",
or: "or",
step5: "5. If all checks pass, DNS propagation may be slow. Some providers can take several hours.",
},
errors: {
cnameNotValid: "The CNAME for domain {domain} has not been validated. Set the CNAME record first, then click Validate.",
wildcardNotSupportHttp: "Domain {domain} is a wildcard domain and does not support HTTP validation.",
uploadMethodRequired: "The upload method for domain {domain} is required.",
uploadAccessRequired: "The upload access for domain {domain} is required.",
websiteRootRequired: "The website root path for domain {domain} is required.",
dnsProviderRequired: "In DNS mode, the DNS type and access information for domain {domain} are required.",
},
uploader: {
aliyunOss: "Alibaba Cloud OSS",
tencentCos: "Tencent Cloud COS",
qiniuOss: "Qiniu OSS",
sshDeprecated: "SSH (deprecated, use SFTP instead)",
},
domainFrom: {
manual: "Manual",
auto: "Automatic",
},
},
}; };
@@ -18,4 +18,24 @@ export default {
cname_feature_guide: "CNAME feature principle and usage guide", cname_feature_guide: "CNAME feature principle and usage guide",
mainDomain: "Main Domain", mainDomain: "Main Domain",
cnameRecord: "CNAME Record Management", cnameRecord: "CNAME Record Management",
importRecords: "Import CNAME Records",
batchImport: "Batch Import",
exportRecordsTip: "After exporting CNAME records, you can batch import CNAME DNS records to the domain registrar.",
batchExport: "Batch Export",
resetStatus: "Reset Status",
confirmResetStatus: "Are you sure you want to reset the validation status?",
resetStatusTooltip: "Reset validation status and validate again",
cname: {
importRecords: "Import CNAME Records",
batchImport: "Batch Import",
exportRecordsTip: "After exporting CNAME records, you can batch import CNAME DNS records to the domain registrar.",
batchExport: "Batch Export",
resetStatus: "Reset Status",
confirmResetStatus: "Are you sure you want to reset the validation status?",
resetStatusTooltip: "Reset validation status and validate again",
domainList: "Domain List",
domainListHelper: "One domain per line for batch import\nRemove *. from wildcard domains\nExisting records will be skipped automatically",
cnameService: "CNAME Service",
importTaskSubmitted: "Import task submitted",
},
}; };
@@ -113,4 +113,36 @@ export default {
helpDocLink: "Help Docs", helpDocLink: "Help Docs",
suite: "Suite", suite: "Suite",
helpDoc: "Help Document", helpDoc: "Help Document",
pluginCommon: {
test: "Test",
errorWithMessage: "Error: {message}",
testRequestSuccess: "Test request succeeded",
responseSuffix: ", response: {response}",
searchKeyword: "Search keyword",
search: "Search",
refreshOptions: "Refresh options",
getOptionsError: "Failed to get options: {message}",
getDataSuccessSelect: "Data loaded. Please select from the dropdown.",
getDataSuccessEmpty: "Data loaded. No data found.",
domainSearchPlaceholder: "Search domains here (from Settings -> Domain Management), or type directly in the input above",
importDomain: "Import Domains",
manageDomain: "Manage Domains",
refreshMyDomains: "Refresh my domain list",
upload: "Upload",
uploadCert: "Upload Certificate",
certName: "Certificate Name",
certNameHelper: "Display name after upload",
uploadSuccess: "Upload successful",
selectCertFirst: "Please select a domain certificate first",
targetStepNotFound: "Target step not found. Please select a domain certificate first.",
},
captcha: {
inputImageCode: "Please enter the image captcha",
refresh: "Refresh captcha",
clickToVerify: "Click to verify",
verifySuccess: "Verification successful",
verifyFailed: "Verification failed. Please try again.",
loadFailed: "Failed to load captcha",
notLoaded: "Captcha is not loaded yet",
},
}; };
@@ -97,6 +97,7 @@ export default {
pipeline: "Pipeline", pipeline: "Pipeline",
}, },
editSchedule: "Edit Schedule", editSchedule: "Edit Schedule",
webhook: "Webhook",
timerTrigger: "Timer Trigger", timerTrigger: "Timer Trigger",
schedule: "Schedule", schedule: "Schedule",
selectCron: "Please select a schedule Cron", selectCron: "Please select a schedule Cron",
@@ -18,6 +18,7 @@ export default {
project: { project: {
noProjectJoined: "You haven't joined any projects yet", noProjectJoined: "You haven't joined any projects yet",
applyToJoin: "Please apply to join a project to start using", applyToJoin: "Please apply to join a project to start using",
projectList: "Project List",
systemProjects: "System Project List", systemProjects: "System Project List",
createdAt: "Created At", createdAt: "Created At",
applyJoin: "Apply to Join", applyJoin: "Apply to Join",
@@ -7,8 +7,13 @@ export default {
settingLink: "Site Monitoring Settings", settingLink: "Site Monitoring Settings",
limitInfo: "Basic edition limited to 1, professional and above unlimited, current", limitInfo: "Basic edition limited to 1, professional and above unlimited, current",
checkAll: "Check All", checkAll: "Check All",
checkNow: "Check Now",
batchDelete: "Batch Delete",
confirmTitle: "Confirm", confirmTitle: "Confirm",
confirmContent: "Confirm to trigger check for all site certificates?", confirmContent: "Confirm to trigger check for all site certificates?",
batchDeleteConfirm: "Are you sure you want to batch delete these {count} records?",
deleteSuccess: "Delete successful",
selectRecordsFirst: "Please select records first",
checkSubmitted: "Check task submitted", checkSubmitted: "Check task submitted",
pleaseRefresh: "Please refresh the page later to see the results", pleaseRefresh: "Please refresh the page later to see the results",
siteName: "Site Name", siteName: "Site Name",
@@ -39,6 +44,10 @@ export default {
ipCheckHelper: "Enable to check certificate expiration time on each IP (or source site domain) ", ipCheckHelper: "Enable to check certificate expiration time on each IP (or source site domain) ",
ipSyncAuto: "Enable IP Sync Auto", ipSyncAuto: "Enable IP Sync Auto",
ipSyncMode: "IP Sync Mode", ipSyncMode: "IP Sync Mode",
ipSyncModeHelper: "Choose whether to check only IPv4, only IPv6, or all IPs.",
ipSyncModeAll: "Check all IPs",
ipSyncModeIPV4Only: "Check IPv4 only",
ipSyncModeIPV6Only: "Check IPv6 only",
ipIgnoreCoherence: "Ignore Certificate Coherence", ipIgnoreCoherence: "Ignore Certificate Coherence",
ipIgnoreCoherenceHelper: "Enable to ignore certificate coherence check, only check certificate expiration time", ipIgnoreCoherenceHelper: "Enable to ignore certificate coherence check, only check certificate expiration time",
selectRequired: "Please select", selectRequired: "Please select",
@@ -49,6 +58,10 @@ export default {
certInfoId: "Certificate ID", certInfoId: "Certificate ID",
checkSubmittedRefresh: "Check task submitted. Please refresh later to view the result.", checkSubmittedRefresh: "Check task submitted. Please refresh later to view the result.",
ipManagement: "IP Management", ipManagement: "IP Management",
siteIpMonitor: "Site IP Monitor",
ipList: "IP List",
ipListHelper: "One IP or CNAME domain per line",
enterImportIpOrDomain: "Please enter the IPs or domains to import",
bulkImport: "Bulk Import", bulkImport: "Bulk Import",
basicLimitError: "Basic version allows only one monitoring site. Please upgrade to the Pro version.", basicLimitError: "Basic version allows only one monitoring site. Please upgrade to the Pro version.",
limitExceeded: "Sorry, you can only create up to {max} monitoring records. Please purchase or upgrade your plan.", limitExceeded: "Sorry, you can only create up to {max} monitoring records. Please purchase or upgrade your plan.",
@@ -14,5 +14,93 @@ export default {
gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加", gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加",
fromType: "来源类型", fromType: "来源类型",
expirationDate: "到期时间", expirationDate: "到期时间",
cnameManagedInCnamePage: "CNAME方式请前往CNAME记录页面进行管理",
subdomainConfirmTitle: "子域名确认",
subdomainConfirmContent: "检测到{domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?",
importFromProvider: "从域名提供商导入",
syncExpirationDate: "同步域名过期时间",
syncTaskSubmitted: "同步任务已提交",
expirationMonitorSetting: "域名过期监控设置",
subdomainDnsHelper: "注意:DNS校验方式下,子域名不需要在此处维护,否则会影响证书申请(子域名托管或免费二级域名除外)",
path: "路径",
addImportTask: "添加导入任务",
refresh: "刷新",
progress: "进度",
operation: "操作",
total: "总数",
skipped: "跳过",
failed: "失败",
notExecuted: "未执行",
execute: "执行",
delete: "删除",
confirmDelete: "确认删除吗?",
domainProvider: "域名提供商",
domainProviderAccessType: "域名提供商访问类型",
domainProviderAccess: "域名提供商授权",
}, },
verifyPlan: {
expandTip: "这里可以放大",
mainDomain: "主域名",
challengeType: "验证方式",
challengePlan: "验证计划",
dnsType: "DNS类型",
dnsProvider: "DNS提供商",
dnsAccess: "DNS授权",
pleaseSelect: "请选择",
httpHelper: "证书颁发机构将请求 https://yourdomain/.well-known/acme-challenge/xxxxxx 来验证域名所有权。",
dnsChallenge: "DNS验证",
cnameChallenge: "CNAME验证",
cnameProxyChallenge: "CNAME代理验证",
httpChallenge: "HTTP验证",
domainTitle: "域名:{domain}",
resetStatusTooltip: "重置校验状态,重新校验",
clickToValidate: "点击验证",
keepCnameTitle: "后续自动申请证书需要",
keepCname: "不要删除CNAME",
resetStatus: "重置状态",
confirmResetStatus: "确定要重置校验状态吗?",
hostRecord: "主机记录",
recordType: "记录类型",
setCnameRecord: "请设置CNAME记录(验证成功以后不要删除)",
operation: "操作",
websiteDomain: "网站域名",
uploadMethod: "上传方式",
uploadAccess: "上传授权",
websiteRootPath: "网站根目录路径",
websiteRootPlaceholder: "网站根目录,如:/www/wwwroot",
status: {
pendingCname: "待设置CNAME",
validating: "验证中",
valid: "验证成功",
failed: "验证失败",
timeout: "验证超时",
},
cnameTip: {
intro: "多试几次,如果仍然无法验证通过,请按如下步骤排查问题:",
step1: "1. 解析记录应该添加在{domain}域名下",
step2: "2. 要添加的是CNAME类型的记录,不是TXT",
step3: "3. 核对记录值是否是:{value}",
step4: "4. 在验证中状态下,运行下面的命令,查看cname和txt解析是否正确",
or: "或者",
step5: "5. 如果以上检查都没有问题,则可能是DNS解析生效时间比较慢,某些提供商延迟可能高达几个小时",
},
errors: {
cnameNotValid: "域名{domain}的CNAME未验证通过,请先设置CNAME记录,点击验证按钮",
wildcardNotSupportHttp: "域名{domain}为通配符域名,不支持HTTP校验",
uploadMethodRequired: "域名{domain}的上传方式必须填写",
uploadAccessRequired: "域名{domain}的上传授权信息必须填写",
websiteRootRequired: "域名{domain}的网站根路径必须填写",
dnsProviderRequired: "DNS模式下,域名{domain}的DNS类型和授权信息必须填写",
},
uploader: {
aliyunOss: "阿里云OSS",
tencentCos: "腾讯云COS",
qiniuOss: "七牛OSS",
sshDeprecated: "SSH(已废弃,请选择SFTP方式)",
},
domainFrom: {
manual: "手动",
auto: "自动",
},
},
}; };
@@ -18,4 +18,24 @@ export default {
cname_feature_guide: "CNAME功能原理及使用说明", cname_feature_guide: "CNAME功能原理及使用说明",
mainDomain: "主域名", mainDomain: "主域名",
cnameRecord: "CNAME记录管理", cnameRecord: "CNAME记录管理",
importRecords: "导入CNAME记录",
batchImport: "批量导入",
exportRecordsTip: "导出CNAME记录之后,可用于批量导入cname解析到域名注册商",
batchExport: "批量导出",
resetStatus: "重置状态",
confirmResetStatus: "确定要重置校验状态吗?",
resetStatusTooltip: "重置校验状态,重新校验",
cname: {
importRecords: "导入CNAME记录",
batchImport: "批量导入",
exportRecordsTip: "导出CNAME记录之后,可用于批量导入cname解析到域名注册商",
batchExport: "批量导出",
resetStatus: "重置状态",
confirmResetStatus: "确定要重置校验状态吗?",
resetStatusTooltip: "重置校验状态,重新校验",
domainList: "域名列表",
domainListHelper: "每个域名一行,批量导入\n泛域名请去掉*.\n已经存在的会自动跳过",
cnameService: "CNAME服务",
importTaskSubmitted: "导入任务已提交",
},
}; };
@@ -117,4 +117,36 @@ export default {
helpDocLink: "帮助文档", helpDocLink: "帮助文档",
suite: "套餐", suite: "套餐",
helpDoc: "帮助说明", helpDoc: "帮助说明",
pluginCommon: {
test: "测试",
errorWithMessage: "错误:{message}",
testRequestSuccess: "测试请求成功",
responseSuffix: ",返回:{response}",
searchKeyword: "查询关键字",
search: "查询",
refreshOptions: "刷新选项",
getOptionsError: "获取选项出错:{message}",
getDataSuccessSelect: "获取数据成功,请从下拉框中选择",
getDataSuccessEmpty: "获取数据成功,没有数据",
domainSearchPlaceholder: "这里可以搜索域名(数据来自“设置->域名管理”),您也可以直接在上面输入框输入",
importDomain: "导入域名",
manageDomain: "管理域名",
refreshMyDomains: "刷新我的域名列表",
upload: "上传",
uploadCert: "上传证书",
certName: "证书名称",
certNameHelper: "上传后证书显示名称",
uploadSuccess: "上传成功",
selectCertFirst: "请先选择域名证书",
targetStepNotFound: "找不到目标步骤,请先选择域名证书",
},
captcha: {
inputImageCode: "请输入图形验证码",
refresh: "刷新验证码",
clickToVerify: "点击验证",
verifySuccess: "验证成功",
verifyFailed: "验证失败,请重试",
loadFailed: "加载验证码失败",
notLoaded: "验证码未加载完成",
},
}; };
@@ -7,8 +7,13 @@ export default {
settingLink: "站点监控设置", settingLink: "站点监控设置",
limitInfo: "基础版限制1条,专业版以上无限制,当前", limitInfo: "基础版限制1条,专业版以上无限制,当前",
checkAll: "检查全部", checkAll: "检查全部",
checkNow: "立即检查",
batchDelete: "批量删除",
confirmTitle: "确认", confirmTitle: "确认",
confirmContent: "确认触发检查全部站点证书吗?", confirmContent: "确认触发检查全部站点证书吗?",
batchDeleteConfirm: "确定要批量删除这{count}条记录吗",
deleteSuccess: "删除成功",
selectRecordsFirst: "请先勾选记录",
checkSubmitted: "检查任务已提交", checkSubmitted: "检查任务已提交",
pleaseRefresh: "请稍后刷新页面查看结果", pleaseRefresh: "请稍后刷新页面查看结果",
siteName: "站点名称", siteName: "站点名称",
@@ -53,6 +58,10 @@ export default {
certInfoId: "证书ID", certInfoId: "证书ID",
checkSubmittedRefresh: "检查任务已提交,请稍后刷新查看结果", checkSubmittedRefresh: "检查任务已提交,请稍后刷新查看结果",
ipManagement: "IP管理", ipManagement: "IP管理",
siteIpMonitor: "站点IP监控",
ipList: "IP列表",
ipListHelper: "IP或者CNAME域名,一行一个",
enterImportIpOrDomain: "请输入要导入的IP或域名",
bulkImport: "批量导入", bulkImport: "批量导入",
basicLimitError: "基础版只能添加一个监控站点,请赞助升级专业版", basicLimitError: "基础版只能添加一个监控站点,请赞助升级专业版",
limitExceeded: "对不起,您最多只能创建条{max}监控记录,请购买或升级套餐", limitExceeded: "对不起,您最多只能创建条{max}监控记录,请购买或升级套餐",
@@ -89,15 +89,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
form: { form: {
async beforeSubmit({ form }) { async beforeSubmit({ form }) {
if (form.challengeType === "cname") { if (form.challengeType === "cname") {
throw new Error("CNAME方式请前往CNAME记录页面进行管理"); throw new Error(t("certd.domain.cnameManagedInCnamePage"));
} }
if (form.challengeType === "dns") { if (form.challengeType === "dns") {
const isSubdomain = await api.IsSubdomain({ domain: form.domain }); const isSubdomain = await api.IsSubdomain({ domain: form.domain });
if (isSubdomain && !subdomainConfirmed.value) { if (isSubdomain && !subdomainConfirmed.value) {
Modal.confirm({ Modal.confirm({
title: "子域名确认", title: t("certd.domain.subdomainConfirmTitle"),
content: `检测到${form.domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?`, content: t("certd.domain.subdomainConfirmContent", { domain: form.domain }),
okText: "确认", okText: t("common.confirm"),
okType: "danger", okType: "danger",
onOk: () => { onOk: () => {
subdomainConfirmed.value = true; subdomainConfirmed.value = true;
@@ -119,9 +119,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
import: { import: {
show: hasActionPermission("write"), show: hasActionPermission("write"),
title: "从域名提供商导入域名", title: t("certd.domain.importFromProvider"),
type: "primary", type: "primary",
text: "从域名提供商导入", text: t("certd.domain.importFromProvider"),
needPlus: true, needPlus: true,
color: "gold", color: "gold",
icon: "mingcute:vip-1-line", icon: "mingcute:vip-1-line",
@@ -135,14 +135,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
syncExpirationDate: { syncExpirationDate: {
show: hasActionPermission("write"), show: hasActionPermission("write"),
title: "同步域名过期时间", title: t("certd.domain.syncExpirationDate"),
type: "primary", type: "primary",
icon: "ion:refresh-outline", icon: "ion:refresh-outline",
text: "同步域名过期时间", text: t("certd.domain.syncExpirationDate"),
click: async () => { click: async () => {
await api.SyncExpirationStart(); await api.SyncExpirationStart();
notification.success({ notification.success({
message: "同步任务已提交", message: t("certd.domain.syncTaskSubmitted"),
}); });
setTimeout(() => { setTimeout(() => {
crudExpose.doRefresh(); crudExpose.doRefresh();
@@ -151,10 +151,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
monitorSettingSave: { monitorSettingSave: {
show: hasActionPermission("write"), show: hasActionPermission("write"),
title: "域名过期监控设置", title: t("certd.domain.expirationMonitorSetting"),
type: "primary", type: "primary",
icon: "ion:save-outline", icon: "ion:save-outline",
text: "域名过期监控设置", text: t("certd.domain.expirationMonitorSetting"),
click: async () => { click: async () => {
router.push({ router.push({
path: "/certd/cert/domain/setting", path: "/certd/cert/domain/setting",
@@ -184,7 +184,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
form: { form: {
required: true, required: true,
helper: "注意:DNS校验方式下,子域名不需要在此处维护,否则会影响证书申请(子域名托管或免费二级域名除外)", helper: t("certd.domain.subdomainDnsHelper"),
}, },
editForm: { editForm: {
component: { component: {
@@ -361,7 +361,7 @@ 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.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 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> <fs-values-format class={"ml-5"} modelValue={row.httpUploaderAccess} dict={accessDict} color={"auto"}></fs-values-format>
<a-tag class={"ml-5 flex items-center"}>{row.httpUploadRootDir}</a-tag> <a-tag class={"ml-5 flex items-center"}>{t("certd.domain.path")}: {row.httpUploadRootDir}</a-tag>
</div> </div>
); );
} }
@@ -1,16 +1,16 @@
<template> <template>
<div class="domain-import-task-status min-h-[300px]"> <div class="domain-import-task-status min-h-[300px]">
<div class="action mb-5"> <div class="action mb-5">
<fs-button type="primary" icon="mingcute:vip-1-line" @click="addTask">添加导入任务</fs-button> <fs-button type="primary" icon="mingcute:vip-1-line" @click="addTask">{{ t("certd.domain.addImportTask") }}</fs-button>
<fs-button type="primary" icon="ion:refresh-outline" class="ml-2" @click="loadImportTaskStatus">刷新</fs-button> <fs-button type="primary" icon="ion:refresh-outline" class="ml-2" @click="loadImportTaskStatus">{{ t("certd.domain.refresh") }}</fs-button>
</div> </div>
<div class="table-container overflow-auto mb-10"> <div class="table-container overflow-auto mb-10">
<table class="cd-table border-gray-300 w-full"> <table class="cd-table border-gray-300 w-full">
<thead> <thead>
<tr> <tr>
<th class="w-[220px]">来源</th> <th class="w-[220px]">{{ t("certd.sourcee") }}</th>
<th class="">进度</th> <th class="">{{ t("certd.domain.progress") }}</th>
<th class="w-[220px]">操作</th> <th class="w-[220px]">{{ t("certd.domain.operation") }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -27,23 +27,23 @@
<td> <td>
<div v-if="item.task"> <div v-if="item.task">
<div> <div>
<a-tag color="blue">总数{{ item.task?.total }}</a-tag> <a-tag color="blue">{{ t("certd.domain.total") }}{{ item.task?.total }}</a-tag>
<a-tag color="success" class="ml-2">成功{{ item.task?.successCount }}</a-tag> <a-tag color="success" class="ml-2">{{ t("certd.success") }}{{ item.task?.successCount }}</a-tag>
<a-tag type="info" class="ml-2">跳过{{ item.task?.skipCount }}</a-tag> <a-tag type="info" class="ml-2">{{ t("certd.domain.skipped") }}{{ item.task?.skipCount }}</a-tag>
<a-tooltip v-if="item.task?.errors.length > 0"> <a-tooltip v-if="item.task?.errors.length > 0">
<template #title> <template #title>
<div v-for="error in item.task?.errors" :key="error">{{ error }}</div> <div v-for="error in item.task?.errors" :key="error">{{ error }}</div>
</template> </template>
<a-tag color="red" class="ml-2">失败{{ item.task?.errors.length }}</a-tag> <a-tag color="red" class="ml-2">{{ t("certd.domain.failed") }}{{ item.task?.errors.length }}</a-tag>
</a-tooltip> </a-tooltip>
</div> </div>
<a-progress :percent="item.task?.progress" size="small" status="active" /> <a-progress :percent="item.task?.progress" size="small" status="active" />
</div> </div>
<div v-else>未执行</div> <div v-else>{{ t("certd.domain.notExecuted") }}</div>
</td> </td>
<td> <td>
<fs-button type="primary" icon="ion:play-outline" :disabled="item.task?.status === 'running'" @click="startTask(item)">执行</fs-button> <fs-button type="primary" icon="ion:play-outline" :disabled="item.task?.status === 'running'" @click="startTask(item)">{{ t("certd.domain.execute") }}</fs-button>
<fs-button type="primary" class="ml-2" danger icon="ion:trash-outline" @click="deleteTask(item)">删除</fs-button> <fs-button type="primary" class="ml-2" danger icon="ion:trash-outline" @click="deleteTask(item)">{{ t("certd.domain.delete") }}</fs-button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -58,11 +58,13 @@ import { onMounted, onUnmounted, ref } from "vue";
import * as api from "./api"; import * as api from "./api";
import { useDomainImport } from "./use"; import { useDomainImport } from "./use";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { useI18n } from "/@/locales";
defineOptions({ defineOptions({
name: "DomainImportTaskStatus", name: "DomainImportTaskStatus",
}); });
const list = ref([]); const list = ref([]);
const { t } = useI18n();
async function loadImportTaskStatus() { async function loadImportTaskStatus() {
const res = await api.ImportTaskStatus(); const res = await api.ImportTaskStatus();
@@ -77,8 +79,8 @@ async function startTask(item: any) {
async function deleteTask(item: any) { async function deleteTask(item: any) {
Modal.confirm({ Modal.confirm({
title: "确认删除吗?", title: t("certd.domain.confirmDelete"),
okText: "确认", okText: t("common.confirm"),
okType: "danger", okType: "danger",
onOk: async () => { onOk: async () => {
await api.ImportTaskDelete(item.key); await api.ImportTaskDelete(item.key);
@@ -5,12 +5,14 @@ import { compute } from "@fast-crud/fast-crud";
import { Dicts } from "/@/components/plugins/lib/dicts"; import { Dicts } from "/@/components/plugins/lib/dicts";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import DomainImportTaskStatus from "./import.vue"; import DomainImportTaskStatus from "./import.vue";
import { useI18n } from "/@/locales";
export function useDomainImport() { export function useDomainImport() {
const { openFormDialog } = useFormDialog(); const { openFormDialog } = useFormDialog();
const { t } = useI18n();
const columns = { const columns = {
dnsProviderType: { dnsProviderType: {
title: "域名提供商", title: t("certd.domain.domainProvider"),
type: "text", type: "text",
form: { form: {
component: { component: {
@@ -29,14 +31,14 @@ export function useDomainImport() {
}, },
}, },
dnsProviderAccessType: { dnsProviderAccessType: {
title: "域名提供商访问类型", title: t("certd.domain.domainProviderAccessType"),
type: "text", type: "text",
form: { form: {
show: false, show: false,
}, },
}, },
dnsProviderAccessId: { dnsProviderAccessId: {
title: "域名提供商授权", title: t("certd.domain.domainProviderAccess"),
type: "text", type: "text",
form: { form: {
component: { component: {
@@ -52,7 +54,7 @@ export function useDomainImport() {
return function openDomainImportDialog(req: { afterSubmit?: (res?: any) => void; form?: any }) { return function openDomainImportDialog(req: { afterSubmit?: (res?: any) => void; form?: any }) {
openFormDialog({ openFormDialog({
title: "从域名提供商导入域名", title: t("certd.domain.importFromProvider"),
columns: columns, columns: columns,
initialForm: { initialForm: {
...req.form, ...req.form,
@@ -73,10 +75,11 @@ export function useDomainImport() {
export function useDomainImportManage() { export function useDomainImportManage() {
const { openFormDialog } = useFormDialog(); const { openFormDialog } = useFormDialog();
const { t } = useI18n();
const settingStore = useSettingStore(); const settingStore = useSettingStore();
return async function openDomainImportManageDialog(req: { afterSubmit?: (res?: any) => void; form?: any; zIndex?: number }) { return async function openDomainImportManageDialog(req: { afterSubmit?: (res?: any) => void; form?: any; zIndex?: number }) {
await openFormDialog({ await openFormDialog({
title: "从域名提供商导入域名", title: t("certd.domain.importFromProvider"),
body: () => { body: () => {
return <DomainImportTaskStatus />; return <DomainImportTaskStatus />;
}, },
@@ -79,9 +79,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
import: { import: {
show: hasActionPermission("write"), show: hasActionPermission("write"),
title: "导入CNAME记录", title: t("certd.cname.importRecords"),
type: "primary", type: "primary",
text: "批量导入", text: t("certd.cname.batchImport"),
icon: "mingcute:vip-1-line", icon: "mingcute:vip-1-line",
click: () => { click: () => {
settingStore.checkPlus(); settingStore.checkPlus();
@@ -95,9 +95,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
export: { export: {
title: "导出CNAME记录之后,可用于批量导入cname解析到域名注册商", title: t("certd.cname.exportRecordsTip"),
type: "primary", type: "primary",
text: "批量导出", text: t("certd.cname.batchExport"),
icon: "mingcute:vip-1-line", icon: "mingcute:vip-1-line",
click: () => { click: () => {
settingStore.checkPlus(); settingStore.checkPlus();
@@ -235,8 +235,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
cellRender({ value, row }) { cellRender({ value, row }) {
async function resetStatus() { async function resetStatus() {
Modal.confirm({ Modal.confirm({
title: "重置状态", title: t("certd.cname.resetStatus"),
content: "确定要重置校验状态吗?", content: t("certd.cname.confirmResetStatus"),
onOk: async () => { onOk: async () => {
await api.ResetStatus(row.id); await api.ResetStatus(row.id);
await crudExpose.doRefresh(); await crudExpose.doRefresh();
@@ -253,7 +253,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
)} )}
{row.status === "valid" && ( {row.status === "valid" && (
<a-tooltip title={"重置校验状态,重新校验"}> <a-tooltip title={t("certd.cname.resetStatusTooltip")}>
<fs-icon class={"ml-5 pointer "} icon="solar:undo-left-square-bold" onClick={resetStatus}></fs-icon> <fs-icon class={"ml-5 pointer "} icon="solar:undo-left-square-bold" onClick={resetStatus}></fs-icon>
</a-tooltip> </a-tooltip>
)} )}
@@ -2,6 +2,7 @@ import { dict } from "@fast-crud/fast-crud";
import { message } from "ant-design-vue"; import { message } from "ant-design-vue";
import * as api from "./api"; import * as api from "./api";
import { useFormDialog } from "/@/use/use-dialog"; import { useFormDialog } from "/@/use/use-dialog";
import { useI18n } from "/@/locales";
export const cnameProviderDict = dict({ export const cnameProviderDict = dict({
url: "/cname/provider/list", url: "/cname/provider/list",
@@ -10,10 +11,11 @@ export const cnameProviderDict = dict({
}); });
export function useCnameImport() { export function useCnameImport() {
const { openFormDialog } = useFormDialog(); const { openFormDialog } = useFormDialog();
const { t } = useI18n();
const columns = { const columns = {
domainList: { domainList: {
title: "域名列表", title: t("certd.cname.domainList"),
type: "text", type: "text",
form: { form: {
component: { component: {
@@ -24,11 +26,11 @@ export function useCnameImport() {
span: 24, span: 24,
}, },
required: true, required: true,
helper: "每个域名一行,批量导入\n泛域名请去掉*.\n已经存在的会自动跳过", helper: t("certd.cname.domainListHelper"),
}, },
}, },
cnameProviderId: { cnameProviderId: {
title: "CNAME服务", title: t("certd.cname.cnameService"),
type: "dict-select", type: "dict-select",
dict: cnameProviderDict, dict: cnameProviderDict,
form: { form: {
@@ -39,14 +41,14 @@ export function useCnameImport() {
return function openCnameImportDialog(req: { afterSubmit?: () => void }) { return function openCnameImportDialog(req: { afterSubmit?: () => void }) {
openFormDialog({ openFormDialog({
title: "导入CNAME记录", title: t("certd.cname.importRecords"),
columns: columns, columns: columns,
onSubmit: async (form: any) => { onSubmit: async (form: any) => {
await api.Import({ await api.Import({
domainList: form.domainList, domainList: form.domainList,
cnameProviderId: form.cnameProviderId, cnameProviderId: form.cnameProviderId,
}); });
message.success("导入任务已提交"); message.success(t("certd.cname.importTaskSubmitted"));
if (req.afterSubmit) { if (req.afterSubmit) {
req.afterSubmit(); req.afterSubmit();
} }
@@ -69,17 +69,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const handleBatchDelete = () => { const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) { if (selectedRowKeys.value?.length > 0) {
Modal.confirm({ Modal.confirm({
title: "确认", title: t("monitor.confirmTitle"),
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`, content: t("monitor.batchDeleteConfirm", { count: selectedRowKeys.value.length }),
async onOk() { async onOk() {
await api.BatchDelObj(selectedRowKeys.value); await api.BatchDelObj(selectedRowKeys.value);
message.info("删除成功"); message.info(t("monitor.deleteSuccess"));
crudExpose.doRefresh(); crudExpose.doRefresh();
selectedRowKeys.value = []; selectedRowKeys.value = [];
}, },
}); });
} else { } else {
message.error("请先勾选记录"); message.error(t("monitor.selectRecordsFirst"));
} }
}; };
@@ -265,7 +265,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "link", type: "link",
text: null, text: null,
tooltip: { tooltip: {
title: "立即检查", title: t("monitor.checkNow"),
}, },
icon: "ion:play-sharp", icon: "ion:play-sharp",
click: async ({ row }) => { click: async ({ row }) => {
@@ -17,7 +17,7 @@
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> <fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left> <template #pagination-left>
<a-tooltip title="批量删除"> <a-tooltip :title="t('monitor.batchDelete')">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button> <fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip> </a-tooltip>
</template> </template>
@@ -2,14 +2,16 @@ import { useFormWrapper } from "@fast-crud/fast-crud";
import SiteIpCertMonitor from "./index.vue"; import SiteIpCertMonitor from "./index.vue";
import { siteIpApi } from "/@/views/certd/monitor/site/ip/api"; import { siteIpApi } from "/@/views/certd/monitor/site/ip/api";
import { useI18n } from "/@/locales";
export function useSiteIpMonitor() { export function useSiteIpMonitor() {
const { openDialog, openCrudFormDialog } = useFormWrapper(); const { openDialog, openCrudFormDialog } = useFormWrapper();
const { t } = useI18n();
async function openSiteIpMonitorDialog(opts: { siteId: number }) { async function openSiteIpMonitorDialog(opts: { siteId: number }) {
await openDialog({ await openDialog({
wrapper: { wrapper: {
title: "站点IP监控", title: t("monitor.siteIpMonitor"),
width: "80%", width: "80%",
is: "a-modal", is: "a-modal",
footer: false, footer: false,
@@ -40,10 +42,10 @@ export function useSiteIpMonitor() {
columns: { columns: {
text: { text: {
type: "textarea", type: "textarea",
title: "IP列表", title: t("monitor.ipList"),
form: { form: {
helper: "IP或者CNAME域名,一行一个", helper: t("monitor.ipListHelper"),
rules: [{ required: true, message: "请输入要导入的IP或域名" }], rules: [{ required: true, message: t("monitor.enterImportIpOrDomain") }],
component: { component: {
placeholder: "192.168.1.2\ncname.foo.com", placeholder: "192.168.1.2\ncname.foo.com",
rows: 8, rows: 8,