Files
Auto-ssl/frontend/src/views/CertCreate.vue
T
cnbugs 285df5a12f fix: cert create form credential selection validation
- Explicitly set selectedCredId in onCredSelected
- Separate credential validation from manual input validation
2026-07-24 19:10:34 +08:00

339 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="cert-create">
<h2 class="page-title">申请新证书</h2>
<el-card>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="140px"
style="max-width: 620px"
>
<el-form-item label="域名" prop="domain">
<el-input v-model="form.domain" placeholder="example.com" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="admin@example.com" />
</el-form-item>
<el-form-item label="ACME 提供商" prop="provider">
<el-radio-group v-model="form.provider">
<el-radio value="letsencrypt">Let's Encrypt</el-radio>
<el-radio value="zerossl">ZeroSSL</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="验证方式" prop="challenge_type">
<el-radio-group v-model="form.challenge_type" @change="onChallengeChange">
<el-radio value="dns">DNS-01(推荐)</el-radio>
<el-radio value="http">HTTP-01(需80端口)</el-radio>
</el-radio-group>
</el-form-item>
<!-- DNS mode: credential or manual -->
<template v-if="form.challenge_type === 'dns'">
<el-form-item label="使用方式">
<el-radio-group v-model="credMode">
<el-radio value="credential">选择已有凭证</el-radio>
<el-radio value="manual">手动输入</el-radio>
</el-radio-group>
</el-form-item>
<!-- Select existing credential -->
<template v-if="credMode === 'credential'">
<el-form-item label="选择凭证">
<el-select v-model="selectedCredId" placeholder="选择已保存的 DNS 凭证" style="width: 100%" @change="onCredSelected">
<el-option
v-for="cred in credentials"
:key="cred.id"
:label="cred.name + ' (' + typeLabel(cred.type) + ')'"
:value="cred.id"
/>
</el-select>
<div class="form-tip" v-if="credentials.length === 0">
暂无凭证,请先在 <router-link to="/credentials">DNS 凭证</router-link> 中添加
</div>
</el-form-item>
</template>
<!-- Manual input -->
<template v-if="credMode === 'manual'">
<el-form-item label="DNS 提供商" prop="dns_provider">
<el-select v-model="form.dns_provider" placeholder="选择 DNS 提供商" style="width: 100%" @change="onProviderChange">
<el-option label="阿里云 DNS (Aliyun)" value="alidns" />
<el-option label="Cloudflare" value="cloudflare" />
<el-option label="DNSPod" value="dnspod" />
</el-select>
</el-form-item>
<!-- Aliyun DNS Config -->
<template v-if="form.dns_provider === 'alidns'">
<el-form-item label="AccessKey ID">
<el-input v-model="manualConfig.ali_key" placeholder="Aliyun AccessKey ID" />
</el-form-item>
<el-form-item label="AccessKey Secret">
<el-input v-model="manualConfig.ali_secret" type="password" show-password placeholder="Aliyun AccessKey Secret" />
</el-form-item>
</template>
<!-- Cloudflare Config -->
<template v-if="form.dns_provider === 'cloudflare'">
<el-form-item label="API Token">
<el-input v-model="manualConfig.cf_api_token" type="password" show-password placeholder="Cloudflare API Token" />
</el-form-item>
</template>
<!-- DNSPod Config -->
<template v-if="form.dns_provider === 'dnspod'">
<el-form-item label="ID">
<el-input v-model="manualConfig.dnspod_id" placeholder="DNSPod ID" />
</el-form-item>
<el-form-item label="Key">
<el-input v-model="manualConfig.dnspod_key" type="password" show-password placeholder="DNSPod Key" />
</el-form-item>
</template>
<!-- Save as credential option -->
<el-form-item v-if="form.dns_provider">
<el-checkbox v-model="saveAsCredential">保存为新凭证</el-checkbox>
</el-form-item>
<el-form-item v-if="saveAsCredential && form.dns_provider" label="凭证名称">
<el-input v-model="newCredName" placeholder="例如:阿里云生产环境" />
</el-form-item>
</template>
</template>
<el-form-item label="自动续期">
<el-switch v-model="form.auto_renew" />
</el-form-item>
<el-form-item v-if="form.auto_renew" label="提前续期天数">
<el-input-number v-model="form.renew_days" :min="1" :max="60" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit" :loading="submitting">
<el-icon><Plus /></el-icon> 提交申请
</el-button>
<el-button @click="$router.push('/certificates')">取消</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { certApi, credApi } from '../api'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
interface Credential {
id: number
name: string
type: string
masked: string
}
const router = useRouter()
const formRef = ref()
const submitting = ref(false)
const credentials = ref<Credential[]>([])
// Credential mode: select existing or manual input
const credMode = ref<'credential' | 'manual'>('credential')
const selectedCredId = ref<number | null>(null)
const saveAsCredential = ref(false)
const newCredName = ref('')
const form = reactive({
domain: '',
email: '',
provider: 'letsencrypt',
challenge_type: 'dns',
dns_provider: 'alidns',
auto_renew: true,
renew_days: 30,
})
const manualConfig = reactive({
ali_key: '',
ali_secret: '',
cf_api_token: '',
dnspod_id: '',
dnspod_key: '',
})
const rules = {
domain: [{ required: true, message: '请输入域名', trigger: 'blur' }],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' },
],
}
const typeLabel = (type: string) => {
const labels: Record<string, string> = {
alidns: '阿里云 DNS',
cloudflare: 'Cloudflare',
dnspod: 'DNSPod',
}
return labels[type] || type
}
const loadCredentials = async () => {
try {
const res = await credApi.list()
credentials.value = res.data
} catch {
// Ignore errors loading credentials
}
}
const onChallengeChange = () => {
// Reset DNS config when switching challenge type
Object.keys(manualConfig).forEach(k => (manualConfig as any)[k] = '')
selectedCredId.value = null
credMode.value = 'credential'
saveAsCredential.value = false
newCredName.value = ''
}
const onProviderChange = () => {
Object.keys(manualConfig).forEach(k => (manualConfig as any)[k] = '')
}
const onCredSelected = (credId: number) => {
// Just store the selection, backend resolves DNS config
selectedCredId.value = credId
}
const buildDnsConfigJson = (): string => {
switch (form.dns_provider) {
case 'alidns':
return JSON.stringify({
ali_key: manualConfig.ali_key,
ali_secret: manualConfig.ali_secret,
})
case 'cloudflare':
return JSON.stringify({
cf_api_token: manualConfig.cf_api_token,
})
case 'dnspod':
return JSON.stringify({
dnspod_id: manualConfig.dnspod_id,
dnspod_key: manualConfig.dnspod_key,
})
default:
return '{}'
}
}
const handleSubmit = async () => {
const valid = await formRef.value.validate().catch(() => false)
if (!valid) return
// Validate manual fields
if (form.challenge_type === 'dns' && credMode.value === 'manual') {
if (!form.dns_provider) {
ElMessage.warning('请选择 DNS 提供商')
return
}
// Check required fields
if (form.dns_provider === 'alidns' && (!manualConfig.ali_key || !manualConfig.ali_secret)) {
ElMessage.warning('请填写阿里云 AccessKey')
return
}
if (form.dns_provider === 'cloudflare' && !manualConfig.cf_api_token) {
ElMessage.warning('请填写 Cloudflare API Token')
return
}
if (form.dns_provider === 'dnspod' && (!manualConfig.dnspod_id || !manualConfig.dnspod_key)) {
ElMessage.warning('请填写 DNSPod 凭证')
return
}
if (saveAsCredential.value && !newCredName.value) {
ElMessage.warning('请输入凭证名称')
return
}
}
submitting.value = true
try {
const payload: any = {
domain: form.domain,
email: form.email,
provider: form.provider,
challenge_type: form.challenge_type,
auto_renew: form.auto_renew,
renew_days: form.renew_days,
}
if (form.challenge_type === 'dns') {
if (credMode.value === 'credential') {
if (!selectedCredId.value) {
ElMessage.warning('请选择一个 DNS 凭证')
submitting.value = false
return
}
// Use existing credential
payload.credential_id = selectedCredId.value
} else if (credMode.value === 'manual') {
// Manual input
payload.dns_provider = form.dns_provider
payload.dns_config = buildDnsConfigJson()
// Optionally save as new credential
if (saveAsCredential.value && newCredName.value) {
await credApi.create({
name: newCredName.value,
type: form.dns_provider,
data: payload.dns_config,
})
}
} else {
ElMessage.warning('请选择 DNS 凭证或选择手动输入')
submitting.value = false
return
}
}
await certApi.create(payload)
ElMessage.success('证书申请已提交后台处理中')
router.push('/certificates')
} catch (e: any) {
ElMessage.error('提交失败: ' + (e.response?.data?.error || e.message))
} finally {
submitting.value = false
}
}
onMounted(loadCredentials)
</script>
<style scoped>
.page-title {
margin-bottom: 20px;
font-size: 24px;
color: #1a1a2e;
}
.form-tip {
font-size: 12px;
color: #999;
margin-top: 4px;
}
.form-tip a {
color: #1890ff;
text-decoration: none;
}
.form-tip a:hover {
text-decoration: underline;
}
</style>