mirror of
https://github.com/certd/certd.git
synced 2026-07-29 16:47:36 +08:00
refactor: move
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="page-index flex-center">
|
||||
<H2 class="title">CERT-D</H2>
|
||||
<div class="page-body">
|
||||
|
||||
<a-tabs @change="callback">
|
||||
<a-tab-pane key="1" tab="创建新证书">
|
||||
<div class="create-from-domains">
|
||||
<div class="input-row flex-row">
|
||||
<a-select
|
||||
size="large"
|
||||
mode="tags"
|
||||
:placeholder="$t('please.input.domain')"
|
||||
v-model:value="formData.cert.domains"
|
||||
:open="false"
|
||||
></a-select>
|
||||
<div class="row-append">
|
||||
<a-button size="large" type="primary" @click="createFromDomain">创建新证书</a-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="helper">
|
||||
支持泛域名(例如*.test.yourdomain.com)<br/>
|
||||
支持多个域名打包到一张证书(输入一个域名后回车,再输下一个)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="从配置导入" force-render>
|
||||
<a-textarea v-model:value="optionsText" class="textarea" type="textarea" :auto-size="autoSize" allow-clear></a-textarea>
|
||||
<a-button class="mt-10" type="primary" @click="createFromText">导入</a-button>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { reactive, toRaw, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
export default {
|
||||
setup () {
|
||||
const formData = reactive({
|
||||
cert: {
|
||||
domains: [],
|
||||
email: '',
|
||||
dnsProvider: { type: undefined }
|
||||
}
|
||||
})
|
||||
const router = useRouter()
|
||||
const createFromDomain = () => {
|
||||
goToDetail(JSON.stringify(formData))
|
||||
}
|
||||
|
||||
const goToDetail = (options) => {
|
||||
router.push({ name: 'detail', params: { options } })
|
||||
}
|
||||
const autoSize = reactive({ minRows: 8, maxRows: 10 })
|
||||
|
||||
const optionsText = ref()
|
||||
|
||||
const createFromText = () => {
|
||||
try {
|
||||
JSON.parse(optionsText.value)
|
||||
} catch (e) {
|
||||
throw new Error('json格式有误', e)
|
||||
}
|
||||
goToDetail(optionsText.value)
|
||||
}
|
||||
|
||||
return {
|
||||
createFromDomain,
|
||||
formData,
|
||||
autoSize,
|
||||
optionsText,
|
||||
createFromText
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.page-index{
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
&.flex-center{
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.title{
|
||||
margin:50px;
|
||||
}
|
||||
.page-body{
|
||||
min-width: 700px;width: 60%
|
||||
}
|
||||
.create-from-domains{
|
||||
width:100%;
|
||||
.input-row{
|
||||
width:100%;
|
||||
.ant-select{
|
||||
flex:1;
|
||||
}
|
||||
.row-append{
|
||||
padding-left:10px
|
||||
}
|
||||
}
|
||||
}
|
||||
.helper{
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
.ant-tabs-bar {
|
||||
margin: 0 0 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
title="证书申请配置"
|
||||
placement="right"
|
||||
:closable="true"
|
||||
width="600px"
|
||||
v-model:visible="visible"
|
||||
:after-visible-change="afterVisibleChange"
|
||||
>
|
||||
|
||||
<d-container>
|
||||
<a-form class="domain-form" :model="formData" :scrollToFirstError="true" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
|
||||
<h3>域名信息</h3>
|
||||
<a-form-item :label="$t('domain')" v-bind="validateInfos.domains">
|
||||
<a-select
|
||||
mode="tags"
|
||||
:placeholder="$t('please.input.domain')"
|
||||
v-model:value="formData.domains"
|
||||
:open="false"
|
||||
></a-select>
|
||||
<template #extra >
|
||||
例如:*.yourdomain.com,输入完成后回车,支持多个
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('email')" v-bind="validateInfos.email">
|
||||
<a-input v-model:value="formData.email"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="dns验证" v-bind="validateInfos['dnsProvider.type']">
|
||||
<a-select v-model:value="formData.dnsProvider.type" @change="onCurrentDnsProviderChanged">
|
||||
<a-select-option v-for="item of dnsProviderDefineList" :key="item.name" :value="item.name">{{item.label}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-if="currentDnsProviderDefine">
|
||||
<a-form-item v-for="(item,key) in currentDnsProviderDefine.input" v-bind="item.component || {}" :key="key" :label="item.label" :name="'dnsProvider.'+key">
|
||||
<component-render v-model:value="formData.dnsProvider[key]" v-bind="item.component || {}"></component-render>
|
||||
<template #extra v-if="item.desc" >
|
||||
{{item.desc}}
|
||||
</template>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item label="CA" v-bind="validateInfos.ca">
|
||||
<a-radio-group v-model:value="formData.ca" >
|
||||
<a-radio value="LetEncrypt">
|
||||
LetEncrypt
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<h3>CSR <span>必须全英文</span></h3>
|
||||
<a-form-item label="国家" v-bind="validateInfos['csr.country']">
|
||||
<a-input v-model:value="formData.csr.country"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="省份" v-bind="validateInfos['csr.state']">
|
||||
<a-input v-model:value="formData.csr.state"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="市区" v-bind="validateInfos['csr.locality']">
|
||||
<a-input v-model:value="formData.csr.locality"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="组织" v-bind="validateInfos['csr.organization']">
|
||||
<a-input v-model:value="formData.csr.organization"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="部门" v-bind="validateInfos['csr.organizationUnit']">
|
||||
<a-input v-model:value="formData.csr.organizationUnit"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="联系人邮箱">
|
||||
<a-input v-model:value="formData.csr.emailAddress"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="onSubmit">
|
||||
确定
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
</d-container>
|
||||
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useForm } from '@ant-design-vue/use'
|
||||
import dnsProviderApi from '../../../api/api.dns-providers'
|
||||
import _ from 'lodash-es'
|
||||
|
||||
function useDrawer () {
|
||||
const visible = ref(false)
|
||||
const afterVisibleChange = (val) => {
|
||||
console.log('visible', val)
|
||||
}
|
||||
const open = () => {
|
||||
visible.value = true
|
||||
}
|
||||
const close = () => {
|
||||
visible.value = false
|
||||
}
|
||||
return {
|
||||
afterVisibleChange,
|
||||
open,
|
||||
close,
|
||||
visible
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'cert-form',
|
||||
emits: ['update:accessProviders', 'update:cert'],
|
||||
// 属性定义
|
||||
props: {
|
||||
cert: {
|
||||
type: Object
|
||||
},
|
||||
accessProviders: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
setup (props, context) {
|
||||
const drawer = useDrawer()
|
||||
|
||||
const certFormData = {
|
||||
domains: [],
|
||||
email: undefined,
|
||||
dnsProvider: { type: undefined, accessProvider: undefined },
|
||||
ca: 'LetEncrypt',
|
||||
csr: {
|
||||
country: '',
|
||||
state: 'GuangDong',
|
||||
locality: 'ShengZhen',
|
||||
organization: 'CertD Org.',
|
||||
organizationUnit: 'IT Department',
|
||||
emailAddress: undefined
|
||||
}
|
||||
}
|
||||
const formData = reactive(certFormData)
|
||||
watch(props.cert, () => {
|
||||
console.log('cert props')
|
||||
_.merge(formData, props.cert)
|
||||
}, { immediate: true })
|
||||
|
||||
const rules = reactive({
|
||||
domains: [{
|
||||
type: 'array',
|
||||
required: true,
|
||||
message: '请输入域名'
|
||||
}],
|
||||
email: [{
|
||||
type: 'email',
|
||||
required: true,
|
||||
message: '请输入正确的邮箱'
|
||||
}],
|
||||
'dnsProvider.type': [{
|
||||
required: true,
|
||||
message: '请选择dns类型'
|
||||
}],
|
||||
'dnsProvider.accessProvider': [{
|
||||
required: true,
|
||||
message: '请选择dns授权提供者'
|
||||
}],
|
||||
'csr.country': [{ required: true, message: '请输入国家代码' }],
|
||||
'csr.state': [{ required: true, message: '请输入省份' }],
|
||||
'csr.locality': [{ required: true, message: '请输入市区' }],
|
||||
'csr.organization': [{ required: false, message: '请输入组织名称' }],
|
||||
'csr.organizationUnit': [{ required: false, message: '请输入部门名称' }],
|
||||
'csr.emailAddress': [{ required: false, message: '请输入邮箱' }]
|
||||
})
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { resetFields, validate, validateInfos, mergeValidateInfo } = useForm(formData, rules)
|
||||
|
||||
console.log('validateInfos', validateInfos)
|
||||
const onSubmit = async e => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
await validate()
|
||||
context.emit('update:cert', formData)
|
||||
drawer.close()
|
||||
} catch (err) {
|
||||
console.error('表单校验错误', err)
|
||||
}
|
||||
}
|
||||
const reset = () => {
|
||||
resetFields()
|
||||
}
|
||||
|
||||
const providerManagerRef = ref(null)
|
||||
const providerManagerOpen = () => {
|
||||
console.log('providerManagerRef', providerManagerRef)
|
||||
if (providerManagerRef.value) {
|
||||
providerManagerRef.value.open()
|
||||
}
|
||||
}
|
||||
const accessProvidersUpdate = (val) => {
|
||||
console.log('accessUpdate', val)
|
||||
context.emit('update:accessProviders', val)
|
||||
}
|
||||
|
||||
const dnsProviderDefineList = ref()
|
||||
const currentDnsProviderDefine = ref()
|
||||
const onCreate = async () => {
|
||||
const list = await dnsProviderApi.list()
|
||||
dnsProviderDefineList.value = list
|
||||
onCurrentDnsProviderChanged(formData?.dnsProvider?.type)
|
||||
}
|
||||
const onCurrentDnsProviderChanged = (type) => {
|
||||
if (type == null) {
|
||||
return
|
||||
}
|
||||
|
||||
formData.dnsProvider.type = type
|
||||
formData.dnsProvider.accessProvider = null
|
||||
for (const item of dnsProviderDefineList.value) {
|
||||
if (item.name === type) {
|
||||
currentDnsProviderDefine.value = item
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
onCreate()
|
||||
|
||||
return {
|
||||
labelCol: { span: 4 },
|
||||
wrapperCol: { span: 18 },
|
||||
formData,
|
||||
onSubmit,
|
||||
reset,
|
||||
validateInfos,
|
||||
providerManagerRef,
|
||||
providerManagerOpen,
|
||||
accessProvidersUpdate,
|
||||
dnsProviderDefineList,
|
||||
onCurrentDnsProviderChanged,
|
||||
currentDnsProviderDefine,
|
||||
...drawer
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.ant-form.domain-form {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 10px 24px;
|
||||
|
||||
h3 {
|
||||
span {
|
||||
font-weight: 200;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
placement="right"
|
||||
:closable="true"
|
||||
width="600px"
|
||||
v-model:visible="taskDrawerVisible"
|
||||
:after-visible-change="taskDrawerOnAfterVisibleChange"
|
||||
>
|
||||
|
||||
<template #title>
|
||||
编辑任务
|
||||
<a-button @click="taskDelete()">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-if="currentTask">
|
||||
<d-container v-if="currentTask._isAdd" class="task-edit-form">
|
||||
<a-row :gutter="10">
|
||||
<a-col v-for="(item,index) of taskPluginDefineList" :key="index" class="task-plugin" :span="12">
|
||||
<a-card hoverable :class="{'current':item.name === currentTask.type}"
|
||||
@click="taskTypeSelected(item)" @dblclick="taskTypeSelected(item);taskTypeSave()">
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<a-avatar :src="item.icon||'/images/plugin.png'"/>
|
||||
<span class="title">{{ item.label }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<span :title="item.desc">{{ item.desc }}</span>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-button type="primary" @click="taskTypeSave">
|
||||
确定
|
||||
</a-button>
|
||||
</d-container>
|
||||
<d-container v-else class="d-container" >
|
||||
<a-form ref="taskFormRef" class="task-form" :model="currentTask" :label-col="labelCol" :wrapper-col="wrapperCol">
|
||||
<a-form-item label="任务名称" name="taskName" :rules="rules.name">
|
||||
<a-input
|
||||
placeholder="请输入任务名称"
|
||||
v-model:value="currentTask.taskName"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-for="(item,key) in currentPlugin.input" v-bind="item.component || {}" :key="key" :label="item.label" :name="'props.'+key">
|
||||
<component-render v-model:value="currentTask['props.'+key]" v-bind="item.component || {}"></component-render>
|
||||
<template #extra v-if="item.desc" >
|
||||
{{item.desc}}
|
||||
</template>
|
||||
</a-form-item>
|
||||
|
||||
</a-form>
|
||||
|
||||
<template #footer>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" @click="taskSave">
|
||||
确定
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</d-container>
|
||||
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { message } from 'ant-design-vue'
|
||||
import pluginsApi from '../../../api/api.plugins'
|
||||
import { ref } from 'vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import _ from 'lodash-es'
|
||||
/**
|
||||
* task drawer
|
||||
* @returns
|
||||
*/
|
||||
function useTaskForm (context) {
|
||||
const taskPluginDefineList = ref([])
|
||||
const onCreated = async () => {
|
||||
const plugins = await pluginsApi.list()
|
||||
console.log('plugins', plugins)
|
||||
taskPluginDefineList.value = plugins
|
||||
}
|
||||
|
||||
onCreated()
|
||||
|
||||
const currentTask = ref({ taskName: undefined })
|
||||
const currentTaskIndex = ref()
|
||||
const currentDeploy = ref()
|
||||
const currentPlugin = ref(null)
|
||||
const taskFormRef = ref(null)
|
||||
const taskDrawerVisible = ref(false)
|
||||
const rules = ref({
|
||||
name: [{
|
||||
type: 'string',
|
||||
required: true,
|
||||
message: '请输入名称'
|
||||
}]
|
||||
})
|
||||
const taskAdd = (deploy) => {
|
||||
const task = { taskName: '新任务', type: undefined, _isAdd: true }
|
||||
currentDeploy.value = deploy
|
||||
currentDeploy.value.tasks.push(task)
|
||||
const index = deploy.tasks.length - 1
|
||||
currentTask.value = deploy.tasks[index]
|
||||
currentTaskIndex.value = index
|
||||
taskDrawerShow()
|
||||
console.log('currentTaskTypeChanged:', currentTask.value)
|
||||
}
|
||||
|
||||
const taskTypeSelected = (item) => {
|
||||
currentTask.value.type = item.name
|
||||
currentTask.value.taskName = item.label
|
||||
console.log('currentTaskTypeChanged:', currentTask.value)
|
||||
}
|
||||
|
||||
const taskTypeSave = () => {
|
||||
currentTask.value._isAdd = false
|
||||
if (currentTask.value.type == null) {
|
||||
message.warn('请先选择类型')
|
||||
return
|
||||
}
|
||||
// 给task的input设置默认值
|
||||
changeCurrentPlugin(currentTask.value)
|
||||
|
||||
if (currentTask.value.props) {
|
||||
currentTask.value.props = {}
|
||||
}
|
||||
for (const key in currentPlugin.value.input) {
|
||||
const input = currentPlugin.value.input[key]
|
||||
if (input.default != null) {
|
||||
currentTask.value.props[key] = input.default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const taskDrawerShow = () => {
|
||||
taskDrawerVisible.value = true
|
||||
}
|
||||
const taskDrawerClose = () => {
|
||||
taskDrawerVisible.value = false
|
||||
}
|
||||
|
||||
const taskDrawerOnAfterVisibleChange = (val) => {
|
||||
console.log('taskDrawerOnAfterVisibleChange', val)
|
||||
}
|
||||
|
||||
const taskEdit = (deploy, task, index) => {
|
||||
currentTask.value = flatData(_.cloneDeep(task))
|
||||
console.log('currentTaskEdit', currentTask.value)
|
||||
currentTaskIndex.value = index
|
||||
currentDeploy.value = deploy
|
||||
changeCurrentPlugin(currentTask.value)
|
||||
|
||||
taskDrawerShow()
|
||||
}
|
||||
|
||||
const changeCurrentPlugin = (task) => {
|
||||
const taskType = task.type
|
||||
const currentPlugins = taskPluginDefineList.value.filter(p => {
|
||||
return p.name === taskType
|
||||
})
|
||||
if (currentPlugins.length <= 0) {
|
||||
task.type = undefined
|
||||
task._isAdd = true
|
||||
// throw new Error('未知插件:' + taskType)
|
||||
}
|
||||
currentPlugin.value = currentPlugins[0]
|
||||
|
||||
console.log('currentTaskTypeChanged:', currentTask.value)
|
||||
}
|
||||
|
||||
const flatData = (obj, parentKey = '', member = obj) => {
|
||||
_.forEach(member, (value, key) => {
|
||||
const pathKey = parentKey + key
|
||||
if (!_.isArray(value) && _.isObjectLike(value)) {
|
||||
parentKey = pathKey + '.'
|
||||
flatData(obj, parentKey, value)
|
||||
|
||||
if (parentKey === '') {
|
||||
delete (obj[key])
|
||||
}
|
||||
} else {
|
||||
obj[pathKey] = value
|
||||
}
|
||||
})
|
||||
return obj
|
||||
}
|
||||
|
||||
const unFlatData = (obj) => {
|
||||
const newObj = {}
|
||||
_.merge(newObj, obj)
|
||||
for (const key in obj) {
|
||||
const value = obj[key]
|
||||
if (key.indexOf('.') >= 0) {
|
||||
delete newObj[key]
|
||||
_.set(newObj, key, value)
|
||||
}
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
const taskSave = async (e) => {
|
||||
console.log('currentTaskSave', currentTask.value, currentTaskIndex.value)
|
||||
e.preventDefault()
|
||||
await taskFormRef.value.validate()
|
||||
const newTask = unFlatData(currentTask.value)
|
||||
currentDeploy.value.tasks[currentTaskIndex.value] = newTask
|
||||
// context.emit('update', newTask)
|
||||
taskDrawerClose()
|
||||
}
|
||||
|
||||
const taskDelete = () => {
|
||||
if (currentTaskIndex.value != null) {
|
||||
currentDeploy.value.tasks.splice(currentTaskIndex.value, 1)
|
||||
}
|
||||
taskDrawerClose()
|
||||
}
|
||||
|
||||
return {
|
||||
taskTypeSelected,
|
||||
taskTypeSave,
|
||||
taskPluginDefineList,
|
||||
taskFormRef,
|
||||
taskAdd,
|
||||
taskEdit,
|
||||
taskDrawerShow,
|
||||
taskDrawerVisible,
|
||||
taskDrawerOnAfterVisibleChange,
|
||||
currentTask,
|
||||
currentTaskIndex,
|
||||
currentPlugin,
|
||||
taskSave,
|
||||
taskDelete,
|
||||
rules
|
||||
}
|
||||
}
|
||||
|
||||
function useProviderManager () {
|
||||
const providerManager = ref(null)
|
||||
const providerManagerOpen = () => {
|
||||
providerManager.value.open()
|
||||
}
|
||||
return { providerManager, providerManagerOpen }
|
||||
}
|
||||
export default {
|
||||
name: 'task-form',
|
||||
emits: ['update'],
|
||||
props: {
|
||||
options: {}
|
||||
},
|
||||
setup (props, context) {
|
||||
return {
|
||||
...useTaskForm(context),
|
||||
...useProviderManager(),
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 16 }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.task-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>
|
||||
@@ -0,0 +1,495 @@
|
||||
<template>
|
||||
|
||||
<div class="page-detail">
|
||||
|
||||
<div class="flow">
|
||||
<div class="flow-group flow-cert">
|
||||
<h3 class="group-head">
|
||||
证书申请
|
||||
</h3>
|
||||
<a-divider></a-divider>
|
||||
<div class="cert-display group-body">
|
||||
<a-button class="cert-edit-btn" type="link" @click="certFormOpen">
|
||||
编辑
|
||||
</a-button>
|
||||
|
||||
<div class="label-list">
|
||||
<div class="title">证书</div>
|
||||
<div class="label-item">
|
||||
<label>域名:</label>
|
||||
<div class="value">
|
||||
<a-tag type="primary" v-for="item of options.cert.domains " :key="item">
|
||||
{{ item }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label-item">
|
||||
<label>邮箱:</label>
|
||||
<div>
|
||||
{{ options.cert.email }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>dns校验:</label>
|
||||
<div>
|
||||
<span v-if="options.cert?.dnsProvider">
|
||||
{{ options.cert.dnsProvider?.type }}-
|
||||
{{options.accessProviders[options.cert.dnsProvider.accessProvider]?.label}}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>CA:</label>
|
||||
<div>
|
||||
{{ options.cert.ca }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="title">
|
||||
CSR
|
||||
<span>必须全英文</span>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>国家:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.country }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>省份:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.state }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>市区:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.locality }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>组织:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.organization }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>部门:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.organizationUnit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-item">
|
||||
<label>邮箱:</label>
|
||||
<div>
|
||||
{{ options.cert.csr.emailAddress }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flow-group flow-deploy">
|
||||
<h3 class="group-head">
|
||||
部署流程
|
||||
<PlusCircleOutlined title="添加部署流程" class="add-icon" @click="deployAdd"/>
|
||||
</h3>
|
||||
<a-divider></a-divider>
|
||||
|
||||
<div class="group-body deploy-list">
|
||||
|
||||
<a-card class="deploy-item" v-for="(deploy,index) of options.deploy" :key="index">
|
||||
<template #title>
|
||||
<div class="deploy-name">
|
||||
<template v-if="deploy._isEdit">
|
||||
<a-input v-model:value="deploy.deployName"
|
||||
:validateStatus="deploy.deployName?'':'error'"
|
||||
placeholder="请输入流程名称"
|
||||
@keyup.enter="deployCloseEditMode(deploy)"
|
||||
@blur="deployCloseEditMode(deploy)"
|
||||
>
|
||||
<template #suffix>
|
||||
<CheckOutlined @click="deployCloseEditMode(deploy)" style="color: rgba(0,0,0,.45)"/>
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span @click="deployNameEdit"> <NodeIndexOutlined/> {{ deploy.deployName }}</span>
|
||||
<EditOutlined class="ml-10 edit-icon" @click="deployOpenEditMode(deploy)"/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button type="danger" @click="deployDelete(deploy,index)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
<div class="task-list">
|
||||
<div class="task-item-wrapper" v-for="(task,iindex) of deploy.tasks" :key="iindex">
|
||||
<a-button class="task-item" shape="round" @click="taskEdit(deploy,task,index)">
|
||||
<ThunderboltOutlined/>
|
||||
{{ task.taskName }}
|
||||
</a-button>
|
||||
<ArrowRightOutlined class="task-next-icon"/>
|
||||
</div>
|
||||
<div class="task-item-wrapper">
|
||||
<a-button type="primary" class="task-item" shape="round" @click="taskAdd(deploy)">
|
||||
<PlusOutlined/>
|
||||
添加新任务
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-group flow-export">
|
||||
<h3 class="group-head">
|
||||
导出
|
||||
</h3>
|
||||
<a-divider></a-divider>
|
||||
|
||||
<div class="export group-body" >
|
||||
|
||||
<d-container>
|
||||
<template #header>
|
||||
<div><a-button @click="exportsToZip">导出可执行项目</a-button></div>
|
||||
<br/>
|
||||
<div> <a-button @click="exportsToJson">复制options.json</a-button></div>
|
||||
<br/>
|
||||
</template>
|
||||
<pre class="json">{{options}}</pre>
|
||||
|
||||
</d-container>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<cert-form ref="certFormRef" v-model:cert="options.cert" v-model:access-providers="options.accessProviders"></cert-form>
|
||||
|
||||
<task-form ref="taskFormRef" @update="taskUpdated" ></task-form>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { message } from 'ant-design-vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { reactive, ref, toRef, toRefs, provide, readonly } from 'vue'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { useRoute } from 'vue-router'
|
||||
import CertForm from './components/cert-form'
|
||||
import TaskForm from './components/task-form'
|
||||
import exportsApi from '../../api/api.exports'
|
||||
import _ from 'lodash-es'
|
||||
import DContainer from '../../components/d-container'
|
||||
|
||||
function useDeploy (options) {
|
||||
const deployAdd = () => {
|
||||
options.deploy.push({
|
||||
deployName: `D${options.deploy.length + 1}-新部署流程`,
|
||||
_isEdit: false,
|
||||
tasks: []
|
||||
})
|
||||
}
|
||||
|
||||
const deployCloseEditMode = (deploy) => {
|
||||
if (!deploy.deployName) {
|
||||
message.error('请输入流程名称')
|
||||
return
|
||||
}
|
||||
deploy._isEdit = false
|
||||
console.log('options', options)
|
||||
}
|
||||
|
||||
const deployOpenEditMode = (deploy) => {
|
||||
deploy._isEdit = true
|
||||
}
|
||||
|
||||
const deployDelete = (deploy, index) => {
|
||||
options.deploy.splice(index, 1)
|
||||
}
|
||||
|
||||
return {
|
||||
deployAdd, deployCloseEditMode, deployOpenEditMode, deployDelete
|
||||
}
|
||||
}
|
||||
|
||||
function useProvideAccessProviders (options) {
|
||||
provide('get:accessProviders', () => {
|
||||
return options.accessProviders
|
||||
})
|
||||
provide('update:accessProviders', (providers) => {
|
||||
options.accessProviders = providers
|
||||
})
|
||||
}
|
||||
|
||||
function useExports (options) {
|
||||
return {
|
||||
async exportsToZip () {
|
||||
await exportsApi.exportsToZip(options)
|
||||
},
|
||||
async exportsToJson () {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { DContainer, CertForm, TaskForm },
|
||||
setup () {
|
||||
const route = useRoute()
|
||||
console.log('route', route)
|
||||
const optionParams = route.params.options ? JSON.parse(route.params.options) : {}
|
||||
const optionsDefault = {
|
||||
cert: {
|
||||
csr: {
|
||||
country: 'CN',
|
||||
state: 'GuangDong',
|
||||
locality: 'ShengZhen',
|
||||
organization: 'CertD Org.',
|
||||
organizationUnit: 'IT Department'
|
||||
}
|
||||
},
|
||||
accessProviders: [],
|
||||
deploy: []
|
||||
}
|
||||
_.merge(optionsDefault, optionParams)
|
||||
// optionsDefault.accessProviders = reactive(optionsDefault.accessProviders)
|
||||
const options = reactive(optionsDefault)
|
||||
|
||||
const certFormChanged = (value) => {
|
||||
console.log('certFormChanged', value)
|
||||
options.cert = value
|
||||
}
|
||||
|
||||
const certFormRef = ref(null)
|
||||
const certFormOpen = () => {
|
||||
certFormRef.value.open()
|
||||
}
|
||||
|
||||
const taskFormRef = ref(null)
|
||||
const taskAdd = (deploy) => {
|
||||
taskFormRef.value.taskAdd(deploy)
|
||||
}
|
||||
const taskEdit = (deploy, task, index) => {
|
||||
taskFormRef.value.taskEdit(deploy, task, index)
|
||||
}
|
||||
|
||||
useProvideAccessProviders(options)
|
||||
|
||||
return {
|
||||
options,
|
||||
certFormChanged,
|
||||
certFormRef,
|
||||
certFormOpen,
|
||||
...useDeploy(options),
|
||||
taskFormRef,
|
||||
taskAdd,
|
||||
taskEdit,
|
||||
...useExports(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.page-detail {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
.label-list {
|
||||
.title{
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color:#555;
|
||||
span{
|
||||
font-weight: 200;
|
||||
margin-left:5px;
|
||||
font-size: 12px;
|
||||
color:#888;
|
||||
}
|
||||
}
|
||||
.label-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: baseline;
|
||||
padding: 8px 0px;
|
||||
|
||||
label {
|
||||
width: 70px;
|
||||
flex-shrink: 0;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1.5715;
|
||||
text-align: end;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow {
|
||||
height: 100%;
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.flow-group {
|
||||
min-width: 300px;
|
||||
height: 100%;
|
||||
border-right: 1px #eee solid;
|
||||
padding: 20px;
|
||||
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.group-head {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.add-icon {
|
||||
margin-left: 20px;
|
||||
font-size: 24px;
|
||||
color: #737070;
|
||||
}
|
||||
}
|
||||
.group-body{
|
||||
flex:1
|
||||
}
|
||||
}
|
||||
|
||||
.flow-cert {
|
||||
max-width: 400px;
|
||||
|
||||
.cert-display {
|
||||
position: relative;
|
||||
|
||||
.cert-edit-btn {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.flow-deploy {
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.deploy-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.deploy-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// min-width:70%;
|
||||
.deploy-name {
|
||||
max-width: 300px;
|
||||
|
||||
.edit-icon {
|
||||
color: #737070;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flow-export{
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ant-form.task-form {
|
||||
padding: 10px 24px;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.task-item-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
//border: 1px solid #eee;
|
||||
//padding: 10px 20px;
|
||||
//border-radius: 20px;
|
||||
}
|
||||
|
||||
.task-add-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.task-next-icon {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-type-selector {
|
||||
|
||||
}
|
||||
|
||||
.task-form {
|
||||
.task-plugin-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.task-plugin {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user