fix(frontend): polish cert dialog and wire upload API
Prefer drag-and-drop for crt/key with PEM paste collapsed; rename primary actions; add HTTP-01 prechecks. Upload posts to POST /system/tls/certificate with cert_source from status.
This commit is contained in:
1 parent
df3e119099
commit
a9bc68cd28
3 files changed
+69
-12
No files matched your search
@@ -972,7 +972,13 @@
|
|||||||
"http01Hint": "Let’s Encrypt HTTP-01. Port 80 must be reachable. Auto-renewal status appears in the list.",
|
"http01Hint": "Let’s Encrypt HTTP-01. Port 80 must be reachable. Auto-renewal status appears in the list.",
|
||||||
"certKeyRequired": "Certificate and private key are required",
|
"certKeyRequired": "Certificate and private key are required",
|
||||||
"certUploaded": "Certificate uploaded",
|
"certUploaded": "Certificate uploaded",
|
||||||
"uploadApiPending": "Upload API is not ready yet (POST /system/tls/certificate). Use HTTP-01 for now."
|
"uploadApiPending": "Upload API is not ready yet (POST /system/tls/certificate). Use HTTP-01 for now.",
|
||||||
|
"dropHint": "Drop a file here, or click to choose",
|
||||||
|
"pastePem": "Or paste PEM text",
|
||||||
|
"importCert": "Import certificate",
|
||||||
|
"startIssue": "Start issuance",
|
||||||
|
"http01CheckDns": "DNS already points to this public entry",
|
||||||
|
"http01CheckPort": "Issuance port is reachable (usually 80, or matches entry port)"
|
||||||
},
|
},
|
||||||
"feishuSync": {
|
"feishuSync": {
|
||||||
"title": "Feishu directory",
|
"title": "Feishu directory",
|
||||||
|
|||||||
@@ -972,7 +972,13 @@
|
|||||||
"http01Hint": "使用 Let’s Encrypt HTTP-01 验证;需公网可访问 80 端口。过期前将尝试自动续期,失败时列表会显示状态。",
|
"http01Hint": "使用 Let’s Encrypt HTTP-01 验证;需公网可访问 80 端口。过期前将尝试自动续期,失败时列表会显示状态。",
|
||||||
"certKeyRequired": "请填写证书与私钥",
|
"certKeyRequired": "请填写证书与私钥",
|
||||||
"certUploaded": "证书已上传",
|
"certUploaded": "证书已上传",
|
||||||
"uploadApiPending": "上传证书接口尚未就绪,请先由后端落地 POST /system/tls/certificate,或改用 HTTP-01 申请。"
|
"uploadApiPending": "上传证书接口尚未就绪,请先由后端落地 POST /system/tls/certificate,或改用 HTTP-01 申请。",
|
||||||
|
"dropHint": "拖拽文件到此处,或点击选择",
|
||||||
|
"pastePem": "或粘贴 PEM 文本",
|
||||||
|
"importCert": "导入证书",
|
||||||
|
"startIssue": "开始申请",
|
||||||
|
"http01CheckDns": "DNS 已指向本机公网入口",
|
||||||
|
"http01CheckPort": "申请用端口可达(通常为 80,或与入口配置一致)"
|
||||||
},
|
},
|
||||||
"feishuSync": {
|
"feishuSync": {
|
||||||
"title": "飞书组织与成员",
|
"title": "飞书组织与成员",
|
||||||
|
|||||||
@@ -138,16 +138,42 @@
|
|||||||
<template v-if="certForm.method === 'upload'">
|
<template v-if="certForm.method === 'upload'">
|
||||||
<el-form label-position="top" style="margin-top:16px">
|
<el-form label-position="top" style="margin-top:16px">
|
||||||
<el-form-item :label="$t('tls.certPem')" required>
|
<el-form-item :label="$t('tls.certPem')" required>
|
||||||
<el-input v-model="certForm.certPem" type="textarea" :rows="6" :placeholder="$t('tls.certPemPlaceholder')" />
|
<el-upload
|
||||||
<el-upload :show-file-list="false" accept=".crt,.pem,.cer" :before-upload="(f) => readPemFile(f, 'certPem')">
|
drag
|
||||||
<el-button class="upload-btn" style="width:100%;margin-top:8px">{{ $t('tls.uploadCertFile') }}</el-button>
|
class="pem-drop"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept=".crt,.pem,.cer"
|
||||||
|
:before-upload="(f) => readPemFile(f, 'certPem')"
|
||||||
|
>
|
||||||
|
<div class="pem-drop-inner">
|
||||||
|
<div class="pem-drop-title">{{ certForm.certFileName || $t('tls.uploadCertFile') }}</div>
|
||||||
|
<div class="pem-drop-hint">{{ $t('tls.dropHint') }}</div>
|
||||||
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
<el-collapse style="margin-top:8px">
|
||||||
|
<el-collapse-item :title="$t('tls.pastePem')" name="cert">
|
||||||
|
<el-input v-model="certForm.certPem" type="textarea" :rows="5" :placeholder="$t('tls.certPemPlaceholder')" />
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item :label="$t('tls.keyPem')" required>
|
<el-form-item :label="$t('tls.keyPem')" required>
|
||||||
<el-input v-model="certForm.keyPem" type="textarea" :rows="6" :placeholder="$t('tls.keyPemPlaceholder')" />
|
<el-upload
|
||||||
<el-upload :show-file-list="false" accept=".key,.pem" :before-upload="(f) => readPemFile(f, 'keyPem')">
|
drag
|
||||||
<el-button class="upload-btn" style="width:100%;margin-top:8px">{{ $t('tls.uploadKeyFile') }}</el-button>
|
class="pem-drop"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept=".key,.pem"
|
||||||
|
:before-upload="(f) => readPemFile(f, 'keyPem')"
|
||||||
|
>
|
||||||
|
<div class="pem-drop-inner">
|
||||||
|
<div class="pem-drop-title">{{ certForm.keyFileName || $t('tls.uploadKeyFile') }}</div>
|
||||||
|
<div class="pem-drop-hint">{{ $t('tls.dropHint') }}</div>
|
||||||
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
|
<el-collapse style="margin-top:8px">
|
||||||
|
<el-collapse-item :title="$t('tls.pastePem')" name="key">
|
||||||
|
<el-input v-model="certForm.keyPem" type="textarea" :rows="5" :placeholder="$t('tls.keyPemPlaceholder')" />
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
@@ -160,13 +186,19 @@
|
|||||||
<el-form-item :label="$t('tls.applyEmail')">
|
<el-form-item :label="$t('tls.applyEmail')">
|
||||||
<el-input v-model.trim="certForm.email" placeholder="admin@example.com" />
|
<el-input v-model.trim="certForm.email" placeholder="admin@example.com" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-alert type="info" :closable="false" :title="$t('tls.http01Hint')" />
|
<el-alert type="info" :closable="false" :title="$t('tls.http01Hint')" style="margin-bottom:12px" />
|
||||||
|
<ul class="http01-checklist">
|
||||||
|
<li>{{ $t('tls.http01CheckDns') }}</li>
|
||||||
|
<li>{{ $t('tls.http01CheckPort') }}</li>
|
||||||
|
</ul>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="certDialog = false">{{ $t('common.cancel') }}</el-button>
|
<el-button @click="certDialog = false">{{ $t('common.cancel') }}</el-button>
|
||||||
<el-button type="primary" :loading="certSubmitting" @click="submitCert">{{ $t('common.submit') }}</el-button>
|
<el-button type="primary" :loading="certSubmitting" @click="submitCert">
|
||||||
|
{{ certForm.method === 'upload' ? $t('tls.importCert') : $t('tls.startIssue') }}
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<pre v-if="nginxActivateLog" ref="nginxActivateLogView" class="nginx-preview" role="log" aria-live="polite" style="margin-top:12px">{{ nginxActivateLog }}</pre>
|
<pre v-if="nginxActivateLog" ref="nginxActivateLogView" class="nginx-preview" role="log" aria-live="polite" style="margin-top:12px">{{ nginxActivateLog }}</pre>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -207,6 +239,8 @@ const certForm = reactive({
|
|||||||
method: 'upload',
|
method: 'upload',
|
||||||
certPem: '',
|
certPem: '',
|
||||||
keyPem: '',
|
keyPem: '',
|
||||||
|
certFileName: '',
|
||||||
|
keyFileName: '',
|
||||||
host: '',
|
host: '',
|
||||||
email: '',
|
email: '',
|
||||||
})
|
})
|
||||||
@@ -243,6 +277,8 @@ function resetCertForm() {
|
|||||||
certForm.method = 'upload'
|
certForm.method = 'upload'
|
||||||
certForm.certPem = ''
|
certForm.certPem = ''
|
||||||
certForm.keyPem = ''
|
certForm.keyPem = ''
|
||||||
|
certForm.certFileName = ''
|
||||||
|
certForm.keyFileName = ''
|
||||||
certForm.host = entry.host
|
certForm.host = entry.host
|
||||||
certForm.email = ''
|
certForm.email = ''
|
||||||
nginxActivateLog.value = ''
|
nginxActivateLog.value = ''
|
||||||
@@ -255,7 +291,11 @@ function openAddCert() {
|
|||||||
|
|
||||||
function readPemFile(file, field) {
|
function readPemFile(file, field) {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
reader.onload = () => { certForm[field] = String(reader.result || '') }
|
reader.onload = () => {
|
||||||
|
certForm[field] = String(reader.result || '')
|
||||||
|
if (field === 'certPem') certForm.certFileName = file.name
|
||||||
|
if (field === 'keyPem') certForm.keyFileName = file.name
|
||||||
|
}
|
||||||
reader.readAsText(file)
|
reader.readAsText(file)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -461,5 +501,10 @@ onMounted(() => { if (!auth.mustChange) refreshAll() })
|
|||||||
transition: border-color .15s, box-shadow .15s;
|
transition: border-color .15s, box-shadow .15s;
|
||||||
}
|
}
|
||||||
.method-card.active { border-color: var(--el-color-primary); box-shadow: 0 0 0 1px var(--el-color-primary) inset; }
|
.method-card.active { border-color: var(--el-color-primary); box-shadow: 0 0 0 1px var(--el-color-primary) inset; }
|
||||||
.upload-btn { color: var(--el-color-primary); border-style: dashed; }
|
.pem-drop { width: 100%; }
|
||||||
|
.pem-drop :deep(.el-upload), .pem-drop :deep(.el-upload-dragger) { width: 100%; }
|
||||||
|
.pem-drop-inner { padding: 8px 0; }
|
||||||
|
.pem-drop-title { color: var(--el-color-primary); font-weight: 600; }
|
||||||
|
.pem-drop-hint { margin-top: 4px; font-size: 12px; color: var(--text-secondary); }
|
||||||
|
.http01-checklist { margin: 0; padding-left: 1.2em; color: var(--text-secondary); font-size: 13px; line-height: 1.7; }
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user