Compare commits

...

29 Commits

Author SHA1 Message Date
usual2970
31e9f08b47 Merge pull request #94 from usual2970/feat/notify
Feat/notify
2024-09-24 22:40:42 +08:00
yoan
ac4904fb9a Enhance the message notification feature 2024-09-24 22:39:42 +08:00
yoan
4c9095400e message push config 2024-09-23 23:13:34 +08:00
yoan
38d975a3bb optimize tencent cdn deploy 2024-09-22 22:36:08 +08:00
yoan
5422f17fab update ui 2024-09-22 22:25:08 +08:00
yoan
2a1af1e7cd Merge branch 'main' into feat/notify 2024-09-22 20:07:56 +08:00
usual2970
5eec1cf5ca Merge pull request #74 from usual2970/hotfix/bug_0922
fix some bugs
2024-09-22 20:01:44 +08:00
yoan
a259ccdfec fix some bugs 2024-09-22 20:00:55 +08:00
yoan
48c1c1e996 temp save 2024-09-22 18:12:12 +08:00
usual2970
2cca82eb95 Merge pull request #61 from usual2970/feat/settings
Feat/settings
2024-09-21 06:38:04 +08:00
yoan
30beee6027 support email modification 2024-09-21 06:37:11 +08:00
yoan
b649348162 Merge branch 'main' into feat/settings 2024-09-21 06:35:16 +08:00
yoan
b912c5e688 support email update 2024-09-21 06:34:32 +08:00
usual2970
5981200df2 Merge pull request #59 from minibear2021/patch-1
Update README.md
2024-09-21 06:25:23 +08:00
Chen Gang
f9e7bfd606 Update README.md 2024-09-20 17:37:28 +08:00
yoan
7f6549bdf3 add wechat group 2024-09-20 10:26:33 +08:00
yoan
2af26dbfe0 add community 2024-09-19 18:06:18 +08:00
yoan
b7f382e16f add telegram group 2024-09-19 15:57:04 +08:00
yoan
7762955989 add telegram group 2024-09-19 15:52:10 +08:00
yoan
1ab603b506 add telegram group 2024-09-19 15:47:09 +08:00
yoan
b432cbfd3f push image to Dockerhub 2024-09-19 09:55:37 +08:00
yoan
e4d76113f8 push image to Dockerhub 2024-09-19 09:51:19 +08:00
usual2970
12a3adc559 Merge pull request #46 from usual2970/feat/force_deploy
force deploy and custom nameservers
2024-09-19 08:41:08 +08:00
yoan
e50f1a74d6 general domain include root domain 2024-09-19 08:39:59 +08:00
yoan
ba6a504588 force deploy and custom nameservers 2024-09-18 22:42:18 +08:00
usual2970
2d37c42584 Merge pull request #41 from PBK-B/feat-ali-dcdn
feat: 支持阿里云 DCDN (全站加速) 部署
2024-09-18 07:47:32 +08:00
PBK-B
f4b3a8cf81 feat: domains add aliyun-dcdn item #40 2024-09-17 19:29:30 +08:00
PBK-B
0390ac3eda feat: support aliyun dcdn(esa) 2024-09-17 19:10:25 +08:00
yoan
1977201051 add issue templates 2024-09-17 10:47:49 +08:00
61 changed files with 4587 additions and 584 deletions

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,32 @@
---
name: Bug report
about: 创建一个报告来帮助我们改进
title: "[Bug] 标题简要描述问题"
labels: bug
assignees: ''
---
**描述问题**
简要描述问题是什么
**复现步骤**
复现该问题的步骤:
1. 去到 '...'
2. 点击 '...'
3. 滚动到 '...'
4. 发现问题
**期望的结果**
简要描述你期望发生的事情。
**截图**
如有可能,请添加截图以帮助解释问题。
**环境**
- 操作系统: [e.g. Windows, macOS]
- 浏览器: [e.g. Chrome, Safari]
- 仓库版本: [e.g. v1.0.0]
**其他信息**
在此处添加关于该问题的任何其他信息。

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 加入频道讨论
url: https://t.me/+ZXphsppxUg41YmVl
about: 加入到电报频道寻求更多帮助

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: 提出一个新功能请求
title: "[Feature] 简要描述你希望实现的功能"
labels: enhancement
assignees: ''
---
**功能描述**
简要描述你希望添加的功能和相关问题。
**动机**
为什么这个功能对项目有帮助?
**替代方案**
描述你已经考虑过的替代方案。
**其他信息**
在这里添加任何相关的附加信息或截图。

View File

