Compare commits

...

99 Commits

Author SHA1 Message Date
yoan
bff18a7be7 update vite plugin to preserve special file 2024-10-23 08:36:25 +08:00
yoan
bac00491fe v0.2.7 2024-10-23 07:56:29 +08:00
usual2970
f8da3ded0d Merge pull request #242 from LeoChen98/feat-tencent-cos-support
feat: add tencent cos deploy support
2024-10-23 07:55:03 +08:00
Leo Chen
b01849eb0c update readme for new feat. 2024-10-22 22:32:35 +08:00
Leo Chen
c9eb487953 fix ui-deployer route 2024-10-22 22:18:52 +08:00
yoan
dc383644d6 update make file 2024-10-22 22:13:01 +08:00
Leo Chen
a8f718afa0 add tencent-cos ui
表单主要从OSS表单修改
2024-10-22 21:56:26 +08:00
Leo Chen
cd76d170b2 fix region of cos 2024-10-22 21:55:49 +08:00
Leo Chen
7b129c11e9 Merge branch 'usual2970:main' into feat-tencent-cos-support 2024-10-22 21:22:45 +08:00
Leo Chen
f7972d5b68 remove unused imports 2024-10-22 21:19:20 +08:00
usual2970
b1a0d84033 Merge pull request #227 from fudiwei/feat/cloud-load-balance
feat: cloud load balance pre-works
2024-10-22 21:18:46 +08:00
usual2970
969fba8a57 Merge branch 'main' into feat/cloud-load-balance 2024-10-22 21:13:59 +08:00
Leo Chen
63865b5fbd fix ResourceType 2024-10-22 21:11:49 +08:00
Leo Chen
46c32f15e3 feat: add tencent cos deploy support
新增腾讯云COS配置支持
2024-10-22 21:07:58 +08:00
usual2970
5f62c887c0 Merge pull request #236 from liburdi/docs/upd_readme
feat: reamdme里面的源码启动命令已经失效,因为ui/dist不在代码仓库中管理
2024-10-22 20:11:29 +08:00
usual2970
c85beaa52b Merge pull request #238 from usual2970/feat/qiniu_wildcard
Qiniu cdn supports wildcard domain deployment
2024-10-22 20:08:40 +08:00
Fu Diwei
885cdfaec9 fix: fix repeat certificates judgement logical in tencentcloud ssl uploader 2024-10-22 18:39:42 +08:00
Fu Diwei
011130432c refactor: clean code 2024-10-22 18:06:56 +08:00
Fu Diwei
062d66222a refactor: divide DeployList 2024-10-22 17:44:39 +08:00
Fu Diwei
e53749e16e refactor: clean code 2024-10-22 15:58:58 +08:00
yoan
dbfb84ec6d qiniu cdn supports wildcard domain deployment 2024-10-22 15:41:44 +08:00
liburdi
265842feeb feat: 根据输入的参数,显示不同的内容 2024-10-22 13:52:33 +08:00
liburdi
0c35928eee feat: 启动时终端打印 2024-10-22 12:26:08 +08:00
liburdi
ea4bcb4aaf feat: reamdme里面的源码启动命令已经失效,因为ui/dist不在代码仓库中管理 2024-10-22 12:17:43 +08:00
Fu Diwei
716f5f1426 refactor: clean code 2024-10-22 11:38:55 +08:00
yoan
4e86c1eb45 v0.2.6 2024-10-22 08:26:27 +08:00
yoan
0576a8bec3 Merge branch 'main' of github.com:usual2970/certimate 2024-10-22 07:32:13 +08:00
yoan
97f334b5ab v0.2.6 2024-10-22 07:31:47 +08:00
Fu Diwei
18a7bf0d66 feat: set default region when applying certificates by huaweicloud 2024-10-21 15:10:14 +08:00
Fu Diwei
908d33f186 refactor: clean code 2024-10-21 15:07:09 +08:00
Fu Diwei
68b9171390 Merge branch 'main' into feat/cloud-load-balance 2024-10-21 14:55:19 +08:00
Fu Diwei
45005a5073 refactor: clean code 2024-10-21 14:53:43 +08:00
usual2970
028eb088a5 Merge pull request #229 from PBK-B/feat_disable_cname
feat: 支持配置 CNAME 认证跟随禁用
2024-10-21 13:53:21 +08:00
PBK-B
8f98664665 fix: handle i18n label naming conventions 2024-10-21 09:18:41 +08:00
PBK-B
699385a8c4 fix: adjust the string conversion syntax 2024-10-21 09:18:41 +08:00
PBK-B
64b7ed00f5 feat: docking disableFollowCNAME config item function #228 2024-10-21 09:18:41 +08:00
PBK-B
2c75d2bfde feat: domain config add disable follow CNAME #228 2024-10-21 09:18:41 +08:00
Fu Diwei
9c41b0e357 refactor: clean code 2024-10-21 09:15:36 +08:00
yoan
ec6f10053a Merge branch 'main' of github.com:usual2970/certimate 2024-10-21 07:34:47 +08:00
yoan
0095600615 v0.2.5 2024-10-21 07:34:33 +08:00
Fu Diwei
b031f00764 feat: add aliyun cas uploader 2024-10-21 00:35:16 +08:00
Fu Diwei
a4fc8dfc56 feat: add tencentcloud ssl uploader 2024-10-20 23:53:10 +08:00
Fu Diwei
f168bd903d feat: add huaweicloud elb uploader 2024-10-20 21:33:08 +08:00
Fu Diwei
fc55e37454 chore: git keep /ui/dist 2024-10-20 21:09:28 +08:00
usual2970
84e2fd4f5c Merge pull request #225 from PBK-B/fix_dcdn_wildcard
fix: handle aliyun dcdn does not support wildcard domain #223
2024-10-20 21:04:08 +08:00
usual2970
0037659462 Merge pull request #224 from usual2970/feat/version-improve
Document location adjustment
2024-10-20 21:00:23 +08:00
usual2970
364289894e Merge pull request #222 from usual2970/feat/cert-leftday
Add remaining days
2024-10-20 21:00:03 +08:00
Fu Diwei
f6a3f4edfa refactor: optimize code 2024-10-20 20:42:13 +08:00
Fu Diwei
560d21c854 refactor: optimize code 2024-10-20 20:10:07 +08:00
yoan
4719f99155 Document location adjustment 2024-10-20 17:56:23 +08:00
Fu Diwei
3a213dc9c3 feat: do not use region from access when deploy to huaweicloud cdn 2024-10-20 17:51:36 +08:00
PBK-B
0d07c7c234 fix: handle aliyun dcdn does not support wildcard domain #223 2024-10-20 17:49:44 +08:00
yoan
21670f64d1 Document location adjustment 2024-10-20 17:44:54 +08:00
Fu Diwei
f0e7fe695d clean code 2024-10-20 17:24:23 +08:00
yoan
2bab727569 add remaining days 2024-10-20 17:15:20 +08:00
Fu Diwei
8d41a9aae7 Merge branch 'main' into feat/cloud-load-balance 2024-10-20 16:48:18 +08:00
Fu Diwei
896b5d3a13 Merge branch 'main' into feat/cloud-load-balance 2024-10-20 16:48:06 +08:00
Fu Diwei
88e64717cd feat: support using scm service on deployment to huaweicloud cdn 2024-10-20 16:42:05 +08:00
yoan
2d275a14ab update ISSUE template 2024-10-20 13:43:36 +08:00
yoan
1b796cffd1 fix httpreq and powerdns timeout 2024-10-20 13:37:28 +08:00
usual2970
f53e54c8de Merge pull request #220 from usual2970/feat/update_gomod
update package name
2024-10-20 13:26:53 +08:00
yoan
a9b9be96cb fix conflict 2024-10-20 13:26:25 +08:00
yoan
1033885c99 Merge branch 'zzci-main' 2024-10-20 13:01:53 +08:00
yoan
c45ad3c901 update actions,fix something 2024-10-20 13:00:27 +08:00
Roy
24192b61c1 Merge branch 'main' into main 2024-10-20 08:33:33 +07:00
Roy
1562e92e74 merge source 2024-10-20 09:31:20 +08:00
usual2970
17f72eb9cb Merge pull request #218 from fudiwei/feat/huaweicloud-cdn
feat: huaweicloud cdn
2024-10-20 06:30:48 +08:00
Roy
57ae6d5b40 add ui/dist to .gitignore. change dockerfile to build front. 2024-10-20 04:51:06 +08:00
Roy
467e4c4634 add powerdns,http request apply. 2024-10-19 22:46:37 +08:00
Roy
d6d296b546 add powerdns,http request apply. 2024-10-19 22:46:15 +08:00
yoan
499bbe4fa7 fix deployment dialog title 2024-10-19 22:23:56 +08:00
Fu Diwei
ae814766f3 Merge branch 'main' into feat/huaweicloud-cdn 2024-10-19 21:15:32 +08:00
yoan
17cfeee374 update package name 2024-10-19 21:15:01 +08:00
RHQYZ
efa394e9bd Merge branch 'usual2970:main' into main 2024-10-19 21:14:05 +08:00
RHQYZ
94ca0f27bf Merge branch 'main' into feat/huaweicloud-cdn 2024-10-19 18:49:48 +08:00
Fu Diwei
e08df5e6d8 refactor: fix typo 2024-10-19 18:46:27 +08:00
Fu Diwei
952c6ef73d feat: add huaweicloud cdn deployer 2024-10-19 18:46:02 +08:00
usual2970
b9902c926f Merge pull request #217 from usual2970/feat/push_test_msg
Notify push test
2024-10-19 18:15:16 +08:00
yoan
7fa6ea1797 Notify push test 2024-10-19 18:12:45 +08:00
Fu Diwei
be3cdbf585 refactor 2024-10-19 17:10:42 +08:00
Fu Diwei
6225969d4c refactor 2024-10-19 10:02:31 +08:00
yoan
4382474449 v0.2.4 2024-10-19 08:35:24 +08:00
yoan
678ef9c232 Merge branch 'fudiwei-feat/k8s' 2024-10-19 08:34:09 +08:00
yoan
3d535320b9 fix dependency 2024-10-19 08:31:03 +08:00
Fu Diwei
77d3e40ffb Merge branch 'feat/k8s' of https://github.com/fudiwei/certimate into feat/k8s 2024-10-18 19:58:58 +08:00
Fu Diwei
5dca64d3d3 refactor: clean code 2024-10-18 19:58:15 +08:00
RHQYZ
02d582b564 Merge branch 'main' into feat/k8s 2024-10-18 18:03:54 +08:00
Fu Diwei
8e906cbf23 feat: add k8s secret deployer 2024-10-18 17:56:27 +08:00
Fu Diwei
411b7bbfe2 feat: add k8s provider
commit
2024-10-18 17:54:53 +08:00
Fu Diwei
3093fc6b02 refactor
refactor

refactor
2024-10-18 17:54:53 +08:00
yoan
3c4b7d251a v0.2.3 2024-10-18 17:54:52 +08:00
Fu Diwei
f87a1be192 feat: add aws route53 provider 2024-10-18 17:54:01 +08:00
yoan
9b91cbd67e v0.2.3 2024-10-18 06:49:16 +08:00
usual2970
9b5e1052a1 Merge pull request #210 from fudiwei/feat/aws
feat: add aws provider
2024-10-18 06:47:52 +08:00
Fu Diwei
0d47d7cfd0 Merge branch 'main' into feat/aws 2024-10-17 18:27:01 +08:00
Fu Diwei
ef87975c80 feat: add aws route53 provider 2024-10-17 18:22:23 +08:00
yoan
9db757fbbb mod tidy 2024-10-17 17:57:04 +08:00
usual2970
f6ef305441 Merge pull request #208 from g1335333249/main
通知方式支持飞书
2024-10-17 17:55:52 +08:00
g1335333249
004c6a8506 Notify Add Lark 2024-10-17 12:39:18 +08:00
155 changed files with 6771 additions and 2769 deletions

View File

@@ -7,7 +7,7 @@ assignees: ""
---
**描述问题**
简要描述问题是什么
简要描述问题是什么1 个 ISSUE 只描述一个问题。
**复现步骤**
复现该问题的步骤:

View File

@@ -7,7 +7,7 @@ assignees: ""
---
**功能描述**
简要描述你希望添加的功能和相关问题。
简要描述你希望添加的功能和相关问题1 个 ISSUE 只描述一个功能
**动机**
为什么这个功能对项目有帮助?

View File

@@ -52,7 +52,8 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile_build
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -14,18 +14,18 @@ jobs:
with:
fetch-depth: 0
# - name: Set up Node.js
# uses: actions/setup-node@v4
# with:
# node-version: 20.11.0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.11.0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ">=1.22.5"
# - name: Build Admin dashboard UI
# run: npm --prefix=./ui ci && npm --prefix=./ui run build
- name: Build Admin dashboard UI
run: npm --prefix=./ui ci && npm --prefix=./ui run build
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3

5
.gitignore vendored
View File

