Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12a3adc559 | ||
|
|
e50f1a74d6 | ||
|
|
ba6a504588 | ||
|
|
2d37c42584 | ||
|
|
f4b3a8cf81 | ||
|
|
0390ac3eda | ||
|
|
1977201051 | ||
|
|
2efe0de0cf | ||
|
|
34e40e5e54 | ||
|
|
e7e269dfb0 | ||
|
|
fa85580e35 | ||
|
|
500fce6180 | ||
|
|
f501df2804 | ||
|
|
6c1b1fb72b | ||
|
|
505cfc5c1e | ||
|
|
ac14924a73 | ||
|
|
7550aec904 | ||
|
|
139a6980ac | ||
|
|
d5a6411e26 | ||
|
|
1fcd9897be | ||
|
|
99d4bf2624 |
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: 创建一个报告来帮助我们改进
|
||||
title: "[Bug] 标题简要描述问题"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**描述问题**
|
||||
简要描述问题是什么
|
||||
|
||||
**复现步骤**
|
||||
复现该问题的步骤:
|
||||
1. 去到 '...'
|
||||
2. 点击 '...'
|
||||
3. 滚动到 '...'
|
||||
4. 发现问题
|
||||
|
||||
**期望的结果**
|
||||
简要描述你期望发生的事情。
|
||||
|
||||
**截图**
|
||||
如有可能,请添加截图以帮助解释问题。
|
||||
|
||||
**环境**
|
||||
- 操作系统: [e.g. Windows, macOS]
|
||||
- 浏览器: [e.g. Chrome, Safari]
|
||||
- 仓库版本: [e.g. v1.0.0]
|
||||
|
||||
**其他信息**
|
||||
在此处添加关于该问题的任何其他信息。
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: 提出一个新功能请求
|
||||
title: "[Feature] 简要描述你希望实现的功能"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**功能描述**
|
||||
简要描述你希望添加的功能和相关问题。
|
||||
|
||||
**动机**
|
||||
为什么这个功能对项目有帮助?
|
||||
|
||||
**替代方案**
|
||||
描述你已经考虑过的替代方案。
|
||||
|
||||
**其他信息**
|
||||
在这里添加任何相关的附加信息或截图。
|
||||
24
README.md
24
README.md
@@ -4,15 +4,20 @@
|
||||
|
||||
做个人产品或在小企业负责运维的同学,需要管理多个域名,要给域名申请证书。但手动申请证书有以下缺点:
|
||||
|
||||
1. 😱麻烦:申请、部署证书虽不复杂,但也挺麻烦的,尤其是你维护多个域名的时候。
|
||||
1. 😱麻烦:申请、部署证书虽不困难,但也挺麻烦的,尤其是维护多个域名的时候。
|
||||
2. 😭易忘:当前免费证书有效期仅90天,这就要求定期操作,增加工作量的同时,也很容易忘掉,导致网站无法访问。
|
||||
|
||||
Certimate 就是为了解决上述问题而产生的,它具有以下特点:
|
||||
|
||||
1. 操作简单:自动申请、部署 SSL 证书,并在证书即将过期时自动续期,无需人工干预。
|
||||
1. 操作简单:自动申请、部署、续期 SSL 证书,全程无需人工干预。
|
||||
2. 支持私有部署:部署方法简单,只需下载二进制文件执行即可。二进制文件、docker 镜像全部用 github actions 生成,过程透明,可自行审计。
|
||||
3. 数据安全:由于是私有部署,所有数据均存储在本地,不会保存在服务商的服务器,确保数据的安全性。
|
||||
|
||||
相关文章:
|
||||
|
||||
* [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
|
||||
* [域名变量及部署授权组介绍](https://docs.certimate.me/blog/multi-deployer)
|
||||
|
||||
|
||||
Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决方案。使用文档请访问[https://docs.certimate.me](https://docs.certimate.me)
|
||||
|
||||
@@ -29,7 +34,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
|
||||
- [2. dns 服务商授权信息](#2-dns-服务商授权信息)
|
||||
- [3. 部署服务商授权信息](#3-部署服务商授权信息)
|
||||
- [六、常见问题](#六常见问题)
|
||||
- [七、许可证](#七许可证)
|
||||
- [七、贡献](#七贡献)
|
||||
|
||||
|
||||
|
||||
@@ -162,6 +167,15 @@ Q: 自动续期证书?
|
||||
|
||||
|
||||
|
||||
## 七、许可证
|
||||
## 七、贡献
|
||||
|
||||
Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.md)。你可以使用它做任何你想做的事,甚至把它当作一个付费服务提供给用户。
|
||||
|
||||
你可以通过以下方式来支持 Certimate 的开发:
|
||||
|
||||
* 提交代码:如果你发现了 bug 或有新的功能需求,而你又有相关经验,可以提交代码给我们。
|
||||
* 提交 issue:功能建议或者 bug 可以[提交 issue](https://github.com/usual2970/certimate/issues) 给我们。
|
||||
|
||||
支持更多服务商、UI 的优化改进、BUG 修复、文档完善等,欢迎大家提交 PR。
|
||||
|
||||
|
||||
Certimate 采用 MIT 许可证,详情请查看 [LICENSE](LICENSE.md) 文件。
|
||||
|
||||
22
go.mod
22
go.mod
@@ -7,9 +7,9 @@ toolchain go1.22.5
|
||||
require (
|
||||
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8
|
||||
github.com/alibabacloud-go/tea v1.2.1
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9
|
||||
github.com/alibabacloud-go/tea v1.2.2
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
|
||||
github.com/go-acme/lego/v4 v4.17.4
|
||||
github.com/gojek/heimdall/v7 v7.0.3
|
||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
|
||||
@@ -23,16 +23,24 @@ require (
|
||||
golang.org/x/crypto v0.26.0
|
||||
)
|
||||
|
||||
require github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
require (
|
||||
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
|
||||
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
|
||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
|
||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi v0.1.18 // indirect
|
||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2
|
||||
github.com/alibabacloud-go/debug v1.0.0 // indirect
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
|
||||
github.com/alibabacloud-go/tea-utils v1.4.3 // indirect
|
||||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 // indirect
|
||||
github.com/aliyun/credentials-go v1.3.1 // indirect
|
||||
@@ -57,7 +65,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.5.5 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.5.6 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.97.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
|
||||
43
go.sum
43
go.sum
@@ -35,26 +35,61 @@ github.com/alibabacloud-go/cas-20200407/v2 v2.3.0 h1:nOrp0n2nFZiYN0wIG7S26YVVaMM
|
||||
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0/go.mod h1:yzkgdLANANu/v56k0ptslGl++JJL4Op1V09HTavfoCo=
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0 h1:yTKngw4rBR3hdpoo/uCyBffYXfPfjNjlaDL8nTYUIds=
|
||||
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0/go.mod h1:HxQrwVKBx3/6bIwmdDcpqBpSQt2tpi/j4LfEhl+QFPk=
|
||||
github.com/alibabacloud-go/darabonba-openapi v0.1.18 h1:3eUVmAr7WCJp7fgIvmCd9ZUyuwtJYbtUqJIed5eXCmk=
|
||||
github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.6/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8 h1:benoD0QHDrylMzEQVpX/6uKtrN8LohT66ZlKXVJh7pM=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9 h1:fxMCrZatZfXq5nLcgkmWBXmU3FLC1OR+m/SqVtMqflk=
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
|
||||
github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
|
||||
github.com/alibabacloud-go/dcdn-20180115 v1.0.20 h1:Vp6K2GxtSL1DzZ2dyumbEPuujzxGFN0Hau+mwcqBrVo=
|
||||
github.com/alibabacloud-go/dcdn-20180115 v1.0.20/go.mod h1:FYEDKSB19NzejWQWGzq5QMi+w01xtDnAMd/I+Qz0nKw=
|
||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2 h1:WKMtPfhEmf8jX4FvdG7MFBJeCknPQ+FEHQppDcaCoU0=
|
||||
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2/go.mod h1:dGuR8qQqofJKl99rVaWvObnP3bMkru3cdOtqJJ95048=
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
|
||||
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||
github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA=
|
||||
github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 h1:L0TIjr9Qh/SLVc1yPhFkcB9+9SbCNK/jPq4ZKB5zmnc=
|
||||
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1/go.mod h1:EKxBRDLcMzwl4VLF/1WJwlByZZECJawPXUvinKMsTTs=
|
||||
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.10/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.12/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||
github.com/alibabacloud-go/tea v1.2.1 h1:rFF1LnrAdhaiPmKwH5xwYOKlMh66CqRwPUTzIK74ask=
|
||||
github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
|
||||
github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
|
||||
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||
github.com/alibabacloud-go/tea-fileform v1.1.1 h1:1YG6erAP3joQ0XdCXYIotuD7zyOM6qCR49xkp5FZDeU=
|
||||
github.com/alibabacloud-go/tea-fileform v1.1.1/go.mod h1:ZeCV91o4ISmxidd686f0ebdS5EDHWU+vW+TkjLhrsFE=
|
||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 h1:EhAHI6edMeqgkZEqP7r4nc9iMWAUBKGxJHoBsOSKTtU=
|
||||
github.com/alibabacloud-go/tea-oss-sdk v1.1.3/go.mod h1:yUnodpR3Bf2rudLE7V/Gft5txjJF30Pk+hH77K/Eab0=
|
||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS3IaDWE7M9nVLRc=
|
||||
github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY=
|
||||
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||
github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||
github.com/alibabacloud-go/tea-utils v1.4.3 h1:8SzwmmRrOnQ09Hf5a9GyfJc0d7Sjv6fmsZoF4UDbFjo=
|
||||
github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
|
||||
github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
|
||||
github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5 h1:EUakYEUAwr6L3wLT0vejIw2rc0IA1RSXDwLnIb3f2vU=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 h1:lM7JnA9dEdDFH9XOgRNQMDTQnOjlLkDTNA7c0aWTQ30=
|
||||
@@ -111,6 +146,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/clbanning/mxj/v2 v2.5.6 h1:Jm4VaCI/+Ug5Q57IzEoZbwx4iQFA6wkXv72juUSeK+g=
|
||||
github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.97.0 h1:feZRGiRF1EbljnNIYdt8014FnOLtC3CCvgkLXu915ks=
|
||||
github.com/cloudflare/cloudflare-go v0.97.0/go.mod h1:JXRwuTfHpe5xFg8xytc2w0XC6LcrFsBVMS4WlVaiGg8=
|
||||
@@ -395,6 +432,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -438,6 +476,7 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -473,6 +512,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -482,6 +522,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -494,8 +535,10 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
@@ -20,8 +22,11 @@ const (
|
||||
configTypeAliyun = "aliyun"
|
||||
configTypeCloudflare = "cloudflare"
|
||||
configTypeNamesilo = "namesilo"
|
||||
configTypeGodaddy = "godaddy"
|
||||
)
|
||||
|
||||
const defaultEmail = "536464346@qq.com"
|
||||
|
||||
type Certificate struct {
|
||||
CertUrl string `json:"certUrl"`
|
||||
CertStableUrl string `json:"certStableUrl"`
|
||||
@@ -32,9 +37,10 @@ type Certificate struct {
|
||||
}
|
||||
|
||||
type ApplyOption struct {
|
||||
Email string `json:"email"`
|
||||
Domain string `json:"domain"`
|
||||
Access string `json:"access"`
|
||||
Email string `json:"email"`
|
||||
Domain string `json:"domain"`
|
||||
Access string `json:"access"`
|
||||
Nameservers string `json:"nameservers"`
|
||||
}
|
||||
|
||||
type MyUser struct {
|
||||
@@ -59,10 +65,15 @@ type Applicant interface {
|
||||
|
||||
func Get(record *models.Record) (Applicant, error) {
|
||||
access := record.ExpandedOne("access")
|
||||
email := record.GetString("email")
|
||||
if email == "" {
|
||||
email = defaultEmail
|
||||
}
|
||||
option := &ApplyOption{
|
||||
Email: "536464346@qq.com",
|
||||
Domain: record.GetString("domain"),
|
||||
Access: access.GetString("config"),
|
||||
Email: email,
|
||||
Domain: record.GetString("domain"),
|
||||
Access: access.GetString("config"),
|
||||
Nameservers: record.GetString("nameservers"),
|
||||
}
|
||||
switch access.GetString("configType") {
|
||||
case configTypeTencent:
|
||||
@@ -73,6 +84,8 @@ func Get(record *models.Record) (Applicant, error) {
|
||||
return NewCloudflare(option), nil
|
||||
case configTypeNamesilo:
|
||||
return NewNamesilo(option), nil
|
||||
case configTypeGodaddy:
|
||||
return NewGodaddy(option), nil
|
||||
default:
|
||||
return nil, errors.New("unknown config type")
|
||||
}
|
||||
@@ -102,7 +115,13 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.Challenge.SetDNS01Provider(provider)
|
||||
challengeOptions := make([]dns01.ChallengeOption, 0)
|
||||
nameservers := ParseNameservers(option.Nameservers)
|
||||
if len(nameservers) > 0 {
|
||||
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(nameservers))
|
||||
}
|
||||
|
||||
client.Challenge.SetDNS01Provider(provider, challengeOptions...)
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
@@ -111,8 +130,16 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
}
|
||||
myUser.Registration = reg
|
||||
|
||||
domains := []string{option.Domain}
|
||||
|
||||
// 如果是通配置符域名,把根域名也加入
|
||||
if strings.HasPrefix(option.Domain, "*.") && len(strings.Split(option.Domain, ".")) == 3 {
|
||||
rootDomain := strings.TrimPrefix(option.Domain, "*.")
|
||||
domains = append(domains, rootDomain)
|
||||
}
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{option.Domain},
|
||||
Domains: domains,
|
||||
Bundle: true,
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
@@ -129,3 +156,22 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
||||
Csr: string(certificates.CSR),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseNameservers(ns string) []string {
|
||||
nameservers := make([]string, 0)
|
||||
|
||||
lines := strings.Split(ns, ";")
|
||||
|
||||
for _, line := range lines {
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
nameservers = append(nameservers, line)
|
||||
}
|
||||
|
||||
return nameservers
|
||||
}
|
||||
|
||||
35
internal/applicant/godaddy.go
Normal file
35
internal/applicant/godaddy.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package applicant
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
)
|
||||
|
||||
type godaddy struct {
|
||||
option *ApplyOption
|
||||
}
|
||||
|
||||
func NewGodaddy(option *ApplyOption) Applicant {
|
||||
return &godaddy{
|
||||
option: option,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *godaddy) Apply() (*Certificate, error) {
|
||||
|
||||
access := &domain.GodaddyAccess{}
|
||||
json.Unmarshal([]byte(a.option.Access), access)
|
||||
|
||||
os.Setenv("GODADDY_API_KEY", access.ApiKey)
|
||||
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
|
||||
|
||||
dnsProvider, err := godaddyProvider.NewDNSProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apply(a.option, dnsProvider)
|
||||
}
|
||||
@@ -38,6 +38,10 @@ func NewAliyun(option *DeployerOption) (Deployer, error) {
|
||||
|
||||
}
|
||||
|
||||
func (a *aliyun) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (a *aliyun) GetInfo() []string {
|
||||
return a.infos
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ func NewAliyunCdn(option *DeployerOption) (*AliyunCdn, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AliyunCdn) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (a *AliyunCdn) GetInfo() []string {
|
||||
return a.infos
|
||||
}
|
||||
|
||||
86
internal/deployer/aliyun_esa.go
Normal file
86
internal/deployer/aliyun_esa.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* @Author: Bin
|
||||
* @Date: 2024-09-17
|
||||
* @FilePath: /certimate/internal/deployer/aliyun_esa.go
|
||||
*/
|
||||
package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
type AliyunEsa struct {
|
||||
client *dcdn20180115.Client
|
||||
option *DeployerOption
|
||||
infos []string
|
||||
}
|
||||
|
||||
func NewAliyunEsa(option *DeployerOption) (*AliyunEsa, error) {
|
||||
access := &domain.AliyunAccess{}
|
||||
json.Unmarshal([]byte(option.Access), access)
|
||||
a := &AliyunEsa{
|
||||
option: option,
|
||||
}
|
||||
client, err := a.createClient(access.AccessKeyId, access.AccessKeySecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AliyunEsa{
|
||||
client: client,
|
||||
option: option,
|
||||
infos: make([]string, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AliyunEsa) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (a *AliyunEsa) GetInfo() []string {
|
||||
return a.infos
|
||||
}
|
||||
|
||||
func (a *AliyunEsa) Deploy(ctx context.Context) error {
|
||||
|
||||
certName := fmt.Sprintf("%s-%s", a.option.Domain, a.option.DomainId)
|
||||
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(a.option.Domain),
|
||||
CertName: tea.String(certName),
|
||||
CertType: tea.String("upload"),
|
||||
SSLProtocol: tea.String("on"),
|
||||
SSLPub: tea.String(a.option.Certificate.Certificate),
|
||||
SSLPri: tea.String(a.option.Certificate.PrivateKey),
|
||||
CertRegion: tea.String("cn-hangzhou"),
|
||||
}
|
||||
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
resp, err := a.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.infos = append(a.infos, toStr("dcdn设置证书", resp))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AliyunEsa) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKeyId),
|
||||
AccessKeySecret: tea.String(accessKeySecret),
|
||||
}
|
||||
config.Endpoint = tea.String("dcdn.aliyuncs.com")
|
||||
_result = &dcdn20180115.Client{}
|
||||
_result, _err = dcdn20180115.NewClient(config)
|
||||
return _result, _err
|
||||
}
|
||||
@@ -2,9 +2,12 @@ package deployer
|
||||
|
||||
import (
|
||||
"certimate/internal/applicant"
|
||||
"certimate/internal/utils/app"
|
||||
"certimate/internal/utils/variables"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
@@ -13,6 +16,7 @@ import (
|
||||
const (
|
||||
targetAliyunOss = "aliyun-oss"
|
||||
targetAliyunCdn = "aliyun-cdn"
|
||||
targetAliyunEsa = "aliyun-dcdn"
|
||||
targetSSH = "ssh"
|
||||
targetWebhook = "webhook"
|
||||
targetTencentCdn = "tencent-cdn"
|
||||
@@ -20,25 +24,85 @@ const (
|
||||
)
|
||||
|
||||
type DeployerOption struct {
|
||||
DomainId string `json:"domainId"`
|
||||
Domain string `json:"domain"`
|
||||
Product string `json:"product"`
|
||||
Access string `json:"access"`
|
||||
Certificate applicant.Certificate `json:"certificate"`
|
||||
DomainId string `json:"domainId"`
|
||||
Domain string `json:"domain"`
|
||||
Product string `json:"product"`
|
||||
Access string `json:"access"`
|
||||
AceessRecord *models.Record `json:"-"`
|
||||
Certificate applicant.Certificate `json:"certificate"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
|
||||
type Deployer interface {
|
||||
Deploy(ctx context.Context) error
|
||||
GetInfo() []string
|
||||
GetID() string
|
||||
}
|
||||
|
||||
func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
||||
access := record.ExpandedOne("targetAccess")
|
||||
func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
|
||||
rs := make([]Deployer, 0)
|
||||
|
||||
if record.GetString("targetAccess") != "" {
|
||||
singleDeployer, err := Get(record, cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs = append(rs, singleDeployer)
|
||||
}
|
||||
|
||||
if record.GetString("group") != "" {
|
||||
group := record.ExpandedOne("group")
|
||||
|
||||
if errs := app.GetApp().Dao().ExpandRecord(group, []string{"access"}, nil); len(errs) > 0 {
|
||||
|
||||
errList := make([]error, 0)
|
||||
for name, err := range errs {
|
||||
errList = append(errList, fmt.Errorf("展开记录失败,%s: %w", name, err))
|
||||
}
|
||||
err := errors.Join(errList...)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := group.ExpandedAll("access")
|
||||
|
||||
deployers, err := getByGroup(record, cert, records...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs = append(rs, deployers...)
|
||||
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
|
||||
}
|
||||
|
||||
func getByGroup(record *models.Record, cert *applicant.Certificate, accesses ...*models.Record) ([]Deployer, error) {
|
||||
|
||||
rs := make([]Deployer, 0)
|
||||
|
||||
for _, access := range accesses {
|
||||
deployer, err := getWithAccess(record, cert, access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs = append(rs, deployer)
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
|
||||
}
|
||||
|
||||
func getWithAccess(record *models.Record, cert *applicant.Certificate, access *models.Record) (Deployer, error) {
|
||||
|
||||
option := &DeployerOption{
|
||||
DomainId: record.Id,
|
||||
Domain: record.GetString("domain"),
|
||||
Product: getProduct(record),
|
||||
Access: access.GetString("config"),
|
||||
DomainId: record.Id,
|
||||
Domain: record.GetString("domain"),
|
||||
Product: getProduct(record),
|
||||
Access: access.GetString("config"),
|
||||
AceessRecord: access,
|
||||
Variables: variables.Parse2Map(record.GetString("variables")),
|
||||
}
|
||||
if cert != nil {
|
||||
option.Certificate = *cert
|
||||
@@ -54,6 +118,8 @@ func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
||||
return NewAliyun(option)
|
||||
case targetAliyunCdn:
|
||||
return NewAliyunCdn(option)
|
||||
case targetAliyunEsa:
|
||||
return NewAliyunEsa(option)
|
||||
case targetSSH:
|
||||
return NewSSH(option)
|
||||
case targetWebhook:
|
||||
@@ -66,6 +132,13 @@ func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
|
||||
|
||||
access := record.ExpandedOne("targetAccess")
|
||||
|
||||
return getWithAccess(record, cert, access)
|
||||
}
|
||||
|
||||
func getProduct(record *models.Record) string {
|
||||
targetType := record.GetString("targetType")
|
||||
rs := strings.Split(targetType, "-")
|
||||
|
||||
@@ -33,6 +33,10 @@ func NewQiNiu(option *DeployerOption) (*qiuniu, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *qiuniu) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (q *qiuniu) GetInfo() []string {
|
||||
return q.info
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
xpath "path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
sshPkg "golang.org/x/crypto/ssh"
|
||||
@@ -35,6 +36,10 @@ func NewSSH(option *DeployerOption) (Deployer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *ssh) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (s *ssh) GetInfo() []string {
|
||||
return s.infos
|
||||
}
|
||||
@@ -44,6 +49,15 @@ func (s *ssh) Deploy(ctx context.Context) error {
|
||||
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 将证书路径和命令中的变量替换为实际值
|
||||
for k, v := range s.option.Variables {
|
||||
key := fmt.Sprintf("${%s}", k)
|
||||
access.CertPath = strings.ReplaceAll(access.CertPath, key, v)
|
||||
access.KeyPath = strings.ReplaceAll(access.KeyPath, key, v)
|
||||
access.Command = strings.ReplaceAll(access.Command, key, v)
|
||||
}
|
||||
|
||||
// 连接
|
||||
client, err := s.getClient(access)
|
||||
if err != nil {
|
||||
|
||||
@@ -39,6 +39,10 @@ func NewTencentCdn(option *DeployerOption) (Deployer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *tencentCdn) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (t *tencentCdn) GetInfo() []string {
|
||||
return t.infos
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ func NewWebhook(option *DeployerOption) (Deployer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *webhook) GetID() string {
|
||||
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
|
||||
}
|
||||
|
||||
func (w *webhook) GetInfo() []string {
|
||||
return w.infos
|
||||
}
|
||||
|
||||
@@ -22,3 +22,8 @@ type QiniuAccess struct {
|
||||
type NameSiloAccess struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
type GodaddyAccess struct {
|
||||
ApiKey string `json:"apiKey"`
|
||||
ApiSecret string `json:"apiSecret"`
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func deploy(ctx context.Context, record *models.Record) error {
|
||||
return err
|
||||
}
|
||||
history.record(checkPhase, "获取记录成功", nil)
|
||||
if errs := app.GetApp().Dao().ExpandRecord(currRecord, []string{"access", "targetAccess"}, nil); len(errs) > 0 {
|
||||
if errs := app.GetApp().Dao().ExpandRecord(currRecord, []string{"access", "targetAccess", "group"}, nil); len(errs) > 0 {
|
||||
|
||||
errList := make([]error, 0)
|
||||
for name, err := range errs {
|
||||
@@ -96,24 +96,28 @@ func deploy(ctx context.Context, record *models.Record) error {
|
||||
|
||||
// ############3.部署证书
|
||||
history.record(deployPhase, "开始部署", nil, false)
|
||||
deployer, err := deployer.Get(currRecord, certificate)
|
||||
deployers, err := deployer.Gets(currRecord, certificate)
|
||||
if err != nil {
|
||||
history.record(deployPhase, "获取deployer失败", &RecordInfo{Err: err})
|
||||
app.GetApp().Logger().Error("获取deployer失败", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = deployer.Deploy(ctx); err != nil {
|
||||
for _, deployer := range deployers {
|
||||
if err = deployer.Deploy(ctx); err != nil {
|
||||
|
||||
app.GetApp().Logger().Error("部署失败", "err", err)
|
||||
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
|
||||
return err
|
||||
}
|
||||
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
|
||||
Info: deployer.GetInfo(),
|
||||
}, false)
|
||||
|
||||
app.GetApp().Logger().Error("部署失败", "err", err)
|
||||
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
|
||||
return err
|
||||
}
|
||||
|
||||
app.GetApp().Logger().Info("部署成功")
|
||||
history.record(deployPhase, "部署成功", &RecordInfo{
|
||||
Info: deployer.GetInfo(),
|
||||
}, true)
|
||||
history.record(deployPhase, "部署成功", nil, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
30
internal/utils/variables/variables.go
Normal file
30
internal/utils/variables/variables.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package variables
|
||||
|
||||
import "strings"
|
||||
|
||||
// Parse2Map 将变量赋值字符串解析为map
|
||||
func Parse2Map(str string) map[string]string {
|
||||
|
||||
m := make(map[string]string)
|
||||
|
||||
lines := strings.Split(str, ";")
|
||||
|
||||
for _, line := range lines {
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
kv := strings.Split(line, "=")
|
||||
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
m[kv[0]] = kv[1]
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
56
internal/utils/variables/variables_test.go
Normal file
56
internal/utils/variables/variables_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package variables
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse2Map(t *testing.T) {
|
||||
type args struct {
|
||||
str string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]string
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
args: args{
|
||||
str: "a=1;b=2;c=3",
|
||||
},
|
||||
want: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
args: args{
|
||||
str: `a=1;
|
||||
b=2;
|
||||
c=`,
|
||||
},
|
||||
want: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
args: args{
|
||||
str: "1",
|
||||
},
|
||||
want: map[string]string{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Parse2Map(tt.args.str); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Parse2Map() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,73 +12,10 @@ import (
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-07-29 09:44:56.398Z",
|
||||
"updated": "2024-09-07 07:42:28.389Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "z3p974ainxjqlvs",
|
||||
"created": "2024-07-29 10:02:48.334Z",
|
||||
"updated": "2024-09-07 07:42:28.389Z",
|
||||
"updated": "2024-09-12 13:09:54.500Z",
|
||||
"name": "domains",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
@@ -333,7 +270,7 @@ func init() {
|
||||
{
|
||||
"id": "4yzbv8urny5ja1e",
|
||||
"created": "2024-07-29 10:04:39.685Z",
|
||||
"updated": "2024-09-10 12:51:41.405Z",
|
||||
"updated": "2024-09-12 13:18:00.093Z",
|
||||
"name": "access",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
@@ -381,7 +318,8 @@ func init() {
|
||||
"webhook",
|
||||
"cloudflare",
|
||||
"qiniu",
|
||||
"namesilo"
|
||||
"namesilo",
|
||||
"godaddy"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -397,6 +335,23 @@ func init() {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hsxcnlvd",
|
||||
"name": "usage",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"apply",
|
||||
"deploy",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
@@ -412,7 +367,7 @@ func init() {
|
||||
{
|
||||
"id": "0a1o4e6sstp694f",
|
||||
"created": "2024-07-30 06:30:27.801Z",
|
||||
"updated": "2024-09-07 07:42:28.390Z",
|
||||
"updated": "2024-09-12 13:09:54.500Z",
|
||||
"name": "deployments",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
@@ -493,6 +448,69 @@ func init() {
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-09-12 13:09:54.234Z",
|
||||
"updated": "2024-09-12 13:09:54.500Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
}
|
||||
]`
|
||||
|
||||
20
migrations/1726147268_update_access_usage.go
Normal file
20
migrations/1726147268_update_access_usage.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/dbx"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
// add up queries...
|
||||
db.NewQuery("update access set usage='all' where configType in ('aliyun', 'tencent')").Execute()
|
||||
db.NewQuery("update access set usage='deploy' where configType in ('ssh', 'webhook','qiniu')").Execute()
|
||||
db.NewQuery("update access set usage='apply' where configType in ('cloudflare','namesilo','godaddy')").Execute()
|
||||
return nil
|
||||
}, func(db dbx.Builder) error {
|
||||
// add down queries...
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -12,73 +12,10 @@ import (
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-07-29 09:44:56.398Z",
|
||||
"updated": "2024-09-02 14:02:40.191Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "z3p974ainxjqlvs",
|
||||
"created": "2024-07-29 10:02:48.334Z",
|
||||
"updated": "2024-09-04 15:10:19.440Z",
|
||||
"updated": "2024-09-12 23:13:12.119Z",
|
||||
"name": "domains",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
@@ -97,6 +34,19 @@ func init() {
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ukkhuw85",
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "v98eebqq",
|
||||
@@ -333,7 +283,7 @@ func init() {
|
||||
{
|
||||
"id": "4yzbv8urny5ja1e",
|
||||
"created": "2024-07-29 10:04:39.685Z",
|
||||
"updated": "2024-09-04 15:09:46.789Z",
|
||||
"updated": "2024-09-12 23:08:52.810Z",
|
||||
"name": "access",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
@@ -380,7 +330,9 @@ func init() {
|
||||
"ssh",
|
||||
"webhook",
|
||||
"cloudflare",
|
||||
"qiniu"
|
||||
"qiniu",
|
||||
"namesilo",
|
||||
"godaddy"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -396,6 +348,23 @@ func init() {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hsxcnlvd",
|
||||
"name": "usage",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"apply",
|
||||
"deploy",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
@@ -411,7 +380,7 @@ func init() {
|
||||
{
|
||||
"id": "0a1o4e6sstp694f",
|
||||
"created": "2024-07-30 06:30:27.801Z",
|
||||
"updated": "2024-09-02 14:02:40.191Z",
|
||||
"updated": "2024-09-12 23:08:52.810Z",
|
||||
"name": "deployments",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
@@ -492,6 +461,114 @@ func init() {
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-09-12 13:09:54.234Z",
|
||||
"updated": "2024-09-12 23:08:52.811Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dy6ccjb60spfy6p",
|
||||
"created": "2024-09-12 23:12:21.677Z",
|
||||
"updated": "2024-09-12 23:19:09.110Z",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "1tcmdsdf",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "f9wyhypi",
|
||||
"name": "content",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]`
|
||||
|
||||
679
migrations/1726299230_collections_snapshot.go
Normal file
679
migrations/1726299230_collections_snapshot.go
Normal file
@@ -0,0 +1,679 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "z3p974ainxjqlvs",
|
||||
"created": "2024-07-29 10:02:48.334Z",
|
||||
"updated": "2024-09-14 02:53:22.520Z",
|
||||
"name": "domains",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "iuaerpl2",
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ukkhuw85",
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "v98eebqq",
|
||||
"name": "crontab",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "alc8e9ow",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "topsc9bj",
|
||||
"name": "certUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "vixgq072",
|
||||
"name": "certStableUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g3a3sza5",
|
||||
"name": "privateKey",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "gr6iouny",
|
||||
"name": "certificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "tk6vnrmn",
|
||||
"name": "issuerCertificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "sjo6ibse",
|
||||
"name": "csr",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "x03n1bkj",
|
||||
"name": "expiredAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "srybpixz",
|
||||
"name": "targetType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun-oss",
|
||||
"aliyun-cdn",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"tencent-cdn",
|
||||
"qiniu-cdn"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xy7yk0mb",
|
||||
"name": "targetAccess",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "6jqeyggw",
|
||||
"name": "enabled",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hdsjcchf",
|
||||
"name": "deployed",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "aiya3rev",
|
||||
"name": "rightnow",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ixznmhzc",
|
||||
"name": "lastDeployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ghtlkn5j",
|
||||
"name": "lastDeployment",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "0a1o4e6sstp694f",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "zfnyj9he",
|
||||
"name": "variables",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "1bspzuku",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "4yzbv8urny5ja1e",
|
||||
"created": "2024-07-29 10:04:39.685Z",
|
||||
"updated": "2024-09-13 23:47:27.173Z",
|
||||
"name": "access",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "geeur58v",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "iql7jpwx",
|
||||
"name": "config",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hwy7m03o",
|
||||
"name": "configType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun",
|
||||
"tencent",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"cloudflare",
|
||||
"qiniu",
|
||||
"namesilo",
|
||||
"godaddy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lr33hiwg",
|
||||
"name": "deleted",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hsxcnlvd",
|
||||
"name": "usage",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"apply",
|
||||
"deploy",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "c8egzzwj",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "0a1o4e6sstp694f",
|
||||
"created": "2024-07-30 06:30:27.801Z",
|
||||
"updated": "2024-09-13 12:52:50.804Z",
|
||||
"name": "deployments",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "farvlzk7",
|
||||
"name": "domain",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "z3p974ainxjqlvs",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "jx5f69i3",
|
||||
"name": "log",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "qbxdtg9q",
|
||||
"name": "phase",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"check",
|
||||
"apply",
|
||||
"deploy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "rglrp1hz",
|
||||
"name": "phaseSuccess",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lt1g1blu",
|
||||
"name": "deployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-09-12 13:09:54.234Z",
|
||||
"updated": "2024-09-12 23:34:40.687Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dy6ccjb60spfy6p",
|
||||
"created": "2024-09-12 23:12:21.677Z",
|
||||
"updated": "2024-09-12 23:34:40.687Z",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "1tcmdsdf",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "f9wyhypi",
|
||||
"name": "content",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "teolp9pl72dxlxq",
|
||||
"created": "2024-09-13 12:51:05.611Z",
|
||||
"updated": "2024-09-14 00:01:58.239Z",
|
||||
"name": "access_groups",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "7sajiv6i",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xp8admif",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": null,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]`
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daos.New(db).ImportCollections(collections, true, nil)
|
||||
}, func(db dbx.Builder) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
85
migrations/1726569833_updated_domains.go
Normal file
85
migrations/1726569833_updated_domains.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
dao := daos.New(db);
|
||||
|
||||
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update
|
||||
edit_targetType := &schema.SchemaField{}
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"system": false,
|
||||
"id": "srybpixz",
|
||||
"name": "targetType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun-oss",
|
||||
"aliyun-cdn",
|
||||
"aliyun-dcdn",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"tencent-cdn",
|
||||
"qiniu-cdn"
|
||||
]
|
||||
}
|
||||
}`), edit_targetType); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(edit_targetType)
|
||||
|
||||
return dao.SaveCollection(collection)
|
||||
}, func(db dbx.Builder) error {
|
||||
dao := daos.New(db);
|
||||
|
||||
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update
|
||||
edit_targetType := &schema.SchemaField{}
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"system": false,
|
||||
"id": "srybpixz",
|
||||
"name": "targetType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun-oss",
|
||||
"aliyun-cdn",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"tencent-cdn",
|
||||
"qiniu-cdn"
|
||||
]
|
||||
}
|
||||
}`), edit_targetType); err != nil {
|
||||
return err
|
||||
}
|
||||
collection.Schema.AddField(edit_targetType)
|
||||
|
||||
return dao.SaveCollection(collection)
|
||||
})
|
||||
}
|
||||
694
migrations/1726670437_collections_snapshot.go
Normal file
694
migrations/1726670437_collections_snapshot.go
Normal file
@@ -0,0 +1,694 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := `[
|
||||
{
|
||||
"id": "z3p974ainxjqlvs",
|
||||
"created": "2024-07-29 10:02:48.334Z",
|
||||
"updated": "2024-09-18 14:23:22.359Z",
|
||||
"name": "domains",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "iuaerpl2",
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ukkhuw85",
|
||||
"name": "email",
|
||||
"type": "email",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "v98eebqq",
|
||||
"name": "crontab",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "alc8e9ow",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "topsc9bj",
|
||||
"name": "certUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "vixgq072",
|
||||
"name": "certStableUrl",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g3a3sza5",
|
||||
"name": "privateKey",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "gr6iouny",
|
||||
"name": "certificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "tk6vnrmn",
|
||||
"name": "issuerCertificate",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "sjo6ibse",
|
||||
"name": "csr",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "x03n1bkj",
|
||||
"name": "expiredAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "srybpixz",
|
||||
"name": "targetType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun-oss",
|
||||
"aliyun-cdn",
|
||||
"aliyun-dcdn",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"tencent-cdn",
|
||||
"qiniu-cdn"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xy7yk0mb",
|
||||
"name": "targetAccess",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "6jqeyggw",
|
||||
"name": "enabled",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hdsjcchf",
|
||||
"name": "deployed",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "aiya3rev",
|
||||
"name": "rightnow",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ixznmhzc",
|
||||
"name": "lastDeployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "ghtlkn5j",
|
||||
"name": "lastDeployment",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "0a1o4e6sstp694f",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "zfnyj9he",
|
||||
"name": "variables",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "1bspzuku",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "g65gfh7a",
|
||||
"name": "nameservers",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "4yzbv8urny5ja1e",
|
||||
"created": "2024-07-29 10:04:39.685Z",
|
||||
"updated": "2024-09-17 00:53:25.859Z",
|
||||
"name": "access",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "geeur58v",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "iql7jpwx",
|
||||
"name": "config",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hwy7m03o",
|
||||
"name": "configType",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"aliyun",
|
||||
"tencent",
|
||||
"ssh",
|
||||
"webhook",
|
||||
"cloudflare",
|
||||
"qiniu",
|
||||
"namesilo",
|
||||
"godaddy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lr33hiwg",
|
||||
"name": "deleted",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "hsxcnlvd",
|
||||
"name": "usage",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"apply",
|
||||
"deploy",
|
||||
"all"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "c8egzzwj",
|
||||
"name": "group",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "teolp9pl72dxlxq",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "0a1o4e6sstp694f",
|
||||
"created": "2024-07-30 06:30:27.801Z",
|
||||
"updated": "2024-09-17 00:53:25.859Z",
|
||||
"name": "deployments",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "farvlzk7",
|
||||
"name": "domain",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "z3p974ainxjqlvs",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": 1,
|
||||
"displayFields": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "jx5f69i3",
|
||||
"name": "log",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "qbxdtg9q",
|
||||
"name": "phase",
|
||||
"type": "select",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": [
|
||||
"check",
|
||||
"apply",
|
||||
"deploy"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "rglrp1hz",
|
||||
"name": "phaseSuccess",
|
||||
"type": "bool",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "lt1g1blu",
|
||||
"name": "deployedAt",
|
||||
"type": "date",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": "",
|
||||
"max": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "_pb_users_auth_",
|
||||
"created": "2024-09-12 13:09:54.234Z",
|
||||
"updated": "2024-09-17 00:53:25.859Z",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"thumbs": null,
|
||||
"maxSelect": 1,
|
||||
"maxSize": 5242880,
|
||||
"protected": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dy6ccjb60spfy6p",
|
||||
"created": "2024-09-12 23:12:21.677Z",
|
||||
"updated": "2024-09-17 00:53:25.860Z",
|
||||
"name": "settings",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "1tcmdsdf",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "f9wyhypi",
|
||||
"name": "content",
|
||||
"type": "json",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSize": 2000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "teolp9pl72dxlxq",
|
||||
"created": "2024-09-13 12:51:05.611Z",
|
||||
"updated": "2024-09-17 00:53:25.860Z",
|
||||
"name": "access_groups",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"system": false,
|
||||
"id": "7sajiv6i",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"system": false,
|
||||
"id": "xp8admif",
|
||||
"name": "access",
|
||||
"type": "relation",
|
||||
"required": false,
|
||||
"presentable": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"collectionId": "4yzbv8urny5ja1e",
|
||||
"cascadeDelete": false,
|
||||
"minSelect": null,
|
||||
"maxSelect": null,
|
||||
"displayFields": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]`
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daos.New(db).ImportCollections(collections, true, nil)
|
||||
}, func(db dbx.Builder) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
1
ui/dist/assets/index-Cg0yCJnh.css
vendored
1
ui/dist/assets/index-Cg0yCJnh.css
vendored
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-ChWRjRip.css
vendored
Normal file
1
ui/dist/assets/index-ChWRjRip.css
vendored
Normal file
File diff suppressed because one or more lines are too long
284
ui/dist/assets/index-D8T6Buyh.js
vendored
284
ui/dist/assets/index-D8T6Buyh.js
vendored
File diff suppressed because one or more lines are too long
293
ui/dist/assets/index-liy7dSav.js
vendored
Normal file
293
ui/dist/assets/index-liy7dSav.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
ui/dist/imgs/providers/favicon-docs.dogecloud.com-1-200x200.svg
vendored
Normal file
7
ui/dist/imgs/providers/favicon-docs.dogecloud.com-1-200x200.svg
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
||||
<path d="M0 0 C8.00019718 6.13887963 14.34445574 12.99622218 18.3125 22.3125 C15.06464319 20.86413142 12.72723872 19.02613433 10.0625 16.6875 C-1.60459795 7.2541616 -16.18206674 0.63880608 -31.375 1.4375 C-43.09356312 2.78801868 -53.73683191 6.67588577 -62 15.5 C-71.51641841 27.69982709 -72.77906448 40.43807508 -71.6875 55.3125 C-69.75496452 69.19218976 -63.07550701 81.31999767 -54.6875 92.3125 C-53.96433594 93.26769531 -53.96433594 93.26769531 -53.2265625 94.2421875 C-49.80310028 98.49787258 -45.86843357 101.64242084 -41.4453125 104.83203125 C-40.86523438 105.32058594 -40.28515625 105.80914062 -39.6875 106.3125 C-39.6875 106.9725 -39.6875 107.6325 -39.6875 108.3125 C-45.21884376 108.61097445 -50.74543637 108.82766089 -56.28295898 108.97167969 C-58.1623705 109.03173819 -60.04122791 109.11342379 -61.91870117 109.21777344 C-77.40966514 110.05667884 -77.40966514 110.05667884 -82.80957031 105.921875 C-85.19168462 102.82857874 -86.97025266 99.81402723 -88.6875 96.3125 C-89.16856201 95.46268555 -89.64962402 94.61287109 -90.14526367 93.73730469 C-91.39695086 91.46566255 -92.49925956 89.17751393 -93.5625 86.8125 C-93.93689209 85.9877417 -94.31128418 85.1629834 -94.69702148 84.31323242 C-101.84695145 68.18991103 -104.37476618 48.01876725 -97.94140625 31.1171875 C-97.52761719 30.19164062 -97.11382812 29.26609375 -96.6875 28.3125 C-96.26339844 27.30445313 -95.83929688 26.29640625 -95.40234375 25.2578125 C-88.42098361 9.9727176 -77.15139172 -1.27183748 -61.6875 -7.6875 C-40.67179933 -14.71095263 -18.47073623 -12.74198216 0 0 Z " fill="#70A2EA" transform="translate(151.6875,52.6875)"/>
|
||||
<path d="M0 0 C-0.19335938 0.56847656 -0.38671875 1.13695313 -0.5859375 1.72265625 C-1.13131792 3.33989819 -1.66856703 4.95989684 -2.19921875 6.58203125 C-2.82868314 8.48270666 -3.50036013 10.36940667 -4.1875 12.25 C-7.48441131 27.46651373 -2.93859997 42.92249193 5 56 C10.27285559 63.85713427 17.42266677 71.71133339 26 76 C28.93108041 76.12365056 31.83742448 76.18844664 34.76953125 76.203125 C35.64268478 76.20882507 36.51583832 76.21452515 37.41545105 76.22039795 C39.26564317 76.22985345 41.11585218 76.23638411 42.96606445 76.24023438 C45.79082844 76.24992565 48.61486044 76.28093755 51.43945312 76.3125 C53.23697485 76.31903152 55.03450177 76.32428141 56.83203125 76.328125 C58.0963372 76.3466452 58.0963372 76.3466452 59.38618469 76.36553955 C64.08745075 76.34777001 67.19523771 76.06753063 71 73 C76.14974577 66.13367231 76.85747523 60.40719967 76 52 C72.94347249 40.85669565 66.57825718 33.02825174 57.07421875 26.66796875 C41.98336589 18.48017438 25.32550226 16.19330935 8.4375 15.4375 C7.52419922 15.39431641 6.61089844 15.35113281 5.66992188 15.30664062 C3.44672009 15.20188243 1.22343293 15.09970983 -1 15 C-1 14.34 -1 13.68 -1 13 C24.14499626 7.59108915 50.41887378 8.13666192 72.734375 22.42578125 C84.76471086 30.78479048 94.05571707 43.44065596 97 58 C97.83992741 68.1617447 96.68764977 76.53638183 90.375 84.75 C82.2520582 93.4708233 71.29646331 96.75137499 59.70800781 97.22460938 C55.46822317 97.34944357 51.22762628 97.41269989 46.98632812 97.45996094 C45.16900252 97.48655278 43.35182742 97.52746211 41.53515625 97.58300781 C30.19480451 97.9245351 21.92945424 97.88338566 13 90 C12.08879395 89.26837646 12.08879395 89.26837646 11.15917969 88.52197266 C-1.6887866 77.91909315 -10.95597826 65.95656876 -16 50 C-16.4125 48.7625 -16.825 47.525 -17.25 46.25 C-19.92772011 34.34631695 -18.39209844 21.70178399 -12.3515625 11.109375 C-4.91282895 0 -4.91282895 0 0 0 Z " fill="#79AFEB" transform="translate(103,64)"/>
|
||||
<path d="M0 0 C-0.42954381 1.3983022 -0.86907034 2.79353907 -1.3125 4.1875 C-1.55613281 4.96480469 -1.79976562 5.74210937 -2.05078125 6.54296875 C-3.87971163 11.27711365 -6.67096194 14.91291499 -9.9375 18.75 C-16.72958324 27.14022047 -20.11560508 36.06308588 -19 47 C-17.21558238 54.72810276 -14.24239154 60.64225254 -7.625 65.25 C-1.74362035 68.9878388 -1.74362035 68.9878388 5.015625 69.8046875 C7.86441607 69.65527538 9.37848936 69.78974988 12 71 C16.09718736 75.3958468 19.01252254 80.82170573 22 86 C22.99935816 87.6670516 23.99905049 89.3339034 25 91 C5.62249954 91.36484834 -12.69092394 90.61101563 -28 77 C-36.44050971 67.52265225 -39.76608235 57.3077398 -39.3828125 44.7109375 C-38.14502828 32.71200883 -32.24783597 21.70604907 -24 13 C-23.34 13 -22.68 13 -22 13 C-22 12.34 -22 11.68 -22 11 C-16.32431209 6.09717172 -7.83025539 0 0 0 Z " fill="#73A7EA" transform="translate(39,70)"/>
|
||||
<path d="M0 0 C-2.12910166 2.33471584 -4.07517335 3.68146181 -6.9375 5 C-9.90732447 6.41679699 -12.38725285 7.9901945 -15 10 C-15.66 10 -16.32 10 -17 10 C-17 10.66 -17 11.32 -17 12 C-18.12525155 13.02649277 -19.30282645 13.99587696 -20.5 14.9375 C-33.49161609 26.0469385 -41.59305065 43.11799329 -43.1953125 60.06591797 C-43.714182 69.16559176 -42.71323311 78.06671309 -41 87 C-46.81627942 82.15310048 -49.61443677 75.38967056 -51 68 C-51.69814606 57.29890869 -51.08055936 48.06628054 -47 38 C-46.67257813 37.15308594 -46.34515625 36.30617187 -46.0078125 35.43359375 C-41.86810307 26.02104276 -34.29231204 18.10356984 -27 11 C-27.433125 10.51789062 -27.86625 10.03578125 -28.3125 9.5390625 C-28.869375 8.90742187 -29.42625 8.27578125 -30 7.625 C-30.556875 6.99851562 -31.11375 6.37203125 -31.6875 5.7265625 C-33 4 -33 4 -33 2 C-27.87373026 1.19944082 -22.8041505 0.72131302 -17.625 0.4375 C-16.8812915 0.3950415 -16.13758301 0.35258301 -15.37133789 0.30883789 C-10.24014323 0.03021845 -5.13944387 -0.09344443 0 0 Z " fill="#71A4EA" transform="translate(88,39)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
5
ui/dist/imgs/providers/godaddy.svg
vendored
Normal file
5
ui/dist/imgs/providers/godaddy.svg
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#1bdbdb"/>
|
||||
<path d="M697.6 315.9c-53.2-33.2-123.3-25.3-185.6 13.9-62.4-39.3-132.4-47.2-185.6-13.9-84.1 52.5-94.3 187.8-22.8 302.2 52.7 84.3 135.1 133.7 208.4 132.8 73.3.9 155.7-48.5 208.4-132.8 71.5-114.4 61.3-249.7-22.8-302.2M342.2 594c-15-24.1-26.1-49.5-33-75.5-6.5-24.5-8.9-48.5-7.1-71.2 3.2-42.3 20.4-75.2 48.4-92.7s65.2-18.6 104.5-2.9c5.9 2.4 11.8 5.1 17.6 8.1-21 19-40.3 41.9-56.7 68.1-43.4 69.5-56.6 146.7-41.5 208.4-11.8-12.8-22.6-27-32.2-42.3m372.6-75.6c-6.9 26.1-17.9 51.5-33 75.5-9.6 15.4-20.4 29.5-32.3 42.3 13.5-55.2 4.4-122.9-28.9-186.3-2.3-4.5-7.7-5.9-12-3.3l-103.5 64.7c-4 2.5-5.2 7.7-2.7 11.7l15.2 24.3c2.5 4 7.7 5.2 11.7 2.7l67.1-41.9c2.2 6.4 4.3 12.9 6 19.5 6.5 24.5 8.9 48.5 7.1 71.2-3.2 42.3-20.4 75.2-48.4 92.7-14 8.8-30.3 13.4-48 13.9h-2.2c-17.7-.5-34-5.1-48-13.9-28-17.5-45.2-50.4-48.4-92.7-1.7-22.7.7-46.7 7.1-71.2 6.8-26.1 17.9-51.5 33-75.5 15-24.1 33-45.2 53.4-62.8 19.2-16.6 39.7-29.2 60.9-37.6 39.4-15.7 76.5-14.6 104.5 2.9s45.2 50.4 48.4 92.7c1.8 22.6-.6 46.6-7 71.1" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||
<script type="module" crossorigin src="/assets/index-D8T6Buyh.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Cg0yCJnh.css">
|
||||
<script type="module" crossorigin src="/assets/index-liy7dSav.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-ChWRjRip.css">
|
||||
</head>
|
||||
<body class="bg-background">
|
||||
<div id="root"></div>
|
||||
|
||||
30
ui/package-lock.json
generated
30
ui/package-lock.json
generated
@@ -20,6 +20,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
@@ -1762,6 +1763,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tabs": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz",
|
||||
"integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-roving-focus": "1.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.1.tgz",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
|
||||
<path d="M0 0 C8.00019718 6.13887963 14.34445574 12.99622218 18.3125 22.3125 C15.06464319 20.86413142 12.72723872 19.02613433 10.0625 16.6875 C-1.60459795 7.2541616 -16.18206674 0.63880608 -31.375 1.4375 C-43.09356312 2.78801868 -53.73683191 6.67588577 -62 15.5 C-71.51641841 27.69982709 -72.77906448 40.43807508 -71.6875 55.3125 C-69.75496452 69.19218976 -63.07550701 81.31999767 -54.6875 92.3125 C-53.96433594 93.26769531 -53.96433594 93.26769531 -53.2265625 94.2421875 C-49.80310028 98.49787258 -45.86843357 101.64242084 -41.4453125 104.83203125 C-40.86523438 105.32058594 -40.28515625 105.80914062 -39.6875 106.3125 C-39.6875 106.9725 -39.6875 107.6325 -39.6875 108.3125 C-45.21884376 108.61097445 -50.74543637 108.82766089 -56.28295898 108.97167969 C-58.1623705 109.03173819 -60.04122791 109.11342379 -61.91870117 109.21777344 C-77.40966514 110.05667884 -77.40966514 110.05667884 -82.80957031 105.921875 C-85.19168462 102.82857874 -86.97025266 99.81402723 -88.6875 96.3125 C-89.16856201 95.46268555 -89.64962402 94.61287109 -90.14526367 93.73730469 C-91.39695086 91.46566255 -92.49925956 89.17751393 -93.5625 86.8125 C-93.93689209 85.9877417 -94.31128418 85.1629834 -94.69702148 84.31323242 C-101.84695145 68.18991103 -104.37476618 48.01876725 -97.94140625 31.1171875 C-97.52761719 30.19164062 -97.11382812 29.26609375 -96.6875 28.3125 C-96.26339844 27.30445313 -95.83929688 26.29640625 -95.40234375 25.2578125 C-88.42098361 9.9727176 -77.15139172 -1.27183748 -61.6875 -7.6875 C-40.67179933 -14.71095263 -18.47073623 -12.74198216 0 0 Z " fill="#70A2EA" transform="translate(151.6875,52.6875)"/>
|
||||
<path d="M0 0 C-0.19335938 0.56847656 -0.38671875 1.13695313 -0.5859375 1.72265625 C-1.13131792 3.33989819 -1.66856703 4.95989684 -2.19921875 6.58203125 C-2.82868314 8.48270666 -3.50036013 10.36940667 -4.1875 12.25 C-7.48441131 27.46651373 -2.93859997 42.92249193 5 56 C10.27285559 63.85713427 17.42266677 71.71133339 26 76 C28.93108041 76.12365056 31.83742448 76.18844664 34.76953125 76.203125 C35.64268478 76.20882507 36.51583832 76.21452515 37.41545105 76.22039795 C39.26564317 76.22985345 41.11585218 76.23638411 42.96606445 76.24023438 C45.79082844 76.24992565 48.61486044 76.28093755 51.43945312 76.3125 C53.23697485 76.31903152 55.03450177 76.32428141 56.83203125 76.328125 C58.0963372 76.3466452 58.0963372 76.3466452 59.38618469 76.36553955 C64.08745075 76.34777001 67.19523771 76.06753063 71 73 C76.14974577 66.13367231 76.85747523 60.40719967 76 52 C72.94347249 40.85669565 66.57825718 33.02825174 57.07421875 26.66796875 C41.98336589 18.48017438 25.32550226 16.19330935 8.4375 15.4375 C7.52419922 15.39431641 6.61089844 15.35113281 5.66992188 15.30664062 C3.44672009 15.20188243 1.22343293 15.09970983 -1 15 C-1 14.34 -1 13.68 -1 13 C24.14499626 7.59108915 50.41887378 8.13666192 72.734375 22.42578125 C84.76471086 30.78479048 94.05571707 43.44065596 97 58 C97.83992741 68.1617447 96.68764977 76.53638183 90.375 84.75 C82.2520582 93.4708233 71.29646331 96.75137499 59.70800781 97.22460938 C55.46822317 97.34944357 51.22762628 97.41269989 46.98632812 97.45996094 C45.16900252 97.48655278 43.35182742 97.52746211 41.53515625 97.58300781 C30.19480451 97.9245351 21.92945424 97.88338566 13 90 C12.08879395 89.26837646 12.08879395 89.26837646 11.15917969 88.52197266 C-1.6887866 77.91909315 -10.95597826 65.95656876 -16 50 C-16.4125 48.7625 -16.825 47.525 -17.25 46.25 C-19.92772011 34.34631695 -18.39209844 21.70178399 -12.3515625 11.109375 C-4.91282895 0 -4.91282895 0 0 0 Z " fill="#79AFEB" transform="translate(103,64)"/>
|
||||
<path d="M0 0 C-0.42954381 1.3983022 -0.86907034 2.79353907 -1.3125 4.1875 C-1.55613281 4.96480469 -1.79976562 5.74210937 -2.05078125 6.54296875 C-3.87971163 11.27711365 -6.67096194 14.91291499 -9.9375 18.75 C-16.72958324 27.14022047 -20.11560508 36.06308588 -19 47 C-17.21558238 54.72810276 -14.24239154 60.64225254 -7.625 65.25 C-1.74362035 68.9878388 -1.74362035 68.9878388 5.015625 69.8046875 C7.86441607 69.65527538 9.37848936 69.78974988 12 71 C16.09718736 75.3958468 19.01252254 80.82170573 22 86 C22.99935816 87.6670516 23.99905049 89.3339034 25 91 C5.62249954 91.36484834 -12.69092394 90.61101563 -28 77 C-36.44050971 67.52265225 -39.76608235 57.3077398 -39.3828125 44.7109375 C-38.14502828 32.71200883 -32.24783597 21.70604907 -24 13 C-23.34 13 -22.68 13 -22 13 C-22 12.34 -22 11.68 -22 11 C-16.32431209 6.09717172 -7.83025539 0 0 0 Z " fill="#73A7EA" transform="translate(39,70)"/>
|
||||
<path d="M0 0 C-2.12910166 2.33471584 -4.07517335 3.68146181 -6.9375 5 C-9.90732447 6.41679699 -12.38725285 7.9901945 -15 10 C-15.66 10 -16.32 10 -17 10 C-17 10.66 -17 11.32 -17 12 C-18.12525155 13.02649277 -19.30282645 13.99587696 -20.5 14.9375 C-33.49161609 26.0469385 -41.59305065 43.11799329 -43.1953125 60.06591797 C-43.714182 69.16559176 -42.71323311 78.06671309 -41 87 C-46.81627942 82.15310048 -49.61443677 75.38967056 -51 68 C-51.69814606 57.29890869 -51.08055936 48.06628054 -47 38 C-46.67257813 37.15308594 -46.34515625 36.30617187 -46.0078125 35.43359375 C-41.86810307 26.02104276 -34.29231204 18.10356984 -27 11 C-27.433125 10.51789062 -27.86625 10.03578125 -28.3125 9.5390625 C-28.869375 8.90742187 -29.42625 8.27578125 -30 7.625 C-30.556875 6.99851562 -31.11375 6.37203125 -31.6875 5.7265625 C-33 4 -33 4 -33 2 C-27.87373026 1.19944082 -22.8041505 0.72131302 -17.625 0.4375 C-16.8812915 0.3950415 -16.13758301 0.35258301 -15.37133789 0.30883789 C-10.24014323 0.03021845 -5.13944387 -0.09344443 0 0 Z " fill="#71A4EA" transform="translate(88,39)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
5
ui/public/imgs/providers/godaddy.svg
Normal file
5
ui/public/imgs/providers/godaddy.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#1bdbdb"/>
|
||||
<path d="M697.6 315.9c-53.2-33.2-123.3-25.3-185.6 13.9-62.4-39.3-132.4-47.2-185.6-13.9-84.1 52.5-94.3 187.8-22.8 302.2 52.7 84.3 135.1 133.7 208.4 132.8 73.3.9 155.7-48.5 208.4-132.8 71.5-114.4 61.3-249.7-22.8-302.2M342.2 594c-15-24.1-26.1-49.5-33-75.5-6.5-24.5-8.9-48.5-7.1-71.2 3.2-42.3 20.4-75.2 48.4-92.7s65.2-18.6 104.5-2.9c5.9 2.4 11.8 5.1 17.6 8.1-21 19-40.3 41.9-56.7 68.1-43.4 69.5-56.6 146.7-41.5 208.4-11.8-12.8-22.6-27-32.2-42.3m372.6-75.6c-6.9 26.1-17.9 51.5-33 75.5-9.6 15.4-20.4 29.5-32.3 42.3 13.5-55.2 4.4-122.9-28.9-186.3-2.3-4.5-7.7-5.9-12-3.3l-103.5 64.7c-4 2.5-5.2 7.7-2.7 11.7l15.2 24.3c2.5 4 7.7 5.2 11.7 2.7l67.1-41.9c2.2 6.4 4.3 12.9 6 19.5 6.5 24.5 8.9 48.5 7.1 71.2-3.2 42.3-20.4 75.2-48.4 92.7-14 8.8-30.3 13.4-48 13.9h-2.2c-17.7-.5-34-5.1-48-13.9-28-17.5-45.2-50.4-48.4-92.7-1.7-22.7.7-46.7 7.1-71.2 6.8-26.1 17.9-51.5 33-75.5 15-24.1 33-45.2 53.4-62.8 19.2-16.6 39.7-29.2 60.9-37.6 39.4-15.7 76.5-14.6 104.5 2.9s45.2 50.4 48.4 92.7c1.8 22.6-.6 46.6-7 71.1" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, AliyunConfig } from "@/domain/access";
|
||||
import { Access, accessFormType, AliyunConfig, getUsageByConfigType } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
|
||||
@@ -59,6 +59,7 @@ const AccessAliyunForm = ({
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
accessKeyId: data.accessKeyId,
|
||||
accessKeySecret: data.accessSecretId,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, CloudflareConfig } from "@/domain/access";
|
||||
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -56,6 +56,7 @@ const AccessCloudflareForm = ({
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
dnsApiToken: data.dnsApiToken,
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import AccessCloudflareForm from "./AccessCloudflareForm";
|
||||
import AccessQiniuForm from "./AccessQiniuForm";
|
||||
import AccessNamesiloForm from "./AccessNamesiloForm";
|
||||
import AccessGodaddyFrom from "./AccessGodaddyForm";
|
||||
|
||||
type TargetConfigEditProps = {
|
||||
op: "add" | "edit";
|
||||
@@ -122,6 +123,16 @@ export function AccessEdit({
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case "godaddy":
|
||||
form = (
|
||||
<AccessGodaddyFrom
|
||||
data={data}
|
||||
onAfterReq={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const getOptionCls = (val: string) => {
|
||||
|
||||
198
ui/src/components/certimate/AccessGodaddyForm.tsx
Normal file
198
ui/src/components/certimate/AccessGodaddyForm.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
import z from "zod";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
GodaddyConfig,
|
||||
} from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
|
||||
const AccessGodaddyFrom = ({
|
||||
data,
|
||||
onAfterReq,
|
||||
}: {
|
||||
data?: Access;
|
||||
onAfterReq: () => void;
|
||||
}) => {
|
||||
const { addAccess, updateAccess } = useConfig();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1).max(64),
|
||||
configType: accessFormType,
|
||||
apiKey: z.string().min(1).max(64),
|
||||
apiSecret: z.string().min(1).max(64),
|
||||
});
|
||||
|
||||
let config: GodaddyConfig = {
|
||||
apiKey: "",
|
||||
apiSecret: "",
|
||||
};
|
||||
if (data) config = data.config as GodaddyConfig;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name,
|
||||
configType: "godaddy",
|
||||
apiKey: config.apiKey,
|
||||
apiSecret: config.apiSecret,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
console.log(data);
|
||||
const req: Access = {
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
apiKey: data.apiKey,
|
||||
apiSecret: data.apiSecret,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const rs = await save(req);
|
||||
|
||||
onAfterReq();
|
||||
|
||||
req.id = rs.id;
|
||||
req.created = rs.created;
|
||||
req.updated = rs.updated;
|
||||
if (data.id) {
|
||||
updateAccess(req);
|
||||
return;
|
||||
}
|
||||
addAccess(req);
|
||||
} catch (e) {
|
||||
const err = e as ClientResponseError;
|
||||
|
||||
Object.entries(err.response.data as PbErrorData).forEach(
|
||||
([key, value]) => {
|
||||
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||
type: "manual",
|
||||
message: value.message,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="max-w-[35em] mx-auto mt-10">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
console.log(e);
|
||||
e.stopPropagation();
|
||||
form.handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>名称</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入授权名称" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>配置类型</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="hidden">
|
||||
<FormLabel>配置类型</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>GODADDY_API_KEY</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入GODADDY_API_KEY" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiSecret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>GODADDY_API_SECRET</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入GODADDY_API_SECRET" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">保存</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessGodaddyFrom;
|
||||
120
ui/src/components/certimate/AccessGroupEdit.tsx
Normal file
120
ui/src/components/certimate/AccessGroupEdit.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "../ui/form";
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { update } from "@/repository/access_group";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { useState } from "react";
|
||||
|
||||
type AccessGroupEditProps = {
|
||||
className?: string;
|
||||
trigger: React.ReactNode;
|
||||
};
|
||||
|
||||
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
|
||||
const { reloadAccessGroups } = useConfig();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1).max(64),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
try {
|
||||
await update({
|
||||
name: data.name,
|
||||
});
|
||||
|
||||
// 更新本地状态
|
||||
reloadAccessGroups();
|
||||
|
||||
setOpen(false);
|
||||
} catch (e) {
|
||||
const err = e as ClientResponseError;
|
||||
|
||||
Object.entries(err.response.data as PbErrorData).forEach(
|
||||
([key, value]) => {
|
||||
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||
type: "manual",
|
||||
message: value.message,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog onOpenChange={setOpen} open={open}>
|
||||
<DialogTrigger asChild className={cn(className)}>
|
||||
{trigger}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加分组</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="container py-3">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
console.log(e);
|
||||
e.stopPropagation();
|
||||
form.handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>组名</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入组名" {...field} type="text" />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">保存</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessGroupEdit;
|
||||
217
ui/src/components/certimate/AccessGroupList.tsx
Normal file
217
ui/src/components/certimate/AccessGroupList.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
|
||||
import Show from "@/components/Show";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { getProviderInfo } from "@/domain/access";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { remove } from "@/repository/access_group";
|
||||
import { Group } from "lucide-react";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const AccessGroupList = () => {
|
||||
const {
|
||||
config: { accessGroups },
|
||||
reloadAccessGroups,
|
||||
} = useConfig();
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
|
||||
const handleRemoveClick = async (id: string) => {
|
||||
try {
|
||||
await remove(id);
|
||||
reloadAccessGroups();
|
||||
} catch (e) {
|
||||
toast({
|
||||
title: "删除失败",
|
||||
description: getErrMessage(e),
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddAccess = () => {
|
||||
navigate("/access");
|
||||
};
|
||||
return (
|
||||
<div className="mt-10">
|
||||
<Show when={accessGroups.length == 0}>
|
||||
<>
|
||||
<div className="flex flex-col items-center mt-10">
|
||||
<span className="bg-orange-100 p-5 rounded-full">
|
||||
<Group size={40} className="text-primary" />
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
请添加域名开始部署证书吧。
|
||||
</div>
|
||||
<AccessGroupEdit
|
||||
trigger={<Button>新增授权组</Button>}
|
||||
className="mt-3"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</Show>
|
||||
|
||||
<ScrollArea className="h-[75vh] overflow-hidden">
|
||||
<div className="flex gap-5 flex-wrap">
|
||||
{accessGroups.map((accessGroup) => (
|
||||
<Card className="w-full md:w-[350px]">
|
||||
<CardHeader>
|
||||
<CardTitle>{accessGroup.name}</CardTitle>
|
||||
<CardDescription>
|
||||
共有
|
||||
{accessGroup.expand ? accessGroup.expand.access.length : 0}
|
||||
个部署授权配置
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="min-h-[180px]">
|
||||
{accessGroup.expand ? (
|
||||
<>
|
||||
{accessGroup.expand.access.slice(0, 3).map((access) => (
|
||||
<div key={access.id} className="flex flex-col mb-3">
|
||||
<div className="flex items-center">
|
||||
<div className="">
|
||||
<img
|
||||
src={getProviderInfo(access.configType)![1]}
|
||||
alt="provider"
|
||||
className="w-8 h-8"
|
||||
></img>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<div className="text-sm font-semibold text-gray-700 dark:text-gray-200">
|
||||
{access.name}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{getProviderInfo(access.configType)![0]}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex text-gray-700 dark:text-gray-200 items-center">
|
||||
<div>
|
||||
<Group size={40} />
|
||||
</div>
|
||||
<div className="ml-2">
|
||||
暂无部署授权配置,请添加后开始使用吧
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex justify-end w-full">
|
||||
<Show
|
||||
when={
|
||||
accessGroup.expand && accessGroup.expand.access.length > 0
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={"link"}
|
||||
onClick={() => {
|
||||
navigate(
|
||||
`/access?accessGroupId=${accessGroup.id}&tab=access`,
|
||||
{
|
||||
replace: true,
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
所有授权
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show
|
||||
when={
|
||||
!accessGroup.expand ||
|
||||
accessGroup.expand.access.length == 0
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Button size="sm" onClick={handleAddAccess}>
|
||||
新增授权
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div className="ml-3">
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant={"destructive"} size={"sm"}>
|
||||
删除
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="dark:text-gray-200">
|
||||
删除组
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确定要删除部署授权组吗?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className="dark:text-gray-200">
|
||||
取消
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
handleRemoveClick(
|
||||
accessGroup.id ? accessGroup.id : ""
|
||||
);
|
||||
}}
|
||||
>
|
||||
确认
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessGroupList;
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, NamesiloConfig } from "@/domain/access";
|
||||
import { Access, accessFormType, getUsageByConfigType, NamesiloConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -56,6 +56,7 @@ const AccessNamesiloForm = ({
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
apiKey: data.apiKey,
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, QiniuConfig } from "@/domain/access";
|
||||
import { Access, accessFormType, getUsageByConfigType, QiniuConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
|
||||
@@ -59,6 +59,7 @@ const AccessQiniuForm = ({
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
accessKey: data.accessKey,
|
||||
secretKey: data.secretKey,
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Access, accessFormType, SSHConfig } from "@/domain/access";
|
||||
import {
|
||||
Access,
|
||||
accessFormType,
|
||||
getUsageByConfigType,
|
||||
SSHConfig,
|
||||
} from "@/domain/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -19,6 +24,17 @@ import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { readFileContent } from "@/lib/file";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import AccessGroupEdit from "./AccessGroupEdit";
|
||||
import { Plus } from "lucide-react";
|
||||
import { updateById } from "@/repository/access_group";
|
||||
|
||||
const AccessSSHForm = ({
|
||||
data,
|
||||
@@ -27,12 +43,19 @@ const AccessSSHForm = ({
|
||||
data?: Access;
|
||||
onAfterReq: () => void;
|
||||
}) => {
|
||||
const { addAccess, updateAccess } = useConfig();
|
||||
const {
|
||||
addAccess,
|
||||
updateAccess,
|
||||
reloadAccessGroups,
|
||||
config: { accessGroups },
|
||||
} = useConfig();
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const [fileName, setFileName] = useState("");
|
||||
|
||||
const originGroup = data ? (data.group ? data.group : "") : "";
|
||||
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1).max(64),
|
||||
@@ -40,6 +63,7 @@ const AccessSSHForm = ({
|
||||
host: z.string().ip({
|
||||
message: "请输入合法的IP地址",
|
||||
}),
|
||||
group: z.string().optional(),
|
||||
port: z.string().min(1).max(5),
|
||||
username: z.string().min(1).max(64),
|
||||
password: z.string().min(0).max(64),
|
||||
@@ -69,6 +93,7 @@ const AccessSSHForm = ({
|
||||
id: data?.id,
|
||||
name: data?.name,
|
||||
configType: "ssh",
|
||||
group: data?.group,
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
username: config.username,
|
||||
@@ -83,10 +108,15 @@ const AccessSSHForm = ({
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
console.log(data);
|
||||
let group = data.group;
|
||||
if (group == "emptyId") group = "";
|
||||
|
||||
const req: Access = {
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
group: group,
|
||||
config: {
|
||||
host: data.host,
|
||||
port: data.port,
|
||||
@@ -109,9 +139,28 @@ const AccessSSHForm = ({
|
||||
req.updated = rs.updated;
|
||||
if (data.id) {
|
||||
updateAccess(req);
|
||||
return;
|
||||
} else {
|
||||
addAccess(req);
|
||||
}
|
||||
addAccess(req);
|
||||
|
||||
// 同步更新授权组
|
||||
if (group != originGroup) {
|
||||
if (originGroup) {
|
||||
await updateById({
|
||||
id: originGroup,
|
||||
"access-": req.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (group) {
|
||||
await updateById({
|
||||
id: group,
|
||||
"access+": req.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
reloadAccessGroups();
|
||||
} catch (e) {
|
||||
const err = e as ClientResponseError;
|
||||
|
||||
@@ -171,6 +220,67 @@ const AccessSSHForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="group"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>授权配置组(用于将一个域名证书部署到多个 ssh 主机)</div>
|
||||
<AccessGroupEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
新增
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
defaultValue="emptyId"
|
||||
onValueChange={(value) => {
|
||||
form.setValue("group", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择分组" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="emptyId">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 rounded cursor-pointer"
|
||||
)}
|
||||
>
|
||||
--
|
||||
</div>
|
||||
</SelectItem>
|
||||
{accessGroups.map((item) => (
|
||||
<SelectItem
|
||||
value={item.id ? item.id : ""}
|
||||
key={item.id}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 rounded cursor-pointer"
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="id"
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, TencentConfig } from "@/domain/access";
|
||||
import { Access, accessFormType, getUsageByConfigType, TencentConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -58,6 +58,7 @@ const AccessTencentForm = ({
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
secretId: data.secretId,
|
||||
secretKey: data.secretKey,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Access, accessFormType, WebhookConfig } from "@/domain/access";
|
||||
import { Access, accessFormType, getUsageByConfigType, WebhookConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
@@ -56,6 +56,7 @@ const WebhookForm = ({
|
||||
id: data.id as string,
|
||||
name: data.name,
|
||||
configType: data.configType,
|
||||
usage: getUsageByConfigType(data.configType),
|
||||
config: {
|
||||
url: data.url,
|
||||
},
|
||||
|
||||
141
ui/src/components/certimate/EmailsEdit.tsx
Normal file
141
ui/src/components/certimate/EmailsEdit.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../ui/dialog";
|
||||
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "../ui/form";
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { update } from "@/repository/settings";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { useState } from "react";
|
||||
|
||||
type EmailsEditProps = {
|
||||
className?: string;
|
||||
trigger: React.ReactNode;
|
||||
};
|
||||
|
||||
const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
const {
|
||||
config: { emails },
|
||||
setEmails,
|
||||
} = useConfig();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
email: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
if (emails.content.emails.includes(data.email)) {
|
||||
form.setError("email", {
|
||||
message: "邮箱已存在",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存到 config
|
||||
const newEmails = [...emails.content.emails, data.email];
|
||||
|
||||
try {
|
||||
const resp = await update({
|
||||
...emails,
|
||||
name: "emails",
|
||||
content: {
|
||||
emails: newEmails,
|
||||
},
|
||||
});
|
||||
|
||||
// 更新本地状态
|
||||
setEmails(resp);
|
||||
|
||||
// 关闭弹窗
|
||||
form.reset();
|
||||
form.clearErrors();
|
||||
|
||||
setOpen(false);
|
||||
} catch (e) {
|
||||
const err = e as ClientResponseError;
|
||||
|
||||
Object.entries(err.response.data as PbErrorData).forEach(
|
||||
([key, value]) => {
|
||||
form.setError(key as keyof z.infer<typeof formSchema>, {
|
||||
type: "manual",
|
||||
message: value.message,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog onOpenChange={setOpen} open={open}>
|
||||
<DialogTrigger asChild className={cn(className)}>
|
||||
{trigger}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加邮箱</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="container py-3">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
console.log(e);
|
||||
e.stopPropagation();
|
||||
form.handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>邮箱</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入邮箱" {...field} type="email" />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">保存</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailsEdit;
|
||||
31
ui/src/components/certimate/Version.tsx
Normal file
31
ui/src/components/certimate/Version.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { BookOpen } from "lucide-react";
|
||||
|
||||
import { Separator } from "../ui/separator";
|
||||
import { version } from "@/domain/version";
|
||||
|
||||
const Version = () => {
|
||||
return (
|
||||
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
|
||||
<div className=""></div>
|
||||
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
|
||||
<a
|
||||
href="https://docs.certimate.me"
|
||||
target="_blank"
|
||||
className="flex items-center"
|
||||
>
|
||||
<BookOpen size={16} />
|
||||
<div className="ml-1">文档</div>
|
||||
</a>
|
||||
<Separator orientation="vertical" className="mx-2" />
|
||||
<a
|
||||
href="https://github.com/usual2970/certimate/releases"
|
||||
target="_blank"
|
||||
>
|
||||
{version}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Version;
|
||||
53
ui/src/components/ui/tabs.tsx
Normal file
53
ui/src/components/ui/tabs.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
@@ -5,11 +5,16 @@ export const accessTypeMap: Map<string, [string, string]> = new Map([
|
||||
["aliyun", ["阿里云", "/imgs/providers/aliyun.svg"]],
|
||||
["cloudflare", ["Cloudflare", "/imgs/providers/cloudflare.svg"]],
|
||||
["namesilo", ["Namesilo", "/imgs/providers/namesilo.svg"]],
|
||||
["godaddy", ["GoDaddy", "/imgs/providers/godaddy.svg"]],
|
||||
["qiniu", ["七牛云", "/imgs/providers/qiniu.svg"]],
|
||||
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
|
||||
["webhook", ["Webhook", "/imgs/providers/webhook.svg"]],
|
||||
]);
|
||||
|
||||
export const getProviderInfo = (t: string) => {
|
||||
return accessTypeMap.get(t);
|
||||
};
|
||||
|
||||
export const accessFormType = z.union(
|
||||
[
|
||||
z.literal("aliyun"),
|
||||
@@ -19,14 +24,19 @@ export const accessFormType = z.union(
|
||||
z.literal("cloudflare"),
|
||||
z.literal("qiniu"),
|
||||
z.literal("namesilo"),
|
||||
z.literal("godaddy"),
|
||||
],
|
||||
{ message: "请选择云服务商" }
|
||||
);
|
||||
|
||||
type AccessUsage = "apply" | "deploy" | "all";
|
||||
|
||||
export type Access = {
|
||||
id: string;
|
||||
name: string;
|
||||
configType: string;
|
||||
usage: AccessUsage;
|
||||
group?: string;
|
||||
config:
|
||||
| TencentConfig
|
||||
| AliyunConfig
|
||||
@@ -34,7 +44,8 @@ export type Access = {
|
||||
| WebhookConfig
|
||||
| CloudflareConfig
|
||||
| QiniuConfig
|
||||
| NamesiloConfig;
|
||||
| NamesiloConfig
|
||||
| GodaddyConfig;
|
||||
|
||||
deleted?: string;
|
||||
created?: string;
|
||||
@@ -67,6 +78,10 @@ export type AliyunConfig = {
|
||||
export type NamesiloConfig = {
|
||||
apiKey: string;
|
||||
};
|
||||
export type GodaddyConfig = {
|
||||
apiKey: string;
|
||||
apiSecret: string;
|
||||
};
|
||||
|
||||
export type SSHConfig = {
|
||||
host: string;
|
||||
@@ -79,3 +94,22 @@ export type SSHConfig = {
|
||||
certPath: string;
|
||||
keyPath: string;
|
||||
};
|
||||
|
||||
export const getUsageByConfigType = (configType: string): AccessUsage => {
|
||||
switch (configType) {
|
||||
case "aliyun":
|
||||
case "tencent":
|
||||
return "all";
|
||||
case "ssh":
|
||||
case "webhook":
|
||||
case "qiniu":
|
||||
return "deploy";
|
||||
|
||||
case "cloudflare":
|
||||
case "namesilo":
|
||||
case "godaddy":
|
||||
return "apply";
|
||||
default:
|
||||
return "all";
|
||||
}
|
||||
};
|
||||
|
||||
10
ui/src/domain/access_groups.ts
Normal file
10
ui/src/domain/access_groups.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Access } from "./access";
|
||||
|
||||
export type AccessGroup = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
access?: string[];
|
||||
expand?: {
|
||||
access: Access[];
|
||||
};
|
||||
};
|
||||
@@ -3,15 +3,20 @@ import { Deployment, Pahse } from "./deployment";
|
||||
export type Domain = {
|
||||
id: string;
|
||||
domain: string;
|
||||
email?: string;
|
||||
crontab: string;
|
||||
access: string;
|
||||
targetAccess: string;
|
||||
targetAccess?: string;
|
||||
targetType: string;
|
||||
expiredAt?: string;
|
||||
phase?: Pahse;
|
||||
phaseSuccess?: boolean;
|
||||
lastDeployedAt?: string;
|
||||
variables?: string;
|
||||
nameservers?: string;
|
||||
group?: string;
|
||||
enabled?: boolean;
|
||||
deployed?: boolean;
|
||||
created?: string;
|
||||
updated?: string;
|
||||
deleted?: string;
|
||||
@@ -37,6 +42,7 @@ export const getLastDeployment = (domain: Domain): Deployment | undefined => {
|
||||
export const targetTypeMap: Map<string, [string, string]> = new Map([
|
||||
["aliyun-cdn", ["阿里云-CDN", "/imgs/providers/aliyun.svg"]],
|
||||
["aliyun-oss", ["阿里云-OSS", "/imgs/providers/aliyun.svg"]],
|
||||
["aliyun-dcdn", ["阿里云-DCDN", "/imgs/providers/aliyun.svg"]],
|
||||
["tencent-cdn", ["腾讯云-CDN", "/imgs/providers/tencent.svg"]],
|
||||
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
|
||||
["qiniu-cdn", ["七牛云-CDN", "/imgs/providers/qiniu.svg"]],
|
||||
|
||||
9
ui/src/domain/settings.ts
Normal file
9
ui/src/domain/settings.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type Setting = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
content: EmailsSetting;
|
||||
};
|
||||
|
||||
type EmailsSetting = {
|
||||
emails: string[];
|
||||
};
|
||||
1
ui/src/domain/version.ts
Normal file
1
ui/src/domain/version.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const version = "Certimate v0.1.8";
|
||||
@@ -7,7 +7,7 @@ import { ThemeProvider } from "./components/ThemeProvider.tsx";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
||||
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
|
||||
@@ -5,15 +5,7 @@ import {
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from "react-router-dom";
|
||||
import {
|
||||
BookOpen,
|
||||
CircleUser,
|
||||
Earth,
|
||||
History,
|
||||
Home,
|
||||
Menu,
|
||||
Server,
|
||||
} from "lucide-react";
|
||||
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
@@ -30,7 +22,8 @@ import { cn } from "@/lib/utils";
|
||||
import { ConfigProvider } from "@/providers/config";
|
||||
import { getPb } from "@/repository/api";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
import Version from "@/components/certimate/Version";
|
||||
|
||||
export default function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
@@ -161,7 +154,7 @@ export default function Dashboard() {
|
||||
to="/access"
|
||||
className={cn(
|
||||
"mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 hover:text-foreground",
|
||||
getClass("/dns_provider")
|
||||
getClass("/access")
|
||||
)}
|
||||
>
|
||||
<Server className="h-5 w-5" />
|
||||
@@ -199,7 +192,7 @@ export default function Dashboard() {
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem onClick={handleSettingClick}>
|
||||
设置
|
||||
偏好设置
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={handleLogoutClick}>
|
||||
@@ -211,26 +204,7 @@ export default function Dashboard() {
|
||||
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 relative">
|
||||
<Outlet />
|
||||
|
||||
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
|
||||
<div className=""></div>
|
||||
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
|
||||
<a
|
||||
href="https://docs.certimate.me"
|
||||
target="_blank"
|
||||
className="flex items-center"
|
||||
>
|
||||
<BookOpen size={16} />
|
||||
<div className="ml-1">文档</div>
|
||||
</a>
|
||||
<Separator orientation="vertical" className="mx-2" />
|
||||
<a
|
||||
href="https://github.com/usual2970/certimate/releases"
|
||||
target="_blank"
|
||||
>
|
||||
Certimate v0.1.3
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Version />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import Version from "@/components/certimate/Version";
|
||||
|
||||
import { getPb } from "@/repository/api";
|
||||
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
|
||||
const LoginLayout = () => {
|
||||
@@ -8,6 +11,8 @@ const LoginLayout = () => {
|
||||
return (
|
||||
<div className="container">
|
||||
<Outlet />
|
||||
|
||||
<Version />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { AccessEdit } from "@/components/certimate/AccessEdit";
|
||||
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
|
||||
import AccessGroupList from "@/components/certimate/AccessGroupList";
|
||||
import XPagination from "@/components/certimate/XPagination";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Access as AccessType, accessTypeMap } from "@/domain/access";
|
||||
import { convertZulu2Beijing } from "@/lib/time";
|
||||
import { useConfig } from "@/providers/config";
|
||||
@@ -23,6 +26,10 @@ const Access = () => {
|
||||
const page = query.get("page");
|
||||
const pageNumber = page ? Number(page) : 1;
|
||||
|
||||
const tab = query.get("tab");
|
||||
|
||||
const accessGroupId = query.get("accessGroupId");
|
||||
|
||||
const startIndex = (pageNumber - 1) * perPage;
|
||||
const endIndex = startIndex + perPage;
|
||||
|
||||
@@ -31,95 +38,141 @@ const Access = () => {
|
||||
deleteAccess(rs.id);
|
||||
};
|
||||
|
||||
const handleTabItemClick = (tab: string) => {
|
||||
query.set("tab", tab);
|
||||
navigate({ search: query.toString() });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-muted-foreground">授权管理</div>
|
||||
<AccessEdit trigger={<Button>添加授权</Button>} op="add" />
|
||||
{tab != "access_group" ? (
|
||||
<AccessEdit trigger={<Button>添加授权</Button>} op="add" />
|
||||
) : (
|
||||
<AccessGroupEdit trigger={<Button>添加授权组</Button>} />
|
||||
)}
|
||||
</div>
|
||||
{accesses.length === 0 ? (
|
||||
<div className="flex flex-col items-center mt-10">
|
||||
<span className="bg-orange-100 p-5 rounded-full">
|
||||
<Key size={40} className="text-primary" />
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
请添加授权开始部署证书吧。
|
||||
</div>
|
||||
<AccessEdit
|
||||
trigger={<Button>添加授权</Button>}
|
||||
op="add"
|
||||
className="mt-3"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||
<div className="w-48">名称</div>
|
||||
<div className="w-48">服务商</div>
|
||||
|
||||
<div className="w-52">创建时间</div>
|
||||
<div className="w-52">更新时间</div>
|
||||
<div className="grow">操作</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
授权列表
|
||||
</div>
|
||||
{accesses.slice(startIndex, endIndex).map((access) => (
|
||||
<div
|
||||
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
|
||||
key={access.id}
|
||||
>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{access.name}
|
||||
</div>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center space-x-2">
|
||||
<img
|
||||
src={accessTypeMap.get(access.configType)?.[1]}
|
||||
className="w-6"
|
||||
/>
|
||||
<div>{accessTypeMap.get(access.configType)?.[0]}</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
|
||||
创建于 {access.created && convertZulu2Beijing(access.created)}
|
||||
</div>
|
||||
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
|
||||
更新于 {access.updated && convertZulu2Beijing(access.updated)}
|
||||
</div>
|
||||
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<Button variant={"link"} className="p-0">
|
||||
编辑
|
||||
</Button>
|
||||
}
|
||||
op="edit"
|
||||
data={access}
|
||||
/>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="p-0"
|
||||
onClick={() => {
|
||||
handleDelete(access);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<XPagination
|
||||
totalPages={totalPages}
|
||||
currentPage={pageNumber}
|
||||
onPageChange={(page) => {
|
||||
query.set("page", page.toString());
|
||||
navigate({ search: query.toString() });
|
||||
<Tabs
|
||||
defaultValue={tab ? tab : "access"}
|
||||
value={tab ? tab : "access"}
|
||||
className="w-full mt-5"
|
||||
>
|
||||
<TabsList className="space-x-5 px-3">
|
||||
<TabsTrigger
|
||||
value="access"
|
||||
onClick={() => {
|
||||
handleTabItemClick("access");
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
授权管理
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="access_group"
|
||||
onClick={() => {
|
||||
handleTabItemClick("access_group");
|
||||
}}
|
||||
>
|
||||
授权组管理
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="access">
|
||||
{accesses.length === 0 ? (
|
||||
<div className="flex flex-col items-center mt-10">
|
||||
<span className="bg-orange-100 p-5 rounded-full">
|
||||
<Key size={40} className="text-primary" />
|
||||
</span>
|
||||
|
||||
<div className="text-center text-sm text-muted-foreground mt-3">
|
||||
请添加授权开始部署证书吧。
|
||||
</div>
|
||||
<AccessEdit
|
||||
trigger={<Button>添加授权</Button>}
|
||||
op="add"
|
||||
className="mt-3"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
|
||||
<div className="w-48">名称</div>
|
||||
<div className="w-48">服务商</div>
|
||||
|
||||
<div className="w-52">创建时间</div>
|
||||
<div className="w-52">更新时间</div>
|
||||
<div className="grow">操作</div>
|
||||
</div>
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
授权列表
|
||||
</div>
|
||||
{accesses
|
||||
.filter((item) => {
|
||||
return accessGroupId ? item.group == accessGroupId : true;
|
||||
})
|
||||
.slice(startIndex, endIndex)
|
||||
.map((access) => (
|
||||
<div
|
||||
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
|
||||
key={access.id}
|
||||
>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
|
||||
{access.name}
|
||||
</div>
|
||||
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center space-x-2">
|
||||
<img
|
||||
src={accessTypeMap.get(access.configType)?.[1]}
|
||||
className="w-6"
|
||||
/>
|
||||
<div>{accessTypeMap.get(access.configType)?.[0]}</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
|
||||
创建于{" "}
|
||||
{access.created && convertZulu2Beijing(access.created)}
|
||||
</div>
|
||||
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
|
||||
更新于{" "}
|
||||
{access.updated && convertZulu2Beijing(access.updated)}
|
||||
</div>
|
||||
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<Button variant={"link"} className="p-0">
|
||||
编辑
|
||||
</Button>
|
||||
}
|
||||
op="edit"
|
||||
data={access}
|
||||
/>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="p-0"
|
||||
onClick={() => {
|
||||
handleDelete(access);
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<XPagination
|
||||
totalPages={totalPages}
|
||||
currentPage={pageNumber}
|
||||
onPageChange={(page) => {
|
||||
query.set("page", page.toString());
|
||||
navigate({ search: query.toString() });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="access_group">
|
||||
<AccessGroupList />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -171,7 +171,7 @@ const Dashboard = () => {
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigate("/");
|
||||
navigate("/edit");
|
||||
}}
|
||||
>
|
||||
添加域名
|
||||
|
||||
@@ -34,16 +34,23 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Plus } from "lucide-react";
|
||||
import { AccessEdit } from "@/components/certimate/AccessEdit";
|
||||
import { accessTypeMap } from "@/domain/access";
|
||||
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Edit = () => {
|
||||
const {
|
||||
config: { accesses },
|
||||
config: { accesses, emails, accessGroups },
|
||||
} = useConfig();
|
||||
|
||||
const [domain, setDomain] = useState<Domain>();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const [tab, setTab] = useState<"base" | "advance">("base");
|
||||
|
||||
const [targetType, setTargetType] = useState(domain ? domain.targetType : "");
|
||||
|
||||
useEffect(() => {
|
||||
// Parsing query parameters
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
@@ -52,6 +59,7 @@ const Edit = () => {
|
||||
const fetchData = async () => {
|
||||
const data = await get(id);
|
||||
setDomain(data);
|
||||
setTargetType(data.targetType);
|
||||
};
|
||||
fetchData();
|
||||
}
|
||||
@@ -62,15 +70,17 @@ const Edit = () => {
|
||||
domain: z.string().regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
|
||||
message: "请输入正确的域名",
|
||||
}),
|
||||
email: z.string().email().optional(),
|
||||
access: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||
message: "请选择DNS服务商授权配置",
|
||||
}),
|
||||
targetAccess: z.string().regex(/^[a-zA-Z0-9]+$/, {
|
||||
message: "请选择部署服务商配置",
|
||||
}),
|
||||
targetAccess: z.string().optional(),
|
||||
targetType: z.string().regex(/^[a-zA-Z0-9-]+$/, {
|
||||
message: "请选择部署服务类型",
|
||||
}),
|
||||
variables: z.string().optional(),
|
||||
group: z.string().optional(),
|
||||
nameservers: z.string().optional(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@@ -78,9 +88,13 @@ const Edit = () => {
|
||||
defaultValues: {
|
||||
id: "",
|
||||
domain: "",
|
||||
email: "",
|
||||
access: "",
|
||||
targetAccess: "",
|
||||
targetType: "",
|
||||
variables: "",
|
||||
group: "",
|
||||
nameservers: "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -89,20 +103,26 @@ const Edit = () => {
|
||||
form.reset({
|
||||
id: domain.id,
|
||||
domain: domain.domain,
|
||||
email: domain.email,
|
||||
access: domain.access,
|
||||
targetAccess: domain.targetAccess,
|
||||
targetType: domain.targetType,
|
||||
variables: domain.variables,
|
||||
group: domain.group,
|
||||
nameservers: domain.nameservers,
|
||||
});
|
||||
}
|
||||
}, [domain, form]);
|
||||
|
||||
const [targetType, setTargetType] = useState(domain ? domain.targetType : "");
|
||||
|
||||
const targetAccesses = accesses.filter((item) => {
|
||||
if (item.usage == "apply") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetType == "") {
|
||||
return true;
|
||||
}
|
||||
const types = form.getValues().targetType.split("-");
|
||||
const types = targetType.split("-");
|
||||
return item.configType === types[0];
|
||||
});
|
||||
|
||||
@@ -111,13 +131,32 @@ const Edit = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
const group = data.group == "emptyId" ? "" : data.group;
|
||||
const targetAccess =
|
||||
data.targetAccess === "emptyId" ? "" : data.targetAccess;
|
||||
if (group == "" && targetAccess == "") {
|
||||
form.setError("group", {
|
||||
type: "manual",
|
||||
message: "部署授权和部署授权组至少选一个",
|
||||
});
|
||||
form.setError("targetAccess", {
|
||||
type: "manual",
|
||||
message: "部署授权和部署授权组至少选一个",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const req: Domain = {
|
||||
id: data.id as string,
|
||||
crontab: "0 0 * * *",
|
||||
domain: data.domain,
|
||||
email: data.email,
|
||||
access: data.access,
|
||||
targetAccess: data.targetAccess,
|
||||
group: group,
|
||||
targetAccess: targetAccess,
|
||||
targetType: data.targetType,
|
||||
variables: data.variables,
|
||||
nameservers: data.nameservers,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -152,183 +191,360 @@ const Edit = () => {
|
||||
<>
|
||||
<div className="">
|
||||
<Toaster />
|
||||
<div className="border-b dark:border-stone-500 h-10 text-muted-foreground">
|
||||
<div className=" h-5 text-muted-foreground">
|
||||
{domain?.id ? "编辑" : "新增"}域名
|
||||
</div>
|
||||
<div className="max-w-[35em] mx-auto mt-10">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 dark:text-stone-200"
|
||||
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
|
||||
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex">
|
||||
<div
|
||||
className={cn(
|
||||
"cursor-pointer text-right",
|
||||
tab === "base" ? "text-primary" : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
setTab("base");
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="domain"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>域名</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入域名" {...field} />
|
||||
</FormControl>
|
||||
基础设置
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"cursor-pointer text-right",
|
||||
tab === "advance" ? "text-primary" : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
setTab("advance");
|
||||
}}
|
||||
>
|
||||
高级设置
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="w-full md:w-[35em] bg-gray-100 dark:bg-gray-900 p-5 rounded mt-3 md:mt-0">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8 dark:text-stone-200"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="domain"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel>域名</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="请输入域名" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="access"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>DNS 服务商授权配置</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
新增
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("access", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择授权配置" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>服务商授权配置</SelectLabel>
|
||||
{accesses.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>Email(申请证书需要提供邮箱)</div>
|
||||
<EmailsEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
新增
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("email", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择邮箱" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>邮箱列表</SelectLabel>
|
||||
{emails.content.emails.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
<div>{item}</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="access"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="flex w-full justify-between">
|
||||
<div>DNS 服务商授权配置</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
新增
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("access", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择授权配置" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>服务商授权配置</SelectLabel>
|
||||
{accesses
|
||||
.filter((item) => item.usage != "deploy")
|
||||
.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={
|
||||
accessTypeMap.get(
|
||||
item.configType
|
||||
)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetType"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel>部署服务类型</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
setTargetType(value);
|
||||
form.setValue("targetType", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择部署服务类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>部署服务类型</SelectLabel>
|
||||
{targetTypeKeys.map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={targetTypeMap.get(key)?.[1]}
|
||||
/>
|
||||
<div>{targetTypeMap.get(key)?.[0]}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetAccess"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "base"}>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>部署服务商授权配置</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
新增
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("targetAccess", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择授权配置" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>
|
||||
服务商授权配置{form.getValues().targetAccess}
|
||||
</SelectLabel>
|
||||
<SelectItem value="emptyId">
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={
|
||||
accessTypeMap.get(item.configType)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
--
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
{targetAccesses.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={
|
||||
accessTypeMap.get(item.configType)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetType"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>部署服务类型</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
setTargetType(value);
|
||||
form.setValue("targetType", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择部署服务类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>部署服务类型</SelectLabel>
|
||||
{targetTypeKeys.map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={targetTypeMap.get(key)?.[1]}
|
||||
/>
|
||||
<div>{targetTypeMap.get(key)?.[0]}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="group"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance" || targetType != "ssh"}>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>
|
||||
部署配置组(用于将一个域名证书部署到多个 ssh 主机)
|
||||
</div>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
value={field.value}
|
||||
defaultValue="emptyId"
|
||||
onValueChange={(value) => {
|
||||
form.setValue("group", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择分组" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="emptyId">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 rounded cursor-pointer"
|
||||
)}
|
||||
>
|
||||
--
|
||||
</div>
|
||||
</SelectItem>
|
||||
{accessGroups
|
||||
.filter((item) => {
|
||||
return (
|
||||
item.expand && item.expand?.access.length > 0
|
||||
);
|
||||
})
|
||||
.map((item) => (
|
||||
<SelectItem
|
||||
value={item.id ? item.id : ""}
|
||||
key={item.id}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center space-x-2 rounded cursor-pointer"
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="variables"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance"}>
|
||||
<FormLabel>变量</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={`可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;`}
|
||||
{...field}
|
||||
className="placeholder:whitespace-pre-wrap"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetAccess"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="w-full flex justify-between">
|
||||
<div>部署服务商授权配置</div>
|
||||
<AccessEdit
|
||||
trigger={
|
||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
||||
<Plus size={14} />
|
||||
新增
|
||||
</div>
|
||||
}
|
||||
op="add"
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue("targetAccess", value);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择授权配置" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>服务商授权配置</SelectLabel>
|
||||
{targetAccesses.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
className="w-6"
|
||||
src={
|
||||
accessTypeMap.get(item.configType)?.[1]
|
||||
}
|
||||
/>
|
||||
<div>{item.name}</div>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nameservers"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden={tab != "advance"}>
|
||||
<FormLabel>域名服务器</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={`自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;`}
|
||||
{...field}
|
||||
className="placeholder:whitespace-pre-wrap"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">保存</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">保存</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -149,6 +149,10 @@ const Home = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleForceClick = async (domain: Domain) => {
|
||||
await handleRightNowClick({ ...domain, deployed: false });
|
||||
};
|
||||
|
||||
const handleDownloadClick = async (domain: Domain) => {
|
||||
const zipName = `${domain.id}-${domain.domain}.zip`;
|
||||
const files: CustomFile[] = [
|
||||
@@ -291,6 +295,23 @@ const Home = () => {
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show
|
||||
when={
|
||||
(domain.enabled ? true : false) && domain.deployed
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
<Button
|
||||
variant={"link"}
|
||||
className="p-0"
|
||||
onClick={() => handleForceClick(domain)}
|
||||
>
|
||||
强行部署
|
||||
</Button>
|
||||
</Show>
|
||||
|
||||
<Show when={domain.expiredAt ? true : false}>
|
||||
<Separator orientation="vertical" className="h-4 mx-2" />
|
||||
<Button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Access } from "@/domain/access";
|
||||
import { list } from "@/repository/access";
|
||||
|
||||
import { list as getAccessGroups } from "@/repository/access_group";
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
@@ -10,9 +10,14 @@ import {
|
||||
useReducer,
|
||||
} from "react";
|
||||
import { configReducer } from "./reducer";
|
||||
import { getEmails } from "@/repository/settings";
|
||||
import { Setting } from "@/domain/settings";
|
||||
import { AccessGroup } from "@/domain/access_groups";
|
||||
|
||||
export type ConfigData = {
|
||||
accesses: Access[];
|
||||
emails: Setting;
|
||||
accessGroups: AccessGroup[];
|
||||
};
|
||||
|
||||
export type ConfigContext = {
|
||||
@@ -20,6 +25,9 @@ export type ConfigContext = {
|
||||
deleteAccess: (id: string) => void;
|
||||
addAccess: (access: Access) => void;
|
||||
updateAccess: (access: Access) => void;
|
||||
setEmails: (email: Setting) => void;
|
||||
setAccessGroups: (accessGroups: AccessGroup[]) => void;
|
||||
reloadAccessGroups: () => void;
|
||||
};
|
||||
|
||||
const Context = createContext({} as ConfigContext);
|
||||
@@ -31,7 +39,11 @@ interface ContainerProps {
|
||||
}
|
||||
|
||||
export const ConfigProvider = ({ children }: ContainerProps) => {
|
||||
const [config, dispatchConfig] = useReducer(configReducer, { accesses: [] });
|
||||
const [config, dispatchConfig] = useReducer(configReducer, {
|
||||
accesses: [],
|
||||
emails: { content: { emails: [] } },
|
||||
accessGroups: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const featchData = async () => {
|
||||
@@ -41,6 +53,31 @@ export const ConfigProvider = ({ children }: ContainerProps) => {
|
||||
featchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const featchEmails = async () => {
|
||||
const emails = await getEmails();
|
||||
dispatchConfig({ type: "SET_EMAILS", payload: emails });
|
||||
};
|
||||
featchEmails();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const featchAccessGroups = async () => {
|
||||
const accessGroups = await getAccessGroups();
|
||||
dispatchConfig({ type: "SET_ACCESS_GROUPS", payload: accessGroups });
|
||||
};
|
||||
featchAccessGroups();
|
||||
}, []);
|
||||
|
||||
const reloadAccessGroups = useCallback(async () => {
|
||||
const accessGroups = await getAccessGroups();
|
||||
dispatchConfig({ type: "SET_ACCESS_GROUPS", payload: accessGroups });
|
||||
}, []);
|
||||
|
||||
const setEmails = useCallback((emails: Setting) => {
|
||||
dispatchConfig({ type: "SET_EMAILS", payload: emails });
|
||||
}, []);
|
||||
|
||||
const deleteAccess = useCallback((id: string) => {
|
||||
dispatchConfig({ type: "DELETE_ACCESS", payload: id });
|
||||
}, []);
|
||||
@@ -53,15 +90,24 @@ export const ConfigProvider = ({ children }: ContainerProps) => {
|
||||
dispatchConfig({ type: "UPDATE_ACCESS", payload: access });
|
||||
}, []);
|
||||
|
||||
const setAccessGroups = useCallback((accessGroups: AccessGroup[]) => {
|
||||
dispatchConfig({ type: "SET_ACCESS_GROUPS", payload: accessGroups });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
config: {
|
||||
accesses: config.accesses,
|
||||
emails: config.emails,
|
||||
accessGroups: config.accessGroups,
|
||||
},
|
||||
deleteAccess,
|
||||
addAccess,
|
||||
setEmails,
|
||||
updateAccess,
|
||||
setAccessGroups,
|
||||
reloadAccessGroups,
|
||||
}}
|
||||
>
|
||||
{children && children}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Access } from "@/domain/access";
|
||||
import { ConfigData } from ".";
|
||||
import { Setting } from "@/domain/settings";
|
||||
import { AccessGroup } from "@/domain/access_groups";
|
||||
|
||||
type Action =
|
||||
| { type: "ADD_ACCESS"; payload: Access }
|
||||
| { type: "DELETE_ACCESS"; payload: string }
|
||||
| { type: "UPDATE_ACCESS"; payload: Access }
|
||||
| { type: "SET_ACCESSES"; payload: Access[] };
|
||||
| { type: "SET_ACCESSES"; payload: Access[] }
|
||||
| { type: "SET_EMAILS"; payload: Setting }
|
||||
| { type: "ADD_EMAIL"; payload: string }
|
||||
| { type: "SET_ACCESS_GROUPS"; payload: AccessGroup[] };
|
||||
|
||||
export const configReducer = (
|
||||
state: ConfigData,
|
||||
@@ -40,6 +45,29 @@ export const configReducer = (
|
||||
),
|
||||
};
|
||||
}
|
||||
case "SET_EMAILS": {
|
||||
return {
|
||||
...state,
|
||||
emails: action.payload,
|
||||
};
|
||||
}
|
||||
case "ADD_EMAIL": {
|
||||
return {
|
||||
...state,
|
||||
emails: {
|
||||
...state.emails,
|
||||
content: {
|
||||
emails: [...state.emails.content.emails, action.payload],
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_ACCESS_GROUPS": {
|
||||
return {
|
||||
...state,
|
||||
accessGroups: action.payload,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
49
ui/src/repository/access_group.ts
Normal file
49
ui/src/repository/access_group.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { AccessGroup } from "@/domain/access_groups";
|
||||
import { getPb } from "./api";
|
||||
import { Access } from "@/domain/access";
|
||||
|
||||
export const list = async () => {
|
||||
const resp = await getPb()
|
||||
.collection("access_groups")
|
||||
.getFullList<AccessGroup>({
|
||||
sort: "-created",
|
||||
expand: "access",
|
||||
});
|
||||
|
||||
return resp;
|
||||
};
|
||||
|
||||
export const remove = async (id: string) => {
|
||||
const pb = getPb();
|
||||
|
||||
// 查询有没有关联的access
|
||||
const accessGroup = await pb.collection("access").getList<Access>(1, 1, {
|
||||
filter: `group='${id}' && deleted=null`,
|
||||
});
|
||||
|
||||
if (accessGroup.items.length > 0) {
|
||||
throw new Error("该分组下有授权配置,无法删除");
|
||||
}
|
||||
|
||||
await pb.collection("access_groups").delete(id);
|
||||
};
|
||||
|
||||
export const update = async (accessGroup: AccessGroup) => {
|
||||
const pb = getPb();
|
||||
if (accessGroup.id) {
|
||||
return await pb
|
||||
.collection("access_groups")
|
||||
.update(accessGroup.id, accessGroup);
|
||||
}
|
||||
return await pb.collection("access_groups").create(accessGroup);
|
||||
};
|
||||
|
||||
type UpdateByIdReq = {
|
||||
id: string;
|
||||
[key: string]: string | string[];
|
||||
};
|
||||
|
||||
export const updateById = async (req: UpdateByIdReq) => {
|
||||
const pb = getPb();
|
||||
return await pb.collection("access_groups").update(req.id, req);
|
||||
};
|
||||
26
ui/src/repository/settings.ts
Normal file
26
ui/src/repository/settings.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Setting } from "@/domain/settings";
|
||||
import { getPb } from "./api";
|
||||
|
||||
export const getEmails = async () => {
|
||||
try {
|
||||
const resp = await getPb()
|
||||
.collection("settings")
|
||||
.getFirstListItem<Setting>("name='emails'");
|
||||
return resp;
|
||||
} catch (e) {
|
||||
return {
|
||||
content: { emails: [] },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const update = async (setting: Setting) => {
|
||||
const pb = getPb();
|
||||
let resp: Setting;
|
||||
if (setting.id) {
|
||||
resp = await pb.collection("settings").update(setting.id, setting);
|
||||
} else {
|
||||
resp = await pb.collection("settings").create(setting);
|
||||
}
|
||||
return resp;
|
||||
};
|
||||
Reference in New Issue
Block a user