@@ -4,6 +4,12 @@ on:
push:
tags:
- "*"
workflow_dispatch:
inputs:
tag:
description: "Tag version to be used for Docker image"
required: true
default: "v0.1.9"
jobs:
build-and-push:
@@ -19,7 +25,22 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
usual2970/certimate
registry.cn-shanghai.aliyuncs.com/usual2970/certimate
- name: Log in to DOCKERHUB
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Log in to ALIYUNCS
uses: docker/login-action@v3
with:
@@ -27,10 +48,6 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract Git tag
id: get_tag
run: echo "tag=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
@@ -38,6 +55,4 @@ jobs:
file: ./Dockerfile_build
platforms: linux/amd64,linux/arm64
push: true
tags: |
registry.cn-shanghai.aliyuncs.com/usual2970/certimate:${{ env.tag }}
registry.cn-shanghai.aliyuncs.com/usual2970/certimate:latest
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -35,6 +35,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
- [3. 部署服务商授权信息](#3-部署服务商授权信息)
- [六、常见问题](#六常见问题)
- [七、贡献](#七贡献)
- [八、加入社区](#八加入社区)
@@ -114,7 +115,7 @@ go run main.go serve
Certimate 的工作流程如下:
* 用户通过 Certimate 管理页面填写申请证书的信息包括域名、dns 服务商的授权信息、以及要部署到的服务商的授权信息。
* Certimate 向证书商的 API 发起申请请求,获取 SSL 证书。
* Certimate 向证书商的 API 发起申请请求,获取 SSL 证书。
* Certimate 存储证书信息,包括证书内容、私钥、证书有效期等,并在证书即将过期时自动续期。
* Certimate 向服务商的 API 发起部署请求,将证书部署到服务商的服务器上。
@@ -178,4 +179,10 @@ Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.
支持更多服务商、UI 的优化改进、BUG 修复、文档完善等,欢迎大家提交 PR。
## 八、加入社区
* [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
* 微信群聊
<img src="https://i.imgur.com/lJUfTeD.png" width="400"/>

BIN
certimate
View File

Binary file not shown.

25
go.mod
View File

@@ -7,12 +7,13 @@ toolchain go1.22.5
require (
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8
github.com/alibabacloud-go/tea v1.2.1
github.com/alibabacloud-go/tea-utils/v2 v2.0.5
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9
github.com/alibabacloud-go/tea v1.2.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.6
github.com/go-acme/lego/v4 v4.17.4
github.com/gojek/heimdall/v7 v7.0.3
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/nikoksr/notify v1.0.0
github.com/pkg/sftp v1.13.6
github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/pocketbase v0.22.18
@@ -23,16 +24,26 @@ require (
golang.org/x/crypto v0.26.0
)
require github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
require (
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
github.com/blinkbean/dingtalk v1.1.3 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
)
require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
github.com/alibabacloud-go/dcdn-20180115/v3 v3.4.2
github.com/alibabacloud-go/debug v1.0.0 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.4.3 // indirect
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 // indirect
github.com/aliyun/credentials-go v1.3.1 // indirect
@@ -57,7 +68,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/clbanning/mxj/v2 v2.5.6 // indirect
github.com/cloudflare/cloudflare-go v0.97.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect

82
go.sum
View File

@@ -8,10 +8,10 @@ cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY=
cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o=
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
@@ -35,26 +35,49 @@ github.com/alibabacloud-go/cas-20200407/v2 v2.3.0 h1:nOrp0n2nFZiYN0wIG7S26YVVaMM
github.com/alibabacloud-go/cas-20200407/v2 v2.3.0/go.mod h1:yzkgdLANANu/v56k0ptslGl++JJL4Op1V09HTavfoCo=
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0 h1:yTKngw4rBR3hdpoo/uCyBffYXfPfjNjlaDL8nTYUIds=
github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0/go.mod h1:HxQrwVKBx3/6bIwmdDcpqBpSQt2tpi/j4LfEhl+QFPk=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.6/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8 h1:benoD0QHDrylMzEQVpX/6uKtrN8LohT66ZlKXVJh7pM=
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI=
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
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/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/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 h1:L0TIjr9Qh/SLVc1yPhFkcB9+9SbCNK/jPq4ZKB5zmnc=
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1/go.mod h1:EKxBRDLcMzwl4VLF/1WJwlByZZECJawPXUvinKMsTTs=
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.10/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
github.com/alibabacloud-go/tea v1.1.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.2.1 h1:rFF1LnrAdhaiPmKwH5xwYOKlMh66CqRwPUTzIK74ask=
github.com/alibabacloud-go/tea v1.1.19/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=
github.com/alibabacloud-go/tea-fileform v1.1.1 h1:1YG6erAP3joQ0XdCXYIotuD7zyOM6qCR49xkp5FZDeU=
github.com/alibabacloud-go/tea-fileform v1.1.1/go.mod h1:ZeCV91o4ISmxidd686f0ebdS5EDHWU+vW+TkjLhrsFE=
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 h1:EhAHI6edMeqgkZEqP7r4nc9iMWAUBKGxJHoBsOSKTtU=
github.com/alibabacloud-go/tea-oss-sdk v1.1.3/go.mod h1:yUnodpR3Bf2rudLE7V/Gft5txjJF30Pk+hH77K/Eab0=
github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS3IaDWE7M9nVLRc=
github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY=
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils v1.4.3 h1:8SzwmmRrOnQ09Hf5a9GyfJc0d7Sjv6fmsZoF4UDbFjo=
github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5 h1:EUakYEUAwr6L3wLT0vejIw2rc0IA1RSXDwLnIb3f2vU=
github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA=
github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
github.com/alibabacloud-go/tea-xml v1.1.1/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 h1:lM7JnA9dEdDFH9XOgRNQMDTQnOjlLkDTNA7c0aWTQ30=
@@ -105,12 +128,15 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudr
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/clbanning/mxj/v2 v2.5.6 h1:Jm4VaCI/+Ug5Q57IzEoZbwx4iQFA6wkXv72juUSeK+g=
github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.97.0 h1:feZRGiRF1EbljnNIYdt8014FnOLtC3CCvgkLXu915ks=
github.com/cloudflare/cloudflare-go v0.97.0/go.mod h1:JXRwuTfHpe5xFg8xytc2w0XC6LcrFsBVMS4WlVaiGg8=
@@ -167,6 +193,8 @@ 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-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=
@@ -244,6 +272,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
github.com/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/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=
@@ -291,6 +321,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
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=
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/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
@@ -347,6 +379,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.898/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992 h1:266lOve+E8vzhnrb/Mr05Ee+oxXD9C82JiusY/AZqXw=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
@@ -371,16 +405,16 @@ github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
gocloud.dev v0.37.0 h1:XF1rN6R0qZI/9DYjN16Uy0durAmSlf58DHOcb28GPro=
@@ -395,6 +429,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -438,6 +473,7 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -473,6 +509,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -482,6 +519,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -494,8 +532,10 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -6,10 +6,12 @@ import (
"crypto/elliptic"
"crypto/rand"
"errors"
"strings"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
"github.com/pocketbase/pocketbase/models"
@@ -35,9 +37,10 @@ type Certificate struct {
}
type ApplyOption struct {
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
Nameservers string `json:"nameservers"`
}
type MyUser struct {
@@ -67,9 +70,10 @@ func Get(record *models.Record) (Applicant, error) {
email = defaultEmail
}
option := &ApplyOption{
Email: email,
Domain: record.GetString("domain"),
Access: access.GetString("config"),
Email: email,
Domain: record.GetString("domain"),
Access: access.GetString("config"),
Nameservers: record.GetString("nameservers"),
}
switch access.GetString("configType") {
case configTypeTencent:
@@ -111,7 +115,13 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
return nil, err
}
client.Challenge.SetDNS01Provider(provider)
challengeOptions := make([]dns01.ChallengeOption, 0)
nameservers := ParseNameservers(option.Nameservers)
if len(nameservers) > 0 {
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(nameservers))
}
client.Challenge.SetDNS01Provider(provider, challengeOptions...)
// New users will need to register
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
@@ -120,8 +130,16 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
}
myUser.Registration = reg
domains := []string{option.Domain}
// 如果是通配置符域名,把根域名也加入
if strings.HasPrefix(option.Domain, "*.") && len(strings.Split(option.Domain, ".")) == 3 {
rootDomain := strings.TrimPrefix(option.Domain, "*.")
domains = append(domains, rootDomain)
}
request := certificate.ObtainRequest{
Domains: []string{option.Domain},
Domains: domains,
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
@@ -138,3 +156,22 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
Csr: string(certificates.CSR),
}, nil
}
func ParseNameservers(ns string) []string {
nameservers := make([]string, 0)
lines := strings.Split(ns, ";")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
nameservers = append(nameservers, line)
}
return nameservers
}

View File

@@ -0,0 +1,86 @@
/*
* @Author: Bin
* @Date: 2024-09-17
* @FilePath: /certimate/internal/deployer/aliyun_esa.go
*/
package deployer
import (
"certimate/internal/domain"
"context"
"encoding/json"
"fmt"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
)
type AliyunEsa struct {
client *dcdn20180115.Client
option *DeployerOption
infos []string
}
func NewAliyunEsa(option *DeployerOption) (*AliyunEsa, error) {
access := &domain.AliyunAccess{}
json.Unmarshal([]byte(option.Access), access)
a := &AliyunEsa{
option: option,
}
client, err := a.createClient(access.AccessKeyId, access.AccessKeySecret)
if err != nil {
return nil, err
}
return &AliyunEsa{
client: client,
option: option,
infos: make([]string, 0),
}, nil
}
func (a *AliyunEsa) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (a *AliyunEsa) GetInfo() []string {
return a.infos
}
func (a *AliyunEsa) Deploy(ctx context.Context) error {
certName := fmt.Sprintf("%s-%s", a.option.Domain, a.option.DomainId)
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
DomainName: tea.String(a.option.Domain),
CertName: tea.String(certName),
CertType: tea.String("upload"),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(a.option.Certificate.Certificate),
SSLPri: tea.String(a.option.Certificate.PrivateKey),
CertRegion: tea.String("cn-hangzhou"),
}
runtime := &util.RuntimeOptions{}
resp, err := a.client.SetDcdnDomainSSLCertificateWithOptions(setDcdnDomainSSLCertificateRequest, runtime)
if err != nil {
return err
}
a.infos = append(a.infos, toStr("dcdn设置证书", resp))
return nil
}
func (a *AliyunEsa) createClient(accessKeyId, accessKeySecret string) (_result *dcdn20180115.Client, _err error) {
config := &openapi.Config{
AccessKeyId: tea.String(accessKeyId),
AccessKeySecret: tea.String(accessKeySecret),
}
config.Endpoint = tea.String("dcdn.aliyuncs.com")
_result = &dcdn20180115.Client{}
_result, _err = dcdn20180115.NewClient(config)
return _result, _err
}

View File

@@ -16,6 +16,7 @@ import (
const (
targetAliyunOss = "aliyun-oss"
targetAliyunCdn = "aliyun-cdn"
targetAliyunEsa = "aliyun-dcdn"
targetSSH = "ssh"
targetWebhook = "webhook"
targetTencentCdn = "tencent-cdn"
@@ -117,6 +118,8 @@ func getWithAccess(record *models.Record, cert *applicant.Certificate, access *m
return NewAliyun(option)
case targetAliyunCdn:
return NewAliyunCdn(option)
case targetAliyunEsa:
return NewAliyunEsa(option)
case targetSSH:
return NewSSH(option)
case targetWebhook:

View File

@@ -2,16 +2,14 @@ package deployer
import (
"certimate/internal/domain"
"certimate/internal/utils/rand"
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"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"
tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813"
)
type tencentCdn struct {
@@ -49,14 +47,6 @@ func (t *tencentCdn) GetInfo() []string {
func (t *tencentCdn) Deploy(ctx context.Context) error {
// 查询有没有对应的资源
resource, err := t.resource()
if err != nil {
return fmt.Errorf("failed to get resource: %w", err)
}
t.infos = append(t.infos, toStr("查询对应的资源", resource))
// 上传证书
certId, err := t.uploadCert()
if err != nil {
@@ -64,7 +54,7 @@ func (t *tencentCdn) Deploy(ctx context.Context) error {
}
t.infos = append(t.infos, toStr("上传证书", certId))
if err := t.deploy(resource, certId); err != nil {
if err := t.deploy(certId); err != nil {
return fmt.Errorf("failed to deploy: %w", err)
}
@@ -82,7 +72,7 @@ func (t *tencentCdn) uploadCert() (string, error) {
request.CertificatePublicKey = common.StringPtr(t.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(t.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(t.option.Domain)
request.Alias = common.StringPtr(t.option.Domain + "_" + rand.RandStr(6))
request.Repeatable = common.BoolPtr(true)
response, err := client.UploadCertificate(request)
@@ -93,22 +83,17 @@ func (t *tencentCdn) uploadCert() (string, error) {
return *response.Response.CertificateId, nil
}
func (t *tencentCdn) deploy(resource *tag.ResourceTagMapping, certId string) error {
func (t *tencentCdn) deploy(certId string) error {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := ssl.NewClient(t.credential, "", cpf)
resourceId, err := getResourceId(resource)
if err != nil {
return fmt.Errorf("failed to get resource id: %w", err)
}
// 实例化一个请求对象,每个接口都会对应一个request对象
request := ssl.NewDeployCertificateInstanceRequest()
request.CertificateId = common.StringPtr(certId)
request.InstanceIdList = common.StringPtrs([]string{resourceId})
request.InstanceIdList = common.StringPtrs([]string{t.option.Domain})
request.ResourceType = common.StringPtr("cdn")
request.Status = common.Int64Ptr(1)
@@ -121,55 +106,3 @@ func (t *tencentCdn) deploy(resource *tag.ResourceTagMapping, certId string) err
t.infos = append(t.infos, toStr("部署证书", resp.Response))
return nil
}
func (t *tencentCdn) resource() (*tag.ResourceTagMapping, error) {
request := tag.NewGetResourcesRequest()
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "tag.tencentcloudapi.com"
client, err := tag.NewClient(t.credential, "", cpf)
if err != nil {
return nil, fmt.Errorf("failed to create client: %w", err)
}
response, err := client.GetResources(request)
if err != nil {
return nil, fmt.Errorf("failed to get resources: %w", err)
}
for _, resource := range response.Response.ResourceTagMappingList {
if t.compare(resource) {
return resource, nil
}
}
return nil, errors.New("no resource found")
}
func (t *tencentCdn) compare(resource *tag.ResourceTagMapping) bool {
slices := strings.Split(*resource.Resource, "/")
if len(slices) != 3 {
return false
}
typeSlices := strings.Split(slices[0], "::")
if len(typeSlices) != 3 {
return false
}
if typeSlices[1] != "cdn" || slices[2] != t.option.Domain {
return false
}
return true
}
func getResourceId(resource *tag.ResourceTagMapping) (string, error) {
slices := strings.Split(*resource.Resource, "/")
if len(slices) != 3 {
return "", errors.New("invalid resource")
}
return slices[2], nil
}

View File

@@ -62,7 +62,10 @@ func deploy(ctx context.Context, record *models.Record) error {
history.record(checkPhase, "证书在有效期内且已部署,跳过", &RecordInfo{
Info: []string{fmt.Sprintf("证书有效期至 %s", expiredAt.Format("2006-01-02"))},
}, true)
return err
// 跳过的情况也算成功
history.setWholeSuccess(true)
return nil
}
history.record(checkPhase, "检查通过", nil, true)
@@ -119,5 +122,7 @@ func deploy(ctx context.Context, record *models.Record) error {
app.GetApp().Logger().Info("部署成功")
history.record(deployPhase, "部署成功", nil, true)
history.setWholeSuccess(true)
return nil
}

View File

@@ -28,6 +28,7 @@ type history struct {
PhaseSuccess bool `json:"phaseSuccess"`
DeployedAt string `json:"deployedAt"`
Cert *applicant.Certificate `json:"cert"`
WholeSuccess bool `json:"wholeSuccess"`
}
func NewHistory(record *models.Record) *history {
@@ -68,6 +69,10 @@ func (a *history) setCert(cert *applicant.Certificate) {
a.Cert = cert
}
func (a *history) setWholeSuccess(success bool) {
a.WholeSuccess = success
}
func (a *history) commit() error {
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("deployments")
if err != nil {
@@ -81,6 +86,7 @@ func (a *history) commit() error {
record.Set("log", a.Log)
record.Set("phase", string(a.Phase))
record.Set("phaseSuccess", a.PhaseSuccess)
record.Set("wholeSuccess", a.WholeSuccess)
if err := app.GetApp().Dao().SaveRecord(record); err != nil {
return err

View File

@@ -1,6 +1,7 @@
package domains
import (
"certimate/internal/notify"
"certimate/internal/utils/app"
"context"
)
@@ -25,6 +26,11 @@ func InitSchedule() {
}
}
// 过期提醒
app.GetScheduler().Add("expire", "0 0 * * *", func() {
notify.PushExpireMsg()
})
// 启动定时任务
app.GetScheduler().Start()
app.GetApp().Logger().Info("定时任务启动成功", "total", app.GetScheduler().Total())

98
internal/notify/expire.go Normal file
View File

@@ -0,0 +1,98 @@
package notify
import (
"certimate/internal/utils/app"
"certimate/internal/utils/xtime"
"strconv"
"strings"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/models"
)
type msg struct {
subject string
message string
}
const (
defaultExpireSubject = "您有{COUNT}张证书即将过期"
defaultExpireMsg = "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
)
func PushExpireMsg() {
// 查询即将过期的证书
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "expiredAt<{:time}&&certUrl!=''", "-created", 500, 0,
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 15)})
if err != nil {
app.GetApp().Logger().Error("find expired domains by filter", "error", err)
return
}
// 组装消息
msg := buildMsg(records)
if msg == nil {
return
}
if err := Send(msg.subject, msg.message); err != nil {
app.GetApp().Logger().Error("send expire msg", "error", err)
}
}
type notifyTemplates struct {
NotifyTemplates []notifyTemplate `json:"notifyTemplates"`
}
type notifyTemplate struct {
Title string `json:"title"`
Content string `json:"content"`
}
func buildMsg(records []*models.Record) *msg {
if len(records) == 0 {
return nil
}
// 查询模板信息
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
title := defaultExpireSubject
content := defaultExpireMsg
if err == nil {
var templates *notifyTemplates
templateRecord.UnmarshalJSONField("content", templates)
if templates != nil && len(templates.NotifyTemplates) > 0 {
title = templates.NotifyTemplates[0].Title
content = templates.NotifyTemplates[0].Content
}
}
// 替换变量
count := len(records)
domains := make([]string, count)
for i, record := range records {
domains[i] = record.GetString("domain")
}
countStr := strconv.Itoa(count)
domainStr := strings.Join(domains, ",")
title = strings.ReplaceAll(title, "{COUNT}", countStr)
title = strings.ReplaceAll(title, "{DOMAINS}", domainStr)
content = strings.ReplaceAll(content, "{COUNT}", countStr)
content = strings.ReplaceAll(content, "{DOMAINS}", domainStr)
// 返回消息
return &msg{
subject: title,
message: content,
}
}

129
internal/notify/notify.go Normal file
View File

@@ -0,0 +1,129 @@
package notify
import (
"certimate/internal/utils/app"
"context"
"fmt"
"strconv"
notifyPackage "github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/dingding"
"github.com/nikoksr/notify/service/telegram"
"github.com/nikoksr/notify/service/http"
)
const (
notifyChannelDingtalk = "dingtalk"
notifyChannelWebhook = "webhook"
notifyChannelTelegram = "telegram"
)
func Send(title, content string) error {
// 获取所有的推送渠道
notifiers, err := getNotifiers()
if err != nil {
return err
}
if len(notifiers) == 0 {
return nil
}
// 添加推送渠道
notifyPackage.UseServices(notifiers...)
// 发送消息
return notifyPackage.Send(context.Background(), title, content)
}
func getNotifiers() ([]notifyPackage.Notifier, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
if err != nil {
return nil, fmt.Errorf("find notifyChannels error: %w", err)
}
notifiers := make([]notifyPackage.Notifier, 0)
rs := make(map[string]map[string]any)
if err := resp.UnmarshalJSONField("content", &rs); err != nil {
return nil, fmt.Errorf("unmarshal notifyChannels error: %w", err)
}
for k, v := range rs {
if !getBool(v, "enabled") {
continue
}
switch k {
case notifyChannelTelegram:
temp := getTelegramNotifier(v)
if temp == nil {
continue
}
notifiers = append(notifiers, temp)
case notifyChannelDingtalk:
notifiers = append(notifiers, getDingTalkNotifier(v))
case notifyChannelWebhook:
notifiers = append(notifiers, getWebhookNotifier(v))
}
}
return notifiers, nil
}
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
rs := http.New()
rs.AddReceiversURLs(getString(conf, "url"))
return rs
}
func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier {
rs, err := telegram.New(getString(conf, "apiToken"))
if err != nil {
return nil
}
chatId := getString(conf, "chatId")
id, err := strconv.ParseInt(chatId, 10, 64)
if err != nil {
return nil
}
rs.AddReceivers(id)
return rs
}
func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier {
return dingding.New(&dingding.Config{
Token: getString(conf, "accessToken"),
Secret: getString(conf, "secret"),
})
}
func getString(conf map[string]any, key string) string {
if _, ok := conf[key]; !ok {
return ""
}
return conf[key].(string)
}
func getBool(conf map[string]any, key string) bool {
if _, ok := conf[key]; !ok {
return false
}
return conf[key].(bool)
}

View File

@@ -13,3 +13,10 @@ func BeijingTimeStr() string {
// 格式化为字符串
return now.Format("2006-01-02 15:04:05")
}
func GetTimeAfter(d time.Duration) string {
t := time.Now().UTC()
return t.Add(d).Format("2006-01-02 15:04:05")
}

View File

@@ -0,0 +1,85 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
if err != nil {
return err
}
// update
edit_targetType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "srybpixz",
"name": "targetType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun-oss",
"aliyun-cdn",
"aliyun-dcdn",
"ssh",
"webhook",
"tencent-cdn",
"qiniu-cdn"
]
}
}`), edit_targetType); err != nil {
return err
}
collection.Schema.AddField(edit_targetType)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("z3p974ainxjqlvs")
if err != nil {
return err
}
// update
edit_targetType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "srybpixz",
"name": "targetType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun-oss",
"aliyun-cdn",
"ssh",
"webhook",
"tencent-cdn",
"qiniu-cdn"
]
}
}`), edit_targetType); err != nil {
return err
}
collection.Schema.AddField(edit_targetType)
return dao.SaveCollection(collection)
})
}

View File

@@ -0,0 +1,694 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models"
)
func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `[
{
"id": "z3p974ainxjqlvs",
"created": "2024-07-29 10:02:48.334Z",
"updated": "2024-09-18 14:23:22.359Z",
"name": "domains",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "iuaerpl2",
"name": "domain",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "ukkhuw85",
"name": "email",
"type": "email",
"required": false,
"presentable": false,
"unique": false,
"options": {
"exceptDomains": null,
"onlyDomains": null
}
},
{
"system": false,
"id": "v98eebqq",
"name": "crontab",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "alc8e9ow",
"name": "access",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "topsc9bj",
"name": "certUrl",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "vixgq072",
"name": "certStableUrl",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "g3a3sza5",
"name": "privateKey",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "gr6iouny",
"name": "certificate",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "tk6vnrmn",
"name": "issuerCertificate",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "sjo6ibse",
"name": "csr",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "x03n1bkj",
"name": "expiredAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "srybpixz",
"name": "targetType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun-oss",
"aliyun-cdn",
"aliyun-dcdn",
"ssh",
"webhook",
"tencent-cdn",
"qiniu-cdn"
]
}
},
{
"system": false,
"id": "xy7yk0mb",
"name": "targetAccess",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "6jqeyggw",
"name": "enabled",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "hdsjcchf",
"name": "deployed",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "aiya3rev",
"name": "rightnow",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "ixznmhzc",
"name": "lastDeployedAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "ghtlkn5j",
"name": "lastDeployment",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "0a1o4e6sstp694f",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "zfnyj9he",
"name": "variables",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "1bspzuku",
"name": "group",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "teolp9pl72dxlxq",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "g65gfh7a",
"name": "nameservers",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "4yzbv8urny5ja1e",
"created": "2024-07-29 10:04:39.685Z",
"updated": "2024-09-17 00:53:25.859Z",
"name": "access",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "geeur58v",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "iql7jpwx",
"name": "config",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"ssh",
"webhook",
"cloudflare",
"qiniu",
"namesilo",
"godaddy"
]
}
},
{
"system": false,
"id": "lr33hiwg",
"name": "deleted",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "hsxcnlvd",
"name": "usage",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"apply",
"deploy",
"all"
]
}
},
{
"system": false,
"id": "c8egzzwj",
"name": "group",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "teolp9pl72dxlxq",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "0a1o4e6sstp694f",
"created": "2024-07-30 06:30:27.801Z",
"updated": "2024-09-17 00:53:25.859Z",
"name": "deployments",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "farvlzk7",
"name": "domain",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "z3p974ainxjqlvs",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "jx5f69i3",
"name": "log",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "qbxdtg9q",
"name": "phase",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"check",
"apply",
"deploy"
]
}
},
{
"system": false,
"id": "rglrp1hz",
"name": "phaseSuccess",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "lt1g1blu",
"name": "deployedAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"created": "2024-09-12 13:09:54.234Z",
"updated": "2024-09-17 00:53:25.859Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
},
{
"id": "dy6ccjb60spfy6p",
"created": "2024-09-12 23:12:21.677Z",
"updated": "2024-09-17 00:53:25.860Z",
"name": "settings",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "1tcmdsdf",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "f9wyhypi",
"name": "content",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "teolp9pl72dxlxq",
"created": "2024-09-13 12:51:05.611Z",
"updated": "2024-09-17 00:53:25.860Z",
"name": "access_groups",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "7sajiv6i",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "xp8admif",
"name": "access",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
}
]`
collections := []*models.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
return err
}
return daos.New(db).ImportCollections(collections, true, nil)
}, func(db dbx.Builder) error {
return nil
})
}

View File

@@ -0,0 +1,704 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models"
)
func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `[
{
"id": "z3p974ainxjqlvs",
"created": "2024-07-29 10:02:48.334Z",
"updated": "2024-09-19 00:27:35.936Z",
"name": "domains",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "iuaerpl2",
"name": "domain",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "ukkhuw85",
"name": "email",
"type": "email",
"required": false,
"presentable": false,
"unique": false,
"options": {
"exceptDomains": null,
"onlyDomains": null
}
},
{
"system": false,
"id": "v98eebqq",
"name": "crontab",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "alc8e9ow",
"name": "access",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "topsc9bj",
"name": "certUrl",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "vixgq072",
"name": "certStableUrl",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "g3a3sza5",
"name": "privateKey",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "gr6iouny",
"name": "certificate",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "tk6vnrmn",
"name": "issuerCertificate",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "sjo6ibse",
"name": "csr",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "x03n1bkj",
"name": "expiredAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "srybpixz",
"name": "targetType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun-oss",
"aliyun-cdn",
"aliyun-dcdn",
"ssh",
"webhook",
"tencent-cdn",
"qiniu-cdn"
]
}
},
{
"system": false,
"id": "xy7yk0mb",
"name": "targetAccess",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "6jqeyggw",
"name": "enabled",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "hdsjcchf",
"name": "deployed",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "aiya3rev",
"name": "rightnow",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "ixznmhzc",
"name": "lastDeployedAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "ghtlkn5j",
"name": "lastDeployment",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "0a1o4e6sstp694f",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "zfnyj9he",
"name": "variables",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "1bspzuku",
"name": "group",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "teolp9pl72dxlxq",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "g65gfh7a",
"name": "nameservers",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "4yzbv8urny5ja1e",
"created": "2024-07-29 10:04:39.685Z",
"updated": "2024-09-19 00:27:35.936Z",
"name": "access",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "geeur58v",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "iql7jpwx",
"name": "config",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"ssh",
"webhook",
"cloudflare",
"qiniu",
"namesilo",
"godaddy"
]
}
},
{
"system": false,
"id": "lr33hiwg",
"name": "deleted",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "hsxcnlvd",
"name": "usage",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"apply",
"deploy",
"all"
]
}
},
{
"system": false,
"id": "c8egzzwj",
"name": "group",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "teolp9pl72dxlxq",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "0a1o4e6sstp694f",
"created": "2024-07-30 06:30:27.801Z",
"updated": "2024-09-22 11:46:35.167Z",
"name": "deployments",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "farvlzk7",
"name": "domain",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "z3p974ainxjqlvs",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "jx5f69i3",
"name": "log",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "qbxdtg9q",
"name": "phase",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"check",
"apply",
"deploy"
]
}
},
{
"system": false,
"id": "rglrp1hz",
"name": "phaseSuccess",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "lt1g1blu",
"name": "deployedAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "wledpzgb",
"name": "wholeSuccess",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"created": "2024-09-12 13:09:54.234Z",
"updated": "2024-09-19 00:27:35.937Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
},
{
"id": "dy6ccjb60spfy6p",
"created": "2024-09-12 23:12:21.677Z",
"updated": "2024-09-19 00:27:35.937Z",
"name": "settings",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "1tcmdsdf",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "f9wyhypi",
"name": "content",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "teolp9pl72dxlxq",
"created": "2024-09-13 12:51:05.611Z",
"updated": "2024-09-19 00:27:35.937Z",
"name": "access_groups",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "7sajiv6i",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "xp8admif",
"name": "access",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
}
]`
collections := []*models.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
return err
}
return daos.New(db).ImportCollections(collections, true, nil)
}, func(db dbx.Builder) error {
return nil
})
}