@@ -9,13 +9,14 @@
*.njsproj
*.sln
*.sw?
__debug_bin*
vendor
pb_data
build
main
/ui/dist/*
!/ui/dist/.gitkeep
./dist
./certimate
/docker/data

33
Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
FROM node:20-alpine3.19 AS front-builder
WORKDIR /app
COPY . /app/
RUN \
cd /app/ui && \
npm install && \
npm run build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY ../. /app/
RUN rm -rf /app/ui/dist
COPY --from=front-builder /app/ui/dist /app/ui/dist
RUN go build -o certimate
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/certimate .
ENTRYPOINT ["./certimate", "serve", "--http", "0.0.0.0:8090"]

View File

@@ -1,16 +0,0 @@
FROM golang:1.22-alpine as builder
WORKDIR /app
COPY ../. /app/
RUN go build -o certimate
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/certimate .
ENTRYPOINT ["./certimate", "serve", "--http", "0.0.0.0:8090"]

View File

@@ -35,3 +35,6 @@ help:
@echo " make help - 显示此帮助信息"
.PHONY: all build clean help
local.run:
go mod vendor&& npm --prefix=./ui install && npm --prefix=./ui run build && go run main.go serve --http 127.0.0.1:8090

View File

@@ -55,8 +55,7 @@ mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubuserconten
```bash
git clone EMAIL:usual2970/certimate.git
cd certimate
go mod vendor
go run main.go serve
make local.run
```
## 二、使用
@@ -75,15 +74,19 @@ go run main.go serve
| 服务商 | 支持申请证书 | 支持部署证书 | 备注 |
| :--------: | :----------: | :----------: | ------------------------------------------------------------ |
| 阿里云 | √ | √ | 可签发在阿里云注册的域名;可部署到阿里云 OSS、CDN |
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN |
| 华为云 | √ | | 可签发在华为云注册的域名 |
| 腾讯云 | √ | √ | 可签发在腾讯云注册的域名;可部署到腾讯云 CDN、COS |
| 华为云 | √ | | 可签发在华为云注册的域名;可部署到华为云 CDN |
| 七牛云 | | √ | 可部署到七牛云 CDN |
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名CloudFlare 服务自带 SSL 证书 |
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
| Namesilo | √ | | 可签发在 Namesilo 注册的域名 |
| PowerDNS | √ | | 可签发在 PowerDNS 托管的域名 |
| HTTP 请求 | √ | | 可签发允许通过 HTTP 请求修改 DNS 的域名 |
| 本地部署 | | √ | 可部署到本地服务器 |
| SSH | | √ | 可部署到 SSH 服务器 |
| Webhook | | √ | 可部署时回调到 Webhook |
| Kubernetes | | √ | 可部署到 Kubernetes Secret |
## 四、系统截图

View File

@@ -54,8 +54,7 @@ mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubuserconten
```bash
git clone EMAIL:usual2970/certimate.git
cd certimate
go mod vendor
go run main.go serve
make local.run
```
## Usage
@@ -74,15 +73,19 @@ password1234567890
| Provider | Registration | Deployment | Remarks |
| :-----------: | :----------: | :--------: | ------------------------------------------------------------------------------------------- |
| Alibaba Cloud | √ | √ | Supports domains registered on Alibaba Cloud; supports deployment to Alibaba Cloud OSS, CDN |
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN |
| Huawei Cloud | √ | | Supports domains registered on Huawei Cloud |
| Tencent Cloud | √ | √ | Supports domains registered on Tencent Cloud; supports deployment to Tencent Cloud CDN, COS |
| Huawei Cloud | √ | | Supports domains registered on Huawei Cloud; supports deployment to Huawei Cloud CDN |
| Qiniu Cloud | | √ | Supports deployment to Qiniu Cloud CDN |
| AWS | √ | | Supports domains managed on AWS Route53 |
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
| GoDaddy | √ | | Supports domains registered on GoDaddy |
| Namesilo | √ | | Supports domains registered on Namesilo |
| PowerDNS | √ | | Supports domains managed on PowerDNS |
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
| Local Deploy | | √ | Supports deployment to local servers |
| SSH | | √ | Supports deployment to SSH servers |
| Webhook | | √ | Supports callback to Webhook |
| Kubernetes | | √ | Supports deployment to Kubernetes Secret |
## Screenshots

42
go.mod
View File

@@ -1,17 +1,19 @@
module certimate
module github.com/usual2970/certimate
go 1.22.0
toolchain go1.23.2
require (
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/go-acme/lego/v4 v4.19.2
github.com/gojek/heimdall/v7 v7.0.3
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/nikoksr/notify v1.0.0
github.com/pkg/sftp v1.13.6
@@ -22,6 +24,8 @@ require (
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
golang.org/x/crypto v0.27.0
k8s.io/apimachinery v0.31.1
k8s.io/client-go v0.31.1
)
require (
@@ -29,26 +33,52 @@ require (
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/aws/aws-sdk-go-v2/service/route53 v1.43.2 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-lark/lark v1.14.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // 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/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2
github.com/alibabacloud-go/debug v1.0.0 // indirect
github.com/alibabacloud-go/debug v1.0.1 // 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.5 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 // indirect
github.com/aliyun/credentials-go v1.3.1 // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect

105
go.sum
View File

@@ -29,19 +29,36 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8=
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g=
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1 h1:kAxd9IkdMaIX9aoBRA34q9WXKnkKTucil/zUlG4/3vo=
github.com/alibabacloud-go/cas-20200407/v3 v3.0.1/go.mod h1:gElMYWcjdjKgqq9/2YxE6BIUMs10ZNGM4PRiRlDXgBs=
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-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY=
github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE=
github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8=
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
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-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
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/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/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg=
github.com/alibabacloud-go/debug v1.0.1/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=
@@ -53,9 +70,11 @@ github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9Q
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.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
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=
@@ -82,8 +101,10 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.63.15/go.mod h1:SOSDHfe1kX91v3W5QiBsWS
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@@ -117,6 +138,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsd
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY=
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2 h1:957e1/SwXIfPi/0OUJkH9YnPZRe9G6Kisd/xUhF7AUE=
github.com/aws/aws-sdk-go-v2/service/route53 v1.43.2/go.mod h1:343vcjcyOTuHTBBgUrOxPM36/jE96qLZnGL447ldrB0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc=
@@ -157,6 +180,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elastic/go-sysinfo v1.0.2/go.mod h1:O/D5m1VpYLwGjCYzEt63g3Z1uO3jXfwyzzjiW90t8cY=
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -168,6 +193,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
@@ -178,10 +205,19 @@ github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-lark/lark v1.14.1 h1:qWYQTk6wLwf/08u8WbdNAHNmfqavdOvmsENlQ+Cb8aY=
github.com/go-lark/lark v1.14.1/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -193,11 +229,15 @@ github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPF
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/gojek/heimdall/v7 v7.0.3 h1:+5sAhl8S0m+qRRL8IVeHCJudFh/XkG3wyO++nvOg+gc=
github.com/gojek/heimdall/v7 v7.0.3/go.mod h1:Z43HtMid7ysSjmsedPTXAki6jcdcNVnjn5pmsTyiMic=
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf h1:5xRGbUdOmZKoDXkGx5evVLehuCMpuO1hl701bEQqXOM=
@@ -226,6 +266,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -234,12 +276,15 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 h1:e+8XbKB6IMn8A4OAyZccO4pYfB3s7bt6azNIPE7AnPg=
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
@@ -263,6 +308,8 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4Dvx
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 h1:X3E16S6AUZsQKhJIQ5kNnylnp0GtSy2YhIbxfvDavtU=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -271,8 +318,12 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -280,6 +331,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -296,6 +349,8 @@ github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQ
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f/go.mod h1:aEt7p9Rvh67BYApmZwNDPpgircTO2kgdmDUoF/1QmwA=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -319,6 +374,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@@ -326,6 +383,10 @@ github.com/nikoksr/notify v1.0.0 h1:qe9/6FRsWdxBgQgWcpvQ0sv8LRGJZDpRB4TkL2uNdO8=
github.com/nikoksr/notify v1.0.0/go.mod h1:hPaaDt30d6LAA7/5nb0e48Bp/MctDfycCSs8VEgN29I=
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -352,8 +413,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
@@ -405,12 +466,15 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
@@ -444,6 +508,7 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45
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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
@@ -465,6 +530,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -480,6 +546,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -492,6 +559,7 @@ 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -503,6 +571,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
@@ -530,6 +599,7 @@ 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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
@@ -542,6 +612,7 @@ 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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
@@ -574,6 +645,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
@@ -627,6 +700,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -643,6 +718,18 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
@@ -670,3 +757,9 @@ modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -7,7 +7,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/alidns"
"certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain"
)
type aliyun struct {

View File

@@ -7,8 +7,13 @@ import (
"crypto/rand"
"errors"
"fmt"
"os"
"strconv"
"strings"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
@@ -16,18 +21,18 @@ import (
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/pocketbase/pocketbase/models"
"certimate/internal/domain"
"certimate/internal/utils/app"
)
const (
configTypeAliyun = "aliyun"
configTypeTencent = "tencent"
configTypeHuaweicloud = "huaweicloud"
configTypeHuaweiCloud = "huaweicloud"
configTypeAws = "aws"
configTypeCloudflare = "cloudflare"
configTypeNamesilo = "namesilo"
configTypeGodaddy = "godaddy"
configTypePdns = "pdns"
configTypeHttpreq = "httpreq"
)
const defaultSSLProvider = "letsencrypt"
@@ -60,12 +65,13 @@ type Certificate struct {
}
type ApplyOption struct {
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
KeyAlgorithm string `json:"keyAlgorithm"`
Nameservers string `json:"nameservers"`
Timeout int64 `json:"timeout"`
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
KeyAlgorithm string `json:"keyAlgorithm"`
Nameservers string `json:"nameservers"`
Timeout int64 `json:"timeout"`
DisableFollowCNAME bool `json:"disableFollowCNAME"`
}
type ApplyUser struct {
@@ -112,12 +118,13 @@ func Get(record *models.Record) (Applicant, error) {
}
option := &ApplyOption{
Email: applyConfig.Email,
Domain: record.GetString("domain"),
Access: access.GetString("config"),
KeyAlgorithm: applyConfig.KeyAlgorithm,
Nameservers: applyConfig.Nameservers,
Timeout: applyConfig.Timeout,
Email: applyConfig.Email,
Domain: record.GetString("domain"),
Access: access.GetString("config"),
KeyAlgorithm: applyConfig.KeyAlgorithm,
Nameservers: applyConfig.Nameservers,
Timeout: applyConfig.Timeout,
DisableFollowCNAME: applyConfig.DisableFollowCNAME,
}
switch access.GetString("configType") {
@@ -125,14 +132,20 @@ func Get(record *models.Record) (Applicant, error) {
return NewAliyun(option), nil
case configTypeTencent:
return NewTencent(option), nil
case configTypeHuaweicloud:
case configTypeHuaweiCloud:
return NewHuaweiCloud(option), nil
case configTypeAws:
return NewAws(option), nil
case configTypeCloudflare:
return NewCloudflare(option), nil
case configTypeNamesilo:
return NewNamesilo(option), nil
case configTypeGodaddy:
return NewGodaddy(option), nil
case configTypePdns:
return NewPdns(option), nil
case configTypeHttpreq:
return NewHttpreq(option), nil
default:
return nil, errors.New("unknown config type")
}
@@ -168,6 +181,10 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
return nil, err
}
// Some unified lego environment variables are configured here.
// link: https://github.com/go-acme/lego/issues/1867
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME))
myUser := ApplyUser{
Email: option.Email,
key: privateKey,

39
internal/applicant/aws.go Normal file
View File

@@ -0,0 +1,39 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/route53"
"github.com/usual2970/certimate/internal/domain"
)
type aws struct {
option *ApplyOption
}
func NewAws(option *ApplyOption) Applicant {
return &aws{
option: option,
}
}
func (t *aws) Apply() (*Certificate, error) {
access := &domain.AwsAccess{}
json.Unmarshal([]byte(t.option.Access), access)
os.Setenv("AWS_REGION", access.Region)
os.Setenv("AWS_ACCESS_KEY_ID", access.AccessKeyId)
os.Setenv("AWS_SECRET_ACCESS_KEY", access.SecretAccessKey)
os.Setenv("AWS_HOSTED_ZONE_ID", access.HostedZoneId)
os.Setenv("AWS_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))
dnsProvider, err := route53.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(t.option, dnsProvider)
}

View File

@@ -7,7 +7,7 @@ import (
cf "github.com/go-acme/lego/v4/providers/dns/cloudflare"
"certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain"
)
type cloudflare struct {

View File

@@ -7,7 +7,7 @@ import (
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
"certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain"
)
type godaddy struct {

View File

@@ -0,0 +1,38 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/httpreq"
"github.com/usual2970/certimate/internal/domain"
)
type httpReq struct {
option *ApplyOption
}
func NewHttpreq(option *ApplyOption) Applicant {
return &httpReq{
option: option,
}
}
func (a *httpReq) Apply() (*Certificate, error) {
access := &domain.HttpreqAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("HTTPREQ_ENDPOINT", access.Endpoint)
os.Setenv("HTTPREQ_MODE", access.Mode)
os.Setenv("HTTPREQ_USERNAME", access.Username)
os.Setenv("HTTPREQ_PASSWORD", access.Password)
os.Setenv("HTTPREQ_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := httpreq.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -7,7 +7,7 @@ import (
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
"certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain"
)
type huaweicloud struct {
@@ -24,7 +24,12 @@ func (t *huaweicloud) Apply() (*Certificate, error) {
access := &domain.HuaweiCloudAccess{}
json.Unmarshal([]byte(t.option.Access), access)
os.Setenv("HUAWEICLOUD_REGION", access.Region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
region := access.Region
if region == "" {
region = "cn-north-1"
}
os.Setenv("HUAWEICLOUD_REGION", region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
os.Setenv("HUAWEICLOUD_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", t.option.Timeout))

View File

@@ -7,7 +7,7 @@ import (
namesiloProvider "github.com/go-acme/lego/v4/providers/dns/namesilo"
"certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain"
)
type namesilo struct {

View File

@@ -0,0 +1,36 @@
package applicant
import (
"encoding/json"
"fmt"
"os"
"github.com/go-acme/lego/v4/providers/dns/pdns"
"github.com/usual2970/certimate/internal/domain"
)
type powerdns struct {
option *ApplyOption
}
func NewPdns(option *ApplyOption) Applicant {
return &powerdns{
option: option,
}
}
func (a *powerdns) Apply() (*Certificate, error) {
access := &domain.PdnsAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("PDNS_API_URL", access.ApiUrl)
os.Setenv("PDNS_API_KEY", access.ApiKey)
os.Setenv("PDNS_PROPAGATION_TIMEOUT", fmt.Sprintf("%d", a.option.Timeout))
dnsProvider, err := pdns.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"certimate/internal/domain"
"github.com/usual2970/certimate/internal/domain"
)
type tencent struct {

View File

@@ -1,68 +0,0 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"certimate/internal/domain"
)
type aliyun struct {
client *oss.Client
option *DeployerOption
infos []string
}
func NewAliyun(option *DeployerOption) (Deployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
a := &aliyun{
option: option,
infos: make([]string, 0),
}
client, err := a.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
a.client = client
return a, nil
}
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
}
func (a *aliyun) Deploy(ctx context.Context) error {
err := a.client.PutBucketCnameWithCertificate(getDeployString(a.option.DeployConfig, "bucket"), oss.PutBucketCname{
Cname: getDeployString(a.option.DeployConfig, "domain"),
CertificateConfiguration: &oss.CertificateConfiguration{
Certificate: a.option.Certificate.Certificate,
PrivateKey: a.option.Certificate.PrivateKey,
Force: true,
},
})
if err != nil {
return fmt.Errorf("deploy aliyun oss error: %w", err)
}
return nil
}
func (a *aliyun) createClient(accessKeyId, accessKeySecret string) (*oss.Client, error) {
client, err := oss.New(
getDeployString(a.option.DeployConfig, "endpoint"),
accessKeyId,
accessKeySecret,
)
if err != nil {
return nil, fmt.Errorf("create aliyun client error: %w", err)
}
return client, nil
}

View File

@@ -10,67 +10,69 @@ import (
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"certimate/internal/domain"
"certimate/internal/utils/rand"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type AliyunCdn struct {
type AliyunCDNDeployer struct {
client *cdn20180510.Client
option *DeployerOption
infos []string
}
func NewAliyunCdn(option *DeployerOption) (*AliyunCdn, error) {
func NewAliyunCDNDeployer(option *DeployerOption) (*AliyunCDNDeployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
a := &AliyunCdn{
d := &AliyunCDNDeployer{
option: option,
}
client, err := a.createClient(access.AccessKeyId, access.AccessKeySecret)
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
return &AliyunCdn{
return &AliyunCDNDeployer{
client: client,
option: option,
infos: make([]string, 0),
}, nil
}
func (a *AliyunCdn) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
func (d *AliyunCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (a *AliyunCdn) GetInfo() []string {
return a.infos
func (d *AliyunCDNDeployer) GetInfo() []string {
return d.infos
}
func (a *AliyunCdn) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s-%s", a.option.Domain, a.option.DomainId, rand.RandStr(6))
func (d *AliyunCDNDeployer) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
setCdnDomainSSLCertificateRequest := &cdn20180510.SetCdnDomainSSLCertificateRequest{
DomainName: tea.String(getDeployString(a.option.DeployConfig, "domain")),
DomainName: tea.String(getDeployString(d.option.DeployConfig, "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),
SSLPub: tea.String(d.option.Certificate.Certificate),
SSLPri: tea.String(d.option.Certificate.PrivateKey),
CertRegion: tea.String("cn-hangzhou"),
}
runtime := &util.RuntimeOptions{}
resp, err := a.client.SetCdnDomainSSLCertificateWithOptions(setCdnDomainSSLCertificateRequest, runtime)
resp, err := d.client.SetCdnDomainSSLCertificateWithOptions(setCdnDomainSSLCertificateRequest, runtime)
if err != nil {
return err
}
a.infos = append(a.infos, toStr("cdn设置证书", resp))
d.infos = append(d.infos, toStr("cdn设置证书", resp))
return nil
}
func (a *AliyunCdn) createClient(accessKeyId, accessKeySecret string) (_result *cdn20180510.Client, _err error) {
func (d *AliyunCDNDeployer) createClient(accessKeyId, accessKeySecret string) (_result *cdn20180510.Client, _err error) {
config := &openapi.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),

View File

@@ -9,73 +9,83 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
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"
"certimate/internal/domain"
"certimate/internal/utils/rand"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type AliyunEsa struct {
type AliyunESADeployer struct {
client *dcdn20180115.Client
option *DeployerOption
infos []string
}
func NewAliyunEsa(option *DeployerOption) (*AliyunEsa, error) {
func NewAliyunESADeployer(option *DeployerOption) (*AliyunESADeployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
a := &AliyunEsa{
d := &AliyunESADeployer{
option: option,
}
client, err := a.createClient(access.AccessKeyId, access.AccessKeySecret)
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
return &AliyunEsa{
return &AliyunESADeployer{
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 (d *AliyunESADeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (a *AliyunEsa) GetInfo() []string {
return a.infos
func (d *AliyunESADeployer) GetInfo() []string {
return d.infos
}
func (a *AliyunEsa) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s-%s", a.option.Domain, a.option.DomainId, rand.RandStr(6))
func (d *AliyunESADeployer) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
// 支持泛解析域名,在 Aliyun DCND 中泛解析域名表示为 .example.com
domain := getDeployString(d.option.DeployConfig, "domain")
if strings.HasPrefix(domain, "*") {
domain = strings.TrimPrefix(domain, "*")
}
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
DomainName: tea.String(getDeployString(a.option.DeployConfig, "domain")),
DomainName: tea.String(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),
SSLPub: tea.String(d.option.Certificate.Certificate),
SSLPri: tea.String(d.option.Certificate.PrivateKey),
CertRegion: tea.String("cn-hangzhou"),
}
runtime := &util.RuntimeOptions{}
resp, err := a.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
resp, err := d.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
if err != nil {
return err
}
a.infos = append(a.infos, toStr("dcdn设置证书", resp))
d.infos = append(d.infos, toStr("dcdn设置证书", resp))
return nil
}
func (a *AliyunEsa) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
func (d *AliyunESADeployer) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
config := &openapi.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),

View File

@@ -0,0 +1,70 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/usual2970/certimate/internal/domain"
)
type AliyunOSSDeployer struct {
client *oss.Client
option *DeployerOption
infos []string
}
func NewAliyunOSSDeployer(option *DeployerOption) (Deployer, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
d := &AliyunOSSDeployer{
option: option,
infos: make([]string, 0),
}
client, err := d.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
d.client = client
return d, nil
}
func (d *AliyunOSSDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *AliyunOSSDeployer) GetInfo() []string {
return d.infos
}
func (d *AliyunOSSDeployer) Deploy(ctx context.Context) error {
err := d.client.PutBucketCnameWithCertificate(getDeployString(d.option.DeployConfig, "bucket"), oss.PutBucketCname{
Cname: getDeployString(d.option.DeployConfig, "domain"),
CertificateConfiguration: &oss.CertificateConfiguration{
Certificate: d.option.Certificate.Certificate,
PrivateKey: d.option.Certificate.PrivateKey,
Force: true,
},
})
if err != nil {
return fmt.Errorf("deploy aliyun oss error: %w", err)
}
return nil
}
func (d *AliyunOSSDeployer) createClient(accessKeyId, accessKeySecret string) (*oss.Client, error) {
client, err := oss.New(
getDeployString(d.option.DeployConfig, "endpoint"),
accessKeyId,
accessKeySecret,
)
if err != nil {
return nil, fmt.Errorf("create aliyun client error: %w", err)
}
return client, nil
}

View File

@@ -9,20 +9,23 @@ import (
"github.com/pocketbase/pocketbase/models"
"certimate/internal/applicant"
"certimate/internal/domain"
"certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
)
const (
targetAliyunOss = "aliyun-oss"
targetAliyunCdn = "aliyun-cdn"
targetAliyunEsa = "aliyun-dcdn"
targetSSH = "ssh"
targetWebhook = "webhook"
targetTencentCdn = "tencent-cdn"
targetQiniuCdn = "qiniu-cdn"
targetLocal = "local"
targetAliyunOSS = "aliyun-oss"
targetAliyunCDN = "aliyun-cdn"
targetAliyunESA = "aliyun-dcdn"
targetTencentCDN = "tencent-cdn"
targetTencentCOS = "tencent-cos"
targetHuaweiCloudCDN = "huaweicloud-cdn"
targetQiniuCdn = "qiniu-cdn"
targetLocal = "local"
targetSSH = "ssh"
targetWebhook = "webhook"
targetK8sSecret = "k8s-secret"
)
type DeployerOption struct {
@@ -30,7 +33,7 @@ type DeployerOption struct {
Domain string `json:"domain"`
Product string `json:"product"`
Access string `json:"access"`
AceessRecord *models.Record `json:"-"`
AccessRecord *models.Record `json:"-"`
DeployConfig domain.DeployConfig `json:"deployConfig"`
Certificate applicant.Certificate `json:"certificate"`
Variables map[string]string `json:"variables"`
@@ -60,7 +63,6 @@ func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error
}
for _, deployConfig := range deployConfigs {
deployer, err := getWithDeployConfig(record, cert, deployConfig)
if err != nil {
return nil, err
@@ -83,7 +85,7 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
Domain: record.GetString("domain"),
Product: getProduct(deployConfig.Type),
Access: access.GetString("config"),
AceessRecord: access,
AccessRecord: access,
DeployConfig: deployConfig,
}
if cert != nil {
@@ -96,23 +98,28 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep
}
switch deployConfig.Type {
case targetAliyunOss:
return NewAliyun(option)
case targetAliyunCdn:
return NewAliyunCdn(option)
case targetAliyunEsa:
return NewAliyunEsa(option)
case targetSSH:
return NewSSH(option)
case targetWebhook:
return NewWebhook(option)
case targetTencentCdn:
return NewTencentCdn(option)
case targetAliyunOSS:
return NewAliyunOSSDeployer(option)
case targetAliyunCDN:
return NewAliyunCDNDeployer(option)
case targetAliyunESA:
return NewAliyunESADeployer(option)
case targetTencentCDN:
return NewTencentCDNDeployer(option)
case targetTencentCOS:
return NewTencentCOSDeployer(option)
case targetHuaweiCloudCDN:
return NewHuaweiCloudCDNDeployer(option)
case targetQiniuCdn:
return NewQiNiu(option)
return NewQiniuCDNDeployer(option)
case targetLocal:
return NewLocal(option), nil
return NewLocalDeployer(option)
case targetSSH:
return NewSSHDeployer(option)
case targetWebhook:
return NewWebhookDeployer(option)
case targetK8sSecret:
return NewK8sSecretDeployer(option)
}
return nil, errors.New("not implemented")
}

View File

@@ -0,0 +1,206 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/global"
cdn "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2"
cdnModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/model"
cdnRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/cdn/v2/region"
"github.com/usual2970/certimate/internal/domain"
uploader "github.com/usual2970/certimate/internal/pkg/core/uploader"
"github.com/usual2970/certimate/internal/pkg/utils/cast"
)
type HuaweiCloudCDNDeployer struct {
option *DeployerOption
infos []string
}
func NewHuaweiCloudCDNDeployer(option *DeployerOption) (Deployer, error) {
return &HuaweiCloudCDNDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *HuaweiCloudCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *HuaweiCloudCDNDeployer) GetInfo() []string {
return d.infos
}
func (d *HuaweiCloudCDNDeployer) Deploy(ctx context.Context) error {
access := &domain.HuaweiCloudAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
// TODO: CDN 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
client, err := d.createClient("", access.AccessKeyId, access.SecretAccessKey)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("SDK 客户端创建成功", nil))
// 查询加速域名配置
// REF: https://support.huaweicloud.com/api-cdn/ShowDomainFullConfig.html
showDomainFullConfigReq := &cdnModel.ShowDomainFullConfigRequest{
DomainName: d.option.DeployConfig.GetConfigAsString("domain"),
}
showDomainFullConfigResp, err := client.ShowDomainFullConfig(showDomainFullConfigReq)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("已查询到加速域名配置", showDomainFullConfigResp))
// 更新加速域名配置
// REF: https://support.huaweicloud.com/api-cdn/UpdateDomainMultiCertificates.html
// REF: https://support.huaweicloud.com/usermanual-cdn/cdn_01_0306.html
updateDomainMultiCertificatesReqBodyContent := &huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent{}
updateDomainMultiCertificatesReqBodyContent.DomainName = d.option.DeployConfig.GetConfigAsString("domain")
updateDomainMultiCertificatesReqBodyContent.HttpsSwitch = 1
var updateDomainMultiCertificatesResp *cdnModel.UpdateDomainMultiCertificatesResponse
if d.option.DeployConfig.GetConfigAsBool("useSCM") {
uploader, err := uploader.NewHuaweiCloudSCMUploader(&uploader.HuaweiCloudSCMUploaderConfig{
Region: "", // TODO: SCM 服务与 DNS 服务所支持的区域可能不一致,这里暂时不传而是使用默认值,仅支持华为云国内版
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
})
if err != nil {
return err
}
// 上传证书到 SCM
uploadResult, err := uploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("已上传证书", uploadResult))
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(2)
updateDomainMultiCertificatesReqBodyContent.SCMCertificateId = cast.StringPtr(uploadResult.CertId)
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(uploadResult.CertName)
} else {
updateDomainMultiCertificatesReqBodyContent.CertificateType = cast.Int32Ptr(0)
updateDomainMultiCertificatesReqBodyContent.CertName = cast.StringPtr(fmt.Sprintf("certimate-%d", time.Now().UnixMilli()))
updateDomainMultiCertificatesReqBodyContent.Certificate = cast.StringPtr(d.option.Certificate.Certificate)
updateDomainMultiCertificatesReqBodyContent.PrivateKey = cast.StringPtr(d.option.Certificate.PrivateKey)
}
updateDomainMultiCertificatesReqBodyContent = mergeHuaweiCloudCDNConfig(showDomainFullConfigResp.Configs, updateDomainMultiCertificatesReqBodyContent)
updateDomainMultiCertificatesReq := &huaweicloudCDNUpdateDomainMultiCertificatesRequest{
Body: &huaweicloudCDNUpdateDomainMultiCertificatesRequestBody{
Https: updateDomainMultiCertificatesReqBodyContent,
},
}
updateDomainMultiCertificatesResp, err = executeHuaweiCloudCDNUploadDomainMultiCertificates(client, updateDomainMultiCertificatesReq)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("已更新加速域名配置", updateDomainMultiCertificatesResp))
return nil
}
func (d *HuaweiCloudCDNDeployer) createClient(region, accessKeyId, secretAccessKey string) (*cdn.CdnClient, error) {
auth, err := global.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return nil, err
}
if region == "" {
region = "cn-north-1" // CDN 服务默认区域:华北一北京
}
hcRegion, err := cdnRegion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := cdn.CdnClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := cdn.NewCdnClient(hcClient)
return client, nil
}
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent struct {
cdnModel.UpdateDomainMultiCertificatesRequestBodyContent `json:",inline"`
SCMCertificateId *string `json:"scm_certificate_id,omitempty"`
}
type huaweicloudCDNUpdateDomainMultiCertificatesRequestBody struct {
Https *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent `json:"https,omitempty"`
}
type huaweicloudCDNUpdateDomainMultiCertificatesRequest struct {
Body *huaweicloudCDNUpdateDomainMultiCertificatesRequestBody `json:"body,omitempty"`
}
func executeHuaweiCloudCDNUploadDomainMultiCertificates(client *cdn.CdnClient, request *huaweicloudCDNUpdateDomainMultiCertificatesRequest) (*cdnModel.UpdateDomainMultiCertificatesResponse, error) {
// 华为云官方 SDK 中目前提供的字段缺失,这里暂时先需自定义请求
// 可能需要等之后 SDK 更新
requestDef := cdn.GenReqDefForUpdateDomainMultiCertificates()
if resp, err := client.HcClient.Sync(request, requestDef); err != nil {
return nil, err
} else {
return resp.(*cdnModel.UpdateDomainMultiCertificatesResponse), nil
}
}
func mergeHuaweiCloudCDNConfig(src *cdnModel.ConfigsGetBody, dest *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent) *huaweicloudCDNUpdateDomainMultiCertificatesRequestBodyContent {
if src == nil {
return dest
}
// 华为云 API 中不传的字段表示使用默认值、而非保留原值,因此这里需要把原配置中的参数重新赋值回去
// 而且蛋疼的是查询接口返回的数据结构和更新接口传入的参数结构不一致,需要做很多转化
if *src.OriginProtocol == "follow" {
dest.AccessOriginWay = cast.Int32Ptr(1)
} else if *src.OriginProtocol == "http" {
dest.AccessOriginWay = cast.Int32Ptr(2)
} else if *src.OriginProtocol == "https" {
dest.AccessOriginWay = cast.Int32Ptr(3)
}
if src.ForceRedirect != nil {
dest.ForceRedirectConfig = &cdnModel.ForceRedirect{}
if src.ForceRedirect.Status == "on" {
dest.ForceRedirectConfig.Switch = 1
dest.ForceRedirectConfig.RedirectType = src.ForceRedirect.Type
} else {
dest.ForceRedirectConfig.Switch = 0
}
}
if src.Https != nil {
if *src.Https.Http2Status == "on" {
dest.Http2 = cast.Int32Ptr(1)
}
}
return dest
}

View File

@@ -0,0 +1,108 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
k8sMetaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/usual2970/certimate/internal/domain"
)
type K8sSecretDeployer struct {
option *DeployerOption
infos []string
}
func NewK8sSecretDeployer(option *DeployerOption) (Deployer, error) {
return &K8sSecretDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *K8sSecretDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *K8sSecretDeployer) GetInfo() []string {
return d.infos
}
func (d *K8sSecretDeployer) Deploy(ctx context.Context) error {
access := &domain.KubernetesAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
client, err := d.createClient(access)
if err != nil {
return err
}
d.infos = append(d.infos, toStr("kubeClient 创建成功", nil))
namespace := getDeployString(d.option.DeployConfig, "namespace")
if namespace == "" {
namespace = "default"
}
secretName := getDeployString(d.option.DeployConfig, "secretName")
if secretName == "" {
return fmt.Errorf("k8s secret name is empty")
}
secretDataKeyForCrt := getDeployString(d.option.DeployConfig, "secretDataKeyForCrt")
if secretDataKeyForCrt == "" {
namespace = "tls.crt"
}
secretDataKeyForKey := getDeployString(d.option.DeployConfig, "secretDataKeyForKey")
if secretDataKeyForKey == "" {
namespace = "tls.key"
}
// 获取 Secret 实例
secret, err := client.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, k8sMetaV1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get k8s secret: %w", err)
}
// 更新 Secret Data
secret.Data[secretDataKeyForCrt] = []byte(d.option.Certificate.Certificate)
secret.Data[secretDataKeyForKey] = []byte(d.option.Certificate.PrivateKey)
_, err = client.CoreV1().Secrets(namespace).Update(context.TODO(), secret, k8sMetaV1.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed to update k8s secret: %w", err)
}
d.infos = append(d.infos, toStr("证书已更新到 K8s Secret", nil))
return nil
}
func (d *K8sSecretDeployer) createClient(access *domain.KubernetesAccess) (*kubernetes.Clientset, error) {
kubeConfig, err := clientcmd.Load([]byte(access.KubeConfig))
if err != nil {
return nil, err
}
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: ""},
&clientcmd.ConfigOverrides{CurrentContext: kubeConfig.CurrentContext},
)
config, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
client, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -8,37 +8,37 @@ import (
"os/exec"
"path/filepath"
"runtime"
"github.com/usual2970/certimate/internal/domain"
)
type localAccess struct{}
type local struct {
type LocalDeployer struct {
option *DeployerOption
infos []string
}
func NewLocal(option *DeployerOption) *local {
return &local{
func NewLocalDeployer(option *DeployerOption) (Deployer, error) {
return &LocalDeployer{
option: option,
infos: make([]string, 0),
}
}, nil
}
func (l *local) GetID() string {
return fmt.Sprintf("%s-%s", l.option.AceessRecord.GetString("name"), l.option.AceessRecord.Id)
func (d *LocalDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (l *local) GetInfo() []string {
func (d *LocalDeployer) GetInfo() []string {
return []string{}
}
func (l *local) Deploy(ctx context.Context) error {
access := &localAccess{}
if err := json.Unmarshal([]byte(l.option.Access), access); err != nil {
func (d *LocalDeployer) Deploy(ctx context.Context) error {
access := &domain.LocalAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
preCommand := getDeployString(l.option.DeployConfig, "preCommand")
preCommand := getDeployString(d.option.DeployConfig, "preCommand")
if preCommand != "" {
if err := execCmd(preCommand); err != nil {
@@ -46,18 +46,18 @@ func (l *local) Deploy(ctx context.Context) error {
}
}
// 复制文件
if err := copyFile(l.option.Certificate.Certificate, getDeployString(l.option.DeployConfig, "certPath")); err != nil {
// 复制证书文件
if err := copyFile(getDeployString(d.option.DeployConfig, "certPath"), d.option.Certificate.Certificate); err != nil {
return fmt.Errorf("复制证书失败: %w", err)
}
if err := copyFile(l.option.Certificate.PrivateKey, getDeployString(l.option.DeployConfig, "keyPath")); err != nil {
// 复制私钥文件
if err := copyFile(getDeployString(d.option.DeployConfig, "keyPath"), d.option.Certificate.PrivateKey); err != nil {
return fmt.Errorf("复制私钥失败: %w", err)
}
// 执行命令
if err := execCmd(getDeployString(l.option.DeployConfig, "command")); err != nil {
if err := execCmd(getDeployString(d.option.DeployConfig, "command")); err != nil {
return fmt.Errorf("执行命令失败: %w", err)
}
@@ -85,7 +85,7 @@ func execCmd(command string) error {
return nil
}
func copyFile(content string, path string) error {
func copyFile(path string, content string) error {
dir := filepath.Dir(path)
// 如果目录不存在,创建目录

View File

@@ -10,23 +10,23 @@ import (
"github.com/qiniu/go-sdk/v7/auth"
"certimate/internal/domain"
xhttp "certimate/internal/utils/http"
"github.com/usual2970/certimate/internal/domain"
xhttp "github.com/usual2970/certimate/internal/utils/http"
)
const qiniuGateway = "http://api.qiniu.com"
type qiuniu struct {
type QiniuCDNDeployer struct {
option *DeployerOption
info []string
credentials *auth.Credentials
}
func NewQiNiu(option *DeployerOption) (*qiuniu, error) {
func NewQiniuCDNDeployer(option *DeployerOption) (*QiniuCDNDeployer, error) {
access := &domain.QiniuAccess{}
json.Unmarshal([]byte(option.Access), access)
return &qiuniu{
return &QiniuCDNDeployer{
option: option,
info: make([]string, 0),
@@ -34,41 +34,39 @@ 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 (d *QiniuCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (q *qiuniu) GetInfo() []string {
return q.info
func (d *QiniuCDNDeployer) GetInfo() []string {
return d.info
}
func (q *qiuniu) Deploy(ctx context.Context) error {
func (d *QiniuCDNDeployer) Deploy(ctx context.Context) error {
// 上传证书
certId, err := q.uploadCert()
certId, err := d.uploadCert()
if err != nil {
return fmt.Errorf("uploadCert failed: %w", err)
}
// 获取域名信息
domainInfo, err := q.getDomainInfo()
domainInfo, err := d.getDomainInfo()
if err != nil {
return fmt.Errorf("getDomainInfo failed: %w", err)
}
// 判断域名是否启用 https
if domainInfo.Https != nil && domainInfo.Https.CertID != "" {
// 启用了 https
// 修改域名证书
err = q.modifyDomainCert(certId)
err = d.modifyDomainCert(certId)
if err != nil {
return fmt.Errorf("modifyDomainCert failed: %w", err)
}
} else {
// 没启用 https
// 启用 https
err = q.enableHttps(certId)
err = d.enableHttps(certId)
if err != nil {
return fmt.Errorf("enableHttps failed: %w", err)
}
@@ -77,10 +75,11 @@ func (q *qiuniu) Deploy(ctx context.Context) error {
return nil
}
func (q *qiuniu) enableHttps(certId string) error {
path := fmt.Sprintf("/domain/%s/sslize", getDeployString(q.option.DeployConfig, "domain"))
func (d *QiniuCDNDeployer) enableHttps(certId string) error {
domain := d.option.DeployConfig.GetDomain()
path := fmt.Sprintf("/domain/%s/sslize", domain)
body := &modifyDomainCertReq{
body := &qiniuModifyDomainCertReq{
CertID: certId,
ForceHttps: true,
Http2Enable: true,
@@ -91,7 +90,7 @@ func (q *qiuniu) enableHttps(certId string) error {
return fmt.Errorf("enable https failed: %w", err)
}
_, err = q.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
if err != nil {
return fmt.Errorf("enable https failed: %w", err)
}
@@ -99,19 +98,21 @@ func (q *qiuniu) enableHttps(certId string) error {
return nil
}
type domainInfo struct {
Https *modifyDomainCertReq `json:"https"`
type qiniuDomainInfo struct {
Https *qiniuModifyDomainCertReq `json:"https"`
}
func (q *qiuniu) getDomainInfo() (*domainInfo, error) {
path := fmt.Sprintf("/domain/%s", getDeployString(q.option.DeployConfig, "domain"))
func (d *QiniuCDNDeployer) getDomainInfo() (*qiniuDomainInfo, error) {
domain := d.option.DeployConfig.GetDomain()
res, err := q.req(qiniuGateway+path, http.MethodGet, nil)
path := fmt.Sprintf("/domain/%s", domain)
res, err := d.req(qiniuGateway+path, http.MethodGet, nil)
if err != nil {
return nil, fmt.Errorf("req failed: %w", err)
}
resp := &domainInfo{}
resp := &qiniuDomainInfo{}
err = json.Unmarshal(res, resp)
if err != nil {
return nil, fmt.Errorf("json.Unmarshal failed: %w", err)
@@ -120,25 +121,25 @@ func (q *qiuniu) getDomainInfo() (*domainInfo, error) {
return resp, nil
}
type uploadCertReq struct {
type qiniuUploadCertReq struct {
Name string `json:"name"`
CommonName string `json:"common_name"`
Pri string `json:"pri"`
Ca string `json:"ca"`
}
type uploadCertResp struct {
type qiniuUploadCertResp struct {
CertID string `json:"certID"`
}
func (q *qiuniu) uploadCert() (string, error) {
func (d *QiniuCDNDeployer) uploadCert() (string, error) {
path := "/sslcert"
body := &uploadCertReq{
Name: getDeployString(q.option.DeployConfig, "domain"),
CommonName: getDeployString(q.option.DeployConfig, "domain"),
Pri: q.option.Certificate.PrivateKey,
Ca: q.option.Certificate.Certificate,
body := &qiniuUploadCertReq{
Name: getDeployString(d.option.DeployConfig, "domain"),
CommonName: getDeployString(d.option.DeployConfig, "domain"),
Pri: d.option.Certificate.PrivateKey,
Ca: d.option.Certificate.Certificate,
}
bodyBytes, err := json.Marshal(body)
@@ -146,11 +147,11 @@ func (q *qiuniu) uploadCert() (string, error) {
return "", fmt.Errorf("json.Marshal failed: %w", err)
}
res, err := q.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes))
res, err := d.req(qiniuGateway+path, http.MethodPost, bytes.NewReader(bodyBytes))
if err != nil {
return "", fmt.Errorf("req failed: %w", err)
}
resp := &uploadCertResp{}
resp := &qiniuUploadCertResp{}
err = json.Unmarshal(res, resp)
if err != nil {
return "", fmt.Errorf("json.Unmarshal failed: %w", err)
@@ -159,16 +160,17 @@ func (q *qiuniu) uploadCert() (string, error) {
return resp.CertID, nil
}
type modifyDomainCertReq struct {
type qiniuModifyDomainCertReq struct {
CertID string `json:"certId"`
ForceHttps bool `json:"forceHttps"`
Http2Enable bool `json:"http2Enable"`
}
func (q *qiuniu) modifyDomainCert(certId string) error {
path := fmt.Sprintf("/domain/%s/httpsconf", getDeployString(q.option.DeployConfig, "domain"))
func (d *QiniuCDNDeployer) modifyDomainCert(certId string) error {
domain := d.option.DeployConfig.GetDomain()
path := fmt.Sprintf("/domain/%s/httpsconf", domain)
body := &modifyDomainCertReq{
body := &qiniuModifyDomainCertReq{
CertID: certId,
ForceHttps: true,
Http2Enable: true,
@@ -179,7 +181,7 @@ func (q *qiuniu) modifyDomainCert(certId string) error {
return fmt.Errorf("json.Marshal failed: %w", err)
}
_, err = q.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
_, err = d.req(qiniuGateway+path, http.MethodPut, bytes.NewReader(bodyBytes))
if err != nil {
return fmt.Errorf("req failed: %w", err)
}
@@ -187,12 +189,12 @@ func (q *qiuniu) modifyDomainCert(certId string) error {
return nil
}
func (q *qiuniu) req(url, method string, body io.Reader) ([]byte, error) {
func (d *QiniuCDNDeployer) req(url, method string, body io.Reader) ([]byte, error) {
req := xhttp.BuildReq(url, method, body, map[string]string{
"Content-Type": "application/json",
})
if err := q.credentials.AddToken(auth.TokenQBox, req); err != nil {
if err := d.credentials.AddToken(auth.TokenQBox, req); err != nil {
return nil, fmt.Errorf("credentials.AddToken failed: %w", err)
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/qiniu/go-sdk/v7/auth"
"certimate/internal/applicant"
"github.com/usual2970/certimate/internal/applicant"
)
func Test_qiuniu_uploadCert(t *testing.T) {
@@ -36,7 +36,7 @@ func Test_qiuniu_uploadCert(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, _ := NewQiNiu(tt.fields.option)
q, _ := NewQiniuCDNDeployer(tt.fields.option)
got, err := q.uploadCert()
if (err != nil) != tt.wantErr {
t.Errorf("qiuniu.uploadCert() error = %v, wantErr %v", err, tt.wantErr)
@@ -78,7 +78,7 @@ func Test_qiuniu_modifyDomainCert(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
q, _ := NewQiNiu(tt.fields.option)
q, _ := NewQiniuCDNDeployer(tt.fields.option)
if err := q.modifyDomainCert(tt.args.certId); (err != nil) != tt.wantErr {
t.Errorf("qiuniu.modifyDomainCert() error = %v, wantErr %v", err, tt.wantErr)
}

View File

@@ -10,87 +10,80 @@ import (
"github.com/pkg/sftp"
sshPkg "golang.org/x/crypto/ssh"
"github.com/usual2970/certimate/internal/domain"
)
type ssh struct {
type SSHDeployer struct {
option *DeployerOption
infos []string
}
type sshAccess struct {
Host string `json:"host"`
Port string `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Key string `json:"key"`
KeyPassphrase string `json:"keyPassphrase"`
}
func NewSSH(option *DeployerOption) (Deployer, error) {
return &ssh{
func NewSSHDeployer(option *DeployerOption) (Deployer, error) {
return &SSHDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (a *ssh) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
func (d *SSHDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (s *ssh) GetInfo() []string {
return s.infos
func (d *SSHDeployer) GetInfo() []string {
return d.infos
}
func (s *ssh) Deploy(ctx context.Context) error {
access := &sshAccess{}
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
func (d *SSHDeployer) Deploy(ctx context.Context) error {
access := &domain.SSHAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return err
}
// 连接
client, err := s.getClient(access)
client, err := d.createClient(access)
if err != nil {
return err
}
defer client.Close()
s.infos = append(s.infos, toStr("ssh连接成功", nil))
d.infos = append(d.infos, toStr("ssh连接成功", nil))
// 执行前置命令
preCommand := getDeployString(s.option.DeployConfig, "preCommand")
preCommand := getDeployString(d.option.DeployConfig, "preCommand")
if preCommand != "" {
stdout, stderr, err := s.sshExecCommand(client, preCommand)
stdout, stderr, err := d.sshExecCommand(client, preCommand)
if err != nil {
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
}
// 上传证书
if err := s.upload(client, s.option.Certificate.Certificate, getDeployString(s.option.DeployConfig, "certPath")); err != nil {
if err := d.upload(client, d.option.Certificate.Certificate, getDeployString(d.option.DeployConfig, "certPath")); err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
s.infos = append(s.infos, toStr("ssh上传证书成功", nil))
d.infos = append(d.infos, toStr("ssh上传证书成功", nil))
// 上传私钥
if err := s.upload(client, s.option.Certificate.PrivateKey, getDeployString(s.option.DeployConfig, "keyPath")); err != nil {
if err := d.upload(client, d.option.Certificate.PrivateKey, getDeployString(d.option.DeployConfig, "keyPath")); err != nil {
return fmt.Errorf("failed to upload private key: %w", err)
}
s.infos = append(s.infos, toStr("ssh上传私钥成功", nil))
d.infos = append(d.infos, toStr("ssh上传私钥成功", nil))
// 执行命令
stdout, stderr, err := s.sshExecCommand(client, getDeployString(s.option.DeployConfig, "command"))
stdout, stderr, err := d.sshExecCommand(client, getDeployString(d.option.DeployConfig, "command"))
if err != nil {
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
s.infos = append(s.infos, toStr("ssh执行命令成功", stdout))
d.infos = append(d.infos, toStr("ssh执行命令成功", stdout))
return nil
}
func (s *ssh) sshExecCommand(client *sshPkg.Client, command string) (string, string, error) {
func (d *SSHDeployer) sshExecCommand(client *sshPkg.Client, command string) (string, string, error) {
session, err := client.NewSession()
if err != nil {
return "", "", fmt.Errorf("failed to create ssh session: %w", err)
@@ -105,7 +98,7 @@ func (s *ssh) sshExecCommand(client *sshPkg.Client, command string) (string, str
return stdoutBuf.String(), stderrBuf.String(), err
}
func (s *ssh) upload(client *sshPkg.Client, content, path string) error {
func (d *SSHDeployer) upload(client *sshPkg.Client, content, path string) error {
sftpCli, err := sftp.NewClient(client)
if err != nil {
return fmt.Errorf("failed to create sftp client: %w", err)
@@ -130,7 +123,7 @@ func (s *ssh) upload(client *sshPkg.Client, content, path string) error {
return nil
}
func (s *ssh) getClient(access *sshAccess) (*sshPkg.Client, error) {
func (d *SSHDeployer) createClient(access *domain.SSHAccess) (*sshPkg.Client, error) {
var authMethod sshPkg.AuthMethod
if access.Key != "" {

View File

@@ -0,0 +1,108 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type TencentCOSDeployer struct {
option *DeployerOption
credential *common.Credential
infos []string
}
func NewTencentCOSDeployer(option *DeployerOption) (Deployer, error) {
access := &domain.TencentAccess{}
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
}
credential := common.NewCredential(
access.SecretId,
access.SecretKey,
)
return &TencentCOSDeployer{
option: option,
credential: credential,
infos: make([]string, 0),
}, nil
}
func (d *TencentCOSDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *TencentCOSDeployer) GetInfo() []string {
return d.infos
}
func (d *TencentCOSDeployer) Deploy(ctx context.Context) error {
// 上传证书
certId, err := d.uploadCert()
if err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
d.infos = append(d.infos, toStr("上传证书", certId))
if err := d.deploy(certId); err != nil {
return fmt.Errorf("failed to deploy: %w", err)
}
return nil
}
// 上传证书与CDN部署的上传方法一致。
func (d *TencentCOSDeployer) uploadCert() (string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
client, _ := ssl.NewClient(d.credential, "", cpf)
request := ssl.NewUploadCertificateRequest()
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
request.Repeatable = common.BoolPtr(false)
response, err := client.UploadCertificate(request)
if err != nil {
return "", fmt.Errorf("failed to upload certificate: %w", err)
}
return *response.Response.CertificateId, nil
}
func (d *TencentCOSDeployer) deploy(certId string) error {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := ssl.NewClient(d.credential, getDeployString(d.option.DeployConfig, "region"), cpf)
// 实例化一个请求对象,每个接口都会对应一个request对象
request := ssl.NewDeployCertificateInstanceRequest()
request.CertificateId = common.StringPtr(certId)
request.ResourceType = common.StringPtr("cos")
request.Status = common.Int64Ptr(1)
domain := getDeployString(d.option.DeployConfig, "domain")
request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s#%s#%s", getDeployString(d.option.DeployConfig, "region"), getDeployString(d.option.DeployConfig, "bucket"), domain)})
// 返回的resp是一个DeployCertificateInstanceResponse的实例与请求对象对应
resp, err := client.DeployCertificateInstance(request)
if err != nil {
return fmt.Errorf("failed to deploy certificate: %w", err)
}
d.infos = append(d.infos, toStr("部署证书", resp.Response))
return nil
}

View File

@@ -12,17 +12,17 @@ import (
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"certimate/internal/domain"
"certimate/internal/utils/rand"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/rand"
)
type tencentCdn struct {
type TencentCDNDeployer struct {
option *DeployerOption
credential *common.Credential
infos []string
}
func NewTencentCdn(option *DeployerOption) (Deployer, error) {
func NewTencentCDNDeployer(option *DeployerOption) (Deployer, error) {
access := &domain.TencentAccess{}
if err := json.Unmarshal([]byte(option.Access), access); err != nil {
return nil, fmt.Errorf("failed to unmarshal tencent access: %w", err)
@@ -33,47 +33,47 @@ func NewTencentCdn(option *DeployerOption) (Deployer, error) {
access.SecretKey,
)
return &tencentCdn{
return &TencentCDNDeployer{
option: option,
credential: credential,
infos: make([]string, 0),
}, nil
}
func (a *tencentCdn) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
func (d *TencentCDNDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (t *tencentCdn) GetInfo() []string {
return t.infos
func (d *TencentCDNDeployer) GetInfo() []string {
return d.infos
}
func (t *tencentCdn) Deploy(ctx context.Context) error {
func (d *TencentCDNDeployer) Deploy(ctx context.Context) error {
// 上传证书
certId, err := t.uploadCert()
certId, err := d.uploadCert()
if err != nil {
return fmt.Errorf("failed to upload certificate: %w", err)
}
t.infos = append(t.infos, toStr("上传证书", certId))
d.infos = append(d.infos, toStr("上传证书", certId))
if err := t.deploy(certId); err != nil {
if err := d.deploy(certId); err != nil {
return fmt.Errorf("failed to deploy: %w", err)
}
return nil
}
func (t *tencentCdn) uploadCert() (string, error) {
func (d *TencentCDNDeployer) uploadCert() (string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
client, _ := ssl.NewClient(t.credential, "", cpf)
client, _ := ssl.NewClient(d.credential, "", cpf)
request := ssl.NewUploadCertificateRequest()
request.CertificatePublicKey = common.StringPtr(t.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(t.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(t.option.Domain + "_" + rand.RandStr(6))
request.CertificatePublicKey = common.StringPtr(d.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(d.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(d.option.Domain + "_" + rand.RandStr(6))
request.Repeatable = common.BoolPtr(false)
response, err := client.UploadCertificate(request)
@@ -84,11 +84,11 @@ func (t *tencentCdn) uploadCert() (string, error) {
return *response.Response.CertificateId, nil
}
func (t *tencentCdn) deploy(certId string) error {
func (d *TencentCDNDeployer) deploy(certId string) error {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := ssl.NewClient(t.credential, "", cpf)
client, _ := ssl.NewClient(d.credential, "", cpf)
// 实例化一个请求对象,每个接口都会对应一个request对象
request := ssl.NewDeployCertificateInstanceRequest()
@@ -98,9 +98,9 @@ func (t *tencentCdn) deploy(certId string) error {
request.Status = common.Int64Ptr(1)
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
domain := getDeployString(t.option.DeployConfig, "domain")
domain := getDeployString(d.option.DeployConfig, "domain")
if strings.Contains(domain, "*") {
list, errGetList := t.getDomainList()
list, errGetList := d.getDomainList()
if errGetList != nil {
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
}
@@ -117,18 +117,18 @@ func (t *tencentCdn) deploy(certId string) error {
if err != nil {
return fmt.Errorf("failed to deploy certificate: %w", err)
}
t.infos = append(t.infos, toStr("部署证书", resp.Response))
d.infos = append(d.infos, toStr("部署证书", resp.Response))
return nil
}
func (t *tencentCdn) getDomainList() ([]string, error) {
func (d *TencentCDNDeployer) getDomainList() ([]string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "cdn.tencentcloudapi.com"
client, _ := cdn.NewClient(t.credential, "", cpf)
client, _ := cdn.NewClient(d.credential, "", cpf)
request := cdn.NewDescribeCertDomainsRequest()
cert := base64.StdEncoding.EncodeToString([]byte(t.option.Certificate.Certificate))
cert := base64.StdEncoding.EncodeToString([]byte(d.option.Certificate.Certificate))
request.Cert = &cert
response, err := client.DescribeCertDomains(request)

View File

@@ -7,51 +7,48 @@ import (
"fmt"
"net/http"
xhttp "certimate/internal/utils/http"
"github.com/usual2970/certimate/internal/domain"
xhttp "github.com/usual2970/certimate/internal/utils/http"
)
type webhookAccess struct {
Url string `json:"url"`
type WebhookDeployer struct {
option *DeployerOption
infos []string
}
type hookData struct {
func NewWebhookDeployer(option *DeployerOption) (Deployer, error) {
return &WebhookDeployer{
option: option,
infos: make([]string, 0),
}, nil
}
func (d *WebhookDeployer) GetID() string {
return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id)
}
func (d *WebhookDeployer) GetInfo() []string {
return d.infos
}
type webhookData struct {
Domain string `json:"domain"`
Certificate string `json:"certificate"`
PrivateKey string `json:"privateKey"`
Variables map[string]string `json:"variables"`
}
type webhook struct {
option *DeployerOption
infos []string
}
func NewWebhook(option *DeployerOption) (Deployer, error) {
return &webhook{
option: option,
infos: make([]string, 0),
}, 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
}
func (w *webhook) Deploy(ctx context.Context) error {
access := &webhookAccess{}
if err := json.Unmarshal([]byte(w.option.Access), access); err != nil {
func (d *WebhookDeployer) Deploy(ctx context.Context) error {
access := &domain.WebhookAccess{}
if err := json.Unmarshal([]byte(d.option.Access), access); err != nil {
return fmt.Errorf("failed to parse hook access config: %w", err)
}
data := &hookData{
Domain: w.option.Domain,
Certificate: w.option.Certificate.Certificate,
PrivateKey: w.option.Certificate.PrivateKey,
Variables: getDeployVariables(w.option.DeployConfig),
data := &webhookData{
Domain: d.option.Domain,
Certificate: d.option.Certificate.Certificate,
PrivateKey: d.option.Certificate.PrivateKey,
Variables: getDeployVariables(d.option.DeployConfig),
}
body, _ := json.Marshal(data)
@@ -63,7 +60,7 @@ func (w *webhook) Deploy(ctx context.Context) error {
return fmt.Errorf("failed to send hook request: %w", err)
}
w.infos = append(w.infos, toStr("webhook response", string(resp)))
d.infos = append(d.infos, toStr("webhook response", string(resp)))
return nil
}

View File

@@ -16,6 +16,13 @@ type HuaweiCloudAccess struct {
SecretAccessKey string `json:"secretAccessKey"`
}
type AwsAccess struct {
Region string `json:"region"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
HostedZoneId string `json:"hostedZoneId"`
}
type CloudflareAccess struct {
DnsApiToken string `json:"dnsApiToken"`
}
@@ -33,3 +40,34 @@ type GodaddyAccess struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
}
type PdnsAccess struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
}
type HttpreqAccess struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode"`
Username string `json:"username"`
Password string `json:"password"`
}
type LocalAccess struct{}
type SSHAccess struct {
Host string `json:"host"`
Port string `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Key string `json:"key"`
KeyPassphrase string `json:"keyPassphrase"`
}
type WebhookAccess struct {
Url string `json:"url"`
}
type KubernetesAccess struct {
KubeConfig string `json:"kubeConfig"`
}

View File

@@ -1,11 +1,14 @@
package domain
import "strings"
type ApplyConfig struct {
Email string `json:"email"`
Access string `json:"access"`
KeyAlgorithm string `json:"keyAlgorithm"`
Nameservers string `json:"nameservers"`
Timeout int64 `json:"timeout"`
Email string `json:"email"`
Access string `json:"access"`
KeyAlgorithm string `json:"keyAlgorithm"`
Nameservers string `json:"nameservers"`
Timeout int64 `json:"timeout"`
DisableFollowCNAME bool `json:"disableFollowCNAME"`
}
type DeployConfig struct {
@@ -15,6 +18,92 @@ type DeployConfig struct {
Config map[string]any `json:"config"`
}
// 以字符串形式获取配置项。
//
// 入参:
// - key: 配置项的键。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回空字符串。
func (dc *DeployConfig) GetConfigAsString(key string) string {
return dc.GetConfigOrDefaultAsString(key, "")
}
// 以字符串形式获取配置项。
//
// 入参:
// - key: 配置项的键。
// - defaultValue: 默认值。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是字符串,则返回默认值。
func (dc *DeployConfig) GetConfigOrDefaultAsString(key string, defaultValue string) string {
if dc.Config == nil {
return defaultValue
}
if value, ok := dc.Config[key]; ok {
if result, ok := value.(string); ok {
return result
}
}
return defaultValue
}
// 以布尔形式获取配置项。
//
// 入参:
// - key: 配置项的键。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回 false。
func (dc *DeployConfig) GetConfigAsBool(key string) bool {
return dc.GetConfigOrDefaultAsBool(key, false)
}
// 以布尔形式获取配置项。
//
// 入参:
// - key: 配置项的键。
// - defaultValue: 默认值。
//
// 出参:
// - 配置项的值。如果配置项不存在或者类型不是布尔,则返回默认值。
func (dc *DeployConfig) GetConfigOrDefaultAsBool(key string, defaultValue bool) bool {
if dc.Config == nil {
return defaultValue
}
if value, ok := dc.Config[key]; ok {
if result, ok := value.(bool); ok {
return result
}
}
return defaultValue
}
// GetDomain returns the domain from the deploy config
// if the domain is a wildcard domain, and wildcard is true, return the wildcard domain
func (dc *DeployConfig) GetDomain(wildcard ...bool) string {
val := dc.GetConfigAsString("domain")
if val == "" {
return ""
}
if !strings.HasPrefix(val, "*") {
return val
}
if len(wildcard) > 0 && wildcard[0] {
return val
}
return strings.TrimPrefix(val, "*")
}
type KV struct {
Key string `json:"key"`
Value string `json:"value"`

23
internal/domain/err.go Normal file
View File

@@ -0,0 +1,23 @@
package domain
var ErrAuthFailed = NewXError(4999, "auth failed")
type XError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func NewXError(code int, msg string) *XError {
return &XError{code, msg}
}
func (e *XError) Error() string {
return e.Msg
}
func (e *XError) GetCode() int {
if e.Code == 0 {
return 100
}
return e.Code
}

12
internal/domain/notify.go Normal file
View File

@@ -0,0 +1,12 @@
package domain
const (
NotifyChannelDingtalk = "dingtalk"
NotifyChannelWebhook = "webhook"
NotifyChannelTelegram = "telegram"
NotifyChannelLark = "lark"
)
type NotifyTestPushReq struct {
Channel string `json:"channel"`
}

View File

@@ -0,0 +1,31 @@
package domain
import (
"encoding/json"
"fmt"
"time"
)
type Setting struct {
ID string `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type ChannelsConfig map[string]map[string]any
func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
conf := &ChannelsConfig{}
if err := json.Unmarshal([]byte(s.Content), conf); err != nil {
return nil, err
}
v, ok := (*conf)[channel]
if !ok {
return nil, fmt.Errorf("channel %s not found", channel)
}
return v, nil
}

View File

@@ -7,9 +7,9 @@ import (
"github.com/pocketbase/pocketbase/models"
"certimate/internal/applicant"
"certimate/internal/deployer"
"certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/deployer"
"github.com/usual2970/certimate/internal/utils/app"
)
type Phase string

View File

@@ -6,7 +6,7 @@ import (
"github.com/pocketbase/pocketbase/models"
"certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/utils/app"
)
func create(ctx context.Context, record *models.Record) error {

View File

@@ -3,7 +3,7 @@ package domains
import (
"github.com/pocketbase/pocketbase/core"
"certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/utils/app"
)
const tableName = "domains"

View File

@@ -5,9 +5,9 @@ import (
"github.com/pocketbase/pocketbase/models"
"certimate/internal/applicant"
"certimate/internal/utils/app"
"certimate/internal/utils/xtime"
"github.com/usual2970/certimate/internal/applicant"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/utils/xtime"
)
type historyItem struct {

View File

@@ -3,8 +3,8 @@ package domains
import (
"context"
"certimate/internal/notify"
"certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/utils/app"
)
func InitSchedule() {

View File

@@ -8,8 +8,8 @@ import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
"certimate/internal/utils/app"
"certimate/internal/utils/xtime"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/internal/utils/xtime"
)
type msg struct {

View File

@@ -5,18 +5,14 @@ import (
"fmt"
"strconv"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
notifyPackage "github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/dingding"
"github.com/nikoksr/notify/service/http"
"github.com/nikoksr/notify/service/lark"
"github.com/nikoksr/notify/service/telegram"
"certimate/internal/utils/app"
)
const (
notifyChannelDingtalk = "dingtalk"
notifyChannelWebhook = "webhook"
notifyChannelTelegram = "telegram"
)
func Send(title, content string) error {
@@ -37,6 +33,28 @@ func Send(title, content string) error {
return n.Send(context.Background(), title, content)
}
type sendTestParam struct {
Title string `json:"title"`
Content string `json:"content"`
Channel string `json:"channel"`
Conf map[string]any `json:"conf"`
}
func SendTest(param *sendTestParam) error {
notifier, err := getNotifier(param.Channel, param.Conf)
if err != nil {
return err
}
n := notifyPackage.New()
// 添加推送渠道
n.UseServices(notifier)
// 发送消息
return n.Send(context.Background(), param.Title, param.Content)
}
func getNotifiers() ([]notifyPackage.Notifier, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
if err != nil {
@@ -57,25 +75,38 @@ func getNotifiers() ([]notifyPackage.Notifier, error) {
continue
}
switch k {
case notifyChannelTelegram:
temp := getTelegramNotifier(v)
if temp == nil {
continue
}
notifiers = append(notifiers, temp)
case notifyChannelDingtalk:
notifiers = append(notifiers, getDingTalkNotifier(v))
case notifyChannelWebhook:
notifiers = append(notifiers, getWebhookNotifier(v))
notifier, err := getNotifier(k, v)
if err != nil {
continue
}
notifiers = append(notifiers, notifier)
}
return notifiers, nil
}
func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, error) {
switch channel {
case domain.NotifyChannelTelegram:
temp := getTelegramNotifier(conf)
if temp == nil {
return nil, fmt.Errorf("telegram notifier config error")
}
return temp, nil
case domain.NotifyChannelDingtalk:
return getDingTalkNotifier(conf), nil
case domain.NotifyChannelLark:
return getLarkNotifier(conf), nil
case domain.NotifyChannelWebhook:
return getWebhookNotifier(conf), nil
}
return nil, fmt.Errorf("notifier not found")
}
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
rs := http.New()
@@ -108,6 +139,10 @@ func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier {
})
}
func getLarkNotifier(conf map[string]any) notifyPackage.Notifier {
return lark.NewWebhookService(getString(conf, "webhookUrl"))
}
func getString(conf map[string]any, key string) string {
if _, ok := conf[key]; !ok {
return ""

View File

@@ -0,0 +1,46 @@
package notify
import (
"context"
"fmt"
"github.com/usual2970/certimate/internal/domain"
)
const (
notifyTestTitle = "测试通知"
notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。"
)
type SettingRepository interface {
GetByName(ctx context.Context, name string) (*domain.Setting, error)
}
type NotifyService struct {
settingRepo SettingRepository
}
func NewNotifyService(settingRepo SettingRepository) *NotifyService {
return &NotifyService{
settingRepo: settingRepo,
}
}
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
if err != nil {
return fmt.Errorf("get notify channels setting failed: %w", err)
}
conf, err := setting.GetChannelContent(req.Channel)
if err != nil {
return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err)
}
return SendTest(&sendTestParam{
Title: notifyTestTitle,
Content: notifyTestBody,
Channel: req.Channel,
Conf: conf,
})
}

View File

@@ -0,0 +1,27 @@
package uploader
import "context"
// 表示定义证书上传者的抽象类型接口。
// 云服务商通常会提供 SSL 证书管理服务,可供用户集中管理证书。
// 注意与 `Deployer` 区分,“上传”通常为“部署”的前置操作。
type Uploader interface {
// 上传证书。
//
// 入参:
// - ctx
// - certPem证书 PEM 内容
// - privkeyPem私钥 PEM 内容
//
// 出参:
// - res
// - err
Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error)
}
// 表示证书上传结果的数据结构,包含上传后的证书 ID、名称和其他数据。
type UploadResult struct {
CertId string `json:"certId"`
CertName string `json:"certName"`
CertData map[string]any `json:"certData,omitempty"`
}

View File

@@ -0,0 +1,164 @@
package uploader
import (
"context"
"fmt"
"strings"
"time"
cas20200407 "github.com/alibabacloud-go/cas-20200407/v3/client"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/usual2970/certimate/internal/pkg/utils/x509"
)
type AliyunCASUploaderConfig struct {
Region string `json:"region"`
AccessKeyId string `json:"accessKeyId"`
AccessKeySecret string `json:"accessKeySecret"`
}
type AliyunCASUploader struct {
config *AliyunCASUploaderConfig
sdkClient *cas20200407.Client
sdkRuntime *util.RuntimeOptions
}
func NewAliyunCASUploader(config *AliyunCASUploaderConfig) (*AliyunCASUploader, error) {
client, err := (&AliyunCASUploader{config: config}).createSdkClient()
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &AliyunCASUploader{
config: config,
sdkClient: client,
sdkRuntime: &util.RuntimeOptions{},
}, nil
}
func (u *AliyunCASUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
// 解析证书内容
certX509, err := x509.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
// 查询证书列表,避免重复上传
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listusercertificateorder
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-getusercertificatedetail
listUserCertificateOrderPage := int64(1)
listUserCertificateOrderLimit := int64(50)
for {
listUserCertificateOrderReq := &cas20200407.ListUserCertificateOrderRequest{
CurrentPage: tea.Int64(listUserCertificateOrderPage),
ShowSize: tea.Int64(listUserCertificateOrderLimit),
OrderType: tea.String("CERT"),
}
listUserCertificateOrderResp, err := u.sdkClient.ListUserCertificateOrderWithOptions(listUserCertificateOrderReq, u.sdkRuntime)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.ListUserCertificateOrder': %w", err)
}
if listUserCertificateOrderResp.Body.CertificateOrderList != nil {
for _, certDetail := range listUserCertificateOrderResp.Body.CertificateOrderList {
if strings.EqualFold(certX509.SerialNumber.Text(16), *certDetail.SerialNo) {
getUserCertificateDetailReq := &cas20200407.GetUserCertificateDetailRequest{
CertId: certDetail.CertificateId,
}
getUserCertificateDetailResp, err := u.sdkClient.GetUserCertificateDetailWithOptions(getUserCertificateDetailReq, u.sdkRuntime)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.GetUserCertificateDetail': %w", err)
}
var isSameCert bool
if *getUserCertificateDetailResp.Body.Cert == certPem {
isSameCert = true
} else {
cert, err := x509.ParseCertificateFromPEM(*getUserCertificateDetailResp.Body.Cert)
if err != nil {
continue
}
isSameCert = x509.EqualCertificate(certX509, cert)
}
// 如果已存在相同证书,直接返回已有的证书信息
if isSameCert {
return &UploadResult{
CertId: fmt.Sprintf("%d", tea.Int64Value(certDetail.CertificateId)),
CertName: *certDetail.Name,
}, nil
}
}
}
}
if listUserCertificateOrderResp.Body.CertificateOrderList == nil || len(listUserCertificateOrderResp.Body.CertificateOrderList) < int(listUserCertificateOrderLimit) {
break
}
listUserCertificateOrderPage += 1
if listUserCertificateOrderPage > 99 { // 避免死循环
break
}
}
// 生成新证书名(需符合阿里云命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate_%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-uploadusercertificate
uploadUserCertificateReq := &cas20200407.UploadUserCertificateRequest{
Name: tea.String(certName),
Cert: tea.String(certPem),
Key: tea.String(privkeyPem),
}
uploadUserCertificateResp, err := u.sdkClient.UploadUserCertificateWithOptions(uploadUserCertificateReq, u.sdkRuntime)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'cas.UploadUserCertificate': %w", err)
}
certId = fmt.Sprintf("%d", tea.Int64Value(uploadUserCertificateResp.Body.CertId))
return &UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func (u *AliyunCASUploader) createSdkClient() (*cas20200407.Client, error) {
region := u.config.Region
accessKeyId := u.config.AccessKeyId
accessKeySecret := u.config.AccessKeySecret
if region == "" {
region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州
}
aConfig := &openapi.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
}
var endpoint string
switch region {
case "cn-hangzhou":
endpoint = "cas.aliyuncs.com"
case "ap-southeast-1":
endpoint = "cas.ap-southeast-1.aliyuncs.com"
case "eu-central-1":
endpoint = "cas.eu-central-1.aliyuncs.com"
default:
endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region)
}
aConfig.Endpoint = tea.String(endpoint)
client, err := cas20200407.NewClient(aConfig)
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,159 @@
package uploader
import (
"context"
"fmt"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
hcElb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
hcElbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
hcElbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
"github.com/usual2970/certimate/internal/pkg/utils/cast"
"github.com/usual2970/certimate/internal/pkg/utils/x509"
)
type HuaweiCloudELBUploaderConfig struct {
Region string `json:"region"`
ProjectId string `json:"projectId"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
type HuaweiCloudELBUploader struct {
config *HuaweiCloudELBUploaderConfig
sdkClient *hcElb.ElbClient
}
func NewHuaweiCloudELBUploader(config *HuaweiCloudELBUploaderConfig) (*HuaweiCloudELBUploader, error) {
client, err := (&HuaweiCloudELBUploader{config: config}).createSdkClient()
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &HuaweiCloudELBUploader{
config: config,
sdkClient: client,
}, nil
}
func (u *HuaweiCloudELBUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
// 解析证书内容
newCert, err := x509.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
// 遍历查询已有证书,避免重复上传
// REF: https://support.huaweicloud.com/api-elb/ListCertificates.html
listCertificatesPage := 1
listCertificatesLimit := int32(2000)
var listCertificatesMarker *string = nil
for {
listCertificatesReq := &hcElbModel.ListCertificatesRequest{
Limit: cast.Int32Ptr(listCertificatesLimit),
Marker: listCertificatesMarker,
Type: &[]string{"server"},
}
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elb.ListCertificates': %w", err)
}
if listCertificatesResp.Certificates != nil {
for _, certDetail := range *listCertificatesResp.Certificates {
var isSameCert bool
if certDetail.Certificate == certPem {
isSameCert = true
} else {
cert, err := x509.ParseCertificateFromPEM(certDetail.Certificate)
if err != nil {
continue
}
isSameCert = x509.EqualCertificate(cert, newCert)
}
// 如果已存在相同证书,直接返回已有的证书信息
if isSameCert {
return &UploadResult{
CertId: certDetail.Id,
CertName: certDetail.Name,
}, nil
}
}
}
if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
break
}
listCertificatesMarker = listCertificatesResp.PageInfo.NextMarker
listCertificatesPage++
if listCertificatesPage >= 9 { // 避免死循环
break
}
}
// 生成新证书名(需符合华为云命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 创建新证书
// REF: https://support.huaweicloud.com/api-elb/CreateCertificate.html
createCertificateReq := &hcElbModel.CreateCertificateRequest{
Body: &hcElbModel.CreateCertificateRequestBody{
Certificate: &hcElbModel.CreateCertificateOption{
ProjectId: cast.StringPtr(u.config.ProjectId),
Name: cast.StringPtr(certName),
Certificate: cast.StringPtr(certPem),
PrivateKey: cast.StringPtr(privkeyPem),
},
},
}
createCertificateResp, err := u.sdkClient.CreateCertificate(createCertificateReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'elb.CreateCertificate': %w", err)
}
certId = createCertificateResp.Certificate.Id
certName = createCertificateResp.Certificate.Name
return &UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func (u *HuaweiCloudELBUploader) createSdkClient() (*hcElb.ElbClient, error) {
region := u.config.Region
accessKeyId := u.config.AccessKeyId
secretAccessKey := u.config.SecretAccessKey
if region == "" {
region = "cn-north-4" // ELB 服务默认区域:华北四北京
}
auth, err := basic.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return nil, err
}
hcRegion, err := hcElbRegion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := hcElb.ElbClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := hcElb.NewElbClient(hcClient)
return client, nil
}

View File

@@ -0,0 +1,167 @@
package uploader
import (
"context"
"fmt"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
hcScm "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3"
hcScmModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/model"
hcScmRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/scm/v3/region"
"github.com/usual2970/certimate/internal/pkg/utils/cast"
"github.com/usual2970/certimate/internal/pkg/utils/x509"
)
type HuaweiCloudSCMUploaderConfig struct {
Region string `json:"region"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
type HuaweiCloudSCMUploader struct {
config *HuaweiCloudSCMUploaderConfig
sdkClient *hcScm.ScmClient
}
func NewHuaweiCloudSCMUploader(config *HuaweiCloudSCMUploaderConfig) (*HuaweiCloudSCMUploader, error) {
client, err := (&HuaweiCloudSCMUploader{config: config}).createSdkClient()
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &HuaweiCloudSCMUploader{
config: config,
sdkClient: client,
}, nil
}
func (u *HuaweiCloudSCMUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
// 解析证书内容
certX509, err := x509.ParseCertificateFromPEM(certPem)
if err != nil {
return nil, err
}
// 遍历查询已有证书,避免重复上传
// REF: https://support.huaweicloud.com/api-ccm/ListCertificates.html
// REF: https://support.huaweicloud.com/api-ccm/ExportCertificate_0.html
listCertificatesPage := 1
listCertificatesLimit := int32(50)
listCertificatesOffset := int32(0)
for {
listCertificatesReq := &hcScmModel.ListCertificatesRequest{
Limit: cast.Int32Ptr(listCertificatesLimit),
Offset: cast.Int32Ptr(listCertificatesOffset),
SortDir: cast.StringPtr("DESC"),
SortKey: cast.StringPtr("certExpiredTime"),
}
listCertificatesResp, err := u.sdkClient.ListCertificates(listCertificatesReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'scm.ListCertificates': %w", err)
}
if listCertificatesResp.Certificates != nil {
for _, certDetail := range *listCertificatesResp.Certificates {
exportCertificateReq := &hcScmModel.ExportCertificateRequest{
CertificateId: certDetail.Id,
}
exportCertificateResp, err := u.sdkClient.ExportCertificate(exportCertificateReq)
if err != nil {
if exportCertificateResp != nil && exportCertificateResp.HttpStatusCode == 404 {
continue
}
return nil, fmt.Errorf("failed to execute sdk request 'scm.ExportCertificate': %w", err)
}
var isSameCert bool
if *exportCertificateResp.Certificate == certPem {
isSameCert = true
} else {
cert, err := x509.ParseCertificateFromPEM(*exportCertificateResp.Certificate)
if err != nil {
continue
}
isSameCert = x509.EqualCertificate(certX509, cert)
}
// 如果已存在相同证书,直接返回已有的证书信息
if isSameCert {
return &UploadResult{
CertId: certDetail.Id,
CertName: certDetail.Name,
}, nil
}
}
}
if listCertificatesResp.Certificates == nil || len(*listCertificatesResp.Certificates) < int(listCertificatesLimit) {
break
}
listCertificatesOffset += listCertificatesLimit
listCertificatesPage += 1
if listCertificatesPage > 99 { // 避免死循环
break
}
}
// 生成新证书名(需符合华为云命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://support.huaweicloud.com/api-ccm/ImportCertificate.html
importCertificateReq := &hcScmModel.ImportCertificateRequest{
Body: &hcScmModel.ImportCertificateRequestBody{
Name: certName,
Certificate: certPem,
PrivateKey: privkeyPem,
},
}
importCertificateResp, err := u.sdkClient.ImportCertificate(importCertificateReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'scm.ImportCertificate': %w", err)
}
certId = *importCertificateResp.CertificateId
return &UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func (u *HuaweiCloudSCMUploader) createSdkClient() (*hcScm.ScmClient, error) {
region := u.config.Region
accessKeyId := u.config.AccessKeyId
secretAccessKey := u.config.SecretAccessKey
if region == "" {
region = "cn-north-4" // SCM 服务默认区域:华北四北京
}
auth, err := basic.NewCredentialsBuilder().
WithAk(accessKeyId).
WithSk(secretAccessKey).
SafeBuild()
if err != nil {
return nil, err
}
hcRegion, err := hcScmRegion.SafeValueOf(region)
if err != nil {
return nil, err
}
hcClient, err := hcScm.ScmClientBuilder().
WithRegion(hcRegion).
WithCredential(auth).
SafeBuild()
if err != nil {
return nil, err
}
client := hcScm.NewScmClient(hcClient)
return client, nil
}

View File

@@ -0,0 +1,91 @@
package uploader
import (
"context"
"fmt"
"time"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
"github.com/usual2970/certimate/internal/pkg/utils/cast"
)
type TencentCloudSSLUploaderConfig struct {
Region string `json:"region"`
SecretId string `json:"secretId"`
SecretKey string `json:"secretKey"`
}
type TencentCloudSSLUploader struct {
config *TencentCloudSSLUploaderConfig
sdkClient *tcSsl.Client
}
func NewTencentCloudSSLUploader(config *TencentCloudSSLUploaderConfig) (*TencentCloudSSLUploader, error) {
client, err := (&TencentCloudSSLUploader{config: config}).createSdkClient()
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
return &TencentCloudSSLUploader{
config: config,
sdkClient: client,
}, nil
}
func (u *TencentCloudSSLUploader) Upload(ctx context.Context, certPem string, privkeyPem string) (res *UploadResult, err error) {
// 生成新证书名(需符合腾讯云命名规则)
var certId, certName string
certName = fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传新证书
// REF: https://cloud.tencent.com/document/product/400/41665
uploadCertificateReq := &tcSsl.UploadCertificateRequest{
Alias: cast.StringPtr(certName),
CertificatePublicKey: cast.StringPtr(certPem),
CertificatePrivateKey: cast.StringPtr(privkeyPem),
Repeatable: cast.BoolPtr(false),
}
uploadCertificateResp, err := u.sdkClient.UploadCertificate(uploadCertificateReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.UploadCertificate': %w", err)
}
// 获取证书详情
// REF: https://cloud.tencent.com/document/api/400/41673
//
// P.S. 上传重复证书会返回上一次的证书 ID这里需要重新获取一遍证书名https://github.com/usual2970/certimate/pull/227
describeCertificateDetailReq := &tcSsl.DescribeCertificateDetailRequest{
CertificateId: uploadCertificateResp.Response.CertificateId,
}
describeCertificateDetailResp, err := u.sdkClient.DescribeCertificateDetail(describeCertificateDetailReq)
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'ssl.DescribeCertificateDetail': %w", err)
}
certId = *describeCertificateDetailResp.Response.CertificateId
certName = *describeCertificateDetailResp.Response.Alias
return &UploadResult{
CertId: certId,
CertName: certName,
}, nil
}
func (u *TencentCloudSSLUploader) createSdkClient() (*tcSsl.Client, error) {
region := u.config.Region
secretId := u.config.SecretId
secretKey := u.config.SecretKey
if region == "" {
region = "ap-guangzhou" // SSL 服务默认区域:广州
}
credential := common.NewCredential(secretId, secretKey)
client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile())
if err != nil {
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,25 @@
package cast
func Int32Ptr(i int32) *int32 {
return &i
}
func Int64Ptr(i int64) *int64 {
return &i
}
func UInt32Ptr(i uint32) *uint32 {
return &i
}
func UInt64Ptr(i uint64) *uint64 {
return &i
}
func StringPtr(s string) *string {
return &s
}
func BoolPtr(b bool) *bool {
return &b
}

View File

@@ -0,0 +1,48 @@
package x509
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
// 从 PEM 编码的证书字符串解析并返回一个 x509.Certificate 对象。
//
// 入参:
// - certPem: 证书 PEM 内容。
//
// 出参:
// - cert:
// - err:
func ParseCertificateFromPEM(certPem string) (cert *x509.Certificate, err error) {
pemData := []byte(certPem)
block, _ := pem.Decode(pemData)
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
return cert, nil
}
// 比较两个 x509.Certificate 对象,判断它们是否是同一张证书。
// 注意,这不是精确比较,而只是基于证书序列号和数字签名的快速判断,但对于权威 CA 签发的证书来说不会存在误判。
//
// 入参:
// - a: 待比较的第一个 x509.Certificate 对象。
// - b: 待比较的第二个 x509.Certificate 对象。
//
// 出参:
// - 是否相同。
func EqualCertificate(a, b *x509.Certificate) bool {
return string(a.Signature) == string(b.Signature) &&
a.SignatureAlgorithm == b.SignatureAlgorithm &&
a.SerialNumber.String() == b.SerialNumber.String() &&
a.Issuer.SerialNumber == b.Issuer.SerialNumber &&
a.Subject.SerialNumber == b.Subject.SerialNumber
}

View File

@@ -0,0 +1,31 @@
package repository
import (
"context"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/app"
)
type SettingRepository struct{}
func NewSettingRepository() *SettingRepository {
return &SettingRepository{}
}
func (s *SettingRepository) GetByName(ctx context.Context, name string) (*domain.Setting, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='"+name+"'")
if err != nil {
return nil, err
}
rs := &domain.Setting{
ID: resp.GetString("id"),
Name: resp.GetString("name"),
Content: resp.GetString("content"),
Created: resp.GetTime("created"),
Updated: resp.GetTime("updated"),
}
return rs, nil
}

41
internal/rest/notify.go Normal file
View File

@@ -0,0 +1,41 @@
package rest
import (
"context"
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/utils/resp"
"github.com/labstack/echo/v5"
)
type NotifyService interface {
Test(ctx context.Context, req *domain.NotifyTestPushReq) error
}
type notifyHandler struct {
service NotifyService
}
func NewNotifyHandler(route *echo.Group, service NotifyService) {
handler := &notifyHandler{
service: service,
}
group := route.Group("/notify")
group.POST("/test", handler.test)
}
func (handler *notifyHandler) test(c echo.Context) error {
req := &domain.NotifyTestPushReq{}
if err := c.Bind(req); err != nil {
return err
}
if err := handler.service.Test(c.Request().Context(), req); err != nil {
return resp.Err(c, err)
}
return resp.Succ(c, nil)
}

19
internal/routes/routes.go Normal file
View File

@@ -0,0 +1,19 @@
package routes
import (
"github.com/usual2970/certimate/internal/notify"
"github.com/usual2970/certimate/internal/repository"
"github.com/usual2970/certimate/internal/rest"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/apis"
)
func Register(e *echo.Echo) {
notifyRepo := repository.NewSettingRepository()
notifySvc := notify.NewNotifyService(notifyRepo)
group := e.Group("/api", apis.RequireAdminAuth())
rest.NewNotifyHandler(group, notifySvc)
}

View File

@@ -0,0 +1,39 @@
package resp
import (
"net/http"
"github.com/usual2970/certimate/internal/domain"
"github.com/labstack/echo/v5"
)
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func Succ(e echo.Context, data interface{}) error {
rs := &Response{
Code: 0,
Msg: "success",
Data: data,
}
return e.JSON(http.StatusOK, rs)
}
func Err(e echo.Context, err error) error {
xerr, ok := err.(*domain.XError)
code := 100
if ok {
code = xerr.GetCode()
}
rs := &Response{
Code: code,
Msg: err.Error(),
Data: nil,
}
return e.JSON(http.StatusOK, rs)
}

23
main.go
View File

@@ -1,6 +1,7 @@
package main
import (
"flag"
"log"
"os"
"strings"
@@ -10,10 +11,12 @@ import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/plugins/migratecmd"
"certimate/internal/domains"
"certimate/internal/utils/app"
_ "certimate/migrations"
"certimate/ui"
_ "github.com/usual2970/certimate/migrations"
"github.com/usual2970/certimate/internal/domains"
"github.com/usual2970/certimate/internal/routes"
"github.com/usual2970/certimate/internal/utils/app"
"github.com/usual2970/certimate/ui"
_ "time/tzdata"
)
@@ -23,6 +26,12 @@ func main() {
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
// 获取启动命令中的http参数
var httpFlag string
flag.StringVar(&httpFlag, "http", "127.0.0.1:8090", "HTTP server address")
// "serve"影响解析
_ = flag.CommandLine.Parse(os.Args[2:])
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
// enable auto creation of migration files when making collection changes in the Admin UI
// (the isGoRun check is to enable it only during development)
@@ -32,9 +41,10 @@ func main() {
domains.AddEvent()
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
domains.InitSchedule()
routes.Register(e.Router)
e.Router.GET(
"/*",
echo.StaticDirectoryHandler(ui.DistDirFS, false),
@@ -44,6 +54,9 @@ func main() {
return nil
})
defer log.Println("Exit!")
log.Printf("Visit the website: http://%s", httpFlag)
if err := app.Start(); err != nil {
log.Fatal(err)
}

View File

@@ -11,7 +11,7 @@ import (
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
if err != nil {
@@ -47,7 +47,7 @@ func init() {
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
if err != nil {

View File

@@ -11,7 +11,7 @@ import (
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
@@ -50,7 +50,7 @@ func init() {
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {

View File

@@ -0,0 +1,93 @@
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("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"aws",
"cloudflare",
"namesilo",
"godaddy",
"local",
"ssh",
"webhook"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"cloudflare",
"namesilo",
"godaddy",
"local",
"ssh",
"webhook"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
})
}

View File

@@ -0,0 +1,95 @@
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("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"aws",
"cloudflare",
"namesilo",
"godaddy",
"local",
"ssh",
"webhook",
"k8s"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"aws",
"cloudflare",
"namesilo",
"godaddy",
"local",
"ssh",
"webhook"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
})
}

View File

@@ -0,0 +1,98 @@
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("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"aws",
"cloudflare",
"namesilo",
"godaddy",
"pdns",
"httpreq",
"local",
"ssh",
"webhook",
"k8s"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db)
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"aws",
"cloudflare",
"namesilo",
"godaddy",
"local",
"ssh",
"webhook",
"k8s"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
})
}

1
ui/dist/.gitkeep vendored Normal file
View File

@@ -0,0 +1 @@


View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9731" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M1020.586667 361.813333c0-92.16-75.093333-167.253333-167.253334-167.253333h-266.24l23.893334 95.573333 228.693333 51.2c20.48 3.413333 37.546667 23.893333 37.546667 44.373334v245.76c0 20.48-17.066667 40.96-37.546667 44.373333l-228.693333 51.2-27.306667 92.16h266.24c92.16 0 167.253333-75.093333 167.253333-167.253333 3.413333 0 3.413333-290.133333 3.413334-290.133334zM187.733333 672.426667c-20.48-3.413333-37.546667-23.893333-37.546666-44.373334v-245.76c0-20.48 17.066667-40.96 37.546666-44.373333l228.693334-51.2 23.893333-95.573333H174.08C81.92 191.146667 6.826667 266.24 6.826667 358.4V648.533333c0 92.16 75.093333 167.253333 167.253333 167.253334h266.24l-23.893333-95.573334c0 3.413333-228.693333-47.786667-228.693334-47.786666z m215.04-211.626667h218.453334v88.746667h-218.453334v-88.746667z" fill="#ff6b01" p-id="9732"></path></svg>

Before

Width:  |  Height:  |  Size: 1017 B

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4261" width="200" height="200"><path d="M704.38432 718.72c6.304-21.632 3.872-41.408-6.624-56.128-9.568-13.504-25.792-21.28-45.28-22.208l-369.472-4.8h-0.096a6.688 6.688 0 0 1-5.568-3.008l-0.032-0.032a8.192 8.192 0 0 1-0.896-6.624 10.24 10.24 0 0 1 8.672-6.656l372.736-4.8c44.16-2.08 92.16-37.824 108.96-81.632l21.28-55.52a11.584 11.584 0 0 0 0.64-7.168 242.4 242.4 0 0 0-236.8-189.664 242.656 242.656 0 0 0-229.856 164.768 110.176 110.176 0 0 0-76.544-21.28 109.28 109.28 0 0 0-94.816 135.648 155.04 155.04 0 0 0-150.656 155.168c0 7.456 0.608 15.008 1.504 22.496a7.456 7.456 0 0 0 7.2 6.304h681.888a9.312 9.312 0 0 0 8.672-6.624l5.12-18.208z m117.6-237.376c-3.296 0-6.88 0-10.176 0.48-2.4 0-4.512 1.76-5.408 4.16l-14.4 50.112c-6.304 21.632-3.904 41.408 6.592 56.16 9.632 13.504 25.824 21.248 45.344 22.176l78.656 4.832c2.368 0 4.512 1.12 5.664 3.008a8.64 8.64 0 0 1 0.928 6.656 10.24 10.24 0 0 1-8.704 6.624l-81.952 4.8c-44.416 2.08-92.096 37.824-108.928 81.664l-5.984 15.296c-1.216 3.04 0.928 6.048 4.192 6.048h281.504a7.36 7.36 0 0 0 7.2-5.376c4.8-17.408 7.488-35.712 7.488-54.624 0-111.04-90.656-201.696-202.016-201.696z" fill="#F38020" p-id="4262"></path></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5245" width="200" height="200"><path d="M683.52 924.16c69.632-32.768 165.888-91.648 245.76-194.56 20.48-26.112 37.376-52.224 51.2-76.8 12.8-31.232 27.648-76.8 35.84-133.12 18.432-127.488-12.288-222.208-20.48-245.76-12.8-37.376-29.696-80.896-71.68-122.88-65.536-65.536-145.408-78.336-168.96-81.92-57.856-8.192-103.424 2.048-138.24 10.24-23.552 5.632-68.096 16.384-117.76 46.08-39.424 24.064-64.512 48.64-81.92 66.56-48.64 49.152-73.216 95.232-107.52 158.72-20.48 37.376-37.376 70.144-51.2 117.76-2.56 9.216-9.216 32.768-15.36 76.8-6.144 41.984-11.776 101.376-10.24 175.104-50.176-82.432-79.36-154.112-97.28-205.824-15.872-46.08-22.016-74.752-25.6-102.4-7.168-56.832-13.824-110.08 15.36-158.72 38.912-64.512 116.224-78.848 138.752-83.456 95.232-17.408 169.984 29.696 188.928 42.496 27.136-24.064 54.784-47.616 81.92-71.68-31.744-25.088-89.088-62.464-168.96-76.8-16.896-3.072-57.856-9.216-109.056-4.096-39.424 4.096-96.768 9.728-152.064 50.176-11.776 8.704-46.08 35.328-71.68 81.92-38.912 71.168-32.768 142.848-25.6 230.4 3.584 44.032 10.24 79.872 15.36 102.4 34.816 125.44 86.528 210.432 122.88 261.12 29.184 39.936 51.2 61.952 57.856 68.608 27.648 27.136 95.232 91.136 203.264 115.712 31.744 7.168 98.304 21.504 179.2-1.536 27.136-7.68 99.84-29.184 155.648-96.256 76.288-91.136 69.12-202.752 64-270.848-4.608-71.68-24.576-115.2-30.72-128-20.992-43.52-47.616-73.728-66.56-92.16-80.384 32.256-160.256 65.024-240.64 97.28l34.816 86.528 164.864-71.168c13.824 24.064 34.304 67.072 35.84 122.88 2.56 92.16-46.592 203.264-143.36 240.64-50.688 19.456-131.584 25.088-179.2-20.48-28.672-27.136-33.28-61.44-40.96-117.76-4.096-28.16-12.8-111.104 15.36-215.04 10.24-37.888 34.816-113.152 92.16-189.44 33.28-44.032 62.976-69.632 71.68-76.8 27.648-23.04 57.344-47.616 100.864-61.44 16.896-5.12 99.328-28.672 178.688 17.92 67.584 39.424 90.112 104.96 99.328 130.048 13.824 38.912 14.336 70.656 15.36 97.28 0.512 26.624 2.048 84.992-25.6 153.6-24.064 59.392-58.368 99.84-81.92 122.88-40.448 75.776-81.408 150.528-122.368 225.792z" fill="#13EAE4" p-id="5246"></path></svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4252" width="200" height="200"><path d="M378.88 143.36c20.48-6.826667 40.96-10.24 61.44-13.653333 23.893333 30.72 30.72 71.68 40.96 109.226666 6.826667 40.96 13.653333 81.92 13.653333 122.88 6.826667 27.306667 3.413333 58.026667 3.413334 85.333334s-3.413333 51.2 0 78.506666c0 37.546667-3.413333 71.68-3.413334 109.226667-6.826667 23.893333 0 47.786667-6.826666 71.68-10.24-3.413333-13.653333-13.653333-17.066667-20.48-40.96-61.44-78.506667-122.88-112.64-187.733333-34.133333-68.266667-68.266667-136.533333-71.68-215.04-6.826667-61.44 34.133333-119.466667 92.16-139.946667z m211.626667-10.24c6.826667-3.413333 10.24 0 17.066666 0 27.306667 6.826667 58.026667 10.24 81.92 27.306667s44.373333 40.96 51.2 68.266666c10.24 34.133333 6.826667 71.68 0 105.813334-10.24 44.373333-30.72 85.333333-51.2 126.293333-6.826667 13.653333-13.653333 23.893333-20.48 37.546667-10.24 23.893333-27.306667 47.786667-40.96 71.68-23.893333 40.96-47.786667 75.093333-71.68 116.053333-3.413333 3.413333-6.826667 13.653333-13.653333 6.826667-3.413333-40.96-6.826667-81.92-10.24-126.293334-6.826667-54.613333-3.413333-105.813333-3.413333-160.426666 3.413333-34.133333 3.413333-71.68 6.826666-105.813334 6.826667-40.96 13.653333-85.333333 27.306667-126.293333 13.653333-10.24 13.653333-30.72 27.306667-40.96zM160.426667 266.24c3.413333 0 6.826667 6.826667 10.24 10.24 98.986667 129.706667 187.733333 266.24 259.413333 413.013333 6.826667 10.24 13.653333 23.893333 13.653333 37.546667-13.653333-3.413333-23.893333-10.24-34.133333-17.066667-64.853333-34.133333-129.706667-71.68-194.56-109.226666-23.893333-17.066667-47.786667-34.133333-68.266667-51.2-40.96-27.306667-68.266667-78.506667-64.853333-129.706667 3.413333-61.44 37.546667-112.64 78.506667-153.6z m706.56 0h6.826666c17.066667 23.893333 40.96 47.786667 54.613334 75.093333 13.653333 23.893333 20.48 54.613333 23.893333 81.92 0 30.72-10.24 64.853333-34.133333 88.746667-13.653333 13.653333-23.893333 27.306667-40.96 37.546667-78.506667 61.44-163.84 109.226667-252.586667 153.6-13.653333 6.826667-23.893333 17.066667-40.96 17.066666 3.413333-17.066667 13.653333-34.133333 20.48-47.786666 58.026667-119.466667 129.706667-232.106667 208.213333-341.333334 17.066667-17.066667 37.546667-40.96 54.613334-64.853333z m-856.746667 273.066667c3.413333-3.413333 0-10.24 6.826667-13.653334 10.24 3.413333 20.48 10.24 27.306666 13.653334 122.88 68.266667 245.76 136.533333 365.226667 211.626666 3.413333 3.413333 6.826667 6.826667 6.826667 10.24H180.906667c-47.786667 0-92.16-20.48-126.293334-54.613333-27.306667-30.72-51.2-71.68-54.613333-112.64 6.826667-17.066667 3.413333-34.133333 10.24-54.613333z m983.04-3.413334c6.826667-3.413333 17.066667-10.24 23.893333-6.826666 0 17.066667 6.826667 37.546667 6.826667 54.613333-3.413333 23.893333-3.413333 44.373333-13.653333 64.853333-6.826667 17.066667-17.066667 37.546667-30.72 51.2-17.066667 13.653333-27.306667 30.72-47.786667 40.96-20.48 17.066667-51.2 20.48-75.093333 23.893334h-245.76c3.413333-3.413333 3.413333-6.826667 6.826666-10.24 122.88-78.506667 249.173333-150.186667 375.466667-218.453334zM184.32 798.72c44.373333-3.413333 88.746667 0 133.12-6.826667 30.72 0 64.853333-3.413333 95.573333 0-6.826667 13.653333-23.893333 20.48-34.133333 27.306667-34.133333 23.893333-68.266667 44.373333-105.813333 61.44s-81.92 10.24-112.64-13.653333c-23.893333-17.066667-44.373333-44.373333-58.026667-68.266667h81.92z m433.493333-6.826667c30.72-3.413333 61.44 0 95.573334 0 40.96 3.413333 85.333333 0 129.706666 6.826667 30.72 3.413333 61.44 0 88.746667 3.413333-10.24 20.48-27.306667 40.96-44.373333 58.026667-27.306667 27.306667-68.266667 40.96-105.813334 34.133333-34.133333-10.24-61.44-30.72-92.16-47.786666-17.066667-10.24-30.72-20.48-47.786666-30.72-10.24-10.24-17.066667-13.653333-23.893334-23.893334z" fill="#C71F1E" p-id="4253"></path></svg>

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6224" width="200" height="200"><path d="M776.416 1024H247.648a41.184 41.184 0 0 1-41.056-41.024V591.808c0-22.56 18.464-41.056 41.056-41.056h49.664v-63.232c0-118.4 96.352-214.688 214.688-214.688s214.688 96.352 214.688 214.688v63.232h49.664c22.56 0 41.056 18.464 41.056 41.056v391.168a41.184 41.184 0 0 1-41.024 41.056z m-237.632-216.416a54.4 54.4 0 0 0-26.688-101.728h-0.128a54.4 54.4 0 0 0-27.072 101.568l0.256 0.128v52.992a26.784 26.784 0 0 0 53.568 0v-52.992z m-118.336-256.832h183.168v-63.232c0-50.464-41.088-91.552-91.552-91.552s-91.552 41.088-91.552 91.552v63.232z m-226.432-58.304H66.432a37.44 37.44 0 1 1 0-74.944h127.584a37.44 37.44 0 1 1 0 74.944z m89.888-200.704h-0.096c-9.024 0-17.312-3.232-23.744-8.576l0.064 0.064-100.896-82.976a37.44 37.44 0 1 1 47.68-57.856l-0.064-0.064 100.896 82.976a37.44 37.44 0 0 1-23.808 66.4h-0.064zM512 203.52a37.44 37.44 0 0 1-37.472-37.472V37.44a37.44 37.44 0 1 1 74.944 0v128.608A37.44 37.44 0 0 1 512 203.52z m228.096 88.224h-0.16a37.44 37.44 0 0 1-23.744-66.336l0.064-0.064 100.896-82.976a37.44 37.44 0 0 1 47.68 57.824l-0.064 0.064-100.896 82.976c-6.4 5.312-14.72 8.544-23.776 8.544z m217.472 200.704h-128.8a37.44 37.44 0 1 1 0-74.944h128.8a37.44 37.44 0 1 1 0 74.944z" fill="#003A70" p-id="6225"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="26535" width="200" height="200"><path d="M339.008 128a64 64 0 0 1 41.6 15.36L512 256h384a64 64 0 0 1 64 64v512a64 64 0 0 1-64 64H128a64 64 0 0 1-64-64V192a64 64 0 0 1 64-64h211.008zM883.2 486.4H140.8v332.8h742.4V486.4zM334.208 204.736L288 204.8H140.8v204.8h742.4V332.8H483.584l-21.568-18.496L334.208 204.8z" fill="#525962" p-id="26536"></path></svg>

Before

Width:  |  Height:  |  Size: 447 B

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7279" width="200" height="200"><path d="M799.8 943.7H220c-59.5-4.6-99-37.8-126-88.8C81.8 832 81.8 806.8 81.7 782c-0.4-180.3 1.5-360.7-1-541-1.2-83.1 69.1-160.3 156.5-159.6 184.7 1.6 369.4 1.1 554.1 0.2 76.9-0.4 152.1 67.6 151.6 155.1-1 186 0.3 372.1-0.6 558.1-0.4 80.6-57.4 139.3-138.7 147-1.4 0.2-2.6 1.2-3.8 1.9z" fill="#031B4E" p-id="7280"></path><path d="M500.9 435.5c-47.8-0.3-94.7-5.6-140.2-21.4-17.7-6.2-23.7-16.3-25.3-35.9-2.6-30.7 8.4-47.2 35-61.3 41.9-22.2 81.6-48.7 121.7-74 10.2-6.4 17.9-7 28.4-0.1 44.9 29.8 90.3 58.9 135.9 87.7 8.2 5.1 11.3 10.5 11.5 20.3 1.2 57.7 1.1 58-55.2 72.5-36.7 9.3-74.1 12.3-111.8 12.2zM336.3 543.5c111.7 39.3 220.4 39.5 331.2 0V628c0 9.6-7 13-14 16.8-20.4 10.9-42.6 15.3-65 18.6-73 10.8-145.6 10.5-217-10.4-34.5-10.1-35.2-12.1-35.2-47.2v-62.3z" fill="#FEFEFE" p-id="7281"></path><path d="M667.6 423.1c0 26-1.5 50.3 0.5 74.4 1.7 20.6-8 29.6-25.2 35.6-38.7 13.5-78.8 17.9-119.2 19.4-53.4 2-106.2-2-157.7-18.1-20.9-6.5-32.4-16.8-30-41.1 2.2-22.5 0.5-45.4 0.5-69.9 52.9 27.5 109.2 31.7 165.8 31.7 55.8-0.1 111.6-3.7 165.3-32zM336.4 661.3c111.6 38.1 220.2 39 331.2-0.2 0 26.2-0.1 54.9 0 83.6 0.1 11.8-8.9 15.4-17.2 19.2-25.5 11.6-52.8 16.4-80.2 19.3-63.8 6.8-127.4 6.4-189.9-10.3-10.8-2.9-21.3-7.3-31.6-11.8-8.4-3.7-12.7-10.2-12.5-20.2 0.5-26.9 0.2-53.9 0.2-79.6z" fill="#FEFEFE" p-id="7282"></path></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8269" width="200" height="200"><path d="M998.4 191.488c-1.536-3.072-5.12-6.144-11.776-4.608-12.8 3.072-159.232 246.784-474.624 240.128-59.904 1.024-113.664-6.656-161.792-19.968l-25.6-87.04s-5.12-21.504-26.112-32.768c-14.336-7.68-23.04-5.12-24.576-3.072-1.536 2.048-1.536 4.096-1.536 4.096l12.8 96.256C124.928 315.904 46.08 189.44 36.864 187.392c-6.656-1.536-10.24 1.536-11.776 4.608-2.56 5.12 0 12.288 0 12.288 45.568 134.144 143.36 240.128 269.824 296.448l34.816 232.448c2.56 66.56 47.104 104.448 104.96 104.448h173.568c57.856 0 101.376-41.472 104.96-104.448l31.744-192.512s0.512-2.56-1.024-3.584c-2.048-1.024-16.896-1.536-47.616 20.992-30.72 22.528-40.96 55.296-40.96 55.296s-33.28 79.872-41.984 114.176c-9.216 35.84-49.664 32.768-49.664 32.768h-92.16c-31.232 0-34.304-27.648-34.304-27.648L378.88 529.408c42.496 10.752 86.528 16.384 133.12 15.872 228.352 1.024 417.792-137.728 486.912-341.504 0 0 2.56-7.168-0.512-12.288" fill="#00AAE7" p-id="8270"></path></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="23812" width="200" height="200"><path d="M128 128h768a42.667 42.667 0 0 1 42.667 42.667v682.666A42.667 42.667 0 0 1 896 896H128a42.667 42.667 0 0 1-42.667-42.667V170.667A42.667 42.667 0 0 1 128 128z m384 512v85.333h256V640H512zM358.997 512L238.336 632.661l60.33 60.374L479.702 512 298.667 330.965l-60.331 60.374L358.997 512z" p-id="23813"></path></svg>

Before

Width:  |  Height:  |  Size: 451 B

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="17958" width="200" height="200"><path d="M512 170.666667c130.474667 0 240.938667 83.797333 277.930667 199.296a198.826667 198.826667 0 0 0-41.557334-0.597334 222.293333 222.293333 0 0 0-49.706666 10.624C668.202667 309.333333 596.096 259.754667 512 259.754667c-100.266667 0-183.466667 70.528-199.381333 163.029333a279.04 279.04 0 0 0-89.429334-3.84C241.28 278.954667 363.690667 170.666667 512 170.666667z" fill="#006DFE" p-id="17959"></path><path d="M258.474667 417.322667c54.442667 0 104.192 20.181333 142.165333 53.418666 16.085333 14.08 45.226667 39.68 87.381333 76.8l-7.381333-6.528-61.568 60.885334-54.4-54.4c-34.218667-34.261333-66.090667-47.957333-106.197333-47.957334a133.589333 133.589333 0 0 0 0 267.221334c10.666667 0 29.312 0.768 56.064 2.346666l-90.453334 77.141334A215.893333 215.893333 0 0 1 258.432 417.28z" fill="#00CDD8" p-id="17960"></path><path d="M674.346667 434.474667a215.808 215.808 0 0 1 168.618666 397.354666c-15.36 6.485333-38.186667 15.957333-63.146666 16.213334-72.106667 0.597333-244.181333 0.896-516.352 0.938666h-42.666667a206248.106667 206248.106667 0 0 0 397.013333-380.714666c18.261333-17.578667 41.130667-27.264 56.533334-33.792z m41.856 80.554666c-9.258667 3.925333-23.04 9.770667-34.048 20.352-30.165333 29.098667-109.952 105.642667-239.445334 229.632h53.418667c148.181333 0 242.773333-0.213333 283.733333-0.554666 15.061333-0.128 28.842667-5.845333 38.101334-9.813334a130.133333 130.133333 0 0 0-101.76-239.616z" fill="#00A2FF" p-id="17961"></path></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19933" width="200" height="200"><path d="M749.1 515.5c-23.8 0.1-47.3 4.5-69.6 12.9l-80.1-159.5c28.6-31.1 31.5-78 6.9-112.3-24.6-34.4-69.9-46.8-108.5-29.7s-60.1 58.8-51.3 100.2c8.7 41.3 45.3 70.9 87.5 70.8 3.3 0.2 6.6 0.2 9.8 0l107.6 214.5 27.8-13.9c49.1-28.4 110.6-24.4 155.6 10.2 45 34.6 64.7 92.9 49.9 147.7-14.8 54.8-61.2 95.2-117.5 102.4-56.3 7.2-111.4-20.3-139.5-69.6l-54.3 31.6c49.4 85.6 153.2 123.4 246.1 89.5 92.9-33.9 148-129.6 130.6-227S848 514.9 749.1 515.2v0.3z" fill="#3296FA" p-id="19934"></path><path d="M404.3 463.6L295.7 630.2c-6.8-1.4-13.7-2-20.7-2-41.6-0.1-77.8 28.2-87.7 68.6-9.8 40.4 9.3 82.3 46.3 101.3s82.2 10.2 109.3-21.3 29.1-77.5 4.8-111.2l142.5-218.9-26.1-17c-49.7-28.2-77.4-83.7-70.1-140.3 7.3-56.7 48-103.3 103.2-118.1 55.2-14.8 113.8 5.2 148.5 50.6 34.6 45.4 38.4 107.3 9.5 156.6l54.3 31.2c18.1-30.9 27.6-66 27.5-101.8 1-94.9-63.7-177.9-156.1-200.1-92.3-22.2-187.7 22.4-229.9 107.4s-20.1 188 53.4 248.1v0.3z" fill="#3296FA" p-id="19935"></path><path d="M665.3 749.3c15.1 40.6 57.2 64.6 99.8 57 42.7-7.7 73.7-44.8 73.7-88.2 0-43.4-31.1-80.5-73.7-88.2s-84.7 16.3-99.8 57H415.2v31.2c0 77.6-62.9 140.5-140.5 140.5s-140.5-62.9-140.5-140.5 62.9-140.5 140.5-140.5v-63.1c-108.6-0.6-198.6 84.2-204.4 192.7s74.5 202.4 182.5 213.5c108 11.1 205.8-64.6 222.1-172l190.4 0.6z" fill="#3296FA" p-id="19936"></path></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
<svg class="icon" viewBox="0 0 1113 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18956" width="200" height="200"><path d="M107.287 512.928c0-79.047-0.045-158.094 0.03-237.141 0.02-21.15 1.256-42.135 9.203-62.181 23.74-59.883 67.166-95.237 130.865-105.518 8.582-1.385 17.221-1.667 25.889-1.667 159.718 0.009 319.436-0.021 479.155 0.019 74.315 0.019 133.182 41.364 157.993 111.108 6.12 17.204 8.542 35.132 8.54 53.403-0.022 161.072 0.122 322.144-0.072 483.215-0.091 75.418-50.342 141.59-119.876 158.868-15.044 3.738-30.331 5.422-45.807 5.423-160.26 0.014-320.521 0.29-480.78-0.12-71.188-0.182-121.825-33.848-152.353-97.864-10.72-22.48-12.842-46.672-12.817-71.218 0.081-78.774 0.03-157.551 0.03-236.327z m283.66-4.813v136.454H254.062v138.583h138.402V645.64h138.004v137.437h138.576V644.705h137.443V505.977H668.339V367.658H530.496V229.98H391.685v138.154H254.216c-0.365 1.798-0.66 2.576-0.661 3.354-0.035 43.047 0.079 86.094-0.188 129.139-0.042 6.738 3.542 6.504 8.205 6.495 39.798-0.073 79.596-0.06 119.394-0.007 3.173 0.005 6.462-0.676 9.981 1z" fill="#4D70D4" p-id="18957"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

14
ui/dist/index.html vendored
View File

@@ -1,14 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-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-BTjl7ZFn.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-YqBWA4KK.css">
</head>
<body class="bg-background">
<div id="root"></div>
</body>
</html>

28
ui/dist/vite.svg vendored
View File

@@ -1,28 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.92591302712353 216.92591302712353" height="216.92591302712353"
width="216.92591302712353">
<g><svg /></g>
<g id="icon-0"><svg viewBox="0 0 216.92591302712353 216.92591302712353" height="216.92591302712353"
width="216.92591302712353">
<g>
<path
d="M0 108.463c0-59.902 48.561-108.463 108.463-108.463 59.902 0 108.463 48.561 108.463 108.463 0 59.902-48.561 108.463-108.463 108.463-59.902 0-108.463-48.561-108.463-108.463zM108.463 211.779c57.06 0 103.316-46.256 103.316-103.316 0-57.06-46.256-103.316-103.316-103.316-57.06 0-103.316 46.256-103.316 103.316 0 57.06 46.256 103.316 103.316 103.316z"
data-fill-palette-color="accent" fill="#f97317" stroke="transparent" />
<ellipse rx="107.37832694842615" ry="107.37832694842615" cx="108.46295651356176" cy="108.46295651356176"
fill="#f97317" stroke="transparent" stroke-width="0" fill-opacity="1"
data-fill-palette-color="accent" />
</g>
<g transform="matrix(1,0,0,1,64.03903745467258,44.049576960135155)"><svg
viewBox="0 0 88.84783811777835 128.82675910685322" height="128.82675910685322"
width="88.84783811777835">
<g><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
x="0" y="0" viewBox="4.99999928747888 13.445 56.29700142504224 81.6290007125211"
enable-background="new 0 0 100 100" xml:space="preserve" height="128.82675910685322"
width="88.84783811777835" class="icon-dxe-0" data-fill-palette-color="quaternary"
id="dxe-0">
<path
d="M58.482 47.222H55.667V35.963C55.667 23.527 45.587 13.445 33.148 13.445S10.63 23.528 10.63 35.963V47.222H7.815A2.813 2.813 0 0 0 5 50.037V92.259A2.813 2.813 0 0 0 7.815 95.074H58.482A2.813 2.813 0 0 0 61.297 92.259V50.037A2.815 2.815 0 0 0 58.482 47.222M35.963 72.039V86.63H30.333V72.039C27.062 70.876 24.703 67.784 24.703 64.111A8.445 8.445 0 0 1 41.591 64.111C41.593 67.784 39.234 70.876 35.963 72.039M47.222 47.222H19.074V35.963C19.074 28.203 25.388 21.889 33.148 21.889S47.222 28.203 47.222 35.963z"
fill="#ffffff" data-fill-palette-color="quaternary" />
</svg></g>
</svg></g>
</svg></g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

62
ui/package-lock.json generated
View File

@@ -48,6 +48,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.0.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
@@ -60,6 +61,7 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"fs-extra": "^11.2.0",
"postcss": "^8.4.40",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.7",
@@ -2453,6 +2455,25 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/fs-extra": {
"version": "11.0.4",
"resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-11.0.4.tgz",
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
"dev": true,
"dependencies": {
"@types/jsonfile": "*",
"@types/node": "*"
}
},
"node_modules/@types/jsonfile": {
"version": "6.1.4",
"resolved": "https://registry.npmmirror.com/@types/jsonfile/-/jsonfile-6.1.4.tgz",
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "22.0.0",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.0.0.tgz",
@@ -3699,6 +3720,20 @@
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3826,6 +3861,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
@@ -4130,6 +4171,18 @@
"node": ">=6"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz",
@@ -5525,6 +5578,15 @@
"integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==",
"dev": true
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/update-browserslist-db": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",

View File

@@ -50,6 +50,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.0.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
@@ -62,6 +63,7 @@
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"fs-extra": "^11.2.0",
"postcss": "^8.4.40",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.7",

View File

@@ -1 +1 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9731" width="64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M1020.586667 361.813333c0-92.16-75.093333-167.253333-167.253334-167.253333h-266.24l23.893334 95.573333 228.693333 51.2c20.48 3.413333 37.546667 23.893333 37.546667 44.373334v245.76c0 20.48-17.066667 40.96-37.546667 44.373333l-228.693333 51.2-27.306667 92.16h266.24c92.16 0 167.253333-75.093333 167.253333-167.253333 3.413333 0 3.413333-290.133333 3.413334-290.133334zM187.733333 672.426667c-20.48-3.413333-37.546667-23.893333-37.546666-44.373334v-245.76c0-20.48 17.066667-40.96 37.546666-44.373333l228.693334-51.2 23.893333-95.573333H174.08C81.92 191.146667 6.826667 266.24 6.826667 358.4V648.533333c0 92.16 75.093333 167.253333 167.253333 167.253334h266.24l-23.893333-95.573334c0 3.413333-228.693333-47.786667-228.693334-47.786666z m215.04-211.626667h218.453334v88.746667h-218.453334v-88.746667z" fill="#ff6b01" p-id="9732"></path></svg>
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5262" width="200" height="200"><path d="M512 64a448 448 0 1 1 0 896A448 448 0 0 1 512 64z" fill="#FF6A00" p-id="5263"></path><path d="M324.8 602.624a26.752 26.752 0 0 1-21.312-25.92v-142.72a27.712 27.712 0 0 1 21.376-25.984l132.416-28.672 13.952-56.896H317.312a97.6 97.6 0 0 0-98.24 96.96v169.344c0.384 54.08 44.16 97.856 98.24 98.176h153.92l-13.888-56.512-132.544-27.776zM710.4 322.432c54.016 0.128 97.92 43.584 98.56 97.6v170.176a98.368 98.368 0 0 1-98.56 98.048H555.328l14.08-56.832 132.608-28.736a27.84 27.84 0 0 0 21.376-25.92v-142.72a26.88 26.88 0 0 0-21.376-25.984l-132.544-28.8-14.08-56.832zM570.368 497.92v13.952H457.28v-13.952h113.088z" fill="#FFFFFF" p-id="5264"></path></svg>

Before

Width:  |  Height:  |  Size: 1017 B

After

Width:  |  Height:  |  Size: 785 B

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
<svg class="icon" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" height="200" width="200">
<circle style="fill:#32BEA6;" cx="256" cy="256" r="256"/><g><path style="fill:#FFFFFF;" d="M58.016,202.296h18.168v42.48h0.296c2.192-3.368,5.128-6.152,8.936-8.2 c3.512-2.056,7.76-3.224,12.304-3.224c12.16,0,24.896,8.064,24.896,30.912v42.04H104.6v-39.992c0-10.4-3.808-18.168-13.776-18.168 c-7.032,0-12.008,4.688-13.912,10.112c-0.584,1.472-0.728,3.368-0.728,5.424v42.624H58.016V202.296z"/><path style="fill:#FFFFFF;" d="M161.76,214.6v20.368h17.144v13.48H161.76v31.496c0,8.64,2.344,13.176,9.224,13.176 c3.08,0,5.424-0.44,7.032-0.872l0.296,13.768c-2.64,1.032-7.328,1.768-13.04,1.768c-6.584,0-12.16-2.2-15.52-5.856 c-3.816-4.112-5.568-10.544-5.568-19.92v-33.544h-10.248V234.96h10.248v-16.12L161.76,214.6z"/><path style="fill:#FFFFFF;" d="M213.192,214.6v20.368h17.144v13.48h-17.144v31.496c0,8.64,2.344,13.176,9.224,13.176 c3.08,0,5.424-0.44,7.032-0.872l0.296,13.768c-2.64,1.032-7.328,1.768-13.04,1.768c-6.584,0-12.16-2.2-15.52-5.856 c-3.816-4.112-5.568-10.544-5.568-19.92v-33.544h-10.248V234.96h10.248v-16.12L213.192,214.6z"/><path style="fill:#FFFFFF;" d="M243.984,258.688c0-9.376-0.296-16.992-0.592-23.728h15.832l0.872,10.984h0.296 c5.264-8.056,13.616-12.6,24.464-12.6c16.408,0,30.024,14.064,30.024,36.328c0,25.784-16.256,38.232-32.512,38.232 c-8.936,0-16.408-3.808-20.072-9.512H262v36.904h-18.016V258.688z M262,276.416c0,1.76,0.144,3.368,0.584,4.976 c1.76,7.328,8.2,12.6,15.824,12.6c11.424,0,18.168-9.52,18.168-23.584c0-12.592-6.16-22.848-17.728-22.848 c-7.472,0-14.36,5.424-16.112,13.336c-0.448,1.464-0.736,3.072-0.736,4.536L262,276.416L262,276.416z"/><path style="fill:#FFFFFF;" d="M327.504,247.12c0-6.744,4.688-11.568,11.136-11.568c6.592,0,10.984,4.832,11.136,11.568 c0,6.592-4.392,11.432-11.136,11.432C332.048,258.552,327.504,253.712,327.504,247.12z M327.504,296.488 c0-6.744,4.688-11.576,11.136-11.576c6.592,0,10.984,4.688,11.136,11.576c0,6.448-4.392,11.424-11.136,11.424 C332.048,307.912,327.504,302.936,327.504,296.488z"/><path style="fill:#FFFFFF;" d="M355.8,312.16l35.744-106.2h12.6l-35.752,106.2H355.8z"/><path style="fill:#FFFFFF;" d="M405.176,312.16l35.744-106.2h12.592l-35.728,106.2H405.176z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<svg class="icon" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" version="1.1" width="200" height="200"><path d="M18.97 21.14c0 5.293-4.248 9.585-9.487 9.585S0 26.432 0 21.14s4.245-9.585 9.485-9.585 9.485 4.293 9.485 9.585z" fill="#e38000"/><path d="M18.97 42.865c0 5.29-4.248 9.58-9.487 9.58S0 48.156 0 42.86s4.245-9.585 9.485-9.585 9.485 4.293 9.485 9.585zM41.488 21.14c0 5.293-4.25 9.585-9.49 9.585s-9.485-4.29-9.485-9.585 4.248-9.585 9.485-9.585 9.487 4.293 9.487 9.585zm0 21.726c0 5.29-4.25 9.58-9.49 9.58s-9.485-4.29-9.485-9.585 4.248-9.585 9.485-9.585 9.487 4.293 9.487 9.585zM64 21.14c0 5.293-4.245 9.585-9.485 9.585s-9.485-4.29-9.485-9.585 4.245-9.585 9.485-9.585S64 15.848 64 21.14z" fill="#e17f03"/><path d="M64 42.865c0 5.29-4.245 9.58-9.485 9.58s-9.485-4.29-9.485-9.585 4.245-9.585 9.485-9.585S64 37.57 64 42.86z" fill="#e38000"/></svg>

After

Width:  |  Height:  |  Size: 858 B

22
ui/src/api/notify.ts Normal file
View File

@@ -0,0 +1,22 @@
import { getPb } from "@/repository/api";
export const notifyTest = async (channel: string) => {
const pb = getPb();
const resp = await pb.send("/api/notify/test", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: {
channel,
},
});
if (resp.code != 0) {
throw new Error(resp.msg);
}
return resp;
};

View File

@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { Access, AliyunConfig, accessFormType, getUsageByConfigType } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type Access, type AliyunConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { useConfigContext } from "@/providers/config";
type AccessAliyunFormProps = {
op: "add" | "edit" | "copy";
@@ -19,7 +19,7 @@ type AccessAliyunFormProps = {
};
const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
const { addAccess, updateAccess } = useConfig();
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@@ -27,7 +27,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
configType: accessTypeFormSchema,
accessKeyId: z
.string()
.min(1, "access.authorization.form.access_key_id.placeholder")
@@ -60,7 +60,7 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
usage: accessProvidersMap.get(data.configType)!.usage,
config: {
accessKeyId: data.accessKeyId,
accessKeySecret: data.accessSecretId,
@@ -98,98 +98,96 @@ const AccessAliyunForm = ({ data, op, onAfterReq }: AccessAliyunFormProps) => {
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accessSecretId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_secret.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="accessSecretId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_secret.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</div>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};

View File

@@ -0,0 +1,238 @@
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { ClientResponseError } from "pocketbase";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { Access, accessProvidersMap, accessTypeFormSchema, type AwsConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
type AccessAwsFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
onAfterReq: () => void;
};
const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessTypeFormSchema,
region: z
.string()
.min(1, "access.authorization.form.region.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessKeyId: z
.string()
.min(1, "access.authorization.form.access_key_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.min(1, "access.authorization.form.secret_access_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
hostedZoneId: z
.string()
.min(0, "access.authorization.form.aws_hosted_zone_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: AwsConfig = {
region: "cn-north-1",
accessKeyId: "",
secretAccessKey: "",
hostedZoneId: "",
};
if (data) config = data.config as AwsConfig;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || "",
configType: "aws",
region: config.region,
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
hostedZoneId: config.hostedZoneId,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: accessProvidersMap.get(data.configType)!.usage,
config: {
region: data.region,
accessKeyId: data.accessKeyId,
secretAccessKey: data.secretAccessKey,
hostedZoneId: data.hostedZoneId,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id && op == "edit") {
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;
}
};
return (
<>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.region.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="secretAccessKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="hostedZoneId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.aws_hosted_zone_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.aws_hosted_zone_id.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};
export default AccessAwsForm;

View File

@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type Access, type CloudflareConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { useConfigContext } from "@/providers/config";
type AccessCloudflareFormProps = {
op: "add" | "edit" | "copy";
@@ -19,7 +19,7 @@ type AccessCloudflareFormProps = {
};
const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProps) => {
const { addAccess, updateAccess } = useConfig();
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@@ -27,7 +27,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
configType: accessTypeFormSchema,
dnsApiToken: z
.string()
.min(1, "access.authorization.form.cloud_dns_api_token.placeholder")
@@ -54,7 +54,7 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
usage: accessProvidersMap.get(data.configType)!.usage,
config: {
dnsApiToken: data.dnsApiToken,
},
@@ -88,81 +88,79 @@ const AccessCloudflareForm = ({ data, op, onAfterReq }: AccessCloudflareFormProp
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dnsApiToken"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.cloud_dns_api_token.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="dnsApiToken"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.cloud_dns_api_token.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</div>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};

View File

@@ -8,15 +8,19 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
import AccessAliyunForm from "./AccessAliyunForm";
import AccessTencentForm from "./AccessTencentForm";
import AccessHuaweicloudForm from "./AccessHuaweicloudForm";
import AccessHuaweiCloudForm from "./AccessHuaweicloudForm";
import AccessQiniuForm from "./AccessQiniuForm";
import AccessAwsForm from "./AccessAwsForm";
import AccessCloudflareForm from "./AccessCloudflareForm";
import AccessNamesiloForm from "./AccessNamesiloForm";
import AccessGodaddyForm from "./AccessGodaddyForm";
import AccessPdnsForm from "./AccessPdnsForm";
import AccessHttpreqForm from "./AccessHttpreqForm";
import AccessLocalForm from "./AccessLocalForm";
import AccessSSHForm from "./AccessSSHForm";
import AccessWebhookForm from "./AccessWebhookForm";
import { Access, accessTypeMap } from "@/domain/access";
import AccessKubernetesForm from "./AccessKubernetesForm";
import { Access, accessProvidersMap } from "@/domain/access";
type AccessEditProps = {
op: "add" | "edit" | "copy";
@@ -25,18 +29,17 @@ type AccessEditProps = {
data?: Access;
};
const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
const [open, setOpen] = useState(false);
const AccessEditDialog = ({ trigger, op, data, className }: AccessEditProps) => {
const { t } = useTranslation();
const typeKeys = Array.from(accessTypeMap.keys());
const [open, setOpen] = useState(false);
const [configType, setConfigType] = useState(data?.configType || "");
let form = <> </>;
let childComponent = <> </>;
switch (configType) {
case "aliyun":
form = (
childComponent = (
<AccessAliyunForm
data={data}
op={op}
@@ -47,7 +50,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "tencent":
form = (
childComponent = (
<AccessTencentForm
data={data}
op={op}
@@ -58,8 +61,8 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "huaweicloud":
form = (
<AccessHuaweicloudForm
childComponent = (
<AccessHuaweiCloudForm
data={data}
op={op}
onAfterReq={() => {
@@ -69,7 +72,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "qiniu":
form = (
childComponent = (
<AccessQiniuForm
data={data}
op={op}
@@ -79,8 +82,19 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
/>
);
break;
case "aws":
childComponent = (
<AccessAwsForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "cloudflare":
form = (
childComponent = (
<AccessCloudflareForm
data={data}
op={op}
@@ -91,7 +105,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "namesilo":
form = (
childComponent = (
<AccessNamesiloForm
data={data}
op={op}
@@ -102,7 +116,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "godaddy":
form = (
childComponent = (
<AccessGodaddyForm
data={data}
op={op}
@@ -112,8 +126,30 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
/>
);
break;
case "pdns":
childComponent = (
<AccessPdnsForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "httpreq":
childComponent = (
<AccessHttpreqForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "local":
form = (
childComponent = (
<AccessLocalForm
data={data}
op={op}
@@ -124,7 +160,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "ssh":
form = (
childComponent = (
<AccessSSHForm
data={data}
op={op}
@@ -135,7 +171,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
break;
case "webhook":
form = (
childComponent = (
<AccessWebhookForm
data={data}
op={op}
@@ -145,6 +181,17 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
/>
);
break;
case "k8s":
childComponent = (
<AccessKubernetesForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
}
const getOptionCls = (val: string) => {
@@ -159,38 +206,45 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>
{op == "add" ? t("access.authorization.add") : op == "edit" ? t("access.authorization.edit") : t("access.authorization.copy")}
{
{
["add"]: t("access.authorization.add"),
["edit"]: t("access.authorization.edit"),
["copy"]: t("access.authorization.copy"),
}[op]
}
</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[80vh]">
<div className="container py-3">
<Label>{t("access.authorization.form.type.label")}</Label>
<div>
<Label>{t("access.authorization.form.type.label")}</Label>
<Select
onValueChange={(val) => {
setConfigType(val);
}}
defaultValue={configType}
>
<SelectTrigger className="mt-3">
<SelectValue placeholder={t("access.authorization.form.type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t("access.authorization.form.type.list")}</SelectLabel>
{Array.from(accessProvidersMap.entries()).map(([key, provider]) => (
<SelectItem value={key} key={key}>
<div className={cn("flex items-center space-x-2 rounded cursor-pointer", getOptionCls(key))}>
<img src={provider.icon} className="h-6 w-6" />
<div>{t(provider.name)}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<Select
onValueChange={(val) => {
setConfigType(val);
}}
defaultValue={configType}
>
<SelectTrigger className="mt-3">
<SelectValue placeholder={t("access.authorization.form.type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t("access.authorization.form.type.list")}</SelectLabel>
{typeKeys.map((key) => (
<SelectItem value={key} key={key}>
<div className={cn("flex items-center space-x-2 rounded cursor-pointer", getOptionCls(key))}>
<img src={accessTypeMap.get(key)?.[1]} className="h-6 w-6" />
<div>{t(accessTypeMap.get(key)?.[0] || "")}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
{form}
<div className="mt-8">{childComponent}</div>
</div>
</ScrollArea>
</DialogContent>
@@ -198,4 +252,4 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
);
};
export default AccessEdit;
export default AccessEditDialog;

View File

@@ -8,9 +8,9 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { PbErrorData } from "@/domain/base";
import { Access, accessFormType, getUsageByConfigType, GodaddyConfig } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type Access, type GodaddyConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { useConfigContext } from "@/providers/config";
type AccessGodaddyFormProps = {
op: "add" | "edit" | "copy";
@@ -19,7 +19,7 @@ type AccessGodaddyFormProps = {
};
const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) => {
const { addAccess, updateAccess } = useConfig();
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@@ -27,7 +27,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
configType: accessTypeFormSchema,
apiKey: z
.string()
.min(1, "access.authorization.form.godaddy_api_key.placeholder")
@@ -60,7 +60,7 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
usage: accessProvidersMap.get(data.configType)!.usage,
config: {
apiKey: data.apiKey,
apiSecret: data.apiSecret,
@@ -95,96 +95,94 @@ const AccessGodaddyForm = ({ data, op, onAfterReq }: AccessGodaddyFormProps) =>
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.godaddy_api_key.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.godaddy_api_key.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="apiSecret"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.godaddy_api_secret.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="apiSecret"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.godaddy_api_secret.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</div>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};

View File

@@ -12,7 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { update } from "@/repository/access_group";
import { useConfig } from "@/providers/config";
import { useConfigContext } from "@/providers/config";
type AccessGroupEditProps = {
className?: string;
@@ -20,7 +20,7 @@ type AccessGroupEditProps = {
};
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
const { reloadAccessGroups } = useConfig();
const { reloadAccessGroups } = useConfigContext();
const [open, setOpen] = useState(false);
const { t } = useTranslation();

View File

@@ -19,16 +19,16 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
import { ScrollArea } from "@/components/ui/scroll-area";
import { useToast } from "@/components/ui/use-toast";
import AccessGroupEdit from "./AccessGroupEdit";
import { getProviderInfo } from "@/domain/access";
import { accessProvidersMap } from "@/domain/access";
import { getErrMessage } from "@/lib/error";
import { useConfig } from "@/providers/config";
import { useConfigContext } from "@/providers/config";
import { remove } from "@/repository/access_group";
const AccessGroupList = () => {
const {
config: { accessGroups },
reloadAccessGroups,
} = useConfig();
} = useConfigContext();
const { toast } = useToast();
@@ -86,11 +86,11 @@ const AccessGroupList = () => {
<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>
<img src={accessProvidersMap.get(access.configType)!.icon} 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 className="text-xs text-muted-foreground">{accessProvidersMap.get(access.configType)!.name}</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,233 @@
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { ClientResponseError } from "pocketbase";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { PbErrorData } from "@/domain/base";
import { accessProvidersMap, accessTypeFormSchema, type Access, type HttpreqConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfigContext } from "@/providers/config";
type AccessHttpreqFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
onAfterReq: () => void;
};
const AccessHttpreqForm = ({ data, op, onAfterReq }: AccessHttpreqFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessTypeFormSchema,
endpoint: z.string().url("common.errmsg.url_invalid"),
mode: z.enum(["RAW", ""]),
username: z
.string()
.min(1, "access.authorization.form.access_key_secret.placeholder")
.max(128, t("common.errmsg.string_max", { max: 128 })),
password: z
.string()
.min(1, "access.authorization.form.access_key_secret.placeholder")
.max(128, t("common.errmsg.string_max", { max: 128 })),
});
let config: HttpreqConfig = {
endpoint: "",
mode: "",
username: "",
password: "",
};
if (data) config = data.config as HttpreqConfig;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || "",
configType: "httpreq",
endpoint: config.endpoint,
mode: config.mode === "RAW" ? "RAW" : "",
username: config.username,
password: config.password,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: accessProvidersMap.get(data.configType)!.usage,
config: {
endpoint: data.endpoint,
mode: data.mode,
username: data.username,
password: data.password,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id && op == "edit") {
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;
}
};
return (
<>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="endpoint"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.httpreq_endpoint.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.httpreq_endpoint.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="mode"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.httpreq_mode.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.httpreq_mode.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.username.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.username.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.password.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.password.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};
export default AccessHttpreqForm;

View File

@@ -8,18 +8,18 @@ import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { PbErrorData } from "@/domain/base";
import { Access, accessFormType, HuaweicloudConfig, getUsageByConfigType } from "@/domain/access";
import { accessProvidersMap, accessTypeFormSchema, type Access, type HuaweiCloudConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { useConfigContext } from "@/providers/config";
type AccessHuaweicloudFormProps = {
type AccessHuaweiCloudFormProps = {
op: "add" | "edit" | "copy";
data?: Access;
onAfterReq: () => void;
};
const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormProps) => {
const { addAccess, updateAccess } = useConfig();
const AccessHuaweiCloudForm = ({ data, op, onAfterReq }: AccessHuaweiCloudFormProps) => {
const { addAccess, updateAccess } = useConfigContext();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
@@ -27,7 +27,7 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
configType: accessTypeFormSchema,
region: z
.string()
.min(1, "access.authorization.form.region.placeholder")
@@ -38,16 +38,16 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.min(1, "access.authorization.form.access_key_secret.placeholder")
.min(1, "access.authorization.form.secret_access_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: HuaweicloudConfig = {
let config: HuaweiCloudConfig = {
region: "cn-north-1",
accessKeyId: "",
secretAccessKey: "",
};
if (data) config = data.config as HuaweicloudConfig;
if (data) config = data.config as HuaweiCloudConfig;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
@@ -66,7 +66,7 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
usage: accessProvidersMap.get(data.configType)!.usage,
config: {
region: data.region,
accessKeyId: data.accessKeyId,
@@ -104,115 +104,113 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.name.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.region.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.region.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_id.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="secretAccessKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.access_key_secret.placeholder")} {...field} />
</FormControl>
<FormField
control={form.control}
name="secretAccessKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.secret_access_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.secret_access_key.placeholder")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</div>
<div className="flex justify-end">
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>
</>
);
};
export default AccessHuaweicloudForm;
export default AccessHuaweiCloudForm;

Some files were not shown because too many files have changed in this diff Show More