Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bff18a7be7 | ||
|
|
bac00491fe | ||
|
|
f8da3ded0d | ||
|
|
b01849eb0c | ||
|
|
c9eb487953 | ||
|
|
dc383644d6 | ||
|
|
a8f718afa0 | ||
|
|
cd76d170b2 | ||
|
|
7b129c11e9 | ||
|
|
f7972d5b68 | ||
|
|
b1a0d84033 | ||
|
|
969fba8a57 | ||
|
|
63865b5fbd | ||
|
|
46c32f15e3 | ||
|
|
5f62c887c0 | ||
|
|
c85beaa52b | ||
|
|
885cdfaec9 | ||
|
|
011130432c | ||
|
|
062d66222a | ||
|
|
e53749e16e | ||
|
|
dbfb84ec6d | ||
|
|
265842feeb | ||
|
|
0c35928eee | ||
|
|
ea4bcb4aaf | ||
|
|
716f5f1426 | ||
|
|
4e86c1eb45 | ||
|
|
0576a8bec3 | ||
|
|
97f334b5ab | ||
|
|
18a7bf0d66 | ||
|
|
908d33f186 | ||
|
|
68b9171390 | ||
|
|
45005a5073 | ||
|
|
028eb088a5 | ||
|
|
8f98664665 | ||
|
|
699385a8c4 | ||
|
|
64b7ed00f5 | ||
|
|
2c75d2bfde | ||
|
|
9c41b0e357 | ||
|
|
ec6f10053a | ||
|
|
0095600615 | ||
|
|
b031f00764 | ||
|
|
a4fc8dfc56 | ||
|
|
f168bd903d | ||
|
|
fc55e37454 | ||
|
|
84e2fd4f5c | ||
|
|
0037659462 | ||
|
|
364289894e | ||
|
|
f6a3f4edfa | ||
|
|
560d21c854 | ||
|
|
4719f99155 | ||
|
|
3a213dc9c3 | ||
|
|
0d07c7c234 | ||
|
|
21670f64d1 | ||
|
|
f0e7fe695d | ||
|
|
2bab727569 | ||
|
|
8d41a9aae7 | ||
|
|
896b5d3a13 | ||
|
|
88e64717cd | ||
|
|
2d275a14ab | ||
|
|
1b796cffd1 | ||
|
|
f53e54c8de | ||
|
|
a9b9be96cb | ||
|
|
1033885c99 | ||
|
|
c45ad3c901 | ||
|
|
24192b61c1 | ||
|
|
1562e92e74 | ||
|
|
17f72eb9cb | ||
|
|
57ae6d5b40 | ||
|
|
467e4c4634 | ||
|
|
d6d296b546 | ||
|
|
499bbe4fa7 | ||
|
|
ae814766f3 | ||
|
|
17cfeee374 | ||
|
|
efa394e9bd | ||
|
|
94ca0f27bf | ||
|
|
e08df5e6d8 | ||
|
|
952c6ef73d | ||
|
|
b9902c926f | ||
|
|
7fa6ea1797 | ||
|
|
be3cdbf585 | ||
|
|
6225969d4c | ||
|
|
4382474449 | ||
|
|
678ef9c232 | ||
|
|
3d535320b9 | ||
|
|
77d3e40ffb | ||
|
|
5dca64d3d3 | ||
|
|
02d582b564 | ||
|
|
8e906cbf23 | ||
|
|
411b7bbfe2 | ||
|
|
3093fc6b02 | ||
|
|
3c4b7d251a | ||
|
|
f87a1be192 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,7 +7,7 @@ assignees: ""
|
||||
---
|
||||
|
||||
**描述问题**
|
||||
简要描述问题是什么
|
||||
简要描述问题是什么,1 个 ISSUE 只描述一个问题。
|
||||
|
||||
**复现步骤**
|
||||
复现该问题的步骤:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,7 +7,7 @@ assignees: ""
|
||||
---
|
||||
|
||||
**功能描述**
|
||||
简要描述你希望添加的功能和相关问题。
|
||||
简要描述你希望添加的功能和相关问题,1 个 ISSUE 只描述一个功能。
|
||||
|
||||
**动机**
|
||||
为什么这个功能对项目有帮助?
|
||||
|
||||
3
.github/workflows/push_image.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
12
.github/workflows/release.yml
vendored
@@ -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
@@ -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
@@ -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"]
|
||||
@@ -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"]
|
||||
3
Makefile
@@ -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
|
||||
|
||||
10
README.md
@@ -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,16 +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 |
|
||||
|
||||
## 四、系统截图
|
||||
|
||||
|
||||
10
README_EN.md
@@ -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,16 +73,19 @@ password:1234567890
|
||||
| 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
|
||||
|
||||
|
||||
40
go.mod
@@ -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 (
|
||||
@@ -31,26 +35,50 @@ require (
|
||||
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
|
||||
|
||||
99
go.sum
@@ -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=
|
||||
@@ -159,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=
|
||||
@@ -170,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=
|
||||
@@ -186,6 +211,13 @@ 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=
|
||||
@@ -197,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=
|
||||
@@ -230,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=
|
||||
@@ -238,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=
|
||||
@@ -267,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=
|
||||
@@ -279,6 +322,8 @@ 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=
|
||||
@@ -286,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=
|
||||
@@ -302,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=
|
||||
@@ -325,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=
|
||||
@@ -332,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=
|
||||
@@ -358,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=
|
||||
@@ -411,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=
|
||||
@@ -450,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=
|
||||
@@ -471,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=
|
||||
@@ -486,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=
|
||||
@@ -498,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=
|
||||
@@ -509,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=
|
||||
@@ -536,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=
|
||||
@@ -548,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=
|
||||
@@ -580,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=
|
||||
@@ -633,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=
|
||||
@@ -649,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=
|
||||
@@ -676,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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package applicant
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"certimate/internal/utils/app"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"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"
|
||||
@@ -23,11 +26,13 @@ import (
|
||||
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,7 +132,7 @@ 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
|
||||
@@ -135,6 +142,10 @@ func Get(record *models.Record) (Applicant, error) {
|
||||
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")
|
||||
}
|
||||
@@ -170,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,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package applicant
|
||||
|
||||
import (
|
||||
"certimate/internal/domain"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-acme/lego/v4/providers/dns/route53"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
)
|
||||
|
||||
type aws struct {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
38
internal/applicant/httpreq.go
Normal 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)
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
36
internal/applicant/pdns.go
Normal 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
70
internal/deployer/aliyun_oss.go
Normal 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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
206
internal/deployer/huaweicloud_cdn.go
Normal 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
|
||||
}
|
||||
108
internal/deployer/k8s_secret.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
// 如果目录不存在,创建目录
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 != "" {
|
||||
|
||||
108
internal/deployer/tencent-cos.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -40,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"`
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -0,0 +1,12 @@
|
||||
package domain
|
||||
|
||||
const (
|
||||
NotifyChannelDingtalk = "dingtalk"
|
||||
NotifyChannelWebhook = "webhook"
|
||||
NotifyChannelTelegram = "telegram"
|
||||
NotifyChannelLark = "lark"
|
||||
)
|
||||
|
||||
type NotifyTestPushReq struct {
|
||||
Channel string `json:"channel"`
|
||||
}
|
||||
31
internal/domain/setting.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"certimate/internal/utils/app"
|
||||
"context"
|
||||
"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"
|
||||
@@ -13,13 +15,6 @@ import (
|
||||
"github.com/nikoksr/notify/service/telegram"
|
||||
)
|
||||
|
||||
const (
|
||||
notifyChannelDingtalk = "dingtalk"
|
||||
notifyChannelWebhook = "webhook"
|
||||
notifyChannelTelegram = "telegram"
|
||||
notifyChannelLark = "lark"
|
||||
)
|
||||
|
||||
func Send(title, content string) error {
|
||||
// 获取所有的推送渠道
|
||||
notifiers, err := getNotifiers()
|
||||
@@ -38,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 {
|
||||
@@ -58,27 +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 notifyChannelLark:
|
||||
notifiers = append(notifiers, getLarkNotifier(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()
|
||||
|
||||
|
||||
46
internal/notify/service.go
Normal 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,
|
||||
})
|
||||
}
|
||||
27
internal/pkg/core/uploader/uploader.go
Normal 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"`
|
||||
}
|
||||
164
internal/pkg/core/uploader/uploader_aliyun_cas.go
Normal 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
|
||||
}
|
||||
159
internal/pkg/core/uploader/uploader_huaweicloud_elb.go
Normal 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
|
||||
}
|
||||
167
internal/pkg/core/uploader/uploader_huaweicloud_scm.go
Normal 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
|
||||
}
|
||||
91
internal/pkg/core/uploader/uploader_tencentcloud_ssl.go
Normal 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
|
||||
}
|
||||
25
internal/pkg/utils/cast/cast.go
Normal 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
|
||||
}
|
||||
48
internal/pkg/utils/x509/x509.go
Normal 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
|
||||
}
|
||||
31
internal/repository/setting.go
Normal 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
@@ -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 := ¬ifyHandler{
|
||||
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
@@ -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)
|
||||
}
|
||||
39
internal/utils/resp/resp.go
Normal 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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
@@ -51,7 +51,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 {
|
||||
|
||||
95
migrations/1729241998_updated_access.go
Normal 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)
|
||||
})
|
||||
}
|
||||
98
migrations/1729339341_updated_access.go
Normal 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
@@ -0,0 +1 @@
|
||||
|
||||
1
ui/dist/assets/index-YqBWA4KK.css
vendored
329
ui/dist/assets/index-j5PIjQ7r.js
vendored
1
ui/dist/imgs/providers/aliyun.svg
vendored
@@ -1 +0,0 @@
|
||||
<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: 785 B |
1
ui/dist/imgs/providers/aws.svg
vendored
1
ui/dist/imgs/providers/cloudflare.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/godaddy.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/huaweicloud.svg
vendored
@@ -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>
|
||||
1
ui/dist/imgs/providers/letsencrypt.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/local.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/namesilo.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/qiniu.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/ssh.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/tencent.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/webhook.svg
vendored
@@ -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 |
1
ui/dist/imgs/providers/zerossl.svg
vendored
@@ -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
@@ -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-j5PIjQ7r.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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
2
ui/public/imgs/providers/httpreq.svg
Normal 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 |
1
ui/public/imgs/providers/k8s.svg
Normal file
1
ui/public/imgs/providers/pdns.svg
Normal 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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,9 +8,9 @@ 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, AwsConfig, getUsageByConfigType } from "@/domain/access";
|
||||
import { Access, accessProvidersMap, accessTypeFormSchema, type AwsConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
|
||||
type AccessAwsFormProps = {
|
||||
op: "add" | "edit" | "copy";
|
||||
@@ -19,7 +19,7 @@ type AccessAwsFormProps = {
|
||||
};
|
||||
|
||||
const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
||||
const { addAccess, updateAccess } = useConfig();
|
||||
const { addAccess, updateAccess } = useConfigContext();
|
||||
const { t } = useTranslation();
|
||||
const formSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
@@ -27,7 +27,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
||||
.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")
|
||||
@@ -72,7 +72,7 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
||||
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,
|
||||
@@ -111,128 +111,126 @@ const AccessAwsForm = ({ data, op, onAfterReq }: AccessAwsFormProps) => {
|
||||
|
||||
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.secret_access_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.secret_access_key.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>
|
||||
)}
|
||||
/>
|
||||
|
||||
<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>
|
||||
<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 />
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,16 +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";
|
||||
@@ -26,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}
|
||||
@@ -48,7 +50,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "tencent":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessTencentForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -59,8 +61,8 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "huaweicloud":
|
||||
form = (
|
||||
<AccessHuaweicloudForm
|
||||
childComponent = (
|
||||
<AccessHuaweiCloudForm
|
||||
data={data}
|
||||
op={op}
|
||||
onAfterReq={() => {
|
||||
@@ -70,7 +72,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "qiniu":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessQiniuForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -81,7 +83,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "aws":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessAwsForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -92,7 +94,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "cloudflare":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessCloudflareForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -103,7 +105,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "namesilo":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessNamesiloForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -114,7 +116,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "godaddy":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessGodaddyForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -124,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}
|
||||
@@ -136,7 +160,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "ssh":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessSSHForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -147,7 +171,7 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
break;
|
||||
case "webhook":
|
||||
form = (
|
||||
childComponent = (
|
||||
<AccessWebhookForm
|
||||
data={data}
|
||||
op={op}
|
||||
@@ -157,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) => {
|
||||
@@ -171,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>
|
||||
@@ -210,4 +252,4 @@ const AccessEdit = ({ trigger, op, data, className }: AccessEditProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessEdit;
|
||||
export default AccessEditDialog;
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
233
ui/src/components/certimate/AccessHttpreqForm.tsx
Normal 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;
|
||||
@@ -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")
|
||||
@@ -42,12 +42,12 @@ const AccessHuaweicloudForm = ({ data, op, onAfterReq }: AccessHuaweicloudFormPr
|
||||
.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.secret_access_key.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.secret_access_key.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;
|
||||
|
||||
193
ui/src/components/certimate/AccessKubernetesForm.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
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 { readFileContent } from "@/lib/file";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { accessProvidersMap, accessTypeFormSchema, type Access, type KubernetesConfig } from "@/domain/access";
|
||||
import { save } from "@/repository/access";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
|
||||
type AccessKubernetesFormProps = {
|
||||
op: "add" | "edit" | "copy";
|
||||
data?: Access;
|
||||
onAfterReq: () => void;
|
||||
};
|
||||
|
||||
const AccessKubernetesForm = ({ data, op, onAfterReq }: AccessKubernetesFormProps) => {
|
||||
const { addAccess, updateAccess } = useConfigContext();
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [fileName, setFileName] = useState("");
|
||||
|
||||
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,
|
||||
kubeConfig: z
|
||||
.string()
|
||||
.min(1, "access.authorization.form.k8s_kubeconfig.placeholder")
|
||||
.max(20480, t("common.errmsg.string_max", { max: 20480 })),
|
||||
kubeConfigFile: z.any().optional(),
|
||||
});
|
||||
|
||||
let config: KubernetesConfig & { kubeConfigFile?: string } = {
|
||||
kubeConfig: "",
|
||||
kubeConfigFile: "",
|
||||
};
|
||||
if (data) config = data.config as typeof config;
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
id: data?.id,
|
||||
name: data?.name || "",
|
||||
configType: "k8s",
|
||||
kubeConfig: config.kubeConfig,
|
||||
kubeConfigFile: config.kubeConfigFile,
|
||||
},
|
||||
});
|
||||
|
||||
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: {
|
||||
kubeConfig: data.kubeConfig,
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
const savedFile = file;
|
||||
setFileName(savedFile.name);
|
||||
const content = await readFileContent(savedFile);
|
||||
form.setValue("kubeConfig", content);
|
||||
};
|
||||
|
||||
const handleSelectFileClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
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="kubeConfig"
|
||||
render={({ field }) => (
|
||||
<FormItem hidden>
|
||||
<FormLabel>{t("access.authorization.form.k8s_kubeconfig.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("access.authorization.form.k8s_kubeconfig.placeholder")} {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="kubeConfigFile"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("access.authorization.form.k8s_kubeconfig.label")}</FormLabel>
|
||||
<FormControl>
|
||||
<div>
|
||||
<Button type={"button"} variant={"secondary"} size={"sm"} className="w-48" onClick={handleSelectFileClick}>
|
||||
{fileName ? fileName : t("access.authorization.form.k8s_kubeconfig_file.placeholder")}
|
||||
</Button>
|
||||
<Input
|
||||
placeholder={t("access.authorization.form.k8s_kubeconfig.placeholder")}
|
||||
{...field}
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
hidden
|
||||
type="file"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">{t("common.save")}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessKubernetesForm;
|
||||