Compare commits

...

114 Commits

Author SHA1 Message Date
yoan
f148240bcf v0.1.19 2024-10-12 08:04:34 +08:00
yoan
f914931bc9 go mod tidy 2024-10-11 22:28:26 +08:00
yoan
8c1033634d update Dockerfile name 2024-10-11 22:23:41 +08:00
usual2970
781b79f529 Merge pull request #184 from fudiwei/feat/huaweicloud
feat: add huaweicloud provider
2024-10-11 22:08:18 +08:00
RHQYZ
ad91703492 Merge branch 'main' into feat/huaweicloud 2024-10-11 09:43:01 +08:00
Fu Diwei
a007c81e9a feat: add huaweicloud provider 2024-10-11 09:30:14 +08:00
yoan
39bffe3389 v0.1.18 2024-10-11 07:53:32 +08:00
usual2970
3f2767b28b Merge pull request #183 from LeoChen98/feat-tencent-cdn-extensive-support
add feat: support for tencent cdn extensive domain
2024-10-11 07:02:24 +08:00
Leo Chen
312c6e685a change var name style 2024-10-10 22:45:19 +08:00
Leo Chen
d2b6ab75b7 add feat: support for tencent cdn extensive domain 2024-10-10 19:01:32 +08:00
usual2970
dc16294b3d Update README_EN.md 2024-10-09 12:33:42 +08:00
usual2970
77dfcef168 Update README.md 2024-10-09 12:31:53 +08:00
yoan
30ef5841d6 dark mode style fix 2024-10-09 09:26:39 +08:00
yoan
217ba85ff8 v0.1.16 2024-10-09 09:04:23 +08:00
yoan
71e2555391 multiple domain support 2024-10-08 22:02:00 +08:00
yoan
f036eb1cf2 Add the functionality to authorize copying 2024-10-04 08:19:46 +08:00
usual2970
1347066549 Merge pull request #131 from liburdi/hotfix/access_copy_word
fix: update en.json
2024-10-03 07:55:09 +08:00
liburdi
7fc149f67d fix: update en.json 2024-10-02 12:55:24 +08:00
yoan
dfba5ee638 Merge branch 'liburdi-feature/copy_access' 2024-10-01 07:04:56 +08:00
yoan
9ba79f996f fix conflict 2024-10-01 07:04:40 +08:00
yoan
cd85000908 Merge branch 'JonathanSimon123-main' 2024-10-01 06:59:18 +08:00
liburdi
995349ab3e feat: add issues 124 2024-09-30 18:22:16 +08:00
simon
4fa8031318 feat:Add star sequence diagram 2024-09-30 14:08:11 +08:00
simon
3f45bb1629 Merge branch 'main' of https://github.com/JonathanSimon123/certimate 2024-09-30 11:46:08 +08:00
蒋驰磊
0e139e6284 feat:Add star sequence diagram 2024-09-30 11:44:37 +08:00
JonathanSimon123
82dbfc6de3 Update README.md
feature:Add start sequence diagram
2024-09-30 11:04:41 +08:00
JonathanSimon123
9b2937d601 Update README_EN.md
feature:Add start sequence diagram
2024-09-30 11:02:06 +08:00
yoan
3375839a40 Merge branch 'l123wx-local_deployment_translate' 2024-09-30 07:35:26 +08:00
yoan
7f5ff6fab5 build ui 2024-09-30 07:34:44 +08:00
usual2970
5160b4c3d9 Merge pull request #120 from yanlc39/dev/fix_docker_deploy_way
簡化 Docker 部署的方式
2024-09-30 07:33:27 +08:00
Elvis Liao
85234b21c7 chore: translate local deployment form 2024-09-29 20:43:42 +08:00
yanlc39
223af9e09d feat: let docker deploy way simpley 2024-09-29 17:42:05 +08:00
usual2970
49fdf8213a Merge pull request #117 from sgpublic-forks/feat/pre_command
fix: wrong pre command reference
2024-09-29 08:30:04 +08:00
Madray Haven
7a48101015 fix: wrong pre command reference 2024-09-28 17:28:55 +08:00
yoan
6b85b4a0c9 Merge branch 'l123wx-i18n' 2024-09-28 07:47:11 +08:00
yoan
3c56a53e91 translate new element to English 2024-09-28 07:46:55 +08:00
yoan
9797a0835d Merge branch 'sgpublic-forks-feat/pre_command' 2024-09-28 07:05:02 +08:00
yoan
b6dc57f3e4 ui build 2024-09-28 07:04:17 +08:00
yoan
78ac21c767 Merge branch 'feat/pre_command' of github.com:sgpublic-forks/certimate into sgpublic-forks-feat/pre_command 2024-09-28 07:01:46 +08:00
elvis liao
1e2d8fa027 chore: abstract resources configure 2024-09-27 16:31:24 +08:00
Madray Haven
e7e2e4786d chore: create a func for running ssh command 2024-09-27 16:14:35 +08:00
Madray Haven
9acdd15c1e feat: pre command for ssh deploy 2024-09-27 15:12:04 +08:00
elvis liao
fcc0dd93fd refactor(DeployProgress): 重构逻辑,修复样式问题 2024-09-27 13:42:58 +08:00
elvis liao
5eba437732 feat: english translation 2024-09-27 13:41:40 +08:00
elvis liao
0d0fcfccf3 feat: i18n transformation completed 2024-09-27 13:01:37 +08:00
yoan
993ef7bf57 Add contributing guide 2024-09-27 07:26:11 +08:00
yoan
46080b311a v0.1.12 2024-09-27 06:59:16 +08:00
yoan
1fbe6b55c1 v0.1.12 2024-09-27 06:43:31 +08:00
yoan
07795568bf v0.1.12 2024-09-27 06:42:52 +08:00
Elvis Liao
e820e5599b wip: i18n
- Change locales json to one-dimensional format
2024-09-27 00:42:40 +08:00
usual2970
cb8636faec Merge pull request #109 from liburdi/feature/optimize_placeholder
Feature/optimize placeholder
2024-09-26 20:33:13 +08:00
yoan
aa1046c39a fixconflict 2024-09-26 20:31:04 +08:00
usual2970
7e94ba0875 Merge branch 'main' into feature/optimize_placeholder 2024-09-26 20:28:23 +08:00
yoan
077b365458 fix conflict 2024-09-26 20:25:31 +08:00
Elvis Liao
b3f1e1e444 chore: import i18n 2024-09-26 19:47:50 +08:00
liburdi
ead8e1fec5 feat: npm run build 2024-09-26 19:13:48 +08:00
liburdi
5be1139c1a docs: update readme 2024-09-26 18:55:19 +08:00
liburdi
45e218dd5b feat: optimize placeholder 2024-09-26 18:50:50 +08:00
elvis liao
0abb030889 wip: i18n chinese 2024-09-26 17:57:30 +08:00
elvis liao
85df8eb09d feat: Add i18n 2024-09-26 17:53:44 +08:00
usual2970
c6291b42fc Merge pull request #106 from usual2970/feat/local_deploy
Add local deployer
2024-09-26 17:05:18 +08:00
yoan
2634789769 Add local deployer 2024-09-26 17:04:49 +08:00
usual2970
253075e7c0 Merge pull request #105 from usual2970/feat/zerossl
Add zerossl provider
2024-09-26 16:09:20 +08:00
yoan
363fbdee00 Add zerossl provider 2024-09-26 16:07:51 +08:00
yoan
a9fdceca6f Update readme 2024-09-25 19:50:45 +08:00
usual2970
f9cb605eb4 Update README.md 2024-09-25 19:41:51 +08:00
yoan
d5867d0971 Add an English Version of the README 2024-09-25 19:40:01 +08:00
yoan
f0faee34a4 Add an English readme 2024-09-25 19:37:56 +08:00
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
yoan
2efe0de0cf UI optimization 2024-09-17 08:59:39 +08:00
yoan
34e40e5e54 UI Optimization 2024-09-16 08:44:36 +08:00
usual2970
e7e269dfb0 Merge pull request #37 from usual2970/feat/access_ui
Merge authorization group into authorization management
2024-09-15 21:59:03 +08:00
yoan
fa85580e35 Merge authorization group into authorization management 2024-09-15 21:58:08 +08:00
usual2970
500fce6180 Update README.md 2024-09-14 22:02:40 +08:00
usual2970
f501df2804 Merge pull request #30 from usual2970/feat/deploy_group
添加部署变量、部署授权组的支持
2024-09-14 15:39:26 +08:00
yoan
6c1b1fb72b Support deploying one certificate to multiple SSH hosts, and support deploying multiple certificates to one SSH host. 2024-09-14 15:36:15 +08:00
yoan
505cfc5c1e Fix godaddy error 2024-09-13 09:56:01 +08:00
yoan
ac14924a73 update ui 2024-09-13 07:55:25 +08:00
yoan
7550aec904 When adding a domain, you can also add a custom email address. 2024-09-13 07:36:26 +08:00
yoan
139a6980ac Add godaddy provider 2024-09-12 21:59:50 +08:00
yoan
d5a6411e26 Merge branch 'main' of github.com:usual2970/certimate 2024-09-12 06:11:34 +08:00
yoan
1fcd9897be update README 2024-09-12 06:11:15 +08:00
usual2970
99d4bf2624 Update README.md 2024-09-11 08:03:18 +08:00
yoan
844347acf9 fix file select 2024-09-11 07:35:19 +08:00
usual2970
eeae9b4405 Merge pull request #16 from gexin1/main
fix: 首页展示问题和授权管理弹窗无法滚动
2024-09-11 06:37:38 +08:00
river
feef94f873 fix: 1. 修复首页控制面板在md和2xl的时候会超出屏幕
2.修复授权管理-添加授权选择ssh 弹窗过长超出屏幕无法滚动
2024-09-10 22:56:54 +08:00
124 changed files with 11628 additions and 1760 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

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

