mirror of
https://github.com/certd/certd.git
synced 2026-06-26 04:47:32 +08:00
Merge branch 'codex/v2-persist-01' into v2-invite
This commit is contained in:
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.40.3](https://github.com/certd/certd/compare/v1.40.2...v1.40.3) (2026-05-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复暗黑模式下注册页面验证码看不清的问题 ([5ba33be](https://github.com/certd/certd/commit/5ba33be30f765f06cafbfcc04f5e25320db01449))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 修复商业版套餐添加和修改时的字段显示 ([fb5b00d](https://github.com/certd/certd/commit/fb5b00d73f925036a65ce5003c57c1199578c34d))
|
||||
|
||||
## [1.40.2](https://github.com/certd/certd/compare/v1.40.1...v1.40.2) (2026-05-19)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-client
|
||||
|
||||
## [1.40.1](https://github.com/certd/certd/compare/v1.40.0...v1.40.1) (2026-05-18)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.40.1",
|
||||
"version": "1.40.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -106,8 +106,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.40.1",
|
||||
"@certd/pipeline": "^1.40.1",
|
||||
"@certd/lib-iframe": "^1.40.3",
|
||||
"@certd/pipeline": "^1.40.3",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -54,6 +54,30 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">wechat</div>
|
||||
<div class="code-name">&#xe735;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">QQ</div>
|
||||
<div class="code-name">&#xe667;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">wechat</div>
|
||||
<div class="code-name">&#xe60c;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">acepanel</div>
|
||||
<div class="code-name">&#xe609;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">飞牛</div>
|
||||
@@ -252,7 +276,7 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.svg?t=1766772710945#iconfont') format('svg');
|
||||
src: url('iconfont.svg?t=1779270521617#iconfont') format('svg');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -278,6 +302,42 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-wxpay"></span>
|
||||
<div class="name">
|
||||
wechat
|
||||
</div>
|
||||
<div class="code-name">.icon-wxpay
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-qq"></span>
|
||||
<div class="name">
|
||||
QQ
|
||||
</div>
|
||||
<div class="code-name">.icon-qq
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-wechat"></span>
|
||||
<div class="name">
|
||||
wechat
|
||||
</div>
|
||||
<div class="code-name">.icon-wechat
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-acepanel"></span>
|
||||
<div class="name">
|
||||
acepanel
|
||||
</div>
|
||||
<div class="code-name">.icon-acepanel
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-fnos"></span>
|
||||
<div class="name">
|
||||
@@ -575,6 +635,38 @@
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-wxpay"></use>
|
||||
</svg>
|
||||
<div class="name">wechat</div>
|
||||
<div class="code-name">#icon-wxpay</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-qq"></use>
|
||||
</svg>
|
||||
<div class="name">QQ</div>
|
||||
<div class="code-name">#icon-qq</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-wechat"></use>
|
||||
</svg>
|
||||
<div class="name">wechat</div>
|
||||
<div class="code-name">#icon-wechat</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-acepanel"></use>
|
||||
</svg>
|
||||
<div class="name">acepanel</div>
|
||||
<div class="code-name">#icon-acepanel</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-fnos"></use>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4688792 */
|
||||
src: url('iconfont.svg?t=1766772710945#iconfont') format('svg');
|
||||
src: url('iconfont.svg?t=1779270521617#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -11,6 +11,22 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-wxpay:before {
|
||||
content: "\e735";
|
||||
}
|
||||
|
||||
.icon-qq:before {
|
||||
content: "\e667";
|
||||
}
|
||||
|
||||
.icon-wechat:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.icon-acepanel:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.icon-fnos:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,34 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "5466096",
|
||||
"name": "wechat",
|
||||
"font_class": "wxpay",
|
||||
"unicode": "e735",
|
||||
"unicode_decimal": 59189
|
||||
},
|
||||
{
|
||||
"icon_id": "9186714",
|
||||
"name": "QQ",
|
||||
"font_class": "qq",
|
||||
"unicode": "e667",
|
||||
"unicode_decimal": 58983
|
||||
},
|
||||
{
|
||||
"icon_id": "26267577",
|
||||
"name": "wechat",
|
||||
"font_class": "wechat",
|
||||
"unicode": "e60c",
|
||||
"unicode_decimal": 58892
|
||||
},
|
||||
{
|
||||
"icon_id": "37557578",
|
||||
"name": "acepanel",
|
||||
"font_class": "acepanel",
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "45984300",
|
||||
"name": "飞牛",
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="wxpay" unicode="" d="M410.493712 251.773712c-64.448471-36.97706-74.006881 20.759958-74.006881 20.759958l-80.772173 193.983933c-31.078562 92.178305 26.897497 41.56191 26.897498 41.561909s49.746372-38.732181 87.50193-62.333712c37.732946-23.602608 80.745253-6.927882 80.745254-6.927882l528.043743 250.842313C881.479874 814.421333 720.547129 896 538.352656 896 241.013636 896 0 678.901232 0 411.080547c0-154.046856 79.806318-291.154103 204.11518-380.019214L181.698086-101.56551s-10.92805-38.720336 26.945952-20.759958c25.808892 12.243853 91.603314 56.122953 130.768353 82.82771 61.570288-22.083298 128.651441-34.345455 198.970414-34.345455 297.315331 0 538.378498 217.098768 538.378499 484.924837 0 77.573115-20.313102 150.8338-56.295235 215.861568-168.236416-104.176656-559.545472-346.282128-609.973434-375.167327z" horiz-adv-x="1076" />
|
||||
|
||||
<glyph glyph-name="qq" unicode="" d="M511.09761-61.257c-80.159 0-153.737 25.019-201.11 62.386-24.057-6.702-54.831-17.489-74.252-30.864-16.617-11.439-14.546-23.106-11.55-27.816 13.15-20.689 225.583-13.211 286.912-6.767v3.061zM496.65061-61.257c80.157 0 153.737 25.019 201.11 62.386 24.057-6.702 54.83-17.489 74.253-30.864 16.616-11.439 14.543-23.106 11.55-27.816-13.15-20.689-225.584-13.211-286.914-6.767v3.061zM497.12861 421.476c131.934 0.876 237.669 25.783 273.497 35.34 8.541 2.28 13.11 6.364 13.11 6.364 0.03 1.172 0.542 20.952 0.542 31.155C784.27761 666.167 701.12561 838.827 496.64061 838.838 292.15661 838.827 209.00061 666.168 209.00061 494.335c0-10.203 0.516-29.983 0.547-31.155 0 0 3.717-3.821 10.529-5.67 33.078-8.98 140.803-35.139 276.08-36.034h0.972zM860.28261 276.218c-8.12 26.086-19.204 56.506-30.427 85.72 0 0-6.456 0.795-9.718-0.148-100.71-29.205-222.773-47.818-315.792-46.695h-0.962C410.88561 313.983 289.65061 332.383 189.27961 361.302 185.44461 362.405 177.87261 361.937 177.87261 361.937 166.64961 332.724 155.56661 302.304 147.44761 276.218 108.72961 151.832 121.27261 100.356 130.82461 99.202c20.496-2.474 79.78 93.637 79.78 93.637 0-97.66 88.324-247.617 290.576-248.996a718.01 718.01 0 0 0 5.367 0C708.80161-54.778 797.12261 95.178 797.12261 192.838c0 0 59.284-96.111 79.783-93.637 9.55 1.154 22.093 52.63-16.623 177.017M434.38261 579.083c-27.9-1.24-51.745 30.106-53.24 69.956-1.518 39.877 19.858 73.207 47.764 74.454 27.875 1.224 51.703-30.109 53.218-69.974 1.527-39.877-19.853-73.2-47.742-74.436m206.67 69.956c-1.494-39.85-25.34-71.194-53.24-69.956-27.888 1.238-49.269 34.559-47.742 74.435 1.513 39.868 25.341 71.201 53.216 69.974 27.909-1.247 49.285-34.576 47.767-74.453M683.94261 527.373c-7.323 17.609-81.062 37.227-172.353 37.227h-0.98c-91.29 0-165.031-19.618-172.352-37.227a6.244 6.244 0 0 1-0.535-2.505c0-1.269 0.393-2.414 1.006-3.386 6.168-9.765 88.054-58.018 171.882-58.018h0.98c83.827 0 165.71 48.25 171.881 58.016a6.352 6.352 0 0 1 1.002 3.395c0 0.897-0.2 1.736-0.531 2.498M467.63161 639.623c1.26-15.886-7.377-30-19.266-31.542-11.907-1.544-22.569 10.083-23.836 25.978-1.243 15.895 7.381 30.008 19.25 31.538 11.927 1.549 22.607-10.088 23.852-25.974m73.097-7.935c2.533 4.118 19.827 25.77 55.62 17.886 9.401-2.07 13.75-5.116 14.668-6.316 1.355-1.77 1.726-4.29 0.352-7.684-2.722-6.725-8.338-6.542-11.454-5.226-2.01 0.85-26.94 15.889-49.905-6.553-1.579-1.545-4.405-2.074-7.085-0.242-2.678 1.834-3.786 5.553-2.196 8.135M504.33261 311.505h-0.967c-63.568-0.752-140.646 7.504-215.286 21.92-6.391-36.262-10.25-81.838-6.936-136.196 8.37-137.384 91.62-223.736 220.118-224.996H506.48461c128.498 1.26 211.748 87.612 220.12 224.996 3.314 54.362-0.547 99.938-6.94 136.203-74.654-14.423-151.745-22.684-215.332-21.927M323.27461 318.984v-137.468s64.957-12.705 130.031-3.91V304.41c-41.225 2.262-85.688 7.304-130.031 14.574M788.09761 463.464s-121.98-40.387-283.743-41.539h-0.962c-161.497 1.147-283.328 41.401-283.744 41.539l-40.854-106.952c102.186-32.31 228.837-53.135 324.598-51.926l0.96 0.002c95.768-1.216 222.4 19.61 324.6 51.924l-40.855 106.952z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="wechat" unicode="" d="M1024.16 201.184c0 149.92-143.104 271.392-319.584 271.392-176.576 0-319.68-121.504-319.68-271.392S528-70.208 704.576-70.208c55.456 0 107.648 12.096 153.184 33.248l125.984-54.528-14.592 140.544c34.784 43.392 55.04 95.808 55.04 152.128zM596.832 274.72c-25.152 0-45.472 20.352-45.472 45.472s20.32 45.472 45.472 45.472c25.12 0 45.44-20.384 45.44-45.472s-20.384-45.472-45.44-45.472z m215.392 0c-25.056 0-45.44 20.352-45.44 45.472s20.384 45.472 45.44 45.472c25.184 0 45.536-20.384 45.536-45.472s-20.352-45.472-45.536-45.472zM704.576 508.512c49.376 0 96.416-8.8 139.264-24.64 0.32 5.728 0.992 11.232 0.992 16.992 0 198.08-189.152 358.624-422.432 358.624C189.184 859.488 0.032 698.976 0.032 500.864c0-74.496 26.816-143.776 72.704-201.12L53.472 114.08l166.432 72.096c41.216-19.2 86.784-32.16 134.88-38.784-3.616 17.504-5.824 35.424-5.824 53.792 0.032 169.44 159.552 307.296 355.616 307.296z m-139.808 209.6c33.184 0 60-26.88 60-60 0-33.184-26.816-60.064-60-60.064s-60.032 26.88-60.032 60.064c0 33.152 26.88 60 60.032 60zM280.032 598.048c-33.184 0-60 26.88-60 60.064 0 33.152 26.848 60 60 60 33.184 0 60.032-26.88 60.032-60s-26.88-60.064-60.032-60.064z" horiz-adv-x="1025" />
|
||||
|
||||
<glyph glyph-name="acepanel" unicode="" d="M0 896m100.8867 0l822.2266 0q100.8867 0 100.8867-100.8867l0-822.2266q0-100.8867-100.8867-100.8867l-822.2266 0q-100.8867 0-100.8867 100.8867l0 822.2266q0 100.8867 100.8867 100.8867ZM469.123153 705.576355h171.507389l2.522167-29.004926 3.783252-30.26601 6.305418-55.487685 7.566503-69.359606 3.783251-29.004926 2.522167-23.960591 5.044335-42.876847 2.522168-26.482759 5.044335-40.35468 2.522167-23.960591 5.044335-44.137931 3.783252-32.788177 2.522167-21.438424v-6.305419h229.517241l-7.566502-27.743842-15.133005-50.44335-11.349754-39.093596-11.349753-39.093596-3.783252-10.08867h-23.960591l6.305419 6.305419 7.566503 8.827586v3.783251l-21.438424 16.394089h-3.783251l-8.827587-10.08867-11.349753-6.305419-8.827586-2.522167h-11.349754l-10.08867 3.783251-6.305419 7.566502-2.522167 7.566503v8.827586l1.261083 1.261084 36.571429 3.783251 25.221675 7.566503 13.871921 7.566502 8.827586 8.827586 5.044335 10.08867 2.522168 16.394089-2.522168 12.610837-6.305418 10.08867-8.827587 7.566503-15.133005 6.305418-5.044335 1.261084h-29.004926l-16.394088-5.044335-13.871922-7.566502-12.610837-11.349754-12.610837-18.916256-7.566503-20.17734-2.522167-17.655172v-17.655173l2.522167-15.133005 6.305419-15.133005 5.044335-6.305418v-2.522168h-49.182266l7.566502 10.08867 7.566503 11.349754v5.044335l-25.221675 11.349753-3.783251 1.261084-8.827587-12.610837-6.305418-6.305419-8.827587-3.783251h-8.827586l-10.08867 3.783251-5.044335 6.305419-2.522167 10.08867 2.522167 20.17734 5.044335 23.960591 5.044335 12.610837 6.305419 7.566503 7.566502 3.783251 6.305419 1.261084h8.827586l8.827587-3.783252 6.305418-10.08867 1.261084-3.783251 6.305419 1.261084 26.482758 10.08867-1.261083 10.08867-7.566503 12.610837-10.08867 7.566503-13.871921 6.305418-8.827586 1.261084h-25.221675l-15.133005-5.044335-13.871921-7.566502-12.610838-11.349754-8.827586-13.871921-6.305418-12.610838-3.783252-12.610837-2.522167-22.699507 1.261084-25.221675 6.305418-15.133005 6.305419-11.349754h-37.832512l-2.522168 16.394089-3.783251 45.399015-3.783251 35.310344-5.044335 59.270936H435.073892l6.305418 15.133005 18.916257 39.093596 10.088669 22.699508 10.08867 20.17734 6.305419 13.871921h66.837439l-1.261084 5.044335-5.044335 71.881773-2.522168 29.004926-3.783251 55.487685-6.305419 81.970443v11.349754h-8.827586l-2.522167-7.566502-20.17734-44.137931-20.17734-44.137932-22.699507-49.182266-23.960592-51.704433-20.17734-42.876847-10.088669-23.960591-12.610838-27.743843-10.08867-20.17734-10.08867-21.438423-10.08867-20.17734-15.133005-31.527094-22.699507-46.660098-13.871921-29.004926-15.133005-31.527094-13.871921-29.004926-1.261084-1.261084H139.980296l2.522167 7.566503 17.655172 35.310344 12.610838 23.960591 29.004926 56.748769 12.610837 23.960591 16.394089 32.788177 12.610838 23.960592 25.221674 49.182266 17.655173 34.049261 10.08867 18.916256 10.08867 20.17734 30.26601 59.270936 17.655172 34.049261 11.349754 21.438423 10.08867 20.17734 29.004926 56.748769 21.438423 41.615763 18.916257 36.571429 22.699507 44.137931zM809.615764 170.876847h13.871921l7.566502-3.783251 2.522168-3.783251v-11.349754l-6.305419-7.566502-12.610837-5.044335-22.699508-3.783252h-7.566502l2.522167 13.871922 6.305419 10.08867 10.08867 8.827586z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="fnos" unicode="" d="M144.482434 896h730.821577c67.983402-28.323585 116.141145-76.481328 144.46473-144.46473v-730.821577c-28.323585-67.983402-76.481328-116.141145-144.46473-144.46473h-730.821577c-67.983402 28.323585-116.141145 76.481328-144.46473 144.46473v730.821577c28.323585 67.983402 76.481328 116.141145 144.46473 144.46473zM229.461687 632.564315c-1.665593-32.445079 1.164216-66.43678 8.497926-101.975103 17.990108-19.800166 40.654075-29.717245 67.983402-29.742739-18.63595-17.208299-27.133876-38.453112-25.493776-63.73444-84.486373 1.053743-117.058921 43.543369-97.726141 127.46888a431.320697 431.320697 0 0 0 46.738589 67.983402zM781.826833 632.564315c66.938158-53.536929 75.436083-114.44156 25.493776-182.705394a3226.917178 3226.917178 0 0 0-399.40249-12.746888v-237.941909h50.987552c-9.883087 77.900481 24.108614 109.062373 101.975104 93.477179a275.647203 275.647203 0 0 0 4.248962 67.983402 80.458357 80.458357 0 0 0 21.244813 12.746888 571.281527 571.281527 0 0 0 169.958507-4.248962c-32.436581-40.280166-74.926207-60.105826-127.46888-59.485477 11.149278-79.387618-22.842423-107.719701-101.975104-84.979254a347.871071 347.871071 0 0 0-4.248962-76.481327 59.273029 59.273029 0 0 0-29.742739-21.244814 361.14483 361.14483 0 0 0-110.473029 0 59.273029 59.273029 0 0 0-29.742739 21.244814 814.381676 814.381676 0 0 0-12.746888 161.460581 814.381676 814.381676 0 0 0 12.746888 161.46058 305.415436 305.415436 0 0 0 38.240664 29.742739l365.410789 8.497925c15.576697 4.248963 25.493776 14.166041 29.742738 29.742739 5.761593 31.62078 4.34244 62.782672-4.248962 93.477178z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="ksyun" unicode="" d="M718.5408 442.1632c-4.1984 0-8.3968 0.7168-12.5952 0.7168a103.8336 103.8336 0 0 1-67.8912-25.088 139.6736 139.6736 0 0 1 13.6192 60.3136 133.12 133.12 0 0 1-2.4576 25.7024 139.5712 139.5712 0 0 1-274.432 0 133.12 133.12 0 0 1-2.4576-25.7024 139.6736 139.6736 0 0 1 13.6192-60.3136 103.8336 103.8336 0 0 1-67.8912 25.088c-4.1984 0-8.3968 0-12.5952-0.7168a104.5504 104.5504 0 1 1 101.0688-159.4368 116.736 116.736 0 0 1 6.144 11.0592 105.1648 105.1648 0 0 1 4.9152 76.6976 62.6688 62.6688 0 0 0 27.3408-20.48l2.6624-3.8912a83.2512 83.2512 0 0 0 133.9392 3.3792l4.7104-6.8608 51.2-73.9328a84.0704 84.0704 0 0 1 62.464-34.6112h5.5296a104.5504 104.5504 0 0 1 12.5952 208.384zM512 896a512 512 0 1 1 512-512A512 512 0 0 1 512 896z m200.8064-732.3648h-6.8608a152.4736 152.4736 0 0 0-120.5248 58.9824S509.2352 331.3664 508.0064 332.8a42.1888 42.1888 0 0 1-32.4608 15.2576 40.96 40.96 0 0 1-24.064-7.5776l122.88-175.4112a62.5664 62.5664 0 0 1 77.0048-20.48A97.5872 97.5872 0 0 0 593.92 112.4352a97.0752 97.0752 0 0 0-95.5392 39.1168l-49.4592 70.656a174.8992 174.8992 0 1 0-143.36 290.5088 209.7152 209.7152 0 0 0 413.9008 0 174.7968 174.7968 0 0 0-6.144-349.0816z" horiz-adv-x="1024" />
|
||||
|
||||
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 94 KiB |
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<div class="flex captcha-image-input">
|
||||
<a-input :value="valueRef" :placeholder="t('certd.captcha.inputImageCode')" autocomplete="off" @update:value="onChange">
|
||||
<template #prefix>
|
||||
<fs-icon icon="ion:image-outline"></fs-icon>
|
||||
@@ -71,3 +71,10 @@ function emitChange(value: any) {
|
||||
emit("change", value);
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.captcha-image-input {
|
||||
.input-right {
|
||||
background-color: #cfcfcf;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+15
-8
@@ -11,14 +11,16 @@
|
||||
<td class="record-value" :title="cnameRecord.recordValue">
|
||||
<fs-copyable v-model="cnameRecord.recordValue"></fs-copyable>
|
||||
</td>
|
||||
<td class="status center flex-center">
|
||||
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
|
||||
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
|
||||
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.verifyPlan.resetStatusTooltip')">
|
||||
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
|
||||
</a-tooltip>
|
||||
<td class="status center">
|
||||
<span class="status-content">
|
||||
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
|
||||
<a-tooltip v-if="cnameRecord.error" :title="cnameRecord.error">
|
||||
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="cnameRecord.status === 'valid'" :title="t('certd.verifyPlan.resetStatusTooltip')">
|
||||
<fs-icon class="ml-2 color-yellow text-md pointer" icon="solar:undo-left-square-bold" @click="resetStatus"></fs-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</td>
|
||||
<td class="center">
|
||||
<template v-if="cnameRecord.status !== 'valid'">
|
||||
@@ -142,5 +144,10 @@ async function resetStatus() {
|
||||
.fs-copyable {
|
||||
width: 100%;
|
||||
}
|
||||
.status-content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+20
-5
@@ -2,11 +2,11 @@
|
||||
<table class="cname-verify-plan">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 160px">{{ t("certd.verifyPlan.hostRecord") }}</td>
|
||||
<td style="width: 100px; text-align: center">{{ t("certd.verifyPlan.recordType") }}</td>
|
||||
<td style="width: 250px">{{ t("certd.verifyPlan.setCnameRecord") }}</td>
|
||||
<td style="width: 120px" class="center">{{ t("certd.status") }}</td>
|
||||
<td style="width: 90px" class="center">{{ t("certd.verifyPlan.operation") }}</td>
|
||||
<td class="col-host">{{ t("certd.verifyPlan.hostRecord") }}</td>
|
||||
<td class="col-type center">{{ t("certd.verifyPlan.recordType") }}</td>
|
||||
<td class="col-value">{{ t("certd.verifyPlan.setCnameRecord") }}</td>
|
||||
<td class="col-status center">{{ t("certd.status") }}</td>
|
||||
<td class="col-action center">{{ t("certd.verifyPlan.operation") }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<template v-for="key in domains" :key="key">
|
||||
@@ -49,6 +49,21 @@ function onRecordChange(domain: string, record: CnameRecord) {
|
||||
.cname-verify-plan {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
.col-host {
|
||||
width: 220px;
|
||||
}
|
||||
.col-type {
|
||||
width: 100px;
|
||||
}
|
||||
.col-value {
|
||||
width: 360px;
|
||||
}
|
||||
.col-status {
|
||||
width: 120px;
|
||||
}
|
||||
.col-action {
|
||||
width: 150px;
|
||||
}
|
||||
tbody tr td {
|
||||
border-top: 1px solid #e8e8e8 !important;
|
||||
}
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<tbody class="dns-persist-record-info">
|
||||
<tr v-if="dnsPersistRecord">
|
||||
<td class="host-record" :title="dnsPersistRecord.hostRecord">
|
||||
<fs-copyable v-model="dnsPersistRecord.hostRecord"></fs-copyable>
|
||||
</td>
|
||||
<td style="text-align: center">TXT</td>
|
||||
<td class="record-value" :title="dnsPersistRecord.recordValue">
|
||||
<fs-copyable v-model="dnsPersistRecord.recordValue"></fs-copyable>
|
||||
</td>
|
||||
<td class="status center">
|
||||
<fs-values-format v-model="dnsPersistRecord.status" :dict="statusDict" />
|
||||
</td>
|
||||
<td class="center">
|
||||
<template v-if="dnsPersistRecord.status !== 'valid'">
|
||||
<a-space>
|
||||
<a-button type="primary" size="small" @click="openSettingDialog">设置TXT</a-button>
|
||||
<a-button type="primary" size="small" :loading="loading" @click="doVerify">校验</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<div v-else class="helper">请勿删除TXT记录</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td colspan="5" class="color-red">{{ errorMessage || "请先选择ACME账号授权" }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { dict } from "@fast-crud/fast-crud";
|
||||
import { message } from "ant-design-vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { GetByDomain, Verify } from "/@/views/certd/cert/dns-persist/api";
|
||||
import { useDnsPersistSettingDialog } from "/@/views/certd/cert/dns-persist/use-setting-dialog";
|
||||
import { DnsPersistRecord } from "./type";
|
||||
|
||||
defineOptions({
|
||||
name: "DnsPersistRecordInfo",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
domain: string;
|
||||
caType?: string;
|
||||
acmeAccountAccessId?: number;
|
||||
commonAcmeAccountAccessId?: number;
|
||||
wildcard?: boolean;
|
||||
persistUntil?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [DnsPersistRecord];
|
||||
}>();
|
||||
|
||||
const statusDict = dict({
|
||||
data: [
|
||||
{ value: "pending", label: "待设置", color: "warning" },
|
||||
{ value: "validating", label: "校验中", color: "blue" },
|
||||
{ value: "valid", label: "有效", color: "green" },
|
||||
{ value: "failed", label: "请重试", color: "red" },
|
||||
],
|
||||
});
|
||||
|
||||
const dnsPersistRecord = ref<DnsPersistRecord | null>(null);
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const { openDnsPersistSettingDialog } = useDnsPersistSettingDialog();
|
||||
|
||||
function onRecordChange() {
|
||||
if (dnsPersistRecord.value) {
|
||||
emit("change", dnsPersistRecord.value);
|
||||
} else {
|
||||
emit("change", {
|
||||
domain: props.domain,
|
||||
status: null,
|
||||
} as any);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecord() {
|
||||
errorMessage.value = "";
|
||||
dnsPersistRecord.value = null;
|
||||
if (!props.domain || (!props.acmeAccountAccessId && !props.commonAcmeAccountAccessId)) {
|
||||
onRecordChange();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
dnsPersistRecord.value = await GetByDomain({
|
||||
domain: props.domain,
|
||||
caType: props.caType,
|
||||
acmeAccountAccessId: props.acmeAccountAccessId,
|
||||
commonAcmeAccountAccessId: props.commonAcmeAccountAccessId,
|
||||
wildcard: props.wildcard,
|
||||
persistUntil: props.persistUntil,
|
||||
createOnNotFound: true,
|
||||
});
|
||||
onRecordChange();
|
||||
} catch (e: any) {
|
||||
errorMessage.value = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.domain, props.caType, props.acmeAccountAccessId, props.commonAcmeAccountAccessId, props.wildcard, props.persistUntil],
|
||||
async () => {
|
||||
await loadRecord();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
async function doVerify() {
|
||||
if (!dnsPersistRecord.value?.id) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const ok = await Verify(dnsPersistRecord.value.id);
|
||||
message[ok ? "success" : "error"](ok ? "校验成功" : "未找到匹配的TXT记录,请稍后重试");
|
||||
await loadRecord();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openSettingDialog() {
|
||||
if (!dnsPersistRecord.value) {
|
||||
return;
|
||||
}
|
||||
openDnsPersistSettingDialog({
|
||||
record: dnsPersistRecord.value,
|
||||
onDone: loadRecord,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.dns-persist-record-info {
|
||||
.fs-copyable {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<table class="dns-persist-verify-plan">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="col-host">TXT主机名</td>
|
||||
<td class="col-type center">记录类型</td>
|
||||
<td class="col-value">请设置TXT记录(验证成功以后不要删除)</td>
|
||||
<td class="col-status center">状态</td>
|
||||
<td class="col-action center">操作</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<template v-for="key in domains" :key="key">
|
||||
<dns-persist-record-info
|
||||
:domain="key"
|
||||
:ca-type="caType"
|
||||
:acme-account-access-id="acmeAccountAccessId"
|
||||
:common-acme-account-access-id="commonAcmeAccountAccessId"
|
||||
:wildcard="modelValue[key]?.wildcard"
|
||||
:persist-until="modelValue[key]?.persistUntil"
|
||||
@change="onRecordChange(key, $event)"
|
||||
/>
|
||||
</template>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import DnsPersistRecordInfo from "./dns-persist-record-info.vue";
|
||||
import { DnsPersistRecord } from "./type";
|
||||
|
||||
defineOptions({
|
||||
name: "DnsPersistVerifyPlan",
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change"]);
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, DnsPersistRecord>;
|
||||
caType?: string;
|
||||
acmeAccountAccessId?: number;
|
||||
commonAcmeAccountAccessId?: number;
|
||||
}>();
|
||||
|
||||
const domains = computed(() => {
|
||||
return Object.keys(props.modelValue || {});
|
||||
});
|
||||
|
||||
function onRecordChange(domain: string, record: DnsPersistRecord) {
|
||||
const value = { ...props.modelValue };
|
||||
value[domain] = {
|
||||
...value[domain],
|
||||
...record,
|
||||
};
|
||||
emit("update:modelValue", value);
|
||||
emit("change", value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.dns-persist-verify-plan {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
.col-host {
|
||||
width: 220px;
|
||||
}
|
||||
.col-type {
|
||||
width: 100px;
|
||||
}
|
||||
.col-value {
|
||||
width: 360px;
|
||||
}
|
||||
.col-status {
|
||||
width: 120px;
|
||||
}
|
||||
.col-action {
|
||||
width: 150px;
|
||||
}
|
||||
tbody tr td {
|
||||
border-top: 1px solid #e8e8e8 !important;
|
||||
}
|
||||
tr {
|
||||
td {
|
||||
border: 0 !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+48
-1
@@ -46,13 +46,28 @@
|
||||
<div class="form-item">
|
||||
<span class="label">{{ t("certd.verifyPlan.dnsAccess") }}:</span>
|
||||
<span class="input">
|
||||
<access-selector v-model="item.dnsProviderAccessId" size="small" :type="item.dnsProviderAccessType || item.dnsProviderType" :placeholder="t('certd.verifyPlan.pleaseSelect')" @change="onPlanChanged"></access-selector>
|
||||
<access-selector
|
||||
v-model="item.dnsProviderAccessId"
|
||||
size="small"
|
||||
:type="item.dnsProviderAccessType || item.dnsProviderType"
|
||||
:placeholder="t('certd.verifyPlan.pleaseSelect')"
|
||||
@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 v-if="item.type === 'dns-persist'" class="plan-dns-persist">
|
||||
<dns-persist-verify-plan
|
||||
v-model="item.dnsPersistVerifyPlan"
|
||||
:ca-type="caType"
|
||||
:acme-account-access-id="acmeAccountAccessId"
|
||||
:common-acme-account-access-id="commonAcmeAccountAccessId"
|
||||
@change="onPlanChanged"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="item.type === 'http'" class="plan-http">
|
||||
<http-verify-plan v-model="item.httpVerifyPlan" @change="onPlanChanged" />
|
||||
<div class="helper">{{ t("certd.verifyPlan.httpHelper") }}</div>
|
||||
@@ -76,6 +91,7 @@ import { useI18n } from "vue-i18n";
|
||||
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 DnsPersistVerifyPlan from "./dns-persist-verify-plan.vue";
|
||||
import HttpVerifyPlan from "./http-verify-plan.vue";
|
||||
import { Form } from "ant-design-vue";
|
||||
import { DomainsVerifyPlanInput } from "./type";
|
||||
@@ -92,6 +108,10 @@ const challengeTypeOptions = ref<any[]>([
|
||||
label: t("certd.verifyPlan.dnsChallenge"),
|
||||
value: "dns",
|
||||
},
|
||||
{
|
||||
label: "DNS持久验证",
|
||||
value: "dns-persist",
|
||||
},
|
||||
{
|
||||
label: t("certd.verifyPlan.cnameChallenge"),
|
||||
value: "cname",
|
||||
@@ -106,6 +126,9 @@ const props = defineProps<{
|
||||
modelValue?: DomainsVerifyPlanInput;
|
||||
domains?: string[];
|
||||
defaultType?: string;
|
||||
caType?: string;
|
||||
acmeAccountAccessId?: number;
|
||||
commonAcmeAccountAccessId?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -189,11 +212,15 @@ async function onDomainsChanged(domains: string[]) {
|
||||
|
||||
const cnameOrigin = planItem.cnameVerifyPlan;
|
||||
const httpOrigin = planItem.httpVerifyPlan;
|
||||
const dnsPersistOrigin = planItem.dnsPersistVerifyPlan;
|
||||
planItem.cnameVerifyPlan = {};
|
||||
planItem.httpVerifyPlan = {};
|
||||
planItem.dnsPersistVerifyPlan = {};
|
||||
const cnamePlan = planItem.cnameVerifyPlan;
|
||||
const httpPlan = planItem.httpVerifyPlan;
|
||||
const dnsPersistPlan = planItem.dnsPersistVerifyPlan;
|
||||
for (const subDomain of domainGroupItem.keySubDomains) {
|
||||
const wildcard = true;
|
||||
if (!cnameOrigin[subDomain]) {
|
||||
//@ts-ignore
|
||||
planItem.cnameVerifyPlan[subDomain] = {
|
||||
@@ -225,6 +252,19 @@ async function onDomainsChanged(domains: string[]) {
|
||||
domain: subDomain,
|
||||
};
|
||||
}
|
||||
|
||||
if (!dnsPersistOrigin?.[subDomain]) {
|
||||
//@ts-ignore
|
||||
dnsPersistPlan[subDomain] = {
|
||||
domain: subDomain,
|
||||
wildcard,
|
||||
};
|
||||
} else {
|
||||
dnsPersistPlan[subDomain] = {
|
||||
...dnsPersistOrigin[subDomain],
|
||||
wildcard,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const subDomain of Object.keys(cnamePlan)) {
|
||||
@@ -238,6 +278,12 @@ async function onDomainsChanged(domains: string[]) {
|
||||
delete httpPlan[subDomain];
|
||||
}
|
||||
}
|
||||
|
||||
for (const subDomain of Object.keys(dnsPersistPlan)) {
|
||||
if (!domainGroupItem.keySubDomains.includes(subDomain)) {
|
||||
delete dnsPersistPlan[subDomain];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const domain of Object.keys(planRef.value)) {
|
||||
const mainDomains = Object.keys(domainGroups);
|
||||
@@ -268,6 +314,7 @@ watch(
|
||||
overflow-x: auto;
|
||||
.fullscreen-modal {
|
||||
display: none;
|
||||
background-color: rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
&.fullscreen {
|
||||
|
||||
+18
-1
@@ -7,15 +7,32 @@ export type HttpRecord = {
|
||||
httpUploadRootDir: string;
|
||||
};
|
||||
|
||||
export type DnsPersistRecord = {
|
||||
id?: number;
|
||||
domain: string;
|
||||
mainDomain?: string;
|
||||
status?: string;
|
||||
hostRecord?: string;
|
||||
recordValue?: string;
|
||||
caType?: string;
|
||||
acmeAccountAccessId?: number;
|
||||
accountUri?: string;
|
||||
wildcard?: boolean;
|
||||
persistUntil?: number;
|
||||
dnsProviderType?: string;
|
||||
dnsProviderAccess?: number;
|
||||
};
|
||||
|
||||
export type DomainVerifyPlanInput = {
|
||||
domain: string;
|
||||
domains: string[];
|
||||
type: "cname" | "dns" | "http";
|
||||
type: "cname" | "dns" | "http" | "dns-persist";
|
||||
dnsProviderType?: string;
|
||||
dnsProviderAccessType?: string;
|
||||
dnsProviderAccessId?: number;
|
||||
cnameVerifyPlan?: Record<string, CnameRecord>;
|
||||
httpVerifyPlan?: Record<string, HttpRecord>;
|
||||
dnsPersistVerifyPlan?: Record<string, DnsPersistRecord>;
|
||||
};
|
||||
export type DomainsVerifyPlanInput = {
|
||||
[key: string]: DomainVerifyPlanInput;
|
||||
|
||||
+8
@@ -46,6 +46,14 @@ function checkDomainVerifyPlan(rule: any, value: DomainsVerifyPlanInput) {
|
||||
if (!value[domain].dnsProviderType || !value[domain].dnsProviderAccessId) {
|
||||
throw new Error($t("certd.verifyPlan.errors.dnsProviderRequired", { domain }));
|
||||
}
|
||||
} else if (type === "dns-persist") {
|
||||
const subDomains = Object.keys(value[domain].dnsPersistVerifyPlan || {});
|
||||
for (const subDomain of subDomains) {
|
||||
const plan = value[domain].dnsPersistVerifyPlan[subDomain];
|
||||
if (plan.status !== "valid") {
|
||||
throw new Error(`DNS持久验证记录(${subDomain})还未校验成功`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="refresh-input">
|
||||
<div class="refresh-input-line">
|
||||
<a-input class="refresh-input-control" :value="value" :placeholder="placeholder" allow-clear @update:value="emit('update:value', $event)"></a-input>
|
||||
<fs-button :loading="loading" type="primary" :text="buttonText" :icon="icon" @click="doRefresh"></fs-button>
|
||||
<a-input class="refresh-input-control" :value="value" :placeholder="placeholder" :allow-clear="!disabled" :disabled="disabled" @update:value="emit('update:value', $event)"></a-input>
|
||||
<fs-button :loading="loading" :disabled="disabled" type="primary" :text="buttonText" :icon="icon" @click="doRefresh"></fs-button>
|
||||
</div>
|
||||
<div class="helper" :class="{ error: hasError }">
|
||||
{{ message }}
|
||||
@@ -25,6 +25,7 @@ type RefreshInputProps = ComponentPropsType & {
|
||||
icon?: string;
|
||||
placeholder?: string;
|
||||
successMessage?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const fromType: any = inject("getFromType");
|
||||
@@ -49,6 +50,9 @@ const placeholder = computed(() => props.placeholder || "");
|
||||
const successMessage = computed(() => props.successMessage || "刷新成功,请保存配置");
|
||||
|
||||
const doRefresh = async () => {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ function createChallengeTypeDict() {
|
||||
return dict({
|
||||
data: [
|
||||
{ value: "dns", label: $t("certd.verifyPlan.dnsChallenge"), color: "green" },
|
||||
{ value: "dns-persist", label: "DNS持久验证", color: "cyan" },
|
||||
{ value: "cname", label: $t("certd.verifyPlan.cnameProxyChallenge"), color: "blue" },
|
||||
{ value: "http", label: $t("certd.verifyPlan.httpChallenge"), color: "yellow" },
|
||||
],
|
||||
@@ -39,7 +40,12 @@ export const Dicts = {
|
||||
sslProviderDict: dict({
|
||||
data: [
|
||||
{ value: "letsencrypt", label: "Let's Encrypt" },
|
||||
{ value: "letsencrypt_staging", label: "Let's Encrypt测试环境" },
|
||||
{ value: "google", label: "Google" },
|
||||
{ value: "zerossl", label: "ZeroSSL" },
|
||||
{ value: "sslcom", label: "SSL.com" },
|
||||
{ value: "litessl", label: "litessl" },
|
||||
{ value: "custom", label: "自定义ACME" },
|
||||
],
|
||||
}),
|
||||
get challengeTypeDict() {
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
siteMonitor: "Site Certificate Monitor",
|
||||
settings: "Settings",
|
||||
accessManager: "Access Management",
|
||||
dnsPersistRecord: "DNS Persist Records",
|
||||
subDomain: "Subdomain Delegation Settings",
|
||||
pipelineGroup: "Pipeline Group Management",
|
||||
openKey: "Open API Key",
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
siteMonitor: "站点证书监控",
|
||||
settings: "设置",
|
||||
accessManager: "授权管理",
|
||||
dnsPersistRecord: "DNS持久验证记录",
|
||||
subDomain: "子域名托管设置",
|
||||
pipelineGroup: "流水线分组管理",
|
||||
openKey: "开放接口密钥",
|
||||
|
||||
@@ -186,6 +186,17 @@ export const certdResources = [
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.dnsPersistRecord",
|
||||
name: "DnsPersistRecord",
|
||||
path: "/certd/cert/dns-persist",
|
||||
component: "/certd/cert/dns-persist/index.vue",
|
||||
meta: {
|
||||
icon: "ion:shield-half-outline",
|
||||
auth: true,
|
||||
keepAlive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "certd.subDomain",
|
||||
name: "SubDomain",
|
||||
|
||||
@@ -12,6 +12,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const { props, ctx, api } = context;
|
||||
const lastResRef = ref();
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
query.query = query.query || {};
|
||||
if (props.subtype) {
|
||||
query.query.subtype = props.subtype;
|
||||
} else {
|
||||
delete query.query.subtype;
|
||||
}
|
||||
return await context.api.GetList(query);
|
||||
};
|
||||
const editRequest = async (req: EditReq) => {
|
||||
@@ -47,7 +53,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
const { myProjectDict } = useDicts();
|
||||
const typeRef = ref("aliyun");
|
||||
context.typeRef = typeRef;
|
||||
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api);
|
||||
const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api, props.subtype);
|
||||
commonColumnsDefine.type.form.component.disabled = true;
|
||||
const projectStore = useProjectStore();
|
||||
return {
|
||||
|
||||
@@ -21,6 +21,10 @@ export default defineComponent({
|
||||
type: String, //user | sys
|
||||
default: "user",
|
||||
},
|
||||
subtype: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
modelValue: {},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
@@ -30,10 +34,17 @@ export default defineComponent({
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||
|
||||
// 你可以调用此方法,重新初始化crud配置
|
||||
function refreshSearch() {
|
||||
const form: any = { type: props.type };
|
||||
if (props.subtype) {
|
||||
form.subtype = props.subtype;
|
||||
}
|
||||
crudExpose.setSearchFormData({ form, mergeForm: true });
|
||||
crudExpose.doRefresh();
|
||||
}
|
||||
function onTypeChanged(value: any) {
|
||||
context.typeRef.value = value;
|
||||
crudExpose.setSearchFormData({ form: { type: value }, mergeForm: true });
|
||||
crudExpose.doRefresh();
|
||||
refreshSearch();
|
||||
}
|
||||
watch(
|
||||
() => {
|
||||
@@ -44,6 +55,14 @@ export default defineComponent({
|
||||
onTypeChanged(value);
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => {
|
||||
return props.subtype;
|
||||
},
|
||||
() => {
|
||||
refreshSearch();
|
||||
}
|
||||
);
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
onTypeChanged(props.type);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<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">
|
||||
<cert-access-modal v-model="selectedId" :type="type" :from="from"></cert-access-modal>
|
||||
<cert-access-modal v-model="selectedId" :type="type" :subtype="subtype" :from="from"></cert-access-modal>
|
||||
</div>
|
||||
</a-modal>
|
||||
</a-form-item-rest>
|
||||
@@ -35,6 +35,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: "aliyun",
|
||||
},
|
||||
subtype: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ColumnCompositionProps, dict } from "@fast-crud/fast-crud";
|
||||
import { computed, provide, ref, toRef } from "vue";
|
||||
import { provide, ref, toRef } from "vue";
|
||||
import { useReference } from "/@/use/use-refrence";
|
||||
import { forEach, get, merge, set } from "lodash-es";
|
||||
import SecretPlainGetter from "/@/views/certd/access/access-selector/access/secret-plain-getter.vue";
|
||||
import { utils } from "/@/utils";
|
||||
|
||||
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||
export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any, fixedSubtype?: string) {
|
||||
provide("getFromType", api.from);
|
||||
provide("accessApi", api);
|
||||
provide("get:plugin:type", () => {
|
||||
@@ -34,6 +34,13 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) {
|
||||
}
|
||||
}
|
||||
console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value);
|
||||
if (mode === "add" && define.subtype && fixedSubtype) {
|
||||
form.access = form.access || {};
|
||||
const subtypeKey = `access.${define.subtype}`;
|
||||
if (get(form, subtypeKey) == null) {
|
||||
set(form, subtypeKey, fixedSubtype);
|
||||
}
|
||||
}
|
||||
forEach(define.input, (value: any, mapKey: any) => {
|
||||
const key = "access." + mapKey;
|
||||
const field = {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { request } from "/src/api/service";
|
||||
|
||||
const apiPrefix = "/cert/dns-persist";
|
||||
|
||||
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 BuildRecord(body: { domain: string; accountUri: string; wildcard?: boolean; persistUntil?: number }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/build",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export async function GetByDomain(body: { domain: string; caType?: string; acmeAccountAccessId?: number; commonAcmeAccountAccessId?: number; wildcard?: boolean; persistUntil?: number; createOnNotFound?: boolean }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/getByDomain",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export async function CheckRecord(body: { hostRecord: string; recordValue: string }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/check",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export async function Verify(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/verify",
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function TriggerVerify(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/triggerVerify",
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function CreateTxt(body: { id: number; dnsProviderType?: string; dnsProviderAccess?: number }) {
|
||||
return await request({
|
||||
url: apiPrefix + "/createTxt",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { message, Modal, notification } from "ant-design-vue";
|
||||
import * as api from "./api";
|
||||
import { Dicts } from "/@/components/plugins/lib/dicts";
|
||||
import { createAccessApi } from "/@/views/certd/access/api";
|
||||
import { useDnsPersistSettingDialog } from "./use-setting-dialog";
|
||||
|
||||
function parseAccount(account: any) {
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
if (typeof account === "string") {
|
||||
return JSON.parse(account);
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const accessApi = createAccessApi();
|
||||
const { openDnsPersistSettingDialog } = useDnsPersistSettingDialog();
|
||||
const accessDict = dict({
|
||||
value: "id",
|
||||
label: "name",
|
||||
url: "accessDict",
|
||||
async getNodesByValues(ids: number[]) {
|
||||
return await accessApi.GetDictByIds(ids);
|
||||
},
|
||||
});
|
||||
|
||||
const dnsProviderTypeDict = dict({
|
||||
url: "pi/dnsProvider/dnsProviderTypeDict",
|
||||
});
|
||||
const statusDict = dict({
|
||||
data: [
|
||||
{ value: "pending", label: "待设置", color: "warning" },
|
||||
{ value: "created", label: "已创建", color: "blue" },
|
||||
{ value: "validating", label: "校验中", color: "blue" },
|
||||
{ value: "valid", label: "有效", color: "green" },
|
||||
{ value: "failed", label: "请重试", color: "red" },
|
||||
],
|
||||
});
|
||||
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
return await api.GetList(query);
|
||||
};
|
||||
const editRequest = async ({ form, row }: EditReq) => {
|
||||
form.id = row.id;
|
||||
return await api.UpdateObj(form);
|
||||
};
|
||||
const delRequest = async ({ row }: DelReq) => {
|
||||
const res = await api.DelObj(row.id);
|
||||
if (res?.message) {
|
||||
notification.warning({
|
||||
message: "请到供应商删除TXT记录",
|
||||
description: res.message,
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
return res;
|
||||
};
|
||||
const addRequest = async ({ form }: AddReq) => {
|
||||
return await api.AddObj(form);
|
||||
};
|
||||
|
||||
async function fillRecord(form: any) {
|
||||
if (!form.domain || !form.acmeAccountAccessId) {
|
||||
return;
|
||||
}
|
||||
const access: any = await accessApi.GetObj(form.acmeAccountAccessId);
|
||||
const setting = JSON.parse(access.setting || "{}");
|
||||
const account = parseAccount(setting.account);
|
||||
if (!account?.accountUri) {
|
||||
message.error("ACME账号授权缺少accountUri,请重新生成账号");
|
||||
return;
|
||||
}
|
||||
const record = await api.BuildRecord({
|
||||
domain: form.domain,
|
||||
accountUri: account.accountUri,
|
||||
wildcard: true,
|
||||
persistUntil: form.persistUntil,
|
||||
});
|
||||
form.caType = account.caType;
|
||||
form.accountUri = account.accountUri;
|
||||
form.hostRecord = record.hostRecord;
|
||||
form.recordValue = record.recordValue;
|
||||
form.status = "pending";
|
||||
}
|
||||
|
||||
async function verifyRecord(row: any) {
|
||||
const ok = await api.Verify(row.id);
|
||||
message[ok ? "success" : "error"](ok ? "校验成功" : "未找到匹配的TXT记录,请稍后重试");
|
||||
await crudExpose.doRefresh();
|
||||
return ok;
|
||||
}
|
||||
|
||||
function showRecordHelp(row: any) {
|
||||
openDnsPersistSettingDialog({
|
||||
record: row,
|
||||
async onDone() {
|
||||
await crudExpose.doRefresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
crudOptions: {
|
||||
request: {
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest,
|
||||
},
|
||||
actionbar: {
|
||||
buttons: {
|
||||
add: {
|
||||
icon: "ion:add-circle-outline",
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
minWidth: 120,
|
||||
fixed: "right",
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
title: "ID",
|
||||
key: "id",
|
||||
type: "number",
|
||||
column: { width: 80, order: -999 },
|
||||
form: { show: false },
|
||||
},
|
||||
domain: {
|
||||
title: "域名",
|
||||
type: "text",
|
||||
search: { show: true },
|
||||
form: {
|
||||
required: true,
|
||||
valueChange({ form }) {
|
||||
fillRecord(form);
|
||||
},
|
||||
},
|
||||
},
|
||||
mainDomain: {
|
||||
title: "主域名",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 160,
|
||||
order: 901,
|
||||
},
|
||||
},
|
||||
wildcard: {
|
||||
title: "通配符",
|
||||
type: "dict-switch",
|
||||
form: {
|
||||
show: false,
|
||||
value: true,
|
||||
},
|
||||
column: { show: false },
|
||||
},
|
||||
acmeAccountAccessId: {
|
||||
title: "ACME账号授权",
|
||||
type: "dict-select",
|
||||
dict: accessDict,
|
||||
form: {
|
||||
required: true,
|
||||
order: -9,
|
||||
component: {
|
||||
name: "AccessSelector",
|
||||
vModel: "modelValue",
|
||||
type: "acmeAccount",
|
||||
subtype: compute(({ form }) => {
|
||||
return form.caType;
|
||||
}),
|
||||
},
|
||||
valueChange({ form }) {
|
||||
fillRecord(form);
|
||||
},
|
||||
},
|
||||
column: {
|
||||
width: 180,
|
||||
},
|
||||
},
|
||||
caType: {
|
||||
title: "颁发机构",
|
||||
type: "dict-select",
|
||||
dict: Dicts.sslProviderDict,
|
||||
form: {
|
||||
required: true,
|
||||
value: "letsencrypt",
|
||||
order: -10,
|
||||
valueChange({ form }) {
|
||||
form.acmeAccountAccessId = null;
|
||||
fillRecord(form);
|
||||
},
|
||||
},
|
||||
column: { width: 120 },
|
||||
},
|
||||
persistUntil: {
|
||||
title: "有效期至",
|
||||
type: "datetime",
|
||||
form: {
|
||||
helper: "可选;为空表示长期有效",
|
||||
order: 20,
|
||||
valueChange({ form }) {
|
||||
fillRecord(form);
|
||||
},
|
||||
},
|
||||
column: { width: 180, order: 900 },
|
||||
},
|
||||
hostRecord: {
|
||||
title: "TXT主机名",
|
||||
type: "copyable",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 220,
|
||||
cellRender({ value }) {
|
||||
return (
|
||||
<a-tooltip title={value}>
|
||||
<fs-copyable modelValue={value}></fs-copyable>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
recordValue: {
|
||||
title: "请设置TXT记录",
|
||||
type: "copyable",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
width: 380,
|
||||
cellRender({ value }) {
|
||||
return (
|
||||
<a-tooltip title={value}>
|
||||
<fs-copyable modelValue={value}></fs-copyable>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
dnsProviderType: {
|
||||
title: "DNS服务商",
|
||||
type: "dict-select",
|
||||
dict: dnsProviderTypeDict,
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
name: "DnsProviderSelector",
|
||||
},
|
||||
},
|
||||
column: { show: false },
|
||||
},
|
||||
dnsProviderAccess: {
|
||||
title: "DNS授权",
|
||||
type: "dict-select",
|
||||
dict: accessDict,
|
||||
form: {
|
||||
show: false,
|
||||
component: {
|
||||
name: "AccessSelector",
|
||||
vModel: "modelValue",
|
||||
type: compute(({ form }) => {
|
||||
const type = form.dnsProviderType || "aliyun";
|
||||
return dnsProviderTypeDict?.dataMap[type]?.accessType || type;
|
||||
}),
|
||||
},
|
||||
},
|
||||
column: { show: false },
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
type: "dict-select",
|
||||
dict: statusDict,
|
||||
form: {
|
||||
show: false,
|
||||
value: "pending",
|
||||
},
|
||||
column: {
|
||||
width: 120,
|
||||
cellRender({ value, row }) {
|
||||
async function resetStatus() {
|
||||
Modal.confirm({
|
||||
title: "重新校验",
|
||||
content: "确认将该记录状态重置为待设置,并重新校验吗?",
|
||||
onOk: async () => {
|
||||
await api.UpdateObj({ id: row.id, status: "pending" });
|
||||
await verifyRecord(row);
|
||||
},
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div class={"flex flex-left"}>
|
||||
<fs-values-format modelValue={value} dict={statusDict}></fs-values-format>
|
||||
{row.status === "valid" && (
|
||||
<a-tooltip title="撤销并重新校验">
|
||||
<fs-icon class={"ml-5 pointer color-yellow"} icon="solar:undo-left-square-bold" onClick={resetStatus}></fs-icon>
|
||||
</a-tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
triggerValidate: {
|
||||
title: "校验",
|
||||
type: "text",
|
||||
form: {
|
||||
show: false,
|
||||
},
|
||||
column: {
|
||||
conditionalRenderDisabled: true,
|
||||
width: 210,
|
||||
align: "center",
|
||||
cellRender({ row }) {
|
||||
return (
|
||||
<a-space>
|
||||
{row.status === "valid" ? (
|
||||
<span class="text-gray-500">请勿删除TXT记录</span>
|
||||
) : (
|
||||
<>
|
||||
<a-button type="primary" size="small" onClick={() => showRecordHelp(row)}>
|
||||
设置TXT
|
||||
</a-button>
|
||||
<a-button type="primary" size="small" onClick={() => verifyRecord(row)}>
|
||||
校验
|
||||
</a-button>
|
||||
</>
|
||||
)}
|
||||
</a-space>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
accountUri: {
|
||||
title: "Account URI",
|
||||
type: "text",
|
||||
form: { show: false },
|
||||
column: { show: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<fs-page class="page-cert-dns-persist">
|
||||
<template #header>
|
||||
<div>
|
||||
<div class="title">DNS持久验证记录</div>
|
||||
<div class="text-orange-500 mt-5">当前仅 Let's Encrypt 测试环境可以申请 DNS 持久验证证书。</div>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding"></fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import createCrudOptions from "./crud";
|
||||
|
||||
defineOptions({
|
||||
name: "DnsPersistRecord",
|
||||
});
|
||||
|
||||
const context: any = {
|
||||
permission: { isProjectPermission: true },
|
||||
};
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(async () => {
|
||||
await crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,133 @@
|
||||
import { message } from "ant-design-vue";
|
||||
import { reactive } from "vue";
|
||||
import AccessSelector from "/@/views/certd/access/access-selector/index.vue";
|
||||
import DnsProviderSelector from "/@/components/plugins/cert/dns-provider-selector/index.vue";
|
||||
import { useFormDialog } from "/@/use/use-dialog";
|
||||
import { CreateTxt, TriggerVerify } from "./api";
|
||||
|
||||
export type DnsPersistSettingRecord = {
|
||||
id?: number;
|
||||
mainDomain?: string;
|
||||
hostRecord?: string;
|
||||
recordValue?: string;
|
||||
dnsProviderType?: string;
|
||||
dnsProviderAccess?: number;
|
||||
};
|
||||
|
||||
export function useDnsPersistSettingDialog() {
|
||||
const { openFormDialog } = useFormDialog();
|
||||
|
||||
function copyableRow(label: string, value?: string) {
|
||||
return (
|
||||
<div class="mb-10 flex items-center">
|
||||
<div style={{ width: "90px", flexShrink: 0 }}>{label}</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<fs-copyable class="w-full" model-value={value || ""}></fs-copyable>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function openDnsPersistSettingDialog(req: { record: DnsPersistSettingRecord; onDone?: () => Promise<void> | void }) {
|
||||
const record = req.record;
|
||||
const form = reactive({
|
||||
mode: "manual",
|
||||
dnsProviderType: record.dnsProviderType || "",
|
||||
dnsProviderAccessType: "",
|
||||
dnsProviderAccess: record.dnsProviderAccess || null,
|
||||
});
|
||||
|
||||
async function submit() {
|
||||
if (!record.id) {
|
||||
return;
|
||||
}
|
||||
if (form.mode === "manual") {
|
||||
await TriggerVerify(record.id);
|
||||
message.success("已提交校验");
|
||||
await req.onDone?.();
|
||||
return;
|
||||
}
|
||||
if (!form.dnsProviderType || !form.dnsProviderAccess) {
|
||||
throw new Error("请选择DNS服务商和授权");
|
||||
}
|
||||
await CreateTxt({
|
||||
id: record.id,
|
||||
dnsProviderType: form.dnsProviderType,
|
||||
dnsProviderAccess: form.dnsProviderAccess,
|
||||
});
|
||||
message.success("TXT记录已创建");
|
||||
await req.onDone?.();
|
||||
}
|
||||
|
||||
await openFormDialog({
|
||||
title: "设置DNS TXT记录",
|
||||
wrapper: {
|
||||
width: 680,
|
||||
buttons: {
|
||||
reset: {
|
||||
show: false,
|
||||
},
|
||||
ok: {
|
||||
show: true,
|
||||
text: "确定",
|
||||
},
|
||||
},
|
||||
},
|
||||
body: () => (
|
||||
<div>
|
||||
<a-radio-group value={form.mode} buttonStyle="solid" class="mb-10" onUpdate:value={(value: string) => (form.mode = value)}>
|
||||
<a-radio-button value="manual">手动添加</a-radio-button>
|
||||
<a-radio-button value="auto">选择授权添加</a-radio-button>
|
||||
</a-radio-group>
|
||||
{form.mode === "manual" ? (
|
||||
<div>
|
||||
<a-alert class="mb-10" type="info" show-icon message="请到DNS解析控制台添加以下TXT记录,添加后点击确定会立即校验。" />
|
||||
{copyableRow("主域名", record.mainDomain)}
|
||||
{copyableRow("TXT主机名", record.hostRecord)}
|
||||
{copyableRow("TXT值", record.recordValue)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<a-alert class="mb-10" type="info" show-icon message="请选择DNS服务商和授权,系统会创建TXT记录,后续校验由后台完成。" />
|
||||
{copyableRow("主域名", record.mainDomain)}
|
||||
<div class="mb-10 flex items-center">
|
||||
<div style={{ width: "90px", flexShrink: 0 }}>DNS服务商</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<DnsProviderSelector
|
||||
class="w-full"
|
||||
style={{ width: "100%" }}
|
||||
modelValue={form.dnsProviderType}
|
||||
onUpdate:modelValue={(value: string) => {
|
||||
form.dnsProviderType = value;
|
||||
form.dnsProviderAccess = null;
|
||||
}}
|
||||
onSelectedChange={(option: any) => {
|
||||
form.dnsProviderAccessType = option?.accessType || form.dnsProviderType;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-10 flex items-center">
|
||||
<div style={{ width: "90px", flexShrink: 0 }}>DNS授权</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<AccessSelector
|
||||
modelValue={form.dnsProviderAccess}
|
||||
type={form.dnsProviderAccessType || form.dnsProviderType || "aliyun"}
|
||||
onUpdate:modelValue={(value: number) => {
|
||||
form.dnsProviderAccess = value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
onSubmit: submit,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
openDnsPersistSettingDialog,
|
||||
};
|
||||
}
|
||||
@@ -92,6 +92,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
if (form.challengeType === "cname") {
|
||||
throw new Error(t("certd.domain.cnameManagedInCnamePage"));
|
||||
}
|
||||
if (form.challengeType === "dns-persist") {
|
||||
throw new Error("DNS持久验证记录请在DNS持久验证记录页面管理");
|
||||
}
|
||||
if (form.challengeType === "dns") {
|
||||
const isSubdomain = await api.IsSubdomain({ domain: form.domain });
|
||||
if (isSubdomain && !subdomainConfirmed.value) {
|
||||
@@ -221,6 +224,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
crudExpose.getFormWrapperRef().close();
|
||||
},
|
||||
});
|
||||
} else if (value === "dns-persist") {
|
||||
Modal.confirm({
|
||||
title: "请前往DNS持久验证记录页面添加记录",
|
||||
content: "DNS持久验证需要先配置ACME账号和_validation-persist持久TXT记录,续期时不再增删DNS记录;当前仅 Let's Encrypt 测试环境可以申请。",
|
||||
async onOk() {
|
||||
router.push({
|
||||
path: "/certd/cert/dns-persist",
|
||||
});
|
||||
crudExpose.getFormWrapperRef().close();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
+4
@@ -213,6 +213,10 @@ function useStepForm() {
|
||||
const stepOpen = (step: any, emit: any) => {
|
||||
callback.value = emit;
|
||||
currentStep.value = merge({ input: {}, strategy: {} }, step);
|
||||
// 旧版证书申请任务没有 version 字段,编辑时补成 1,保持旧任务继续走兼容逻辑。
|
||||
if (mode.value === "edit" && currentStep.value.type === "CertApply" && currentStep.value.input?.version == null) {
|
||||
currentStep.value.input.version = 1;
|
||||
}
|
||||
if (step.type) {
|
||||
changeCurrentPlugin(currentStep.value);
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ const hasNewVersion = computed(() => {
|
||||
return isNewVersion(version.value, latestVersion.value);
|
||||
});
|
||||
async function loadLatestVersion() {
|
||||
// version.value = settingsStore.app.version; //前端有缓存 可能不准确
|
||||
latestVersion.value = await api.GetLatestVersion();
|
||||
console.log("latestVersion", latestVersion.value);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="flex-between mt-5">
|
||||
<div class="flex-o"><fs-icon icon="ant-design:check-outlined" class="color-green mr-5" /> 监控站点数:</div>
|
||||
<suite-value :model-value="detail.monitorCount.max" :used="detail.monitorCount.used" unit="次" />
|
||||
<suite-value :model-value="detail.monitorCount.max" :used="detail.monitorCount.used" unit="个" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -265,7 +265,6 @@ onMounted(() => {});
|
||||
.input-right {
|
||||
width: 160px;
|
||||
margin-left: 10px;
|
||||
background: #cfcfcf !important;
|
||||
}
|
||||
|
||||
.forge-password {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<passkey-login></passkey-login>
|
||||
<template v-for="item in oauthProviderList" :key="buildProviderKey(item)">
|
||||
<div v-if="item.addonId" class="oauth-icon-button pointer" @click="goOauthLogin(item)">
|
||||
<div><fs-icon :icon="item.icon" class="text-blue-600 text-40" /></div>
|
||||
<div><fs-icon :icon="item.icon" class="text-40" /></div>
|
||||
<div class="ellipsis title" :title="item.addonTitle || item.title">{{ item.addonTitle || item.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -125,7 +125,6 @@ async function goOauthLogin(item: OauthProviderItem) {
|
||||
}
|
||||
.fs-icon {
|
||||
font-size: 36px;
|
||||
color: #006be6;
|
||||
margin: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<div class="sys-plugin-config settings-form">
|
||||
<a-form :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finish-failed="onFinishFailed">
|
||||
<a-form-item label="公共Google EAB授权" :name="['CertApply', 'sysSetting', 'input', 'googleCommonEabAccessId']">
|
||||
<a-form-item v-show="false" label="公共Google EAB授权" :name="['CertApply', 'sysSetting', 'input', 'googleCommonEabAccessId']">
|
||||
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.googleCommonEabAccessId" type="eab" from="sys"></access-selector>
|
||||
<div class="helper">
|
||||
<div>设置公共Google EAB授权给用户使用,避免用户自己去翻墙获取Google EAB授权</div>
|
||||
@@ -16,7 +16,14 @@
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="公共ZeroSSL EAB授权" :name="['CertApply', 'sysSetting', 'input', 'zerosslCommonEabAccessId']">
|
||||
<a-form-item label="公共Google ACME账号" :name="['CertApply', 'sysSetting', 'input', 'googleCommonAcmeAccountAccessId']">
|
||||
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.googleCommonAcmeAccountAccessId" type="acmeAccount" subtype="google" from="sys"></access-selector>
|
||||
<div class="helper">
|
||||
<div>优先推荐配置公共ACME账号。配置后普通用户申请Google证书时无需选择账号,也不会重复消费公共EAB。</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-show="false" label="公共ZeroSSL EAB授权" :name="['CertApply', 'sysSetting', 'input', 'zerosslCommonEabAccessId']">
|
||||
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.zerosslCommonEabAccessId" type="eab" from="sys"></access-selector>
|
||||
<div class="helper">
|
||||
<div>设置公共ZeroSSL EAB授权给用户使用,避免用户自己去翻墙获取Zero EAB授权</div>
|
||||
@@ -26,7 +33,11 @@
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="公共litessl EAB授权" :name="['CertApply', 'sysSetting', 'input', 'litesslCommonEabAccessId']">
|
||||
<a-form-item label="公共ZeroSSL ACME账号" :name="['CertApply', 'sysSetting', 'input', 'zerosslCommonAcmeAccountAccessId']">
|
||||
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.zerosslCommonAcmeAccountAccessId" type="acmeAccount" subtype="zerossl" from="sys"></access-selector>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item v-show="false" label="公共litessl EAB授权" :name="['CertApply', 'sysSetting', 'input', 'litesslCommonEabAccessId']">
|
||||
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.litesslCommonEabAccessId" type="eab" from="sys"></access-selector>
|
||||
<div class="helper">
|
||||
<div>设置公共litessl EAB授权给用户使用,避免用户自己获取litessl EAB授权</div>
|
||||
@@ -36,6 +47,10 @@
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="公共litessl ACME账号" :name="['CertApply', 'sysSetting', 'input', 'litesslCommonAcmeAccountAccessId']">
|
||||
<access-selector v-model:model-value="formState.CertApply.sysSetting.input.litesslCommonAcmeAccountAccessId" type="acmeAccount" subtype="litessl" from="sys"></access-selector>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="其他配置">
|
||||
<a-button type="primary" @click="doPluginConfig">证书申请插件默认值设置</a-button>
|
||||
<div class="helper">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-tag color="green"> {{ durationDict.dataMap[modelValue]?.label }}</a-tag>
|
||||
<a-tag color="green"> {{ durationDict.dataMap[modelValue]?.label || modelValue + "天" }}</a-tag>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -123,10 +123,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
value: "id",
|
||||
label: "nickName",
|
||||
}),
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
form: {
|
||||
show: true,
|
||||
component: {
|
||||
disabled: true,
|
||||
disabled: false,
|
||||
crossPage: true,
|
||||
multiple: false,
|
||||
select: {
|
||||
@@ -170,11 +175,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
productType: {
|
||||
title: t("certd.type"),
|
||||
type: "dict-select",
|
||||
editForm: {
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.package"), value: "suite", color: "green" },
|
||||
@@ -182,7 +182,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: true,
|
||||
show: false,
|
||||
component: {
|
||||
disabled: true,
|
||||
},
|
||||
@@ -205,6 +205,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
"content.maxDomainCount": {
|
||||
title: t("certd.domain_count"),
|
||||
type: "text",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
key: ["content", "maxDomainCount"],
|
||||
component: {
|
||||
@@ -227,6 +230,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
"content.maxWildcardDomainCount": {
|
||||
title: t("certd.wildcardDomainCountPart"),
|
||||
type: "text",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
key: ["content", "maxWildcardDomainCount"],
|
||||
component: {
|
||||
@@ -249,6 +255,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
"content.maxPipelineCount": {
|
||||
title: t("certd.pipeline_count"),
|
||||
type: "text",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
key: ["content", "maxPipelineCount"],
|
||||
component: {
|
||||
@@ -271,6 +280,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
"content.maxDeployCount": {
|
||||
title: t("certd.deploy_count"),
|
||||
type: "text",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
key: ["content", "maxDeployCount"],
|
||||
component: {
|
||||
@@ -296,6 +308,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
"content.maxMonitorCount": {
|
||||
title: t("certd.monitor_count"),
|
||||
type: "text",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
key: ["content", "maxMonitorCount"],
|
||||
component: {
|
||||
@@ -317,7 +332,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
duration: {
|
||||
title: t("certd.duration"),
|
||||
type: "text",
|
||||
type: "number",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [{ required: true, message: t("certd.field_required") }],
|
||||
},
|
||||
@@ -363,6 +381,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
expiresTime: {
|
||||
title: t("certd.expires_time"),
|
||||
type: "date",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
valueBuilder: ({ value }) => {
|
||||
return dayjs(value);
|
||||
@@ -393,6 +414,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
{ label: t("certd.is_present_no"), value: false, color: "blue" },
|
||||
],
|
||||
}),
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
value: true,
|
||||
},
|
||||
@@ -404,6 +428,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
deployCountUsed: {
|
||||
title: t("certd.deploy_count_used"),
|
||||
type: "number",
|
||||
addForm: {
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
value: 0,
|
||||
rules: [{ required: true, message: t("certd.field_required") }],
|
||||
|
||||
Reference in New Issue
Block a user