后端前端运维MCP 服务【免费下载链接】nginx-uiYet another WebUI for Nginx项目地址https://gitcode.com/gh_mirrors/ngi/nginx-ui点击查看免费下载导读Nginx UI 的证书管理页面长期以来在卡片头部暴露三个操作入口Import导入、Self-signed Certificate自签名与Issue certificate签发其中后两者分别通过不同的弹窗生成证书最终却汇入同一张证书列表从用户视角看存在明显的功能冗余。本文基于仓库内已批准的设计文档 2026-05-23-merge-self-signed-into-issue-cert-design.md完整讲解这次前端 UI 重构的设计目标、实现方案、行为变更、测试策略与 i18n 处理并结合仓库中已落地的源码给出可验证的对应关系。读完本文你将掌握 Nginx UI 证书模块单一入口 类型分支的弹窗设计模式以及如何在不触碰后端的前提下安全合并两条证书生成流程。背景三个入口的冗余与合并动机现状分析证书列表页app/src/views/certificate/CertificateList/Certificate.vue的卡片头部ACard的#extra插槽当前呈现三个动作Import——$router.push(/certificates/import)跳转到证书导入页Self-signed Certificate—— 打开SelfSignedCertForm.vue自签名弹窗Issue certificate—— 打开DNSIssueCertificate.vue弹窗走 ACME 通配符 / 自定义域名签发流程。关键差异在于自签名证书由 Nginx UI 自身直接生成本地生成公私钥对而 Issue 流程通过 ACME 协议向证书颁发机构申请依赖 DNS 凭证两者的生成手段不同但最终产物落在同一张证书列表。从用户视角看两个按钮指向的都是创建一张新证书这种双按钮布局是冗余的。设计文档由此提出合并方案让Issue certificate 成为创建新证书的唯一入口ACME 与自签名作为该对话框内部的证书类型certificate type并列选项。目标与非目标合并设计的核心目标设计文档 Goal 节卡片头部动作从三个收缩为两个Import与Issue certificateIssue Certificate 对话框在既有 Certificate Type 下拉框中新增Self-signed选项选中Self-signed时表单主体切换为自签名字段集提交路由到既有的自签名生成 API后端零改动——这是一次纯前端 UI 重构。同时明确列出非目标防止范围蔓延不动站点编辑器site editor里的自签名快捷方式app/src/views/site/site_edit/components/Cert/SelfSignedCert.vue它继续直接使用SelfSignedCertForm.vue带默认域名不动既有自签名证书的编辑视图SelfSignedCertManagement.vue不重命名DNSIssueCertificate.vue否则只会搅动 i18n.pot的引用不涉及后端 / API / Go 测试的任何改动。受影响文件清单设计文档给出了精确的改动边界仓库当前状态与之一一对应文件变化app/src/views/certificate/components/DNSIssueCertificate.vuecertType联合类型扩展self_signed条件渲染SelfSignedCertFields选中时调用自签名 API。app/src/views/certificate/CertificateList/Certificate.vue移除独立的自签名按钮及相关 import / ref / handler。app/src/views/certificate/components/SelfSignedCertForm.vue保持不动仍被站点编辑器使用。app/src/views/certificate/components/SelfSignedCertFields.vue保持不动作为合并后对话框内复用的字段集。从源码看SelfSignedCertForm.vue与DNSIssueCertificate.vue目前都引用了SelfSignedCertFields.vue作为共享字段集见 DNSIssueCertificate.vue 第 238 行与 SelfSignedCertForm.vue 第 87 行这正是字段集复用、弹窗外壳各异的组件化思路。实现方案一DNSIssueCertificate.vue 的类型分支状态新增合并后DNSIssueCertificate.vue需要承载三套并行的表单状态。仓库实际实现DNSIssueCertificate.vue 第 19–56 行与设计文档完全吻合type CertType wildcard | custom | self_signed const step ref(0) const visible ref(false) const data ref({}) as RefAutoCertOptions const domain ref() const certType refCertType(wildcard) const customDomains refstring[]([]) const errored ref(false) const selfSignedLoading ref(false)其中certType: Refwildcard | custom | self_signed—— 在既有联合类型上追加self_signedselfSignedPayload: RefSelfSignedCertPayload—— 镜像SelfSignedCertForm.emptyForm()的形状{ name: , domains: [], ip_addresses: [], key_type: PrivateKeyTypeEnum.P256, validity_days: 365, sync_node_ids: [] }selfSignedLoading: Refboolean—— POST 请求在途时禁用 Generate 按钮。open()在重置既有状态step、data、domain、certType、customDomains、errored的同时也通过emptySelfSignedPayload()重置selfSignedPayload保证每次打开弹窗都是干净的初始表单。模板分支模板层的核心逻辑第 170–273 行Certificate Type 下拉框的选项列表certTypeOptions增加第三项{ value: self_signed, label: $gettext(Self-signed Certificate) }certType self_signed分支只渲染SelfSignedCertFields v-modelselfSignedPayload /同时隐藏通配符域名输入框、自定义域名列表、AutoCertForm块与ObtainCertLive步骤后者的挂载被隔离在step 1的v-show中天然不会出现在自签名流程底部按钮双态切换certType self_signed时按钮文案为Generate绑定:loadingselfSignedLoading点击触发submitSelfSigned()其他模式保持Next按钮触发issueCert()。submitSelfSigned()与 SelfSignedCertForm 完全一致的语言文案设计文档给出的submitSelfSigned()骨架在仓库中已按第二版增强设计落地见下文第五节其核心流程为先校验 Name 非空再校验 domains/ip_addresses 至少一项非空随后以cert.generate_self_signed()提交成功则关闭弹窗并emit(issued)刷新列表。设计文档特别强调提示文案与SelfSignedCertForm的现有文案逐字一致从而直接复用已有翻译。行为细节状态隔离与步骤机设计文档记录了三条行为约定均已在源码中体现切换certType不清空其他分支的状态——selfSignedPayload、customDomains、data是相互独立的 ref用户可在通配符 / 自定义域名 / 自签名之间来回切换而不丢失已输入内容step仅在 ACME 路径上推进——issueCert()校验通过后step.value 1进入ObtainCertLive在线签发步骤自签名路径停留在表单视图生成成功后直接关闭computedDomain/computedDomains/computedMainDomain只服务于 ACME 分支——通配符模式下组合出*.domain与裸域名自定义域名模式下取去空后的数组供ObtainCertLive.issue_cert()使用。实现方案二Certificate.vue 的动作收敛证书列表页CertificateList/Certificate.vue的改造是从模板与脚本中删减当前源码 第 124–188 行展示了合并后的状态删除SafetyOutlined图标导入、SelfSignedCertForm导入、Cert类型导入原仅被onSelfSignedCreated使用删除refSelfSignedref 与onSelfSignedCreated函数删除模板中渲染 Self-signed Certificate 动作的AButton块删除模板底部SelfSignedCertForm refrefSelfSigned createdonSelfSignedCreated /挂载块。合并后卡片头部的#extra插槽仅保留Discover证书发现扫描、Import与Issue certificate三个链接按钮其中WildcardCertificate即DNSIssueCertificate.vue的本地别名命名保留语义上现为 Issue Certificate成为唯一的创建弹窗其issued事件直接触发refTable.refresh()刷新表格。行为变更统一弹窗退出契约设计文档明确标注了一个可感知的交互行为变化合并前自签名证书生成后onSelfSignedCreated会执行router.push(/certificates/:id)跳转到证书编辑页合并后自签名证书生成后弹窗直接关闭 表格刷新复用既有issuedemit与 ACME 签发完成的交互行为保持一致。设计理由合并后的弹窗只有一个退出契约。用户想编辑新证书时点击列表中的新行即可进入编辑器这避免了 ACME 用户不会体验到的上下文跳转割裂感。从当前源码看submitSelfSigned()成功分支正是visible.value false; emit(issued)与issueCert()成功后的emit(issued)行为统一。纵深自签名生成的后端链路与 Name 校验虽然设计文档声明无后端改动但理解合并后的自签名提交语义仍需了解其背后的 API。仓库中的后端链路如下请求结构体与校验api/certificate/self_signed.go 中的SelfSignedCertRequest定义了生成 / 修改自签名证书的请求载荷type SelfSignedCertRequest struct { Name string json:name binding:required Domains []string json:domains binding:omitempty IPAddresses []string json:ip_addresses binding:omitempty,dive,ip KeyType string json:key_type binding:omitempty,auto_cert_key_type ValidityDays int json:validity_days binding:omitempty,min1,max3650 SyncNodeIds []uint64 json:sync_node_ids binding:omitempty }要点Name为binding:required空名称在cosy.BindAndValid阶段直接返回 4xx这一约束同时作用于GenerateSelfSignedCert与ModifySelfSignedCert两个 handler二者绑定同一结构体IPAddresses逐项做ip格式校验omitempty,dive,ip客户端不做 IP 格式验证错误由后端 toast 呈现ValidityDays取值区间1–3650KeyType复用auto_cert_key_type校验器。生成流程与失败回滚GenerateSelfSignedCert的完整流程api/certificate/self_signed.go 第 37–117 行cosy.BindAndValid绑定并校验请求buildSelfSignedOptions规范化域名/IP 数组normalizeStringSlice去空白要求二者至少一项非空否则返回cert.ErrSelfSignedNoSAN并从首个域名或 IP 推导 CommonName在数据库创建model.Cert记录AutoCert: model.AutoCertSelfSigned依据记录 ID 推导文件系统安全的证书目录 slugselfSignedSlug对中文/Unicode 名称做 IDNA 转换并过滤非法字符目录形如ssl/{slug}_{id}写入fullchain.cer与private.key写文件失败时清理目录并回滚数据库记录避免留下孤儿文件或孤儿记录成功后计算指纹并持久化路径最后cert.SyncToRemoteServer同步到远端节点。ModifySelfSignedCert则复用既有文件路径与私钥重新签发rewriteSelfSignedFiles保证引用该证书的站点不断链随后nginx.Reload()重载配置。空 Name 的后端回归测试设计文档规划的测试用例已在仓库落地TestGenerateSelfSignedCertRejectsEmptyNameapi/certificate/self_signed_test.go 第 174–200 行通过setupSelfSignedAPITest辅助函数构造路由POST 一个不含 name的SelfSignedCertRequest仅domains: [named.example]、key_type: EC256、validity_days: 30断言响应码落在[400, 500)区间且响应体提及name字段。配套增强表单一致性、续期提示与必填 Name仓库中还包含第二份关联设计文档 2026-05-23-self-signed-enhancements-design.md它是在合并设计之上的一组 UX 增强Builds on 关系已在当前源码中全部落地与合并设计共同构成完整的自签名体验统一的StringListInput多行输入组件app/src/components/StringListInput/StringListInput.vue封装了一行一个输入框 Add / Remove的数组编辑模式被自定义域名分支与自签名 Domains / IP Addresses 字段共同使用DNSIssueCertificate.vue第 203 行、SelfSignedCertFields.vue第 43–55 行。组件只负责编辑不做校验模型数组允许含单个空串由调用方在提交时负责过滤。表单内续期策略提示SelfSignedCertFields.vue顶部在!props.hideRenewalNote时渲染AAlert提示Nginx UI 将依据全局证书续期间隔与本证书有效期自动续期。SelfSignedCertManagement.vue第 35 行传入hide-renewal-note以抑制重复提示它自身已有managed by Nginx UI告警。Name 必填的前后端双重保障前端在DNSIssueCertificate.submitSelfSigned()、SelfSignedCertForm.submit()、CertificateEditor.save()isSelfSigned分支CertificateEditor.vue 第 82–96 行三处统一执行trim → 过滤空串 → 校验 Name 非空 → 校验域名/IP 至少一项而后端Name的binding:required作为最后防线。载荷工厂的空行播种emptySelfSignedPayload()DNSIssueCertificate.vue第 45 行、SelfSignedCertForm.emptyForm()、cert.ts的toSelfSignedPayload(c)app/src/api/cert.ts 第 112–130 行均以[]播种数组让用户打开表单即看到一个可编辑的空行后者还负责将后端规范形式的 key_type如EC256、RSA2048归一化为表单选项所需的旧式键P256、2048。前端 API 封装对应关系cert.generate_self_signed(payload)→POST /self_signed_certcert.modify_self_signed(id, payload)→POST /self_signed_cert/{id}app/src/api/cert.ts 第 139–144 行。测试策略前端门禁 手工冒烟 后端回归设计文档的测试策略分三层前端门禁bun run lint、bun run lint:fix、bun run typecheck必须在app/目录下全部通过手工冒烟验证证书列表头部仅剩 Import Issue certificateIssue Certificate 默认通配符且 ACME 端到端可用切换自定义域名无回归切换到 Self-signed 显示自签名字段集且 Generate 可创建行、关闭弹窗并刷新表格空域名空 IP 点击 Generate 出现既有校验错误站点编辑器 → SelfSignedCert.vue 仍独立弹窗且行为不变后端回归go test ./api/certificate/... ./internal/cert/...保持全绿新增空 Name 4xx 用例见上文。由于不涉及后端行为变更既有 Go 测试即为权威依据无需新增更多 Go 测试。i18n 与发布计划i18n合并设计刻意做到零新增字符串——下拉选项Self-signed Certificate复用既有按钮文案Generate、Please enter at least one domain or IP address、Self-signed certificate generated、Failed to generate self-signed certificate均已存在于SelfSignedCertForm.vue。增强设计补充的新字符串Add Item、Add IP Address、Enter certificate name、Please enter a name for the certificate、续期策略提示等也已进入源码的$gettext调用messages.pot的再生成属于后续运维事项。Rollout单个 PR 合入dev分支评审关注点集中在证书列表头部、Issue Certificate 弹窗以及对站点编辑器自签名路径的回归检查。增强设计则注明是叠加在已合入feature/self-signed-certificate的两个 commit 之上的单 PR。小结可复用的单入口 类型分支弹窗模式从这份设计文档及其仓库实现中可以提炼出一个可复用的前端弹窗设计模式当一个创建弹窗需要承载多条证书生成路径时用类型下拉 条件字段集 独立载荷状态 统一退出契约来收敛入口——类型切换隔离各分支状态、共享字段集组件避免重复代码、emit(issued)统一驱动列表刷新。这套思路在 Nginx UI 的证书模块通配符 / 自定义域名 / 自签名中得到了完整实践对同类 WebUI 的证书管理功能设计具有直接的参考价值。赞分享后端前端运维MCP 服务【免费下载链接】nginx-uiYet another WebUI for Nginx项目地址https://gitcode.com/gh_mirrors/ngi/nginx-ui点击查看免费下载相关推荐nginx-ui 证书签发流程重构将 Self-signed 合并进 Issue Certificate 对话框的完整实现方案nginx ui 证书签发流程重构将 Self signed 合并进 Issue Certificate 对话框的完整实现方案 Nginx UI 的证书管理页后端前端运维MCP 服务暗网威胁情报采集一次搞定百余条免费情报源完整清单暗网威胁情报采集一次搞定百余条免费情报源完整清单 攻击发生后最早的线索往往早几天就出现在暗网频道里。deepdarkCTI 把深网、暗网的威胁情报源整理成一curl证书管理自签名证书、CA证书、客户端证书curl证书管理自签名证书、CA证书、客户端证书 你是否曾经在使用curl进行HTTPS请求时遇到过证书验证错误是否对如何正确处理自签名证书、CA证书和客户CLI网络通信创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