72
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,72 @@
# 向 Certimate 贡献代码
感谢你抽出时间来改进 Certimate以下是向 Certimate 主仓库提交 PRPull Request时的操作指南。
- [向 Certimate 贡献代码](#向-certimate-贡献代码)
- [前提条件](#前提条件)
- [修改 Go 代码](#修改-go-代码)
- [修改管理页面 (Admin UI)](#修改管理页面-admin-ui)
## 前提条件
- Go 1.22+ (用于修改 Go 代码)
- Node 20+ (用于修改 UI)
如果还没有这样做,你可以 fork Certimate 的主仓库,并克隆到本地以便进行修改:
```bash
git clone https://github.com/your_username/certimate.git
```
> **重要提示:**
> 建议为每个 bug 修复或新功能创建一个从 `main` 分支派生的新分支。如果你计划提交多个 PR请保持不同的改动在独立分支中以便更容易进行代码审查并最终合并。
> 保持一个 pr 只实现一个功能。
## 修改 Go 代码
假设你已经对 Certimate 的 Go 代码做了一些修改,现在你想要运行它:
1. 进入根目录
2. 运行以下命令启动服务:
```bash
go run main.go serve
```
这将启动一个 Web 服务器,默认运行在 `http://localhost:8090`,并使用来自 `ui/dist` 的预构建管理页面。
**在向主仓库提交 PR 之前,建议你:**
- 为你的改动添加单元测试或集成测试Certimate 使用 Go 的标准 `testing` 包)。你可以通过以下命令运行测试(在项目根目录下):
```bash
go test ./...
```
## 修改管理页面 (Admin UI)
Certimate 的管理页面是一个基于 React 和 Vite 构建的单页应用SPA
要启动 Admin UI
1. 进入 `ui` 项目目录
2. 运行 `npm install` 安装依赖
3. 启动 Vite 开发服务器:
```bash
npm run dev
```
你可以通过浏览器访问 `http://localhost:5173` 来查看运行中的管理页面。
由于 Admin UI 只是一个客户端应用,运行时需要 Certimate 的后端服务作为支撑。你可以手动运行Certimate或者使用预构建的可执行文件。
所有对 Admin UI 的修改将会自动反映在浏览器中,无需手动刷新页面。
完成修改后,运行以下命令来构建 Admin UI以便它能被嵌入到 Go 包中:
```bash
npm run build
```
完成所有步骤后,你可以将改动提交 PR 到 Certimate 主仓库。

73
CONTRIBUTING_EN.md Normal file
View File

@@ -0,0 +1,73 @@
# Contributing to Certimate
Thank you for taking the time to improve Certimate! Below is a guide for submitting a PR (Pull Request) to the main Certimate repository.
- [Contributing to Certimate](#contributing-to-certimate)
- [Prerequisites](#prerequisites)
- [Making Changes in the Go Code](#making-changes-in-the-go-code)
- [Making Changes in the Admin UI](#making-changes-in-the-admin-ui)
## Prerequisites
- Go 1.22+ (for Go code changes)
- Node 20+ (for Admin UI changes)
If you haven't done so already, you can fork the Certimate repository and clone your fork to work locally:
```bash
git clone https://github.com/your_username/certimate.git
```
> **Important:**
> It is recommended to create a new branch from `main` for each bug fix or feature. If you plan to submit multiple PRs, ensure the changes are in separate branches for easier review and eventual merge.
> Keep each PR focused on a single feature or fix.
## Making Changes in the Go Code
Once you have made changes to the Go code in Certimate, follow these steps to run the project:
1. Navigate to the root directory.
2. Start the service by running:
```bash
go run main.go serve
```
This will start a web server at `http://localhost:8090` using the prebuilt Admin UI located in `ui/dist`.
**Before submitting a PR to the main repository, consider:**
- Adding unit or integration tests for your changes. Certimate uses Gos standard `testing` package. You can run tests using the following command (while in the root project directory):
```bash
go test ./...
```
## Making Changes in the Admin UI
Certimates Admin UI is a single-page application (SPA) built using React and Vite.
To start the Admin UI:
1. Navigate to the `ui` project directory.
2. Install the necessary dependencies by running:
```bash
npm install
```
3. Start the Vite development server:
```bash
npm run dev
```
You can now access the running Admin UI at `http://localhost:5173` in your browser.
Since the Admin UI is a client-side application, you will also need to have the Certimate backend running. You can either manually run Certimate or use a prebuilt executable.
Any changes you make in the Admin UI will be automatically reflected in the browser without requiring a page reload.
After completing your changes, build the Admin UI so it can be embedded into the Go package:
```bash
npm run build
```
Once all steps are completed, you are ready to submit a PR to the main Certimate repository.

View File

@@ -1,38 +1,25 @@
[中文](README.md) | [English](README_EN.md)
# 🔒Certimate
做个人产品或在小企业负责运维的同学,需要管理多个域名,要给域名申请证书。但手动申请证书有以下缺点:
1. 😱麻烦:申请、部署证书虽不复杂,但也挺麻烦的,尤其是维护多个域名的时候。
1. 😱麻烦:申请、部署证书虽不困难,但也挺麻烦的,尤其是维护多个域名的时候。
2. 😭易忘当前免费证书有效期仅90天这就要求定期操作增加工作量的同时也很容易忘掉导致网站无法访问。
Certimate 就是为了解决上述问题而产生的,它具有以下特点:
1. 操作简单:自动申请、部署 SSL 证书,并在证书即将过期时自动续期,无需人工干预。
1. 操作简单:自动申请、部署、续期 SSL 证书,全程无需人工干预。
2. 支持私有部署部署方法简单只需下载二进制文件执行即可。二进制文件、docker 镜像全部用 github actions 生成,过程透明,可自行审计。
3. 数据安全:由于是私有部署,所有数据均存储在本地,不会保存在服务商的服务器,确保数据的安全性。
相关文章:
* [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
* [域名变量及部署授权组介绍](https://docs.certimate.me/blog/multi-deployer)
Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决方案。使用文档请访问[https://docs.certimate.me](https://docs.certimate.me)
- [🔒Certimate](#certimate)
- [一、安装](#一安装)
- [1. 二进制文件](#1-二进制文件)
- [2. Docker 安装](#2-docker-安装)
- [3. 源代码安装](#3-源代码安装)
- [二、使用](#二使用)
- [三、支持的服务商列表](#三支持的服务商列表)
- [四、系统截图](#四系统截图)
- [五、概念](#五概念)
- [1. 域名](#1-域名)
- [2. dns 服务商授权信息](#2-dns-服务商授权信息)
- [3. 部署服务商授权信息](#3-部署服务商授权信息)
- [六、常见问题](#六常见问题)
- [七、许可证](#七许可证)
## 一、安装
安装 Certimate 非常简单,你可以选择以下方式之一进行安装:
@@ -45,15 +32,19 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
./certimate serve
```
或运行以下命令自动给 Certimate 自身添加证书
```bash
./certimate serve 你的域名
```
> [!NOTE]
> MacOS 在执行二进制文件时会提示无法打开“certimate”因为Apple无法检查其是否包含恶意软件。可在系统设置> 隐私与安全性> 安全性 中点击 "仍然允许",然后再次尝试执行二进制文件。
### 2. Docker 安装
```bash
git clone git@github.com:usual2970/certimate.git && cd certimate/docker && docker compose up -d
mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubusercontent.com/usual2970/certimate/refs/heads/main/docker/docker-compose.yml && docker compose up -d
```
@@ -62,10 +53,10 @@ git clone git@github.com:usual2970/certimate.git && cd certimate/docker && docke
```bash
git clone EMAIL:usual2970/certimate.git
cd certimate
go mod vendor
go run main.go serve
```
## 二、使用
执行完上述安装操作后,在浏览器中访问 `http://127.0.0.1:8090` 即可访问 Certimate 管理页面。
@@ -79,17 +70,14 @@ go run main.go serve
## 三、支持的服务商列表
| 服务商 | 是否域名服务商 | 是否部署服务 | 备注 |
|------|------|-----|------|
| 阿里云| 是 | 是 | 支持阿里云注册的域名,支持部署到阿里云 CDN,OSS |
| 腾讯云| 是 | 是 | 支持腾讯云注册的域名,支持部署到腾讯云 CDN |
| 七牛云| 否 | 是 | 七牛云没有注册域名服务,支持部署到七牛云 CDN |
|CloudFlare| 是 | 否 | 支持 CloudFlare 注册的域名CloudFlare 服务自带SSL证书 |
|SSH| 否 | 是 | 支持部署到 SSH 服务器 |
|WEBHOOK| 否 | 是 | 支持回调到 WEBHOOK |
| 服务商 | 是否域名服务商 | 是否部署服务 | 备注 |
| ---------- | -------------- | ------------ | ------------------------------------------------------ |
| 阿里云 | 是 | 是 | 支持阿里云注册的域名,支持部署到阿里云 CDN,OSS |
| 腾讯云 | 是 | 是 | 支持腾讯云注册的域名,支持部署到腾讯云 CDN |
| 七牛云 | 否 | 是 | 七牛云没有注册域名服务,支持部署到七牛云 CDN |
| CloudFlare | 是 | 否 | 支持 CloudFlare 注册的域名CloudFlare 服务自带SSL证书 |
| SSH | 否 | 是 | 支持部署到 SSH 服务器 |
| WEBHOOK | 否 | 是 | 支持回调到 WEBHOOK |
## 四、系统截图
@@ -103,13 +91,12 @@ go run main.go serve
![history](https://i.imgur.com/aaPtSW7.jpeg)
## 五、概念
Certimate 的工作流程如下:
* 用户通过 Certimate 管理页面填写申请证书的信息包括域名、dns 服务商的授权信息、以及要部署到的服务商的授权信息。
* Certimate 向证书商的 API 发起申请请求,获取 SSL 证书。
* Certimate 向证书商的 API 发起申请请求,获取 SSL 证书。
* Certimate 存储证书信息,包括证书内容、私钥、证书有效期等,并在证书即将过期时自动续期。
* Certimate 向服务商的 API 发起部署请求,将证书部署到服务商的服务器上。
@@ -147,7 +134,6 @@ Certimate 申请证书后,会自动将证书部署到你指定的目标上,
## 六、常见问题
Q: 提供saas服务吗
> A: 不提供目前仅支持self-hosted私有部署
@@ -160,8 +146,23 @@ Q: 自动续期证书?
> A: 已经申请的证书会在过期前10天自动续期。每天会检查一次证书是否快要过期快要过期时会自动重新申请证书并部署到目标服务上。
## 七、贡献
Certimate 是一个免费且开源的项目,采用 [MIT 开源协议](LICENSE.md)。你可以使用它做任何你想做的事,甚至把它当作一个付费服务提供给用户。
## 七、许可证
你可以通过以下方式来支持 Certimate 的开发:
Certimate 采用 MIT 许可证,详情请查看 [LICENSE](LICENSE.md) 文件
* [提交代码:如果你发现了 bug 或有新的功能需求,而你又有相关经验,可以提交代码给我们](CONTRIBUTING.md)。
* 提交 issue功能建议或者 bug 可以[提交 issue](https://github.com/usual2970/certimate/issues) 给我们。
支持更多服务商、UI 的优化改进、BUG 修复、文档完善等,欢迎大家提交 PR。
## 八、加入社区
* [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
* 微信群聊(超200人需邀请入群可先加作者好友)
<img src="https://i.imgur.com/8xwsLTA.png" width="400"/>
## 九、Star History
[![Stargazers over time](https://starchart.cc/usual2970/certimate.svg?variant=adaptive)](https://starchart.cc/usual2970/certimate)

166
README_EN.md Normal file
View File

@@ -0,0 +1,166 @@
[中文](README.md) | [English](README_EN.md)
# 🔒Certimate
For individuals managing personal projects or those responsible for IT operations in small businesses who need to manage multiple domain names, applying for certificates manually comes with several drawbacks:
1. 😱Troublesome: Applying for and deploying certificates isnt difficult, but it can be quite a hassle, especially when managing multiple domains.
2. 😭Easily forgotten: The current free certificate has a validity period of only 90 days, requiring regular renewal operations. This increases the workload and makes it easy to forget, which can result in the website becoming inaccessible.
Certimate was created to solve the above-mentioned issues and has the following features:
1. Simple operation: Automatically apply, deploy, and renew SSL certificates without any manual intervention.
2. Support for self-hosted deployment: The deployment method is simple; you only need to download the binary file and execute it. Both the binary files and Docker images are generated using GitHub Actions, ensuring a transparent process that can be audited independently.
3. Data security: Since it is a self-hosted deployment, all data is stored locally and will not be saved on the service providers servers, ensuring the security of the data.
Related articles:
* [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
* [Introduction to Domain Variables and Deployment Authorization Groups](https://docs.certimate.me/blog/multi-deployer)
Certimate aims to provide users with a secure and user-friendly SSL certificate management solution. For usage documentation, please visit.[https://docs.certimate.me](https://docs.certimate.me)
## Installation
Installing Certimate is very simple, you can choose one of the following methods for installation:
### 1. Binary File
You can download the precompiled binary files directly from the [Releases page](https://github.com/usual2970/certimate/releases), and after extracting them, execute:
```bash
./certimate serve
```
Or run the following command to automatically add a certificate to Certimate itself.
```bash
./certimate serve yourDomain
```
> [!NOTE]
> When executing the binary file on macOS, you may see a prompt saying: “Cannot open certimate because Apple cannot check it for malicious software.” You can go to System Preferences > Security & Privacy > General, then click “Allow Anyway,” and try executing the binary file again.
### 2. Docker Installation
```bash
mkdir -p ~/.certimate && cd ~/.certimate && curl -O https://raw.githubusercontent.com/usual2970/certimate/refs/heads/main/docker/docker-compose.yml && docker compose up -d
```
### 3. Source Code Installation
```bash
git clone EMAIL:usual2970/certimate.git
cd certimate
go mod vendor
go run main.go serve
```
## Usage
After completing the installation steps above, you can access the Certimate management page by visiting http://127.0.0.1:8090 in your browser.
```bash
usernameadmin@certimate.fun
password1234567890
```
![usage.gif](https://i.imgur.com/zpCoLVM.gif)
## List of Supported Providers
| Provider | Domain Registrar | Deployment Service | Remarks |
| ------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------- |
| Alibaba Cloud | Yes | Yes | Supports domains registered with Alibaba Cloud; supports deployment to Alibaba Cloud CDN and OSS. |
| Tencent Cloud | Yes | Yes | Supports domains registered with Tencent Cloud; supports deployment to Tencent Cloud CDN. |
| Qiniu Cloud | No | Yes | Qiniu Cloud does not offer domain registration services; supports deployment to Qiniu Cloud CDN. |
| Cloudflare | Yes | No | Supports domains registered with Cloudflare; Cloudflare services come with SSL certificates. |
| SSH | No | Yes | Supports deployment to SSH servers. |
| WEBHOOK | No | Yes | Supports callbacks to WEBHOOK. |
## Screenshots
![login](https://i.imgur.com/SYjjbql.jpeg)
![dashboard](https://i.imgur.com/WMVbBId.jpeg)
![domains](https://i.imgur.com/8wit3ZA.jpeg)
![accesses](https://i.imgur.com/EWtOoJ0.jpeg)
![history](https://i.imgur.com/aaPtSW7.jpeg)
## Concepts
The workflow of Certimate is as follows:
* Users fill in the certificate application information on the Certimate management page, including domain name, authorization information for the DNS provider, and authorization information for the service provider to deploy to.
* Certimate sends a request to the certificate vendor's API to apply for an SSL certificate.
* Certimate stores the certificate information, including the certificate content, private key, validity period, etc., and automatically renews the certificate when it is about to expire.
* Certimate sends a deployment request to the service provider's API to deploy the certificate to the service provider's servers.
This involves authorization information for the domain, DNS provider, and deployment service provider.
### 1. Domain
It involves the domain name for which the certificate is being requested.
### 2. Authorization Information for the DNS Provider
To apply for a certificate for a domain, you need to prove that the domain belongs to you. Therefore, when manually applying for a certificate, you typically need to add a TXT record to the DNS records in the domain provider's control panel.
Certimate will automatically add a TXT record for you; you only need to fill in the authorization information for your DNS provider in the Certimate backend.
For example, if you purchased the domain from Alibaba Cloud, the authorization information would be as follows:
```bash
accessKeyId: xxx
accessKeySecret: TOKEN
```
If you purchased the domain from Tencent Cloud, the authorization information would be as follows:
```bash
secretId: xxx
secretKey: TOKEN
```
### 3. Authorization Information for the Deployment Service Provider
After Certimate applies for the certificate, it will automatically deploy the certificate to your specified target, such as Alibaba Cloud CDN. At this point, you need to fill in the authorization information for Alibaba Cloud. Certimate will use the authorization information and domain name you provided to locate the corresponding CDN service and deploy the certificate to that service.
The authorization information for the deployment service provider is the same as that for the DNS provider, with the distinction that the DNS provider's authorization information is used to prove that the domain belongs to you, while the deployment service provider's authorization information is used to provide authorization for the certificate deployment.
## FAQ
Q: Do you provide SaaS services?
> A: No, we do not provide that. Currently, we only support self-hosted.
Q: Data Security?
> A: Since only self-hosted is supported, all data is stored on the users server. Additionally, the source code of Certimate is open-source, and the packaging process for binary files and Docker images is entirely done using GitHub Actions. This process is transparent and visible, allowing for independent auditing.
Q: Automatic Certificate Renewal?
> A: Certificates that have already been issued will be automatically renewed 10 days before expiration. The system checks once a day to see if any certificates are nearing expiration, and if so, it will automatically reapply for the certificate and deploy it to the target service.
## Contributing
Certimate is a free and open-source project, licensed under the [MIT License](LICENSE.md). You can use it for anything you want, even offering it as a paid service to users.
You can support the development of Certimate in the following ways:
* **Submit Code**: If you find a bug or have new feature requests, and you have relevant experience, [you can submit code to us](CONTRIBUTING_EN.md).
* **Submit an Issue**: For feature suggestions or bugs, you can [submit an issue](https://github.com/usual2970/certimate/issues) to us.
Support for more service providers, UI enhancements, bug fixes, and documentation improvements are all welcome. We encourage everyone to submit pull requests (PRs).
## Join the Community
* [Telegram-a new era of messaging](https://t.me/+ZXphsppxUg41YmVl)
* Wechat Group
<img src="https://i.imgur.com/zSHEoIm.png" width="400"/>

BIN
certimate
View File

Binary file not shown.

115
go.mod
View File

@@ -1,72 +1,85 @@
module certimate
go 1.22
go 1.22.0
toolchain go1.22.5
toolchain go1.23.2
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/go-acme/lego/v4 v4.17.4
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.19.2
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
github.com/qiniu/go-sdk/v7 v7.22.0
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.992
golang.org/x/crypto v0.26.0
golang.org/x/crypto v0.27.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/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
)
require (
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
github.com/alibabacloud-go/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/alibaba-cloud-sdk-go v1.63.15 // indirect
github.com/aliyun/credentials-go v1.3.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2 v1.30.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.33 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/cloudflare/cloudflare-go v0.97.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/clbanning/mxj/v2 v2.5.6 // indirect
github.com/cloudflare/cloudflare-go v0.104.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/ganigeorgiev/fexpr v0.4.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf // indirect
@@ -75,8 +88,6 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
@@ -87,39 +98,39 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/dns v1.1.59 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nrdcg/namesilo v0.2.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect
gocloud.dev v0.37.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.25.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
google.golang.org/api v0.189.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/api v0.197.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

311
go.sum
View File

@@ -1,17 +1,17 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE=
cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs=
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U=
cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
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/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs=
cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
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,30 +35,53 @@ 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=
github.com/aliyun/alibaba-cloud-sdk-go v1.62.712/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15 h1:r2uwBUQhLhcPzaWz9tRJqc8MjYwHb+oF2+Q6467BF14=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.15/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28=
github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
@@ -67,53 +90,56 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.51.11 h1:El5VypsMIz7sFwAAj/j06JX9UGs4KAbAIEaZ57bNY4s=
github.com/aws/aws-sdk-go v1.51.11/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g=
github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw=
github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU=
github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks=
github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I=
github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8 h1:u1KOU1S15ufyZqmH/rA3POkiRH6EcDANHj2xHRzq+zc=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8/go.mod h1:WPv2FRnkIOoDv/8j2gSUsI4qDc7392w5anFB/I89GZ8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
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/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o=
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
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=
github.com/cloudflare/cloudflare-go v0.104.0 h1:R/lB0dZupaZbOgibAH/BRrkFbZ6Acn/WsKg2iX2xXuY=
github.com/cloudflare/cloudflare-go v0.104.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -121,8 +147,9 @@ github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
@@ -147,11 +174,11 @@ github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSe
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
github.com/ganigeorgiev/fexpr v0.4.1 h1:hpUgbUEEWIZhSDBtf4M9aUNfQQ0BZkGRaMePy7Gcx5k=
github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/go-acme/lego/v4 v4.17.4 h1:h0nePd3ObP6o7kAkndtpTzCw8shOZuWckNYeUQwo36Q=
github.com/go-acme/lego/v4 v4.17.4/go.mod h1:dU94SvPNqimEeb7EVilGGSnS0nU1O5Exir0pQ4QFL4U=
github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y=
github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -167,6 +194,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=
@@ -186,15 +215,18 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -211,30 +243,26 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 h1:e+8XbKB6IMn8A4OAyZccO4pYfB3s7bt6azNIPE7AnPg=
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 h1:X3E16S6AUZsQKhJIQ5kNnylnp0GtSy2YhIbxfvDavtU=
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
@@ -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=
@@ -251,6 +281,7 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -279,8 +310,8 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -288,9 +319,12 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/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=
@@ -301,8 +335,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.10.1 h1:cw+vsyfCJD8YObOVeqb93YErnlxwYMkNZ4rwN0G0AaA=
github.com/pocketbase/dbx v1.10.1/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.22.18 h1:yVckUhi5GDORqCb0BbtlvRB1CVxHY9HO9btEaeZHVJU=
@@ -345,19 +380,24 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/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/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/cdn v1.0.1017 h1:OymmfmyFkvHirY3WHsoRT3cdTEsqygLbMn8jM41erK4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1017/go.mod h1:gnLxGXlLmF+jDqWR1/RVoF/UUwxQxomQhkc0oN7KeuI=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.992/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898 h1:LoYv5u+gUoFpU/AmIuTRG/2KiEkdm9gCC0dTvk8WITQ=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.898/go.mod h1:c1j6YQ+vCbeA8kJ59Im4UnMd1GxovlpPBDhGZoewfn8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017 h1:SXrldOXwgomYuATVAuz5ofpTjB+99qVELgdy5R5kMgI=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1017/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 h1:QwE0dRkAAbdf+eACnkNULgDn9ZKUJpPWRyXdqJolP5E=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002/go.mod h1:WdC0FYbqYhJwQ3kbqri6hVP5HAEp+rzX9FToItTAzUg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992 h1:A6O89OlCJQUpNxGqC/E5By04UNKBryIt5olQIGOx8mg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.992/go.mod h1:BcvC7ZPdSlhRggVq4J1ToJlgv8bmODIAuSo0naFZOLo=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.992 h1:ttCM2rrkGipHMFTavrPExKCWcfNjT7AMQ5ERrPExdI4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.992/go.mod h1:WtzarrflM+eoyD8vcRuIPd8fT5UXD4IhUry6iSAUnxc=
github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
@@ -366,21 +406,27 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
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.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
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=
@@ -391,12 +437,17 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -418,8 +469,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -429,8 +480,10 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -438,11 +491,13 @@ 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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -463,6 +518,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -473,8 +529,11 @@ 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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -482,22 +541,29 @@ 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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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.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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.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.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -511,8 +577,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -523,27 +589,28 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI=
google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8=
google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ=
google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg=
google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a h1:hqK4+jJZXCU4pW7jsAdGOVFIfLHQeV7LaizZKnZ84HI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0=
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@@ -1,27 +1,51 @@
package applicant
import (
"certimate/internal/utils/app"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"fmt"
"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"
)
const (
configTypeTencent = "tencent"
configTypeAliyun = "aliyun"
configTypeCloudflare = "cloudflare"
configTypeNamesilo = "namesilo"
configTypeAliyun = "aliyun"
configTypeTencent = "tencent"
configTypeHuaweicloud = "huaweicloud"
configTypeCloudflare = "cloudflare"
configTypeNamesilo = "namesilo"
configTypeGodaddy = "godaddy"
)
const defaultSSLProvider = "letsencrypt"
const (
sslProviderLetsencrypt = "letsencrypt"
sslProviderZeroSSL = "zerossl"
)
const (
zerosslUrl = "https://acme.zerossl.com/v2/DV90"
letsencryptUrl = "https://acme-v02.api.letsencrypt.org/directory"
)
var sslProviderUrls = map[string]string{
sslProviderLetsencrypt: letsencryptUrl,
sslProviderZeroSSL: zerosslUrl,
}
const defaultEmail = "536464346@qq.com"
type Certificate struct {
CertUrl string `json:"certUrl"`
CertStableUrl string `json:"certStableUrl"`
@@ -32,9 +56,10 @@ type Certificate struct {
}
type ApplyOption struct {
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
Email string `json:"email"`
Domain string `json:"domain"`
Access string `json:"access"`
Nameservers string `json:"nameservers"`
}
type MyUser struct {
@@ -59,27 +84,60 @@ type Applicant interface {
func Get(record *models.Record) (Applicant, error) {
access := record.ExpandedOne("access")
email := record.GetString("email")
if email == "" {
email = defaultEmail
}
option := &ApplyOption{
Email: "536464346@qq.com",
Domain: record.GetString("domain"),
Access: access.GetString("config"),
Email: email,
Domain: record.GetString("domain"),
Access: access.GetString("config"),
Nameservers: record.GetString("nameservers"),
}
switch access.GetString("configType") {
case configTypeTencent:
return NewTencent(option), nil
case configTypeAliyun:
return NewAliyun(option), nil
case configTypeTencent:
return NewTencent(option), nil
case configTypeHuaweicloud:
return NewHuaweiCloud(option), nil
case configTypeCloudflare:
return NewCloudflare(option), nil
case configTypeNamesilo:
return NewNamesilo(option), nil
case configTypeGodaddy:
return NewGodaddy(option), nil
default:
return nil, errors.New("unknown config type")
}
}
type SSLProviderConfig struct {
Config SSLProviderConfigContent `json:"config"`
Provider string `json:"provider"`
}
type SSLProviderConfigContent struct {
Zerossl struct {
EabHmacKey string `json:"eabHmacKey"`
EabKid string `json:"eabKid"`
}
}
func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, error) {
record, _ := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='ssl-provider'")
sslProvider := &SSLProviderConfig{
Config: SSLProviderConfigContent{},
Provider: defaultSSLProvider,
}
if record != nil {
if err := record.UnmarshalJSONField("content", sslProvider); err != nil {
return nil, err
}
}
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, err
@@ -93,7 +151,7 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
config := lego.NewConfig(&myUser)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
config.CADirURL = sslProviderUrls[sslProvider.Provider]
config.Certificate.KeyType = certcrypto.RSA2048
// A client facilitates communication with the CA server.
@@ -102,17 +160,25 @@ 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})
reg, err := getReg(client, sslProvider)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to register: %w", err)
}
myUser.Registration = reg
domains := strings.Split(option.Domain, ";")
request := certificate.ObtainRequest{
Domains: []string{option.Domain},
Domains: domains,
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
@@ -128,4 +194,50 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
IssuerCertificate: string(certificates.IssuerCertificate),
Csr: string(certificates.CSR),
}, nil
}
func getReg(client *lego.Client, sslProvider *SSLProviderConfig) (*registration.Resource, error) {
var reg *registration.Resource
var err error
switch sslProvider.Provider {
case sslProviderZeroSSL:
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: sslProvider.Config.Zerossl.EabKid,
HmacEncoded: sslProvider.Config.Zerossl.EabHmacKey,
})
case sslProviderLetsencrypt:
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
default:
err = errors.New("unknown ssl provider")
}
if err != nil {
return nil, err
}
return reg, 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,35 @@
package applicant
import (
"certimate/internal/domain"
"encoding/json"
"os"
godaddyProvider "github.com/go-acme/lego/v4/providers/dns/godaddy"
)
type godaddy struct {
option *ApplyOption
}
func NewGodaddy(option *ApplyOption) Applicant {
return &godaddy{
option: option,
}
}
func (a *godaddy) Apply() (*Certificate, error) {
access := &domain.GodaddyAccess{}
json.Unmarshal([]byte(a.option.Access), access)
os.Setenv("GODADDY_API_KEY", access.ApiKey)
os.Setenv("GODADDY_API_SECRET", access.ApiSecret)
dnsProvider, err := godaddyProvider.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(a.option, dnsProvider)
}

View File

@@ -0,0 +1,35 @@
package applicant
import (
"certimate/internal/domain"
"encoding/json"
"os"
huaweicloudProvider "github.com/go-acme/lego/v4/providers/dns/huaweicloud"
)
type huaweicloud struct {
option *ApplyOption
}
func NewHuaweiCloud(option *ApplyOption) Applicant {
return &huaweicloud{
option: option,
}
}
func (t *huaweicloud) Apply() (*Certificate, error) {
access := &domain.HuaweiCloudAccess{}
json.Unmarshal([]byte(t.option.Access), access)
os.Setenv("HUAWEICLOUD_REGION", access.Region) // 华为云的 SDK 要求必须传一个区域,实际上 DNS-01 流程里用不到,但不传会报错
os.Setenv("HUAWEICLOUD_ACCESS_KEY_ID", access.AccessKeyId)
os.Setenv("HUAWEICLOUD_SECRET_ACCESS_KEY", access.SecretAccessKey)
dnsProvider, err := huaweicloudProvider.NewDNSProvider()
if err != nil {
return nil, err
}
return apply(t.option, dnsProvider)
}

View File

@@ -38,6 +38,10 @@ func NewAliyun(option *DeployerOption) (Deployer, error) {
}
func (a *aliyun) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (a *aliyun) GetInfo() []string {
return a.infos
}

View File

@@ -36,6 +36,10 @@ func NewAliyunCdn(option *DeployerOption) (*AliyunCdn, error) {
}, nil
}
func (a *AliyunCdn) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (a *AliyunCdn) GetInfo() []string {
return a.infos
}

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

@@ -2,9 +2,12 @@ package deployer
import (
"certimate/internal/applicant"
"certimate/internal/utils/app"
"certimate/internal/utils/variables"
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/pocketbase/pocketbase/models"
@@ -13,32 +16,94 @@ import (
const (
targetAliyunOss = "aliyun-oss"
targetAliyunCdn = "aliyun-cdn"
targetAliyunEsa = "aliyun-dcdn"
targetSSH = "ssh"
targetWebhook = "webhook"
targetTencentCdn = "tencent-cdn"
targetQiniuCdn = "qiniu-cdn"
targetLocal = "local"
)
type DeployerOption struct {
DomainId string `json:"domainId"`
Domain string `json:"domain"`
Product string `json:"product"`
Access string `json:"access"`
Certificate applicant.Certificate `json:"certificate"`
DomainId string `json:"domainId"`
Domain string `json:"domain"`
Product string `json:"product"`
Access string `json:"access"`
AceessRecord *models.Record `json:"-"`
Certificate applicant.Certificate `json:"certificate"`
Variables map[string]string `json:"variables"`
}
type Deployer interface {
Deploy(ctx context.Context) error
GetInfo() []string
GetID() string
}
func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
access := record.ExpandedOne("targetAccess")
func Gets(record *models.Record, cert *applicant.Certificate) ([]Deployer, error) {
rs := make([]Deployer, 0)
if record.GetString("targetAccess") != "" {
singleDeployer, err := Get(record, cert)
if err != nil {
return nil, err
}
rs = append(rs, singleDeployer)
}
if record.GetString("group") != "" {
group := record.ExpandedOne("group")
if errs := app.GetApp().Dao().ExpandRecord(group, []string{"access"}, nil); len(errs) > 0 {
errList := make([]error, 0)
for name, err := range errs {
errList = append(errList, fmt.Errorf("展开记录失败,%s: %w", name, err))
}
err := errors.Join(errList...)
return nil, err
}
records := group.ExpandedAll("access")
deployers, err := getByGroup(record, cert, records...)
if err != nil {
return nil, err
}
rs = append(rs, deployers...)
}
return rs, nil
}
func getByGroup(record *models.Record, cert *applicant.Certificate, accesses ...*models.Record) ([]Deployer, error) {
rs := make([]Deployer, 0)
for _, access := range accesses {
deployer, err := getWithAccess(record, cert, access)
if err != nil {
return nil, err
}
rs = append(rs, deployer)
}
return rs, nil
}
func getWithAccess(record *models.Record, cert *applicant.Certificate, access *models.Record) (Deployer, error) {
option := &DeployerOption{
DomainId: record.Id,
Domain: record.GetString("domain"),
Product: getProduct(record),
Access: access.GetString("config"),
DomainId: record.Id,
Domain: record.GetString("domain"),
Product: getProduct(record),
Access: access.GetString("config"),
AceessRecord: access,
Variables: variables.Parse2Map(record.GetString("variables")),
}
if cert != nil {
option.Certificate = *cert
@@ -54,6 +119,8 @@ func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
return NewAliyun(option)
case targetAliyunCdn:
return NewAliyunCdn(option)
case targetAliyunEsa:
return NewAliyunEsa(option)
case targetSSH:
return NewSSH(option)
case targetWebhook:
@@ -61,11 +128,21 @@ func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
case targetTencentCdn:
return NewTencentCdn(option)
case targetQiniuCdn:
return NewQiNiu(option)
case targetLocal:
return NewLocal(option), nil
}
return nil, errors.New("not implemented")
}
func Get(record *models.Record, cert *applicant.Certificate) (Deployer, error) {
access := record.ExpandedOne("targetAccess")
return getWithAccess(record, cert, access)
}
func getProduct(record *models.Record) string {
targetType := record.GetString("targetType")
rs := strings.Split(targetType, "-")

106
internal/deployer/local.go Normal file
View File

@@ -0,0 +1,106 @@
package deployer
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
)
type localAccess struct {
Command string `json:"command"`
CertPath string `json:"certPath"`
KeyPath string `json:"keyPath"`
}
type local struct {
option *DeployerOption
infos []string
}
func NewLocal(option *DeployerOption) *local {
return &local{
option: option,
infos: make([]string, 0),
}
}
func (l *local) GetID() string {
return fmt.Sprintf("%s-%s", l.option.AceessRecord.GetString("name"), l.option.AceessRecord.Id)
}
func (l *local) GetInfo() []string {
return []string{}
}
func (l *local) Deploy(ctx context.Context) error {
access := &localAccess{}
if err := json.Unmarshal([]byte(l.option.Access), access); err != nil {
return err
}
// 复制文件
if err := copyFile(l.option.Certificate.Certificate, access.CertPath); err != nil {
return fmt.Errorf("复制证书失败: %w", err)
}
if err := copyFile(l.option.Certificate.PrivateKey, access.KeyPath); err != nil {
return fmt.Errorf("复制私钥失败: %w", err)
}
// 执行命令
if err := execCmd(access.Command); err != nil {
return fmt.Errorf("执行命令失败: %w", err)
}
return nil
}
func execCmd(command string) error {
// 执行命令
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd", "/C", command)
} else {
cmd = exec.Command("sh", "-c", command)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("执行命令失败: %w", err)
}
return nil
}
func copyFile(content string, path string) error {
dir := filepath.Dir(path)
// 如果目录不存在,创建目录
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return fmt.Errorf("创建目录失败: %w", err)
}
// 创建或打开文件
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("创建文件失败: %w", err)
}
defer file.Close()
// 写入内容到文件
_, err = file.Write([]byte(content))
if err != nil {
return fmt.Errorf("写入文件失败: %w", err)
}
return nil
}

View File

@@ -33,6 +33,10 @@ func NewQiNiu(option *DeployerOption) (*qiuniu, error) {
}, nil
}
func (a *qiuniu) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (q *qiuniu) GetInfo() []string {
return q.info
}

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"os"
xpath "path"
"strings"
"github.com/pkg/sftp"
sshPkg "golang.org/x/crypto/ssh"
@@ -18,14 +19,15 @@ type ssh struct {
}
type sshAccess struct {
Host string `json:"host"`
Username string `json:"username"`
Password string `json:"password"`
Key string `json:"key"`
Port string `json:"port"`
Command string `json:"command"`
CertPath string `json:"certPath"`
KeyPath string `json:"keyPath"`
Host string `json:"host"`
Username string `json:"username"`
Password string `json:"password"`
Key string `json:"key"`
Port string `json:"port"`
PreCommand string `json:"preCommand"`
Command string `json:"command"`
CertPath string `json:"certPath"`
KeyPath string `json:"keyPath"`
}
func NewSSH(option *DeployerOption) (Deployer, error) {
@@ -35,6 +37,10 @@ func NewSSH(option *DeployerOption) (Deployer, error) {
}, nil
}
func (a *ssh) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (s *ssh) GetInfo() []string {
return s.infos
}
@@ -44,6 +50,16 @@ func (s *ssh) Deploy(ctx context.Context) error {
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
return err
}
// 将证书路径和命令中的变量替换为实际值
for k, v := range s.option.Variables {
key := fmt.Sprintf("${%s}", k)
access.CertPath = strings.ReplaceAll(access.CertPath, key, v)
access.KeyPath = strings.ReplaceAll(access.KeyPath, key, v)
access.Command = strings.ReplaceAll(access.Command, key, v)
access.PreCommand = strings.ReplaceAll(access.PreCommand, key, v)
}
// 连接
client, err := s.getClient(access)
if err != nil {
@@ -53,14 +69,13 @@ func (s *ssh) Deploy(ctx context.Context) error {
s.infos = append(s.infos, toStr("ssh连接成功", nil))
// 上传
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("failed to create session: %w", err)
// 执行前置命令
if access.PreCommand != "" {
err, stdout, stderr := s.sshExecCommand(client, access.PreCommand)
if err != nil {
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
}
defer session.Close()
s.infos = append(s.infos, toStr("ssh创建session成功", nil))
// 上传证书
if err := s.upload(client, s.option.Certificate.Certificate, access.CertPath); err != nil {
@@ -77,18 +92,28 @@ func (s *ssh) Deploy(ctx context.Context) error {
s.infos = append(s.infos, toStr("ssh上传私钥成功", nil))
// 执行命令
err, stdout, stderr := s.sshExecCommand(client, access.Command)
if err != nil {
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
s.infos = append(s.infos, toStr("ssh执行命令成功", stdout))
return nil
}
func (s *ssh) sshExecCommand(client *sshPkg.Client, command string) (error, string, string) {
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("failed to create ssh session: %w", err), "", ""
}
defer session.Close()
var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf
var stderrBuf bytes.Buffer
session.Stderr = &stderrBuf
if err := session.Run(access.Command); err != nil {
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdoutBuf.String(), stderrBuf.String())
}
s.infos = append(s.infos, toStr("ssh执行命令成功", []string{stdoutBuf.String()}))
return nil
err = session.Run(command)
return err, stdoutBuf.String(), stderrBuf.String()
}
func (s *ssh) upload(client *sshPkg.Client, content, path string) error {

View File

@@ -2,16 +2,17 @@ package deployer
import (
"certimate/internal/domain"
"certimate/internal/utils/rand"
"context"
"encoding/json"
"errors"
"encoding/base64"
"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"
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
)
type tencentCdn struct {
@@ -39,20 +40,16 @@ func NewTencentCdn(option *DeployerOption) (Deployer, error) {
}, nil
}
func (a *tencentCdn) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (t *tencentCdn) GetInfo() []string {
return t.infos
}
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 {
@@ -60,7 +57,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)
}
@@ -78,7 +75,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)
@@ -89,25 +86,35 @@ 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.ResourceType = common.StringPtr("cdn")
request.Status = common.Int64Ptr(1)
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
if(strings.Contains(t.option.Domain, "*")){
list, errGetList := t.getDomainList()
if errGetList != nil {
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
}
if list == nil || len(list) == 0 {
return fmt.Errorf("failed to get certificate domain list: empty list.")
}
request.InstanceIdList = common.StringPtrs(list)
}else{ // 否则直接使用传入的域名
request.InstanceIdList = common.StringPtrs([]string{t.option.Domain})
}
// 返回的resp是一个DeployCertificateInstanceResponse的实例与请求对象对应
resp, err := client.DeployCertificateInstance(request)
@@ -118,54 +125,26 @@ func (t *tencentCdn) deploy(resource *tag.ResourceTagMapping, certId string) err
return nil
}
func (t *tencentCdn) resource() (*tag.ResourceTagMapping, error) {
request := tag.NewGetResourcesRequest()
func (t *tencentCdn) getDomainList() ([]string, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = "tag.tencentcloudapi.com"
cpf.HttpProfile.Endpoint = "cdn.tencentcloudapi.com"
client, _ := cdn.NewClient(t.credential, "", cpf)
client, err := tag.NewClient(t.credential, "", cpf)
request := cdn.NewDescribeCertDomainsRequest()
cert := base64.StdEncoding.EncodeToString([]byte(t.option.Certificate.Certificate))
request.Cert = &cert
response, err := client.DescribeCertDomains(request)
if err != nil {
return nil, fmt.Errorf("failed to create client: %w", err)
return nil, fmt.Errorf("failed to get domain list: %w", err)
}
response, err := client.GetResources(request)
if err != nil {
return nil, fmt.Errorf("failed to get resources: %w", err)
domains := make([]string, 0)
for _, domain := range response.Response.Domains {
domains = append(domains, *domain)
}
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
return domains, nil
}

View File

@@ -32,6 +32,10 @@ func NewWebhook(option *DeployerOption) (Deployer, error) {
}, nil
}
func (a *webhook) GetID() string {
return fmt.Sprintf("%s-%s", a.option.AceessRecord.GetString("name"), a.option.AceessRecord.Id)
}
func (w *webhook) GetInfo() []string {
return w.infos
}

View File

@@ -10,6 +10,12 @@ type TencentAccess struct {
SecretKey string `json:"secretKey"`
}
type HuaweiCloudAccess struct {
Region string `json:"region"`
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
type CloudflareAccess struct {
DnsApiToken string `json:"dnsApiToken"`
}
@@ -22,3 +28,9 @@ type QiniuAccess struct {
type NameSiloAccess struct {
ApiKey string `json:"apiKey"`
}
type GodaddyAccess struct {
ApiKey string `json:"apiKey"`
ApiSecret string `json:"apiSecret"`
}

View File

@@ -41,7 +41,7 @@ func deploy(ctx context.Context, record *models.Record) error {
return err
}
history.record(checkPhase, "获取记录成功", nil)
if errs := app.GetApp().Dao().ExpandRecord(currRecord, []string{"access", "targetAccess"}, nil); len(errs) > 0 {
if errs := app.GetApp().Dao().ExpandRecord(currRecord, []string{"access", "targetAccess", "group"}, nil); len(errs) > 0 {
errList := make([]error, 0)
for name, err := range errs {
@@ -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)
@@ -96,24 +99,30 @@ func deploy(ctx context.Context, record *models.Record) error {
// ############3.部署证书
history.record(deployPhase, "开始部署", nil, false)
deployer, err := deployer.Get(currRecord, certificate)
deployers, err := deployer.Gets(currRecord, certificate)
if err != nil {
history.record(deployPhase, "获取deployer失败", &RecordInfo{Err: err})
app.GetApp().Logger().Error("获取deployer失败", "err", err)
return err
}
if err = deployer.Deploy(ctx); err != nil {
for _, deployer := range deployers {
if err = deployer.Deploy(ctx); err != nil {
app.GetApp().Logger().Error("部署失败", "err", err)
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
return err
}
history.record(deployPhase, fmt.Sprintf("[%s]-部署成功", deployer.GetID()), &RecordInfo{
Info: deployer.GetInfo(),
}, false)
app.GetApp().Logger().Error("部署失败", "err", err)
history.record(deployPhase, "部署失败", &RecordInfo{Err: err, Info: deployer.GetInfo()})
return err
}
app.GetApp().Logger().Info("部署成功")
history.record(deployPhase, "部署成功", &RecordInfo{
Info: deployer.GetInfo(),
}, true)
history.record(deployPhase, "部署成功", nil, true)
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,
}
}

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

@@ -0,0 +1,130 @@
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
}
n := notifyPackage.New()
// 添加推送渠道
n.UseServices(notifiers...)
// 发送消息
return n.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

@@ -0,0 +1,30 @@
package variables
import "strings"
// Parse2Map 将变量赋值字符串解析为map
func Parse2Map(str string) map[string]string {
m := make(map[string]string)
lines := strings.Split(str, ";")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
kv := strings.Split(line, "=")
if len(kv) != 2 {
continue
}
m[kv[0]] = kv[1]
}
return m
}

View File

@@ -0,0 +1,56 @@
package variables
import (
"reflect"
"testing"
)
func TestParse2Map(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want map[string]string
}{
{
name: "test1",
args: args{
str: "a=1;b=2;c=3",
},
want: map[string]string{
"a": "1",
"b": "2",
"c": "3",
},
},
{
name: "test2",
args: args{
str: `a=1;
b=2;
c=`,
},
want: map[string]string{
"a": "1",
"b": "2",
"c": "",
},
},
{
name: "test3",
args: args{
str: "1",
},
want: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Parse2Map(tt.args.str); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Parse2Map() = %v, want %v", got, tt.want)
}
})
}
}

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

@@ -12,73 +12,10 @@ import (
func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `[
{
"id": "_pb_users_auth_",
"created": "2024-07-29 09:44:56.398Z",
"updated": "2024-09-07 07:42:28.389Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
},
{
"id": "z3p974ainxjqlvs",
"created": "2024-07-29 10:02:48.334Z",
"updated": "2024-09-07 07:42:28.389Z",
"updated": "2024-09-12 13:09:54.500Z",
"name": "domains",
"type": "base",
"system": false,
@@ -333,7 +270,7 @@ func init() {
{
"id": "4yzbv8urny5ja1e",
"created": "2024-07-29 10:04:39.685Z",
"updated": "2024-09-10 12:51:41.405Z",
"updated": "2024-09-12 13:18:00.093Z",
"name": "access",
"type": "base",
"system": false,
@@ -381,7 +318,8 @@ func init() {
"webhook",
"cloudflare",
"qiniu",
"namesilo"
"namesilo",
"godaddy"
]
}
},
@@ -397,6 +335,23 @@ func init() {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "hsxcnlvd",
"name": "usage",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"apply",
"deploy",
"all"
]
}
}
],
"indexes": [
@@ -412,7 +367,7 @@ func init() {
{
"id": "0a1o4e6sstp694f",
"created": "2024-07-30 06:30:27.801Z",
"updated": "2024-09-07 07:42:28.390Z",
"updated": "2024-09-12 13:09:54.500Z",
"name": "deployments",
"type": "base",
"system": false,
@@ -493,6 +448,69 @@ func init() {
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"created": "2024-09-12 13:09:54.234Z",
"updated": "2024-09-12 13:09:54.500Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
}
]`

View File

@@ -0,0 +1,20 @@
package migrations
import (
"github.com/pocketbase/dbx"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(db dbx.Builder) error {
// add up queries...
db.NewQuery("update access set usage='all' where configType in ('aliyun', 'tencent')").Execute()
db.NewQuery("update access set usage='deploy' where configType in ('ssh', 'webhook','qiniu')").Execute()
db.NewQuery("update access set usage='apply' where configType in ('cloudflare','namesilo','godaddy')").Execute()
return nil
}, func(db dbx.Builder) error {
// add down queries...
return nil
})
}

View File

@@ -12,73 +12,10 @@ import (
func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `[
{
"id": "_pb_users_auth_",
"created": "2024-07-29 09:44:56.398Z",
"updated": "2024-09-02 14:02:40.191Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
},
{
"id": "z3p974ainxjqlvs",
"created": "2024-07-29 10:02:48.334Z",
"updated": "2024-09-04 15:10:19.440Z",
"updated": "2024-09-12 23:13:12.119Z",
"name": "domains",
"type": "base",
"system": false,
@@ -97,6 +34,19 @@ func init() {
"pattern": ""
}
},
{
"system": false,
"id": "ukkhuw85",
"name": "email",
"type": "email",
"required": false,
"presentable": false,
"unique": false,
"options": {
"exceptDomains": null,
"onlyDomains": null
}
},
{
"system": false,
"id": "v98eebqq",
@@ -333,7 +283,7 @@ func init() {
{
"id": "4yzbv8urny5ja1e",
"created": "2024-07-29 10:04:39.685Z",
"updated": "2024-09-04 15:09:46.789Z",
"updated": "2024-09-12 23:08:52.810Z",
"name": "access",
"type": "base",
"system": false,
@@ -380,7 +330,9 @@ func init() {
"ssh",
"webhook",
"cloudflare",
"qiniu"
"qiniu",
"namesilo",
"godaddy"
]
}
},
@@ -396,6 +348,23 @@ func init() {
"min": "",
"max": ""
}
},
{
"system": false,
"id": "hsxcnlvd",
"name": "usage",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"apply",
"deploy",
"all"
]
}
}
],
"indexes": [
@@ -411,7 +380,7 @@ func init() {
{
"id": "0a1o4e6sstp694f",
"created": "2024-07-30 06:30:27.801Z",
"updated": "2024-09-02 14:02:40.191Z",
"updated": "2024-09-12 23:08:52.810Z",
"name": "deployments",
"type": "base",
"system": false,
@@ -492,6 +461,114 @@ func init() {
"updateRule": null,
"deleteRule": null,
"options": {}
},
{
"id": "_pb_users_auth_",
"created": "2024-09-12 13:09:54.234Z",
"updated": "2024-09-12 23:08:52.811Z",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"system": false,
"id": "users_name",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "users_avatar",
"name": "avatar",
"type": "file",
"required": false,
"presentable": false,
"unique": false,
"options": {
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null,
"maxSelect": 1,
"maxSize": 5242880,
"protected": false
}
}
],
"indexes": [],
"listRule": "id = @request.auth.id",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": "id = @request.auth.id",
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"onlyVerified": false,
"requireEmail": false
}
},
{
"id": "dy6ccjb60spfy6p",
"created": "2024-09-12 23:12:21.677Z",
"updated": "2024-09-12 23:19:09.110Z",
"name": "settings",
"type": "base",
"system": false,
"schema": [
{
"system": false,
"id": "1tcmdsdf",
"name": "name",
"type": "text",
"required": false,
"presentable": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "f9wyhypi",
"name": "content",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
}
],
"indexes": [
"CREATE UNIQUE INDEX ` + "`" + `idx_RO7X9Vw` + "`" + ` ON ` + "`" + `settings` + "`" + ` (` + "`" + `name` + "`" + `)"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
}
]`

View File

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

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
})
}

View File

@@ -0,0 +1,706 @@
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-26 08:20:28.305Z",
"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",
"local"
]
}
},
{
"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-26 08:36:59.632Z",
"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",
"local"
]
}
},
{
"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-24 14:44:48.041Z",
"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-24 14:44:48.041Z",
"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-24 14:44:48.041Z",
"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-24 14:44:48.041Z",
"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,92 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"qiniu",
"cloudflare",
"namesilo",
"godaddy",
"local",
"ssh",
"webhook"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
if err != nil {
return err
}
// update
edit_configType := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "hwy7m03o",
"name": "configType",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"aliyun",
"tencent",
"huaweicloud",
"ssh",
"webhook",
"cloudflare",
"qiniu",
"namesilo",
"godaddy",
"local"
]
}
}`), edit_configType); err != nil {
return err
}
collection.Schema.AddField(edit_configType)
return dao.SaveCollection(collection)
})
}

View File

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

332
ui/dist/assets/index-DpHAV802.js 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

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

After

Width:  |  Height:  |  Size: 5.5 KiB

5
ui/dist/imgs/providers/godaddy.svg vendored Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<circle cx="512" cy="512" r="512" style="fill:#1bdbdb"/>
<path d="M697.6 315.9c-53.2-33.2-123.3-25.3-185.6 13.9-62.4-39.3-132.4-47.2-185.6-13.9-84.1 52.5-94.3 187.8-22.8 302.2 52.7 84.3 135.1 133.7 208.4 132.8 73.3.9 155.7-48.5 208.4-132.8 71.5-114.4 61.3-249.7-22.8-302.2M342.2 594c-15-24.1-26.1-49.5-33-75.5-6.5-24.5-8.9-48.5-7.1-71.2 3.2-42.3 20.4-75.2 48.4-92.7s65.2-18.6 104.5-2.9c5.9 2.4 11.8 5.1 17.6 8.1-21 19-40.3 41.9-56.7 68.1-43.4 69.5-56.6 146.7-41.5 208.4-11.8-12.8-22.6-27-32.2-42.3m372.6-75.6c-6.9 26.1-17.9 51.5-33 75.5-9.6 15.4-20.4 29.5-32.3 42.3 13.5-55.2 4.4-122.9-28.9-186.3-2.3-4.5-7.7-5.9-12-3.3l-103.5 64.7c-4 2.5-5.2 7.7-2.7 11.7l15.2 24.3c2.5 4 7.7 5.2 11.7 2.7l67.1-41.9c2.2 6.4 4.3 12.9 6 19.5 6.5 24.5 8.9 48.5 7.1 71.2-3.2 42.3-20.4 75.2-48.4 92.7-14 8.8-30.3 13.4-48 13.9h-2.2c-17.7-.5-34-5.1-48-13.9-28-17.5-45.2-50.4-48.4-92.7-1.7-22.7.7-46.7 7.1-71.2 6.8-26.1 17.9-51.5 33-75.5 15-24.1 33-45.2 53.4-62.8 19.2-16.6 39.7-29.2 60.9-37.6 39.4-15.7 76.5-14.6 104.5 2.9s45.2 50.4 48.4 92.7c1.8 22.6-.6 46.6-7 71.1" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

18
ui/dist/imgs/providers/letsencrypt.svg vendored Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="96" height="96">
<path d="M0 0 C3.96616708 2.87966696 6.94216973 6.71174376 8.75390625 11.2734375 C8.94921875 14.2890625 8.94921875 14.2890625 8.87890625 17.5234375 C8.86085937 18.60109375 8.8428125 19.67875 8.82421875 20.7890625 C8.80101562 21.60890625 8.7778125 22.42875 8.75390625 23.2734375 C9.86765625 23.2321875 10.98140625 23.1909375 12.12890625 23.1484375 C15.75390625 23.2734375 15.75390625 23.2734375 17.75390625 25.2734375 C17.94178314 28.33535881 18.00936994 31.30529877 17.984375 34.3671875 C17.98391678 35.2696521 17.98345856 36.1721167 17.98298645 37.10192871 C17.97996265 39.01181349 17.97207256 40.92169577 17.9597168 42.83154297 C17.94148066 45.76148172 17.93920695 48.69110422 17.93945312 51.62109375 C17.93453722 53.47396108 17.92870858 55.32682625 17.921875 57.1796875 C17.92076218 58.05991577 17.91964935 58.94014404 17.91850281 59.8470459 C17.86996885 66.04131231 17.86996885 66.04131231 16.75390625 68.2734375 C-1.72609375 68.2734375 -20.20609375 68.2734375 -39.24609375 68.2734375 C-40.95558713 64.85445075 -40.40930624 60.94182137 -40.4140625 57.1796875 C-40.4173909 56.2772229 -40.4207193 55.3747583 -40.42414856 54.44494629 C-40.42921569 52.53508006 -40.43155695 50.62520497 -40.43139648 48.71533203 C-40.43358486 45.78533449 -40.45174793 42.85571222 -40.47070312 39.92578125 C-40.47363664 38.07291796 -40.47562172 36.22005287 -40.4765625 34.3671875 C-40.48374802 33.48695923 -40.49093353 32.60673096 -40.49833679 31.6998291 C-40.47491923 25.50226298 -40.47491923 25.50226298 -38.24609375 23.2734375 C-34.62109375 23.1484375 -34.62109375 23.1484375 -31.24609375 23.2734375 C-31.30410156 22.5 -31.36210937 21.7265625 -31.421875 20.9296875 C-31.73940921 13.59918343 -31.17057807 8.85901698 -26.24609375 3.2734375 C-18.78730361 -3.81241313 -9.01893001 -5.1380571 0 0 Z M-19.24609375 15.2734375 C-19.24609375 17.9134375 -19.24609375 20.5534375 -19.24609375 23.2734375 C-13.96609375 23.2734375 -8.68609375 23.2734375 -3.24609375 23.2734375 C-2.47540396 15.74874343 -2.47540396 15.74874343 -5.37109375 12.2734375 C-8.09392313 10.10082379 -8.09392313 10.10082379 -11.24609375 9.6484375 C-15.38792256 10.5113185 -16.80835846 11.83192885 -19.24609375 15.2734375 Z M-11.24609375 39.2734375 C-11.24609375 44.5534375 -11.24609375 49.8334375 -11.24609375 55.2734375 C-10.58609375 54.9434375 -9.92609375 54.6134375 -9.24609375 54.2734375 C-8.89430178 51.9428157 -8.56279196 49.60908683 -8.24609375 47.2734375 C-7.18359375 44.8359375 -7.18359375 44.8359375 -6.24609375 43.2734375 C-7.60933118 41.2334656 -7.60933118 41.2334656 -9.24609375 39.2734375 C-9.90609375 39.2734375 -10.56609375 39.2734375 -11.24609375 39.2734375 Z " fill="#10376E" transform="translate(59.24609375,27.7265625)"/>
<path d="M0 0 C1.12067871 0.01047363 2.24135742 0.02094727 3.39599609 0.03173828 C5.21357422 0.04140625 5.21357422 0.04140625 7.06787109 0.05126953 C8.33888672 0.06802734 9.60990234 0.08478516 10.91943359 0.10205078 C12.19818359 0.11107422 13.47693359 0.12009766 14.79443359 0.12939453 C17.96128526 0.15302775 21.12776783 0.18597449 24.29443359 0.22705078 C24.4024195 2.66573246 24.48169949 5.09964212 24.54443359 7.53955078 C24.59470703 8.57499023 24.59470703 8.57499023 24.64599609 9.63134766 C24.68505859 11.65283203 24.68505859 11.65283203 24.29443359 15.22705078 C22.27880859 17.20751953 22.27880859 17.20751953 20.29443359 18.22705078 C20.7362031 21.76120687 21.41676358 24.50596315 22.85693359 27.78955078 C24.29443359 32.22705078 24.29443359 32.22705078 24.29443359 45.22705078 C15.05443359 45.22705078 5.81443359 45.22705078 -3.70556641 45.22705078 C-5.41505978 41.80806403 -4.8687789 37.89543466 -4.87353516 34.13330078 C-4.87686356 33.23083618 -4.88019196 32.32837158 -4.88362122 31.39855957 C-4.88868835 29.48869334 -4.89102961 27.57881825 -4.89086914 25.66894531 C-4.89305752 22.73894777 -4.91122059 19.8093255 -4.93017578 16.87939453 C-4.9331093 15.02653124 -4.93509437 13.17366616 -4.93603516 11.32080078 C-4.94322067 10.44057251 -4.95040619 9.56034424 -4.95780945 8.65344238 C-4.92667455 0.41344589 -4.92667455 0.41344589 0 0 Z " fill="#0F3161" transform="translate(23.70556640625,50.77294921875)"/>
<path d="M0 0 C3.95863415 2.87419762 6.96698604 6.71040909 8.75390625 11.2734375 C8.94921875 14.5078125 8.94921875 14.5078125 8.87890625 18.0234375 C8.86085937 19.19390625 8.8428125 20.364375 8.82421875 21.5703125 C8.80101562 22.46234375 8.7778125 23.354375 8.75390625 24.2734375 C4.79390625 24.2734375 0.83390625 24.2734375 -3.24609375 24.2734375 C-3.32859375 22.8709375 -3.41109375 21.4684375 -3.49609375 20.0234375 C-3.85974947 16.86013089 -4.14926646 15.42047154 -5.93359375 12.7109375 C-8.99476118 10.80804964 -10.701589 10.68268671 -14.24609375 11.2734375 C-16.74330199 12.6018971 -16.74330199 12.6018971 -18.24609375 15.2734375 C-18.86751236 18.3074225 -19.06322936 21.16474289 -19.24609375 24.2734375 C-23.20609375 24.2734375 -27.16609375 24.2734375 -31.24609375 24.2734375 C-32.16565531 10.86827339 -32.16565531 10.86827339 -27.671875 4.85546875 C-20.21832025 -3.41491392 -9.84160472 -5.60673239 0 0 Z " fill="#F3A006" transform="translate(59.24609375,27.7265625)"/>
<path d="M0 0 C0 3.96 0 7.92 0 12 C-1.4540625 12.680625 -1.4540625 12.680625 -2.9375 13.375 C-6.07079582 14.65786221 -6.07079582 14.65786221 -7 17 C-7.22467831 18.53668865 -7.40796805 20.07968054 -7.5625 21.625 C-7.706875 23.06875 -7.85125 24.5125 -8 26 C-11.96 26 -15.92 26 -20 26 C-20.91789085 12.61919109 -20.91789085 12.61919109 -16.4375 6.5859375 C-11.06401953 0.65758677 -7.616909 -0.12285337 0 0 Z " fill="#D58C05" transform="translate(48,26)"/>
<path d="M0 0 C0.75 1.6875 0.75 1.6875 1 4 C-1.81446105 8.64997913 -6.25914236 12.37140566 -11 15 C-14 14.875 -14 14.875 -16 14 C-16.80078125 11.796875 -16.80078125 11.796875 -17 9 C-14.98046875 6.578125 -14.98046875 6.578125 -12.1875 4.25 C-10.81658203 3.08210938 -10.81658203 3.08210938 -9.41796875 1.890625 C-5.71909434 -1.00154821 -4.50480579 -1.06692769 0 0 Z " fill="#DA9004" transform="translate(82,13)"/>
<path d="M0 0 C5.0875582 0.44824301 7.6553513 1.85561989 11.4375 5.25 C12.32308594 6.01828125 13.20867188 6.7865625 14.12109375 7.578125 C16 10 16 10 15.78515625 12.796875 C15.52605469 13.52390625 15.26695313 14.2509375 15 15 C12.859375 15.76171875 12.859375 15.76171875 10 16 C7.140625 14.26953125 7.140625 14.26953125 4.25 11.8125 C3.28578125 11.01457031 2.3215625 10.21664063 1.328125 9.39453125 C-1.92423854 6.04939896 -1.92423854 6.04939896 -2 3 C-1.34 2.01 -0.68 1.02 0 0 Z " fill="#DA8F04" transform="translate(15,12)"/>
<path d="M0 0 C3 0.125 3 0.125 4 1.125 C4.07325168 3.98767567 4.09238205 6.825719 4.0625 9.6875 C4.05798828 10.49380859 4.05347656 11.30011719 4.04882812 12.13085938 C4.03700518 14.12893756 4.01906914 16.12697783 4 18.125 C1.6875 19.25 1.6875 19.25 -1 20.125 C-1.99 19.465 -2.98 18.805 -4 18.125 C-4.24817926 15.19116664 -4.32643881 12.48867315 -4.25 9.5625 C-4.24484375 8.76908203 -4.2396875 7.97566406 -4.234375 7.15820312 C-4.13892499 0.17245521 -4.13892499 0.17245521 0 0 Z " fill="#F5A105" transform="translate(48,-0.125)"/>
<path d="M0 0 C0.79792969 0.00386719 1.59585938 0.00773437 2.41796875 0.01171875 C3.21589844 0.00785156 4.01382813 0.00398438 4.8359375 0 C10.79136108 0.01011108 10.79136108 0.01011108 11.91796875 1.13671875 C11.95877658 3.13630239 11.96051231 5.13717129 11.91796875 7.13671875 C8.88367198 8.65386714 5.74195208 8.28035791 2.41796875 8.26171875 C1.72832031 8.26558594 1.03867188 8.26945312 0.328125 8.2734375 C-4.82869832 8.26338521 -4.82869832 8.26338521 -7.08203125 7.13671875 C-7.12457481 5.13717129 -7.12283908 3.13630239 -7.08203125 1.13671875 C-5.19650595 -0.74880655 -2.50434294 0.00425186 0 0 Z " fill="#DA9104" transform="translate(81.08203125,38.86328125)"/>
<path d="M0 0 C0.79792969 0.00386719 1.59585937 0.00773437 2.41796875 0.01171875 C3.21589844 0.00785156 4.01382812 0.00398438 4.8359375 0 C10.79136108 0.01011108 10.79136108 0.01011108 11.91796875 1.13671875 C12.16796875 3.57421875 12.16796875 3.57421875 11.91796875 6.13671875 C8.21555593 8.60499397 6.25885765 8.37430901 1.85546875 8.32421875 C-0.01818359 8.31455078 -0.01818359 8.31455078 -1.9296875 8.3046875 C-5.08203125 8.13671875 -5.08203125 8.13671875 -7.08203125 7.13671875 C-7.12457481 5.13717129 -7.12283908 3.13630239 -7.08203125 1.13671875 C-5.19650595 -0.74880655 -2.50434294 0.00425186 0 0 Z " fill="#D98F04" transform="translate(10.08203125,38.86328125)"/>
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.62661079 5.37336228 -1.26467582 7.51333672 -4.8125 10.375 C-6.09060547 11.43847656 -6.09060547 11.43847656 -7.39453125 12.5234375 C-10 14 -10 14 -12.82421875 13.7265625 C-13.90123047 13.36691406 -13.90123047 13.36691406 -15 13 C-10.43369266 8.12927217 -5.25023461 4.10843602 0 0 Z " fill="#F4A005" transform="translate(81,14)"/>
<path d="M0 0 C0.99 0 1.98 0 3 0 C3 6.6 3 13.2 3 20 C1.68 19.34 0.36 18.68 -1 18 C-1.02684312 15.18743719 -1.04676188 12.37512759 -1.0625 9.5625 C-1.07087891 8.76005859 -1.07925781 7.95761719 -1.08789062 7.13085938 C-1.0965143 5.08704779 -1.0522815 3.04316098 -1 1 C-0.67 0.67 -0.34 0.34 0 0 Z " fill="#D88E04" transform="translate(45,0)"/>
<path d="M0 0 C5.23589631 0.4222497 7.61710401 1.95695114 11.375 5.5625 C12.24898437 6.38878906 13.12296875 7.21507812 14.0234375 8.06640625 C14.67570312 8.70449219 15.32796875 9.34257812 16 10 C15.67 11.32 15.34 12.64 15 14 C9.66666667 10 4.33333333 6 -1 2 C-0.67 1.34 -0.34 0.68 0 0 Z " fill="#F4A005" transform="translate(15,12)"/>
<path d="M0 0 C5.61 0 11.22 0 17 0 C17.33 0.99 17.66 1.98 18 3 C11.73 3 5.46 3 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#F4A005" transform="translate(75,39)"/>
<path d="M0 0 C5.61 0 11.22 0 17 0 C17.33 0.99 17.66 1.98 18 3 C11.73 3 5.46 3 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#F4A005" transform="translate(4,39)"/>
<path d="M0 0 C0 5.61 0 11.22 0 17 C-2 16 -2 16 -3.0625 12.8125 C-3.371875 11.554375 -3.68125 10.29625 -4 9 C-4.268125 7.9275 -4.53625 6.855 -4.8125 5.75 C-5 3 -5 3 -3.6875 1.125 C-2 0 -2 0 0 0 Z " fill="#D6D7D9" transform="translate(48,66)"/>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

10
ui/dist/imgs/providers/local.svg vendored Normal file
View File

@@ -0,0 +1,10 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#29a34a" stroke="#29a34a">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">

After

Width:  |  Height:  |  Size: 1.2 KiB

1
ui/dist/imgs/providers/zerossl.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h500v500c-165 0-330 0-500 0 0-165 0-330 0-500z" fill="#4d70d5"/><path d="m0 0h86v85h85v85h85v86c-28.05 0-56.1 0-85 0v85c-28.05 0-56.1 0-85 0 0-28.05 0-56.1 0-85-28.05 0-56.1 0-85 0v85c-28.38 0-56.76 0-86 0 0-28.38 0-56.76 0-86h85c0-28.05 0-56.1 0-85-27.72 0-55.44 0-84 0 0-28.05 0-56.1 0-85h84c0-28.05 0-56.1 0-85z" fill="#fefefe" transform="translate(175 76)"/><path d="m0 0h1v84h85v1c-28.05 0-56.1 0-85 0v85c-28.38 0-56.76 0-86 0 0-28.38 0-56.76 0-86h85c0-27.72 0-55.44 0-84zm-84 85v84h84c0-27.72 0-55.44 0-84-27.72 0-55.44 0-84 0z" fill="#bcc9ef" transform="translate(175 247)"/></svg>

After

Width:  |  Height:  |  Size: 666 B

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-BKUIxIk5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Kh_0Jotc.css">
<script type="module" crossorigin src="/assets/index-DpHAV802.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DOft-CKV.css">
</head>
<body class="bg-background">
<div id="root"></div>

453
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",
@@ -20,10 +22,14 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jszip": "^3.10.1",
"lucide-react": "^0.417.0",
"moment": "^2.30.1",
@@ -31,6 +37,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.1",
"react-i18next": "^15.0.2",
"react-router-dom": "^6.25.1",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
@@ -50,7 +57,7 @@
"eslint-plugin-react-refresh": "^0.4.7",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"typescript": "^5.2.2",
"typescript": "^5.6.2",
"vite": "^5.3.4"
}
},
@@ -379,6 +386,17 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.25.6",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.6.tgz",
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.24.7",
"resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.24.7.tgz",
@@ -1119,6 +1137,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",
@@ -1168,6 +1216,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",
@@ -1440,6 +1517,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",
@@ -1762,6 +1874,35 @@
}
}
},
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz",
"integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-roving-focus": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.1.tgz",
@@ -1974,9 +2115,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz",
"integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
"cpu": [
"arm"
],
@@ -1987,9 +2128,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz",
"integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
"cpu": [
"arm64"
],
@@ -2000,9 +2141,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz",
"integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
"cpu": [
"arm64"
],
@@ -2013,9 +2154,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz",
"integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
"cpu": [
"x64"
],
@@ -2026,9 +2167,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz",
"integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
"cpu": [
"arm"
],
@@ -2039,9 +2180,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz",
"integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
"cpu": [
"arm"
],
@@ -2052,9 +2193,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz",
"integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
"cpu": [
"arm64"
],
@@ -2065,9 +2206,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz",
"integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
"cpu": [
"arm64"
],
@@ -2078,9 +2219,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz",
"integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
"cpu": [
"ppc64"
],
@@ -2091,9 +2232,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz",
"integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
"cpu": [
"riscv64"
],
@@ -2104,9 +2245,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz",
"integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
"cpu": [
"s390x"
],
@@ -2117,9 +2258,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz",
"integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
"cpu": [
"x64"
],
@@ -2130,9 +2271,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz",
"integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
"cpu": [
"x64"
],
@@ -2143,9 +2284,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz",
"integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
"cpu": [
"arm64"
],
@@ -2156,9 +2297,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz",
"integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
"cpu": [
"ia32"
],
@@ -2169,9 +2310,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz",
"integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
"cpu": [
"x64"
],
@@ -2224,7 +2365,7 @@
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
@@ -2833,6 +2974,14 @@
"resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-4.0.0.tgz",
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"dependencies": {
"node-fetch": "^2.6.12"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3571,6 +3720,52 @@
"node": ">= 0.4"
}
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/i18next": {
"version": "23.15.1",
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-23.15.1.tgz",
"integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/i18next-http-backend": {
"version": "2.6.1",
"resolved": "https://registry.npmmirror.com/i18next-http-backend/-/i18next-http-backend-2.6.1.tgz",
"integrity": "sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==",
"dependencies": {
"cross-fetch": "4.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz",
@@ -3906,9 +4101,9 @@
}
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.7.tgz",
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -3986,6 +4181,25 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.18.tgz",
@@ -4164,9 +4378,9 @@
}
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -4201,9 +4415,9 @@
"integrity": "sha512-WJHyaqdAt95JgZ1OCRD099+DST4IIG0M/jMrCckWYDSN/6ocp61qsz7m6h0xI0J2N79ScBljceEC0fFAaQrrAw=="
},
"node_modules/postcss": {
"version": "8.4.40",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.40.tgz",
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"funding": [
{
"type": "opencollective",
@@ -4220,8 +4434,8 @@
],
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.1",
"source-map-js": "^1.2.0"
"picocolors": "^1.1.0",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -4427,6 +4641,27 @@
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-i18next": {
"version": "15.0.2",
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-15.0.2.tgz",
"integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==",
"dependencies": {
"@babel/runtime": "^7.25.0",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.2.tgz",
@@ -4566,6 +4801,11 @@
"node": ">=8.10.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
@@ -4617,9 +4857,9 @@
}
},
"node_modules/rollup": {
"version": "4.19.0",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.19.0.tgz",
"integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==",
"version": "4.22.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -4632,22 +4872,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.19.0",
"@rollup/rollup-android-arm64": "4.19.0",
"@rollup/rollup-darwin-arm64": "4.19.0",
"@rollup/rollup-darwin-x64": "4.19.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.19.0",
"@rollup/rollup-linux-arm-musleabihf": "4.19.0",
"@rollup/rollup-linux-arm64-gnu": "4.19.0",
"@rollup/rollup-linux-arm64-musl": "4.19.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.0",
"@rollup/rollup-linux-riscv64-gnu": "4.19.0",
"@rollup/rollup-linux-s390x-gnu": "4.19.0",
"@rollup/rollup-linux-x64-gnu": "4.19.0",
"@rollup/rollup-linux-x64-musl": "4.19.0",
"@rollup/rollup-win32-arm64-msvc": "4.19.0",
"@rollup/rollup-win32-ia32-msvc": "4.19.0",
"@rollup/rollup-win32-x64-msvc": "4.19.0",
"@rollup/rollup-android-arm-eabi": "4.22.4",
"@rollup/rollup-android-arm64": "4.22.4",
"@rollup/rollup-darwin-arm64": "4.22.4",
"@rollup/rollup-darwin-x64": "4.22.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
"@rollup/rollup-linux-arm64-gnu": "4.22.4",
"@rollup/rollup-linux-arm64-musl": "4.22.4",
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
"@rollup/rollup-linux-s390x-gnu": "4.22.4",
"@rollup/rollup-linux-x64-gnu": "4.22.4",
"@rollup/rollup-linux-x64-musl": "4.22.4",
"@rollup/rollup-win32-arm64-msvc": "4.22.4",
"@rollup/rollup-win32-ia32-msvc": "4.22.4",
"@rollup/rollup-win32-x64-msvc": "4.22.4",
"fsevents": "~2.3.2"
}
},
@@ -4743,9 +4983,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"engines": {
"node": ">=0.10.0"
}
@@ -5014,6 +5254,11 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@@ -5061,9 +5306,9 @@
}
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -5177,14 +5422,14 @@
}
},
"node_modules/vite": {
"version": "5.3.5",
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.3.5.tgz",
"integrity": "sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==",
"version": "5.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.39",
"rollup": "^4.13.0"
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
@@ -5203,6 +5448,7 @@
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
@@ -5220,6 +5466,9 @@
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
@@ -5231,6 +5480,28 @@
}
}
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",

View File

@@ -4,17 +4,19 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"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",
@@ -22,6 +24,7 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
@@ -37,7 +40,11 @@
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1",
"zod": "^3.23.8"
"zod": "^3.23.8",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"react-i18next": "^15.0.2"
},
"devDependencies": {
"@types/node": "^22.0.0",
@@ -52,7 +59,7 @@
"eslint-plugin-react-refresh": "^0.4.7",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"typescript": "^5.2.2",
"typescript": "^5.6.2",
"vite": "^5.3.4"
}
}

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

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<circle cx="512" cy="512" r="512" style="fill:#1bdbdb"/>
<path d="M697.6 315.9c-53.2-33.2-123.3-25.3-185.6 13.9-62.4-39.3-132.4-47.2-185.6-13.9-84.1 52.5-94.3 187.8-22.8 302.2 52.7 84.3 135.1 133.7 208.4 132.8 73.3.9 155.7-48.5 208.4-132.8 71.5-114.4 61.3-249.7-22.8-302.2M342.2 594c-15-24.1-26.1-49.5-33-75.5-6.5-24.5-8.9-48.5-7.1-71.2 3.2-42.3 20.4-75.2 48.4-92.7s65.2-18.6 104.5-2.9c5.9 2.4 11.8 5.1 17.6 8.1-21 19-40.3 41.9-56.7 68.1-43.4 69.5-56.6 146.7-41.5 208.4-11.8-12.8-22.6-27-32.2-42.3m372.6-75.6c-6.9 26.1-17.9 51.5-33 75.5-9.6 15.4-20.4 29.5-32.3 42.3 13.5-55.2 4.4-122.9-28.9-186.3-2.3-4.5-7.7-5.9-12-3.3l-103.5 64.7c-4 2.5-5.2 7.7-2.7 11.7l15.2 24.3c2.5 4 7.7 5.2 11.7 2.7l67.1-41.9c2.2 6.4 4.3 12.9 6 19.5 6.5 24.5 8.9 48.5 7.1 71.2-3.2 42.3-20.4 75.2-48.4 92.7-14 8.8-30.3 13.4-48 13.9h-2.2c-17.7-.5-34-5.1-48-13.9-28-17.5-45.2-50.4-48.4-92.7-1.7-22.7.7-46.7 7.1-71.2 6.8-26.1 17.9-51.5 33-75.5 15-24.1 33-45.2 53.4-62.8 19.2-16.6 39.7-29.2 60.9-37.6 39.4-15.7 76.5-14.6 104.5 2.9s45.2 50.4 48.4 92.7c1.8 22.6-.6 46.6-7 71.1" style="fill:#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="96" height="96">
<path d="M0 0 C3.96616708 2.87966696 6.94216973 6.71174376 8.75390625 11.2734375 C8.94921875 14.2890625 8.94921875 14.2890625 8.87890625 17.5234375 C8.86085937 18.60109375 8.8428125 19.67875 8.82421875 20.7890625 C8.80101562 21.60890625 8.7778125 22.42875 8.75390625 23.2734375 C9.86765625 23.2321875 10.98140625 23.1909375 12.12890625 23.1484375 C15.75390625 23.2734375 15.75390625 23.2734375 17.75390625 25.2734375 C17.94178314 28.33535881 18.00936994 31.30529877 17.984375 34.3671875 C17.98391678 35.2696521 17.98345856 36.1721167 17.98298645 37.10192871 C17.97996265 39.01181349 17.97207256 40.92169577 17.9597168 42.83154297 C17.94148066 45.76148172 17.93920695 48.69110422 17.93945312 51.62109375 C17.93453722 53.47396108 17.92870858 55.32682625 17.921875 57.1796875 C17.92076218 58.05991577 17.91964935 58.94014404 17.91850281 59.8470459 C17.86996885 66.04131231 17.86996885 66.04131231 16.75390625 68.2734375 C-1.72609375 68.2734375 -20.20609375 68.2734375 -39.24609375 68.2734375 C-40.95558713 64.85445075 -40.40930624 60.94182137 -40.4140625 57.1796875 C-40.4173909 56.2772229 -40.4207193 55.3747583 -40.42414856 54.44494629 C-40.42921569 52.53508006 -40.43155695 50.62520497 -40.43139648 48.71533203 C-40.43358486 45.78533449 -40.45174793 42.85571222 -40.47070312 39.92578125 C-40.47363664 38.07291796 -40.47562172 36.22005287 -40.4765625 34.3671875 C-40.48374802 33.48695923 -40.49093353 32.60673096 -40.49833679 31.6998291 C-40.47491923 25.50226298 -40.47491923 25.50226298 -38.24609375 23.2734375 C-34.62109375 23.1484375 -34.62109375 23.1484375 -31.24609375 23.2734375 C-31.30410156 22.5 -31.36210937 21.7265625 -31.421875 20.9296875 C-31.73940921 13.59918343 -31.17057807 8.85901698 -26.24609375 3.2734375 C-18.78730361 -3.81241313 -9.01893001 -5.1380571 0 0 Z M-19.24609375 15.2734375 C-19.24609375 17.9134375 -19.24609375 20.5534375 -19.24609375 23.2734375 C-13.96609375 23.2734375 -8.68609375 23.2734375 -3.24609375 23.2734375 C-2.47540396 15.74874343 -2.47540396 15.74874343 -5.37109375 12.2734375 C-8.09392313 10.10082379 -8.09392313 10.10082379 -11.24609375 9.6484375 C-15.38792256 10.5113185 -16.80835846 11.83192885 -19.24609375 15.2734375 Z M-11.24609375 39.2734375 C-11.24609375 44.5534375 -11.24609375 49.8334375 -11.24609375 55.2734375 C-10.58609375 54.9434375 -9.92609375 54.6134375 -9.24609375 54.2734375 C-8.89430178 51.9428157 -8.56279196 49.60908683 -8.24609375 47.2734375 C-7.18359375 44.8359375 -7.18359375 44.8359375 -6.24609375 43.2734375 C-7.60933118 41.2334656 -7.60933118 41.2334656 -9.24609375 39.2734375 C-9.90609375 39.2734375 -10.56609375 39.2734375 -11.24609375 39.2734375 Z " fill="#10376E" transform="translate(59.24609375,27.7265625)"/>
<path d="M0 0 C1.12067871 0.01047363 2.24135742 0.02094727 3.39599609 0.03173828 C5.21357422 0.04140625 5.21357422 0.04140625 7.06787109 0.05126953 C8.33888672 0.06802734 9.60990234 0.08478516 10.91943359 0.10205078 C12.19818359 0.11107422 13.47693359 0.12009766 14.79443359 0.12939453 C17.96128526 0.15302775 21.12776783 0.18597449 24.29443359 0.22705078 C24.4024195 2.66573246 24.48169949 5.09964212 24.54443359 7.53955078 C24.59470703 8.57499023 24.59470703 8.57499023 24.64599609 9.63134766 C24.68505859 11.65283203 24.68505859 11.65283203 24.29443359 15.22705078 C22.27880859 17.20751953 22.27880859 17.20751953 20.29443359 18.22705078 C20.7362031 21.76120687 21.41676358 24.50596315 22.85693359 27.78955078 C24.29443359 32.22705078 24.29443359 32.22705078 24.29443359 45.22705078 C15.05443359 45.22705078 5.81443359 45.22705078 -3.70556641 45.22705078 C-5.41505978 41.80806403 -4.8687789 37.89543466 -4.87353516 34.13330078 C-4.87686356 33.23083618 -4.88019196 32.32837158 -4.88362122 31.39855957 C-4.88868835 29.48869334 -4.89102961 27.57881825 -4.89086914 25.66894531 C-4.89305752 22.73894777 -4.91122059 19.8093255 -4.93017578 16.87939453 C-4.9331093 15.02653124 -4.93509437 13.17366616 -4.93603516 11.32080078 C-4.94322067 10.44057251 -4.95040619 9.56034424 -4.95780945 8.65344238 C-4.92667455 0.41344589 -4.92667455 0.41344589 0 0 Z " fill="#0F3161" transform="translate(23.70556640625,50.77294921875)"/>
<path d="M0 0 C3.95863415 2.87419762 6.96698604 6.71040909 8.75390625 11.2734375 C8.94921875 14.5078125 8.94921875 14.5078125 8.87890625 18.0234375 C8.86085937 19.19390625 8.8428125 20.364375 8.82421875 21.5703125 C8.80101562 22.46234375 8.7778125 23.354375 8.75390625 24.2734375 C4.79390625 24.2734375 0.83390625 24.2734375 -3.24609375 24.2734375 C-3.32859375 22.8709375 -3.41109375 21.4684375 -3.49609375 20.0234375 C-3.85974947 16.86013089 -4.14926646 15.42047154 -5.93359375 12.7109375 C-8.99476118 10.80804964 -10.701589 10.68268671 -14.24609375 11.2734375 C-16.74330199 12.6018971 -16.74330199 12.6018971 -18.24609375 15.2734375 C-18.86751236 18.3074225 -19.06322936 21.16474289 -19.24609375 24.2734375 C-23.20609375 24.2734375 -27.16609375 24.2734375 -31.24609375 24.2734375 C-32.16565531 10.86827339 -32.16565531 10.86827339 -27.671875 4.85546875 C-20.21832025 -3.41491392 -9.84160472 -5.60673239 0 0 Z " fill="#F3A006" transform="translate(59.24609375,27.7265625)"/>
<path d="M0 0 C0 3.96 0 7.92 0 12 C-1.4540625 12.680625 -1.4540625 12.680625 -2.9375 13.375 C-6.07079582 14.65786221 -6.07079582 14.65786221 -7 17 C-7.22467831 18.53668865 -7.40796805 20.07968054 -7.5625 21.625 C-7.706875 23.06875 -7.85125 24.5125 -8 26 C-11.96 26 -15.92 26 -20 26 C-20.91789085 12.61919109 -20.91789085 12.61919109 -16.4375 6.5859375 C-11.06401953 0.65758677 -7.616909 -0.12285337 0 0 Z " fill="#D58C05" transform="translate(48,26)"/>
<path d="M0 0 C0.75 1.6875 0.75 1.6875 1 4 C-1.81446105 8.64997913 -6.25914236 12.37140566 -11 15 C-14 14.875 -14 14.875 -16 14 C-16.80078125 11.796875 -16.80078125 11.796875 -17 9 C-14.98046875 6.578125 -14.98046875 6.578125 -12.1875 4.25 C-10.81658203 3.08210938 -10.81658203 3.08210938 -9.41796875 1.890625 C-5.71909434 -1.00154821 -4.50480579 -1.06692769 0 0 Z " fill="#DA9004" transform="translate(82,13)"/>
<path d="M0 0 C5.0875582 0.44824301 7.6553513 1.85561989 11.4375 5.25 C12.32308594 6.01828125 13.20867188 6.7865625 14.12109375 7.578125 C16 10 16 10 15.78515625 12.796875 C15.52605469 13.52390625 15.26695313 14.2509375 15 15 C12.859375 15.76171875 12.859375 15.76171875 10 16 C7.140625 14.26953125 7.140625 14.26953125 4.25 11.8125 C3.28578125 11.01457031 2.3215625 10.21664063 1.328125 9.39453125 C-1.92423854 6.04939896 -1.92423854 6.04939896 -2 3 C-1.34 2.01 -0.68 1.02 0 0 Z " fill="#DA8F04" transform="translate(15,12)"/>
<path d="M0 0 C3 0.125 3 0.125 4 1.125 C4.07325168 3.98767567 4.09238205 6.825719 4.0625 9.6875 C4.05798828 10.49380859 4.05347656 11.30011719 4.04882812 12.13085938 C4.03700518 14.12893756 4.01906914 16.12697783 4 18.125 C1.6875 19.25 1.6875 19.25 -1 20.125 C-1.99 19.465 -2.98 18.805 -4 18.125 C-4.24817926 15.19116664 -4.32643881 12.48867315 -4.25 9.5625 C-4.24484375 8.76908203 -4.2396875 7.97566406 -4.234375 7.15820312 C-4.13892499 0.17245521 -4.13892499 0.17245521 0 0 Z " fill="#F5A105" transform="translate(48,-0.125)"/>
<path d="M0 0 C0.79792969 0.00386719 1.59585938 0.00773437 2.41796875 0.01171875 C3.21589844 0.00785156 4.01382813 0.00398438 4.8359375 0 C10.79136108 0.01011108 10.79136108 0.01011108 11.91796875 1.13671875 C11.95877658 3.13630239 11.96051231 5.13717129 11.91796875 7.13671875 C8.88367198 8.65386714 5.74195208 8.28035791 2.41796875 8.26171875 C1.72832031 8.26558594 1.03867188 8.26945312 0.328125 8.2734375 C-4.82869832 8.26338521 -4.82869832 8.26338521 -7.08203125 7.13671875 C-7.12457481 5.13717129 -7.12283908 3.13630239 -7.08203125 1.13671875 C-5.19650595 -0.74880655 -2.50434294 0.00425186 0 0 Z " fill="#DA9104" transform="translate(81.08203125,38.86328125)"/>
<path d="M0 0 C0.79792969 0.00386719 1.59585937 0.00773437 2.41796875 0.01171875 C3.21589844 0.00785156 4.01382812 0.00398438 4.8359375 0 C10.79136108 0.01011108 10.79136108 0.01011108 11.91796875 1.13671875 C12.16796875 3.57421875 12.16796875 3.57421875 11.91796875 6.13671875 C8.21555593 8.60499397 6.25885765 8.37430901 1.85546875 8.32421875 C-0.01818359 8.31455078 -0.01818359 8.31455078 -1.9296875 8.3046875 C-5.08203125 8.13671875 -5.08203125 8.13671875 -7.08203125 7.13671875 C-7.12457481 5.13717129 -7.12283908 3.13630239 -7.08203125 1.13671875 C-5.19650595 -0.74880655 -2.50434294 0.00425186 0 0 Z " fill="#D98F04" transform="translate(10.08203125,38.86328125)"/>
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.62661079 5.37336228 -1.26467582 7.51333672 -4.8125 10.375 C-6.09060547 11.43847656 -6.09060547 11.43847656 -7.39453125 12.5234375 C-10 14 -10 14 -12.82421875 13.7265625 C-13.90123047 13.36691406 -13.90123047 13.36691406 -15 13 C-10.43369266 8.12927217 -5.25023461 4.10843602 0 0 Z " fill="#F4A005" transform="translate(81,14)"/>
<path d="M0 0 C0.99 0 1.98 0 3 0 C3 6.6 3 13.2 3 20 C1.68 19.34 0.36 18.68 -1 18 C-1.02684312 15.18743719 -1.04676188 12.37512759 -1.0625 9.5625 C-1.07087891 8.76005859 -1.07925781 7.95761719 -1.08789062 7.13085938 C-1.0965143 5.08704779 -1.0522815 3.04316098 -1 1 C-0.67 0.67 -0.34 0.34 0 0 Z " fill="#D88E04" transform="translate(45,0)"/>
<path d="M0 0 C5.23589631 0.4222497 7.61710401 1.95695114 11.375 5.5625 C12.24898437 6.38878906 13.12296875 7.21507812 14.0234375 8.06640625 C14.67570312 8.70449219 15.32796875 9.34257812 16 10 C15.67 11.32 15.34 12.64 15 14 C9.66666667 10 4.33333333 6 -1 2 C-0.67 1.34 -0.34 0.68 0 0 Z " fill="#F4A005" transform="translate(15,12)"/>
<path d="M0 0 C5.61 0 11.22 0 17 0 C17.33 0.99 17.66 1.98 18 3 C11.73 3 5.46 3 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#F4A005" transform="translate(75,39)"/>
<path d="M0 0 C5.61 0 11.22 0 17 0 C17.33 0.99 17.66 1.98 18 3 C11.73 3 5.46 3 -1 3 C-0.67 2.01 -0.34 1.02 0 0 Z " fill="#F4A005" transform="translate(4,39)"/>
<path d="M0 0 C0 5.61 0 11.22 0 17 C-2 16 -2 16 -3.0625 12.8125 C-3.371875 11.554375 -3.68125 10.29625 -4 9 C-4.268125 7.9275 -4.53625 6.855 -4.8125 5.75 C-5 3 -5 3 -3.6875 1.125 C-2 0 -2 0 0 0 Z " fill="#D6D7D9" transform="translate(48,66)"/>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -0,0 +1,10 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#29a34a" stroke="#29a34a">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg height="500" width="500" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h500v500c-165 0-330 0-500 0 0-165 0-330 0-500z" fill="#4d70d5"/><path d="m0 0h86v85h85v85h85v86c-28.05 0-56.1 0-85 0v85c-28.05 0-56.1 0-85 0 0-28.05 0-56.1 0-85-28.05 0-56.1 0-85 0v85c-28.38 0-56.76 0-86 0 0-28.38 0-56.76 0-86h85c0-28.05 0-56.1 0-85-27.72 0-55.44 0-84 0 0-28.05 0-56.1 0-85h84c0-28.05 0-56.1 0-85z" fill="#fefefe" transform="translate(175 76)"/><path d="m0 0h1v84h85v1c-28.05 0-56.1 0-85 0v85c-28.38 0-56.76 0-86 0 0-28.38 0-56.76 0-86h85c0-27.72 0-55.44 0-84zm-84 85v84h84c0-27.72 0-55.44 0-84-27.72 0-55.44 0-84 0z" fill="#bcc9ef" transform="translate(175 247)"/></svg>

After

Width:  |  Height:  |  Size: 666 B

View File

@@ -0,0 +1,33 @@
import { Languages } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
export default function LocaleToggle() {
const { i18n } = useTranslation()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Languages className="h-[1.2rem] w-[1.2rem] dark:text-white" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{Object.keys(i18n.store.data).map(key => (
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>
{i18n.store.data[key].name as string}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -1,4 +1,5 @@
import { Moon, Sun } from "lucide-react";
import { useTranslation } from 'react-i18next'
import { Button } from "@/components/ui/button";
import {
@@ -11,6 +12,8 @@ import { useTheme } from "./ThemeProvider";
export function ThemeToggle() {
const { setTheme } = useTheme();
const { t } = useTranslation();
return (
<DropdownMenu>
@@ -23,13 +26,13 @@ export function ThemeToggle() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
{t('theme.light')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
{t('theme.dark')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
{t('theme.system')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -14,7 +15,7 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, AliyunConfig } from "@/domain/access";
import { Access, accessFormType, AliyunConfig, getUsageByConfigType } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
@@ -23,18 +24,21 @@ import { PbErrorData } from "@/domain/base";
const AccessAliyunForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
accessKeyId: z.string().min(1).max(64),
accessSecretId: z.string().min(1).max(64),
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
accessSecretId: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: AliyunConfig = {
@@ -47,7 +51,7 @@ const AccessAliyunForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "aliyun",
accessKeyId: config.accessKeyId,
accessSecretId: config.accessKeySecret,
@@ -59,6 +63,7 @@ const AccessAliyunForm = ({
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
accessKeyId: data.accessKeyId,
accessKeySecret: data.accessSecretId,
@@ -66,6 +71,7 @@ const AccessAliyunForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -73,10 +79,11 @@ const AccessAliyunForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
}
console.log(req);
addAccess(req);
} catch (e) {
const err = e as ClientResponseError;
@@ -110,9 +117,9 @@ const AccessAliyunForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -125,7 +132,7 @@ const AccessAliyunForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -140,7 +147,7 @@ const AccessAliyunForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -155,9 +162,9 @@ const AccessAliyunForm = ({
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>AccessKeyId</FormLabel>
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
<FormControl>
<Input placeholder="请输入AccessKeyId" {...field} />
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -170,9 +177,9 @@ const AccessAliyunForm = ({
name="accessSecretId"
render={({ field }) => (
<FormItem>
<FormLabel>AccessKeySecret</FormLabel>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
<FormControl>
<Input placeholder="请输入AccessKeySecret" {...field} />
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -183,7 +190,7 @@ const AccessAliyunForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -14,7 +15,7 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, CloudflareConfig } from "@/domain/access";
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -22,17 +23,20 @@ import { PbErrorData } from "@/domain/base";
const AccessCloudflareForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
dnsApiToken: z.string().min(1).max(64),
dnsApiToken: z.string().min(1, 'access.form.cloud.dns.api.token.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: CloudflareConfig = {
@@ -44,7 +48,7 @@ const AccessCloudflareForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "cloudflare",
dnsApiToken: config.dnsApiToken,
},
@@ -56,12 +60,14 @@ const AccessCloudflareForm = ({
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
dnsApiToken: data.dnsApiToken,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -69,7 +75,7 @@ const AccessCloudflareForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
}
@@ -105,9 +111,9 @@ const AccessCloudflareForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -120,7 +126,7 @@ const AccessCloudflareForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -135,7 +141,7 @@ const AccessCloudflareForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -150,9 +156,9 @@ const AccessCloudflareForm = ({
name="dnsApiToken"
render={({ field }) => (
<FormItem>
<FormLabel>CLOUD_DNS_API_TOKEN</FormLabel>
<FormLabel>{t('access.form.cloud.dns.api.token')}</FormLabel>
<FormControl>
<Input placeholder="请输入CLOUD_DNS_API_TOKEN" {...field} />
<Input placeholder={t('access.form.cloud.dns.api.token.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -161,7 +167,7 @@ const AccessCloudflareForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -5,19 +5,15 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useState } from "react";
import AccessTencentForm from "./AccessTencentForm";
import { Label } from "../ui/label";
import { useTranslation } from "react-i18next";
import { Access, accessTypeMap } from "@/domain/access";
import AccessAliyunForm from "./AccessAliyunForm";
import { cn } from "@/lib/utils";
import AccessSSHForm from "./AccessSSHForm";
import WebhookForm from "./AccessWebhookFrom";
import { Label } from "../ui/label";
import {
Select,
SelectContent,
@@ -27,12 +23,19 @@ import {
SelectTrigger,
SelectValue,
} from "../ui/select";
import AccessCloudflareForm from "./AccessCloudflareForm";
import AccessAliyunForm from "./AccessAliyunForm";
import AccessTencentForm from "./AccessTencentForm";
import AccessHuaweicloudForm from "./AccessHuaweicloudForm";
import AccessQiniuForm from "./AccessQiniuForm";
import AccessCloudflareForm from "./AccessCloudflareForm";
import AccessNamesiloForm from "./AccessNamesiloForm";
import AccessGodaddyFrom from "./AccessGodaddyForm";
import AccessLocalForm from "./AccessLocalForm";
import AccessSSHForm from "./AccessSSHForm";
import AccessWebhookForm from "./AccessWebhookFrom";
type TargetConfigEditProps = {
op: "add" | "edit";
op: "add" | "edit" | "copy";
className?: string;
trigger: React.ReactNode;
data?: Access;
@@ -44,6 +47,7 @@ export function AccessEdit({
className,
}: TargetConfigEditProps) {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const typeKeys = Array.from(accessTypeMap.keys());
@@ -51,50 +55,33 @@ export function AccessEdit({
let form = <> </>;
switch (configType) {
case "tencent":
form = (
<AccessTencentForm
data={data}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "aliyun":
form = (
<AccessAliyunForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "ssh":
case "tencent":
form = (
<AccessSSHForm
<AccessTencentForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "webhook":
case "huaweicloud":
form = (
<WebhookForm
data={data}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "cloudflare":
form = (
<AccessCloudflareForm
<AccessHuaweicloudForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
@@ -105,6 +92,18 @@ export function AccessEdit({
form = (
<AccessQiniuForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "cloudflare":
form = (
<AccessCloudflareForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
@@ -115,6 +114,51 @@ export function AccessEdit({
form = (
<AccessNamesiloForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "godaddy":
form = (
<AccessGodaddyFrom
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "local":
form = (
<AccessLocalForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "ssh":
form = (
<AccessSSHForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
/>
);
break;
case "webhook":
form = (
<AccessWebhookForm
data={data}
op={op}
onAfterReq={() => {
setOpen(false);
}}
@@ -134,46 +178,53 @@ export function AccessEdit({
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{op == "add" ? "添加" : "编辑"}</DialogTitle>
<DialogTitle>
{op == "add"
? t("access.add")
: op == "edit"
? t("access.edit")
: t("access.copy")}
</DialogTitle>
</DialogHeader>
<div className="container">
<Label></Label>
<ScrollArea className="max-h-[80vh]">
<div className="container py-3">
<Label>{t("access.type")}</Label>
<Select
onValueChange={(val) => {
console.log(val);
setConfigType(val);
}}
defaultValue={configType}
>
<SelectTrigger className="mt-3">
<SelectValue placeholder="请选择服务商" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel></SelectLabel>
{typeKeys.map((key) => (
<SelectItem value={key} key={key}>
<div
className={cn(
"flex items-center space-x-2 rounded cursor-pointer",
getOptionCls(key)
)}
>
<img
src={accessTypeMap.get(key)?.[1]}
className="h-6 w-6"
/>
<div>{accessTypeMap.get(key)?.[0]}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Select
onValueChange={(val) => {
setConfigType(val);
}}
defaultValue={configType}
>
<SelectTrigger className="mt-3">
<SelectValue placeholder={t("access.type.not.empty")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t("access.type")}</SelectLabel>
{typeKeys.map((key) => (
<SelectItem value={key} key={key}>
<div
className={cn(
"flex items-center space-x-2 rounded cursor-pointer",
getOptionCls(key)
)}
>
<img
src={accessTypeMap.get(key)?.[1]}
className="h-6 w-6"
/>
<div>{t(accessTypeMap.get(key)?.[0] || "")}</div>
</div>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
{form}
</div>
{form}
</div>
</ScrollArea>
</DialogContent>
</Dialog>
);

View File

@@ -0,0 +1,203 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
import z from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import {
Access,
accessFormType,
getUsageByConfigType,
GodaddyConfig,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
const AccessGodaddyFrom = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
apiKey: z.string().min(1, 'access.form.go.daddy.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
apiSecret: z.string().min(1, 'access.form.go.daddy.api.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: GodaddyConfig = {
apiKey: "",
apiSecret: "",
};
if (data) config = data.config as GodaddyConfig;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
configType: "godaddy",
apiKey: config.apiKey,
apiSecret: config.apiSecret,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log(data);
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
apiKey: data.apiKey,
apiSecret: data.apiSecret,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id && op == "edit") {
updateAccess(req);
return;
}
addAccess(req);
} catch (e) {
const err = e as ClientResponseError;
Object.entries(err.response.data as PbErrorData).forEach(
([key, value]) => {
form.setError(key as keyof z.infer<typeof formSchema>, {
type: "manual",
message: value.message,
});
}
);
}
};
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
console.log(e);
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.go.daddy.api.key')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.go.daddy.api.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="apiSecret"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.go.daddy.api.secret')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.go.daddy.api.secret.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>
</div>
</>
);
};
export default AccessGodaddyFrom;

View File

@@ -0,0 +1,121 @@
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { useConfig } from "@/providers/config";
import { update } from "@/repository/access_group";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
import { useState } from "react";
import { useTranslation } from "react-i18next";
type AccessGroupEditProps = {
className?: string;
trigger: React.ReactNode;
};
const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
const { reloadAccessGroups } = useConfig();
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const formSchema = z.object({
name: z.string().min(1, 'access.group.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
try {
await update({
name: data.name,
});
// 更新本地状态
reloadAccessGroups();
setOpen(false);
} catch (e) {
const err = e as ClientResponseError;
Object.entries(err.response.data as PbErrorData).forEach(
([key, value]) => {
form.setError(key as keyof z.infer<typeof formSchema>, {
type: "manual",
message: value.message,
});
}
);
}
};
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild className={cn(className)}>
{trigger}
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{t('access.group.add')}</DialogTitle>
</DialogHeader>
<div className="container py-3">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.group.name')}</FormLabel>
<FormControl>
<Input placeholder={t('access.group.name.not.empty')} {...field} type="text" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
);
};
export default AccessGroupEdit;

View File

@@ -0,0 +1,215 @@
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
import Show from "@/components/Show";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProviderInfo } from "@/domain/access";
import { getErrMessage } from "@/lib/error";
import { useConfig } from "@/providers/config";
import { remove } from "@/repository/access_group";
import { Group } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
const AccessGroupList = () => {
const {
config: { accessGroups },
reloadAccessGroups,
} = useConfig();
const { toast } = useToast();
const navigate = useNavigate();
const { t } = useTranslation();
const handleRemoveClick = async (id: string) => {
try {
await remove(id);
reloadAccessGroups();
} catch (e) {
toast({
title: t('delete.failed'),
description: getErrMessage(e),
variant: "destructive",
});
return;
}
};
const handleAddAccess = () => {
navigate("/access");
};
return (
<div className="mt-10">
<Show when={accessGroups.length == 0}>
<>
<div className="flex flex-col items-center mt-10">
<span className="bg-orange-100 p-5 rounded-full">
<Group size={40} className="text-primary" />
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
{t('access.group.domain.empty')}
</div>
<AccessGroupEdit
trigger={<Button>{t('access.group.add')}</Button>}
className="mt-3"
/>
</div>
</>
</Show>
<ScrollArea className="h-[75vh] overflow-hidden">
<div className="flex gap-5 flex-wrap">
{accessGroups.map((accessGroup) => (
<Card className="w-full md:w-[350px]">
<CardHeader>
<CardTitle>{accessGroup.name}</CardTitle>
<CardDescription>
{t('access.group.total', { total: accessGroup.expand ? accessGroup.expand.access.length : 0 })}
</CardDescription>
</CardHeader>
<CardContent className="min-h-[180px]">
{accessGroup.expand ? (
<>
{accessGroup.expand.access.slice(0, 3).map((access) => (
<div key={access.id} className="flex flex-col mb-3">
<div className="flex items-center">
<div className="">
<img
src={getProviderInfo(access.configType)![1]}
alt="provider"
className="w-8 h-8"
></img>
</div>
<div className="ml-3">
<div className="text-sm font-semibold text-gray-700 dark:text-gray-200">
{access.name}
</div>
<div className="text-xs text-muted-foreground">
{getProviderInfo(access.configType)![0]}
</div>
</div>
</div>
</div>
))}
</>
) : (
<>
<div className="flex text-gray-700 dark:text-gray-200 items-center">
<div>
<Group size={40} />
</div>
<div className="ml-2">
{t('access.group.empty')}
</div>
</div>
</>
)}
</CardContent>
<CardFooter>
<div className="flex justify-end w-full">
<Show
when={
accessGroup.expand && accessGroup.expand.access.length > 0
? true
: false
}
>
<div>
<Button
size="sm"
variant={"link"}
onClick={() => {
navigate(
`/access?accessGroupId=${accessGroup.id}&tab=access`,
{
replace: true,
}
);
}}
>
{t('access.all')}
</Button>
</div>
</Show>
<Show
when={
!accessGroup.expand ||
accessGroup.expand.access.length == 0
? true
: false
}
>
<div>
<Button size="sm" onClick={handleAddAccess}>
{t('access.add')}
</Button>
</div>
</Show>
<div className="ml-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}>
{t('delete')}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200">
{t('access.group.delete')}
</AlertDialogTitle>
<AlertDialogDescription>
{t('access.group.delete.confirm')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200">
{t('cancel')}
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
handleRemoveClick(
accessGroup.id ? accessGroup.id : ""
);
}}
>
{t('confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</CardFooter>
</Card>
))}
</div>
</ScrollArea>
</div>
);
};
export default AccessGroupList;

View File

@@ -0,0 +1,221 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
import z from "zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, HuaweicloudConfig, getUsageByConfigType } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
const AccessHuaweicloudForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
region: z.string().min(1, 'access.form.region.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
secretAccessKey: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: HuaweicloudConfig = {
region: "cn-north-1",
accessKeyId: "",
secretAccessKey: "",
};
if (data) config = data.config as HuaweicloudConfig;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
configType: "huaweicloud",
region: config.region,
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
region: data.region,
accessKeyId: data.accessKeyId,
secretAccessKey: data.secretAccessKey,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id && op == "edit") {
updateAccess(req);
return;
}
console.log(req);
addAccess(req);
} catch (e) {
const err = e as ClientResponseError;
Object.entries(err.response.data as PbErrorData).forEach(
([key, value]) => {
form.setError(key as keyof z.infer<typeof formSchema>, {
type: "manual",
message: value.message,
});
}
);
return;
}
};
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.region')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.region.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="secretAccessKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>
</div>
</>
);
};
export default AccessHuaweicloudForm;

View File

@@ -0,0 +1,228 @@
import {
Access,
accessFormType,
getUsageByConfigType,
LocalConfig,
SSHConfig,
} from "@/domain/access";
import { useConfig } from "@/providers/config";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useTranslation } from "react-i18next";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Textarea } from "../ui/textarea";
import { save } from "@/repository/access";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
const AccessLocalForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess, reloadAccessGroups } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
});
let config: LocalConfig = {
command: "sudo service nginx restart",
certPath: "/etc/nginx/ssl/certificate.crt",
keyPath: "/etc/nginx/ssl/private.key",
};
if (data) config = data.config as SSHConfig;
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
configType: "local",
certPath: config.certPath,
keyPath: config.keyPath,
command: config.command,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
command: data.command,
certPath: data.certPath,
keyPath: data.keyPath,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id && op == "edit") {
updateAccess(req);
} else {
addAccess(req);
}
reloadAccessGroups();
} catch (e) {
const err = e as ClientResponseError;
Object.entries(err.response.data as PbErrorData).forEach(
([key, value]) => {
form.setError(key as keyof z.infer<typeof formSchema>, {
type: "manual",
message: value.message,
});
}
);
return;
}
};
return (
<>
<div className="max-w-[35em] mx-auto mt-10">
<Form {...form}>
<form
onSubmit={(e) => {
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-3"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="certPath"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="keyPath"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="command"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
<FormControl>
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>
</div>
</>
);
};
export default AccessLocalForm;

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -14,7 +15,7 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, NamesiloConfig } from "@/domain/access";
import { Access, accessFormType, getUsageByConfigType, NamesiloConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -22,17 +23,20 @@ import { PbErrorData } from "@/domain/base";
const AccessNamesiloForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
apiKey: z.string().min(1).max(64),
apiKey: z.string().min(1, 'access.form.namesilo.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: NamesiloConfig = {
@@ -44,24 +48,25 @@ const AccessNamesiloForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "namesilo",
apiKey: config.apiKey,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log(data);
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
apiKey: data.apiKey,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -69,7 +74,7 @@ const AccessNamesiloForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
}
@@ -105,9 +110,9 @@ const AccessNamesiloForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -120,7 +125,7 @@ const AccessNamesiloForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -135,7 +140,7 @@ const AccessNamesiloForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -150,9 +155,9 @@ const AccessNamesiloForm = ({
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>NAMESILO_API_KEY</FormLabel>
<FormLabel>{t('access.form.namesilo.api.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入NAMESILO_API_KEY" {...field} />
<Input placeholder={t('access.form.namesilo.api.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -161,7 +166,7 @@ const AccessNamesiloForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -14,7 +15,7 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, QiniuConfig } from "@/domain/access";
import { Access, accessFormType, getUsageByConfigType, QiniuConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
@@ -23,18 +24,21 @@ import { PbErrorData } from "@/domain/base";
const AccessQiniuForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
accessKey: z.string().min(1).max(64),
secretKey: z.string().min(1).max(64),
accessKey: z.string().min(1, 'access.form.access.key.not.empty').max(64),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64),
});
let config: QiniuConfig = {
@@ -47,7 +51,7 @@ const AccessQiniuForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "qiniu",
accessKey: config.accessKey,
secretKey: config.secretKey,
@@ -59,6 +63,7 @@ const AccessQiniuForm = ({
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
accessKey: data.accessKey,
secretKey: data.secretKey,
@@ -66,6 +71,7 @@ const AccessQiniuForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -73,7 +79,7 @@ const AccessQiniuForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
}
@@ -110,9 +116,9 @@ const AccessQiniuForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -125,7 +131,7 @@ const AccessQiniuForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -140,7 +146,7 @@ const AccessQiniuForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -155,9 +161,9 @@ const AccessQiniuForm = ({
name="accessKey"
render={({ field }) => (
<FormItem>
<FormLabel>AccessKey</FormLabel>
<FormLabel>{t('access.form.access.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入AccessKey" {...field} />
<Input placeholder={t('access.form.access.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -170,9 +176,9 @@ const AccessQiniuForm = ({
name="secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>SecretKey</FormLabel>
<FormLabel>{t('access.form.secret.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入SecretKey" {...field} />
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -183,7 +189,7 @@ const AccessQiniuForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,4 +1,9 @@
import { Access, accessFormType, SSHConfig } from "@/domain/access";
import {
Access,
accessFormType,
getUsageByConfigType,
SSHConfig,
} from "@/domain/access";
import { useConfig } from "@/providers/config";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
@@ -18,30 +23,70 @@ import { save } from "@/repository/access";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
import { readFileContent } from "@/lib/file";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { cn } from "@/lib/utils";
import AccessGroupEdit from "./AccessGroupEdit";
import { Plus } from "lucide-react";
import { updateById } from "@/repository/access_group";
const AccessSSHForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const {
addAccess,
updateAccess,
reloadAccessGroups,
config: { accessGroups },
} = useConfig();
const fileInputRef = useRef<HTMLInputElement | null>(null);
const [fileName, setFileName] = useState("");
const { t } = useTranslation();
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),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
host: z.string().ip({
message: "请输入合法的IP地址",
}),
port: z.string().min(1).max(5),
username: z.string().min(1).max(64),
password: z.string().min(0).max(64),
key: z.string().min(0).max(20480),
keyFile: z.string().optional(),
command: z.string().min(1).max(2048),
certPath: z.string().min(0).max(2048),
keyPath: z.string().min(0).max(2048),
host: z.string().refine(
(str) => {
return ipReg.test(str) || domainReg.test(str);
},
{
message: "zod.rule.ssh.host",
}
),
group: z.string().optional(),
port: z.string().min(1, 'access.form.ssh.port.not.empty').max(5, t('zod.rule.string.max', { max: 5 })),
username: z.string().min(1, 'username.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
password: z.string().min(0, 'password.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
key: z.string().min(0, 'access.form.ssh.key.not.empty').max(20480, t('zod.rule.string.max', { max: 20480 })),
keyFile: z.any().optional(),
preCommand: z.string().min(0).max(2048, t('zod.rule.string.max', { max: 2048 })).optional(),
command: z.string().min(1, 'access.form.ssh.command.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
certPath: z.string().min(0, 'access.form.ssh.cert.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
keyPath: z.string().min(0, 'access.form.ssh.key.path.not.empty').max(2048, t('zod.rule.string.max', { max: 2048 })),
});
let config: SSHConfig = {
@@ -51,6 +96,7 @@ const AccessSSHForm = ({
password: "",
key: "",
keyFile: "",
preCommand: "",
command: "sudo service nginx restart",
certPath: "/etc/nginx/ssl/certificate.crt",
keyPath: "/etc/nginx/ssl/private.key",
@@ -61,8 +107,9 @@ const AccessSSHForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "ssh",
group: data?.group,
host: config.host,
port: config.port,
username: config.username,
@@ -72,15 +119,20 @@ const AccessSSHForm = ({
certPath: config.certPath,
keyPath: config.keyPath,
command: config.command,
preCommand: config.preCommand,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log(data);
let group = data.group;
if (group == "emptyId") group = "";
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
group: group,
config: {
host: data.host,
port: data.port,
@@ -88,12 +140,14 @@ const AccessSSHForm = ({
password: data.password,
key: data.key,
command: data.command,
preCommand: data.preCommand,
certPath: data.certPath,
keyPath: data.keyPath,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -101,11 +155,30 @@ const AccessSSHForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
} else {
addAccess(req);
}
addAccess(req);
// 同步更新授权组
if (group != originGroup) {
if (originGroup) {
await updateById({
id: originGroup,
"access-": req.id,
});
}
if (group) {
await updateById({
id: group,
"access+": req.id,
});
}
}
reloadAccessGroups();
} catch (e) {
const err = e as ClientResponseError;
@@ -127,9 +200,16 @@ const AccessSSHForm = ({
) => {
const file = event.target.files?.[0];
if (!file) return;
const content = await readFileContent(file);
const savedFile = file;
setFileName(savedFile.name);
const content = await readFileContent(savedFile);
form.setValue("key", content);
form.setValue("keyFile", "");
};
const handleSelectFileClick = () => {
console.log(fileInputRef.current);
fileInputRef.current?.click();
};
return (
@@ -148,9 +228,70 @@ const AccessSSHForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="group"
render={({ field }) => (
<FormItem>
<FormLabel className="w-full flex justify-between">
<div>{t('access.form.ssh.group.label')}</div>
<AccessGroupEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t('add')}
</div>
}
/>
</FormLabel>
<FormControl>
<Select
{...field}
value={field.value}
defaultValue="emptyId"
onValueChange={(value) => {
form.setValue("group", value);
}}
>
<SelectTrigger>
<SelectValue placeholder={t('access.group.not.empty')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="emptyId">
<div
className={cn(
"flex items-center space-x-2 rounded cursor-pointer"
)}
>
--
</div>
</SelectItem>
{accessGroups.map((item) => (
<SelectItem
value={item.id ? item.id : ""}
key={item.id}
>
<div
className={cn(
"flex items-center space-x-2 rounded cursor-pointer"
)}
>
{item.name}
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
@@ -163,7 +304,7 @@ const AccessSSHForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -178,7 +319,7 @@ const AccessSSHForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -193,9 +334,9 @@ const AccessSSHForm = ({
name="host"
render={({ field }) => (
<FormItem className="grow">
<FormLabel>IP</FormLabel>
<FormLabel>{t('access.form.ssh.host')}</FormLabel>
<FormControl>
<Input placeholder="请输入Host" {...field} />
<Input placeholder={t('access.form.ssh.host.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -208,10 +349,10 @@ const AccessSSHForm = ({
name="port"
render={({ field }) => (
<FormItem>
<FormLabel>SSH端口</FormLabel>
<FormLabel>{t('access.form.ssh.port')}</FormLabel>
<FormControl>
<Input
placeholder="请输入Port"
placeholder={t('access.form.ssh.port.not.empty')}
{...field}
type="number"
/>
@@ -228,9 +369,9 @@ const AccessSSHForm = ({
name="username"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('username')}</FormLabel>
<FormControl>
<Input placeholder="请输入用户名" {...field} />
<Input placeholder={t('username.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -243,10 +384,10 @@ const AccessSSHForm = ({
name="password"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('password')}</FormLabel>
<FormControl>
<Input
placeholder="请输入密码"
placeholder={t('password.not.empty')}
{...field}
type="password"
/>
@@ -262,9 +403,9 @@ const AccessSSHForm = ({
name="key"
render={({ field }) => (
<FormItem hidden>
<FormLabel>Key使</FormLabel>
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入Key" {...field} />
<Input placeholder={t('access.form.ssh.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -277,14 +418,28 @@ const AccessSSHForm = ({
name="keyFile"
render={({ field }) => (
<FormItem>
<FormLabel>Key使</FormLabel>
<FormLabel>{t('access.form.ssh.key')}</FormLabel>
<FormControl>
<Input
placeholder="请输入Key"
{...field}
type="file"
onChange={handleFileChange}
/>
<div>
<Button
type={"button"}
variant={"secondary"}
size={"sm"}
className="w-48"
onClick={handleSelectFileClick}
>
{fileName ? fileName : t('access.form.ssh.key.file.not.empty')}
</Button>
<Input
placeholder={t('access.form.ssh.key.not.empty')}
{...field}
ref={fileInputRef}
className="hidden"
hidden
type="file"
onChange={handleFileChange}
/>
</div>
</FormControl>
<FormMessage />
@@ -297,9 +452,9 @@ const AccessSSHForm = ({
name="certPath"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('access.form.ssh.cert.path')}</FormLabel>
<FormControl>
<Input placeholder="请输入证书上传路径" {...field} />
<Input placeholder={t('access.form.ssh.cert.path.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -312,9 +467,24 @@ const AccessSSHForm = ({
name="keyPath"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('access.form.ssh.key.path')}</FormLabel>
<FormControl>
<Input placeholder="请输入私钥上传路径" {...field} />
<Input placeholder={t('access.form.ssh.key.path.not.empty')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="preCommand"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.ssh.pre.command')}</FormLabel>
<FormControl>
<Textarea placeholder={t('access.form.ssh.pre.command.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -327,9 +497,9 @@ const AccessSSHForm = ({
name="command"
render={({ field }) => (
<FormItem>
<FormLabel>Command</FormLabel>
<FormLabel>{t('access.form.ssh.command')}</FormLabel>
<FormControl>
<Textarea placeholder="请输入要执行的命令" {...field} />
<Textarea placeholder={t('access.form.ssh.command.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -340,7 +510,7 @@ const AccessSSHForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -14,7 +15,7 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, TencentConfig } from "@/domain/access";
import { Access, accessFormType, getUsageByConfigType, TencentConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -22,18 +23,21 @@ import { PbErrorData } from "@/domain/base";
const AccessTencentForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
secretId: z.string().min(1).max(64),
secretKey: z.string().min(1).max(64),
secretId: z.string().min(1, 'access.form.secret.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
});
let config: TencentConfig = {
@@ -46,7 +50,7 @@ const AccessTencentForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "tencent",
secretId: config.secretId,
secretKey: config.secretKey,
@@ -58,6 +62,7 @@ const AccessTencentForm = ({
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
secretId: data.secretId,
secretKey: data.secretKey,
@@ -65,6 +70,7 @@ const AccessTencentForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -72,7 +78,7 @@ const AccessTencentForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
}
@@ -107,9 +113,9 @@ const AccessTencentForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -122,7 +128,7 @@ const AccessTencentForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -137,7 +143,7 @@ const AccessTencentForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -152,9 +158,9 @@ const AccessTencentForm = ({
name="secretId"
render={({ field }) => (
<FormItem>
<FormLabel>SecretId</FormLabel>
<FormLabel>{t('access.form.secret.id')}</FormLabel>
<FormControl>
<Input placeholder="请输入SecretId" {...field} />
<Input placeholder={t('access.form.secret.id.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -167,9 +173,9 @@ const AccessTencentForm = ({
name="secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>SecretKey</FormLabel>
<FormLabel>{t('access.form.secret.key')}</FormLabel>
<FormControl>
<Input placeholder="请输入SecretKey" {...field} />
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -178,7 +184,7 @@ const AccessTencentForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,5 +1,6 @@
import { Input } from "@/components/ui/input";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -14,7 +15,7 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, WebhookConfig } from "@/domain/access";
import { Access, accessFormType, getUsageByConfigType, WebhookConfig } from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -22,17 +23,20 @@ import { PbErrorData } from "@/domain/base";
const WebhookForm = ({
data,
op,
onAfterReq,
}: {
data?: Access;
op: "add" | "edit" | "copy";
onAfterReq: () => void;
}) => {
const { addAccess, updateAccess } = useConfig();
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1).max(64),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
configType: accessFormType,
url: z.string().url(),
url: z.string().url('zod.rule.url'),
});
let config: WebhookConfig = {
@@ -44,24 +48,25 @@ const WebhookForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name,
name: data?.name || '',
configType: "webhook",
url: config.url,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log(data);
const req: Access = {
id: data.id as string,
name: data.name,
configType: data.configType,
usage: getUsageByConfigType(data.configType),
config: {
url: data.url,
},
};
try {
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -69,7 +74,7 @@ const WebhookForm = ({
req.id = rs.id;
req.created = rs.created;
req.updated = rs.updated;
if (data.id) {
if (data.id && op == "edit") {
updateAccess(req);
return;
}
@@ -105,9 +110,9 @@ const WebhookForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input placeholder="请输入授权名称" {...field} />
<Input placeholder={t('access.form.name.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -120,7 +125,7 @@ const WebhookForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -135,7 +140,7 @@ const WebhookForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel></FormLabel>
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -150,9 +155,9 @@ const WebhookForm = ({
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook Url</FormLabel>
<FormLabel>{t('access.form.webhook.url')}</FormLabel>
<FormControl>
<Input placeholder="请输入Webhook Url" {...field} />
<Input placeholder={t('access.form.webhook.url.not.empty')} {...field} />
</FormControl>
<FormMessage />
@@ -161,7 +166,7 @@ const WebhookForm = ({
/>
<div className="flex justify-end">
<Button type="submit"></Button>
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>

View File

@@ -1,3 +1,8 @@
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils"
import { Separator } from "../ui/separator";
type DeployProgressProps = {
@@ -6,80 +11,63 @@ type DeployProgressProps = {
};
const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
let rs = <> </>;
const { t } = useTranslation();
let step = 0;
if (phase === "check") {
if (phaseSuccess) {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
} else {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-red-600"> </div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
}
step = 1
} else if (phase === "apply") {
step = 2
} else if (phase === "deploy") {
step = 3
}
if (phase === "apply") {
if (phaseSuccess) {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
} else {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-red-600"></div>
<Separator className="h-1 grow" />
<div className="text-xs text-nowrap text-muted-foreground"></div>
</div>
);
}
}
if (phase === "deploy") {
if (phaseSuccess) {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
</div>
);
} else {
rs = (
<div className="flex items-center">
<div className="text-xs text-nowrap text-green-600"> </div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-green-600"></div>
<Separator className="h-1 grow bg-green-600" />
<div className="text-xs text-nowrap text-red-600"></div>
</div>
);
}
}
return rs;
return (
<div className="flex items-center">
<div className={
cn(
"text-xs text-nowrap",
step === 1 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 1 ? "text-green-600" : "",
)
}>
{t('deploy.progress.check')}
</div>
<Separator className={
cn(
"h-1 grow max-w-[60px]",
step > 1 ? "bg-green-600" : "",
)
} />
<div className={
cn(
"text-xs text-nowrap",
step < 2 ? "text-muted-foreground" : "",
step === 2 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 2 ? "text-green-600" : "",
)
}>
{t('deploy.progress.apply')}
</div>
<Separator className={
cn(
"h-1 grow max-w-[60px]",
step > 2 ? "bg-green-600" : "",
)
} />
<div className={
cn(
"text-xs text-nowrap",
step < 3 ? "text-muted-foreground" : "",
step === 3 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 3 ? "text-green-600" : "",
)
}>
{t('deploy.progress.deploy')}
</div>
</div>
)
};
export default DeployProgress;

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

@@ -0,0 +1,144 @@
import { cn } from "@/lib/utils";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { useConfig } from "@/providers/config";
import { update } from "@/repository/settings";
import { ClientResponseError } from "pocketbase";
import { PbErrorData } from "@/domain/base";
import { useState } from "react";
import { EmailsSetting } from "@/domain/settings";
type EmailsEditProps = {
className?: string;
trigger: React.ReactNode;
};
const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const {
config: { emails },
setEmails,
} = useConfig();
const [open, setOpen] = useState(false);
const { t } = useTranslation();
const formSchema = z.object({
email: z.string().email("email.valid.message"),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
form.setError("email", {
message: "email.already.exist",
});
return;
}
// 保存到 config
const newEmails = [...(emails.content as EmailsSetting).emails, data.email];
try {
const resp = await update({
...emails,
name: "emails",
content: {
emails: newEmails,
},
});
// 更新本地状态
setEmails(resp);
// 关闭弹窗
form.reset();
form.clearErrors();
setOpen(false);
} catch (e) {
const err = e as ClientResponseError;
Object.entries(err.response.data as PbErrorData).forEach(
([key, value]) => {
form.setError(key as keyof z.infer<typeof formSchema>, {
type: "manual",
message: value.message,
});
}
);
}
};
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild className={cn(className)}>
{trigger}
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{t('email.add')}</DialogTitle>
</DialogHeader>
<div className="container py-3">
<Form {...form}>
<form
onSubmit={(e) => {
console.log(e);
e.stopPropagation();
form.handleSubmit(onSubmit)(e);
}}
className="space-y-8"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{t('email')}</FormLabel>
<FormControl>
<Input placeholder={t('email.not.empty.message')} {...field} type="email" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
</div>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
);
};
export default EmailsEdit;

View File

@@ -0,0 +1,245 @@
import { cn } from "@/lib/utils";
import Show from "../Show";
import { useCallback, useEffect, useMemo, useState } from "react";
import { FormControl, FormLabel } from "../ui/form";
import { Button } from "../ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Input } from "../ui/input";
import { z } from "zod";
import { useTranslation } from "react-i18next";
import { Edit, Plus, Trash2 } from "lucide-react";
type StringListProps = {
className?: string;
value: string;
valueType?: "domain" | "ip";
onValueChange: (value: string) => void;
};
const titles: Record<string, string> = {
domain: "domain",
ip: "IP",
};
const StringList = ({
value,
className,
onValueChange,
valueType = "domain",
}: StringListProps) => {
const [list, setList] = useState<string[]>([]);
const { t } = useTranslation();
useMemo(() => {
if (value) {
setList(value.split(";"));
}
}, [value]);
useEffect(() => {
const changeList = () => {
onValueChange(list.join(";"));
};
changeList();
}, [list]);
const addVal = (val: string) => {
if (list.includes(val)) {
return;
}
setList([...list, val]);
};
const editVal = (index: number, val: string) => {
const newList = [...list];
newList[index] = val;
setList(newList);
};
const onRemoveClick = (index: number) => {
const newList = [...list];
newList.splice(index, 1);
setList(newList);
};
return (
<>
<div className={cn(className)}>
<FormLabel className="flex justify-between items-center">
<div>{t(titles[valueType])}</div>
<Show when={list.length > 0}>
<StringEdit
op="add"
onValueChange={(val: string) => {
addVal(val);
}}
valueType={valueType}
value={""}
trigger={
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div>
</div>
}
/>
</Show>
</FormLabel>
<FormControl>
<Show
when={list.length > 0}
fallback={
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
<div className="text-muted-foreground"></div>
<StringEdit
value={""}
trigger={t("add")}
onValueChange={addVal}
valueType={valueType}
/>
</div>
}
>
<div className="border rounded-md p-3 text-sm mt-2 text-gray-700 space-y-2 dark:text-white dark:border-stone-700 dark:bg-stone-950">
{list.map((item, index) => (
<div key={index} className="flex justify-between items-center">
<div>{item}</div>
<div className="flex space-x-2">
<StringEdit
op="edit"
valueType={valueType}
trigger={
<Edit
size={16}
className="cursor-pointer text-gray-600 dark:text-white"
/>
}
value={item}
onValueChange={(val: string) => {
editVal(index, val);
}}
/>
<Trash2
size={16}
className="cursor-pointer"
onClick={() => {
onRemoveClick(index);
}}
/>
</div>
</div>
))}
</div>
</Show>
</FormControl>
</div>
</>
);
};
export default StringList;
type ValueType = "domain" | "ip";
type StringEditProps = {
value: string;
trigger: React.ReactNode;
onValueChange: (value: string) => void;
valueType: ValueType;
op?: "add" | "edit";
};
const StringEdit = ({
trigger,
value,
onValueChange,
op = "add",
valueType,
}: StringEditProps) => {
const [currentValue, setCurrentValue] = useState<string>("");
const [open, setOpen] = useState<boolean>(false);
const [error, setError] = useState<string>("");
const { t } = useTranslation();
useEffect(() => {
setCurrentValue(value);
}, [value]);
const domainSchema = z
.string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"),
});
const ipSchema = z.string().ip({ message: t("ip.not.empty.verify.message") });
const schedules: Record<ValueType, z.ZodString> = {
domain: domainSchema,
ip: ipSchema,
};
const onSaveClick = useCallback(() => {
const schema = schedules[valueType];
const resp = schema.safeParse(currentValue);
if (!resp.success) {
setError(JSON.parse(resp.error.message)[0].message);
return;
}
setCurrentValue("");
setOpen(false);
setError("");
onValueChange(currentValue);
}, [currentValue]);
return (
<Dialog
open={open}
onOpenChange={(open) => {
setOpen(open);
}}
>
<DialogTrigger className="text-primary">{trigger}</DialogTrigger>
<DialogContent className="dark:text-white">
<DialogHeader>
<DialogTitle className="dark:text-white">
{t(titles[valueType])}
</DialogTitle>
</DialogHeader>
<Input
value={currentValue}
className="dark:text-white"
onChange={(e) => {
setCurrentValue(e.target.value);
}}
/>
<Show when={error.length > 0}>
<div className="text-red-500 text-sm">{error}</div>
</Show>
<DialogFooter>
<Button
onClick={() => {
onSaveClick();
}}
>
{op === "add" ? t("add") : t("confirm")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -0,0 +1,34 @@
import { BookOpen } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Separator } from "../ui/separator";
import { version } from "@/domain/version";
const Version = () => {
const { t } = useTranslation()
return (
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
<div className=""></div>
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
<a
href="https://docs.certimate.me"
target="_blank"
className="flex items-center"
>
<BookOpen size={16} />
<div className="ml-1">{t('document')}</div>
</a>
<Separator orientation="vertical" className="mx-2" />
<a
href="https://github.com/usual2970/certimate/releases"
target="_blank"
>
{version}
</a>
</div>
</div>
);
};
export default Version;

View File

@@ -0,0 +1,148 @@
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";
import { useTranslation } from 'react-i18next'
type DingTalkSetting = {
id: string;
name: string;
data: NotifyChannelDingTalk;
};
const DingTalk = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
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: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${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={t('access.form.ding.access.token.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">{t('setting.notify.config.enable')}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t('save')}
</Button>
</div>
</div>
);
};
export default DingTalk;

View File

@@ -0,0 +1,106 @@
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";
import { useTranslation } from 'react-i18next'
const NotifyTemplate = () => {
const [id, setId] = useState("");
const [templates, setTemplates] = useState<NotifyTemplateT[]>([
defaultNotifyTemplate,
]);
const { toast } = useToast();
const { t } = useTranslation();
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: t('save.succeed'),
description: t('setting.notify.template.save.succeed'),
});
};
return (
<div>
<Input
value={templates[0].title}
onChange={(e) => {
handleTitleChange(e.target.value);
}}
/>
<div className="text-muted-foreground text-sm mt-1">
{t('setting.notify.template.variables.tips.title')}
</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">
{t('setting.notify.template.variables.tips.content')}
</div>
<div className="flex justify-end mt-2">
<Button onClick={handleSaveClick}>{t('save')}</Button>
</div>
</div>
);
};
export default NotifyTemplate;

View File

@@ -0,0 +1,149 @@
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";
import { useTranslation } from "react-i18next";
type TelegramSetting = {
id: string;
name: string;
data: NotifyChannelTelegram;
};
const Telegram = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
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: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${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">{t('setting.notify.config.enable')}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t('save')}
</Button>
</div>
</div>
);
};
export default Telegram;

View File

@@ -0,0 +1,144 @@
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";
import { useTranslation } from 'react-i18next'
type WebhookSetting = {
id: string;
name: string;
data: NotifyChannelWebhook;
};
const Webhook = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
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: t('save.failed'),
description: t('setting.notify.config.save.failed.url.not.valid'),
variant: "destructive",
});
return;
}
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
webhook: {
...webhook.data,
},
},
});
setChannels(resp);
toast({
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${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">{t('setting.notify.config.enable')}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t('save')}
</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

@@ -12,6 +12,7 @@ import {
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { useTranslation } from "react-i18next"
const Form = FormProvider
@@ -145,7 +146,9 @@ const FormMessage = React.forwardRef<
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
const { t } = useTranslation()
const body = error ? t(String(error?.message)) : children
if (!body) {
return null

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

@@ -3,6 +3,7 @@ import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
import { useTranslation } from "react-i18next";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
@@ -62,33 +63,41 @@ PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span></span>
</PaginationLink>
);
}: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation()
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>{t('pagination.prev')}</span>
</PaginationLink>
)
};
PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span></span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
}: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation()
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>{t('pagination.next')}</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
};
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({

View File

@@ -0,0 +1,53 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -1,76 +1,106 @@
import { z } from "zod";
export const accessTypeMap: Map<string, [string, string]> = new Map([
["tencent", ["腾讯云", "/imgs/providers/tencent.svg"]],
["aliyun", ["阿里云", "/imgs/providers/aliyun.svg"]],
["cloudflare", ["Cloudflare", "/imgs/providers/cloudflare.svg"]],
["namesilo", ["Namesilo", "/imgs/providers/namesilo.svg"]],
["qiniu", ["七牛云", "/imgs/providers/qiniu.svg"]],
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
["webhook", ["Webhook", "/imgs/providers/webhook.svg"]],
["aliyun", ["aliyun", "/imgs/providers/aliyun.svg"]],
["tencent", ["tencent", "/imgs/providers/tencent.svg"]],
["huaweicloud", ["huaweicloud", "/imgs/providers/huaweicloud.svg"]],
["qiniu", ["qiniu", "/imgs/providers/qiniu.svg"]],
["cloudflare", ["cloudflare", "/imgs/providers/cloudflare.svg"]],
["namesilo", ["namesilo", "/imgs/providers/namesilo.svg"]],
["godaddy", ["go.daddy", "/imgs/providers/godaddy.svg"]],
["local", ["local.deployment", "/imgs/providers/local.svg"]],
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
]);
export const getProviderInfo = (t: string) => {
return accessTypeMap.get(t);
};
export const accessFormType = z.union(
[
z.literal("aliyun"),
z.literal("tencent"),
z.literal("huaweicloud"),
z.literal("qiniu"),
z.literal("cloudflare"),
z.literal("namesilo"),
z.literal("godaddy"),
z.literal("local"),
z.literal("ssh"),
z.literal("webhook"),
z.literal("cloudflare"),
z.literal("qiniu"),
z.literal("namesilo"),
],
{ message: "请选择云服务商" }
{ message: "access.not.empty" }
);
type AccessUsage = "apply" | "deploy" | "all";
export type Access = {
id: string;
name: string;
configType: string;
usage: AccessUsage;
group?: string;
config:
| TencentConfig
| AliyunConfig
| SSHConfig
| WebhookConfig
| CloudflareConfig
| TencentConfig
| HuaweicloudConfig
| QiniuConfig
| NamesiloConfig;
| CloudflareConfig
| NamesiloConfig
| GodaddyConfig
| LocalConfig
| SSHConfig
| WebhookConfig;
deleted?: string;
created?: string;
updated?: string;
};
export type QiniuConfig = {
accessKey: string;
secretKey: string;
};
export type WebhookConfig = {
url: string;
};
export type CloudflareConfig = {
dnsApiToken: string;
};
export type TencentConfig = {
secretId: string;
secretKey: string;
};
export type AliyunConfig = {
accessKeyId: string;
accessKeySecret: string;
};
export type TencentConfig = {
secretId: string;
secretKey: string;
};
export type HuaweicloudConfig = {
region: string;
accessKeyId: string;
secretAccessKey: string;
};
export type QiniuConfig = {
accessKey: string;
secretKey: string;
};
export type CloudflareConfig = {
dnsApiToken: string;
};
export type NamesiloConfig = {
apiKey: string;
};
export type GodaddyConfig = {
apiKey: string;
apiSecret: string;
};
export type LocalConfig = {
command: string;
certPath: string;
keyPath: string;
};
export type SSHConfig = {
host: string;
port: string;
preCommand?: string;
command: string;
username: string;
password?: string;
@@ -79,3 +109,30 @@ export type SSHConfig = {
certPath: string;
keyPath: string;
};
export type WebhookConfig = {
url: string;
};
export const getUsageByConfigType = (configType: string): AccessUsage => {
switch (configType) {
case "aliyun":
case "tencent":
case "huaweicloud":
return "all";
case "qiniu":
case "local":
case "ssh":
case "webhook":
return "deploy";
case "cloudflare":
case "namesilo":
case "godaddy":
return "apply";
default:
return "all";
}
};

View File

@@ -0,0 +1,10 @@
import { Access } from "./access";
export type AccessGroup = {
id?: string;
name?: string;
access?: string[];
expand?: {
access: Access[];
};
};

View File

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

View File

@@ -3,15 +3,20 @@ import { Deployment, Pahse } from "./deployment";
export type Domain = {
id: string;
domain: string;
email?: string;
crontab: string;
access: string;
targetAccess: string;
targetAccess?: string;
targetType: string;
expiredAt?: string;
phase?: Pahse;
phaseSuccess?: boolean;
lastDeployedAt?: string;
variables?: string;
nameservers?: string;
group?: string;
enabled?: boolean;
deployed?: boolean;
created?: string;
updated?: string;
deleted?: string;
@@ -21,6 +26,22 @@ export type Domain = {
expand?: {
lastDeployment?: Deployment;
};
applyConfig?: ApplyConfig;
deployConfig?: DeployConfig[];
};
export type DeployConfig = {
access: string;
type: string;
config?: Record<string, string>;
};
export type ApplyConfig = {
access: string;
email: string;
timeout?: number;
nameservers?: string;
};
export type Statistic = {
@@ -35,12 +56,14 @@ 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"]],
["tencent-cdn", ["腾讯云-CDN", "/imgs/providers/tencent.svg"]],
["ssh", ["SSH部署", "/imgs/providers/ssh.svg"]],
["qiniu-cdn", ["七牛云-CDN", "/imgs/providers/qiniu.svg"]],
["webhook", ["Webhook", "/imgs/providers/webhook.svg"]],
["aliyun-cdn", ["aliyun.cdn", "/imgs/providers/aliyun.svg"]],
["aliyun-oss", ["aliyun.oss", "/imgs/providers/aliyun.svg"]],
["aliyun-dcdn", ["aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
["tencent-cdn", ["tencent.cdn", "/imgs/providers/tencent.svg"]],
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
["qiniu-cdn", ["qiniu.cdn", "/imgs/providers/qiniu.svg"]],
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
["local", ["local.deployment", "/imgs/providers/local.svg"]],
]);
export const targetTypeKeys = Array.from(targetTypeMap.keys());

66
ui/src/domain/settings.ts Normal file
View File

@@ -0,0 +1,66 @@
export type Setting = {
id?: string;
name?: string;
content?:
| EmailsSetting
| NotifyTemplates
| NotifyChannels
| SSLProviderSetting;
};
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},请保持关注!",
};
export type SSLProvider = "letsencrypt" | "zerossl";
export type SSLProviderSetting = {
provider: SSLProvider;
config: {
[key: string]: {
[key: string]: string;
};
};
};

1
ui/src/domain/version.ts Normal file
View File

@@ -0,0 +1 @@
export const version = "Certimate v0.1.19";

22
ui/src/i18n/index.ts Normal file
View File

@@ -0,0 +1,22 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import resources from './locales'
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'zh',
debug: true,
interpolation: {
escapeValue: false,
},
backend: {
loadPath: '/locales/{{lng}}.json',
}
});
export default i18n;

219
ui/src/i18n/locales/en.json Normal file
View File

@@ -0,0 +1,219 @@
{
"ca": "Certificate Authority",
"username": "Username",
"username.not.empty": "Please enter username",
"password": "Password",
"password.not.empty": "Please enter password",
"email": "Email",
"logout": "Logout",
"setting": "Settings",
"account": "Account",
"template": "Template",
"save": "Save",
"no.data": "No data available",
"status": "Status",
"operation": "Operation",
"enable": "Enable",
"disable": "Disable",
"deploy": "Deploy",
"download": "Download",
"delete": "Delete",
"cancel": "Cancel",
"confirm": "Confirm",
"edit": "Edit",
"copy": "Copy",
"succeed": "Successful",
"add": "Add",
"document": "Document",
"variables": "Variables",
"dns": "Domain Name Server",
"name": "Name",
"create.time": "CreateTime",
"update.time": "UpdateTime",
"created.in": "Created in",
"updated.in": "Updated in",
"basic.setting": "Basic Settings",
"advanced.setting": "Advanced Settings",
"operation.succeed": "Operation Successful",
"save.succeed": "Save Successful",
"save.failed": "Save Failed",
"update.succeed": "Update Successful",
"update.failed": "Update Failed",
"delete.failed": "Delete Failed",
"ding.talk": "Ding Talk",
"telegram": "Telegram",
"webhook": "Webhook",
"local.deployment": "Local Deployment",
"tencent": "Tencent",
"tencent.cdn": "Tencent-CDN",
"aliyun": "Alibaba Cloud",
"aliyun.cdn": "Alibaba Cloud-CDN",
"aliyun.oss": "Alibaba Cloud-OSS",
"aliyun.dcdn": "Alibaba Cloud-DCDN",
"huaweicloud": "Huawei Cloud",
"qiniu": "Qiniu",
"qiniu.cdn": "Qiniu-CDN",
"cloudflare": "Cloudflare",
"namesilo": "Namesilo",
"go.daddy": "GoDaddy",
"ssh": "SSH Deployment",
"zod.rule.string.max": "Please enter no more than {{max}} characters",
"zod.rule.url": "Please enter a valid URL",
"zod.rule.ssh.host": "Please enter the correct domain name or IP",
"login.submit": "Log In",
"login.username.no.empty.message": "Please enter a valid email address",
"login.password.length.message": "Password should be at least 10 characters",
"menu.auth.management": "Authorization Management",
"theme.light": "Light",
"theme.dark": "Dark",
"theme.system": "System",
"dashboard": "Dashboard",
"dashboard.all": "All",
"dashboard.near.expired": "About to Expire",
"dashboard.enabled": "Enabled",
"dashboard.not.enabled": "Not Enabled",
"dashboard.unit": "Unit",
"deployment.log.name": "Deployment History",
"deployment.log.empty": "You have not created any deployments yet, please add a domain to start deployment!",
"deployment.log.status": "Status",
"deployment.log.stage": "Stage",
"deployment.log.last.execution.time": "Last Execution Time",
"deployment.log.detail.button.text": "Log",
"deployment.log.detail": "Deployment Details",
"pagination.next": "Next",
"pagination.prev": "Previous",
"domain": "Domain",
"domain.add": "Add Domain",
"domain.edit":"Edit Domain",
"domain.delete": "Delete Domain",
"domain.not.empty.verify.message": "Please enter domain",
"domain.management.name": "Domain List",
"domain.management.start.deploy.succeed.tips": "Deployment initiated, please check the deployment log later.",
"domain.management.execution.failed": "Execution Failed",
"domain.management.execution.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
"domain.management.empty": "Please add a domain to start deploying the certificate.",
"domain.management.expiry.date": "Validity Period",
"domain.management.expiry.date1": "Valid for {{date}} days",
"domain.management.expiry.date2": "Expiry on {{date}}",
"domain.management.last.execution.time": "Last Execution Time",
"domain.management.last.execution.status": "Last Execution Status",
"domain.management.last.execution.stage": "Last Execution Stage",
"domain.management.enable": "Enable",
"domain.management.start.deploying": "Deploy Now",
"domain.management.forced.deployment": "Force Deployment",
"domain.management.delete.confirm": "Are you sure you want to delete this domain?",
"domain.management.edit.title": "Edit Domain",
"domain.management.edit.dns.access.label": "DNS Provider Authorization Configuration",
"domain.management.edit.dns.access.not.empty.message": "Please select DNS provider authorization configuration",
"domain.management.edit.access.label": "Provider Authorization Configuration",
"domain.management.edit.access.not.empty.message": "Please select authorization configuration",
"domain.management.edit.target.type": "Deployment Service Type",
"domain.management.edit.target.type.not.empty.message": "Please select deployment service type",
"domain.management.edit.succeed.tips": "Successful domain editing",
"domain.management.edit.target.access": "Deployment Service Provider Authorization Configuration",
"domain.management.edit.target.access.content.label": "Provider Authorization Configuration",
"domain.management.edit.target.access.not.empty.message": "Please select authorization configuration",
"domain.management.edit.target.access.verify.msg": "At least one of the deployment authorization and deployment authorization group must be selected",
"domain.management.edit.group.label": "Deployment Configuration Group (used to deploy a domain certificate to multiple ssh hosts)",
"domain.management.edit.group.not.empty.message": "Please select group",
"domain.management.edit.email.not.empty.message": "Please select email",
"domain.management.edit.email.description": "(A email is required to apply for a certificate)",
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "Domain added successfully",
"email.add": "Add Email",
"email.list": "Email List",
"email.valid.message": "Please enter a valid email address",
"email.already.exist": "Email already exists",
"email.not.empty.message": "Please enter email",
"setting.notify.menu": "Notification Push",
"setting.submit": "Confirm Changes",
"setting.account.email.valid.message": "Please enter a valid email address",
"setting.account.email.placeholder": "Please enter email",
"setting.account.email.change.succeed": "Account email altered successfully",
"setting.account.email.change.failed": "Account email alteration failed",
"setting.account.log.back.in": "Please login again",
"setting.password.length.message": "Password should be at least 10 characters",
"setting.password.not.match": "Passwords do not match",
"setting.password.change.succeed": "Password changed successfully",
"setting.password.change.failed": "Password change failed",
"setting.password.current.password": "Current Password",
"setting.password.new.password": "New Password",
"setting.password.confirm.password": "Confirm Password",
"setting.notify.template.save.succeed": "Notification template saved successfully",
"setting.notify.template.variables.tips.title": "Optional variables, COUNT: number of expiring soon",
"setting.notify.template.variables.tips.content": "Optional variables, COUNT: number of expiring soon, DOMAINS: Domain list",
"setting.notify.config.enable": "Enable",
"setting.notify.config.save.succeed": "Configuration saved successfully",
"setting.notify.config.save.failed": "Configuration save failed",
"setting.notify.config.save.failed.url.not.valid": "Invalid Url format",
"setting.ca.not.empty": "Please select a Certificate Authority",
"setting.ca.eab_kid.not.empty": "Please enter EAB_KID",
"setting.ca.eab_hmac_key.not.empty": "Please enter EAB_HMAC_KEY.",
"setting.ca.eab_kid_hmac_key.not.empty": "Please enter EAB_KID and EAB_HMAC_KEY",
"deploy.progress.check": "Check",
"deploy.progress.apply": "Apply",
"deploy.progress.deploy": "Deploy",
"access.management": "Authorization Management",
"access.add": "Add Authorization",
"access.edit": "Edit Authorization",
"access.copy": "Copy Authorization",
"access.delete.confirm": "Are you sure you want to delete the deployment authorization?",
"access.all": "All Authorizations",
"access.list": "Authorization List",
"access.type": "Provider",
"access.type.not.empty": "Please select a provider",
"access.not.empty": "Please select a cloud provider",
"access.empty": "Please add authorization to start deploying certificate.",
"access.group.management": "Authorization Group Management",
"access.group.add": "Add Authorization Group",
"access.group.not.empty": "Please select a group",
"access.group.name": "Group Name",
"access.group.name.not.empty": "Please enter group name",
"access.group.delete": "Delete Group",
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
"access.group.domain.empty": "Please add a domain to start deploying the certificate.",
"access.group.empty": "No deployment authorization configuration yet, please add after starting use.",
"access.group.total": "Totally {{total}} deployment authorization configuration",
"access.form.name.not.empty": "Please enter authorization name",
"access.form.config.field": "Configuration Type",
"access.form.access.key.id": "AccessKeyId",
"access.form.access.key.id.not.empty": "Please enter AccessKeyId",
"access.form.access.key.secret": "AccessKeySecret",
"access.form.access.key.secret.not.empty": "Please enter AccessKeySecret",
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
"access.form.cloud.dns.api.token.not.empty": "Please enter CLOUD_DNS_API_TOKEN",
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
"access.form.go.daddy.api.key.not.empty": "Please enter GO_DADDY_API_KEY",
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
"access.form.go.daddy.api.secret.not.empty": "Please enter GO_DADDY_API_SECRET",
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
"access.form.namesilo.api.key.not.empty": "Please enter NAMESILO_API_KEY",
"access.form.secret.id": "SecretId",
"access.form.secret.id.not.empty": "Please enter SecretId",
"access.form.secret.key": "SecretKey",
"access.form.secret.key.not.empty": "Please enter SecretKey",
"access.form.access.key": "AccessKey",
"access.form.access.key.not.empty": "Please enter AccessKey",
"access.form.region": "Region",
"access.form.region.not.empty": "Please enter Region",
"access.form.webhook.url": "Webhook URL",
"access.form.webhook.url.not.empty": "Please enter Webhook URL",
"access.form.ssh.group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
"access.form.ssh.host": "Server Host",
"access.form.ssh.host.not.empty": "Please enter Host",
"access.form.ssh.port": "SSH Port",
"access.form.ssh.port.not.empty": "Please enter Port",
"access.form.ssh.key": "Key (Log in using certificate)",
"access.form.ssh.key.not.empty": "Please enter Key",
"access.form.ssh.key.file.not.empty": "Please select file",
"access.form.ssh.cert.path": "Certificate Save Path",
"access.form.ssh.cert.path.not.empty": "Please enter certificate save path",
"access.form.ssh.key.path": "Private Key Save Path",
"access.form.ssh.key.path.not.empty": "Please enter private key save path",
"access.form.ssh.pre.command": "Pre-deployment Command",
"access.form.ssh.pre.command.not.empty": "Command to be executed before deploying the certificate",
"access.form.ssh.command": "Command",
"access.form.ssh.command.not.empty": "Please enter command",
"access.form.ding.access.token.placeholder": "Signature for signed addition"
}

View File

@@ -0,0 +1,17 @@
import { Resource } from 'i18next'
import zh from './zh.json'
import en from './en.json'
const resources: Resource = {
zh: {
name: '简体中文',
translation: zh
},
en: {
name: 'English',
translation: en
}
}
export default resources;

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