mirror of
https://github.com/certd/certd.git
synced 2026-04-07 00:10:53 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b3fb7387d | ||
|
|
feac310caf | ||
|
|
d67ec3feb3 | ||
|
|
cf8abb4528 | ||
|
|
d66de26de4 | ||
|
|
7edf3f6147 | ||
|
|
2143dff2ae | ||
|
|
32c714d1b6 | ||
|
|
84e699ee24 | ||
|
|
7fdb572b8b | ||
|
|
cfd3b66be9 | ||
|
|
75c4f9dea8 | ||
|
|
a76a32230d | ||
|
|
0730f5ff4f | ||
|
|
c43d0a684c | ||
|
|
66f1eda6cf | ||
|
|
bf4d191c8b | ||
|
|
d76d56fcce | ||
|
|
251b0c58de | ||
|
|
073cca4e8e | ||
|
|
a4ad99f189 | ||
|
|
d37b910889 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 补充类型断言 ([2143dff](https://github.com/certd/certd/commit/2143dff2ae96e6a78bef9f0498e36f8cd9e6941f))
|
||||
* 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 ([32c714d](https://github.com/certd/certd/commit/32c714d1b6e68c71a74a7452115040c87ac4bfdc))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
|
||||
* 支持上传证书到华为云CCM ([cfd3b66](https://github.com/certd/certd/commit/cfd3b66be9ebf53a26693057e70ed60c3f116be9))
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复登录错误次数过多阻止再次登录逻辑 ([bf4d191](https://github.com/certd/certd/commit/bf4d191c8bd2f9209eb6768f662b9c77de99e998))
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
12
README.md
12
README.md
@@ -16,6 +16,9 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
||||
* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建,过程公开透明
|
||||
* 支持SQLite,PostgreSQL、MySQL数据库
|
||||
|
||||
|
||||

|
||||
|
||||
>
|
||||
> 流水线数量现已调整为无限制,欢迎大家使用
|
||||
>
|
||||
@@ -94,6 +97,12 @@ https://certd.handfree.work/
|
||||
|
||||

|
||||
|
||||
> 注意:
|
||||
> * 本应用存储的证书、授权信息等属于高度敏感数据,请做好安全防护
|
||||
> * 请务必使用HTTPS协议访问本应用,避免被中间人攻击
|
||||
> * 请务必使用web应用防火墙防护本应用,防止XSS、SQL注入等攻击
|
||||
> * 请务必做好服务器本身的安全防护,防止数据库泄露
|
||||
> * 请务必做好数据备份,避免数据丢失
|
||||
|
||||
## 五、 升级
|
||||
|
||||
@@ -197,8 +206,6 @@ https://afdian.com/a/greper
|
||||
1. 可加入发电专属群,可以获得作者一对一技术支持
|
||||
2. 您的需求我们将优先实现,并且将作为专业版功能提供
|
||||
3. 一年期专业版激活码
|
||||
4. 赠送国外免费服务器部署方案(0成本使用Certd,可能需要翻墙,不过现在性能越来越差了)
|
||||
|
||||
|
||||
专业版特权对比
|
||||
|
||||
@@ -229,6 +236,7 @@ https://afdian.com/a/greper
|
||||
* 未获得商业授权情况下,禁止任何对logo、版权信息及授权许可相关代码的修改。
|
||||
* 如需商业授权,请联系作者。
|
||||
|
||||
|
||||
## 十三、我的其他项目(求Star)
|
||||
|
||||
| 项目名称 | stars | 项目描述 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
01:02
|
||||
22:26
|
||||
|
||||
@@ -1,158 +1,166 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
import {defineConfig} from "vitepress";
|
||||
// Import lightbox plugin
|
||||
import lightbox from "vitepress-plugin-lightbox";
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "Certd",
|
||||
titleTemplate: "开源SSL证书管理工具,证书自动化申请部署,让你的网站证书永不过期",
|
||||
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具;证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
|
||||
markdown: {
|
||||
config: (md) => {
|
||||
// Use lightbox plugin
|
||||
md.use(lightbox, {});
|
||||
}
|
||||
},
|
||||
sitemap: {
|
||||
hostname: 'https://certd.docmirror.cn'
|
||||
},
|
||||
head: [
|
||||
// [
|
||||
// 'meta',
|
||||
// {
|
||||
// name: 'viewport',
|
||||
// content:
|
||||
// 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no',
|
||||
// },
|
||||
// ],
|
||||
["meta", {
|
||||
name: "keywords",
|
||||
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化,https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署,SSL平台,证书管理平台,证书流水线"
|
||||
}],
|
||||
// ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
|
||||
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
|
||||
// ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
|
||||
["link", { rel: "icon", href: "/static/logo/logo.svg" }]
|
||||
],
|
||||
themeConfig: {
|
||||
logo: "/static/logo/logo.svg",
|
||||
search: {
|
||||
provider: "local",
|
||||
options: {
|
||||
detailedView: true,
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: "搜索文档",
|
||||
buttonAriaLabel: "搜索文档"
|
||||
},
|
||||
modal: {
|
||||
noResultsText: "无法找到相关结果",
|
||||
resetButtonTitle: "清除查询条件",
|
||||
footer: {
|
||||
selectText: "选择",
|
||||
closeText: "关闭",
|
||||
navigateText: "切换"
|
||||
title: "Certd",
|
||||
titleTemplate: "开源SSL证书管理工具,证书自动化申请部署,让你的网站证书永不过期",
|
||||
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具;证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
|
||||
markdown: {
|
||||
config: (md) => {
|
||||
// Use lightbox plugin
|
||||
md.use(lightbox, {});
|
||||
}
|
||||
},
|
||||
sitemap: {
|
||||
hostname: 'https://certd.docmirror.cn'
|
||||
},
|
||||
head: [
|
||||
// [
|
||||
// 'meta',
|
||||
// {
|
||||
// name: 'viewport',
|
||||
// content:
|
||||
// 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no',
|
||||
// },
|
||||
// ],
|
||||
["meta", {
|
||||
name: "keywords",
|
||||
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化,https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署,SSL平台,证书管理平台,证书流水线"
|
||||
}],
|
||||
// ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
|
||||
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
|
||||
// ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
|
||||
["link", {rel: "icon", href: "/static/logo/logo.svg"}]
|
||||
],
|
||||
themeConfig: {
|
||||
logo: "/static/logo/logo.svg",
|
||||
search: {
|
||||
provider: "local",
|
||||
options: {
|
||||
detailedView: true,
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: "搜索文档",
|
||||
buttonAriaLabel: "搜索文档"
|
||||
},
|
||||
modal: {
|
||||
noResultsText: "无法找到相关结果",
|
||||
resetButtonTitle: "清除查询条件",
|
||||
footer: {
|
||||
selectText: "选择",
|
||||
closeText: "关闭",
|
||||
navigateText: "切换"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: "首页", link: "/" },
|
||||
{ text: "指南", link: "/guide/" },
|
||||
{ text: "插件", link: "/deploy/" },
|
||||
{ text: "商业版", link: "/comm/" },
|
||||
{ text: "Demo体验", link: "https://certd.handfree.work" }
|
||||
],
|
||||
sidebar: {
|
||||
"/guide/": [
|
||||
{
|
||||
text: "入门",
|
||||
items: [
|
||||
{ text: "简介", link: "/guide/" },
|
||||
{ text: "快速开始", link: "/guide/start.md" },
|
||||
{
|
||||
text: "私有化部署",
|
||||
items: [
|
||||
{ text: "docker部署", link: "/guide/install/docker/" },
|
||||
{ text: "宝塔面板部署", link: "/guide/install/baota/" },
|
||||
{ text: "1Panel部署", link: "/guide/install/1panel/" },
|
||||
{ text: "群晖部署", link: "/guide/use/synology/" },
|
||||
{ text: "源码部署", link: "/guide/install/source/" }
|
||||
]
|
||||
},
|
||||
{ text: "演示教程", link: "/guide/tutorial.md" },
|
||||
{ text: "版本升级", link: "/guide/install/upgrade.md" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "特性",
|
||||
items: [
|
||||
{ text: "CNAME代理校验", link: "/guide/feature/cname/index.md" },
|
||||
{ text: "插件列表", link: "/guide/plugins.md" },
|
||||
{ text: "多数据库支持", link: "/guide/install/database.md" },
|
||||
{ text: "开放接口", link: "/guide/open/index.md" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "常见问题",
|
||||
items: [
|
||||
{ text: "群晖证书部署", link: "/guide/use/synology/" },
|
||||
{ text: "腾讯云密钥获取", link: "/guide/use/tencent/" },
|
||||
{ text: "连接windows主机", link: "/guide/use/host/windows.md" },
|
||||
{ text: "Google EAB获取", link: "/guide/use/google/" },
|
||||
{ text: "阿里云相关", link: "/guide/use/aliyun/" },
|
||||
{ text: "忘记密码", link: "/guide/use/forgotpasswd/" },
|
||||
{ text: "数据备份", link: "/guide/use/backup/" },
|
||||
{ text: "Certd本身的证书更新", link: "/guide/use/https/index.md" },
|
||||
{ text: "js脚本插件使用", link: "/guide/use/custom-script/index.md" },
|
||||
{ text: "邮箱配置", link: "/guide/use/email/index.md" },
|
||||
{ text: "IPv6支持", link: "/guide/use/setting/ipv6.md" },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "其他",
|
||||
items: [
|
||||
{ text: "贡献代码", link: "/guide/development/index.md" },
|
||||
{ text: "更新日志", link: "/guide/changelogs/CHANGELOG.md" },
|
||||
{ text: "镜像说明", link: "/guide/image.md" },
|
||||
{ text: "联系我们", link: "/guide/contact/" },
|
||||
{ text: "捐赠", link: "/guide/donate/" },
|
||||
{ text: "开源协议", link: "/guide/license/" },
|
||||
{ text: "我的其他开源项目", link: "/guide/link/" },
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{text: "首页", link: "/"},
|
||||
{text: "指南", link: "/guide/"},
|
||||
{text: "Demo体验", link: "https://certd.handfree.work"}
|
||||
],
|
||||
sidebar: {
|
||||
"/guide/": [
|
||||
{
|
||||
text: "入门",
|
||||
items: [
|
||||
{text: "简介", link: "/guide/"},
|
||||
{text: "快速开始", link: "/guide/start.md"},
|
||||
{
|
||||
text: "私有化部署",
|
||||
items: [
|
||||
{text: "docker部署", link: "/guide/install/docker/"},
|
||||
{text: "宝塔面板部署", link: "/guide/install/baota/"},
|
||||
{text: "1Panel部署", link: "/guide/install/1panel/"},
|
||||
{text: "群晖部署", link: "/guide/use/synology/"},
|
||||
{text: "源码部署", link: "/guide/install/source/"}
|
||||
]
|
||||
},
|
||||
{text: "演示教程", link: "/guide/tutorial.md"},
|
||||
{text: "版本升级", link: "/guide/install/upgrade.md"}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "特性",
|
||||
items: [
|
||||
{text: "CNAME代理校验", link: "/guide/feature/cname/index.md"},
|
||||
{text: "插件列表", link: "/guide/plugins.md"},
|
||||
{text: "多数据库支持", link: "/guide/install/database.md"},
|
||||
{text: "开放接口", link: "/guide/open/index.md"},
|
||||
{
|
||||
text: "站点安全", items: [
|
||||
{text: "安全特性", link: "/guide/feature/safe"},
|
||||
{text: "站点隐藏", link: "/guide/feature/safe/hidden"},
|
||||
{text: "安全生产建议", link: "/guide/feature/safe/suggest"},
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
],
|
||||
"/deploy/":[
|
||||
{
|
||||
text: "部署证书插件",
|
||||
items: [
|
||||
{ text: "插件说明", link: "/deploy/index.md" },
|
||||
{ text: "部署到ESXi", link: "/deploy/ESXi/index.md" },
|
||||
]
|
||||
}
|
||||
],
|
||||
"/comm/": [
|
||||
{
|
||||
text: "商业版",
|
||||
items: [
|
||||
{ text: "支付宝配置", link: "/comm/payments/alipay.md" },
|
||||
{ text: "微信支付配置", link: "/comm/payments/wxpay.md" },
|
||||
{ text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md" },
|
||||
]
|
||||
}
|
||||
]
|
||||
,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "常见问题",
|
||||
items: [
|
||||
{text: "群晖证书部署", link: "/guide/use/synology/"},
|
||||
{text: "腾讯云密钥获取", link: "/guide/use/tencent/"},
|
||||
{text: "连接windows主机", link: "/guide/use/host/windows.md"},
|
||||
{text: "Google EAB获取", link: "/guide/use/google/"},
|
||||
{text: "阿里云相关", link: "/guide/use/aliyun/"},
|
||||
{text: "忘记密码", link: "/guide/use/forgotpasswd/"},
|
||||
{text: "数据备份", link: "/guide/use/backup/"},
|
||||
{text: "Certd本身的证书更新", link: "/guide/use/https/index.md"},
|
||||
{text: "js脚本插件使用", link: "/guide/use/custom-script/index.md"},
|
||||
{text: "邮箱配置", link: "/guide/use/email/index.md"},
|
||||
{text: "IPv6支持", link: "/guide/use/setting/ipv6.md"},
|
||||
{text: "其他插件使用", link: "/deploy/"},
|
||||
{text: "商业版说明", link: "/comm/"},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "其他",
|
||||
items: [
|
||||
{text: "贡献代码", link: "/guide/development/index.md"},
|
||||
{text: "更新日志", link: "/guide/changelogs/CHANGELOG.md"},
|
||||
{text: "镜像说明", link: "/guide/image.md"},
|
||||
{text: "联系我们", link: "/guide/contact/"},
|
||||
{text: "捐赠", link: "/guide/donate/"},
|
||||
{text: "开源协议", link: "/guide/license/"},
|
||||
{text: "我的其他开源项目", link: "/guide/link/"},
|
||||
|
||||
socialLinks: [
|
||||
{ icon: "github", link: "https://github.com/certd/certd" }
|
||||
],
|
||||
footer: {
|
||||
message: "Certd帮助文档 | <a href='https://beian.miit.gov.cn/' target='_blank'>粤ICP备14088435号</a> ",
|
||||
copyright: "Copyright © 2021-present <a href='https://handfree.work/' target='_blank'>handfree.work</a> "
|
||||
]
|
||||
}
|
||||
],
|
||||
"/deploy/": [
|
||||
{
|
||||
text: "部署证书插件",
|
||||
items: [
|
||||
{text: "插件说明", link: "/deploy/index.md"},
|
||||
{text: "部署到ESXi", link: "/deploy/ESXi/index.md"},
|
||||
]
|
||||
}
|
||||
],
|
||||
"/comm/": [
|
||||
{
|
||||
text: "商业版",
|
||||
items: [
|
||||
{text: "支付宝配置", link: "/comm/payments/alipay.md"},
|
||||
{text: "微信支付配置", link: "/comm/payments/wxpay.md"},
|
||||
{text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md"},
|
||||
]
|
||||
}
|
||||
]
|
||||
,
|
||||
},
|
||||
|
||||
socialLinks: [
|
||||
{icon: "github", link: "https://github.com/certd/certd"}
|
||||
],
|
||||
footer: {
|
||||
message: "Certd帮助文档 | <a href='https://beian.miit.gov.cn/' target='_blank'>粤ICP备14088435号</a> ",
|
||||
copyright: "Copyright © 2021-present <a href='https://handfree.work/' target='_blank'>handfree.work</a> "
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复登录错误次数过多阻止再次登录逻辑 ([bf4d191](https://github.com/certd/certd/commit/bf4d191c8bd2f9209eb6768f662b9c77de99e998))
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复某些情况下无法输出日志的bug ([70101bf](https://github.com/certd/certd/commit/70101bfa7ade65678d9202c804bbae2cb808b594))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 修复内置插件分页查询逻辑 ([a2710dd](https://github.com/certd/certd/commit/a2710ddc2525e4e637fd157f0180e6d3b801c8be))
|
||||
|
||||
## [1.33.1](https://github.com/certd/certd/compare/v1.33.0...v1.33.1) (2025-04-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
BIN
docs/guide/feature/safe/hidden/images/hidden1.png
Normal file
BIN
docs/guide/feature/safe/hidden/images/hidden1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/guide/feature/safe/hidden/images/hidden2.png
Normal file
BIN
docs/guide/feature/safe/hidden/images/hidden2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
25
docs/guide/feature/safe/hidden/index.md
Normal file
25
docs/guide/feature/safe/hidden/index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 站点隐藏
|
||||
|
||||
* 一般来说Certd设置好之后,很少需要访问。
|
||||
* 所以我们`平时`可以把`站点访问关闭`,需要的时候再打开,减少站点被攻击的风险
|
||||
|
||||
## 1、开启站点隐藏
|
||||
`系统管理->系统设置->安全设置->站点隐藏 `
|
||||
|
||||
|
||||

|
||||
|
||||
:::warning
|
||||
|
||||
注意保存好`解除地址`和`解除密码`
|
||||
|
||||
:::
|
||||
|
||||
## 2、临时关闭站点隐藏
|
||||
|
||||
访问上面的`解除地址`,输入`解除密码`,`临时解除`站点隐藏
|
||||
|
||||

|
||||
|
||||
## 3、忘记解除地址和解除密码怎么办
|
||||
登录服务器,在数据库平级的目录下创建`.unhidden`文件即可`临时解除`站点隐藏
|
||||
BIN
docs/guide/feature/safe/images/access.png
Normal file
BIN
docs/guide/feature/safe/images/access.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/guide/feature/safe/images/hidden.png
Normal file
BIN
docs/guide/feature/safe/images/hidden.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/guide/feature/safe/images/login.png
Normal file
BIN
docs/guide/feature/safe/images/login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
34
docs/guide/feature/safe/index.md
Normal file
34
docs/guide/feature/safe/index.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 站点安全特性
|
||||
|
||||
Certd 存储了证书以及授权等敏感数据,所以需要严格保障安全。
|
||||
我们非常重视您的数据安全,提供了以下安全特性
|
||||
|
||||
## 1、 授权数据加密存储【默认开启】
|
||||
* 所有的授权敏感字段会加密后存储
|
||||
* 每个用户独立维护授权数据,连管理员都无权查看
|
||||
|
||||

|
||||
星号部分为加密数据
|
||||
|
||||
## 2、 密码防爆破【默认开启】
|
||||
* 登录失败次数过多,账号将被锁定,最高24小时(重启服务可解除锁定)
|
||||
* 用户登录密码加密hash后存储,无法计算出密码明文
|
||||

|
||||
|
||||
## 3、站点隐藏【建议开启】
|
||||
* 一般来说Certd设置好之后,后续很少需要访问修改。
|
||||
* 所以我们平时可以把站点访问关闭,需要的时候再打开,减少站点被攻击的风险
|
||||
* 请前往 `系统管理->系统设置->安全设置->开启站点隐藏`
|
||||
* [站点隐藏设置说明](./hidden/)
|
||||

|
||||
|
||||
## 4、登录二次验证
|
||||
|
||||
待实现
|
||||
|
||||
## 5、数据库自动备份【建议开启】
|
||||
* [自动备份设置说明](../../use/backup/)
|
||||
|
||||
|
||||
## 更多安全生产建议
|
||||
[安全生产建议](./suggest.md)
|
||||
10
docs/guide/feature/safe/suggest.md
Normal file
10
docs/guide/feature/safe/suggest.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 安全生产建议
|
||||
|
||||
尽管`Cert`本身实现了很多安全特性,但`外部环境的安全`仍需要您来确保。
|
||||
请`务必`遵循如下建议做好安全防护
|
||||
|
||||
* 请`务必`使用`HTTPS协议`访问本应用,避免被中间人攻击
|
||||
* 请`务必`使用`web应用防火墙`防护本应用,防止XSS、SQL注入等攻击
|
||||
* 请`务必`做好`服务器本身`的安全防护,防止数据库泄露
|
||||
* 请`务必`做好[`数据备份`](../../use/backup/),避免数据丢失
|
||||
* 建议开启[`站点隐藏`](./hidden/)功能
|
||||
@@ -17,6 +17,8 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
|
||||
* 支持SQLite、Postgresql、MySQL数据库
|
||||
|
||||
|
||||

|
||||
|
||||
## 二、一些说明
|
||||
* 本项目申请证书过程遵循acme协议
|
||||
* 需要验证域名所有权,一般有两种方式
|
||||
|
||||
1
docs/images/intro/intro.svg
Normal file
1
docs/images/intro/intro.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 60 KiB |
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.33.2"
|
||||
"version": "1.33.4"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/publishlab/node-acme-client/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.33.3](https://github.com/publishlab/node-acme-client/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.33.2](https://github.com/publishlab/node-acme-client/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"module": "scr/index.js",
|
||||
"main": "src/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.7.2",
|
||||
@@ -67,5 +67,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1 +1 @@
|
||||
01:29
|
||||
23:45
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -44,5 +44,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import crypto, { BinaryToTextEncoding } from 'crypto';
|
||||
import crypto, { BinaryToTextEncoding } from "crypto";
|
||||
|
||||
function md5(data: string, digest: BinaryToTextEncoding = 'hex') {
|
||||
return crypto.createHash('md5').update(data).digest(digest);
|
||||
function md5(data: string, digest: BinaryToTextEncoding = "hex") {
|
||||
return crypto.createHash("md5").update(data).digest(digest);
|
||||
}
|
||||
function sha256(data: string, digest: BinaryToTextEncoding = 'hex') {
|
||||
return crypto.createHash('sha256').update(data).digest(digest);
|
||||
function sha256(data: string, digest: BinaryToTextEncoding = "hex") {
|
||||
return crypto.createHash("sha256").update(data).digest(digest);
|
||||
}
|
||||
|
||||
function hmacSha256(data: string, digest: BinaryToTextEncoding = 'base64') {
|
||||
return crypto.createHmac('sha256', data).update(Buffer.alloc(0)).digest(digest);
|
||||
function hmacSha256(data: string, digest: BinaryToTextEncoding = "base64") {
|
||||
return crypto.createHmac("sha256", data).update(Buffer.alloc(0)).digest(digest);
|
||||
}
|
||||
|
||||
function base64(data: string) {
|
||||
return Buffer.from(data).toString('base64');
|
||||
return Buffer.from(data).toString("base64");
|
||||
}
|
||||
export const hashUtils = {
|
||||
md5,
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -16,8 +16,8 @@
|
||||
"test": "mocha --loader=ts-node/esm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/plus-core": "^1.33.2",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/plus-core": "^1.33.4",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
@@ -43,5 +43,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
@@ -23,5 +23,5 @@
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -30,5 +30,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -60,5 +60,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -16,7 +16,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -31,5 +31,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -27,10 +27,10 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.33.2",
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/pipeline": "^1.33.2",
|
||||
"@certd/plus-core": "^1.33.2",
|
||||
"@certd/acme-client": "^1.33.4",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/plus-core": "^1.33.4",
|
||||
"@midwayjs/cache": "~3.14.0",
|
||||
"@midwayjs/core": "~3.20.3",
|
||||
"@midwayjs/i18n": "~3.20.3",
|
||||
@@ -61,5 +61,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -66,10 +66,15 @@ export const Constants = {
|
||||
code: 404,
|
||||
message: '页面/文件/资源不存在',
|
||||
},
|
||||
|
||||
preview: {
|
||||
code: 10001,
|
||||
message: '对不起,预览环境不允许修改此数据',
|
||||
},
|
||||
siteOff:{
|
||||
code: 10010,
|
||||
message: '站点已关闭',
|
||||
},
|
||||
openKeyError: {
|
||||
code: 20000,
|
||||
message: 'ApiToken错误',
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from './vip-exception.js';
|
||||
export * from './common-exception.js';
|
||||
export * from './not-found-exception.js';
|
||||
export * from './param-exception.js';
|
||||
export * from './site-off-exception.js';
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
/**
|
||||
*/
|
||||
export class SiteOffException extends BaseException {
|
||||
constructor(message) {
|
||||
super('SiteOffException', Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message);
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export class SysSuiteSetting extends BaseSettings {
|
||||
static __key__ = 'sys.suite';
|
||||
static __access__ = 'private';
|
||||
|
||||
enabled = false;
|
||||
enabled:boolean = false;
|
||||
|
||||
registerGift?: {
|
||||
productId: number;
|
||||
@@ -180,3 +180,25 @@ export class SysSuiteSetting extends BaseSettings {
|
||||
|
||||
intro?: string;
|
||||
}
|
||||
|
||||
|
||||
export type SiteHidden = {
|
||||
enabled: boolean;
|
||||
openPath?: string;
|
||||
//md5 hash 两次后保存
|
||||
openPassword?: string;
|
||||
autoHiddenTimes?: number;
|
||||
hiddenOpenApi?: boolean
|
||||
};
|
||||
export class SysSafeSetting extends BaseSettings {
|
||||
static __title__ = '站点安全设置';
|
||||
static __key__ = 'sys.safe';
|
||||
static __access__ = 'private';
|
||||
|
||||
// 站点隐藏
|
||||
hidden:SiteHidden = {
|
||||
enabled: false,
|
||||
hiddenOpenApi:false,
|
||||
autoHiddenTimes: 5,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -46,5 +46,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-cert
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -15,10 +15,10 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.33.2",
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/pipeline": "^1.33.2",
|
||||
"@certd/plugin-lib": "^1.33.2",
|
||||
"@certd/acme-client": "^1.33.4",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/plugin-lib": "^1.33.4",
|
||||
"@google-cloud/publicca": "^1.3.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -41,5 +41,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -16,8 +16,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/pipeline": "^1.33.2",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@kubernetes/client-node": "0.21.0",
|
||||
"ali-oss": "^6.21.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
@@ -48,5 +48,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "64244af2cce396be5e2831937e5e3024a22899ed"
|
||||
"gitHead": "0730f5ff4f176452f82489caa837b717e176fdca"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline";
|
||||
import { ConnectConfig } from "ssh2";
|
||||
import { SshClient } from "./ssh.js";
|
||||
|
||||
@IsAccess({
|
||||
name: "ssh",
|
||||
title: "主机登录授权",
|
||||
@@ -9,7 +6,7 @@ import { SshClient } from "./ssh.js";
|
||||
icon: "clarity:host-line",
|
||||
input: {},
|
||||
})
|
||||
export class SshAccess extends BaseAccess implements ConnectConfig {
|
||||
export class SshAccess extends BaseAccess {
|
||||
@AccessInput({
|
||||
title: "主机地址",
|
||||
component: {
|
||||
@@ -125,6 +122,7 @@ export class SshAccess extends BaseAccess implements ConnectConfig {
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const { SshClient } = await import("./ssh.js");
|
||||
const client = new SshClient(this.ctx.logger);
|
||||
|
||||
await client.exec({
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
// @ts-ignore
|
||||
import ssh2, { ConnectConfig, ExecOptions } from "ssh2";
|
||||
|
||||
import ssh2Constants from "ssh2/lib/protocol/constants.js";
|
||||
import path from "path";
|
||||
import * as _ from "lodash-es";
|
||||
import { isArray } from "lodash-es";
|
||||
import { ILogger } from "@certd/basic";
|
||||
import { SshAccess } from "./ssh-access.js";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { SocksClient } from "socks";
|
||||
import { SocksProxy, SocksProxyType } from "socks/typings/common/constants.js";
|
||||
|
||||
import fs from "fs";
|
||||
import { SocksProxyType } from "socks/typings/common/constants";
|
||||
|
||||
export type TransportItem = { localPath: string; remotePath: string };
|
||||
export interface SocksProxy {
|
||||
ipaddress?: string;
|
||||
host?: string;
|
||||
port: number;
|
||||
type: any;
|
||||
userId?: string;
|
||||
password?: string;
|
||||
custom_auth_method?: number;
|
||||
custom_auth_request_handler?: () => Promise<Buffer>;
|
||||
custom_auth_response_size?: number;
|
||||
custom_auth_response_handler?: (data: Buffer) => Promise<boolean>;
|
||||
}
|
||||
export type SshConnectConfig = {
|
||||
sock?: any;
|
||||
};
|
||||
|
||||
export class AsyncSsh2Client {
|
||||
conn: ssh2.Client;
|
||||
conn: any;
|
||||
logger: ILogger;
|
||||
connConf: SshAccess & ssh2.ConnectConfig;
|
||||
connConf: SshAccess & SshConnectConfig;
|
||||
windows = false;
|
||||
encoding: string;
|
||||
constructor(connConf: SshAccess, logger: ILogger) {
|
||||
@@ -40,7 +51,10 @@ export class AsyncSsh2Client {
|
||||
if (typeof this.connConf.port === "string") {
|
||||
this.connConf.port = parseInt(this.connConf.port);
|
||||
}
|
||||
const proxyOption: SocksProxy = this.parseSocksProxyFromUri(this.connConf.socksProxy);
|
||||
|
||||
const { SocksClient } = await import("socks");
|
||||
|
||||
const proxyOption = this.parseSocksProxyFromUri(this.connConf.socksProxy);
|
||||
const info = await SocksClient.createConnection({
|
||||
proxy: proxyOption,
|
||||
command: "connect",
|
||||
@@ -53,10 +67,12 @@ export class AsyncSsh2Client {
|
||||
this.connConf.sock = info.socket;
|
||||
}
|
||||
|
||||
const { SUPPORTED_KEX, SUPPORTED_SERVER_HOST_KEY, SUPPORTED_CIPHER, SUPPORTED_MAC } = ssh2Constants;
|
||||
const ssh2 = await import("ssh2");
|
||||
const ssh2Constants = await import("ssh2/lib/protocol/constants.js");
|
||||
const { SUPPORTED_KEX, SUPPORTED_SERVER_HOST_KEY, SUPPORTED_CIPHER, SUPPORTED_MAC } = ssh2Constants.default;
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const conn = new ssh2.Client();
|
||||
const conn = new ssh2.default.Client();
|
||||
conn
|
||||
.on("error", (err: any) => {
|
||||
this.logger.error("连接失败", err);
|
||||
@@ -197,6 +213,8 @@ export class AsyncSsh2Client {
|
||||
}
|
||||
|
||||
async shell(script: string | string[]): Promise<string> {
|
||||
const stripAnsiModule = await import("strip-ansi");
|
||||
const stripAnsi = stripAnsiModule.default;
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
this.logger.info(`执行shell脚本:[${this.connConf.host}][shell]: ` + script);
|
||||
this.conn.shell((err: Error, stream: any) => {
|
||||
@@ -449,7 +467,7 @@ export class SshClient {
|
||||
script = script.join(" && ");
|
||||
} else {
|
||||
const newLine = isLinux ? "\n" : "\r\n";
|
||||
if (_.isArray(script)) {
|
||||
if (isArray(script)) {
|
||||
script = script as Array<string>;
|
||||
script = script.join(newLine);
|
||||
}
|
||||
@@ -465,7 +483,7 @@ export class SshClient {
|
||||
async shell(options: { connectConf: SshAccess; script: string | Array<string> }): Promise<string> {
|
||||
let { script } = options;
|
||||
const { connectConf } = options;
|
||||
if (_.isArray(script)) {
|
||||
if (isArray(script)) {
|
||||
script = script as Array<string>;
|
||||
if (connectConf.windows) {
|
||||
script = script.join("\r\n");
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-client
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
@@ -84,6 +84,7 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-vue": "^1.9.16",
|
||||
"sortablejs": "^1.15.3",
|
||||
"spark-md5": "^3.0.2",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"theme-colors": "^0.1.0",
|
||||
@@ -100,8 +101,8 @@
|
||||
"zod-defaults": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@certd/lib-iframe": "^1.33.2",
|
||||
"@certd/pipeline": "^1.33.2",
|
||||
"@certd/lib-iframe": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
|
||||
@@ -54,23 +54,23 @@ const steps = ref<Step[]>([
|
||||
descriptions: ["本教程演示如何自动申请证书并部署到Nginx上", "仅需3步,全自动申请部署证书"],
|
||||
body: () => {
|
||||
return <SimpleSteps></SimpleSteps>;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/1-add.png",
|
||||
title: "创建证书流水线",
|
||||
descriptions: ["点击添加证书流水线,填写证书申请信息"]
|
||||
descriptions: ["点击添加证书流水线,填写证书申请信息"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/3-add-success.png",
|
||||
title: "流水线创建成功",
|
||||
descriptions: ["点击手动触发即可申请证书"]
|
||||
descriptions: ["点击手动触发即可申请证书"],
|
||||
},
|
||||
{
|
||||
title: "接下来演示如何自动部署证书",
|
||||
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"]
|
||||
}
|
||||
]
|
||||
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "添加部署证书任务",
|
||||
@@ -79,29 +79,29 @@ const steps = ref<Step[]>([
|
||||
{
|
||||
image: "/static/doc/images/5-1-add-host.png",
|
||||
title: "添加证书部署任务",
|
||||
descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"]
|
||||
descriptions: ["这里演示自动部署证书到nginx", "本系统提供海量部署插件,满足您的各种部署需求"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-2-add-host.png",
|
||||
title: "填写任务参数",
|
||||
descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"]
|
||||
descriptions: ["填写主机上证书文件的路径", "选择主机ssh登录授权"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-3-add-host.png",
|
||||
title: "让新证书生效",
|
||||
descriptions: ["执行重启脚本", "让证书生效"]
|
||||
descriptions: ["执行重启脚本", "让证书生效"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-4-add-host.png",
|
||||
title: "部署任务添加成功",
|
||||
descriptions: ["现在可以运行"]
|
||||
descriptions: ["现在可以运行"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/5-5-plugin-list.png",
|
||||
title: "本系统提供茫茫多的部署插件",
|
||||
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"]
|
||||
}
|
||||
]
|
||||
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "运行与测试",
|
||||
@@ -110,44 +110,44 @@ const steps = ref<Step[]>([
|
||||
{
|
||||
image: "/static/doc/images/9-start.png",
|
||||
title: "运行测试一下",
|
||||
descriptions: ["点击手动触发按钮,即可测试运行"]
|
||||
descriptions: ["点击手动触发按钮,即可测试运行"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/10-1-log.png",
|
||||
title: "查看日志",
|
||||
descriptions: ["点击任务可以查看状态和日志"]
|
||||
descriptions: ["点击任务可以查看状态和日志"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/11-1-error.png",
|
||||
title: "执行失败如何排查",
|
||||
descriptions: ["查看错误日志"]
|
||||
descriptions: ["查看错误日志"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/11-2-error.png",
|
||||
title: "执行失败如何排查",
|
||||
descriptions: ["查看错误日志", "这里报的是nginx容器不存在,修改命令,改成正确的nginx容器名称即可"]
|
||||
descriptions: ["查看错误日志", "这里报的是nginx容器不存在,修改命令,改成正确的nginx容器名称即可"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/12-1-log-success.png",
|
||||
title: "执行成功",
|
||||
descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"]
|
||||
descriptions: ["修改正确后,重新点击手动触发,重新运行一次,执行成功"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/12-2-skip-log.png",
|
||||
title: "成功后自动跳过",
|
||||
descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"]
|
||||
descriptions: ["可以看到成功过的将会自动跳过,不会重复执行,只有当参数变更或者证书更新了,才会重新运行"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/13-1-result.png",
|
||||
title: "查看证书部署成功",
|
||||
descriptions: ["访问nginx上的网站,可以看到证书已经部署成功"]
|
||||
descriptions: ["访问nginx上的网站,可以看到证书已经部署成功"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/13-3-download.png",
|
||||
title: "还可以下载证书,手动部署",
|
||||
descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"]
|
||||
}
|
||||
]
|
||||
descriptions: ["如果还没有好用的部署插件,没办法自动部署,你还可以下载证书,手动部署"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "设置定时执行和邮件通知",
|
||||
@@ -156,22 +156,19 @@ const steps = ref<Step[]>([
|
||||
{
|
||||
image: "/static/doc/images/14-timer.png",
|
||||
title: "设置定时执行",
|
||||
descriptions: [
|
||||
"流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了",
|
||||
"推荐配置每天运行一次,在到期前35天才会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。"
|
||||
]
|
||||
descriptions: ["流水线测试成功,接下来配置定时触发,以后每天定时执行就不用管了", "推荐配置每天运行一次,在到期前35天才会重新申请新证书并部署,没到期前会自动跳过,不会重复申请。"],
|
||||
},
|
||||
{
|
||||
image: "/static/doc/images/15-1-email.png",
|
||||
title: "设置邮件通知",
|
||||
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"]
|
||||
descriptions: ["建议选择监听'错误时'和'错误转成功'两种即可,在意外失败时可以尽快去排查问题,(基础版需要配置邮件服务器)"],
|
||||
},
|
||||
{
|
||||
title: "教程结束",
|
||||
descriptions: ["感谢观看,希望对你有所帮助"]
|
||||
}
|
||||
]
|
||||
}
|
||||
descriptions: ["感谢观看,希望对你有所帮助"],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const current = ref(0);
|
||||
|
||||
@@ -99,4 +99,10 @@ span.fs-icon-svg{
|
||||
|
||||
.ant-btn .fs-icon:last-child{
|
||||
margin-right:0px
|
||||
}
|
||||
|
||||
.fs-iconify fs-icon{
|
||||
svg{
|
||||
vertical-align:0 !important;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,6 @@ export const util = {
|
||||
router: routerUtils,
|
||||
tree: treeUtils,
|
||||
hash: hashUtils,
|
||||
amount: amountUtils
|
||||
amount: amountUtils,
|
||||
};
|
||||
export const utils = util;
|
||||
|
||||
@@ -5,5 +5,5 @@ export const amountUtils = {
|
||||
|
||||
toYuan(amount: number): number {
|
||||
return parseFloat((amount / 100).toFixed(2));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -47,4 +47,13 @@ export default {
|
||||
}
|
||||
return desc.replace(/\[(.*)\]\((.*)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
},
|
||||
|
||||
randomString(length: number) {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const hashUtils = {
|
||||
md5(data: string) {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,5 +7,5 @@ export const site = {
|
||||
title: function (titleText: string, baseTitle?: string) {
|
||||
const processTitle = baseTitle || env.TITLE || "Certd";
|
||||
window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ""}`;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export class WebStorage {
|
||||
const stringData = JSON.stringify({
|
||||
value,
|
||||
time: Date.now(),
|
||||
expire: expire != null ? new Date().getTime() + expire * 1000 : null
|
||||
expire: expire != null ? new Date().getTime() + expire * 1000 : null,
|
||||
});
|
||||
this.storage.setItem(this.getKey(key), stringData);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ export async function GetList(query: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/page",
|
||||
method: "post",
|
||||
data: query
|
||||
data: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function AddObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/add",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function UpdateObj(obj: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/update",
|
||||
method: "post",
|
||||
data: obj
|
||||
data: obj,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function DelObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/delete",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,14 @@ export async function GetObj(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/info",
|
||||
method: "post",
|
||||
params: { id }
|
||||
params: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function Unlock(id: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/unlockBlock",
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as api from "./api";
|
||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { useUserStore } from "/@/store/user";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
|
||||
export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||
@@ -26,16 +27,36 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
pageRequest,
|
||||
addRequest,
|
||||
editRequest,
|
||||
delRequest
|
||||
delRequest,
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: "right"
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
unlock: {
|
||||
title: "解除登录锁定",
|
||||
text: null,
|
||||
type: "link",
|
||||
icon: "ion:lock-open-outline",
|
||||
click: async ({ row }) => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
content: "确定要解除该用户的登录锁定吗?",
|
||||
onOk: async () => {
|
||||
await api.Unlock(row.id);
|
||||
notification.success({
|
||||
message: "解除成功",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
scroll: {
|
||||
//使用固定列时需要设置此值,并且大于等于列宽度之和的值
|
||||
x: 1400
|
||||
}
|
||||
x: 1400,
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
@@ -44,8 +65,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
title: "创建时间",
|
||||
@@ -53,8 +74,8 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: { show: false }, // 表单配置
|
||||
column: {
|
||||
width: 180,
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
// updateTime: {
|
||||
// title: "修改时间",
|
||||
@@ -72,64 +93,64 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
form: {
|
||||
rules: [
|
||||
{ required: true, message: "请输入用户名" },
|
||||
{ max: 50, message: "最大50个字符" }
|
||||
]
|
||||
{ max: 50, message: "最大50个字符" },
|
||||
],
|
||||
},
|
||||
editForm: { component: { disabled: false } },
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 200
|
||||
}
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
password: {
|
||||
title: "密码",
|
||||
type: "text",
|
||||
key: "password",
|
||||
column: {
|
||||
show: false
|
||||
show: false,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
component: {
|
||||
showPassword: true
|
||||
showPassword: true,
|
||||
},
|
||||
helper: "填写则修改密码"
|
||||
}
|
||||
helper: "填写则修改密码",
|
||||
},
|
||||
},
|
||||
nickName: {
|
||||
title: "昵称",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }]
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true
|
||||
}
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
email: {
|
||||
title: "邮箱",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }]
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 160
|
||||
}
|
||||
width: 160,
|
||||
},
|
||||
},
|
||||
mobile: {
|
||||
title: "手机号",
|
||||
type: "text",
|
||||
search: { show: true }, // 开启查询
|
||||
form: {
|
||||
rules: [{ max: 50, message: "最大50个字符" }]
|
||||
rules: [{ max: 50, message: "最大50个字符" }],
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 130
|
||||
}
|
||||
width: 130,
|
||||
},
|
||||
},
|
||||
avatar: {
|
||||
title: "头像",
|
||||
@@ -140,12 +161,12 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
//设置高度,修复操作列错位的问题
|
||||
style: {
|
||||
height: "30px",
|
||||
width: "auto"
|
||||
width: "auto",
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `/api/basic/file/download?&key=` + key;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
component: {
|
||||
@@ -154,7 +175,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
cropper: {
|
||||
aspectRatio: 1,
|
||||
autoCropArea: 1,
|
||||
viewMode: 0
|
||||
viewMode: 0,
|
||||
},
|
||||
onReady: null,
|
||||
uploader: {
|
||||
@@ -162,17 +183,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
action: "/basic/file/upload",
|
||||
name: "file",
|
||||
headers: {
|
||||
Authorization: "Bearer " + userStore.getToken
|
||||
Authorization: "Bearer " + userStore.getToken,
|
||||
},
|
||||
successHandle(res: any) {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
},
|
||||
buildUrl(key: string) {
|
||||
return `/api/basic/file/download?&key=` + key;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
status: {
|
||||
title: "状态",
|
||||
@@ -180,24 +201,24 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: "启用", value: 1, color: "green" },
|
||||
{ label: "禁用", value: 0, color: "red" }
|
||||
]
|
||||
{ label: "禁用", value: 0, color: "red" },
|
||||
],
|
||||
}),
|
||||
column: {
|
||||
align: "center",
|
||||
sorter: true,
|
||||
width: 100
|
||||
}
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
remark: {
|
||||
title: "备注",
|
||||
type: "text",
|
||||
column: {
|
||||
sorter: true
|
||||
sorter: true,
|
||||
},
|
||||
form: {
|
||||
rules: [{ max: 100, message: "最大100个字符" }]
|
||||
}
|
||||
rules: [{ max: 100, message: "最大100个字符" }],
|
||||
},
|
||||
},
|
||||
roles: {
|
||||
title: "角色",
|
||||
@@ -205,17 +226,17 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
|
||||
dict: dict({
|
||||
url: "/sys/authority/role/list",
|
||||
value: "id",
|
||||
label: "name"
|
||||
label: "name",
|
||||
}), // 数据字典
|
||||
form: {
|
||||
component: { mode: "multiple" }
|
||||
component: { mode: "multiple" },
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
sortable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sortable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,6 +66,22 @@ export async function SetDisabled(data: { id?: number; name?: string; type?: str
|
||||
});
|
||||
}
|
||||
|
||||
export async function ExportPlugin(id: number) {
|
||||
return await request({
|
||||
url: apiPrefix + "/export",
|
||||
method: "post",
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function ImportPlugin(body: any) {
|
||||
return await request({
|
||||
url: apiPrefix + "/import",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export type PluginConfigBean = {
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
|
||||
@@ -2,8 +2,8 @@ import * as api from "./api";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
//@ts-ignore
|
||||
import yaml from "js-yaml";
|
||||
|
||||
@@ -36,7 +36,74 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
|
||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||
context.selectedRowKeys = selectedRowKeys;
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
|
||||
async function openImportDialog() {
|
||||
function createCrudOptions() {
|
||||
return {
|
||||
crudOptions: {
|
||||
columns: {
|
||||
content: {
|
||||
title: "插件文件",
|
||||
type: "text",
|
||||
form: {
|
||||
component: {
|
||||
name: "pem-input",
|
||||
vModel: "modelValue",
|
||||
textarea: {
|
||||
rows: 8,
|
||||
},
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
helper: "选择插件文件",
|
||||
},
|
||||
},
|
||||
override: {
|
||||
title: "同名覆盖",
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{
|
||||
value: true,
|
||||
label: "覆盖",
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: "不覆盖",
|
||||
},
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
helper: "如果已有相同名称插件,直接覆盖",
|
||||
},
|
||||
},
|
||||
},
|
||||
form: {
|
||||
wrapper: {
|
||||
title: "导入插件",
|
||||
saveRemind: false,
|
||||
},
|
||||
afterSubmit() {
|
||||
notification.success({ message: "操作成功" });
|
||||
},
|
||||
async doSubmit({ form }: any) {
|
||||
return await api.ImportPlugin({
|
||||
...form,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const { crudOptions } = createCrudOptions();
|
||||
await openCrudFormDialog({ crudOptions });
|
||||
}
|
||||
return {
|
||||
crudOptions: {
|
||||
settings: {
|
||||
@@ -65,8 +132,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
buttons: {
|
||||
add: {
|
||||
show: true,
|
||||
icon: "ion:ios-add-circle-outline",
|
||||
text: "自定义插件",
|
||||
},
|
||||
import: {
|
||||
show: true,
|
||||
icon: "ion:cloud-upload-outline",
|
||||
text: "导入",
|
||||
type: "primary",
|
||||
async click() {
|
||||
await openImportDialog();
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
rowHandle: {
|
||||
@@ -85,10 +162,33 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
}),
|
||||
},
|
||||
remove: {
|
||||
order: 999,
|
||||
show: compute(({ row }) => {
|
||||
return row.type === "custom";
|
||||
}),
|
||||
},
|
||||
export: {
|
||||
text: null,
|
||||
icon: "ion:cloud-download-outline",
|
||||
title: "导出",
|
||||
type: "link",
|
||||
show: compute(({ row }) => {
|
||||
return row.type === "custom";
|
||||
}),
|
||||
async click({ row }) {
|
||||
//将文本内容,作为文件下载
|
||||
const content = await api.ExportPlugin(row.id);
|
||||
if (content) {
|
||||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `${row.name}.yaml`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
@@ -182,8 +282,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
form: {
|
||||
show: true,
|
||||
helper: "必须为英文,驼峰命名,类型作为前缀\n例如AliyunDeployToCDN\n插件一旦被使用,不要修改名称",
|
||||
rules: [{ required: true }],
|
||||
helper: "必须为英文或数字,驼峰命名,类型作为前缀\n例如AliyunDeployToCDN\n插件一旦被使用,不要修改名称",
|
||||
rules: [
|
||||
{ required: true },
|
||||
{
|
||||
type: "regexp",
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9]+$/,
|
||||
message: "必须为英文或数字,驼峰命名,类型作为前缀",
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
width: 250,
|
||||
@@ -205,7 +312,14 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
form: {
|
||||
show: true,
|
||||
helper: "上传到插件商店时,将作为插件名称前缀,例如:greper/pluginName",
|
||||
rules: [{ required: true }],
|
||||
rules: [
|
||||
{ required: true },
|
||||
{
|
||||
type: "regexp",
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9]+$/,
|
||||
message: "必须为英文字母或数字",
|
||||
},
|
||||
],
|
||||
},
|
||||
column: {
|
||||
width: 200,
|
||||
|
||||
@@ -103,3 +103,4 @@ export async function GetSmsTypeDefine(type: string) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<!-- </template>-->
|
||||
<div class="sys-settings-body md:p-5">
|
||||
<a-tabs :active-key="activeKey" type="card" class="sys-settings-tabs" @update:active-key="onChange">
|
||||
<a-tab-pane key="" tab="基本设置">
|
||||
<SettingBase v-if="activeKey === ''" />
|
||||
<a-tab-pane key="base" tab="基本设置">
|
||||
<SettingBase v-if="activeKey === 'base'" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="register" tab="注册设置">
|
||||
<SettingRegister v-if="activeKey === 'register'" />
|
||||
@@ -14,6 +14,9 @@
|
||||
<a-tab-pane v-if="settingsStore.isComm" key="payment" tab="支付设置">
|
||||
<SettingPayment v-if="activeKey === 'payment'" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="save" tab="安全设置">
|
||||
<SettingSafe v-if="activeKey === 'save'" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</fs-page>
|
||||
@@ -23,6 +26,7 @@
|
||||
import SettingBase from "/@/views/sys/settings/tabs/base.vue";
|
||||
import SettingRegister from "/@/views/sys/settings/tabs/register.vue";
|
||||
import SettingPayment from "/@/views/sys/settings/tabs/payment.vue";
|
||||
import SettingSafe from "/@/views/sys/settings/tabs/safe.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ref } from "vue";
|
||||
import { useSettingStore } from "/@/store/settings";
|
||||
@@ -30,11 +34,11 @@ defineOptions({
|
||||
name: "SysSettings",
|
||||
});
|
||||
const settingsStore = useSettingStore();
|
||||
const activeKey = ref("");
|
||||
const activeKey = ref("base");
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
if (route.query.tab) {
|
||||
activeKey.value = (route.query.tab as string) || "";
|
||||
activeKey.value = (route.query.tab as string) || "base";
|
||||
}
|
||||
|
||||
function onChange(value: string) {
|
||||
@@ -52,7 +56,7 @@ function onChange(value: string) {
|
||||
<style lang="less">
|
||||
.page-sys-settings {
|
||||
.sys-settings-form {
|
||||
width: 500px;
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
157
packages/ui/certd-client/src/views/sys/settings/tabs/safe.vue
Normal file
157
packages/ui/certd-client/src/views/sys/settings/tabs/safe.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div class="sys-settings-form sys-settings-safe">
|
||||
<a-form ref="formRef" :model="formState" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
|
||||
<h2>站点隐藏</h2>
|
||||
<a-form-item label="启用站点隐藏" :name="['hidden', 'enabled']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.enabled" />
|
||||
<div class="helper">
|
||||
可以在平时关闭站点的可访问性,需要时再打开,增强站点安全性
|
||||
<a href="https://certd.docmirror.cn/guide/feature/safe/hidden" class="flex items-center" target="_blank">
|
||||
<span>帮助说明</span>
|
||||
<fs-icon class="ml-1" icon="mingcute:question-line"></fs-icon
|
||||
></a>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="随机地址" :name="['hidden', 'openPath']" :required="true">
|
||||
<a-input-search v-model:value="formState.hidden.openPath" :allow-clear="true" @search="changeOpenPath">
|
||||
<template #enterButton>
|
||||
<fs-icon icon="ion:refresh"></fs-icon>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<div class="helper">站点被隐藏后,需要访问此URL解锁,才能正常访问</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="完整解除隐藏地址" :name="['hidden', 'openPath']" :required="true">
|
||||
<div class="flex"><fs-copyable v-model="openUrl" class="flex-inline"></fs-copyable></div>
|
||||
<div class="helper red">请保存好此地址</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="解除密码" :name="['hidden', 'openPassword']" :required="false">
|
||||
<a-input-password v-model:value="formState.hidden.openPassword" :allow-clear="true" />
|
||||
<div class="helper">解除隐藏时需要输入密码,第一次需要设置密码,填写则重置密码</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="自动隐藏时间" :name="['hidden', 'autoHiddenTimes']" :required="true">
|
||||
<a-input-number v-model:value="formState.hidden.autoHiddenTimes" :allow-clear="true" />
|
||||
<div class="helper">多少分钟内无请求自动隐藏</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="隐藏开放接口" :name="['hidden', 'hiddenOpenApi']" :required="true">
|
||||
<a-switch v-model:checked="formState.hidden.hiddenOpenApi" />
|
||||
<div class="helper">是否隐藏开放接口,是否放开/api/v1开头的接口</div>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="formState.hidden.enabled" label="立即隐藏站点">
|
||||
<loading-button class="ml-1" type="primary" html-type="button" :click="doHiddenImmediate">立即隐藏</loading-button>
|
||||
</a-form-item>
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
|
||||
<loading-button type="primary" html-type="button" :click="onClick">保存</loading-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { computed, reactive, ref } from "vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { Modal, notification } from "ant-design-vue";
|
||||
import { request } from "/@/api/service";
|
||||
import { util, utils } from "/@/utils";
|
||||
defineOptions({
|
||||
name: "SettingSafe",
|
||||
});
|
||||
|
||||
const api = {
|
||||
async SettingGet() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/get",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
async SettingSave(data: any) {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/save",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
},
|
||||
async HiddenImmediate() {
|
||||
return await request({
|
||||
url: "/sys/settings/safe/hidden",
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
hidden: {
|
||||
enabled: false,
|
||||
autoHiddenTimes: 5,
|
||||
hiddenOpenApi: false,
|
||||
},
|
||||
};
|
||||
const formRef = ref<any>(defaultState);
|
||||
type SiteHidden = {
|
||||
enabled: boolean;
|
||||
openPath?: string;
|
||||
autoHiddenTimes?: number;
|
||||
openPassword?: string;
|
||||
hiddenOpenApi?: boolean;
|
||||
};
|
||||
|
||||
const formState = reactive<
|
||||
Partial<{
|
||||
hidden: SiteHidden;
|
||||
}>
|
||||
>({
|
||||
hidden: { enabled: false },
|
||||
});
|
||||
|
||||
function changeOpenPath() {
|
||||
formState.hidden.openPath = util.randomString(16);
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
const data: any = await api.SettingGet();
|
||||
merge(formState, defaultState, formState, data);
|
||||
if (!formState.hidden.openPath) {
|
||||
changeOpenPath();
|
||||
}
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
const openUrl = computed(() => {
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = `/api/unhidden/${formState.hidden?.openPath || ""}`;
|
||||
//@ts-ignore
|
||||
url.query = undefined;
|
||||
url.hash = "";
|
||||
return url.href;
|
||||
});
|
||||
|
||||
const onClick = async () => {
|
||||
const form = await formRef.value.validateFields();
|
||||
//密码md5
|
||||
// if (form.hidden?.openPassword) {
|
||||
// form.hidden.openPassword = util.hash.md5(form.hidden.openPassword);
|
||||
// }
|
||||
await api.SettingSave(form);
|
||||
await loadSettings();
|
||||
notification.success({
|
||||
message: "保存成功",
|
||||
});
|
||||
};
|
||||
|
||||
async function doHiddenImmediate() {
|
||||
Modal.confirm({
|
||||
title: "确定要立即隐藏站点吗?",
|
||||
content: "隐藏后,将无法访问站点,请谨慎操作",
|
||||
async onOk() {
|
||||
await api.HiddenImmediate();
|
||||
notification.success({
|
||||
message: "站点已隐藏",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.sys-settings-base {
|
||||
}
|
||||
</style>
|
||||
@@ -14,36 +14,19 @@ import DefineOptions from "unplugin-vue-define-options/vite";
|
||||
// 增加环境变量 _
|
||||
process.env.VITE_APP_VERSION = require("./package.json").version;
|
||||
process.env.VITE_APP_BUILD_TIME = require("dayjs")().format("YYYY-M-D HH:mm:ss");
|
||||
|
||||
import { theme } from "ant-design-vue";
|
||||
import * as https from "node:https";
|
||||
const { defaultAlgorithm, defaultSeed } = theme;
|
||||
|
||||
const mapToken = defaultAlgorithm(defaultSeed);
|
||||
|
||||
export default ({ command, mode }) => {
|
||||
console.log("args", command, mode);
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
let devServerFs: any = {};
|
||||
let devAlias: any[] = [];
|
||||
if (mode.startsWith("debug")) {
|
||||
devAlias = [
|
||||
{ find: /@fast-crud\/fast-crud\/dist/, replacement: path.resolve("../../fast-crud/src/") },
|
||||
// { find: /@fast-crud\/fast-crud$/, replacement: path.resolve("../../fast-crud/src/") },
|
||||
{ find: /@fast-crud\/fast-extends\/dist/, replacement: path.resolve("../../fast-extends/src/") },
|
||||
// { find: /@fast-crud\/fast-extends$/, replacement: path.resolve("../../fast-extends/src/") },
|
||||
// { find: /@fast-crud\/ui-antdv$/, replacement: path.resolve("../../ui/ui-antdv/src/") },
|
||||
// { find: /@fast-crud\/ui-interface$/, replacement: path.resolve("../../ui/ui-interface/src/") }
|
||||
{ find: /@fast-crud\/ui-antdv4\/dist/, replacement: path.resolve("../../ui/ui-antdv4/src/") }
|
||||
];
|
||||
devServerFs = {
|
||||
// 这里配置dev启动时读取的项目根目录
|
||||
allow: ["../../../"]
|
||||
};
|
||||
console.log("devAlias", devAlias);
|
||||
}
|
||||
const devServerFs: any = {};
|
||||
const devAlias: any[] = [];
|
||||
const base = "/";
|
||||
// if (mode.startsWith("dev")) {
|
||||
// base = "/dev";
|
||||
// }
|
||||
return {
|
||||
base: "/",
|
||||
base: base,
|
||||
plugins: [
|
||||
DefineOptions(),
|
||||
vueJsx(),
|
||||
@@ -52,12 +35,12 @@ export default ({ command, mode }) => {
|
||||
inject: {
|
||||
data: {
|
||||
title: env.VITE_APP_TITLE,
|
||||
projectPath: env.VITE_APP_PROJECT_PATH
|
||||
}
|
||||
}
|
||||
projectPath: env.VITE_APP_PROJECT_PATH,
|
||||
},
|
||||
},
|
||||
}),
|
||||
// 压缩build后的代码
|
||||
viteCompression()
|
||||
viteCompression(),
|
||||
//主题色替换
|
||||
//...configThemePlugin(true),
|
||||
// viteThemePlugin({
|
||||
@@ -71,29 +54,29 @@ export default ({ command, mode }) => {
|
||||
drop: command === "build" ? ["debugger"] : [],
|
||||
pure: ["console.log", "debugger"],
|
||||
jsxFactory: "h",
|
||||
jsxFragment: "Fragment"
|
||||
jsxFragment: "Fragment",
|
||||
},
|
||||
resolve: {
|
||||
alias: [...devAlias, { find: "/@", replacement: path.resolve("./src") }, { find: "/#", replacement: path.resolve("./types") }],
|
||||
dedupe: ["vue"]
|
||||
dedupe: ["vue"],
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ["ant-design-vue"]
|
||||
include: ["ant-design-vue"],
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
plugins: [visualizer()]
|
||||
}
|
||||
plugins: [visualizer()],
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
// 修改默认主题颜色,配置less变量
|
||||
// modifyVars: generateModifyVars(),
|
||||
javascriptEnabled: true
|
||||
javascriptEnabled: true,
|
||||
// modifyVars: mapToken
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
@@ -105,9 +88,9 @@ export default ({ command, mode }) => {
|
||||
//配套后端 https://github.com/fast-crud/fs-server-js
|
||||
target: "https://127.0.0.1:7002",
|
||||
//忽略证书
|
||||
agent: new https.Agent({ rejectUnauthorized: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
agent: new https.Agent({ rejectUnauthorized: false }),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.33.4](https://github.com/certd/certd/compare/v1.33.3...v1.33.4) (2025-04-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 补充类型断言 ([2143dff](https://github.com/certd/certd/commit/2143dff2ae96e6a78bef9f0498e36f8cd9e6941f))
|
||||
* 修复腾讯云部署到任意资源插件,无法使用之前已上传的腾讯云证书问题 ([32c714d](https://github.com/certd/certd/commit/32c714d1b6e68c71a74a7452115040c87ac4bfdc))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 插件支持导入导出 ([cf8abb4](https://github.com/certd/certd/commit/cf8abb45282070c8ba91469f93fd379fabf1f74a))
|
||||
* 支持上传证书到华为云CCM ([cfd3b66](https://github.com/certd/certd/commit/cfd3b66be9ebf53a26693057e70ed60c3f116be9))
|
||||
|
||||
## [1.33.3](https://github.com/certd/certd/compare/v1.33.2...v1.33.3) (2025-04-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复登录错误次数过多阻止再次登录逻辑 ([bf4d191](https://github.com/certd/certd/commit/bf4d191c8bd2f9209eb6768f662b9c77de99e998))
|
||||
|
||||
## [1.33.2](https://github.com/certd/certd/compare/v1.33.1...v1.33.2) (2025-04-12)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
53
packages/ui/certd-server/export-plugin-yaml.js
Normal file
53
packages/ui/certd-server/export-plugin-yaml.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// 扫描目录,列出文件,然后加载为模块
|
||||
|
||||
import { join } from 'path';
|
||||
import fs from 'fs'
|
||||
import { pathToFileURL } from "node:url";
|
||||
import path from 'path'
|
||||
function scanDir(dir) {
|
||||
const files = fs.readdirSync(dir);
|
||||
const result = [];
|
||||
// 扫描目录及子目录
|
||||
for (const file of files) {
|
||||
if (file.includes("index.js")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory()) {
|
||||
result.push(...scanDir(filePath));
|
||||
} else {
|
||||
if (!file.endsWith(".js")) {
|
||||
continue;
|
||||
}
|
||||
result.push(filePath);
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export default async function loadModules(dir) {
|
||||
const files = scanDir(dir);
|
||||
const modules = {}
|
||||
for (const file of files) {
|
||||
|
||||
try {
|
||||
// 转换为 file:// URL(Windows 必需)
|
||||
const moduleUrl = pathToFileURL(file).href
|
||||
const module = await import(moduleUrl)
|
||||
|
||||
// 如果模块有默认导出,优先使用
|
||||
modules[file] = module.default || module
|
||||
} catch (err) {
|
||||
console.error(`加载模块 ${file} 失败:`, err)
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
const modules = await loadModules('./dist/plugins');
|
||||
|
||||
for (const key in modules) {
|
||||
console.log(key)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.33.2",
|
||||
"version": "1.33.4",
|
||||
"description": "fast-server base midway",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -38,19 +38,19 @@
|
||||
"@aws-sdk/client-acm": "^3.699.0",
|
||||
"@aws-sdk/client-cloudfront": "^3.699.0",
|
||||
"@aws-sdk/client-s3": "^3.705.0",
|
||||
"@certd/acme-client": "^1.33.2",
|
||||
"@certd/basic": "^1.33.2",
|
||||
"@certd/commercial-core": "^1.33.2",
|
||||
"@certd/jdcloud": "^1.33.2",
|
||||
"@certd/lib-huawei": "^1.33.2",
|
||||
"@certd/lib-k8s": "^1.33.2",
|
||||
"@certd/lib-server": "^1.33.2",
|
||||
"@certd/midway-flyway-js": "^1.33.2",
|
||||
"@certd/pipeline": "^1.33.2",
|
||||
"@certd/plugin-cert": "^1.33.2",
|
||||
"@certd/plugin-lib": "^1.33.2",
|
||||
"@certd/plugin-plus": "^1.33.2",
|
||||
"@certd/plus-core": "^1.33.2",
|
||||
"@certd/acme-client": "^1.33.4",
|
||||
"@certd/basic": "^1.33.4",
|
||||
"@certd/commercial-core": "^1.33.4",
|
||||
"@certd/jdcloud": "^1.33.4",
|
||||
"@certd/lib-huawei": "^1.33.4",
|
||||
"@certd/lib-k8s": "^1.33.4",
|
||||
"@certd/lib-server": "^1.33.4",
|
||||
"@certd/midway-flyway-js": "^1.33.4",
|
||||
"@certd/pipeline": "^1.33.4",
|
||||
"@certd/plugin-cert": "^1.33.4",
|
||||
"@certd/plugin-lib": "^1.33.4",
|
||||
"@certd/plugin-plus": "^1.33.4",
|
||||
"@certd/plus-core": "^1.33.4",
|
||||
"@corsinvest/cv4pve-api-javascript": "^8.3.0",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
|
||||
@@ -19,12 +19,18 @@ import * as libServer from '@certd/lib-server';
|
||||
import * as commercial from '@certd/commercial-core';
|
||||
import * as upload from '@midwayjs/upload';
|
||||
import { setLogger } from '@certd/acme-client';
|
||||
import {HiddenMiddleware} from "./middleware/hidden.js";
|
||||
process.on('uncaughtException', error => {
|
||||
console.error('未捕获的异常:', error);
|
||||
// 在这里可以添加日志记录、发送错误通知等操作
|
||||
});
|
||||
|
||||
@Configuration({
|
||||
// detectorOptions: {
|
||||
// ignore: [
|
||||
// '**/plugins/**'
|
||||
// ]
|
||||
// },
|
||||
imports: [
|
||||
koa,
|
||||
orm,
|
||||
@@ -77,6 +83,8 @@ export class MainConfiguration {
|
||||
this.app.useMiddleware([
|
||||
//统一异常处理
|
||||
GlobalExceptionMiddleware,
|
||||
//站点隐藏
|
||||
HiddenMiddleware,
|
||||
//预览模式限制修改id<1000的数据
|
||||
PreviewMiddleware,
|
||||
//授权处理
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import {Body, Controller, Get, Inject, Post, Provide} from '@midwayjs/core';
|
||||
import {Constants, NotFoundException, ParamException, SysInstallInfo, SysSettingsService} from '@certd/lib-server';
|
||||
import {utils} from "@certd/basic";
|
||||
import {hiddenStatus, SafeService} from "../../modules/sys/settings/safe-service.js";
|
||||
import {IMidwayKoaContext} from "@midwayjs/koa";
|
||||
|
||||
const unhiddenHtml = `
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>certd解除站点隐藏</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin:50px;width:500px">
|
||||
<h3>解除站点隐藏</h3>
|
||||
<form method="post">
|
||||
请输入解除密码: <input type="password" name="password" /> <button type="submit">确定</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
`
|
||||
|
||||
@Provide()
|
||||
@Controller('/api/unhidden')
|
||||
export class HnhiddenController {
|
||||
@Inject()
|
||||
ctx: IMidwayKoaContext;
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
|
||||
@Post('/:randomPath', {summary: Constants.per.guest})
|
||||
async randomPath(@Body("password") password: any) {
|
||||
await this.checkUnhiddenPath()
|
||||
const hiddenSetting = await this.safeService.getHiddenSetting()
|
||||
if (utils.hash.md5(password) === hiddenSetting.openPassword) {
|
||||
//解锁
|
||||
hiddenStatus.isHidden = false;
|
||||
const setting = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo)
|
||||
const bindUrl = setting.bindUrl
|
||||
//解锁成功,跳转回首页,redirect
|
||||
this.ctx.response.redirect(bindUrl || "/");
|
||||
return
|
||||
} else {
|
||||
//密码错误
|
||||
throw new ParamException('解锁密码错误');
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:randomPath', {summary: Constants.per.guest})
|
||||
async unhiddenGet() {
|
||||
await this.checkUnhiddenPath()
|
||||
this.ctx.response.body = unhiddenHtml
|
||||
}
|
||||
|
||||
async checkUnhiddenPath() {
|
||||
const hiddenSetting = await this.safeService.getHiddenSetting()
|
||||
if (this.ctx.path != `/api/unhidden/${hiddenSetting.openPath}`) {
|
||||
this.ctx.res.statusCode = 404
|
||||
throw new NotFoundException("Page not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Provide, Controller, Post, Inject, Body, Query, ALL } from '@midwayjs/core';
|
||||
import { UserService } from '../../../modules/sys/authority/service/user-service.js';
|
||||
import { CrudController } from '@certd/lib-server';
|
||||
import { RoleService } from '../../../modules/sys/authority/service/role-service.js';
|
||||
import { PermissionService } from '../../../modules/sys/authority/service/permission-service.js';
|
||||
import { Constants } from '@certd/lib-server';
|
||||
import { In } from 'typeorm';
|
||||
import {Provide, Controller, Post, Inject, Body, Query, ALL} from '@midwayjs/core';
|
||||
import {UserService} from '../../../modules/sys/authority/service/user-service.js';
|
||||
import {CrudController} from '@certd/lib-server';
|
||||
import {RoleService} from '../../../modules/sys/authority/service/role-service.js';
|
||||
import {PermissionService} from '../../../modules/sys/authority/service/permission-service.js';
|
||||
import {Constants} from '@certd/lib-server';
|
||||
import {In} from 'typeorm';
|
||||
import {LoginService} from "../../../modules/login/service/login-service.js";
|
||||
|
||||
/**
|
||||
* 系统用户
|
||||
@@ -20,11 +21,14 @@ export class UserController extends CrudController<UserService> {
|
||||
@Inject()
|
||||
permissionService: PermissionService;
|
||||
|
||||
@Inject()
|
||||
loginService: LoginService;
|
||||
|
||||
getService() {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
@Post('/getSimpleUserByIds', { summary: 'sys:auth:user:add' })
|
||||
@Post('/getSimpleUserByIds', {summary: 'sys:auth:user:add'})
|
||||
async getSimpleUserByIds(@Body('ids') ids: number[]) {
|
||||
const users = await this.service.find({
|
||||
select: {
|
||||
@@ -42,10 +46,10 @@ export class UserController extends CrudController<UserService> {
|
||||
return this.ok(users);
|
||||
}
|
||||
|
||||
@Post('/page', { summary: 'sys:auth:user:view' })
|
||||
@Post('/page', {summary: 'sys:auth:user:view'})
|
||||
async page(
|
||||
@Body(ALL)
|
||||
body
|
||||
body
|
||||
) {
|
||||
const ret = await super.page(body);
|
||||
|
||||
@@ -74,25 +78,26 @@ export class UserController extends CrudController<UserService> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Post('/add', { summary: 'sys:auth:user:add' })
|
||||
@Post('/add', {summary: 'sys:auth:user:add'})
|
||||
async add(
|
||||
@Body(ALL)
|
||||
bean
|
||||
bean
|
||||
) {
|
||||
return await super.add(bean);
|
||||
}
|
||||
|
||||
@Post('/update', { summary: 'sys:auth:user:edit' })
|
||||
@Post('/update', {summary: 'sys:auth:user:edit'})
|
||||
async update(
|
||||
@Body(ALL)
|
||||
bean
|
||||
bean
|
||||
) {
|
||||
return await super.update(bean);
|
||||
}
|
||||
@Post('/delete', { summary: 'sys:auth:user:remove' })
|
||||
|
||||
@Post('/delete', {summary: 'sys:auth:user:remove'})
|
||||
async delete(
|
||||
@Query('id')
|
||||
id: number
|
||||
id: number
|
||||
) {
|
||||
if (id === 1) {
|
||||
throw new Error('不能删除默认的管理员角色');
|
||||
@@ -103,10 +108,23 @@ export class UserController extends CrudController<UserService> {
|
||||
return await super.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除登录锁定
|
||||
*/
|
||||
@Post('/unlockBlock', {summary: "sys:auth:user:edit"})
|
||||
public async unlockBlock(@Body('id') id: number) {
|
||||
const info = await this.service.info(id, ['password']);
|
||||
this.loginService.clearCacheOnSuccess(info.username)
|
||||
if (info.mobile) {
|
||||
this.loginService.clearCacheOnSuccess(info.mobile)
|
||||
}
|
||||
return this.ok(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录用户的个人信息
|
||||
*/
|
||||
@Post('/mine', { summary: Constants.per.authOnly })
|
||||
@Post('/mine', {summary: Constants.per.authOnly})
|
||||
public async mine() {
|
||||
const id = this.getUserId();
|
||||
const info = await this.service.info(id, ['password']);
|
||||
@@ -116,7 +134,7 @@ export class UserController extends CrudController<UserService> {
|
||||
/**
|
||||
* 当前登录用户的权限列表
|
||||
*/
|
||||
@Post('/permissions', { summary: Constants.per.authOnly })
|
||||
@Post('/permissions', {summary: Constants.per.authOnly})
|
||||
public async permissions() {
|
||||
const id = this.getUserId();
|
||||
const permissions = await this.service.getUserPermissions(id);
|
||||
@@ -126,11 +144,13 @@ export class UserController extends CrudController<UserService> {
|
||||
/**
|
||||
* 当前登录用户的权限树形列表
|
||||
*/
|
||||
@Post('/permissionTree', { summary: Constants.per.authOnly })
|
||||
@Post('/permissionTree', {summary: Constants.per.authOnly})
|
||||
public async permissionTree() {
|
||||
const id = this.getUserId();
|
||||
const permissions = await this.service.getUserPermissions(id);
|
||||
const tree = this.permissionService.buildTree(permissions);
|
||||
return this.ok(tree);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { merge } from 'lodash-es';
|
||||
import { CrudController } from '@certd/lib-server';
|
||||
import { PluginService } from '../../../modules/plugin/service/plugin-service.js';
|
||||
import { PluginImportReq, PluginService } from "../../../modules/plugin/service/plugin-service.js";
|
||||
import { CommPluginConfig, PluginConfigService } from '../../../modules/plugin/service/plugin-config-service.js';
|
||||
/**
|
||||
* 插件
|
||||
@@ -82,4 +82,17 @@ export class PluginController extends CrudController<PluginService> {
|
||||
const res = await this.pluginConfigService.saveCommPluginConfig(body);
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
|
||||
@Post('/import', { summary: 'sys:settings:edit' })
|
||||
async import(@Body(ALL) body: PluginImportReq) {
|
||||
const res = await this.service.importPlugin(body);
|
||||
return this.ok(res);
|
||||
}
|
||||
|
||||
@Post('/export', { summary: 'sys:settings:edit' })
|
||||
async export(@Body('id') id: number) {
|
||||
const res = await this.service.exportPlugin(id);
|
||||
return this.ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import {ALL, Body, Controller, Inject, Post, Provide} from '@midwayjs/core';
|
||||
import {BaseController, SysSafeSetting} from '@certd/lib-server';
|
||||
import {cloneDeep} from 'lodash-es';
|
||||
import {SafeService} from "../../../modules/sys/settings/safe-service.js";
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Controller('/api/sys/settings/safe')
|
||||
export class SysSettingsController extends BaseController {
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
|
||||
|
||||
|
||||
@Post("/get", { summary: "sys:settings:view" })
|
||||
async safeGet() {
|
||||
const res = await this.safeService.getSafeSetting()
|
||||
const clone:SysSafeSetting = cloneDeep(res);
|
||||
delete clone.hidden?.openPassword;
|
||||
return this.ok(clone);
|
||||
}
|
||||
|
||||
@Post("/save", { summary: "sys:settings:edit" })
|
||||
async safeSave(@Body(ALL) body: any) {
|
||||
await this.safeService.saveSafeSetting(body);
|
||||
return this.ok({});
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即隐藏
|
||||
*/
|
||||
@Post("/hidden", { summary: "sys:settings:edit" })
|
||||
async hiddenImmediate() {
|
||||
await this.safeService.hiddenImmediately();
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
|
||||
import { CrudController, SysPrivateSettings, SysPublicSettings, SysSettingsEntity, SysSettingsService } from '@certd/lib-server';
|
||||
import { merge } from 'lodash-es';
|
||||
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
|
||||
import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js';
|
||||
import { getEmailSettings } from '../../../modules/sys/settings/fix.js';
|
||||
import { http, logger, simpleNanoId } from '@certd/basic';
|
||||
import { CodeService } from '../../../modules/basic/service/code-service.js';
|
||||
import { SmsServiceFactory } from '../../../modules/basic/sms/factory.js';
|
||||
import {ALL, Body, Controller, Inject, Post, Provide, Query} from '@midwayjs/core';
|
||||
import {
|
||||
CrudController,
|
||||
SysPrivateSettings,
|
||||
SysPublicSettings,
|
||||
SysSafeSetting,
|
||||
SysSettingsEntity,
|
||||
SysSettingsService
|
||||
} from '@certd/lib-server';
|
||||
import {cloneDeep, merge} from 'lodash-es';
|
||||
import {PipelineService} from '../../../modules/pipeline/service/pipeline-service.js';
|
||||
import {UserSettingsService} from '../../../modules/mine/service/user-settings-service.js';
|
||||
import {getEmailSettings} from '../../../modules/sys/settings/fix.js';
|
||||
import {http, logger, simpleNanoId, utils} from '@certd/basic';
|
||||
import {CodeService} from '../../../modules/basic/service/code-service.js';
|
||||
import {SmsServiceFactory} from '../../../modules/basic/sms/factory.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -159,4 +166,29 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
|
||||
async getSmsTypeDefine(@Body('type') type: string) {
|
||||
return this.ok(SmsServiceFactory.getDefine(type));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Post("/safe/get", { summary: "sys:settings:view" })
|
||||
async safeGet() {
|
||||
const res = await this.service.getSetting<SysSafeSetting>(SysSafeSetting);
|
||||
const clone:SysSafeSetting = cloneDeep(res);
|
||||
delete clone.hidden?.openPassword;
|
||||
return this.ok(clone);
|
||||
}
|
||||
|
||||
@Post("/safe/save", { summary: "sys:settings:edit" })
|
||||
async safeSave(@Body(ALL) body: any) {
|
||||
if(body.hidden.openPassword){
|
||||
body.hidden.openPassword = utils.hash.md5(body.hidden.openPassword);
|
||||
}
|
||||
const blankSetting = new SysSafeSetting()
|
||||
const setting = await this.service.getSetting<SysSafeSetting>(SysSafeSetting);
|
||||
const newSetting = merge(blankSetting,cloneDeep(setting), body);
|
||||
if(newSetting.hidden?.enabled && !newSetting.hidden?.openPassword){
|
||||
throw new Error("首次设置需要填写解锁密码")
|
||||
}
|
||||
await this.service.saveSetting(blankSetting);
|
||||
return this.ok({});
|
||||
}
|
||||
}
|
||||
|
||||
54
packages/ui/certd-server/src/middleware/hidden.ts
Normal file
54
packages/ui/certd-server/src/middleware/hidden.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {Inject, Provide} from '@midwayjs/core';
|
||||
import {IMidwayKoaContext, IWebMiddleware, NextFunction} from '@midwayjs/koa';
|
||||
import {hiddenStatus, SafeService} from "../modules/sys/settings/safe-service.js";
|
||||
import {SiteOffException} from "@certd/lib-server";
|
||||
|
||||
|
||||
/**
|
||||
* 隐藏环境
|
||||
*/
|
||||
@Provide()
|
||||
export class HiddenMiddleware implements IWebMiddleware {
|
||||
@Inject()
|
||||
hiddenService: SafeService;
|
||||
|
||||
resolve() {
|
||||
return async (ctx: IMidwayKoaContext, next: NextFunction) => {
|
||||
|
||||
async function pass() {
|
||||
hiddenStatus.updateRequestTime()
|
||||
await next();
|
||||
}
|
||||
|
||||
const hiddenSetting = await this.hiddenService.getHiddenSetting();
|
||||
if (!hiddenSetting || !hiddenSetting?.enabled) {
|
||||
//未开启站点隐藏,直接通过
|
||||
return await pass()
|
||||
}
|
||||
|
||||
const req = ctx.request;
|
||||
if (hiddenSetting.hiddenOpenApi === false && req.url.startsWith(`/api/v1/`) ) {
|
||||
//不隐藏开放接口
|
||||
await next();
|
||||
return
|
||||
}
|
||||
|
||||
//判断当前是否是隐藏状态
|
||||
if (!hiddenStatus.isHidden) {
|
||||
return await pass()
|
||||
}
|
||||
|
||||
//判断是否有解锁文件,如果有就返回true并删除文件
|
||||
if (hiddenStatus.hasUnHiddenFile()) {
|
||||
//临时修改为未隐藏
|
||||
hiddenStatus.isHidden = false;
|
||||
return await pass()
|
||||
}
|
||||
|
||||
if (req.url === `/api/unhidden/${hiddenSetting.openPath}`) {
|
||||
return await pass();
|
||||
}
|
||||
throw new SiteOffException('此站点已关闭');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { UserService } from '../sys/authority/service/user-service.js';
|
||||
import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
|
||||
import { nanoid } from 'nanoid';
|
||||
import crypto from 'crypto';
|
||||
import {SafeService} from "../sys/settings/safe-service.js";
|
||||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -18,6 +19,8 @@ export class AutoAInitSite {
|
||||
sysSettingsService: SysSettingsService;
|
||||
@Inject()
|
||||
plusService: PlusService;
|
||||
@Inject()
|
||||
safeService: SafeService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
@@ -57,6 +60,8 @@ export class AutoAInitSite {
|
||||
logger.error('授权许可验证失败', e);
|
||||
}
|
||||
|
||||
//加载站点隐藏配置
|
||||
await this.safeService.reloadHiddenStatus(true)
|
||||
logger.info('初始化站点完成');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { UserService } from '../../sys/authority/service/user-service.js';
|
||||
import {Config, Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {UserService} from '../../sys/authority/service/user-service.js';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { CommonException } from '@certd/lib-server';
|
||||
import { RoleService } from '../../sys/authority/service/role-service.js';
|
||||
import { UserEntity } from '../../sys/authority/entity/user.js';
|
||||
import { SysSettingsService } from '@certd/lib-server';
|
||||
import { SysPrivateSettings } from '@certd/lib-server';
|
||||
import { cache } from '@certd/basic';
|
||||
import { LoginErrorException } from '@certd/lib-server/dist/basic/exception/login-error-exception.js';
|
||||
import { CodeService } from '../../basic/service/code-service.js';
|
||||
import {CommonException} from '@certd/lib-server';
|
||||
import {RoleService} from '../../sys/authority/service/role-service.js';
|
||||
import {UserEntity} from '../../sys/authority/entity/user.js';
|
||||
import {SysSettingsService} from '@certd/lib-server';
|
||||
import {SysPrivateSettings} from '@certd/lib-server';
|
||||
import {cache} from '@certd/basic';
|
||||
import {LoginErrorException} from '@certd/lib-server/dist/basic/exception/login-error-exception.js';
|
||||
import {CodeService} from '../../basic/service/code-service.js';
|
||||
|
||||
/**
|
||||
* 系统用户
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
export class LoginService {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
@@ -29,51 +29,72 @@ export class LoginService {
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
checkErrorTimes(username: string, errorMessage: string) {
|
||||
const cacheKey = `login_error_times:${username}`;
|
||||
const blockTimesKey = `login_block_times:${username}`;
|
||||
let blockTimes = cache.get(blockTimesKey);
|
||||
let maxWaitMin = 2;
|
||||
const maxRetryTimes = 5;
|
||||
if (blockTimes == null) {
|
||||
blockTimes = 1;
|
||||
checkIsBlocked(username: string) {
|
||||
const blockDurationKey = `login_block_duration:${username}`;
|
||||
const value = cache.get(blockDurationKey);
|
||||
if (value) {
|
||||
const ttl = cache.getRemainingTTL(blockDurationKey)
|
||||
const leftMin = Math.ceil(ttl / 1000 / 60);
|
||||
throw new CommonException(`账号被锁定,请${leftMin}分钟后重试`);
|
||||
}
|
||||
maxWaitMin = maxWaitMin * blockTimes;
|
||||
let ttl = maxWaitMin * 60 * 1000;
|
||||
}
|
||||
|
||||
let errorTimes = cache.get(cacheKey);
|
||||
clearCacheOnSuccess(username: string) {
|
||||
cache.delete(`login_error_times:${username}`);
|
||||
cache.delete(`login_block_times:${username}`);
|
||||
cache.delete(`login_block_duration:${username}`);
|
||||
}
|
||||
|
||||
addErrorTimes(username: string, errorMessage: string) {
|
||||
const errorTimesKey = `login_error_times:${username}`;
|
||||
const blockTimesKey = `login_block_times:${username}`;
|
||||
const blockDurationKey = `login_block_duration:${username}`;
|
||||
let blockTimes = cache.get(blockTimesKey);
|
||||
// let maxWaitMin = 2;
|
||||
const maxRetryTimes = blockTimes > 1 ? 3 : 5;
|
||||
if (blockTimes == null) {
|
||||
blockTimes = 0;
|
||||
}
|
||||
// maxWaitMin = maxWaitMin * blockTimes;
|
||||
// let ttl = maxWaitMin * 60 * 1000;
|
||||
|
||||
let errorTimes = cache.get(errorTimesKey);
|
||||
|
||||
if (errorTimes == null) {
|
||||
errorTimes = 0;
|
||||
} else {
|
||||
const remainingTTL = cache.getRemainingTTL(cacheKey);
|
||||
if (remainingTTL > 0) {
|
||||
ttl = remainingTTL;
|
||||
}
|
||||
}
|
||||
errorTimes += 1;
|
||||
|
||||
cache.set(cacheKey, errorTimes, {
|
||||
ttl: ttl,
|
||||
const ttl24H = 24 * 60 * 60 * 1000;
|
||||
cache.set(errorTimesKey, errorTimes, {
|
||||
ttl: ttl24H,
|
||||
});
|
||||
if (errorTimes >= maxRetryTimes) {
|
||||
if (errorTimes === maxRetryTimes) {
|
||||
blockTimes += 1;
|
||||
cache.set(blockTimesKey, blockTimes, {
|
||||
ttl: 24 * 60 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
if (errorTimes > maxRetryTimes) {
|
||||
blockTimes += 1;
|
||||
cache.set(blockTimesKey, blockTimes, {
|
||||
ttl: ttl24H,
|
||||
});
|
||||
//按照block次数指数递增,最长24小时
|
||||
const ttl = Math.min(blockTimes * blockTimes * 60 * 1000, ttl24H);
|
||||
const leftMin = Math.ceil(ttl / 1000 / 60);
|
||||
cache.set(blockDurationKey, 1, {
|
||||
ttl: ttl,
|
||||
})
|
||||
// 清除error次数
|
||||
cache.delete(errorTimesKey);
|
||||
throw new LoginErrorException(`登录失败次数过多,请${leftMin}分钟后重试`, 0);
|
||||
}
|
||||
const leftTimes = maxRetryTimes - errorTimes;
|
||||
if (leftTimes < 3) {
|
||||
throw new LoginErrorException(`登录失败,剩余尝试次数:${leftTimes}`, leftTimes);
|
||||
throw new LoginErrorException(`登录失败(${errorMessage}),剩余尝试次数:${leftTimes}`, leftTimes);
|
||||
}
|
||||
throw new LoginErrorException(errorMessage, leftTimes);
|
||||
}
|
||||
|
||||
|
||||
async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string }) {
|
||||
|
||||
this.checkIsBlocked(req.mobile)
|
||||
|
||||
const smsChecked = await this.codeService.checkSmsCode({
|
||||
mobile: req.mobile,
|
||||
phoneCode: req.phoneCode,
|
||||
@@ -82,11 +103,11 @@ export class LoginService {
|
||||
throwError: false,
|
||||
});
|
||||
|
||||
const { mobile, phoneCode } = req;
|
||||
const {mobile, phoneCode} = req;
|
||||
if (!smsChecked) {
|
||||
this.checkErrorTimes(mobile, '验证码错误');
|
||||
this.addErrorTimes(mobile, '验证码错误');
|
||||
}
|
||||
let info = await this.userService.findOne({ phoneCode, mobile: mobile });
|
||||
let info = await this.userService.findOne({phoneCode, mobile: mobile});
|
||||
if (info == null) {
|
||||
//用户不存在,注册
|
||||
info = await this.userService.register('mobile', {
|
||||
@@ -95,39 +116,30 @@ export class LoginService {
|
||||
password: '',
|
||||
} as any);
|
||||
}
|
||||
this.clearCacheOnSuccess(mobile);
|
||||
return this.onLoginSuccess(info);
|
||||
}
|
||||
|
||||
async loginByPassword(req: { username: string; password: string; phoneCode: string }) {
|
||||
const { username, password, phoneCode } = req;
|
||||
const info = await this.userService.findOne([{ username: username }, { email: username }, { phoneCode, mobile: username }]);
|
||||
this.checkIsBlocked(req.username)
|
||||
const {username, password, phoneCode} = req;
|
||||
const info = await this.userService.findOne([{username: username}, {email: username}, {
|
||||
phoneCode,
|
||||
mobile: username
|
||||
}]);
|
||||
if (info == null) {
|
||||
throw new CommonException('用户名或密码错误');
|
||||
}
|
||||
const right = await this.userService.checkPassword(password, info.password, info.passwordVersion);
|
||||
if (!right) {
|
||||
this.checkErrorTimes(username, '用户名或密码错误');
|
||||
this.addErrorTimes(username, '用户名或密码错误');
|
||||
}
|
||||
this.clearCacheOnSuccess(username);
|
||||
return this.onLoginSuccess(info);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * login
|
||||
// */
|
||||
// async login(user) {
|
||||
// console.assert(user.username != null, '用户名不能为空');
|
||||
// const info = await this.userService.findOne({ username: user.username });
|
||||
// if (info == null) {
|
||||
// throw new CommonException('用户名或密码错误');
|
||||
// }
|
||||
// const right = await this.userService.checkPassword(user.password, info.password, info.passwordVersion);
|
||||
// if (!right) {
|
||||
// this.checkErrorTimes(user.username, '用户名或密码错误');
|
||||
// }
|
||||
// return await this.onLoginSuccess(info);
|
||||
// }
|
||||
|
||||
private async onLoginSuccess(info: UserEntity) {
|
||||
|
||||
if (info.status === 0) {
|
||||
throw new CommonException('用户已被禁用');
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ export type PluginFindReq = {
|
||||
name?: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class PluginConfigService {
|
||||
|
||||
@@ -12,6 +12,10 @@ import { logger } from "@certd/basic";
|
||||
import yaml from "js-yaml";
|
||||
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
|
||||
|
||||
export type PluginImportReq = {
|
||||
content: string,
|
||||
override?: boolean;
|
||||
};
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@@ -41,7 +45,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
const builtInList = await this.getBuiltInEntityList();
|
||||
|
||||
//获取分页数据
|
||||
const data = builtInList.slice(offset, offset + limit);
|
||||
const data = builtInList.slice(offset, offset + limit);
|
||||
|
||||
return {
|
||||
records: data,
|
||||
@@ -53,7 +57,7 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
|
||||
async getEnabledBuildInGroup(isSimple = false) {
|
||||
const groups = this.builtInPluginService.getGroups();
|
||||
if(isSimple){
|
||||
if (isSimple) {
|
||||
for (const key in groups) {
|
||||
const group = groups[key];
|
||||
group.plugins.forEach(item => {
|
||||
@@ -97,8 +101,8 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
});
|
||||
const disabledNames = list.map(it => it.name);
|
||||
|
||||
return builtInList.filter(it =>{
|
||||
return !disabledNames.includes(it.name)
|
||||
return builtInList.filter(it => {
|
||||
return !disabledNames.includes(it.name);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -168,33 +172,48 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
name: param.name,
|
||||
author: param.author
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (old) {
|
||||
throw new Error(`插件${param.author}/${param.name}已存在`);
|
||||
}
|
||||
|
||||
let plugin:any = {}
|
||||
let plugin: any = {};
|
||||
if (param.pluginType === "access") {
|
||||
plugin = getDefaultAccessPlugin()
|
||||
delete param.group
|
||||
}else if (param.pluginType === "deploy") {
|
||||
plugin = getDefaultDeployPlugin()
|
||||
}else if (param.pluginType === "dnsProvider") {
|
||||
plugin = getDefaultDnsPlugin()
|
||||
delete param.group
|
||||
}else{
|
||||
plugin = getDefaultAccessPlugin();
|
||||
delete param.group;
|
||||
} else if (param.pluginType === "deploy") {
|
||||
plugin = getDefaultDeployPlugin();
|
||||
} else if (param.pluginType === "dnsProvider") {
|
||||
plugin = getDefaultDnsPlugin();
|
||||
delete param.group;
|
||||
} else {
|
||||
throw new Error(`插件类型${param.pluginType}不支持`);
|
||||
}
|
||||
|
||||
return await super.add({
|
||||
return await super.add({
|
||||
...param,
|
||||
...plugin
|
||||
});
|
||||
}
|
||||
|
||||
async update(param: any) {
|
||||
const old = await this.repository.findOne({
|
||||
where: {
|
||||
name: param.name,
|
||||
author: param.author
|
||||
}
|
||||
});
|
||||
|
||||
if (old && old.id !== param.id) {
|
||||
throw new Error(`插件${param.author}/${param.name}已存在`);
|
||||
}
|
||||
|
||||
return await super.update(param);
|
||||
}
|
||||
|
||||
async compile(code: string) {
|
||||
const ts = await import("typescript")
|
||||
const ts = await import("typescript");
|
||||
return ts.transpileModule(code, {
|
||||
compilerOptions: { module: ts.ModuleKind.ESNext }
|
||||
}).outputText;
|
||||
@@ -220,16 +239,16 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
if (info && info.length > 0) {
|
||||
const plugin = info[0];
|
||||
|
||||
try{
|
||||
try {
|
||||
const AsyncFunction = Object.getPrototypeOf(async () => {
|
||||
}).constructor;
|
||||
// const script = await this.compile(plugin.content);
|
||||
const script = plugin.content
|
||||
const script = plugin.content;
|
||||
const getPluginClass = new AsyncFunction(script);
|
||||
return await getPluginClass({ logger: logger });
|
||||
}catch (e) {
|
||||
logger.error("编译插件失败:",e)
|
||||
throw e
|
||||
} catch (e) {
|
||||
logger.error("编译插件失败:", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -284,4 +303,80 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
});
|
||||
}
|
||||
|
||||
async exportPlugin(id: number) {
|
||||
const info = await this.info(id);
|
||||
if (!info) {
|
||||
throw new Error("插件不存在");
|
||||
}
|
||||
const metadata = yaml.load(info.metadata || "");
|
||||
const extra = yaml.load(info.extra || "");
|
||||
const content = info.content;
|
||||
delete info.metadata;
|
||||
delete info.extra;
|
||||
delete info.content;
|
||||
delete info.id;
|
||||
delete info.createTime;
|
||||
delete info.updateTime;
|
||||
const plugin = {
|
||||
...info,
|
||||
...metadata,
|
||||
...extra,
|
||||
content
|
||||
};
|
||||
|
||||
return yaml.dump(plugin) as string;
|
||||
}
|
||||
|
||||
async importPlugin(req: PluginImportReq) {
|
||||
|
||||
const loaded = yaml.load(req.content);
|
||||
if (!loaded) {
|
||||
throw new Error("插件内容不能为空");
|
||||
}
|
||||
delete loaded.id
|
||||
|
||||
const old = await this.repository.findOne({
|
||||
where: {
|
||||
name: loaded.name,
|
||||
author: loaded.author
|
||||
}
|
||||
});
|
||||
|
||||
const metadata = {
|
||||
input: loaded.input,
|
||||
output: loaded.output
|
||||
};
|
||||
const extra = {
|
||||
dependPlugins: loaded.dependPlugins,
|
||||
default: loaded.default,
|
||||
showRunStrategy: loaded.showRunStrategy
|
||||
};
|
||||
|
||||
const pluginEntity = {
|
||||
...loaded,
|
||||
metadata: yaml.dump(metadata),
|
||||
extra: yaml.dump(extra),
|
||||
content: req.content,
|
||||
disabled: false
|
||||
};
|
||||
if (!pluginEntity.pluginType) {
|
||||
throw new Error(`插件类型不能为空`);
|
||||
}
|
||||
|
||||
if (old) {
|
||||
if (!req.override) {
|
||||
throw new Error(`插件${loaded.author}/${loaded.name}已存在`);
|
||||
}
|
||||
//update
|
||||
pluginEntity.id = old.id;
|
||||
await this.update(pluginEntity);
|
||||
} else {
|
||||
//add
|
||||
const { id } = await this.add(pluginEntity);
|
||||
pluginEntity.id = id;
|
||||
}
|
||||
return {
|
||||
id: pluginEntity.id
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
||||
import {SiteHidden, SysSafeSetting, SysSettingsService} from "@certd/lib-server";
|
||||
import fs from "fs";
|
||||
import {logger, utils} from "@certd/basic";
|
||||
import {cloneDeep, merge} from "lodash-es";
|
||||
|
||||
|
||||
export class HiddenStatus {
|
||||
|
||||
|
||||
isHidden = false;
|
||||
lastRequestTime = 0;
|
||||
intervalId: any = null;
|
||||
|
||||
hasUnHiddenFile() {
|
||||
if (fs.existsSync(`./data/.unhidden`)) {
|
||||
fs.unlinkSync(`./data/.unhidden`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
updateRequestTime() {
|
||||
this.lastRequestTime = Date.now();
|
||||
}
|
||||
|
||||
startCheck(autoHiddenTimes = 5) {
|
||||
this.stopCheck()
|
||||
this.intervalId = setInterval(() => {
|
||||
//默认5分钟后自动隐藏
|
||||
if (!this.isHidden && Date.now() - this.lastRequestTime > 1000 * 60 * autoHiddenTimes) {
|
||||
this.isHidden = true;
|
||||
}
|
||||
}, 1000 * 60)
|
||||
}
|
||||
|
||||
stopCheck() {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const hiddenStatus = new HiddenStatus();
|
||||
|
||||
|
||||
@Provide('safeService')
|
||||
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||
export class SafeService {
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
|
||||
async reloadHiddenStatus(immediate = false) {
|
||||
const hidden = await this.getHiddenSetting()
|
||||
if (hidden.enabled) {
|
||||
logger.error("启动站点隐藏");
|
||||
hiddenStatus.isHidden = false
|
||||
if (immediate) {
|
||||
hiddenStatus.isHidden = true;
|
||||
}
|
||||
const autoHiddenTimes = hidden.autoHiddenTimes || 5;
|
||||
hiddenStatus.startCheck(autoHiddenTimes);
|
||||
} else {
|
||||
logger.error("关闭站点隐藏");
|
||||
hiddenStatus.isHidden = false;
|
||||
hiddenStatus.stopCheck()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getHiddenSetting(): Promise<SiteHidden> {
|
||||
const safeSetting = await this.getSafeSetting()
|
||||
return safeSetting.hidden || {enabled: false}
|
||||
}
|
||||
|
||||
async getSafeSetting() {
|
||||
return await this.sysSettingsService.getSetting<SysSafeSetting>(SysSafeSetting)
|
||||
}
|
||||
|
||||
async hiddenImmediately() {
|
||||
return hiddenStatus.isHidden = true
|
||||
}
|
||||
|
||||
async saveSafeSetting(body: SysSafeSetting) {
|
||||
|
||||
// 更新hidden配置
|
||||
if (body.hidden.openPassword) {
|
||||
body.hidden.openPassword = utils.hash.md5(body.hidden.openPassword);
|
||||
}
|
||||
const blankSetting = new SysSafeSetting()
|
||||
const setting = await this.getSafeSetting()
|
||||
const newSetting = merge(blankSetting, cloneDeep(setting), body);
|
||||
if (newSetting.hidden?.enabled && !newSetting.hidden?.openPassword) {
|
||||
throw new Error("首次设置需要填写解锁密码")
|
||||
}
|
||||
|
||||
if(isNaN(newSetting.hidden.autoHiddenTimes) || newSetting.hidden.autoHiddenTimes < 1){
|
||||
newSetting.hidden.autoHiddenTimes = 1
|
||||
}
|
||||
|
||||
await this.sysSettingsService.saveSetting(newSetting);
|
||||
|
||||
await this.reloadHiddenStatus(false)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
//@ts-ignore
|
||||
import { HcClient } from "@huaweicloud/huaweicloud-sdk-core/HcClient";
|
||||
|
||||
export class HuaweiCcmClient {
|
||||
//@ts-ignore
|
||||
hcClient: HcClient;
|
||||
|
||||
//@ts-ignore
|
||||
constructor(client: HcClient) {
|
||||
this.hcClient = client;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
importCertificate(req: {
|
||||
name: string,
|
||||
certificate: string,
|
||||
private_key: string,
|
||||
duplicate_check: boolean,
|
||||
}) {
|
||||
const options: any = {
|
||||
method: "POST",
|
||||
url: "/v3/scm/certificates/import",
|
||||
pathParams: {},
|
||||
queryParams: {},
|
||||
contentType: "application/json",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
/**
|
||||
* name
|
||||
* string 是
|
||||
* 证书名称。字符长度为3~63位, 请输入英文字符,数字,下划线,中划线,英文句点。
|
||||
*
|
||||
* certificate
|
||||
* string 是
|
||||
* 证书内容,可包含中间证书及根证书。若certificate_chain字段传入证书链,则该字段只取证书本身。回车换行需要使用转义字符\n或者\r\n替换。
|
||||
*
|
||||
* certificate_chain
|
||||
* string 否
|
||||
* 证书链,非必填,可通过certificate字段传入。回车换行需要使用转义字符\n或者\r\n替换。
|
||||
*
|
||||
* private_key
|
||||
* string 是
|
||||
* 证书私钥。
|
||||
* 不能上传带有口令保护的私钥,回车换行需要使用转义字符\n或者\r\n替换。
|
||||
*
|
||||
* duplicate_check
|
||||
*/
|
||||
data: {
|
||||
...req
|
||||
}
|
||||
|
||||
};
|
||||
// @ts-ignore
|
||||
options["responseHeaders"] = ["X-Request-Id"];
|
||||
return this.hcClient.sendRequest(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
name: HuaweiUploadToCCM
|
||||
title: 华为-上传证书至CCM
|
||||
desc: 上传证书至华为云CCM
|
||||
type: plugin
|
||||
pluginType: deploy
|
||||
author: certd
|
||||
version: 1.0.0
|
||||
icon: 'svg:icon-huawei'
|
||||
group: huawei
|
||||
default:
|
||||
strategy:
|
||||
runStrategy: 1,
|
||||
input: # 插件的输入参数
|
||||
cert:
|
||||
title: 前置任务证书
|
||||
helper: 请选择前置任务产生的证书 # 帮助说明
|
||||
component:
|
||||
name: output-selector # 输入组件名称
|
||||
vModel: modelValue # 组件参数
|
||||
from:
|
||||
- ApplyCert
|
||||
- ApplyCertLego
|
||||
- ApplyCertUpload
|
||||
required: true
|
||||
certDomains:
|
||||
title: 当前证书域名
|
||||
component:
|
||||
name: cert-domains-getter
|
||||
mergeScript: |
|
||||
return {
|
||||
component:{
|
||||
inputKey: ctx.compute(({form})=>{
|
||||
return form.cert
|
||||
}),
|
||||
}
|
||||
}
|
||||
required: true
|
||||
accessId:
|
||||
title: Access授权
|
||||
helper: xxxx的授权
|
||||
component:
|
||||
name: access-selector # 授权选择组件名称
|
||||
type: aliyun # 授权类型
|
||||
required: true
|
||||
key1:
|
||||
title: 输入示例1
|
||||
required: false
|
||||
key2:
|
||||
title: 可选项
|
||||
component:
|
||||
name: a-select
|
||||
vMode: value
|
||||
options:
|
||||
- value: "1"
|
||||
label: 选项1
|
||||
- value: "2"
|
||||
label: 选项2
|
||||
required: false
|
||||
#output: # 输出参数,一般插件都不需要配置此项
|
||||
# outputName:
|
||||
#
|
||||
@@ -0,0 +1,82 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||
import { HuaweiAccess } from "../../access/index.js";
|
||||
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
|
||||
import { createCertDomainGetterInputDefine } from "@certd/plugin-lib";
|
||||
import { resetLogConfigure } from "@certd/basic";
|
||||
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'HauweiUploadToCCM',
|
||||
title: '华为云-上传证书至CCM',
|
||||
icon: 'svg:icon-huawei',
|
||||
group: pluginGroups.huawei.key,
|
||||
desc: '上传证书到华为云CCM',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class HauweiUploadToCCM extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames]
|
||||
},
|
||||
required: true
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
@TaskInput({
|
||||
title: "Access授权",
|
||||
helper: "华为云授权AccessKeyId、AccessKeySecret",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "huawei"
|
||||
},
|
||||
required: true
|
||||
})
|
||||
accessId!: string;
|
||||
@TaskOutput({
|
||||
title: '华为云CertId',
|
||||
})
|
||||
huaweiCertId!: string;
|
||||
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info("开始部署证书到华为云CCM");
|
||||
const { client } = await this.getCcmClient();
|
||||
|
||||
const res = await client.importCertificate({
|
||||
name: this.appendTimeSuffix("certd"),
|
||||
certificate: this.cert.crt,
|
||||
private_key: this.cert.key,
|
||||
duplicate_check: false
|
||||
});
|
||||
this.huaweiCertId = res.certificate_id;
|
||||
this.logger.info(`上传证书到华为云ccm完成,certificate_id:${this.huaweiCertId}`);
|
||||
}
|
||||
|
||||
async getCcmClient() {
|
||||
const access = await this.getAccess<HuaweiAccess>(this.accessId);
|
||||
const { BasicCredentials } = await import("@huaweicloud/huaweicloud-sdk-core");
|
||||
const { ClientBuilder } = await import("@huaweicloud/huaweicloud-sdk-core/ClientBuilder.js");
|
||||
//@ts-ignore
|
||||
const {HuaweiCcmClient} = await import("./ccm-client.js")
|
||||
|
||||
//恢复华为云把log4j的config改了的问题
|
||||
resetLogConfigure();
|
||||
|
||||
const credentials = new BasicCredentials().withAk(access.accessKeyId).withSk(access.accessKeySecret);
|
||||
const client = new ClientBuilder((hcClient) => {
|
||||
return new HuaweiCcmClient(hcClient);
|
||||
}).withCredential(credentials).withEndpoint("https://scm.cn-north-4.myhuaweicloud.com").build();
|
||||
return {
|
||||
client
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -128,11 +128,12 @@ export class DeployCertToTencentAll extends AbstractTaskPlugin {
|
||||
});
|
||||
|
||||
let certId:string = null
|
||||
if (typeof certId === 'string') {
|
||||
certId = this.tencentCertId as string;
|
||||
if (typeof this.tencentCertId === 'string') {
|
||||
certId = this.tencentCertId as string;
|
||||
} else if (this.tencentCertId && typeof this.tencentCertId === 'object') {
|
||||
certId = await this.uploadToTencent(access, this.tencentCertId as CertInfo);
|
||||
} else {
|
||||
//上传
|
||||
certId = await this.uploadToTencent(access,this.tencentCertId as CertInfo);
|
||||
throw new Error('无效的证书输入类型');
|
||||
}
|
||||
|
||||
const params = {
|
||||
|
||||
173
pnpm-lock.yaml
generated
173
pnpm-lock.yaml
generated
@@ -46,7 +46,7 @@ importers:
|
||||
packages/core/acme-client:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../basic
|
||||
'@peculiar/x509':
|
||||
specifier: ^1.11.0
|
||||
@@ -204,11 +204,11 @@ importers:
|
||||
packages/core/pipeline:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../basic
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.33.0
|
||||
version: link:../../pro/plus-core
|
||||
specifier: ^1.33.2
|
||||
version: 1.33.2
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
version: 1.11.13
|
||||
@@ -412,7 +412,7 @@ importers:
|
||||
packages/libs/lib-k8s:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/basic
|
||||
'@kubernetes/client-node':
|
||||
specifier: 0.21.0
|
||||
@@ -452,17 +452,17 @@ importers:
|
||||
packages/libs/lib-server:
|
||||
dependencies:
|
||||
'@certd/acme-client':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/acme-client
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/basic
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.33.0
|
||||
version: link:../../pro/plus-core
|
||||
specifier: ^1.33.2
|
||||
version: 1.33.2
|
||||
'@midwayjs/cache':
|
||||
specifier: ~3.14.0
|
||||
version: 3.14.0
|
||||
@@ -604,16 +604,16 @@ importers:
|
||||
packages/plugins/plugin-cert:
|
||||
dependencies:
|
||||
'@certd/acme-client':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/acme-client
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/basic
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-lib':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../plugin-lib
|
||||
'@google-cloud/publicca':
|
||||
specifier: ^1.3.0
|
||||
@@ -680,10 +680,10 @@ importers:
|
||||
specifier: ^1.7.10
|
||||
version: 1.8.0
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/basic
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/pipeline
|
||||
'@kubernetes/client-node':
|
||||
specifier: 0.21.0
|
||||
@@ -771,19 +771,19 @@ importers:
|
||||
packages/pro/commercial-core:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../core/basic
|
||||
'@certd/lib-server':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../libs/lib-server
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-plus':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../plugin-plus
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../plus-core
|
||||
'@midwayjs/core':
|
||||
specifier: ~3.20.3
|
||||
@@ -868,22 +868,22 @@ importers:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../core/basic
|
||||
'@certd/lib-k8s':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../libs/lib-k8s
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-cert':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../plugins/plugin-cert
|
||||
'@certd/plugin-lib':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../plugins/plugin-lib
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../plus-core
|
||||
ali-oss:
|
||||
specifier: ^6.21.0
|
||||
@@ -980,7 +980,7 @@ importers:
|
||||
packages/pro/plus-core:
|
||||
dependencies:
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.32.0
|
||||
version: link:../../core/basic
|
||||
dayjs:
|
||||
specifier: ^1.11.7
|
||||
@@ -1223,6 +1223,9 @@ importers:
|
||||
sortablejs:
|
||||
specifier: ^1.15.3
|
||||
version: 1.15.6
|
||||
spark-md5:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
tailwind-merge:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
@@ -1267,10 +1270,10 @@ importers:
|
||||
version: 0.1.3(zod@3.24.2)
|
||||
devDependencies:
|
||||
'@certd/lib-iframe':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../libs/lib-iframe
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/pipeline
|
||||
'@rollup/plugin-commonjs':
|
||||
specifier: ^25.0.7
|
||||
@@ -1450,44 +1453,44 @@ importers:
|
||||
specifier: ^3.705.0
|
||||
version: 3.758.0(aws-crt@1.25.3)
|
||||
'@certd/acme-client':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/acme-client
|
||||
'@certd/basic':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/basic
|
||||
'@certd/commercial-core':
|
||||
specifier: ^1.33.0
|
||||
version: link:../../pro/commercial-core
|
||||
specifier: ^1.33.2
|
||||
version: 1.33.2(better-sqlite3@11.8.1)(encoding@0.1.13)(mysql2@3.14.0)(pg@8.13.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.80)(typescript@5.8.2))
|
||||
'@certd/jdcloud':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../libs/lib-jdcloud
|
||||
'@certd/lib-huawei':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../libs/lib-huawei
|
||||
'@certd/lib-k8s':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../libs/lib-k8s
|
||||
'@certd/lib-server':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../libs/lib-server
|
||||
'@certd/midway-flyway-js':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../libs/midway-flyway-js
|
||||
'@certd/pipeline':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../core/pipeline
|
||||
'@certd/plugin-cert':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../plugins/plugin-cert
|
||||
'@certd/plugin-lib':
|
||||
specifier: ^1.33.0
|
||||
specifier: ^1.33.2
|
||||
version: link:../../plugins/plugin-lib
|
||||
'@certd/plugin-plus':
|
||||
specifier: ^1.33.0
|
||||
version: link:../../pro/plugin-plus
|
||||
specifier: ^1.33.2
|
||||
version: 1.33.2(encoding@0.1.13)
|
||||
'@certd/plus-core':
|
||||
specifier: ^1.33.0
|
||||
version: link:../../pro/plus-core
|
||||
specifier: ^1.33.2
|
||||
version: 1.33.2
|
||||
'@corsinvest/cv4pve-api-javascript':
|
||||
specifier: ^8.3.0
|
||||
version: 8.3.0
|
||||
@@ -2617,6 +2620,15 @@ packages:
|
||||
'@better-scroll/zoom@2.5.1':
|
||||
resolution: {integrity: sha512-aGvFY5ooeZWS4RcxQLD+pGLpQHQxpPy0sMZV3yadcd2QK53PK9gS4Dp+BYfRv8lZ4/P2LoNEhr6Wq1DN6+uPlA==}
|
||||
|
||||
'@certd/commercial-core@1.33.2':
|
||||
resolution: {integrity: sha512-LTRvwRAkMEU+knG+/eA8QbyK3EE2Z2eyn4763ILadCY/qHLAWqxg7NUD+fwsuAoByvsr3l5qLPPOF73p5s1iEA==}
|
||||
|
||||
'@certd/plugin-plus@1.33.2':
|
||||
resolution: {integrity: sha512-x8qdJ1qtYfqVNBQ3uWJuFVYUHHnteoaH/3vnYKjgAnEAcosruZAz8h5RwwOjDhR+Vsdda/sSRdjlTCVDLidHZg==}
|
||||
|
||||
'@certd/plus-core@1.33.2':
|
||||
resolution: {integrity: sha512-CsD+/P3Ycne3KzxrZAkFXGpCO9ZQNY4p28WAA2XfKQ6sLIYLuuDBP18V/E7EygfJEZ/c9KJWMkYgx/4wGGvb6w==}
|
||||
|
||||
'@colors/colors@1.5.0':
|
||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
@@ -14791,6 +14803,75 @@ snapshots:
|
||||
dependencies:
|
||||
'@better-scroll/core': 2.5.1
|
||||
|
||||
'@certd/commercial-core@1.33.2(better-sqlite3@11.8.1)(encoding@0.1.13)(mysql2@3.14.0)(pg@8.13.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.80)(typescript@5.8.2))':
|
||||
dependencies:
|
||||
'@certd/basic': link:packages/core/basic
|
||||
'@certd/lib-server': link:packages/libs/lib-server
|
||||
'@certd/pipeline': link:packages/core/pipeline
|
||||
'@certd/plugin-plus': 1.33.2(encoding@0.1.13)
|
||||
'@certd/plus-core': 1.33.2
|
||||
'@midwayjs/core': 3.20.3
|
||||
'@midwayjs/koa': 3.20.3
|
||||
'@midwayjs/logger': 3.4.2
|
||||
'@midwayjs/typeorm': 3.20.3
|
||||
alipay-sdk: 4.13.0
|
||||
dayjs: 1.11.13
|
||||
typeorm: 0.3.21(better-sqlite3@11.8.1)(mysql2@3.14.0)(pg@8.13.3)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@18.19.80)(typescript@5.8.2))
|
||||
wechatpay-node-v3: 2.2.1
|
||||
transitivePeerDependencies:
|
||||
- '@google-cloud/spanner'
|
||||
- '@sap/hana-client'
|
||||
- better-sqlite3
|
||||
- encoding
|
||||
- hdb-pool
|
||||
- ioredis
|
||||
- mongodb
|
||||
- mssql
|
||||
- mysql2
|
||||
- oracledb
|
||||
- pg
|
||||
- pg-native
|
||||
- pg-query-stream
|
||||
- proxy-agent
|
||||
- redis
|
||||
- reflect-metadata
|
||||
- sql.js
|
||||
- sqlite3
|
||||
- supports-color
|
||||
- ts-node
|
||||
- typeorm-aurora-data-api-driver
|
||||
|
||||
'@certd/plugin-plus@1.33.2(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@alicloud/pop-core': 1.8.0
|
||||
'@baiducloud/sdk': 1.0.2
|
||||
'@certd/basic': link:packages/core/basic
|
||||
'@certd/lib-k8s': link:packages/libs/lib-k8s
|
||||
'@certd/pipeline': link:packages/core/pipeline
|
||||
'@certd/plugin-cert': link:packages/plugins/plugin-cert
|
||||
'@certd/plugin-lib': link:packages/plugins/plugin-lib
|
||||
'@certd/plus-core': 1.33.2
|
||||
ali-oss: 6.22.0
|
||||
baidu-aip-sdk: 4.16.16
|
||||
basic-ftp: 5.0.5
|
||||
cos-nodejs-sdk-v5: 2.14.6
|
||||
crypto-js: 4.2.0
|
||||
dayjs: 1.11.13
|
||||
form-data: 4.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
jsencrypt: 3.3.2
|
||||
qiniu: 7.14.0
|
||||
tencentcloud-sdk-nodejs: 4.0.1045(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- proxy-agent
|
||||
- supports-color
|
||||
|
||||
'@certd/plus-core@1.33.2':
|
||||
dependencies:
|
||||
'@certd/basic': link:packages/core/basic
|
||||
dayjs: 1.11.13
|
||||
|
||||
'@colors/colors@1.5.0':
|
||||
optional: true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user