mirror of
https://github.com/certd/certd.git
synced 2026-04-24 12:27:25 +08:00
feat: 域名验证方法支持CNAME间接方式,此方式支持所有域名注册商,且无需提供Access授权,但是需要手动添加cname解析
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" href="/static/logo.svg"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Certd-让你的证书永不过期</title>
|
||||
<title>Loading</title>
|
||||
<script src="/static/icons/iconfont.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/index.css"/>
|
||||
</head>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@soerenmartius/vue3-clipboard": "^0.1.2",
|
||||
"@vue-js-cron/light": "^4.0.5",
|
||||
"ant-design-vue": "^4.1.2",
|
||||
"async-validator": "^4.2.5",
|
||||
"axios": "^1.7.2",
|
||||
"axios-mock-adapter": "^1.22.0",
|
||||
"base64-js": "^1.5.1",
|
||||
@@ -49,6 +50,7 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"pinia": "2.1.7",
|
||||
"psl": "^1.9.0",
|
||||
"qiniu-js": "^3.4.2",
|
||||
"sortablejs": "^1.15.2",
|
||||
"vue": "^3.4.21",
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
defineOptions({
|
||||
name: "CronEditor"
|
||||
});
|
||||
const props = defineProps<{
|
||||
modelValue?: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<a-select class="pi-dns-provider-selector" :value="modelValue" :options="options" @update:value="onChanged">
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { inject, Ref, ref, watch } from "vue";
|
||||
import * as api from "./api";
|
||||
|
||||
export default {
|
||||
name: "PiDnsProviderSelector",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props:any, ctx:any) {
|
||||
const options = ref<any[]>([]);
|
||||
|
||||
async function onCreate() {
|
||||
const list = await api.GetList();
|
||||
const array: any[] = [];
|
||||
for (let item of list) {
|
||||
array.push({
|
||||
value: item.name,
|
||||
label: item.title
|
||||
});
|
||||
}
|
||||
options.value = array;
|
||||
if (props.modelValue == null && options.value.length > 0) {
|
||||
ctx.emit("update:modelValue", options.value[0].value);
|
||||
}
|
||||
}
|
||||
onCreate();
|
||||
|
||||
function onChanged(value:any) {
|
||||
ctx.emit("update:modelValue", value);
|
||||
}
|
||||
return {
|
||||
options,
|
||||
onChanged
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.step-edit-form {
|
||||
.body {
|
||||
padding: 10px;
|
||||
.ant-card {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.current {
|
||||
border-color: #00b7ff;
|
||||
}
|
||||
|
||||
.ant-card-meta-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ant-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 14px;
|
||||
height: 100px;
|
||||
|
||||
overflow-y: hidden;
|
||||
|
||||
.ant-card-meta-description {
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
height: 40px;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="pi-editable" :class="{ disabled, 'hover-show': hoverShow }">
|
||||
<div class="text-editable" :class="{ disabled, 'hover-show': hoverShow }">
|
||||
<div v-if="isEdit" class="input">
|
||||
<a-input ref="inputRef" v-model:value="valueRef" :validate-status="modelValue ? '' : 'error'" v-bind="input" @keyup.enter="save()" @blur="save()">
|
||||
<template #suffix>
|
||||
@@ -18,7 +18,7 @@
|
||||
import { watch, ref, nextTick } from "vue";
|
||||
|
||||
export default {
|
||||
name: "PiEditable",
|
||||
name: "TextEditable",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.pi-editable {
|
||||
.text-editable {
|
||||
flex: 1;
|
||||
line-height: 34px;
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import PiContainer from "./container.vue";
|
||||
import PiAccessSelector from "../views/certd/access/access-selector/index.vue";
|
||||
import PiDnsProviderSelector from "./dns-provider-selector/index.vue";
|
||||
import PiOutputSelector from "../views/certd/pipeline/pipeline/component/output-selector/index.vue";
|
||||
import PiEditable from "./editable.vue";
|
||||
import TextEditable from "./editable.vue";
|
||||
import vip from "./vip-button/install.js";
|
||||
import { CheckCircleOutlined, InfoCircleOutlined, UndoOutlined } from "@ant-design/icons-vue";
|
||||
import CronEditor from "./cron-editor/index.vue";
|
||||
@@ -13,10 +10,7 @@ import Plugins from "./plugins/index";
|
||||
export default {
|
||||
install(app: any) {
|
||||
app.component("PiContainer", PiContainer);
|
||||
app.component("PiAccessSelector", PiAccessSelector);
|
||||
app.component("PiEditable", PiEditable);
|
||||
app.component("PiOutputSelector", PiOutputSelector);
|
||||
app.component("PiDnsProviderSelector", PiDnsProviderSelector);
|
||||
app.component("TextEditable", TextEditable);
|
||||
|
||||
app.component("CronLight", CronLight);
|
||||
app.component("CronEditor", CronEditor);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<a-select class="dns-provider-selector" :value="modelValue" :options="options" @update:value="onChanged"> </a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref } from "vue";
|
||||
import * as api from "./api";
|
||||
|
||||
export default {
|
||||
name: "DnsProviderSelector",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
setup(props: any, ctx: any) {
|
||||
const options = ref<any[]>([]);
|
||||
|
||||
async function onCreate() {
|
||||
const list = await api.GetList();
|
||||
const array: any[] = [];
|
||||
for (let item of list) {
|
||||
array.push({
|
||||
value: item.name,
|
||||
label: item.title
|
||||
});
|
||||
}
|
||||
options.value = array;
|
||||
if (props.modelValue == null && options.value.length > 0) {
|
||||
ctx.emit("update:modelValue", options.value[0].value);
|
||||
}
|
||||
}
|
||||
onCreate();
|
||||
|
||||
function onChanged(value: any) {
|
||||
ctx.emit("update:modelValue", value);
|
||||
}
|
||||
return {
|
||||
options,
|
||||
onChanged
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/cname/record";
|
||||
|
||||
export type CnameRecord = {
|
||||
id: number;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export async function GetList() {
|
||||
return await request({
|
||||
url: apiPrefix + "/list",
|
||||
method: "post"
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetByDomain(domain: string) {
|
||||
return await request({
|
||||
url: apiPrefix + "/getByDomain",
|
||||
method: "post",
|
||||
data: {
|
||||
domain,
|
||||
createOnNotFound: true
|
||||
}
|
||||
});
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<tr v-if="cnameRecord" class="cname-record-info">
|
||||
<!-- <td class="domain">-->
|
||||
<!-- {{ props.domain }}-->
|
||||
<!-- </td>-->
|
||||
<td class="host-record" :title="'域名:' + props.domain">
|
||||
<fs-copyable v-model="cnameRecord.hostRecord"></fs-copyable>
|
||||
</td>
|
||||
<td class="record-value">
|
||||
<fs-copyable v-model="cnameRecord.recordValue"></fs-copyable>
|
||||
</td>
|
||||
<td class="status center flex-center">
|
||||
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
|
||||
<fs-icon icon="ion:refresh-outline" class="pointer" @click="doRefresh"></fs-icon>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-verify-plan-editor/api";
|
||||
import { ref, watch } from "vue";
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
|
||||
const statusDict = dict({
|
||||
data: [
|
||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
||||
{ label: "验证中", value: "validating", color: "primary" },
|
||||
{ label: "验证成功", value: "valid", color: "success" },
|
||||
{ label: "验证失败", value: "failed", color: "error" }
|
||||
]
|
||||
});
|
||||
|
||||
defineOptions({
|
||||
name: "CnameRecordInfo"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
domain: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: {
|
||||
id: number | null;
|
||||
status: string | null;
|
||||
};
|
||||
}>();
|
||||
|
||||
const cnameRecord = ref<CnameRecord | null>(null);
|
||||
|
||||
function onRecordChange() {
|
||||
emit("change", {
|
||||
id: cnameRecord.value?.id,
|
||||
status: cnameRecord.value?.status
|
||||
});
|
||||
}
|
||||
|
||||
async function doRefresh() {
|
||||
if (!props.domain) {
|
||||
return;
|
||||
}
|
||||
cnameRecord.value = await GetByDomain(props.domain);
|
||||
onRecordChange();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.domain,
|
||||
async (value) => {
|
||||
await doRefresh();
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.cname-record-info {
|
||||
.fs-copyable {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<table class="cname-verify-plan">
|
||||
<tr>
|
||||
<td style="width: 160px">主机记录</td>
|
||||
<td style="width: 250px">请设置CNAME记录</td>
|
||||
<td style="width: 120px" class="center">状态</td>
|
||||
</tr>
|
||||
<template v-for="key in domains" :key="key">
|
||||
<cname-record-info :domain="key" @change="onRecordChange(key, $event)" />
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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 { computed } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "CnameVerifyPlan"
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": any;
|
||||
change: Record<string, any>;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>;
|
||||
}>();
|
||||
|
||||
const domains = computed(() => {
|
||||
return Object.keys(props.modelValue);
|
||||
});
|
||||
|
||||
function onRecordChange(domain: string, record: CnameRecord) {
|
||||
const value = { ...props.modelValue };
|
||||
value[domain] = record;
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.cname-verify-plan {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
tr {
|
||||
td {
|
||||
border: 0 !important;
|
||||
border-bottom: 1px solid #e8e8e8 !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
td {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="domains-verify-plan-editor" :class="{ fullscreen }">
|
||||
<div class="fullscreen-modal" @click="fullscreenExit"></div>
|
||||
<div class="plan-wrapper">
|
||||
<div class="plan-box">
|
||||
<div class="fullscreen-button pointer">
|
||||
<fs-icon :icon="fullscreen ? 'material-symbols:fullscreen' : 'material-symbols:fullscreen-exit'" @click="fullscreen = !fullscreen"></fs-icon>
|
||||
</div>
|
||||
<table class="plan-table">
|
||||
<tr>
|
||||
<th>域名</th>
|
||||
<th>验证方式</th>
|
||||
<th>验证计划</th>
|
||||
</tr>
|
||||
<tr v-for="(item, key) of planRef" :key="key" class="row">
|
||||
<td>{{ item.domain }}</td>
|
||||
<td>
|
||||
<div class="type">
|
||||
<a-select v-model:value="item.type" size="small" :options="challengeTypeOptions" @change="onPlanChanged"></a-select>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding: 0">
|
||||
<div class="plan">
|
||||
<div v-if="item.type === 'dns'" class="plan-dns">
|
||||
<div class="form-item">
|
||||
<span class="label">DNS类型:</span>
|
||||
<span class="input">
|
||||
<fs-dict-select
|
||||
v-model="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>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.type === 'cname'" class="plan-cname">
|
||||
<cname-verify-plan v-model="item.cnameVerifyPlan" @change="onPlanChanged" />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="error">
|
||||
{{ errorMessageRef }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
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 psl from "psl";
|
||||
defineOptions({
|
||||
name: "DomainsVerifyPlanEditor"
|
||||
});
|
||||
|
||||
type DomainVerifyPlanInput = {
|
||||
domain: string;
|
||||
type: "cname" | "dns";
|
||||
dnsProviderType?: string;
|
||||
dnsProviderAccessId?: number;
|
||||
cnameVerifyPlan?: Record<string, CnameRecord>;
|
||||
};
|
||||
type DomainsVerifyPlanInput = {
|
||||
[key: string]: DomainVerifyPlanInput;
|
||||
};
|
||||
|
||||
const challengeTypeOptions = ref<any[]>([
|
||||
{
|
||||
label: "DNS验证",
|
||||
value: "dns"
|
||||
},
|
||||
{
|
||||
label: "CNAME验证",
|
||||
value: "cname"
|
||||
}
|
||||
]);
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: DomainsVerifyPlanInput;
|
||||
domains?: string[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": any;
|
||||
}>();
|
||||
|
||||
const fullscreen = ref(false);
|
||||
function fullscreenExit() {
|
||||
if (fullscreen.value) {
|
||||
fullscreen.value = false;
|
||||
}
|
||||
}
|
||||
const planRef = ref<DomainsVerifyPlanInput>(props.modelValue || {});
|
||||
const dnsProviderTypeDict = dict({
|
||||
url: "pi/dnsProvider/dnsProviderTypeDict"
|
||||
});
|
||||
function onPlanChanged() {
|
||||
debugger;
|
||||
emit("update:modelValue", planRef.value);
|
||||
}
|
||||
|
||||
const errorMessageRef = ref<string>("");
|
||||
function showError(error: string) {
|
||||
errorMessageRef.value = error;
|
||||
}
|
||||
|
||||
type DomainGroup = Record<
|
||||
string,
|
||||
{
|
||||
[key: string]: CnameRecord;
|
||||
}
|
||||
>[];
|
||||
|
||||
function onDomainsChanged(domains: string[]) {
|
||||
console.log("域名变化", domains);
|
||||
if (domains == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domainGroups: DomainGroup = {};
|
||||
for (let domain of domains) {
|
||||
domain = domain.replace("*.", "");
|
||||
const parsed = psl.parse(domain);
|
||||
if (parsed.error) {
|
||||
showError(`域名${domain}解析失败: ${JSON.stringify(parsed.error)}`);
|
||||
continue;
|
||||
}
|
||||
const mainDomain = parsed.domain;
|
||||
let group = domainGroups[mainDomain];
|
||||
if (!group) {
|
||||
group = {};
|
||||
domainGroups[mainDomain] = group;
|
||||
}
|
||||
group[domain] = {
|
||||
id: 0
|
||||
};
|
||||
}
|
||||
|
||||
for (const domain in domainGroups) {
|
||||
let planItem = planRef.value[domain];
|
||||
const subDomains = domainGroups[domain];
|
||||
if (!planItem) {
|
||||
planItem = {
|
||||
domain,
|
||||
type: "cname",
|
||||
cnameVerifyPlan: {
|
||||
...subDomains
|
||||
}
|
||||
};
|
||||
planRef.value[domain] = planItem;
|
||||
} else {
|
||||
const cnamePlan = planItem.cnameVerifyPlan;
|
||||
for (const subDomain in subDomains) {
|
||||
if (!cnamePlan[subDomain]) {
|
||||
cnamePlan[subDomain] = {
|
||||
id: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
for (const subDomain of Object.keys(cnamePlan)) {
|
||||
if (!subDomains[subDomain]) {
|
||||
delete cnamePlan[subDomain];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const domain of Object.keys(planRef.value)) {
|
||||
const mainDomains = Object.keys(domainGroups);
|
||||
if (!mainDomains.includes(domain)) {
|
||||
delete planRef.value[domain];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => {
|
||||
return props.domains;
|
||||
},
|
||||
(domains: string[]) => {
|
||||
onDomainsChanged(domains);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.domains-verify-plan-editor {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
overflow-x: auto;
|
||||
.fullscreen-modal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(74, 74, 74, 0.78);
|
||||
z-index: 1000;
|
||||
padding: 100px;
|
||||
margin: auto;
|
||||
.plan-wrapper {
|
||||
width: 1400px;
|
||||
margin: auto;
|
||||
//background-color: #a3a3a3;
|
||||
//padding: 50px;
|
||||
.plan-box {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen-modal {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.fullscreen-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.plan-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
//table-layout: fixed;
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
text-align: left;
|
||||
padding: 10px 6px;
|
||||
}
|
||||
td {
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
border-left: 1px solid #e8e8e8;
|
||||
padding: 6px 6px;
|
||||
}
|
||||
|
||||
.plan {
|
||||
font-size: 14px;
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
.plan-dns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
.form-item {
|
||||
min-width: 250px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.label {
|
||||
width: 80px;
|
||||
}
|
||||
.input {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.plan-cname {
|
||||
.cname-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.domain {
|
||||
width: 100px;
|
||||
}
|
||||
.cname-record {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { inject, ref, watch } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "CertDomainsGetter"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
inputKey?: string;
|
||||
modelValue?: string[];
|
||||
|
||||
+3
-3
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<a-select class="pi-output-selector" :value="modelValue" :options="options" @update:value="onChanged"> </a-select>
|
||||
<a-select class="output-selector" :value="modelValue" :options="options" @update:value="onChanged"> </a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { inject, onMounted, Ref, ref, watch } from "vue";
|
||||
import { pluginManager } from "../../plugin";
|
||||
|
||||
export default {
|
||||
name: "PiOutputSelector",
|
||||
name: "OutputSelector",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
from: {
|
||||
type: [String, Array]
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "RemoteSelect"
|
||||
});
|
||||
|
||||
const props = defineProps<
|
||||
{
|
||||
watches: string[];
|
||||
@@ -63,14 +67,14 @@ watch(
|
||||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
class="remote-select"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
:options="optionsRef"
|
||||
:value="value"
|
||||
@click="onClick"
|
||||
@update:value="emit('update:value', $event)"
|
||||
/>
|
||||
class="remote-select"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
:options="optionsRef"
|
||||
:value="value"
|
||||
@click="onClick"
|
||||
@update:value="emit('update:value', $event)"
|
||||
/>
|
||||
<div class="helper">
|
||||
{{ message }}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import SynologyIdDeviceGetter from "./synology/device-id-getter.vue";
|
||||
import RemoteSelect from "./common/remote-select.vue";
|
||||
import CertDomainsGetter from "./common/cert-domains-getter.vue";
|
||||
import OutputSelector from "/@/components/plugins/common/output-selector/index.vue";
|
||||
import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selector/index.vue";
|
||||
import DomainsVerifyPlanEditor from "/@/components/plugins/cert/domains-verify-plan-editor/index.vue";
|
||||
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
||||
|
||||
export default {
|
||||
install(app: any) {
|
||||
app.component("OutputSelector", OutputSelector);
|
||||
app.component("DnsProviderSelector", DnsProviderSelector);
|
||||
app.component("DomainsVerifyPlanEditor", DomainsVerifyPlanEditor);
|
||||
app.component("AccessSelector", AccessSelector);
|
||||
|
||||
app.component("SynologyDeviceIdGetter", SynologyIdDeviceGetter);
|
||||
app.component("RemoteSelect", RemoteSelect);
|
||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||
|
||||
@@ -14,6 +14,10 @@ import { defineProps, ref, useAttrs } from "vue";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
|
||||
|
||||
defineOptions({
|
||||
name: "DeviceIdGetter"
|
||||
});
|
||||
|
||||
const props = defineProps<ComponentPropsType>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -68,9 +68,13 @@ function install(app: App, options: any = {}) {
|
||||
},
|
||||
conditionalRender: {
|
||||
match(scope) {
|
||||
if (scope.column.conditionalRenderDisabled) {
|
||||
return false;
|
||||
}
|
||||
if (scope.key === "__blank__") {
|
||||
return false;
|
||||
}
|
||||
|
||||
//不能用 !scope.value , 否则switch组件设置为关之后就消失了
|
||||
const { value, key, props } = scope;
|
||||
return !value && key != "_index" && value != false;
|
||||
@@ -349,8 +353,8 @@ function install(app: App, options: any = {}) {
|
||||
columnProps.column = {};
|
||||
}
|
||||
columnProps.column.resizable = true;
|
||||
if (!columnProps.column.width) {
|
||||
columnProps.column.width = -1;
|
||||
if (columnProps.column.width == null) {
|
||||
columnProps.column.width = 200;
|
||||
} else if (typeof columnProps.column?.width === "string" && columnProps.column.width.indexOf("px") > -1) {
|
||||
columnProps.column.width = parseInt(columnProps.column.width.replace("px", ""));
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import Validator from "async-validator";
|
||||
// 自定义验证器函数
|
||||
function isDomain(rule, value, callback) {
|
||||
function isDomain(rule, value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
let domains: string[] = value;
|
||||
if (typeof value === "string") {
|
||||
domains = value.split(",");
|
||||
}
|
||||
for (const domain of domains) {
|
||||
//域名可以是泛域名,中文域名,数字域名,英文域名,域名中可以包含-和.
|
||||
if (!/^(?:[0-9a-zA-Z\u4e00-\u9fa5-]+\.)+[0-9a-zA-Z\u4e00-\u9fa5-]+$/.test(domain)) {
|
||||
callback(new Error(`域名有误:${domain},请输入正确的域名`));
|
||||
//域名可以是泛域名,中文域名,数字域名,英文域名,域名中可以包含-和. ,可以_开头
|
||||
if (!/^(?:\*\.|[0-9a-zA-Z\u4e00-\u9fa5-]+\.)+[0-9a-zA-Z\u4e00-\u9fa5-]+$/.test(domain)) {
|
||||
throw new Error(`域名有误:${domain},请输入正确的域名`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// 注册自定义验证器
|
||||
Validator.register("domains", isDomain);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const certdResources = [
|
||||
{
|
||||
title: "证书自动化",
|
||||
name: "certd",
|
||||
name: "CertdRoot",
|
||||
path: "/certd",
|
||||
redirect: "/certd/pipeline",
|
||||
meta: {
|
||||
@@ -11,7 +11,7 @@ export const certdResources = [
|
||||
children: [
|
||||
{
|
||||
title: "证书自动化流水线",
|
||||
name: "pipeline",
|
||||
name: "PipelineManager",
|
||||
path: "/certd/pipeline",
|
||||
component: "/certd/pipeline/index.vue",
|
||||
meta: {
|
||||
@@ -20,7 +20,7 @@ export const certdResources = [
|
||||
},
|
||||
{
|
||||
title: "编辑流水线",
|
||||
name: "pipelineEdit",
|
||||
name: "PipelineEdit",
|
||||
path: "/certd/pipeline/detail",
|
||||
component: "/certd/pipeline/detail.vue",
|
||||
meta: {
|
||||
@@ -29,7 +29,7 @@ export const certdResources = [
|
||||
},
|
||||
{
|
||||
title: "执行历史记录",
|
||||
name: "pipelineHistory",
|
||||
name: "PipelineHistory",
|
||||
path: "/certd/history",
|
||||
component: "/certd/history/index.vue",
|
||||
meta: {
|
||||
@@ -38,44 +38,43 @@ export const certdResources = [
|
||||
},
|
||||
{
|
||||
title: "授权管理",
|
||||
name: "access",
|
||||
name: "AccessManager",
|
||||
path: "/certd/access",
|
||||
component: "/certd/access/index.vue",
|
||||
meta: {
|
||||
icon: "ion:disc-outline"
|
||||
icon: "ion:disc-outline",
|
||||
auth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "设置",
|
||||
name: "certdSettings",
|
||||
path: "/certd/settings",
|
||||
redirect: "/certd/settings/email",
|
||||
title: "CNAME记录管理",
|
||||
name: "CnameRecord",
|
||||
path: "/certd/cname/record",
|
||||
component: "/certd/cname/record/index.vue",
|
||||
meta: {
|
||||
icon: "ion:settings-outline",
|
||||
icon: "ion:disc-outline",
|
||||
auth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: "邮箱设置",
|
||||
name: "email",
|
||||
path: "/certd/settings/email",
|
||||
component: "/certd/settings/email-setting.vue",
|
||||
meta: {
|
||||
icon: "ion:mail-outline",
|
||||
auth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "账号信息",
|
||||
name: "userProfile",
|
||||
path: "/certd/mine/user-profile",
|
||||
component: "/certd/mine/user-profile.vue",
|
||||
meta: {
|
||||
icon: "ion:person-outline",
|
||||
auth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "邮箱设置",
|
||||
name: "EmailSetting",
|
||||
path: "/certd/settings/email",
|
||||
component: "/certd/settings/email-setting.vue",
|
||||
meta: {
|
||||
icon: "ion:mail-outline",
|
||||
auth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "账号信息",
|
||||
name: "UserProfile",
|
||||
path: "/certd/mine/user-profile",
|
||||
component: "/certd/mine/user-profile.vue",
|
||||
meta: {
|
||||
icon: "ion:person-outline",
|
||||
auth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import LayoutPass from "/@/layout/layout-pass.vue";
|
||||
import { computed } from "vue";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
export const sysResources = [
|
||||
{
|
||||
title: "系统管理",
|
||||
name: "sys",
|
||||
name: "SysRoot",
|
||||
path: "/sys",
|
||||
redirect: "/sys/settings",
|
||||
component: LayoutPass,
|
||||
@@ -17,7 +15,7 @@ export const sysResources = [
|
||||
children: [
|
||||
{
|
||||
title: "权限管理",
|
||||
name: "authority",
|
||||
name: "AuthorityManager",
|
||||
path: "/sys/authority",
|
||||
redirect: "/sys/authority/permission",
|
||||
meta: {
|
||||
@@ -28,61 +26,62 @@ export const sysResources = [
|
||||
children: [
|
||||
{
|
||||
title: "权限资源管理",
|
||||
name: "permission",
|
||||
name: "PermissionManager",
|
||||
path: "/sys/authority/permission",
|
||||
component: "/sys/authority/permission/index.vue",
|
||||
meta: {
|
||||
icon: "ion:list-outline",
|
||||
//需要校验权限
|
||||
permission: "sys:auth:per:view"
|
||||
},
|
||||
path: "/sys/authority/permission",
|
||||
component: "/sys/authority/permission/index.vue"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "角色管理",
|
||||
name: "role",
|
||||
name: "RoleManager",
|
||||
path: "/sys/authority/role",
|
||||
component: "/sys/authority/role/index.vue",
|
||||
meta: {
|
||||
icon: "ion:people-outline",
|
||||
permission: "sys:auth:role:view"
|
||||
},
|
||||
path: "/sys/authority/role",
|
||||
component: "/sys/authority/role/index.vue"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "用户管理",
|
||||
name: "user",
|
||||
name: "UserManager",
|
||||
path: "/sys/authority/user",
|
||||
component: "/sys/authority/user/index.vue",
|
||||
meta: {
|
||||
icon: "ion:person-outline",
|
||||
permission: "sys:auth:user:view"
|
||||
},
|
||||
path: "/sys/authority/user",
|
||||
component: "/sys/authority/user/index.vue"
|
||||
},
|
||||
{
|
||||
title: "账号绑定",
|
||||
name: "account",
|
||||
meta: {
|
||||
icon: "ion:golf-outline",
|
||||
permission: "sys:settings:view"
|
||||
},
|
||||
path: "/sys/account",
|
||||
component: "/sys/account/index.vue"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "系统设置",
|
||||
name: "settings",
|
||||
name: "SysSettings",
|
||||
path: "/sys/settings",
|
||||
component: "/sys/settings/index.vue",
|
||||
meta: {
|
||||
icon: "ion:settings-outline",
|
||||
permission: "sys:settings:view"
|
||||
},
|
||||
path: "/sys/settings",
|
||||
component: "/sys/settings/index.vue"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "CNAME服务设置",
|
||||
name: "CnameSetting",
|
||||
path: "/sys/cname/provider",
|
||||
component: "/sys/cname/provider/index.vue",
|
||||
meta: {
|
||||
icon: "ion:settings-outline",
|
||||
permission: "sys:settings:view"
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "站点个性化",
|
||||
name: "site",
|
||||
name: "SiteSetting",
|
||||
path: "/sys/site",
|
||||
component: "/sys/site/index.vue",
|
||||
meta: {
|
||||
show: () => {
|
||||
const settingStore = useSettingStore();
|
||||
@@ -90,9 +89,8 @@ export const sysResources = [
|
||||
},
|
||||
icon: "ion:document-text-outline",
|
||||
permission: "sys:settings:view"
|
||||
},
|
||||
component: "/sys/site/index.vue"
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// title: "商业版设置",
|
||||
// name: "SysCommercial",
|
||||
@@ -120,6 +118,16 @@ export const sysResources = [
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
{
|
||||
title: "账号绑定",
|
||||
name: "AccountBind",
|
||||
path: "/sys/account",
|
||||
component: "/sys/account/index.vue",
|
||||
meta: {
|
||||
icon: "ion:golf-outline",
|
||||
permission: "sys:settings:view"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -20,7 +20,7 @@ div#app {
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: 0;
|
||||
margin-top:0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.fs-desc {
|
||||
@@ -69,13 +69,13 @@ h1, h2, h3, h4, h5, h6 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex-col{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll-y{
|
||||
overflow-y: auto;
|
||||
.scroll-y {
|
||||
overflow-y: auto;
|
||||
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -187,21 +188,34 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
|
||||
|
||||
.deleted{
|
||||
.deleted {
|
||||
color: #c7c7c7;
|
||||
//删除线
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.cursor-move{
|
||||
.cursor-move {
|
||||
cursor: move !important;
|
||||
}
|
||||
.cursor-pointer{
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.helper{
|
||||
display: inline-block;
|
||||
color: #aeaeae;
|
||||
font-size: 12px;
|
||||
.helper {
|
||||
display: inline-block;
|
||||
color: #aeaeae;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.fs-copyable {
|
||||
.text {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
position: relative !important;
|
||||
/* right: 0; */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="pi-access-selector">
|
||||
<div class="access-selector">
|
||||
<span v-if="target.name" class="mr-5 cd-flex-inline">
|
||||
<span class="mr-5">{{ target.name }}</span>
|
||||
<fs-icon class="cd-icon-button" icon="ion:close-circle-outline" @click="clear"></fs-icon>
|
||||
</span>
|
||||
<span v-else class="mlr-5 gray">请选择</span>
|
||||
<a-button class="ml-5" @click="chooseForm.open">选择</a-button>
|
||||
<span v-else class="mlr-5 text-gray">{{ placeholder }}</span>
|
||||
<a-button class="ml-5" :size="size" @click="chooseForm.open">选择</a-button>
|
||||
<a-form-item-rest v-if="chooseForm.show">
|
||||
<a-modal v-model:open="chooseForm.show" title="选择授权提供者" width="900px" @ok="chooseForm.ok">
|
||||
<div style="height: 400px; position: relative">
|
||||
@@ -23,7 +23,7 @@ import CertAccessModal from "./access/index.vue";
|
||||
import { GetProviderDefineByAccessType } from "../api";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PiAccessSelector",
|
||||
name: "AccessSelector",
|
||||
components: { CertAccessModal },
|
||||
props: {
|
||||
modelValue: {
|
||||
@@ -33,6 +33,14 @@ export default defineComponent({
|
||||
type: {
|
||||
type: String,
|
||||
default: "aliyun"
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择"
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: "middle"
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
|
||||
export default defineComponent({
|
||||
name: "CertdAccess",
|
||||
name: "AccessManager",
|
||||
setup() {
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: {} });
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/cname/record";
|
||||
|
||||
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 + "/deleteByIds",
|
||||
method: "post",
|
||||
data: { ids }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import * as api from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
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) => {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
});
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
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
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right"
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
domain: {
|
||||
title: "被代理域名",
|
||||
type: "text",
|
||||
search: {
|
||||
show: true
|
||||
},
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
hostRecord: {
|
||||
title: "主机记录",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
cellRender: ({ value }) => {
|
||||
return <fs-copyable v-model={value} />;
|
||||
}
|
||||
}
|
||||
},
|
||||
recordValue: {
|
||||
title: "请设置CNAME",
|
||||
type: "copyable",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 500
|
||||
}
|
||||
},
|
||||
cnameProviderId: {
|
||||
title: "CNAME提供者",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/cname/provider/list",
|
||||
value: "id",
|
||||
label: "domain"
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
onDictChange: ({ form, dict }) => {
|
||||
if (!form.cnameProviderId) {
|
||||
const item = dict.data.find((item) => item.isDefault);
|
||||
if (item) {
|
||||
form.cnameProviderId = item.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
column: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
||||
{ label: "验证中", value: "validating", color: "primary" },
|
||||
{ label: "验证成功", value: "valid", color: "success" },
|
||||
{ label: "验证失败", value: "failed", color: "error" }
|
||||
]
|
||||
}),
|
||||
addForm: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
triggerValidate: {
|
||||
title: "验证",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
conditionalRenderDisabled: true,
|
||||
width: 100,
|
||||
align: "center",
|
||||
cellRender({ row, value }) {
|
||||
if (row.status === "valid") {
|
||||
return "-";
|
||||
}
|
||||
return (
|
||||
<a-button size={"small"} type={"primary"}>
|
||||
点击验证
|
||||
</a-button>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
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,51 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">CNAME记录管理</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 { onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { DeleteBatch } from "/@/views/certd/history/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();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
@@ -23,6 +23,10 @@ import { Ref, ref } from "vue";
|
||||
import { CrudOptions, useColumns, useFormWrapper } from "@fast-crud/fast-crud";
|
||||
import { notification } from "ant-design-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "UserProfile"
|
||||
});
|
||||
|
||||
const userInfo: Ref = ref({});
|
||||
|
||||
const getUserInfo = async () => {
|
||||
|
||||
@@ -3,10 +3,12 @@ import { PluginGroup } from "@certd/pipeline";
|
||||
import { useReference } from "/@/use/use-refrence";
|
||||
import _ from "lodash-es";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
export default function (certPluginGroup: PluginGroup, formWrapperRef: any): CreateCrudOptionsRet {
|
||||
const inputs: any = {};
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
for (const plugin of certPluginGroup.plugins) {
|
||||
for (const inputKey in plugin.input) {
|
||||
if (inputs[inputKey]) {
|
||||
@@ -66,8 +68,8 @@ export default function (certPluginGroup: PluginGroup, formWrapperRef: any): Cre
|
||||
render: () => {
|
||||
return (
|
||||
<ul>
|
||||
<li>JS-ACME:如果你的域名DNS属于阿里云、腾讯云、Cloudflare、西部数码可以选择用它来申请</li>
|
||||
<li>Lego-ACME:基于Lego实现,支持海量DNS提供商,熟悉LEGO的用户可以使用</li>
|
||||
<li>JS-ACME:使用简单方便,功能强大【推荐】</li>
|
||||
<li>Lego-ACME:基于Lego实现,支持海量DNS提供商,熟悉LEGO的用户可以使用【即将废弃】</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -313,6 +313,9 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||
name: "a-input"
|
||||
}
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 350,
|
||||
sorter: true,
|
||||
|
||||
+29
-21
@@ -73,27 +73,28 @@
|
||||
<a-alert type="info" :message="currentPlugin.title" :description="currentPlugin.desc"> </a-alert>
|
||||
</div>
|
||||
</template>
|
||||
<a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<fs-form-item
|
||||
v-model="currentStep.title"
|
||||
:item="{
|
||||
title: '任务名称',
|
||||
key: 'title',
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value'
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
}"
|
||||
:get-context-fn="blankFn"
|
||||
/>
|
||||
<template v-for="(item, key) in currentPlugin.input" :key="key">
|
||||
<fs-form-item v-if="item.show !== false" v-model="currentStep.input[key]" :item="item" :get-context-fn="blankFn" />
|
||||
</template>
|
||||
|
||||
<fs-form-item v-model="currentStep.strategy.runStrategy" :item="runStrategyProps" :get-context-fn="blankFn" />
|
||||
</a-form>
|
||||
<div class="w-100 h-100">
|
||||
<a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<fs-form-item
|
||||
v-model="currentStep.title"
|
||||
:item="{
|
||||
title: '任务名称',
|
||||
key: 'title',
|
||||
component: {
|
||||
name: 'a-input',
|
||||
vModel: 'value'
|
||||
},
|
||||
rules: [{ required: true, message: '此项必填' }]
|
||||
}"
|
||||
:get-context-fn="blankFn"
|
||||
/>
|
||||
<template v-for="(item, key) in currentPlugin.input" :key="key">
|
||||
<fs-form-item v-if="item.show !== false" v-model="currentStep.input[key]" :item="item" :get-context-fn="blankFn" />
|
||||
</template>
|
||||
|
||||
<fs-form-item v-model="currentStep.strategy.runStrategy" :item="runStrategyProps" :get-context-fn="blankFn" />
|
||||
</a-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div v-if="editMode" class="bottom-button">
|
||||
<a-button type="primary" @click="stepSave"> 确定 </a-button>
|
||||
@@ -394,17 +395,24 @@ export default {
|
||||
&.fullscreen {
|
||||
.pi-step-form {
|
||||
.body {
|
||||
margin: auto;
|
||||
.step-plugin {
|
||||
width: 16.666666%;
|
||||
}
|
||||
.step-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 1300px;
|
||||
.fs-form-item {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
.bottom-button {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #header>
|
||||
<div class="title">
|
||||
<fs-button class="back" icon="ion:chevron-back-outline" @click="goBack"></fs-button>
|
||||
<pi-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></pi-editable>
|
||||
<text-editable v-model="pipeline.title" :hover-show="false" :disabled="!editMode"></text-editable>
|
||||
</div>
|
||||
<div class="more">
|
||||
<template v-if="editMode">
|
||||
@@ -24,7 +24,7 @@
|
||||
<template #header>
|
||||
<div class="stage first-stage">
|
||||
<div class="title stage-move-handle">
|
||||
<pi-editable model-value="触发源" :disabled="true" />
|
||||
<text-editable model-value="触发源" :disabled="true" />
|
||||
</div>
|
||||
<div class="tasks">
|
||||
<div class="task-container first-task">
|
||||
@@ -68,7 +68,7 @@
|
||||
<template #item="{ element: stage, index }">
|
||||
<div :key="stage.id" class="stage" :class="{ 'last-stage': isLastStage(index) }">
|
||||
<div class="title">
|
||||
<pi-editable v-model="stage.title" :disabled="!editMode"></pi-editable>
|
||||
<text-editable v-model="stage.title" :disabled="!editMode"></text-editable>
|
||||
<div v-plus class="icon-box stage-move-handle">
|
||||
<fs-icon v-if="editMode" title="拖动排序" icon="ion:move-outline"></fs-icon>
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@
|
||||
<template #footer>
|
||||
<div v-if="editMode" class="stage last-stage">
|
||||
<div class="title">
|
||||
<pi-editable model-value="新阶段" :disabled="true" />
|
||||
<text-editable model-value="新阶段" :disabled="true" />
|
||||
</div>
|
||||
<div class="tasks">
|
||||
<div class="task-container first-task">
|
||||
@@ -188,7 +188,7 @@
|
||||
</div>
|
||||
<div v-else class="stage last-stage">
|
||||
<div class="title">
|
||||
<pi-editable model-value="结束" :disabled="true" />
|
||||
<text-editable model-value="结束" :disabled="true" />
|
||||
</div>
|
||||
<div v-if="pipeline.notifications?.length > 0" class="tasks">
|
||||
<div v-for="(item, index) of pipeline.notifications" :key="index" class="task-container" :class="{ 'first-task': index == 0 }">
|
||||
@@ -704,7 +704,7 @@ export default defineComponent({
|
||||
.back {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.pi-editable {
|
||||
.text-editable {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,12 +79,15 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from "vue";
|
||||
import * as api from "./api";
|
||||
import * as emailApi from "./api.email";
|
||||
|
||||
import { SettingKeys } from "./api";
|
||||
import * as emailApi from "./api.email";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
defineOptions({
|
||||
name: "EmailSetting"
|
||||
});
|
||||
|
||||
interface FormState {
|
||||
host: string;
|
||||
port: number;
|
||||
|
||||
@@ -18,6 +18,10 @@ import { useUserStore } from "/@/store/modules/user";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import * as api from "./api";
|
||||
import { notification } from "ant-design-vue";
|
||||
|
||||
defineOptions({
|
||||
name: "AccountBind"
|
||||
});
|
||||
const iframeRef = ref();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
@@ -29,7 +29,7 @@ import { usePermission } from "/src/plugin/permission";
|
||||
import { useFs, useUi } from "@fast-crud/fast-crud";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AuthorityPermission",
|
||||
name: "AuthorityManager",
|
||||
components: { FsPermissionTree },
|
||||
setup() {
|
||||
// 此处传入permission进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
|
||||
|
||||
@@ -5,7 +5,15 @@
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding" />
|
||||
<a-modal v-model:open="authzDialogVisible" width="860px" title="分配权限" @ok="updatePermission">
|
||||
<fs-permission-tree ref="permissionTreeRef" v-model:checkedKeys="checkedKeys" :tree="permissionTreeData" :editable="false" checkable :replace-fields="{ key: 'id', label: 'title' }"> </fs-permission-tree>
|
||||
<fs-permission-tree
|
||||
ref="permissionTreeRef"
|
||||
v-model:checked-keys="checkedKeys"
|
||||
:tree="permissionTreeData"
|
||||
:editable="false"
|
||||
checkable
|
||||
:replace-fields="{ key: 'id', label: 'title' }"
|
||||
>
|
||||
</fs-permission-tree>
|
||||
</a-modal>
|
||||
</fs-page>
|
||||
</template>
|
||||
@@ -84,7 +92,7 @@ function useAuthz() {
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "AuthorityRole",
|
||||
name: "RoleManager",
|
||||
components: { FsPermissionTree },
|
||||
setup() {
|
||||
//授权配置
|
||||
|
||||
@@ -12,7 +12,7 @@ import { defineComponent, ref, onMounted } from "vue";
|
||||
import { useCrud, useExpose, useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
export default defineComponent({
|
||||
name: "AuthorityUser",
|
||||
name: "UserManager",
|
||||
setup() {
|
||||
// 初始化crud配置
|
||||
// 此处传入权限前缀进行通用按钮权限设置,会通过commonOptions去设置actionbar和rowHandle的按钮的show属性
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/sys/cname/provider";
|
||||
|
||||
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 + "/deleteByIds",
|
||||
method: "post",
|
||||
data: { ids }
|
||||
});
|
||||
}
|
||||
|
||||
export async function SetDefault(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/setDefault",
|
||||
method: "post",
|
||||
data: { id }
|
||||
});
|
||||
}
|
||||
|
||||
export async function SetDisabled(id: any, disabled: boolean) {
|
||||
return await request({
|
||||
url: apiPrefix + "/setDisabled",
|
||||
method: "post",
|
||||
data: { id, disabled }
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
import * as api from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes, utils } from "@fast-crud/fast-crud";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import { Modal } from "ant-design-vue";
|
||||
|
||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
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) => {
|
||||
form.content = JSON.stringify({
|
||||
title: form.title
|
||||
});
|
||||
const res = await api.AddObj(form);
|
||||
return res;
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
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
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 200,
|
||||
fixed: "right"
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: {
|
||||
width: 100
|
||||
},
|
||||
form: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
domain: {
|
||||
title: "域名",
|
||||
type: "text",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
form: {
|
||||
helper: "CNAME域名一旦确定不可修改",
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
dnsProviderType: {
|
||||
title: "DNS提供商",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "pi/dnsProvider/list",
|
||||
value: "key",
|
||||
label: "title"
|
||||
}),
|
||||
form: {
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
accessId: {
|
||||
title: "DNS提供商授权",
|
||||
type: "dict-select",
|
||||
dict: dict({
|
||||
url: "/pi/access/list",
|
||||
value: "id",
|
||||
label: "name"
|
||||
}),
|
||||
form: {
|
||||
component: {
|
||||
name: "access-selector",
|
||||
vModel: "modelValue",
|
||||
type: compute(({ form }) => {
|
||||
return form.dnsProviderType;
|
||||
})
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }]
|
||||
},
|
||||
column: {
|
||||
width: 150,
|
||||
component: {
|
||||
color: "auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
isDefault: {
|
||||
title: "是否默认",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "是", value: true, color: "success" },
|
||||
{ label: "否", value: false, color: "default" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
rules: [{ required: true, message: "请选择是否默认" }]
|
||||
},
|
||||
column: {
|
||||
align: "center",
|
||||
width: 100
|
||||
}
|
||||
},
|
||||
setDefault: {
|
||||
title: "设置默认",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
conditionalRenderDisabled: true,
|
||||
cellRender: ({ row }) => {
|
||||
if (row.isDefault) {
|
||||
return;
|
||||
}
|
||||
const onClick = async () => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: `确定要设置为默认吗?`,
|
||||
onOk: async () => {
|
||||
await api.SetDefault(row.id);
|
||||
await crudExpose.doRefresh();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<a-button type={"link"} size={"small"} onClick={onClick}>
|
||||
设为默认
|
||||
</a-button>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
title: "禁用/启用",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: false, color: "success" },
|
||||
{ label: "禁用", value: true, color: "error" }
|
||||
]
|
||||
}),
|
||||
form: {
|
||||
value: false
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
component: {
|
||||
title: "点击可禁用/启用",
|
||||
on: {
|
||||
async click({ value, row }) {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: `确定要${!value ? "禁用" : "启用"}吗?`,
|
||||
onOk: async () => {
|
||||
await api.SetDisabled(row.id, !value);
|
||||
await crudExpose.doRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160,
|
||||
align: "center"
|
||||
}
|
||||
},
|
||||
updateTime: {
|
||||
title: "更新时间",
|
||||
type: "datetime",
|
||||
form: {
|
||||
show: false
|
||||
},
|
||||
column: {
|
||||
show: true,
|
||||
width: 160
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<fs-page class="page-cert">
|
||||
<template #header>
|
||||
<div class="title">
|
||||
CNAME服务配置
|
||||
<span class="sub">
|
||||
此处配置的域名作为其他域名校验的代理,当别的域名需要申请证书时,通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书,也无需填写AccessSecret。
|
||||
</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 { onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { DeleteBatch } from "/@/views/certd/history/api";
|
||||
|
||||
defineOptions({
|
||||
name: "CnameProvider"
|
||||
});
|
||||
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();
|
||||
});
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
@@ -47,6 +47,10 @@ import { PublicSettingsSave, SettingKeys } from "./api";
|
||||
import { notification } from "ant-design-vue";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
|
||||
defineOptions({
|
||||
name: "SysSettings"
|
||||
});
|
||||
|
||||
interface FormState {
|
||||
registerEnabled: boolean;
|
||||
managerOtherUserPipeline: boolean;
|
||||
|
||||
@@ -68,6 +68,10 @@ import { notification } from "ant-design-vue";
|
||||
import { useSettingStore } from "/src/store/modules/settings";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
|
||||
defineOptions({
|
||||
name: "SiteSetting"
|
||||
});
|
||||
|
||||
interface FormState {
|
||||
title: string;
|
||||
slogan: string;
|
||||
|
||||
Reference in New Issue
Block a user