pref: 支持子域名托管的域名证书申请

This commit is contained in:
xiaojunnuo
2025-04-11 12:13:57 +08:00
parent f68af7dcf2
commit 67f956d4a0
21 changed files with 700 additions and 130 deletions
@@ -1,6 +1,7 @@
import { request } from "/src/api/service";
const apiPrefix = "/cname/record";
const subDomainApiPrefix = "/pi/subDomain";
export type CnameRecord = {
id?: number;
@@ -18,7 +19,7 @@ export type DomainGroupItem = {
export async function GetList() {
return await request({
url: apiPrefix + "/list",
method: "post"
method: "post",
});
}
@@ -28,8 +29,8 @@ export async function GetByDomain(domain: string) {
method: "post",
data: {
domain,
createOnNotFound: true
}
createOnNotFound: true,
},
});
}
@@ -38,7 +39,17 @@ export async function DoVerify(id: number) {
url: apiPrefix + "/verify",
method: "post",
data: {
id
}
id,
},
});
}
export async function ParseDomain(fullDomain: string) {
return await request({
url: subDomainApiPrefix + "/parseDomain",
method: "post",
data: {
fullDomain,
},
});
}
@@ -32,26 +32,14 @@
<div class="form-item">
<span class="label">DNS类型</span>
<span class="input">
<fs-dict-select
v-model:value="item.dnsProviderType"
size="small"
:dict="dnsProviderTypeDict"
placeholder="DNS提供商"
@change="onPlanChanged"
></fs-dict-select>
<fs-dict-select v-model:value="item.dnsProviderType" size="small" :dict="dnsProviderTypeDict" placeholder="DNS提供商" @change="onPlanChanged"></fs-dict-select>
</span>
</div>
<a-divider type="vertical" />
<div class="form-item">
<span class="label">DNS授权</span>
<span class="input">
<access-selector
v-model="item.dnsProviderAccessId"
size="small"
:type="item.dnsProviderType"
placeholder="请选择"
@change="onPlanChanged"
></access-selector>
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderType" placeholder="请选择" @change="onPlanChanged"></access-selector>
</span>
</div>
</div>
@@ -80,28 +68,27 @@ import { dict, FsDictSelect } from "@fast-crud/fast-crud";
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
import CnameVerifyPlan from "./cname-verify-plan.vue";
import HttpVerifyPlan from "./http-verify-plan.vue";
//@ts-ignore
import psl from "psl";
import { Form } from "ant-design-vue";
import { DomainsVerifyPlanInput } from "./type";
import { CnameRecord, DomainGroupItem } from "./api";
import { DomainGroupItem, ParseDomain } from "./api";
defineOptions({
name: "DomainsVerifyPlanEditor"
name: "DomainsVerifyPlanEditor",
});
const challengeTypeOptions = ref<any[]>([
{
label: "DNS验证",
value: "dns"
value: "dns",
},
{
label: "CNAME验证",
value: "cname"
value: "cname",
},
{
label: "HTTP验证",
value: "http"
}
value: "http",
},
]);
const props = defineProps<{
@@ -122,7 +109,7 @@ function fullscreenExit() {
}
const planRef = ref<DomainsVerifyPlanInput>(props.modelValue || {});
const dnsProviderTypeDict = dict({
url: "pi/dnsProvider/dnsProviderTypeDict"
url: "pi/dnsProvider/dnsProviderTypeDict",
});
const formItemContext = Form.useInjectFormItemContext();
@@ -139,7 +126,7 @@ function showError(error: string) {
type DomainGroup = Record<string, DomainGroupItem>;
function onDomainsChanged(domains: string[]) {
async function onDomainsChanged(domains: string[]) {
if (domains == null) {
return;
}
@@ -147,12 +134,7 @@ function onDomainsChanged(domains: string[]) {
const domainGroups: DomainGroup = {};
for (let domain of domains) {
const keyDomain = domain.replace("*.", "");
const parsed = psl.parse(keyDomain);
if (parsed.error) {
showError(`域名${domain}解析失败: ${JSON.stringify(parsed.error)}`);
continue;
}
const mainDomain = parsed.domain;
const mainDomain = await ParseDomain(keyDomain);
if (mainDomain == null) {
continue;
}
@@ -161,7 +143,7 @@ function onDomainsChanged(domains: string[]) {
group = {
domain: mainDomain,
domains: [],
keySubDomains: []
keySubDomains: [],
} as DomainGroupItem;
domainGroups[mainDomain] = group;
}
@@ -180,7 +162,7 @@ function onDomainsChanged(domains: string[]) {
//@ts-ignore
cnameVerifyPlan: {},
//@ts-ignore
httpVerifyPlan: {}
httpVerifyPlan: {},
};
planRef.value[domain] = planItem;
}
@@ -196,7 +178,7 @@ function onDomainsChanged(domains: string[]) {
if (!cnameOrigin[subDomain]) {
//@ts-ignore
planItem.cnameVerifyPlan[subDomain] = {
id: 0
id: 0,
};
} else {
planItem.cnameVerifyPlan[subDomain] = cnameOrigin[subDomain];
@@ -205,14 +187,14 @@ function onDomainsChanged(domains: string[]) {
if (!cnamePlan[subDomain]) {
//@ts-ignore
cnamePlan[subDomain] = {
id: 0
id: 0,
};
}
if (!httpOrigin[subDomain]) {
//@ts-ignore
planItem.httpVerifyPlan[subDomain] = {
domain: subDomain
domain: subDomain,
};
} else {
planItem.httpVerifyPlan[subDomain] = httpOrigin[subDomain];
@@ -221,7 +203,7 @@ function onDomainsChanged(domains: string[]) {
if (!httpPlan[subDomain]) {
//@ts-ignore
httpPlan[subDomain] = {
domain: subDomain
domain: subDomain,
};
}
}
@@ -255,7 +237,7 @@ watch(
},
{
immediate: true,
deep: true
deep: true,
}
);
</script>
@@ -98,6 +98,17 @@ export const certdResources = [
keepAlive: true,
},
},
{
title: "子域名托管设置",
name: "SubDomain",
path: "/certd/pipeline/subDomain",
component: "/certd/pipeline/sub-domain/index.vue",
meta: {
icon: "ion:link-outline",
auth: true,
keepAlive: true,
},
},
{
title: "流水线分组管理",
name: "PipelineGroupManager",
@@ -0,0 +1,60 @@
// @ts-ignore
import { request } from "/src/api/service";
const apiPrefix = "/pi/subDomain";
export async function GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
}
export async function AddObj(obj: any) {
return await request({
url: apiPrefix + "/add",
method: "post",
data: obj,
});
}
export async function UpdateObj(obj: any) {
return await request({
url: apiPrefix + "/update",
method: "post",
data: obj,
});
}
export async function DelObj(id: any) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
}
export async function GetObj(id: any) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
}
export async function GetDetail(id: any) {
return await request({
url: apiPrefix + "/detail",
method: "post",
params: { id },
});
}
export async function DeleteBatch(ids: any[]) {
return await request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids },
});
}
@@ -0,0 +1,123 @@
import * as api from "./api";
import { Ref, ref } from "vue";
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async ({ form, row }: EditReq) => {
form.id = row.id;
const res = await api.UpdateObj(form);
return res;
};
const delRequest = async ({ row }: DelReq) => {
return await api.DelObj(row.id);
};
const addRequest = async ({ form }: AddReq) => {
const res = await api.AddObj(form);
return res;
};
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
return {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
rowSelection: {
enabled: true,
order: -2,
before: true,
// handle: (pluginProps,useCrudProps)=>CrudOptions,
props: {
multiple: true,
crossPage: true,
selectedRowKeys,
},
},
},
},
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
// tabs: {
// name: "status",
// show: true,
// },
rowHandle: {
minWidth: 200,
fixed: "right",
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
column: {
width: 80,
},
form: {
show: false,
},
},
domain: {
title: "子域名",
type: "text",
search: {
show: true,
},
editForm: {
component: {
disabled: true,
},
},
},
disabled: {
title: "是否禁用",
type: "dict-switch",
dict: dict({
data: [
{ value: false, label: "启用", color: "green" },
{ value: true, label: "禁用", color: "gray" },
],
}),
search: {
show: true,
},
form: {
value: false,
},
},
createTime: {
title: "创建时间",
type: "datetime",
form: {
show: false,
},
column: {
sorter: true,
width: 160,
align: "center",
},
},
updateTime: {
title: "更新时间",
type: "datetime",
form: {
show: false,
},
column: {
show: true,
},
},
},
},
};
}
@@ -0,0 +1,57 @@
<template>
<fs-page class="page-cert">
<template #header>
<div class="title">
子域名托管
<span class="sub"> 当你的域名设置了子域名托管需要在此处创建记录否则申请证书将失败 </span>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
import { DeleteBatch } from "./api";
defineOptions({
name: "CnameRecord",
});
const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions });
const selectedRowKeys = context.selectedRowKeys;
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
async onOk() {
await DeleteBatch(selectedRowKeys.value);
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error("请先勾选记录");
}
};
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(async () => {
await crudExpose.doRefresh();
});
</script>
<style lang="less"></style>