Other translations

pull/436/head
Lorenzo 2025-06-25 23:52:44 +02:00
parent cdac12bb2f
commit daaef316e9
18 changed files with 1540 additions and 1328 deletions

View File

@ -1,21 +1,28 @@
<template> <template>
<div class="cron-editor"> <div class="cron-editor">
<div class="flex-o"> <div class="flex-o">
<cron-light :disabled="disabled" :readonly="readonly" :period="period" class="flex-o cron-ant" locale="zh-CN" format="quartz" :model-value="modelValue" @update:model-value="onUpdate" @error="onError" /> <cron-light :disabled="disabled" :readonly="readonly" :period="period" class="flex-o cron-ant"
locale="zh-CN" format="quartz" :model-value="modelValue" @update:model-value="onUpdate"
@error="onError" />
</div> </div>
<div class="mt-5 flex"> <div class="mt-5 flex">
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input> <a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
<fs-icon icon="ion:close-circle" class="pointer fs-16 ml-5 color-gray" title="清除选择" @click="onClear"></fs-icon> <fs-icon icon="ion:close-circle" class="pointer fs-16 ml-5 color-gray" :title="t('certd.cron.clearTip')"
@click="onClear"></fs-icon>
</div> </div>
<div class="helper">下次触发时间{{ nextTime }}</div> <div class="helper">{{ t('certd.cron.nextTrigger') }}{{ nextTime }}</div>
<div class="fs-helper">{{ errorMessage }}</div> <div class="fs-helper">{{ errorMessage }}</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import parser from "cron-parser"; import parser from "cron-parser";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { getCronNextTimes } from "/@/components/cron-editor/utils"; import { getCronNextTimes } from "/@/components/cron-editor/utils";
defineOptions({ defineOptions({
name: "CronEditor", name: "CronEditor",
@ -83,7 +90,7 @@ const onClear = () => {
const nextTime = computed(() => { const nextTime = computed(() => {
if (props.modelValue == null) { if (props.modelValue == null) {
return "请先设置正确的cron表达式"; return t("certd.cron.tip");
} }
try { try {
@ -91,26 +98,31 @@ const nextTime = computed(() => {
return nextTimes.join(""); return nextTimes.join("");
} catch (e) { } catch (e) {
console.log(e); console.log(e);
return "请先设置正确的cron表达式"; return t("certd.cron.tip");
} }
}); });
</script> </script>
<style lang="less"> <style lang="less">
.cron-editor { .cron-editor {
.cron-ant { .cron-ant {
flex-wrap: wrap; flex-wrap: wrap;
&* > {
&*> {
margin-bottom: 2px; margin-bottom: 2px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.vcron-select-list { .vcron-select-list {
min-width: 56px; min-width: 56px;
} }
.vcron-select-input { .vcron-select-input {
min-height: 22px; min-height: 22px;
background-color: #fff; background-color: #fff;
} }
.vcron-select-container { .vcron-select-container {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -185,4 +185,93 @@ export default {
download: { download: {
title: "Download Certificate" title: "Download Certificate"
}, },
source: "Source Code",
github: "GitHub",
gitee: "Gitee",
cron: {
clearTip: "Clear Selection",
nextTrigger: "Next Trigger Time",
tip: "Please set a valid cron expression first"
},
cronForm: {
title: "Scheduled Script",
helper: "Click the button above to select the time for daily execution.\nIt is recommended to run once a day. Tasks will be skipped if the certificate is not expiring.",
required: "This field is required"
},
email: {
title: "Recipient Email",
helper: "Enter your recipient email addresses. Multiple addresses are supported.",
required: "This field is required"
},
plugin: {
selectTitle: "Certificate Apply Plugin",
jsAcme: "JS-ACME: Easy to use, powerful features [Recommended]",
legoAcme: "Lego-ACME: Based on Lego, supports a wide range of DNS providers, suitable for users familiar with Lego"
},
pipelineForm: {
createTitle: "Create Certificate Pipeline",
moreParams: "More Parameters",
triggerCronTitle: "Scheduled Trigger",
triggerCronHelper:
"Click the button above to choose a daily execution time.\nIt is recommended to trigger once per day. The task will be skipped if the certificate has not expired and will not be executed repeatedly.",
notificationTitle: "Failure Notification",
notificationHelper: "Get real-time alerts when the task fails",
groupIdTitle: "Pipeline Group"
},
notificationDefault: "Use Default Notification",
monitor: {
title: "Site Certificate Monitoring",
description: "Check website certificates' expiration at 0:00 daily; reminders sent 10 days before expiration (using default notification channel);",
settingLink: "Site Monitoring Settings",
limitInfo: "Basic edition limited to 1, professional and above unlimited, current",
checkAll: "Check All",
confirmTitle: "Confirm",
confirmContent: "Confirm to trigger check for all site certificates?",
checkSubmitted: "Check task submitted",
pleaseRefresh: "Please refresh the page later to see the results",
siteName: "Site Name",
enterSiteName: "Please enter the site name",
domain: "Domain",
enterDomain: "Please enter the domain",
enterValidDomain: "Please enter a valid domain",
httpsPort: "HTTPS Port",
enterPort: "Please enter the port",
certInfo: "Certificate Info",
issuer: "Issuer",
certDomains: "Certificate Domains",
certProvider: "Issuer",
certStatus: "Certificate Status",
status: {
ok: "Valid",
expired: "Expired",
},
certExpiresTime: "Certificate Expiration",
expired: "expired",
days: "days",
lastCheckTime: "Last Check Time",
disabled: "Enable/Disable",
ipCheck: "Enable IP Check",
selectRequired: "Please select",
ipCheckConfirm: "Are you sure to {status} IP check?",
ipCount: "IP Count",
checkStatus: "Check Status",
pipelineId: "Linked Pipeline ID",
certInfoId: "Certificate ID",
checkSubmittedRefresh: "Check task submitted. Please refresh later to view the result.",
ipManagement: "IP Management",
bulkImport: "Bulk Import",
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.",
},
checkStatus: {
success: "Success",
checking: "Checking",
error: "Error",
},
domainList: {
title: "Domain List",
helper: "Format: domain:port:name, one per line. Port and name are optional.\nExamples:\nwww.baidu.com:443:Baidu\nwww.taobao.com::Taobao\nwww.google.com",
required: "Please enter domains to import",
placeholder: "www.baidu.com:443:Baidu\nwww.taobao.com::Taobao\nwww.google.com\n",
},
}; };

View File

@ -1,22 +0,0 @@
{
"back": "Back",
"backToHome": "Back To Home",
"login": "Login",
"logout": "Logout",
"prompt": "Prompt",
"cancel": "Cancel",
"confirm": "Confirm",
"reset": "Reset",
"noData": "No Data",
"refresh": "Refresh",
"loadingMenu": "Loading Menu",
"query": "Search",
"search": "Search",
"enabled": "Enabled",
"disabled": "Disabled",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"yes": "Yes",
"no": "No"
}

View File

@ -0,0 +1,22 @@
export default {
"back": "Back",
"backToHome": "Back To Home",
"login": "Login",
"logout": "Logout",
"prompt": "Prompt",
"cancel": "Cancel",
"confirm": "Confirm",
"reset": "Reset",
"noData": "No Data",
"refresh": "Refresh",
"loadingMenu": "Loading Menu",
"query": "Search",
"search": "Search",
"enabled": "Enabled",
"disabled": "Disabled",
"edit": "Edit",
"delete": "Delete",
"create": "Create",
"yes": "Yes",
"no": "No"
}

View File

@ -5,6 +5,7 @@ import tutorial from './tutorial';
import preferences from './preferences'; import preferences from './preferences';
import ui from './ui'; import ui from './ui';
import guide from './guide'; import guide from './guide';
import common from './common';
export default { export default {
certd, certd,
@ -13,5 +14,6 @@ export default {
ui, ui,
tutorial, tutorial,
preferences, preferences,
guide guide,
common
}; };

View File

@ -191,4 +191,93 @@ export default {
download: { download: {
title: "下载证书" title: "下载证书"
}, },
source: "源码",
github: "github",
gitee: "gitee",
cron: {
clearTip: "清除选择",
nextTrigger: "下次触发时间",
tip: "请先设置正确的cron表达式"
},
cronForm: {
title: "定时脚本",
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行",
required: "此项必填"
},
email: {
title: "收件邮箱",
helper: "输入你的收件邮箱地址,支持多个邮箱",
required: "此项必填"
},
plugin: {
selectTitle: "证书申请插件",
jsAcme: "JS-ACME使用简单方便功能强大【推荐】",
legoAcme: "Lego-ACME基于Lego实现支持海量DNS提供商熟悉LEGO的用户可以使用"
},
pipelineForm: {
createTitle: "创建证书流水线",
moreParams: "更多参数",
triggerCronTitle: "定时触发",
triggerCronHelper:
"点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行",
notificationTitle: "失败通知",
notificationHelper: "任务执行失败实时提醒",
groupIdTitle: "流水线分组"
},
notificationDefault: "使用默认通知",
monitor: {
title: "站点证书监控",
description: "每天0点检查网站证书的过期时间到期前10天时将发出提醒使用默认通知渠道;",
settingLink: "站点监控设置",
limitInfo: "基础版限制1条专业版以上无限制当前",
checkAll: "检查全部",
confirmTitle: "确认",
confirmContent: "确认触发检查全部站点证书吗?",
checkSubmitted: "检查任务已提交",
pleaseRefresh: "请稍后刷新页面查看结果",
siteName: "站点名称",
enterSiteName: "请输入站点名称",
domain: "网站域名",
enterDomain: "请输入域名",
enterValidDomain: "请输入正确的域名",
httpsPort: "HTTPS端口",
enterPort: "请输入端口",
certInfo: "证书信息",
issuer: "证书颁发机构",
certDomains: "证书域名",
certProvider: "颁发机构",
certStatus: "证书状态",
status: {
ok: "正常",
expired: "过期",
},
certExpiresTime: "证书到期时间",
expired: "过期",
days: "天",
lastCheckTime: "上次检查时间",
disabled: "禁用启用",
ipCheck: "开启IP检查",
selectRequired: "请选择",
ipCheckConfirm: "确定{status}IP检查",
ipCount: "IP数量",
checkStatus: "检查状态",
pipelineId: "关联流水线ID",
certInfoId: "证书ID",
checkSubmittedRefresh: "检查任务已提交,请稍后刷新查看结果",
ipManagement: "IP管理",
bulkImport: "批量导入",
basicLimitError: "基础版只能添加一个监控站点,请赞助升级专业版",
limitExceeded: "对不起,您最多只能创建条{max}监控记录,请购买或升级套餐",
},
checkStatus: {
success: "成功",
checking: "检查中",
error: "异常",
},
domainList: {
title: "域名列表",
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com",
required: "请输入要导入的域名",
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n",
},
}; };

View File

@ -1,22 +0,0 @@
{
"back": "返回",
"backToHome": "返回首页",
"login": "登录",
"logout": "退出登录",
"prompt": "提示",
"cancel": "取消",
"confirm": "确认",
"reset": "重置",
"noData": "暂无数据",
"refresh": "刷新",
"loadingMenu": "加载菜单中",
"query": "查询",
"search": "搜索",
"enabled": "已启用",
"disabled": "已禁用",
"edit": "修改",
"delete": "删除",
"create": "新增",
"yes": "是",
"no": "否"
}

View File

@ -0,0 +1,22 @@
export default {
"back": "返回",
"backToHome": "返回首页",
"login": "登录",
"logout": "退出登录",
"prompt": "提示",
"cancel": "取消",
"confirm": "确认",
"reset": "重置",
"noData": "暂无数据",
"refresh": "刷新",
"loadingMenu": "加载菜单中",
"query": "查询",
"search": "搜索",
"enabled": "已启用",
"disabled": "已禁用",
"edit": "修改",
"delete": "删除",
"create": "新增",
"yes": "是",
"no": "否"
}

View File

@ -5,6 +5,7 @@ import tutorial from './tutorial';
import preferences from './preferences'; import preferences from './preferences';
import ui from './ui'; import ui from './ui';
import guide from './guide'; import guide from './guide';
import common from './common';
export default { export default {
certd, certd,
@ -13,5 +14,6 @@ export default {
ui, ui,
tutorial, tutorial,
preferences, preferences,
guide guide,
common
}; };

View File

@ -1,13 +1,15 @@
import i18n from '/@/locales/i18n';
export const headerResource = [ export const headerResource = [
{ {
title: "文档", title: i18n.global.t("certd.helpDoc"),
path: "https://certd.docmirror.cn", path: "https://certd.docmirror.cn",
meta: { meta: {
icon: "ion:document-text-outline" icon: "ion:document-text-outline"
} }
}, },
{ {
title: "源码", title: i18n.global.t("certd.source"),
name: "source", name: "source",
key: "source", key: "source",
meta: { meta: {
@ -15,14 +17,14 @@ export const headerResource = [
}, },
children: [ children: [
{ {
title: "github", title: i18n.global.t("certd.github"),
path: "https://github.com/certd/certd", path: "https://github.com/certd/certd",
meta: { meta: {
icon: "ion:logo-github" icon: "ion:logo-github"
} }
}, },
{ {
title: "gitee", title: i18n.global.t("certd.gitee"),
path: "https://gitee.com/certd/certd", path: "https://gitee.com/certd/certd",
meta: { meta: {
icon: "ion:logo-octocat" icon: "ion:logo-octocat"
@ -31,3 +33,4 @@ export const headerResource = [
] ]
} }
]; ];

View File

@ -2,16 +2,18 @@ import { IFrameView } from "/@/vben/layouts";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { computed } from "vue"; import { computed } from "vue";
import TutorialButton from "/@/components/tutorial/index.vue"; import TutorialButton from "/@/components/tutorial/index.vue";
import i18n from '/@/locales/i18n';
export const aboutResource = [ export const aboutResource = [
{ {
title: "文档", title: i18n.global.t("certd.dashboard.helpDoc"),
name: "document", name: "document",
path: "/about/doc", path: "/about/doc",
component: IFrameView, component: IFrameView,
meta: { meta: {
icon: "lucide:book-open-text", icon: "lucide:book-open-text",
link: "https://certd.docmirror.cn", link: "https://certd.docmirror.cn",
title: "文档", title: i18n.global.t("certd.dashboard.helpDoc"),
order: 9999, order: 9999,
show: () => { show: () => {
const settingStore = useSettingStore(); const settingStore = useSettingStore();

View File

@ -38,12 +38,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const checkStatusDict = dict({ const checkStatusDict = dict({
data: [ data: [
{ label: "成功", value: "ok", color: "green" }, { label: t("checkStatus.success"), value: "ok", color: "green" },
{ label: "检查中", value: "checking", color: "blue" }, { label: t("checkStatus.checking"), value: "checking", color: "blue" },
{ label: "异常", value: "error", color: "red" }, { label: t("checkStatus.error"), value: "error", color: "red" },
], ],
}); });
const { openSiteIpMonitorDialog } = useSiteIpMonitor(); const { openSiteIpMonitorDialog } = useSiteIpMonitor();
const { openSiteImportDialog } = useSiteImport(); const { openSiteImportDialog } = useSiteImport();
return { return {
@ -74,35 +75,37 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
add: { add: {
async click() { async click() {
if (!settingsStore.isPlus) { if (!settingsStore.isPlus) {
//非plus // 非plus
if (crudBinding.value.data.length >= 1) { if (crudBinding.value.data.length >= 1) {
notification.error({ notification.error({
message: "基础版只能添加一个监控站点,请赞助升级专业版", message: t("certd.monitor.basicLimitError"),
}); });
mitter.emit("openVipModal"); mitter.emit("openVipModal");
return; return;
} }
} }
//检查是否监控站点数量超出限制 //检查是否监控站点数量超出限制
if (settingsStore.isComm && settingsStore.suiteSetting.enabled) { if (settingsStore.isComm && settingsStore.suiteSetting.enabled) {
//检查数量是否超限 // 检查数量是否超限
const suiteDetail = await mySuiteApi.SuiteDetailGet(); const suiteDetail = await mySuiteApi.SuiteDetailGet();
const max = suiteDetail.monitorCount.max; const max = suiteDetail.monitorCount.max;
if (max != -1 && max <= suiteDetail.monitorCount.used) { if (max != -1 && max <= suiteDetail.monitorCount.used) {
notification.error({ notification.error({
message: `对不起,您最多只能创建条${max}监控记录,请购买或升级套餐`, message: t("certd.monitor.limitExceeded", { max }),
}); });
return; return;
} }
} }
await crudExpose.openAdd({}); await crudExpose.openAdd({});
}, },
}, },
//导入按钮 //导入按钮
import: { import: {
show: true, show: true,
text: "批量导入", text: t("certd.monitor.bulkImport"),
type: "primary", type: "primary",
async click() { async click() {
openSiteImportDialog({ openSiteImportDialog({
@ -130,7 +133,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
await api.DoCheck(row.id); await api.DoCheck(row.id);
await crudExpose.doRefresh(); await crudExpose.doRefresh();
notification.success({ notification.success({
message: "检查任务已提交,请稍后刷新查看结果", message: t("certd.monitor.checkSubmittedRefresh"),
}); });
}, },
}, },
@ -138,11 +141,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
order: 10, order: 10,
type: "link", type: "link",
text: null, text: null,
show: compute(({ row }) => { show: compute(({ row }) => row.ipCheck === true),
return row.ipCheck === true;
}),
tooltip: { tooltip: {
title: "IP管理", title: t("certd.monitor.ipManagement"),
}, },
icon: "entypo:address", icon: "entypo:address",
click: async ({ row }) => { click: async ({ row }) => {
@ -172,29 +173,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
name: { name: {
title: "站点名称", title: t("certd.monitor.siteName"),
search: { search: {
show: true, show: true,
}, },
type: "text", type: "text",
form: { form: {
rules: [{ required: true, message: "请输入站点名称" }], rules: [{ required: true, message: t("certd.monitor.enterSiteName") }],
}, },
column: { column: {
width: 160, width: 160,
}, },
}, },
domain: { domain: {
title: "网站域名", title: t("certd.monitor.domain"),
search: { search: {
show: true, show: true,
}, },
type: "text", type: "text",
form: { form: {
rules: [ rules: [
{ required: true, message: "请输入域名" }, { required: true, message: t("certd.monitor.enterDomain") },
//@ts-ignore // @ts-ignore
{ type: "domains", message: "请输入正确的域名" }, { type: "domains", message: t("certd.monitor.enterValidDomain") },
], ],
}, },
column: { column: {
@ -215,14 +216,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
httpsPort: { httpsPort: {
title: "HTTPS端口", title: t("certd.monitor.httpsPort"),
search: { search: {
show: false, show: false,
}, },
type: "number", type: "number",
form: { form: {
value: 443, value: 443,
rules: [{ required: true, message: "请输入端口" }], rules: [{ required: true, message: t("certd.monitor.enterPort") }],
}, },
column: { column: {
width: 100, width: 100,
@ -230,7 +231,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
certInfo: { certInfo: {
title: "证书信息", title: t("certd.monitor.certInfo"),
type: "text", type: "text",
form: { show: false }, form: { show: false },
column: { column: {
@ -243,8 +244,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
content() { content() {
return ( return (
<div> <div>
<div>{row.certProvider}</div> <div>{t("certd.monitor.issuer")}: {row.certProvider}</div>
<div>{row.certDomains}</div> <div>{t("certd.monitor.certDomains")}: {row.certDomains}</div>
</div> </div>
); );
}, },
@ -258,7 +259,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
certDomains: { certDomains: {
title: "证书域名", title: t("certd.monitor.certDomains"),
search: { search: {
show: true, show: true,
}, },
@ -280,7 +281,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
certProvider: { certProvider: {
title: "颁发机构", title: t("certd.monitor.certProvider"),
search: { search: {
show: false, show: false,
}, },
@ -298,15 +299,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
certStatus: { certStatus: {
title: "证书状态", title: t("certd.monitor.certStatus"),
search: { search: {
show: true, show: true,
}, },
type: "dict-select", type: "dict-select",
dict: dict({ dict: dict({
data: [ data: [
{ label: "正常", value: "ok", color: "green" }, { label: t("certd.monitor.status.ok"), value: "ok", color: "green" },
{ label: "过期", value: "expired", color: "red" }, { label: t("certd.monitor.status.expired"), value: "expired", color: "red" },
], ],
}), }),
form: { form: {
@ -320,7 +321,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
certExpiresTime: { certExpiresTime: {
title: "证书到期时间", title: t("certd.monitor.certExpiresTime"),
search: { search: {
show: false, show: false,
}, },
@ -338,12 +339,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const leftDays = dayjs(value).diff(dayjs(), "day"); const leftDays = dayjs(value).diff(dayjs(), "day");
const color = leftDays < 20 ? "red" : "#389e0d"; const color = leftDays < 20 ? "red" : "#389e0d";
const percent = (leftDays / 90) * 100; const percent = (leftDays / 90) * 100;
return <a-progress title={expireDate + "过期"} percent={percent} strokeColor={color} format={(percent: number) => `${leftDays}`} />; return (
<a-progress
title={expireDate + t("certd.monitor.expired")}
percent={percent}
strokeColor={color}
format={(percent: number) => `${leftDays}${t("certd.monitor.days")}`}
/>
);
}, },
}, },
}, },
lastCheckTime: { lastCheckTime: {
title: "上次检查时间", title: t("certd.monitor.lastCheckTime"),
search: { search: {
show: false, show: false,
}, },
@ -357,15 +365,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
disabled: { disabled: {
title: "禁用启用", title: t("certd.monitor.disabled"),
search: { search: {
show: false, show: false,
}, },
type: "dict-switch", type: "dict-switch",
dict: dict({ dict: dict({
data: [ data: [
{ label: "启用", value: false, color: "green" }, { label: t("common.enabled"), value: false, color: "green" },
{ label: "禁用", value: true, color: "red" }, { label: t("common.disabled"), value: true, color: "red" },
], ],
}), }),
form: { form: {
@ -388,17 +396,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
ipCheck: { ipCheck: {
title: "开启IP检查", title: t("certd.monitor.ipCheck"),
type: "dict-switch", type: "dict-switch",
dict: dict({ dict: dict({
data: [ data: [
{ label: "启用", value: true, color: "green" }, { label: t("common.enabled"), value: true, color: "green" },
{ label: "禁用", value: false, color: "gray" }, { label: t("common.disabled"), value: false, color: "gray" },
], ],
}), }),
form: { form: {
value: false, value: false,
rules: [{ required: true, message: "请选择" }], rules: [{ required: true, message: t("certd.monitor.selectRequired") }],
}, },
column: { column: {
align: "center", align: "center",
@ -410,8 +418,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
on: { on: {
change({ row, $event }) { change({ row, $event }) {
Modal.confirm({ Modal.confirm({
title: "提示", title: t("common.confirm"),
content: `确定${$event ? "开启" : "关闭"}IP检查`, content: t("certd.monitor.ipCheckConfirm", { status: $event ? t("common.enabled") : t("common.disabled") }),
onOk: async () => { onOk: async () => {
await api.IpCheckChange(row.id, $event); await api.IpCheckChange(row.id, $event);
await crudExpose.doRefresh(); await crudExpose.doRefresh();
@ -426,10 +434,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
}, }
} as ColumnCompositionProps, } as ColumnCompositionProps,
ipCount: { ipCount: {
title: "IP数量", title: t("certd.monitor.ipCount"),
search: { search: {
show: false, show: false,
}, },
@ -444,7 +452,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
checkStatus: { checkStatus: {
title: "检查状态", title: t("certd.monitor.checkStatus"),
search: { search: {
show: false, show: false,
}, },
@ -457,7 +465,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 100, width: 100,
align: "center", align: "center",
sorter: true, sorter: true,
cellRender({ value, row, key }) { cellRender({ value, row }) {
return ( return (
<a-tooltip title={row.error}> <a-tooltip title={row.error}>
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format> <fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format>
@ -484,7 +492,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
// } // }
// }, // },
pipelineId: { pipelineId: {
title: "关联流水线id", title: t("certd.monitor.pipelineId"),
search: { search: {
show: false, show: false,
}, },
@ -497,7 +505,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
certInfoId: { certInfoId: {
title: "证书id", title: t("certd.monitor.certInfoId"),
search: { search: {
show: false, show: false,
}, },

View File

@ -2,47 +2,55 @@
<fs-page> <fs-page>
<template #header> <template #header>
<div class="title flex items-center"> <div class="title flex items-center">
站点证书监控 {{ t("certd.monitor.title") }}
<div class="sub flex-1"> <div class="sub flex-1">
<div> <div>
每天0点检查网站证书的过期时间到期前10天时将发出提醒使用默认通知渠道; {{ t("certd.monitor.description") }}
<router-link to="/certd/monitor/setting">站点监控设置</router-link> <router-link to="/certd/monitor/setting">{{ t("certd.monitor.settingLink") }}</router-link>
</div>
<div class="flex items-center">
{{ t("certd.monitor.limitInfo") }}
<vip-button class="ml-5" mode="nav"></vip-button>
</div> </div>
<div class="flex items-center">基础版限制1条专业版以上无限制当前<vip-button class="ml-5" mode="nav"></vip-button></div>
</div> </div>
</div> </div>
<div class="more"> <div class="more">
<a-button type="primary" @click="checkAll"></a-button> <a-button type="primary" @click="checkAll">{{ t("certd.monitor.checkAll") }}</a-button>
</div> </div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud> <fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
</fs-page> </fs-page>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onActivated, onMounted } from "vue"; import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { siteInfoApi } from "./api"; import { siteInfoApi } from "./api";
import { Modal, notification } from "ant-design-vue"; import { Modal, notification } from "ant-design-vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
defineOptions({ defineOptions({
name: "SiteCertMonitor", name: "SiteCertMonitor",
}); });
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} }); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
function checkAll() { function checkAll() {
Modal.confirm({ Modal.confirm({
title: "确认", title: t("certd.monitor.confirmTitle"), // ""
content: "确认触发检查全部站点证书吗?", content: t("certd.monitor.confirmContent"), // "?"
onOk: async () => { onOk: async () => {
await siteInfoApi.CheckAll(); await siteInfoApi.CheckAll();
notification.success({ notification.success({
message: "检查任务已提交", message: t("certd.monitor.checkSubmitted"), // ""
description: "请稍后刷新页面查看结果", description: t("certd.monitor.pleaseRefresh"), // ""
}); });
}, },
}); });
} }
// //
onMounted(() => { onMounted(() => {
crudExpose.doRefresh(); crudExpose.doRefresh();

View File

@ -1,7 +1,9 @@
import { useFormWrapper } from "@fast-crud/fast-crud"; import { useFormWrapper } from "@fast-crud/fast-crud";
import { siteInfoApi } from "./api"; import { siteInfoApi } from "./api";
import { useI18n } from "vue-i18n";
export function useSiteImport() { export function useSiteImport() {
const { t } = useI18n();
const { openCrudFormDialog } = useFormWrapper(); const { openCrudFormDialog } = useFormWrapper();
async function openSiteImportDialog(opts: { afterSubmit: any }) { async function openSiteImportDialog(opts: { afterSubmit: any }) {
@ -11,12 +13,12 @@ export function useSiteImport() {
columns: { columns: {
text: { text: {
type: "textarea", type: "textarea",
title: "域名列表", title: t("certd.domainList.title"), // 域名列表
form: { form: {
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com", helper: t("certd.domainList.helper"),
rules: [{ required: true, message: "请输入要导入的域名" }], rules: [{ required: true, message: t("certd.domainList.required") }],
component: { component: {
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n", placeholder: t("certd.domainList.placeholder"),
rows: 8, rows: 8,
}, },
col: { col: {
@ -25,6 +27,7 @@ export function useSiteImport() {
}, },
}, },
}, },
form: { form: {
async doSubmit({ form }) { async doSubmit({ form }) {
return siteInfoApi.Import(form); return siteInfoApi.Import(form);

View File

@ -1,32 +1,22 @@
<template> <template>
<div class="notification-selector"> <div class="notification-selector">
<div class="flex-o w-100"> <div class="flex-o w-100">
<fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled" :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" v-bind="select" @update:value="onChange" /> <fs-dict-select class="flex-1" :value="modelValue" :dict="optionsDictRef" :disabled="disabled"
<fs-table-select :render-label="renderLabel" :slots="selectSlots" :allow-clear="true" v-bind="select"
ref="tableSelectRef" @update:value="onChange" />
class="flex-0" <fs-table-select ref="tableSelectRef" class="flex-0" :model-value="modelValue" :dict="optionsDictRef"
:model-value="modelValue" :create-crud-options="createCrudOptions" :crud-options-override="{
:dict="optionsDictRef"
:create-crud-options="createCrudOptions"
:crud-options-override="{
search: { show: false }, search: { show: false },
table: { table: {
scroll: { scroll: {
x: 540, x: 540,
}, },
}, },
}" }" :show-current="false" :show-select="false" :dialog="{ width: 960 }" :destroy-on-close="false" height="400px"
:show-current="false" v-bind="tableSelect" @update:model-value="onChange" @dialog-closed="doRefresh">
:show-select="false"
:dialog="{ width: 960 }"
:destroy-on-close="false"
height="400px"
v-bind="tableSelect"
@update:model-value="onChange"
@dialog-closed="doRefresh"
>
<template #default="scope"> <template #default="scope">
<fs-button class="ml-5" :disabled="disabled" :size="size" type="primary" icon="ant-design:edit-outlined" @click="scope.open"></fs-button> <fs-button class="ml-5" :disabled="disabled" :size="size" type="primary"
icon="ant-design:edit-outlined" @click="scope.open"></fs-button>
</template> </template>
</fs-table-select> </fs-table-select>
</div> </div>
@ -41,6 +31,9 @@ import { dict } from "@fast-crud/fast-crud";
import createCrudOptions from "../crud"; import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common"; import { notificationProvide } from "/@/views/certd/notification/common";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
defineOptions({ defineOptions({
name: "NotificationSelector", name: "NotificationSelector",
@ -83,7 +76,7 @@ const optionsDictRef = dict({
const data = [ const data = [
{ {
id: 0, id: 0,
name: "使用默认通知", name: t("certd.notificationDefault"),
icon: "ion:notifications", icon: "ion:notifications",
}, },
...dict.data, ...dict.data,
@ -91,6 +84,7 @@ const optionsDictRef = dict({
dict.setData(data); dict.setData(data);
}, },
}); });
const renderLabel = (option: any) => { const renderLabel = (option: any) => {
return <span>{option.name}</span>; return <span>{option.name}</span>;
}; };

View File

@ -11,8 +11,11 @@ import * as api from "../api";
import { PluginGroup, usePluginStore } from "/@/store/plugin"; import { PluginGroup, usePluginStore } from "/@/store/plugin";
import { createNotificationApi } from "/@/views/certd/notification/api"; import { createNotificationApi } from "/@/views/certd/notification/api";
import GroupSelector from "../group/group-selector.vue"; import GroupSelector from "../group/group-selector.vue";
import { useI18n } from "vue-i18n";
export function setRunnableIds(pipeline: any) { export function setRunnableIds(pipeline: any) {
const { t } = useI18n();
const idMap: any = {}; const idMap: any = {};
function createId(oldId: any) { function createId(oldId: any) {
if (oldId == null) { if (oldId == null) {
@ -53,6 +56,7 @@ export function setRunnableIds(pipeline: any) {
} }
export function useCertPipelineCreator() { export function useCertPipelineCreator() {
const { t } = useI18n();
const { openCrudFormDialog } = useFormWrapper(); const { openCrudFormDialog } = useFormWrapper();
const pluginStore = usePluginStore(); const pluginStore = usePluginStore();
@ -117,12 +121,12 @@ export function useCertPipelineCreator() {
wrapper: { wrapper: {
width: 1350, width: 1350,
saveRemind: false, saveRemind: false,
title: "创建证书流水线", title: t("certd.pipelineForm.createTitle"),
}, },
group: { group: {
groups: { groups: {
more: { more: {
header: "更多参数", header: t("certd.pipelineForm.moreParams"),
columns: moreParams, columns: moreParams,
collapsed: true, collapsed: true,
}, },
@ -131,7 +135,7 @@ export function useCertPipelineCreator() {
}, },
columns: { columns: {
certApplyPlugin: { certApplyPlugin: {
title: "证书申请插件", title: t("certd.plugin.selectTitle"),
type: "dict-select", type: "dict-select",
dict: dict({ dict: dict({
data: [ data: [
@ -146,8 +150,8 @@ export function useCertPipelineCreator() {
render: () => { render: () => {
return ( return (
<ul> <ul>
<li>JS-ACME使便</li> <li>{t("certd.plugin.jsAcme")}</li>
<li>Lego-ACMELegoDNSLEGO使</li> <li>{t("certd.plugin.legoAcme")}</li>
</ul> </ul>
); );
}, },
@ -168,7 +172,7 @@ export function useCertPipelineCreator() {
}, },
...inputs, ...inputs,
triggerCron: { triggerCron: {
title: "定时触发", title: t("certd.pipelineForm.triggerCronTitle"),
type: "text", type: "text",
form: { form: {
value: `0 ${randomMin} ${randomHour} * * *`, value: `0 ${randomMin} ${randomHour} * * *`,
@ -177,12 +181,12 @@ export function useCertPipelineCreator() {
vModel: "modelValue", vModel: "modelValue",
placeholder: "0 0 4 * * *", placeholder: "0 0 4 * * *",
}, },
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行", helper: t("certd.pipelineForm.triggerCronHelper"),
order: 100, order: 100,
}, },
}, },
notification: { notification: {
title: "失败通知", title: t("certd.pipelineForm.notificationTitle"),
type: "text", type: "text",
form: { form: {
value: 0, value: 0,
@ -196,11 +200,11 @@ export function useCertPipelineCreator() {
}, },
}, },
order: 101, order: 101,
helper: "任务执行失败实时提醒", helper: t("certd.pipelineForm.notificationHelper"),
}, },
}, },
groupId: { groupId: {
title: "流水线分组", title: t("certd.pipelineForm.groupIdTitle"),
type: "dict-select", type: "dict-select",
dict: groupDictRef, dict: groupDictRef,
form: { form: {
@ -210,7 +214,8 @@ export function useCertPipelineCreator() {
}, },
order: 9999, order: 9999,
}, },
}, }
}, },
}, },
}; };

View File

@ -1,9 +1,7 @@
<template> <template>
<div> <div>
<fs-form-item <fs-form-item v-model="optionsFormState.receivers" :item="{
v-model="optionsFormState.receivers" title: t('certd.email.title'),
:item="{
title: '收件邮箱',
key: 'type', key: 'type',
component: { component: {
name: 'a-select', name: 'a-select',
@ -11,13 +9,13 @@
mode: 'tags', mode: 'tags',
open: false open: false
}, },
helper: '输入你的收件邮箱地址,支持多个邮箱', helper: t('certd.email.helper'),
rules: [{ required: true, message: '此项必填' }] rules: [{ required: true, message: t('certd.email.required') }]
}" }" />
/>
<a-alert v-if="!settingStore.isPlus" class="m-1" type="info"> <a-alert v-if="!settingStore.isPlus" class="m-1" type="info">
<template #message> 还没有配置邮件服务器<router-link :to="{ path: '/sys/settings/email' }">现在就去</router-link> </template> <template #message> 还没有配置邮件服务器<router-link :to="{ path: '/sys/settings/email' }">现在就去</router-link>
</template>
</a-alert> </a-alert>
</div> </div>
</template> </template>
@ -25,11 +23,14 @@
import { Ref, ref, watch } from "vue"; import { Ref, ref, watch } from "vue";
import { useUserStore } from "/@/store/user"; import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
options: { options: {
type: Object as PropType<any>, type: Object as PropType<any>,
default: () => {} default: () => { }
} }
}); });

View File

@ -1,19 +1,21 @@
<template> <template>
<a-drawer v-model:open="triggerDrawerVisible" placement="right" :closable="true" width="650px" class="pi-trigger-form" @after-open-change="triggerDrawerOnAfterVisibleChange"> <a-drawer v-model:open="triggerDrawerVisible" placement="right" :closable="true" width="650px"
class="pi-trigger-form" @after-open-change="triggerDrawerOnAfterVisibleChange">
<template #title> <template #title>
<div> <div>
编辑触发器 编辑触发器
<a-button v-if="mode === 'edit'" @click="triggerDelete()"> <a-button v-if="mode === 'edit'" @click="triggerDelete()">
<template #icon><DeleteOutlined /></template> <template #icon>
<DeleteOutlined />
</template>
</a-button> </a-button>
</div> </div>
</template> </template>
<template v-if="currentTrigger"> <template v-if="currentTrigger">
<pi-container> <pi-container>
<a-form ref="triggerFormRef" class="trigger-form" :model="currentTrigger" :label-col="labelCol" :wrapper-col="wrapperCol"> <a-form ref="triggerFormRef" class="trigger-form" :model="currentTrigger" :label-col="labelCol"
<fs-form-item :wrapper-col="wrapperCol">
v-model="currentTrigger.title" <fs-form-item v-model="currentTrigger.title" :item="{
:item="{
title: '触发器名称', title: '触发器名称',
key: 'title', key: 'title',
component: { component: {
@ -22,12 +24,9 @@
disabled: !editMode, disabled: !editMode,
}, },
rules: [{ required: true, message: '此项必填' }], rules: [{ required: true, message: '此项必填' }],
}" }" />
/>
<fs-form-item <fs-form-item v-model="currentTrigger.type" :item="{
v-model="currentTrigger.type"
:item="{
title: '类型', title: '类型',
key: 'type', key: 'type',
value: 'timer', value: 'timer',
@ -38,23 +37,19 @@
options: [{ value: 'timer', label: '定时' }], options: [{ value: 'timer', label: '定时' }],
}, },
rules: [{ required: true, message: '此项必填' }], rules: [{ required: true, message: '此项必填' }],
}" }" />
/>
<fs-form-item <fs-form-item v-model="currentTrigger.props.cron" :item="{
v-model="currentTrigger.props.cron" title: t('certd.cronForm.title'),
:item="{
title: '定时脚本',
key: 'props.cron', key: 'props.cron',
component: { component: {
disabled: !editMode, disabled: !editMode,
name: 'cron-editor', name: 'cron-editor',
vModel: 'modelValue', vModel: 'modelValue',
}, },
helper: '点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次证书未到期之前任务会跳过不会重复执行', helper: t('certd.cronForm.helper'),
rules: [{ required: true, message: '此项必填' }], rules: [{ required: true, message: t('certd.cronForm.required') }],
}" }" />
/>
</a-form> </a-form>
<template #footer> <template #footer>
@ -192,6 +187,5 @@ export default {
</script> </script>
<style lang="less"> <style lang="less">
.pi-trigger-form { .pi-trigger-form {}
}
</style> </style>