mirror of
https://github.com/certd/certd.git
synced 2026-07-11 15:57:33 +08:00
perf: 新增站点证书监控从DNS解析记录批量导入功能
本次提交新增了从DNS解析记录批量导入站点监控的完整功能: 1. 扩展Registrable类型新增icon字段支持 2. 新增DNS解析记录获取接口和基础实现 3. 为阿里云、腾讯云、Cloudflare等DNS提供商添加解析记录分页获取支持 4. 新增站点监控导入任务管理功能,支持保存、启动、删除导入任务 5. 新增中文/英文多语言支持 6. 优化暗黑模式表格样式 7. 修复ACME账户访问修复逻辑中项目ID可选的问题 8. 优化HiPM DNS提供商的域名获取逻辑
This commit is contained in:
@@ -18,6 +18,7 @@ export default {
|
||||
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",
|
||||
importFromResolveRecords: "Import from DNS Records",
|
||||
syncExpirationDate: "Sync Domain Expiration Time",
|
||||
syncTaskSubmitted: "Sync task submitted",
|
||||
syncExpirationProgress: "Sync Domain Expiration Progress",
|
||||
|
||||
@@ -18,6 +18,7 @@ export default {
|
||||
subdomainConfirmTitle: "子域名确认",
|
||||
subdomainConfirmContent: "检测到{domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?",
|
||||
importFromProvider: "从域名提供商导入",
|
||||
importFromResolveRecords: "从解析记录导入",
|
||||
syncExpirationDate: "同步域名过期时间",
|
||||
syncTaskSubmitted: "同步任务已提交",
|
||||
syncExpirationProgress: "同步域名过期时间进度",
|
||||
|
||||
@@ -8,4 +8,45 @@
|
||||
.vben-normal-menu__item.is-active {
|
||||
background-color: #3b3b3b !important;
|
||||
}
|
||||
|
||||
.cd-table {
|
||||
th,
|
||||
td {
|
||||
border-bottom: 1px solid #303030;
|
||||
border-left: 1px solid #303030;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #1f1f1f;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border-top: 1px solid #303030;
|
||||
|
||||
&:last-child {
|
||||
border-right: 1px solid #303030;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
&:last-child {
|
||||
border-right: 1px solid #303030;
|
||||
}
|
||||
}
|
||||
|
||||
td.position-sticky-right {
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
.position-sticky-right::before {
|
||||
background: #303030;
|
||||
}
|
||||
|
||||
tr.hover-color:hover td {
|
||||
background: #262626;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: #1f3a23;
|
||||
color: #81c784;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,36 @@ export const siteInfoApi = {
|
||||
});
|
||||
},
|
||||
|
||||
async ImportTaskSave(body: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/import/save",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
async ImportTaskStatus() {
|
||||
return await request({
|
||||
url: apiPrefix + "/import/status",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async ImportTaskDelete(key: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/import/delete",
|
||||
method: "post",
|
||||
data: { key },
|
||||
});
|
||||
},
|
||||
async ImportTaskStart(key: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/import/start",
|
||||
method: "post",
|
||||
data: { key },
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
||||
async DisabledChange(id: number, disabled: boolean) {
|
||||
return await request({
|
||||
url: apiPrefix + "/disabledChange",
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useSettingStore } from "/@/store/settings";
|
||||
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
||||
import { mitter } from "/@/utils/util.mitt";
|
||||
import { useSiteIpMonitor } from "./ip/use";
|
||||
import { useSiteImport } from "/@/views/certd/monitor/site/use";
|
||||
import { useSiteImport, useSiteImportTaskManage } from "/@/views/certd/monitor/site/use";
|
||||
import { ref } from "vue";
|
||||
import GroupSelector from "../../basic/group/group-selector.vue";
|
||||
import { createGroupDictRef } from "../../basic/group/api";
|
||||
@@ -53,6 +53,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
|
||||
const { openSiteIpMonitorDialog } = useSiteIpMonitor();
|
||||
const { openSiteImportDialog } = useSiteImport();
|
||||
const openSiteImportTaskManageDialog = useSiteImportTaskManage();
|
||||
|
||||
const certValidDaysRef = ref(10);
|
||||
|
||||
@@ -200,6 +201,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
icon: "ion:add-circle-outline",
|
||||
async click() {
|
||||
if (!settingsStore.isPlus) {
|
||||
// 非plus
|
||||
@@ -236,6 +238,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
show: hasActionPermission("write"),
|
||||
text: t("monitor.bulkImport"),
|
||||
type: "primary",
|
||||
icon: "ion:cloud-upload-outline",
|
||||
async click() {
|
||||
const defaultGroupId = getDefaultGroupId();
|
||||
openSiteImportDialog({
|
||||
@@ -246,10 +249,27 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
});
|
||||
},
|
||||
},
|
||||
importFromProvider: {
|
||||
show: hasActionPermission("write"),
|
||||
title: t("certd.domain.importFromResolveRecords"),
|
||||
text: t("certd.domain.importFromResolveRecords"),
|
||||
type: "primary",
|
||||
needPlus: true,
|
||||
color: "gold",
|
||||
icon: "mingcute:vip-1-line",
|
||||
click: async () => {
|
||||
await openSiteImportTaskManageDialog({
|
||||
afterSubmit: () => {
|
||||
crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
checkAll: {
|
||||
show: true,
|
||||
text: t("monitor.checkAll"),
|
||||
type: "primary",
|
||||
icon: "ion:play-circle-outline",
|
||||
click() {
|
||||
checkAll();
|
||||
},
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="site-info-import-task-status min-h-[300px]">
|
||||
<div class="action mb-5">
|
||||
<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">{{ t("certd.domain.refresh") }}</fs-button>
|
||||
</div>
|
||||
<div class="table-container overflow-auto mb-10">
|
||||
<table class="cd-table border-gray-300 w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[220px]">{{ t("certd.sourcee") }}</th>
|
||||
<th class="">{{ t("certd.domain.progress") }}</th>
|
||||
<th class="w-[220px]">{{ t("certd.domain.operation") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in list" :key="item.key">
|
||||
<td class="ellipsis">
|
||||
<span class="flex items-center pointer" @click="editTask(item)">
|
||||
<span class="flex-1 ellipsis flex items-center">
|
||||
<fs-icon :icon="item.icon" class="mr-2"></fs-icon>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<fs-icon icon="ant-design:edit-outlined" class="ml-2" />
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="item.task">
|
||||
<div>
|
||||
<a-tag color="blue">{{ t("certd.domain.total") }}:{{ item.task?.total }}</a-tag>
|
||||
<a-tag color="success" class="ml-2">{{ t("certd.success") }}:{{ item.task?.successCount }}</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">
|
||||
<template #title>
|
||||
<div v-for="error in item.task?.errors" :key="error">{{ error }}</div>
|
||||
</template>
|
||||
<a-tag color="red" class="ml-2">{{ t("certd.domain.failed") }}:{{ item.task?.errors.length }}</a-tag>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-progress :percent="item.task?.progress" size="small" status="active" />
|
||||
</div>
|
||||
<div v-else>{{ t("certd.domain.notExecuted") }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<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)">{{ t("certd.domain.delete") }}</fs-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import * as api from "./api";
|
||||
import { useSiteImportTask } from "./use";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useI18n } from "/@/locales";
|
||||
defineOptions({
|
||||
name: "SiteInfoImportTaskStatus",
|
||||
});
|
||||
|
||||
const list = ref([]);
|
||||
const { t } = useI18n();
|
||||
|
||||
async function loadImportTaskStatus() {
|
||||
const res = await api.siteInfoApi.ImportTaskStatus();
|
||||
list.value = res || [];
|
||||
}
|
||||
|
||||
async function startTask(item: any) {
|
||||
settingStore.checkPlus();
|
||||
await api.siteInfoApi.ImportTaskStart(item.key);
|
||||
await loadImportTaskStatus();
|
||||
}
|
||||
|
||||
async function deleteTask(item: any) {
|
||||
Modal.confirm({
|
||||
title: t("certd.domain.confirmDelete"),
|
||||
okText: t("common.confirm"),
|
||||
okType: "danger",
|
||||
onOk: async () => {
|
||||
await api.siteInfoApi.ImportTaskDelete(item.key);
|
||||
await loadImportTaskStatus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const openSiteImportTaskDialog = useSiteImportTask();
|
||||
const settingStore = useSettingStore();
|
||||
async function addTask() {
|
||||
settingStore.checkPlus();
|
||||
await openSiteImportTaskDialog({
|
||||
afterSubmit: async (res?: any) => {
|
||||
if (res) {
|
||||
await api.siteInfoApi.ImportTaskStart(res.key);
|
||||
}
|
||||
await loadImportTaskStatus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function editTask(item: any) {
|
||||
settingStore.checkPlus();
|
||||
await openSiteImportTaskDialog({
|
||||
afterSubmit: async () => {
|
||||
await loadImportTaskStatus();
|
||||
},
|
||||
form: item,
|
||||
});
|
||||
}
|
||||
|
||||
const checkIntervalRef = ref();
|
||||
onMounted(async () => {
|
||||
await loadImportTaskStatus();
|
||||
checkIntervalRef.value = setInterval(async () => {
|
||||
await loadImportTaskStatus();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(checkIntervalRef.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.site-info-import-task-status {
|
||||
.table-container {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.ant-progress {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,11 @@
|
||||
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { useFormWrapper, compute } from "@fast-crud/fast-crud";
|
||||
import { siteInfoApi } from "./api";
|
||||
import { useI18n } from "/src/locales";
|
||||
import { useI18n } from "/@/locales";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
import { useFormDialog } from "/@/use/use-dialog";
|
||||
import GroupSelector from "../../basic/group/group-selector.vue";
|
||||
import SiteInfoImportTaskStatus from "./import.vue";
|
||||
|
||||
export function useSiteImport() {
|
||||
const { t } = useI18n();
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
@@ -13,7 +17,7 @@ export function useSiteImport() {
|
||||
columns: {
|
||||
text: {
|
||||
type: "textarea",
|
||||
title: t("certd.domainList.title"), // 域名列表
|
||||
title: t("certd.domainList.title"),
|
||||
form: {
|
||||
helper: t("certd.domainList.helper"),
|
||||
rules: [{ required: true, message: t("certd.domainList.required") }],
|
||||
@@ -21,9 +25,7 @@ export function useSiteImport() {
|
||||
placeholder: t("certd.domainList.placeholder"),
|
||||
rows: 8,
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
col: { span: 24 },
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
@@ -36,13 +38,10 @@ export function useSiteImport() {
|
||||
vModel: "modelValue",
|
||||
type: "site",
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
col: { span: 24 },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
form: {
|
||||
async doSubmit({ form }) {
|
||||
return siteInfoApi.Import(form);
|
||||
@@ -53,7 +52,99 @@ export function useSiteImport() {
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
openSiteImportDialog,
|
||||
return { openSiteImportDialog };
|
||||
}
|
||||
|
||||
export function useSiteImportTask() {
|
||||
const { openFormDialog } = useFormDialog();
|
||||
const { t } = useI18n();
|
||||
|
||||
const columns = {
|
||||
dnsProviderType: {
|
||||
title: t("certd.domain.domainProvider"),
|
||||
type: "text",
|
||||
form: {
|
||||
component: {
|
||||
name: "dns-provider-selector",
|
||||
on: {
|
||||
selectedChange: ({ form, $event }: any) => {
|
||||
form.dnsProviderAccessType = $event.accessType;
|
||||
},
|
||||
},
|
||||
},
|
||||
valueChange({ form }: any) {
|
||||
form.dnsProviderAccessId = null;
|
||||
},
|
||||
},
|
||||
},
|
||||
dnsProviderAccessType: {
|
||||
title: t("certd.domain.domainProviderAccessType"),
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
},
|
||||
dnsProviderAccessId: {
|
||||
title: t("certd.domain.domainProviderAccess"),
|
||||
type: "text",
|
||||
form: {
|
||||
component: {
|
||||
name: "access-selector",
|
||||
vModel: "modelValue",
|
||||
type: compute(({ form }: any) => form.dnsProviderAccessType || form.dnsProviderType),
|
||||
},
|
||||
},
|
||||
},
|
||||
groupId: {
|
||||
title: t("certd.fields.group"),
|
||||
type: "text",
|
||||
form: {
|
||||
component: {
|
||||
name: GroupSelector,
|
||||
vModel: "modelValue",
|
||||
type: "site",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return function openSiteImportTaskDialog(req: { afterSubmit?: (res?: any) => void; form?: any }) {
|
||||
openFormDialog({
|
||||
title: t("certd.domain.importFromProvider"),
|
||||
columns,
|
||||
initialForm: { ...req.form },
|
||||
onSubmit: async (form: any) => {
|
||||
const res = await siteInfoApi.ImportTaskSave({
|
||||
key: form.key,
|
||||
dnsProviderType: form.dnsProviderType,
|
||||
dnsProviderAccessId: form.dnsProviderAccessId,
|
||||
groupId: form.groupId,
|
||||
});
|
||||
if (req.afterSubmit) {
|
||||
req.afterSubmit(res);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function useSiteImportTaskManage() {
|
||||
const { openFormDialog } = useFormDialog();
|
||||
const { t } = useI18n();
|
||||
const settingStore = useSettingStore();
|
||||
return async function openSiteImportTaskManageDialog(req: {
|
||||
afterSubmit?: (res?: any) => void;
|
||||
form?: any;
|
||||
zIndex?: number;
|
||||
}) {
|
||||
settingStore.checkPlus();
|
||||
await openFormDialog({
|
||||
title: t("certd.domain.importFromProvider"),
|
||||
body: () => <SiteInfoImportTaskStatus />,
|
||||
zIndex: req.zIndex,
|
||||
onSubmit: async (form: any) => {
|
||||
if (req.afterSubmit) {
|
||||
req.afterSubmit(form);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user