View File

@@ -0,0 +1,704 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models"
)
func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `[
{
"id": "z3p974ainxjqlvs",
"created": "2024-07-29 10:02:48.334Z",
"updated": "2024-09-22 12:08:14.644Z",
"name": "domains",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "iuaerpl2",
"name": "domain",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "ukkhuw85",
"name": "email",
"type": "email",
"required": false,
"presentable": false,
"unique": false,
"options": {
"exceptDomains": null,
"onlyDomains": null
}
},
{
"system": false,
"id": "v98eebqq",
"name": "crontab",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "alc8e9ow",
"name": "access",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "topsc9bj",
"name": "certUrl",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "vixgq072",
"name": "certStableUrl",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "g3a3sza5",
"name": "privateKey",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "gr6iouny",
"name": "certificate",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "tk6vnrmn",
"name": "issuerCertificate",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "sjo6ibse",
"name": "csr",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "x03n1bkj",
"name": "expiredAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "srybpixz",
"name": "targetType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun-oss",
"aliyun-cdn",
"aliyun-dcdn",
"ssh",
"webhook",
"tencent-cdn",
"qiniu-cdn"
]
}
},
{
"system": false,
"id": "xy7yk0mb",
"name": "targetAccess",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "6jqeyggw",
"name": "enabled",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "hdsjcchf",
"name": "deployed",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "aiya3rev",
"name": "rightnow",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "ixznmhzc",
"name": "lastDeployedAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "ghtlkn5j",
"name": "lastDeployment",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "0a1o4e6sstp694f",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "zfnyj9he",
"name": "variables",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "1bspzuku",
"name": "group",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "teolp9pl72dxlxq",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "g65gfh7a",
"name": "nameservers",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_4ABO6EQ` + "`" + ` ON ` + "`" + `domains` + "`" + ` (` + "`" + `domain` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "4yzbv8urny5ja1e",
"created": "2024-07-29 10:04:39.685Z",
"updated": "2024-09-22 12:08:14.644Z",
"name": "access",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "geeur58v",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "iql7jpwx",
"name": "config",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"ssh",
"webhook",
"cloudflare",
"qiniu",
"namesilo",
"godaddy"
]
}
},
{
"system": false,
"id": "lr33hiwg",
"name": "deleted",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "hsxcnlvd",
"name": "usage",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"apply",
"deploy",
"all"
]
}
},
{
"system": false,
"id": "c8egzzwj",
"name": "group",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "teolp9pl72dxlxq",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_wkoST0j` + "`" + ` ON ` + "`" + `access` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "0a1o4e6sstp694f",
"created": "2024-07-30 06:30:27.801Z",
"updated": "2024-09-22 12:08:14.644Z",
"name": "deployments",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "farvlzk7",
"name": "domain",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "z3p974ainxjqlvs",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": null
}
},
{
"system": false,
"id": "jx5f69i3",
"name": "log",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
},
{
"system": false,
"id": "qbxdtg9q",
"name": "phase",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"check",
"apply",
"deploy"
]
}
},
{
"system": false,
"id": "rglrp1hz",
"name": "phaseSuccess",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
},
{
"system": false,
"id": "lt1g1blu",
"name": "deployedAt",
"type": "date",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "wledpzgb",
"name": "wholeSuccess",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
}
],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"created": "2024-09-12 13:09:54.234Z",
"updated": "2024-09-22 12:08:14.644Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
},
{
"id": "dy6ccjb60spfy6p",
"created": "2024-09-12 23:12:21.677Z",
"updated": "2024-09-22 12:08:14.644Z",
"name": "settings",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "1tcmdsdf",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "f9wyhypi",
"name": "content",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "teolp9pl72dxlxq",
"created": "2024-09-13 12:51:05.611Z",
"updated": "2024-09-22 12:08:14.645Z",
"name": "access_groups",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "7sajiv6i",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "xp8admif",
"name": "access",
"type": "relation",
"required": false,
"presentable": false,
"unique": false,
"options": {
"collectionId": "4yzbv8urny5ja1e",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": null,
"displayFields": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RgRXp0R` + "`" + ` ON ` + "`" + `access_groups` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
}
]`
collections := []*models.Collection{}
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
return err
}
return daos.New(db).ImportCollections(collections, true, nil)
}, func(db dbx.Builder) error {
return nil
})
}

308
ui/dist/assets/index-B-WSkv0U.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

1
ui/dist/assets/index-BLt37AhW.css vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<path d="M0 0 C8.00019718 6.13887963 14.34445574 12.99622218 18.3125 22.3125 C15.06464319 20.86413142 12.72723872 19.02613433 10.0625 16.6875 C-1.60459795 7.2541616 -16.18206674 0.63880608 -31.375 1.4375 C-43.09356312 2.78801868 -53.73683191 6.67588577 -62 15.5 C-71.51641841 27.69982709 -72.77906448 40.43807508 -71.6875 55.3125 C-69.75496452 69.19218976 -63.07550701 81.31999767 -54.6875 92.3125 C-53.96433594 93.26769531 -53.96433594 93.26769531 -53.2265625 94.2421875 C-49.80310028 98.49787258 -45.86843357 101.64242084 -41.4453125 104.83203125 C-40.86523438 105.32058594 -40.28515625 105.80914062 -39.6875 106.3125 C-39.6875 106.9725 -39.6875 107.6325 -39.6875 108.3125 C-45.21884376 108.61097445 -50.74543637 108.82766089 -56.28295898 108.97167969 C-58.1623705 109.03173819 -60.04122791 109.11342379 -61.91870117 109.21777344 C-77.40966514 110.05667884 -77.40966514 110.05667884 -82.80957031 105.921875 C-85.19168462 102.82857874 -86.97025266 99.81402723 -88.6875 96.3125 C-89.16856201 95.46268555 -89.64962402 94.61287109 -90.14526367 93.73730469 C-91.39695086 91.46566255 -92.49925956 89.17751393 -93.5625 86.8125 C-93.93689209 85.9877417 -94.31128418 85.1629834 -94.69702148 84.31323242 C-101.84695145 68.18991103 -104.37476618 48.01876725 -97.94140625 31.1171875 C-97.52761719 30.19164062 -97.11382812 29.26609375 -96.6875 28.3125 C-96.26339844 27.30445313 -95.83929688 26.29640625 -95.40234375 25.2578125 C-88.42098361 9.9727176 -77.15139172 -1.27183748 -61.6875 -7.6875 C-40.67179933 -14.71095263 -18.47073623 -12.74198216 0 0 Z " fill="#70A2EA" transform="translate(151.6875,52.6875)"/>
<path d="M0 0 C-0.19335938 0.56847656 -0.38671875 1.13695313 -0.5859375 1.72265625 C-1.13131792 3.33989819 -1.66856703 4.95989684 -2.19921875 6.58203125 C-2.82868314 8.48270666 -3.50036013 10.36940667 -4.1875 12.25 C-7.48441131 27.46651373 -2.93859997 42.92249193 5 56 C10.27285559 63.85713427 17.42266677 71.71133339 26 76 C28.93108041 76.12365056 31.83742448 76.18844664 34.76953125 76.203125 C35.64268478 76.20882507 36.51583832 76.21452515 37.41545105 76.22039795 C39.26564317 76.22985345 41.11585218 76.23638411 42.96606445 76.24023438 C45.79082844 76.24992565 48.61486044 76.28093755 51.43945312 76.3125 C53.23697485 76.31903152 55.03450177 76.32428141 56.83203125 76.328125 C58.0963372 76.3466452 58.0963372 76.3466452 59.38618469 76.36553955 C64.08745075 76.34777001 67.19523771 76.06753063 71 73 C76.14974577 66.13367231 76.85747523 60.40719967 76 52 C72.94347249 40.85669565 66.57825718 33.02825174 57.07421875 26.66796875 C41.98336589 18.48017438 25.32550226 16.19330935 8.4375 15.4375 C7.52419922 15.39431641 6.61089844 15.35113281 5.66992188 15.30664062 C3.44672009 15.20188243 1.22343293 15.09970983 -1 15 C-1 14.34 -1 13.68 -1 13 C24.14499626 7.59108915 50.41887378 8.13666192 72.734375 22.42578125 C84.76471086 30.78479048 94.05571707 43.44065596 97 58 C97.83992741 68.1617447 96.68764977 76.53638183 90.375 84.75 C82.2520582 93.4708233 71.29646331 96.75137499 59.70800781 97.22460938 C55.46822317 97.34944357 51.22762628 97.41269989 46.98632812 97.45996094 C45.16900252 97.48655278 43.35182742 97.52746211 41.53515625 97.58300781 C30.19480451 97.9245351 21.92945424 97.88338566 13 90 C12.08879395 89.26837646 12.08879395 89.26837646 11.15917969 88.52197266 C-1.6887866 77.91909315 -10.95597826 65.95656876 -16 50 C-16.4125 48.7625 -16.825 47.525 -17.25 46.25 C-19.92772011 34.34631695 -18.39209844 21.70178399 -12.3515625 11.109375 C-4.91282895 0 -4.91282895 0 0 0 Z " fill="#79AFEB" transform="translate(103,64)"/>
<path d="M0 0 C-0.42954381 1.3983022 -0.86907034 2.79353907 -1.3125 4.1875 C-1.55613281 4.96480469 -1.79976562 5.74210937 -2.05078125 6.54296875 C-3.87971163 11.27711365 -6.67096194 14.91291499 -9.9375 18.75 C-16.72958324 27.14022047 -20.11560508 36.06308588 -19 47 C-17.21558238 54.72810276 -14.24239154 60.64225254 -7.625 65.25 C-1.74362035 68.9878388 -1.74362035 68.9878388 5.015625 69.8046875 C7.86441607 69.65527538 9.37848936 69.78974988 12 71 C16.09718736 75.3958468 19.01252254 80.82170573 22 86 C22.99935816 87.6670516 23.99905049 89.3339034 25 91 C5.62249954 91.36484834 -12.69092394 90.61101563 -28 77 C-36.44050971 67.52265225 -39.76608235 57.3077398 -39.3828125 44.7109375 C-38.14502828 32.71200883 -32.24783597 21.70604907 -24 13 C-23.34 13 -22.68 13 -22 13 C-22 12.34 -22 11.68 -22 11 C-16.32431209 6.09717172 -7.83025539 0 0 0 Z " fill="#73A7EA" transform="translate(39,70)"/>
<path d="M0 0 C-2.12910166 2.33471584 -4.07517335 3.68146181 -6.9375 5 C-9.90732447 6.41679699 -12.38725285 7.9901945 -15 10 C-15.66 10 -16.32 10 -17 10 C-17 10.66 -17 11.32 -17 12 C-18.12525155 13.02649277 -19.30282645 13.99587696 -20.5 14.9375 C-33.49161609 26.0469385 -41.59305065 43.11799329 -43.1953125 60.06591797 C-43.714182 69.16559176 -42.71323311 78.06671309 -41 87 C-46.81627942 82.15310048 -49.61443677 75.38967056 -51 68 C-51.69814606 57.29890869 -51.08055936 48.06628054 -47 38 C-46.67257813 37.15308594 -46.34515625 36.30617187 -46.0078125 35.43359375 C-41.86810307 26.02104276 -34.29231204 18.10356984 -27 11 C-27.433125 10.51789062 -27.86625 10.03578125 -28.3125 9.5390625 C-28.869375 8.90742187 -29.42625 8.27578125 -30 7.625 C-30.556875 6.99851562 -31.11375 6.37203125 -31.6875 5.7265625 C-33 4 -33 4 -33 2 C-27.87373026 1.19944082 -22.8041505 0.72131302 -17.625 0.4375 C-16.8812915 0.3950415 -16.13758301 0.35258301 -15.37133789 0.30883789 C-10.24014323 0.03021845 -5.13944387 -0.09344443 0 0 Z " fill="#71A4EA" transform="translate(88,39)"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

4
ui/dist/index.html vendored
View File

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-DoSOua_N.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ChWRjRip.css">
<script type="module" crossorigin src="/assets/index-B-WSkv0U.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BLt37AhW.css">
</head>
<body class="bg-background">
<div id="root"></div>

96
ui/package-lock.json generated
View File

@@ -9,10 +9,12 @@
"version": "0.0.0",
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
@@ -1120,6 +1122,36 @@
"resolved": "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.0.tgz",
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
},
"node_modules/@radix-ui/react-accordion": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-accordion/-/react-accordion-1.2.0.tgz",
"integrity": "sha512-HJOzSX8dQqtsp/3jVxCU3CXEONF7/2jlGAB28oX8TTw1Dz8JYbEI1UcL8355PuLBE41/IRRMvCw7VkiK/jcUOQ==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-collapsible": "1.1.0",
"@radix-ui/react-collection": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-alert-dialog": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.1.tgz",
@@ -1169,6 +1201,35 @@
}
}
},
"node_modules/@radix-ui/react-collapsible": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz",
"integrity": "sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
@@ -1441,6 +1502,41 @@
}
}
},
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.0.tgz",
"integrity": "sha512-OQ8tcwAOR0DhPlSY3e4VMXeHiol7la4PPdJWhhwJiJA+NLX0SaCaonOkRnI3gCDHoZ7Fo7bb/G6q25fRM2Y+3Q==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-collection": "1.1.0",
"@radix-ui/react-compose-refs": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-dismissable-layer": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",

View File

@@ -11,10 +11,12 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<path d="M0 0 C8.00019718 6.13887963 14.34445574 12.99622218 18.3125 22.3125 C15.06464319 20.86413142 12.72723872 19.02613433 10.0625 16.6875 C-1.60459795 7.2541616 -16.18206674 0.63880608 -31.375 1.4375 C-43.09356312 2.78801868 -53.73683191 6.67588577 -62 15.5 C-71.51641841 27.69982709 -72.77906448 40.43807508 -71.6875 55.3125 C-69.75496452 69.19218976 -63.07550701 81.31999767 -54.6875 92.3125 C-53.96433594 93.26769531 -53.96433594 93.26769531 -53.2265625 94.2421875 C-49.80310028 98.49787258 -45.86843357 101.64242084 -41.4453125 104.83203125 C-40.86523438 105.32058594 -40.28515625 105.80914062 -39.6875 106.3125 C-39.6875 106.9725 -39.6875 107.6325 -39.6875 108.3125 C-45.21884376 108.61097445 -50.74543637 108.82766089 -56.28295898 108.97167969 C-58.1623705 109.03173819 -60.04122791 109.11342379 -61.91870117 109.21777344 C-77.40966514 110.05667884 -77.40966514 110.05667884 -82.80957031 105.921875 C-85.19168462 102.82857874 -86.97025266 99.81402723 -88.6875 96.3125 C-89.16856201 95.46268555 -89.64962402 94.61287109 -90.14526367 93.73730469 C-91.39695086 91.46566255 -92.49925956 89.17751393 -93.5625 86.8125 C-93.93689209 85.9877417 -94.31128418 85.1629834 -94.69702148 84.31323242 C-101.84695145 68.18991103 -104.37476618 48.01876725 -97.94140625 31.1171875 C-97.52761719 30.19164062 -97.11382812 29.26609375 -96.6875 28.3125 C-96.26339844 27.30445313 -95.83929688 26.29640625 -95.40234375 25.2578125 C-88.42098361 9.9727176 -77.15139172 -1.27183748 -61.6875 -7.6875 C-40.67179933 -14.71095263 -18.47073623 -12.74198216 0 0 Z " fill="#70A2EA" transform="translate(151.6875,52.6875)"/>
<path d="M0 0 C-0.19335938 0.56847656 -0.38671875 1.13695313 -0.5859375 1.72265625 C-1.13131792 3.33989819 -1.66856703 4.95989684 -2.19921875 6.58203125 C-2.82868314 8.48270666 -3.50036013 10.36940667 -4.1875 12.25 C-7.48441131 27.46651373 -2.93859997 42.92249193 5 56 C10.27285559 63.85713427 17.42266677 71.71133339 26 76 C28.93108041 76.12365056 31.83742448 76.18844664 34.76953125 76.203125 C35.64268478 76.20882507 36.51583832 76.21452515 37.41545105 76.22039795 C39.26564317 76.22985345 41.11585218 76.23638411 42.96606445 76.24023438 C45.79082844 76.24992565 48.61486044 76.28093755 51.43945312 76.3125 C53.23697485 76.31903152 55.03450177 76.32428141 56.83203125 76.328125 C58.0963372 76.3466452 58.0963372 76.3466452 59.38618469 76.36553955 C64.08745075 76.34777001 67.19523771 76.06753063 71 73 C76.14974577 66.13367231 76.85747523 60.40719967 76 52 C72.94347249 40.85669565 66.57825718 33.02825174 57.07421875 26.66796875 C41.98336589 18.48017438 25.32550226 16.19330935 8.4375 15.4375 C7.52419922 15.39431641 6.61089844 15.35113281 5.66992188 15.30664062 C3.44672009 15.20188243 1.22343293 15.09970983 -1 15 C-1 14.34 -1 13.68 -1 13 C24.14499626 7.59108915 50.41887378 8.13666192 72.734375 22.42578125 C84.76471086 30.78479048 94.05571707 43.44065596 97 58 C97.83992741 68.1617447 96.68764977 76.53638183 90.375 84.75 C82.2520582 93.4708233 71.29646331 96.75137499 59.70800781 97.22460938 C55.46822317 97.34944357 51.22762628 97.41269989 46.98632812 97.45996094 C45.16900252 97.48655278 43.35182742 97.52746211 41.53515625 97.58300781 C30.19480451 97.9245351 21.92945424 97.88338566 13 90 C12.08879395 89.26837646 12.08879395 89.26837646 11.15917969 88.52197266 C-1.6887866 77.91909315 -10.95597826 65.95656876 -16 50 C-16.4125 48.7625 -16.825 47.525 -17.25 46.25 C-19.92772011 34.34631695 -18.39209844 21.70178399 -12.3515625 11.109375 C-4.91282895 0 -4.91282895 0 0 0 Z " fill="#79AFEB" transform="translate(103,64)"/>
<path d="M0 0 C-0.42954381 1.3983022 -0.86907034 2.79353907 -1.3125 4.1875 C-1.55613281 4.96480469 -1.79976562 5.74210937 -2.05078125 6.54296875 C-3.87971163 11.27711365 -6.67096194 14.91291499 -9.9375 18.75 C-16.72958324 27.14022047 -20.11560508 36.06308588 -19 47 C-17.21558238 54.72810276 -14.24239154 60.64225254 -7.625 65.25 C-1.74362035 68.9878388 -1.74362035 68.9878388 5.015625 69.8046875 C7.86441607 69.65527538 9.37848936 69.78974988 12 71 C16.09718736 75.3958468 19.01252254 80.82170573 22 86 C22.99935816 87.6670516 23.99905049 89.3339034 25 91 C5.62249954 91.36484834 -12.69092394 90.61101563 -28 77 C-36.44050971 67.52265225 -39.76608235 57.3077398 -39.3828125 44.7109375 C-38.14502828 32.71200883 -32.24783597 21.70604907 -24 13 C-23.34 13 -22.68 13 -22 13 C-22 12.34 -22 11.68 -22 11 C-16.32431209 6.09717172 -7.83025539 0 0 0 Z " fill="#73A7EA" transform="translate(39,70)"/>
<path d="M0 0 C-2.12910166 2.33471584 -4.07517335 3.68146181 -6.9375 5 C-9.90732447 6.41679699 -12.38725285 7.9901945 -15 10 C-15.66 10 -16.32 10 -17 10 C-17 10.66 -17 11.32 -17 12 C-18.12525155 13.02649277 -19.30282645 13.99587696 -20.5 14.9375 C-33.49161609 26.0469385 -41.59305065 43.11799329 -43.1953125 60.06591797 C-43.714182 69.16559176 -42.71323311 78.06671309 -41 87 C-46.81627942 82.15310048 -49.61443677 75.38967056 -51 68 C-51.69814606 57.29890869 -51.08055936 48.06628054 -47 38 C-46.67257813 37.15308594 -46.34515625 36.30617187 -46.0078125 35.43359375 C-41.86810307 26.02104276 -34.29231204 18.10356984 -27 11 C-27.433125 10.51789062 -27.86625 10.03578125 -28.3125 9.5390625 C-28.869375 8.90742187 -29.42625 8.27578125 -30 7.625 C-30.556875 6.99851562 -31.11375 6.37203125 -31.6875 5.7265625 C-33 4 -33 4 -33 2 C-27.87373026 1.19944082 -22.8041505 0.72131302 -17.625 0.4375 C-16.8812915 0.3950415 -16.13758301 0.35258301 -15.37133789 0.30883789 C-10.24014323 0.03021845 -5.13944387 -0.09344443 0 0 Z " fill="#71A4EA" transform="translate(88,39)"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -56,13 +56,22 @@ const AccessSSHForm = ({
const originGroup = data ? (data.group ? data.group : "") : "";
const domainReg = /^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/;
const ipReg =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
configType: accessFormType,
host: z.string().ip({
message: "请输入合法的IP地址",
}),
host: z.string().refine(
(str) => {
return ipReg.test(str) || domainReg.test(str);
},
{
message: "请输入正确的域名或IP",
}
),
group: z.string().optional(),
port: z.string().min(1).max(5),
username: z.string().min(1).max(64),
@@ -316,7 +325,7 @@ const AccessSSHForm = ({
name="host"
render={({ field }) => (
<FormItem className="grow">
<FormLabel>IP</FormLabel>
<FormLabel>HOST</FormLabel>
<FormControl>
<Input placeholder="请输入Host" {...field} />
</FormControl>

View File

@@ -0,0 +1,49 @@
import { Deployment } from "@/domain/deployment";
import { CircleCheck, CircleX } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
type DeployStateProps = {
deployment: Deployment;
};
const DeployState = ({ deployment }: DeployStateProps) => {
// 获取指定阶段的错误信息
const error = (state: "check" | "apply" | "deploy") => {
if (!deployment.log[state]) {
return "";
}
return deployment.log[state][deployment.log[state].length - 1].error;
};
return (
<>
{(deployment.phase === "deploy" && deployment.phaseSuccess) || deployment.wholeSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<>
{error(deployment.phase).length ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild className="cursor-pointer">
<CircleX size={16} className="text-red-700" />
</TooltipTrigger>
<TooltipContent className="max-w-[35em]">
{error(deployment.phase)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<CircleX size={16} className="text-red-700" />
)}
</>
)}
</>
);
};
export default DeployState;

View File

@@ -25,6 +25,7 @@ import { update } from "@/repository/settings";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
import { useState } from "react";
import { EmailsSetting } from "@/domain/settings";
type EmailsEditProps = {
className?: string;
@@ -51,7 +52,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
if (emails.content.emails.includes(data.email)) {
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
form.setError("email", {
message: "邮箱已存在",
});
@@ -59,7 +60,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
}
// 保存到 config
const newEmails = [...emails.content.emails, data.email];
const newEmails = [...(emails.content as EmailsSetting).emails, data.email];
try {
const resp = await update({

View File

@@ -0,0 +1,146 @@
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Switch } from "../ui/switch";
import { Label } from "../ui/label";
import { useNotify } from "@/providers/notify";
import { NotifyChannelDingTalk, NotifyChannels } from "@/domain/settings";
import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
type DingTalkSetting = {
id: string;
name: string;
data: NotifyChannelDingTalk;
};
const DingTalk = () => {
const { config, setChannels } = useNotify();
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
accessToken: "",
secret: "",
enabled: false,
},
});
useEffect(() => {
const getDetailDingTalk = () => {
const df: NotifyChannelDingTalk = {
accessToken: "",
secret: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.dingtalk) {
return df;
}
return chanels.dingtalk as NotifyChannelDingTalk;
};
const data = getDetailDingTalk();
setDingtalk({
id: config.id ?? "",
name: "dingtalk",
data,
});
}, [config]);
const { toast } = useToast();
const handleSaveClick = async () => {
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
dingtalk: {
...dingtalk.data,
},
},
});
setChannels(resp);
toast({
title: "保存成功",
description: "配置保存成功",
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: "保存失败",
description: "配置保存失败:" + msg,
variant: "destructive",
});
}
};
return (
<div>
<Input
placeholder="AccessToken"
value={dingtalk.data.accessToken}
onChange={(e) => {
setDingtalk({
...dingtalk,
data: {
...dingtalk.data,
accessToken: e.target.value,
},
});
}}
/>
<Input
placeholder="加签的签名"
className="mt-2"
value={dingtalk.data.secret}
onChange={(e) => {
setDingtalk({
...dingtalk,
data: {
...dingtalk.data,
secret: e.target.value,
},
});
}}
/>
<div className="flex items-center space-x-1 mt-2">
<Switch
id="airplane-mode"
checked={dingtalk.data.enabled}
onCheckedChange={() => {
setDingtalk({
...dingtalk,
data: {
...dingtalk.data,
enabled: !dingtalk.data.enabled,
},
});
}}
/>
<Label htmlFor="airplane-mode"></Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
</Button>
</div>
</div>
);
};
export default DingTalk;

View File

@@ -0,0 +1,104 @@
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
import { Button } from "../ui/button";
import { useEffect, useState } from "react";
import {
defaultNotifyTemplate,
NotifyTemplates,
NotifyTemplate as NotifyTemplateT,
} from "@/domain/settings";
import { getSetting, update } from "@/repository/settings";
import { useToast } from "../ui/use-toast";
const NotifyTemplate = () => {
const [id, setId] = useState("");
const [templates, setTemplates] = useState<NotifyTemplateT[]>([
defaultNotifyTemplate,
]);
const { toast } = useToast();
useEffect(() => {
const featchData = async () => {
const resp = await getSetting("templates");
if (resp.content) {
setTemplates((resp.content as NotifyTemplates).notifyTemplates);
setId(resp.id ? resp.id : "");
}
};
featchData();
}, []);
const handleTitleChange = (val: string) => {
const template = templates[0];
setTemplates([
{
...template,
title: val,
},
]);
};
const handleContentChange = (val: string) => {
const template = templates[0];
setTemplates([
{
...template,
content: val,
},
]);
};
const handleSaveClick = async () => {
const resp = await update({
id: id,
content: {
notifyTemplates: templates,
},
name: "templates",
});
if (resp.id) {
setId(resp.id);
}
toast({
title: "保存成功",
description: "通知模板保存成功",
});
};
return (
<div>
<Input
value={templates[0].title}
onChange={(e) => {
handleTitleChange(e.target.value);
}}
/>
<div className="text-muted-foreground text-sm mt-1">
, COUNT:即将过期张数
</div>
<Textarea
className="mt-2"
value={templates[0].content}
onChange={(e) => {
handleContentChange(e.target.value);
}}
></Textarea>
<div className="text-muted-foreground text-sm mt-1">
, COUNT:即将过期张数DOMAINS:域名列表
</div>
<div className="flex justify-end mt-2">
<Button onClick={handleSaveClick}></Button>
</div>
</div>
);
};
export default NotifyTemplate;

View File

@@ -0,0 +1,147 @@
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Switch } from "../ui/switch";
import { Label } from "../ui/label";
import { useNotify } from "@/providers/notify";
import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings";
import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
type TelegramSetting = {
id: string;
name: string;
data: NotifyChannelTelegram;
};
const Telegram = () => {
const { config, setChannels } = useNotify();
const [telegram, setTelegram] = useState<TelegramSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
apiToken: "",
chatId: "",
enabled: false,
},
});
useEffect(() => {
const getDetailTelegram = () => {
const df: NotifyChannelTelegram = {
apiToken: "",
chatId: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.telegram) {
return df;
}
return chanels.telegram as NotifyChannelTelegram;
};
const data = getDetailTelegram();
setTelegram({
id: config.id ?? "",
name: "telegram",
data,
});
}, [config]);
const { toast } = useToast();
const handleSaveClick = async () => {
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
telegram: {
...telegram.data,
},
},
});
setChannels(resp);
toast({
title: "保存成功",
description: "配置保存成功",
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: "保存失败",
description: "配置保存失败:" + msg,
variant: "destructive",
});
}
};
return (
<div>
<Input
placeholder="ApiToken"
value={telegram.data.apiToken}
onChange={(e) => {
setTelegram({
...telegram,
data: {
...telegram.data,
apiToken: e.target.value,
},
});
}}
/>
<Input
placeholder="ChatId"
value={telegram.data.chatId}
onChange={(e) => {
setTelegram({
...telegram,
data: {
...telegram.data,
chatId: e.target.value,
},
});
}}
/>
<div className="flex items-center space-x-1 mt-2">
<Switch
id="airplane-mode"
checked={telegram.data.enabled}
onCheckedChange={() => {
setTelegram({
...telegram,
data: {
...telegram.data,
enabled: !telegram.data.enabled,
},
});
}}
/>
<Label htmlFor="airplane-mode"></Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
</Button>
</div>
</div>
);
};
export default Telegram;

View File

@@ -0,0 +1,142 @@
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Switch } from "../ui/switch";
import { Label } from "../ui/label";
import { useNotify } from "@/providers/notify";
import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings";
import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
import { isValidURL } from "@/lib/url";
type WebhookSetting = {
id: string;
name: string;
data: NotifyChannelWebhook;
};
const Webhook = () => {
const { config, setChannels } = useNotify();
const [webhook, setWebhook] = useState<WebhookSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
url: "",
enabled: false,
},
});
useEffect(() => {
const getDetailWebhook = () => {
const df: NotifyChannelWebhook = {
url: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.webhook) {
return df;
}
return chanels.webhook as NotifyChannelWebhook;
};
const data = getDetailWebhook();
setWebhook({
id: config.id ?? "",
name: "webhook",
data,
});
}, [config]);
const { toast } = useToast();
const handleSaveClick = async () => {
try {
webhook.data.url = webhook.data.url.trim();
if (!isValidURL(webhook.data.url)) {
toast({
title: "保存失败",
description: "Url格式不正确",
variant: "destructive",
});
return;
}
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
webhook: {
...webhook.data,
},
},
});
setChannels(resp);
toast({
title: "保存成功",
description: "配置保存成功",
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: "保存失败",
description: "配置保存失败:" + msg,
variant: "destructive",
});
}
};
return (
<div>
<Input
placeholder="Url"
value={webhook.data.url}
onChange={(e) => {
setWebhook({
...webhook,
data: {
...webhook.data,
url: e.target.value,
},
});
}}
/>
<div className="flex items-center space-x-1 mt-2">
<Switch
id="airplane-mode"
checked={webhook.data.enabled}
onCheckedChange={() => {
setWebhook({
...webhook,
data: {
...webhook.data,
enabled: !webhook.data.enabled,
},
});
}}
/>
<Label htmlFor="airplane-mode"></Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
</Button>
</div>
</div>
);
};
export default Webhook;

View File

@@ -0,0 +1,56 @@
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View File

@@ -0,0 +1,128 @@
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

View File

@@ -10,6 +10,7 @@ export type Deployment = {
};
phase: Pahse;
phaseSuccess: boolean;
wholeSuccess: boolean;
deployedAt: string;
created: string;
updated: string;

View File

@@ -13,8 +13,10 @@ export type Domain = {
phaseSuccess?: boolean;
lastDeployedAt?: string;
variables?: string;
nameservers?: string;
group?: string;
enabled?: boolean;
deployed?: boolean;
created?: string;
updated?: string;
deleted?: string;
@@ -40,6 +42,7 @@ export const getLastDeployment = (domain: Domain): Deployment | undefined => {
export const targetTypeMap: Map<string, [string, string]> = new Map([
["aliyun-cdn", ["阿里云-CDN", "/imgs/providers/aliyun.svg"]],
["aliyun-oss", ["阿里云-OSS", "/imgs/providers/aliyun.svg"]],
["aliyun-dcdn", ["阿里云-DCDN", "/imgs/providers/aliyun.svg"]],
["tencent-cdn", ["腾讯云-CDN", "/imgs/providers/tencent.svg"]],
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
["qiniu-cdn", ["七牛云-CDN", "/imgs/providers/qiniu.svg"]],

View File

@@ -1,9 +1,51 @@
export type Setting = {
id?: string;
name?: string;
content: EmailsSetting;
content?: EmailsSetting | NotifyTemplates | NotifyChannels;
};
type EmailsSetting = {
export type EmailsSetting = {
emails: string[];
};
export type NotifyTemplates = {
notifyTemplates: NotifyTemplate[];
};
export type NotifyTemplate = {
title: string;
content: string;
};
export type NotifyChannels = {
dingtalk?: NotifyChannel;
telegram?: NotifyChannel;
webhook?: NotifyChannel;
};
export type NotifyChannel =
| NotifyChannelDingTalk
| NotifyChannelTelegram
| NotifyChannelWebhook;
export type NotifyChannelDingTalk = {
accessToken: string;
secret: string;
enabled: boolean;
};
export type NotifyChannelTelegram = {
apiToken: string;
chatId: string;
enabled: boolean;
};
export type NotifyChannelWebhook = {
url: string;
enabled: boolean;
};
export const defaultNotifyTemplate: NotifyTemplate = {
title: "您有{COUNT}张证书即将过期",
content: "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!",
};

View File

@@ -1 +1 @@
export const version = "Certimate v0.1.7";
export const version = "Certimate v0.1.11";

8
ui/src/lib/url.ts Normal file
View File

@@ -0,0 +1,8 @@
export function isValidURL(url: string): boolean {
try {
new URL(url);
return true;
} catch (error) {
return false;
}
}

View File

@@ -7,7 +7,7 @@ import { ThemeProvider } from "./components/ThemeProvider.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
<RouterProvider router={router} />
</ThemeProvider>
</React.StrictMode>

View File

@@ -46,12 +46,12 @@ export default function Dashboard() {
};
const handleSettingClick = () => {
navigate("/setting/password");
navigate("/setting/account");
};
return (
<>
<ConfigProvider>
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
<div className="grid min-h-screen w-full md:grid-cols-[180px_1fr] lg:grid-cols-[200px_1fr] 2xl:md:grid-cols-[280px_1fr] ">
<div className="hidden border-r dark:border-stone-500 bg-muted/40 md:block">
<div className="flex h-full max-h-screen flex-col gap-2">
<div className="flex h-14 items-center border-b dark:border-stone-500 px-4 lg:h-[60px] lg:px-6">
@@ -192,7 +192,7 @@ export default function Dashboard() {
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleSettingClick}>
</DropdownMenuItem>
<DropdownMenuItem onClick={handleLogoutClick}>

View File

@@ -1,20 +1,68 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Toaster } from "@/components/ui/toaster";
import { Outlet } from "react-router-dom";
import { KeyRound, Megaphone, UserRound } from "lucide-react";
import { useEffect, useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
const SettingLayout = () => {
const location = useLocation();
const [tabValue, setTabValue] = useState("account");
const navigate = useNavigate();
useEffect(() => {
const pathname = location.pathname;
const tabValue = pathname.split("/")[2];
setTabValue(tabValue);
}, [location]);
return (
<div>
<Toaster />
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
</div>
<div className="w-full sm:w-[35em] mt-10 flex flex-col p-3 mx-auto">
{/* <div className="text-muted-foreground">
<span className="transition-all text-sm bg-gray-400 px-3 py-1 rounded-sm text-white cursor-pointer">
密码
</span>
</div> */}
<Outlet />
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
<Tabs defaultValue="account" className="w-full" value={tabValue}>
<TabsList className="mx-auto">
<TabsTrigger
value="account"
onClick={() => {
navigate("/setting/account");
}}
className="px-5"
>
<UserRound size={14} />
<div className="ml-1"></div>
</TabsTrigger>
<TabsTrigger
value="password"
onClick={() => {
navigate("/setting/password");
}}
className="px-5"
>
<KeyRound size={14} />
<div className="ml-1"></div>
</TabsTrigger>
<TabsTrigger
value="notify"
onClick={() => {
navigate("/setting/notify");
}}
className="px-5"
>
<Megaphone size={14} />
<div className="ml-1"></div>
</TabsTrigger>
</TabsList>
<TabsContent value={tabValue}>
<div className="mt-5 w-full md:w-[45em]">
<Outlet />
</div>
</TabsContent>
</Tabs>
</div>
</div>
);

View File

@@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
@@ -17,8 +18,6 @@ import { statistics } from "@/repository/domains";
import {
Ban,
CalendarX2,
CircleCheck,
CircleX,
LoaderPinwheel,
Smile,
SquareSigma,
@@ -204,11 +203,7 @@ const Dashboard = () => {
{deployment.expand.domain?.domain}
</div>
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
{deployment.phase === "deploy" && deployment.phaseSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
<DeployState deployment={deployment} />
</div>
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
<DeployProgress

View File

@@ -37,6 +37,7 @@ import { accessTypeMap } from "@/domain/access";
import EmailsEdit from "@/components/certimate/EmailsEdit";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import { EmailsSetting } from "@/domain/settings";
const Edit = () => {
const {
@@ -80,6 +81,7 @@ const Edit = () => {
}),
variables: z.string().optional(),
group: z.string().optional(),
nameservers: z.string().optional(),
});
const form = useForm<z.infer<typeof formSchema>>({
@@ -93,6 +95,7 @@ const Edit = () => {
targetType: "",
variables: "",
group: "",
nameservers: "",
},
});
@@ -107,6 +110,7 @@ const Edit = () => {
targetType: domain.targetType,
variables: domain.variables,
group: domain.group,
nameservers: domain.nameservers,
});
}
}, [domain, form]);
@@ -153,6 +157,7 @@ const Edit = () => {
targetAccess: targetAccess,
targetType: data.targetType,
variables: data.variables,
nameservers: data.nameservers,
};
try {
@@ -266,11 +271,13 @@ const Edit = () => {
<SelectContent>
<SelectGroup>
<SelectLabel></SelectLabel>
{emails.content.emails.map((item) => (
<SelectItem key={item} value={item}>
<div>{item}</div>
</SelectItem>
))}
{(emails.content as EmailsSetting).emails.map(
(item) => (
<SelectItem key={item} value={item}>
<div>{item}</div>
</SelectItem>
)
)}
</SelectGroup>
</SelectContent>
</Select>
@@ -515,6 +522,26 @@ const Edit = () => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="nameservers"
render={({ field }) => (
<FormItem hidden={tab != "advance"}>
<FormLabel></FormLabel>
<FormControl>
<Textarea
placeholder={`自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;`}
{...field}
className="placeholder:whitespace-pre-wrap"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit"></Button>
</div>

View File

@@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import XPagination from "@/components/certimate/XPagination";
import Show from "@/components/Show";
import {
@@ -31,7 +32,7 @@ import {
} from "@/repository/domains";
import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
import { CircleCheck, CircleX, Earth } from "lucide-react";
import { Earth } from "lucide-react";
import { useEffect, useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
@@ -149,6 +150,10 @@ const Home = () => {
}
};
const handleForceClick = async (domain: Domain) => {
await handleRightNowClick({ ...domain, deployed: false });
};
const handleDownloadClick = async (domain: Domain) => {
const zipName = `${domain.id}-${domain.domain}.zip`;
const files: CustomFile[] = [
@@ -192,12 +197,12 @@ const Home = () => {
) : (
<>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-40"></div>
<div className="w-48"></div>
<div className="w-36"></div>
<div className="w-40"></div>
<div className="w-32"></div>
<div className="w-64"></div>
<div className="w-40 sm:ml-2"></div>
<div className="w-32"></div>
<div className="w-24"></div>
<div className="grow"></div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
@@ -209,10 +214,10 @@ const Home = () => {
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
key={domain.id}
>
<div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center">
<div className="sm:w-36 w-full pt-1 sm:pt-0 flex items-center">
{domain.domain}
</div>
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
<div className="sm:w-40 w-full pt-1 sm:pt-0 flex items-center">
<div>
{domain.expiredAt ? (
<>
@@ -227,12 +232,7 @@ const Home = () => {
<div className="sm:w-32 w-full pt-1 sm:pt-0 flex items-center">
{domain.lastDeployedAt && domain.expand?.lastDeployment ? (
<>
{domain.expand.lastDeployment?.phase === "deploy" &&
domain.expand.lastDeployment?.phaseSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
<DeployState deployment={domain.expand.lastDeployment} />
</>
) : (
"---"
@@ -253,7 +253,7 @@ const Home = () => {
? convertZulu2Beijing(domain.lastDeployedAt)
: "---"}
</div>
<div className="sm:w-32 flex items-center">
<div className="sm:w-24 flex items-center">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
@@ -291,6 +291,23 @@ const Home = () => {
</Button>
</Show>
<Show
when={
(domain.enabled ? true : false) && domain.deployed
? true
: false
}
>
<Separator orientation="vertical" className="h-4 mx-2" />
<Button
variant={"link"}
className="p-0"
onClick={() => handleForceClick(domain)}
>
</Button>
</Show>
<Show when={domain.expiredAt ? true : false}>
<Separator orientation="vertical" className="h-4 mx-2" />
<Button

View File

@@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress";
import DeployState from "@/components/certimate/DeployState";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -13,7 +14,7 @@ import {
import { Deployment, DeploymentListReq, Log } from "@/domain/deployment";
import { convertZulu2Beijing } from "@/lib/time";
import { list } from "@/repository/deployment";
import { CircleCheck, CircleX, Smile } from "lucide-react";
import { Smile } from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
@@ -88,11 +89,7 @@ const History = () => {
{deployment.expand.domain?.domain}
</div>
<div className="sm:w-24 w-full pt-1 sm:pt-0 flex items-center">
{deployment.phase === "deploy" && deployment.phaseSuccess ? (
<CircleCheck size={16} className="text-green-700" />
) : (
<CircleX size={16} className="text-red-700" />
)}
<DeployState deployment={deployment} />
</div>
<div className="sm:w-56 w-full pt-1 sm:pt-0 flex items-center">
<DeployProgress

View File

@@ -0,0 +1,109 @@
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { useToast } from "@/components/ui/use-toast";
import { getErrMessage } from "@/lib/error";
import { getPb } from "@/repository/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { z } from "zod";
const formSchema = z.object({
email: z.string().email("请输入正确的邮箱"),
});
const Account = () => {
const { toast } = useToast();
const navigate = useNavigate();
const [changed, setChanged] = useState(false);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: getPb().authStore.model?.email,
},
});
const onSubmit = async (values: z.infer<typeof formSchema>) => {
try {
await getPb().admins.update(getPb().authStore.model?.id, {
email: values.email,
});
getPb().authStore.clear();
toast({
title: "修改账户邮箱功",
description: "请重新登录",
});
setTimeout(() => {
navigate("/login");
}, 500);
} catch (e) {
const message = getErrMessage(e);
toast({
title: "修改账户邮箱失败",
description: message,
variant: "destructive",
});
}
};
return (
<>
<div className="w-full md:max-w-[35em]">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 dark:text-stone-200"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="请输入邮箱"
{...field}
type="email"
onChange={(e) => {
setChanged(true);
form.setValue("email", e.target.value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
{changed ? (
<Button type="submit"></Button>
) : (
<Button type="submit" disabled variant={"secondary"}>
</Button>
)}
</div>
</form>
</Form>
</div>
</>
);
};
export default Account;

View File

@@ -0,0 +1,56 @@
import DingTalk from "@/components/notify/DingTalk";
import NotifyTemplate from "@/components/notify/NotifyTemplate";
import Telegram from "@/components/notify/Telegram";
import Webhook from "@/components/notify/Webhook";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { NotifyProvider } from "@/providers/notify";
const Notify = () => {
return (
<>
<NotifyProvider>
<div className="border rounded-sm p-5 shadow-lg">
<Accordion type={"multiple"} className="dark:text-stone-200">
<AccordionItem value="item-1" className="dark:border-stone-200">
<AccordionTrigger></AccordionTrigger>
<AccordionContent>
<NotifyTemplate />
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
<div className="border rounded-md p-5 mt-7 shadow-lg">
<Accordion type={"single"} className="dark:text-stone-200">
<AccordionItem value="item-2" className="dark:border-stone-200">
<AccordionTrigger></AccordionTrigger>
<AccordionContent>
<DingTalk />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-4" className="dark:border-stone-200">
<AccordionTrigger>Telegram</AccordionTrigger>
<AccordionContent>
<Telegram />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-5" className="dark:border-stone-200">
<AccordionTrigger>Webhook</AccordionTrigger>
<AccordionContent>
<Webhook />
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</NotifyProvider>
</>
);
};
export default Notify;

View File

@@ -84,64 +84,70 @@ const Password = () => {
return (
<>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 dark:text-stone-200"
>
<FormField
control={form.control}
name="oldPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="当前密码" {...field} type="password" />
</FormControl>
<div className="w-full md:max-w-[35em]">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8 dark:text-stone-200"
>
<FormField
control={form.control}
name="oldPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="当前密码" {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder="newPassword" {...field} type="password" />
</FormControl>
<FormField
control={form.control}
name="newPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="newPassword"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="confirmPassword"
{...field}
type="password"
/>
</FormControl>
<FormField
control={form.control}
name="confirmPassword"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
placeholder="confirmPassword"
{...field}
type="password"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit"></Button>
</div>
</form>
</Form>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit"></Button>
</div>
</form>
</Form>
</div>
</>
);
};

View File

@@ -1,6 +1,6 @@
import { Access } from "@/domain/access";
import { ConfigData } from ".";
import { Setting } from "@/domain/settings";
import { EmailsSetting, Setting } from "@/domain/settings";
import { AccessGroup } from "@/domain/access_groups";
type Action =
@@ -57,7 +57,10 @@ export const configReducer = (
emails: {
...state.emails,
content: {
emails: [...state.emails.content.emails, action.payload],
emails: [
...(state.emails.content as EmailsSetting).emails,
action.payload,
],
},
},
};

View File

@@ -0,0 +1,68 @@
import { NotifyChannel, Setting } from "@/domain/settings";
import { getSetting } from "@/repository/settings";
import {
ReactNode,
useContext,
createContext,
useEffect,
useReducer,
useCallback,
} from "react";
import { notifyReducer } from "./reducer";
export type NotifyContext = {
config: Setting;
setChannel: (data: { channel: string; data: NotifyChannel }) => void;
setChannels: (data: Setting) => void;
};
const Context = createContext({} as NotifyContext);
export const useNotify = () => useContext(Context);
interface ContainerProps {
children: ReactNode;
}
export const NotifyProvider = ({ children }: ContainerProps) => {
const [notify, dispatchNotify] = useReducer(notifyReducer, {});
useEffect(() => {
const featchData = async () => {
const chanels = await getSetting("notifyChannels");
dispatchNotify({
type: "SET_CHANNELS",
payload: chanels,
});
};
featchData();
}, []);
const setChannel = useCallback(
(data: { channel: string; data: NotifyChannel }) => {
dispatchNotify({
type: "SET_CHANNEL",
payload: data,
});
},
[]
);
const setChannels = useCallback((setting: Setting) => {
dispatchNotify({
type: "SET_CHANNELS",
payload: setting,
});
}, []);
return (
<Context.Provider
value={{
config: notify,
setChannel,
setChannels,
}}
>
{children}
</Context.Provider>
);
};

View File

@@ -0,0 +1,35 @@
import { NotifyChannel, Setting } from "@/domain/settings";
type Action =
| {
type: "SET_CHANNEL";
payload: {
channel: string;
data: NotifyChannel;
};
}
| {
type: "SET_CHANNELS";
payload: Setting;
};
export const notifyReducer = (state: Setting, action: Action) => {
switch (action.type) {
case "SET_CHANNEL": {
const channel = action.payload.channel;
return {
...state,
content: {
...state.content,
[channel]: action.payload.data,
},
};
}
case "SET_CHANNELS": {
return { ...action.payload };
}
default:
return state;
}
};

View File

@@ -14,6 +14,20 @@ export const getEmails = async () => {
}
};
export const getSetting = async (name: string) => {
try {
const resp = await getPb()
.collection("settings")
.getFirstListItem<Setting>(`name='${name}'`);
return resp;
} catch (e) {
const rs: Setting = {
name: name,
};
return rs;
}
};
export const update = async (setting: Setting) => {
const pb = getPb();
let resp: Setting;

View File

@@ -10,6 +10,8 @@ import LoginLayout from "./pages/LoginLayout";
import Password from "./pages/setting/Password";
import SettingLayout from "./pages/SettingLayout";
import Dashboard from "./pages/dashboard/Dashboard";
import Account from "./pages/setting/Account";
import Notify from "./pages/setting/Notify";
export const router = createHashRouter([
{
@@ -44,6 +46,14 @@ export const router = createHashRouter([
path: "/setting/password",
element: <Password />,
},
{
path: "/setting/account",
element: <Account />,
},
{
path: "/setting/notify",
element: <Notify />,
},
],
},
],

View File

@@ -9,70 +9,96 @@ module.exports = {
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
'3xl': '1920px',
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
container: {
center: 'true',
padding: '2rem',
screens: {
'2xl': '1400px',
'3xl': '1920px'
}
},
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
},
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [require("tailwindcss-animate")],
}