Compare commits

..

97 Commits

Author SHA1 Message Date
刘祥超
f2e14cf0a6 版本号修改为1.0.4 2023-04-24 10:17:05 +08:00
刘祥超
bc9b6d1595 为集群选择DNS域名时自动排序并不显示已下线域名 2023-04-24 09:38:57 +08:00
刘祥超
fd11b62390 更新components.js 2023-04-23 20:13:23 +08:00
刘祥超
8db00a5ab5 远程升级API节点时自动上传边缘节点安装文件 2023-04-23 19:48:07 +08:00
刘祥超
a6757e9374 申请ACME证书时可以指定平台用户 2023-04-23 15:01:43 +08:00
刘祥超
e0fe385404 证书申请任务可以使用用户筛选 2023-04-23 11:03:07 +08:00
刘祥超
f9f4258ec1 证书列表可以使用用户筛选 2023-04-23 10:44:37 +08:00
刘祥超
f939d563ad 选择证书时,如果证书列表为空,则提示可以搜索未指定用户证书 2023-04-22 12:04:04 +08:00
刘祥超
fad03add54 “网站服务”改为“网站”、“网站列表” 2023-04-21 17:51:25 +08:00
刘祥超
9ab6dab081 SESSION读取失败时不强制重新登录 2023-04-21 15:39:51 +08:00
刘祥超
5a55268830 修复在HTTP/1.x下开多个窗口访问非常慢的问题 2023-04-19 21:03:46 +08:00
刘祥超
fc3769239d 节点列表页CPU、内存使用比例增加百分号 2023-04-19 19:26:49 +08:00
刘祥超
c9fb3153eb 安全设置增加“检查客户端指纹"和"检查客户端区域"选项 2023-04-19 18:25:10 +08:00
刘祥超
a31f9ed9c5 节点列表负载数据如果是0,则显示0.00 2023-04-18 11:23:49 +08:00
刘祥超
2f75828ba4 优化滚动条在firefox上的显示 2023-04-11 16:33:22 +08:00
刘祥超
89679ec358 版本号改为1.1.0 2023-04-10 21:03:41 +08:00
刘祥超
98f77f52df Update Dockerfile 2023-04-10 09:20:59 +08:00
刘祥超
ca647d44bb 版本号修改为1.0.1 2023-04-10 09:17:37 +08:00
刘祥超
838b7dab5b Update Dockerfile 2023-04-10 08:48:09 +08:00
刘祥超
4f7fe247b4 更新components.js 2023-04-09 22:32:30 +08:00
刘祥超
cee20bd7d2 上传证书私钥时可以选择pem文件 2023-04-09 21:29:44 +08:00
刘祥超
f99ed0c4d9 优化grpc参数 2023-04-09 21:05:49 +08:00
刘祥超
4ae6523164 修复一个单词拼写错误 2023-04-09 20:09:12 +08:00
刘祥超
2ece764dcb 优化<combo-box>组件上下键盘键移动速度 2023-04-09 20:08:38 +08:00
刘祥超
f3bdd98af5 优化API升级处理提示文字 2023-04-09 17:29:29 +08:00
刘祥超
3c3c511c5b 版本号更改为1.0.0 2023-04-09 17:24:04 +08:00
刘祥超
45167b87e5 提交components.js 2023-04-09 17:23:51 +08:00
刘祥超
f7d5755744 提升Cookie安全性 2023-04-09 17:10:53 +08:00
刘祥超
e2adafd16b 服务配置菜单项增加配置代号 2023-04-09 15:59:37 +08:00
刘祥超
9d178b238d 审计日志列表增加级别筛选 2023-04-06 10:06:22 +08:00
刘祥超
431054a1be 自动检查管理员弱密码并提醒 2023-04-04 17:26:28 +08:00
刘祥超
5bf6428253 调整表单中注释颜色 2023-04-04 15:22:57 +08:00
刘祥超
6a4a03267b 优化文字提示 2023-04-03 10:03:48 +08:00
刘祥超
6598e16974 优化MySQL系统服务参数 2023-04-03 10:01:35 +08:00
刘祥超
b1943a4cec 优化MySQL系统服务参数 2023-04-02 18:05:54 +08:00
刘祥超
5f70d5afd3 优化文字提示 2023-04-02 10:17:56 +08:00
刘祥超
7dead36212 更新components.js 2023-04-02 10:17:47 +08:00
刘祥超
1f68a7830b 集群服务设置增加“支持低版本HTTP”选项 2023-04-01 09:51:44 +08:00
刘祥超
42c5b7a181 修复在未初始化缓存设置时添加缓存条件产生的panic错误 2023-03-31 17:42:33 +08:00
刘祥超
6596b47e54 证书即将到期提示 2023-03-28 16:52:21 +08:00
刘祥超
02e4a3e244 安装MySQL时指定 innodb_buffer_pool_size 为系统内存的一半 2023-03-28 11:42:19 +08:00
刘祥超
d9af90c76b 优化代码 2023-03-27 17:37:48 +08:00
刘祥超
a54405f24f 自动安装mysql时调整innodb_sort_buffer_size参数值 2023-03-27 17:22:13 +08:00
刘祥超
2b39c5d517 优化代码 2023-03-27 16:31:52 +08:00
刘祥超
a09d295948 优化<combo-box>组件 2023-03-27 11:28:06 +08:00
刘祥超
2c0b0be8c4 优化<combo-box>组件 2023-03-27 11:01:28 +08:00
刘祥超
b289877273 README中增加企业版链接 2023-03-26 20:50:45 +08:00
刘祥超
869d54b9a8 如果管理系统同时设置了HTTP和HTTPS端口,那么访问HTTP登录页时自动跳转到HTTPS地址 2023-03-26 15:54:14 +08:00
刘祥超
df48ae8316 优化缓存策略相关文字提示 2023-03-26 12:23:12 +08:00
刘祥超
e42cf2a420 创建服务和修改服务HTTPS设置时也支持批量上传证书 2023-03-26 12:00:41 +08:00
刘祥超
bd899b649d 创建服务时如果选择了所属用户,则证书列表中默认显示该用户的证书 2023-03-26 10:36:47 +08:00
刘祥超
2bc43ee2a5 实现自动匹配证书和批量选择证书功能 2023-03-25 20:50:27 +08:00
刘祥超
48e2907426 开源版本默认每次只能上传20个证书文件 2023-03-24 15:29:51 +08:00
刘祥超
4e33cce128 批量上传时如果证书文件和私钥文件不一致,也会提示错误 2023-03-24 15:13:28 +08:00
刘祥超
6658028f90 增加批量上传证书功能 2023-03-24 15:05:31 +08:00
刘祥超
3fe67bb179 优化系统设置--Web服务文字提示。 2023-03-24 10:53:40 +08:00
刘祥超
ce9a2d0cc3 证书内容输入框支持拖动文件上传 2023-03-24 10:17:05 +08:00
刘祥超
45b7c7af15 优化界面显示 2023-03-23 15:47:11 +08:00
刘祥超
3116aaeccb 节点列表节点IP超出4个后可以自动隐藏和显示 2023-03-23 14:50:12 +08:00
刘祥超
bfd5517c6c 优化创建网站服务界面 2023-03-22 17:52:22 +08:00
刘祥超
c83598a4f9 优化创建网站服务界面 2023-03-21 16:56:45 +08:00
刘祥超
3dc983871f 优化CNAME查询程序 2023-03-21 11:41:40 +08:00
刘祥超
ac6fcefffd 修改<columns-grid>组件文件名 2023-03-21 11:00:35 +08:00
刘祥超
094080cc9f 优化界面文字 2023-03-20 21:32:18 +08:00
刘祥超
d2dff968fb 自动安装MySQL时自动生成所需的动态库软链接 2023-03-20 21:32:10 +08:00
刘祥超
496f82a01f 日志数据库节点详情中密码使用星号(*)代替 2023-03-20 17:00:54 +08:00
刘祥超
9293c3e861 优化界面 2023-03-20 12:10:08 +08:00
刘祥超
da7d42edd5 修复<file-textarea>组件在firefox上无法复制内容的Bug 2023-03-20 09:43:21 +08:00
刘祥超
87b96d6526 SSH认证添加私钥时可以从私钥文件中直接拖入 2023-03-19 17:49:07 +08:00
刘祥超
a371821ff8 创建SSH认证私钥时校验私钥内容 2023-03-19 17:48:43 +08:00
刘祥超
748cb6eb8f 更新相关库 2023-03-19 17:46:28 +08:00
刘祥超
05bee642e9 配置初始化时增加context参数 2023-03-18 22:12:22 +08:00
刘祥超
081be04592 优化文字提示 2023-03-18 16:08:25 +08:00
刘祥超
f77518d086 生成节点DNS解析时区分节点是否已安装 2023-03-18 16:05:46 +08:00
刘祥超
35e6202b3c 创建节点时自动从节点名称中提取节点IP 2023-03-18 11:36:10 +08:00
刘祥超
1d84ea6ab9 更新components.js 2023-03-18 11:35:42 +08:00
刘祥超
df461d81d8 版本号更改为0.6.5 2023-03-17 15:53:01 +08:00
刘祥超
821d5aa595 优化节点列表连接数显示 2023-03-17 15:25:07 +08:00
刘祥超
c25bd71592 节点列表带宽保存两位小数数字 2023-03-17 15:20:53 +08:00
刘祥超
7e9224680e 当HTTP和HTTPS端口冲突时提示用户 2023-03-17 11:11:54 +08:00
刘祥超
bc89116d3d 编译脚本中暂时不删除.css.map和.js.map文件 2023-03-17 10:20:11 +08:00
刘祥超
aabe75ee13 优化文字提示 2023-03-17 10:19:38 +08:00
刘祥超
8903adc523 优化文字提示 2023-03-16 10:09:17 +08:00
刘祥超
61d8c8cd39 优化节点列表统计项宽度 2023-03-16 09:48:45 +08:00
刘祥超
2e1d991e0c 在开源版本看板增加检查是否为Plus的钩子 2023-03-16 09:37:16 +08:00
刘祥超
5a57167832 更新Dockerfile中的版本号 2023-03-16 09:27:36 +08:00
刘祥超
c3df181dcc 版本号改为0.6.4.2 2023-03-16 08:55:12 +08:00
刘祥超
699ea47ac5 更新components.js 2023-03-16 08:55:01 +08:00
刘祥超
fba69f3109 节点列表增加连接数列 2023-03-15 17:57:32 +08:00
刘祥超
063583dda1 调整节点任务和DNS同步任务弹窗高度 2023-03-15 16:24:14 +08:00
刘祥超
a31ca5cfb6 提高消息弹窗高度 2023-03-15 15:48:35 +08:00
刘祥超
c0a13305ad 管理系统增加两种背景风格 2023-03-15 15:35:11 +08:00
刘祥超
9d8cbf87dc URL跳转之域名跳转增加跳转后域名校验 2023-03-15 15:07:55 +08:00
刘祥超
009f7da26b 优化看板中的统计图表 2023-03-15 14:39:45 +08:00
刘祥超
f1d0359dc4 优化代码 2023-03-13 16:24:37 +08:00
刘祥超
473baa8c5b 版本号改为0.6.5 2023-03-13 16:16:26 +08:00
刘祥超
1a5dda6c72 Update Dockerfile 2023-03-13 12:12:44 +08:00
205 changed files with 3864 additions and 1024 deletions

View File

@@ -56,5 +56,8 @@
## 联系我们
有什么问题和建议都可以加入QQ群 `659832182` 或者 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9) 。
## 企业版
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
## 感谢
* 感谢 [Gitee](https://gitee.com/) 提供国内源代码托管平台

View File

@@ -105,8 +105,8 @@ function build() {
find "$DIST" -name ".DS_Store" -delete
find "$DIST" -name ".gitignore" -delete
find "$DIST" -name "*.less" -delete
find "$DIST" -name "*.css.map" -delete
find "$DIST" -name "*.js.map" -delete
#find "$DIST" -name "*.css.map" -delete
#find "$DIST" -name "*.js.map" -delete
# zip
echo "zip files ..."

View File

@@ -1,7 +1,7 @@
FROM alpine:latest
LABEL maintainer="iwind.liu@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 0.6.4
ENV VERSION 1.0.1
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"

7
go.mod
View File

@@ -15,7 +15,8 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tealeg/xlsx/v3 v3.2.3
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
golang.org/x/sys v0.5.0
golang.org/x/crypto v0.7.0
golang.org/x/sys v0.6.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -33,8 +34,8 @@ require (
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

19
go.sum
View File

@@ -74,10 +74,6 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20230207032553-d6dcde0cd518 h1:zuWjQ57zc67ZSTHpxNK95JYoa9Ph/JRSnapsTY/hlhQ=
github.com/iwind/TeaGo v0.0.0-20230207032553-d6dcde0cd518/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230303070415-9d0689db6456 h1:xv3AVaxuwjThkBDptAfsFSmuHQIrRrvt8BRaekWnsvs=
github.com/iwind/TeaGo v0.0.0-20230303070415-9d0689db6456/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470 h1:TuRxvKRv9PxKVijWOkUnZm5TeanQqWGUJyPx9u6cra4=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
@@ -142,6 +138,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
@@ -166,8 +164,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -195,15 +193,16 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@@ -167,7 +167,7 @@ func AllModuleMaps() []maps.Map {
"url": "/dashboard",
},
{
"name": "网站服务",
"name": "网站列表",
"code": AdminModuleCodeServer,
"url": "/servers",
},

View File

@@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
return &v, nil
}
@@ -83,7 +83,12 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return sharedSecurityConfig, nil
}
config := &systemconfigs.SecurityConfig{}
var config = &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,
CheckClientFingerprint: false,
CheckClientRegion: true,
}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[SECURITY_MANAGER]" + err.Error())
@@ -100,7 +105,9 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
return &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,
Frame: FrameSameOrigin,
AllowLocal: true,
CheckClientFingerprint: false,
CheckClientRegion: true,
}
}

View File

@@ -21,9 +21,9 @@ type APIConfig struct {
// LoadAPIConfig 加载API配置
func LoadAPIConfig() (*APIConfig, error) {
// 候选文件
localFile := Tea.ConfigFile("api.yaml")
isFromLocal := false
paths := []string{localFile}
var localFile = Tea.ConfigFile("api.yaml")
var isFromLocal = false
var paths = []string{localFile}
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/api.yaml")
@@ -45,7 +45,7 @@ func LoadAPIConfig() (*APIConfig, error) {
return nil, err
}
config := &APIConfig{}
var config = &APIConfig{}
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err

View File

@@ -1,9 +1,9 @@
package teaconst
const (
Version = "0.6.4.1"
Version = "1.0.4"
APINodeVersion = "0.6.4.1"
APINodeVersion = "1.0.4"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -2,6 +2,11 @@
package teaconst
import (
"os"
"strings"
)
var (
IsRecoverMode = false
@@ -10,4 +15,18 @@ var (
NewVersionCode = "" // 有新的版本
NewVersionDownloadURL = "" // 新版本下载地址
IsMain = checkMain()
)
// 检查是否为主程序
func checkMain() bool {
if len(os.Args) == 1 ||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
return true
}
exe, _ := os.Executable()
return strings.HasSuffix(exe, ".test") ||
strings.HasSuffix(exe, ".test.exe") ||
strings.Contains(exe, "___")
}

View File

@@ -85,6 +85,9 @@ func (this *AdminNode) Run() {
// 启动API节点
this.startAPINode()
// 启动IP库
this.startIPLibrary()
// 启动Web服务
sessionManager, err := NewSessionManager()
if err != nil {

View File

@@ -0,0 +1,18 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/iwind/TeaGo/logs"
)
// 启动IP库
func (this *AdminNode) startIPLibrary() {
logs.Println("[NODE]initializing ip library ...")
err := iplibrary.InitDefault()
if err != nil {
logs.Println("[NODE]initialize ip library failed: "+err.Error())
}
}

View File

@@ -11,6 +11,7 @@ import (
"strings"
)
// SessionManager SESSION管理
type SessionManager struct {
life uint
}
@@ -39,6 +40,7 @@ func (this *SessionManager) Read(sid string) map[string]string {
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
if err != nil {
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
result["@error"] = err.Error()
return result
}

View File

@@ -544,8 +544,11 @@ func (this *RPCClient) init() error {
}
var conn *grpc.ClientConn
var callOptions = grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024),
grpc.UseCompressor(gzip.Name))
var callOptions = grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(128<<20),
grpc.MaxCallSendMsgSize(128<<20),
grpc.UseCompressor(gzip.Name),
)
if u.Scheme == "http" {
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
} else if u.Scheme == "https" {

View File

@@ -2,6 +2,9 @@ package rpc
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"strings"
"sync"
)
@@ -28,3 +31,23 @@ func SharedRPC() (*RPCClient, error) {
sharedRPC = client
return sharedRPC, nil
}
// IsConnError 是否为连接错误
func IsConnError(err error) bool {
if err == nil {
return false
}
// 检查是否为连接错误
statusErr, ok := status.FromError(err)
if ok {
var errorCode = statusErr.Code()
return errorCode == codes.Unavailable || errorCode == codes.Canceled
}
if strings.Contains(err.Error(), "code = Canceled") {
return true
}
return false
}

View File

@@ -0,0 +1,79 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package apinodeutils
import (
"crypto/md5"
"fmt"
"io"
"os"
)
// DeployFile 部署文件描述
type DeployFile struct {
OS string
Arch string
Version string
Path string
}
// Sum 计算概要
func (this *DeployFile) Sum() (string, error) {
fp, err := os.Open(this.Path)
if err != nil {
return "", err
}
defer func() {
_ = fp.Close()
}()
m := md5.New()
buffer := make([]byte, 128*1024)
for {
n, err := fp.Read(buffer)
if err != nil {
if err == io.EOF {
break
}
return "", err
}
_, err = m.Write(buffer[:n])
if err != nil {
return "", err
}
}
sum := m.Sum(nil)
return fmt.Sprintf("%x", sum), nil
}
// Read 读取一个片段数据
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
fp, err := os.Open(this.Path)
if err != nil {
return nil, offset, err
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
return nil, offset, err
}
if offset >= stat.Size() {
return nil, offset, io.EOF
}
_, err = fp.Seek(offset, io.SeekStart)
if err != nil {
return nil, offset, err
}
buffer := make([]byte, 128*1024)
n, err := fp.Read(buffer)
if err != nil {
return nil, offset, err
}
return buffer[:n], offset + int64(n), nil
}

View File

@@ -0,0 +1,96 @@
package apinodeutils
import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
stringutil "github.com/iwind/TeaGo/utils/string"
"regexp"
)
// DeployManager 节点部署文件管理器
// 如果节点部署文件有变化需要重启API节点以便于生效
type DeployManager struct {
dir string
}
// NewDeployManager 获取新节点部署文件管理器
func NewDeployManager() *DeployManager {
var manager = &DeployManager{
dir: Tea.Root + "/edge-api/deploy",
}
manager.LoadNodeFiles()
manager.LoadNSNodeFiles()
return manager
}
// LoadNodeFiles 加载所有边缘节点文件
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
var keyMap = map[string]*DeployFile{} // key => File
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
for _, file := range files.NewFile(this.dir).List() {
var name = file.Name()
if !reg.MatchString(name) {
continue
}
var matches = reg.FindStringSubmatch(name)
var osName = matches[1]
var arch = matches[2]
var version = matches[3]
var key = osName + "_" + arch
oldFile, ok := keyMap[key]
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
continue
}
keyMap[key] = &DeployFile{
OS: osName,
Arch: arch,
Version: version,
Path: file.Path(),
}
}
var result = []*DeployFile{}
for _, v := range keyMap {
result = append(result, v)
}
return result
}
// LoadNSNodeFiles 加载所有NS节点安装文件
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
var keyMap = map[string]*DeployFile{} // key => File
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
for _, file := range files.NewFile(this.dir).List() {
var name = file.Name()
if !reg.MatchString(name) {
continue
}
var matches = reg.FindStringSubmatch(name)
var osName = matches[1]
var arch = matches[2]
var version = matches[3]
var key = osName + "_" + arch
oldFile, ok := keyMap[key]
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
continue
}
keyMap[key] = &DeployFile{
OS: osName,
Arch: arch,
Version: version,
Path: file.Path(),
}
}
var result = []*DeployFile{}
for _, v := range keyMap {
result = append(result, v)
}
return result
}

View File

@@ -4,6 +4,7 @@ package apinodeutils
import (
"compress/gzip"
"context"
"crypto/md5"
"errors"
"fmt"
@@ -62,7 +63,36 @@ func (this *Upgrader) Upgrade() error {
if err != nil {
return err
}
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(sharedClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
// 升级边缘节点
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
if err != nil {
return err
}
// 升级NS节点
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
if err != nil {
return err
}
// 升级API节点
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
if err != nil {
return errors.New("upgrade api node failed: " + err.Error())
}
return nil
}
// Progress 查看升级进程
func (this *Upgrader) Progress() *Progress {
return this.progress
}
// 升级API节点
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
return err
}
@@ -77,7 +107,7 @@ func (this *Upgrader) Upgrade() error {
return errors.New(reason)
}
localVersion, err := localVersion()
localVersion, err := lookupLocalVersion()
if err != nil {
return errors.New("lookup version failed: " + err.Error())
}
@@ -196,6 +226,124 @@ func (this *Upgrader) Upgrade() error {
return nil
}
func (this *Upgrader) Progress() *Progress {
return this.progress
// 升级边缘节点
func (this *Upgrader) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
// 本地的
var manager = NewDeployManager()
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
for _, deployFile := range manager.LoadNodeFiles() {
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
}
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
if err != nil {
return err
}
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
}
// 对比版本
for key, deployFile := range localFileMap {
remoteDeployFile, ok := remoteFileMap[key]
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
if err != nil {
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
}
}
}
return nil
}
// 升级NS节点
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
// 本地的
var manager = NewDeployManager()
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
for _, deployFile := range manager.LoadNSNodeFiles() {
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
}
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
if err != nil {
return err
}
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
}
// 对比版本
for key, deployFile := range localFileMap {
remoteDeployFile, ok := remoteFileMap[key]
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
if err != nil {
return errors.New("upload deploy file '" + filepath.Base(deployFile.Path) + "' failed: " + err.Error())
}
}
}
return nil
}
// 上传节点文件
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
fp, err := os.Open(path)
if err != nil {
return err
}
defer func() {
_ = fp.Close()
}()
var buf = make([]byte, 128*4096)
var isFirst = true
var hash = md5.New()
for {
n, err := fp.Read(buf)
if n > 0 {
hash.Write(buf[:n])
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
Filename: filepath.Base(path),
Sum: "",
ChunkData: buf[:n],
IsFirstChunk: isFirst,
IsLastChunk: false,
})
if uploadErr != nil {
return uploadErr
}
isFirst = false
}
if err != nil {
if err == io.EOF {
err = nil
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
Filename: filepath.Base(path),
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
ChunkData: nil,
IsFirstChunk: false,
IsLastChunk: true,
})
if uploadErr != nil {
return uploadErr
}
break
}
return err
}
}
return nil
}

View File

@@ -39,7 +39,7 @@ func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool,
return false, "is directory"
}
localVersion, err := localVersion()
localVersion, err := lookupLocalVersion()
if err != nil {
return false, "lookup version failed: " + err.Error()
}
@@ -53,9 +53,7 @@ func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool,
return true, ""
}
func localVersion() (string, error) {
func lookupLocalVersion() (string, error) {
var cmd = exec.Command(apiExe(), "-V")
var output = &bytes.Buffer{}
cmd.Stdout = output
@@ -74,7 +72,6 @@ func localVersion() (string, error) {
return localVersion, nil
}
func apiExe() string {
return Tea.Root + "/edge-api/bin/edge-api"
}
}

View File

@@ -1,26 +1,40 @@
package utils
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/iwind/TeaGo/logs"
"github.com/miekg/dns"
)
// LookupCNAME 获取CNAME
func LookupCNAME(host string) (string, error) {
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
return "", err
var sharedDNSClient *dns.Client
var sharedDNSConfig *dns.ClientConfig
func init() {
if !teaconst.IsMain {
return
}
c := new(dns.Client)
m := new(dns.Msg)
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
logs.Println("ERROR: configure dns client failed: " + err.Error())
return
}
sharedDNSConfig = config
sharedDNSClient = &dns.Client{}
}
// LookupCNAME 获取CNAME
func LookupCNAME(host string) (string, error) {
var m = new(dns.Msg)
m.SetQuestion(host+".", dns.TypeCNAME)
m.RecursionDesired = true
var lastErr error
for _, serverAddr := range config.Servers {
r, _, err := c.Exchange(m, configutils.QuoteIP(serverAddr)+":"+config.Port)
for _, serverAddr := range sharedDNSConfig.Servers {
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
if err != nil {
lastErr = err
continue

View File

@@ -0,0 +1,12 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"testing"
)
func TestLookupCNAME(t *testing.T) {
t.Log(utils.LookupCNAME("www.yun4s.cn"))
}

View File

@@ -75,7 +75,7 @@ func FormatCount(count int64) string {
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
}
func FormatFloat(f interface{}, decimal int) string {
func FormatFloat(f any, decimal int) string {
if f == nil {
return ""
}
@@ -101,10 +101,29 @@ func FormatFloat(f interface{}, decimal int) string {
return ""
}
func FormatFloat2(f interface{}) string {
func FormatFloat2(f any) string {
return FormatFloat(f, 2)
}
// PadFloatZero 为浮点型数字字符串填充足够的0
func PadFloatZero(s string, countZero int) string {
if countZero <= 0 {
return s
}
if len(s) == 0 {
s = "0"
}
var index = strings.Index(s, ".")
if index < 0 {
return s + "." + strings.Repeat("0", countZero)
}
var decimalLen = len(s) - 1 - index
if decimalLen < countZero {
return s + strings.Repeat("0", countZero-decimalLen)
}
return s
}
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
// TrimZeroSuffix 去除小数数字尾部多余的0

View File

@@ -4,6 +4,7 @@ package numberutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/iwind/TeaGo/assert"
"testing"
)
@@ -49,6 +50,24 @@ func TestFormatFloat(t *testing.T) {
t.Log(numberutils.FormatFloat(-221745.12, 2))
}
func TestFormatFloat2(t *testing.T) {
t.Log(numberutils.FormatFloat2(0))
t.Log(numberutils.FormatFloat2(0.0))
t.Log(numberutils.FormatFloat2(1.23456))
t.Log(numberutils.FormatFloat2(1.0))
}
func TestPadFloatZero(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(numberutils.PadFloatZero("1", 0) == "1")
a.IsTrue(numberutils.PadFloatZero("1", 2) == "1.00")
a.IsTrue(numberutils.PadFloatZero("1.1", 2) == "1.10")
a.IsTrue(numberutils.PadFloatZero("1.12", 2) == "1.12")
a.IsTrue(numberutils.PadFloatZero("1.123", 2) == "1.123")
a.IsTrue(numberutils.PadFloatZero("10000.123", 2) == "10000.123")
a.IsTrue(numberutils.PadFloatZero("", 2) == "0.00")
}
func TestTrimZeroSuffix(t *testing.T) {
for _, s := range []string{
"1",

View File

@@ -128,7 +128,7 @@ After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=1s
RestartSec=5s
ExecStart=` + exePath + ` daemon
ExecStop=` + exePath + ` stop
ExecReload=` + exePath + ` reload

View File

@@ -9,7 +9,7 @@ import (
)
func TestNewUpgradeManager(t *testing.T) {
var manager = utils.NewUpgradeManager("admin")
var manager = utils.NewUpgradeManager("admin", "")
var ticker = time.NewTicker(2 * time.Second)
go func() {

View File

@@ -102,7 +102,7 @@ func FailPage(action actions.ActionWrapper, err error) {
var html = `<!DOCTYPE html>
<html>
<head>
<title>有系统错误需要处理</title>
<title>正在处理...</title>
<meta charset="UTF-8"/>
<style type="text/css">
hr { border-top: 1px #ccc solid; }
@@ -116,7 +116,7 @@ func FailPage(action actions.ActionWrapper, err error) {
html += "<div class=\"red\">API节点正在启动请耐心等待完成"
if len(apiNodeProgress) > 0 {
html += "" + apiNodeProgress
html += "" + apiNodeProgress + "(刷新当前页面查看最新状态)"
}
html += "</div>"

View File

@@ -45,6 +45,7 @@ func (this *AdminAction) RunGet(params struct {
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"hasWeakPassword": admin.HasWeakPassword,
"countAccessKeys": countAccessKeys,
}

View File

@@ -15,34 +15,46 @@ func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
page := this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
func (this *IndexAction) RunGet(params struct {
Keyword string
HasWeakPassword bool
}) {
this.Data["keyword"] = params.Keyword
this.Data["hasWeakPassword"] = params.HasWeakPassword
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Offset: page.Offset,
Size: page.Size,
countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{
Keyword: params.Keyword,
HasWeakPassword: params.HasWeakPassword,
})
if err != nil {
this.ErrorPage(err)
return
}
adminMaps := []maps.Map{}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Keyword: params.Keyword,
HasWeakPassword: params.HasWeakPassword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var adminMaps = []maps.Map{}
for _, admin := range adminsResp.Admins {
adminMaps = append(adminMaps, maps.Map{
"id": admin.Id,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"username": admin.Username,
"fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
"id": admin.Id,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"username": admin.Username,
"fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
"hasWeakPassword": admin.HasWeakPassword,
})
}
this.Data["admins"] = adminMaps

View File

@@ -82,7 +82,7 @@ func (this *IndexAction) RunGet(params struct{}) {
this.ErrorPage(err)
return
}
_ = httpsConfig.Init()
_ = httpsConfig.Init(nil)
if httpsConfig.IsOn && len(httpsConfig.Listen) > 0 {
restAccessAddrs = append(restAccessAddrs, httpsConfig.FullAddresses()...)
}

View File

@@ -65,7 +65,10 @@ func (this *IndexAction) RunGet(params struct {
// 证书信息
var certs = []*sslconfigs.SSLCertConfig{}
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId})
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil {
this.ErrorPage(err)
return

View File

@@ -85,7 +85,10 @@ func (this *UpdateAction) RunGet(params struct {
var certs = []*sslconfigs.SSLCertConfig{}
var sslPolicyId = int64(0)
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId})
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil {
this.ErrorPage(err)
return

View File

@@ -32,11 +32,12 @@ func (this *NodesAction) RunGet(params struct {
Keyword string
Level int32
CpuOrder string
MemoryOrder string
TrafficInOrder string
TrafficOutOrder string
LoadOrder string
CpuOrder string
MemoryOrder string
TrafficInOrder string
TrafficOutOrder string
LoadOrder string
ConnectionsOrder string
}) {
this.Data["groupId"] = params.GroupId
this.Data["regionId"] = params.RegionId
@@ -44,7 +45,7 @@ func (this *NodesAction) RunGet(params struct {
this.Data["activeState"] = params.ActiveState
this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level
this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0
this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0 || len(params.ConnectionsOrder) > 0
// 集群是否已经设置了线路
clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
@@ -112,6 +113,10 @@ func (this *NodesAction) RunGet(params struct {
req.LoadAsc = true
} else if params.LoadOrder == "desc" {
req.LoadDesc = true
} else if params.ConnectionsOrder == "asc" {
req.ConnectionsAsc = true
} else if params.ConnectionsOrder == "desc" {
req.ConnectionsDesc = true
}
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), req)
if err != nil {
@@ -121,8 +126,8 @@ func (this *NodesAction) RunGet(params struct {
var nodeMaps = []maps.Map{}
for _, node := range nodesResp.Nodes {
// 状态
isSynced := false
status := &nodeconfigs.NodeStatus{}
var isSynced = false
var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
@@ -211,16 +216,17 @@ func (this *NodesAction) RunGet(params struct {
"error": node.InstallStatus.Error,
},
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.FormatFloat2(status.Load1m),
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
"countConnections": status.ConnectionCount,
},
"cluster": maps.Map{
"id": node.NodeCluster.Id,

View File

@@ -78,6 +78,8 @@ func (this *IndexAction) RunPost(params struct {
HttpAllAllowNodeIP bool
HttpAllDefaultDomain string
HttpAllSupportsLowVersionHTTP bool
HttpAccessLogEnableRequestHeaders bool
HttpAccessLogEnableResponseHeaders bool
HttpAccessLogCommonRequestHeadersOnly bool
@@ -134,6 +136,9 @@ func (this *IndexAction) RunPost(params struct {
config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP
config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain
// HTTP All
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
// 访问日志
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
config.HTTPAccessLog.EnableResponseHeaders = params.HttpAccessLogEnableResponseHeaders

View File

@@ -157,6 +157,7 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
"name": "服务设置",
"url": "/clusters/cluster/settings/global-server-config?clusterId=" + clusterId,
"isActive": selectedItem == "globalServerConfig",
"isOn": true,
})
items = append(items, maps.Map{

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"golang.org/x/crypto/ssh"
)
type CreateAction struct {
@@ -50,6 +51,18 @@ func (this *CreateAction) RunPost(params struct {
if len(params.PrivateKey) == 0 {
this.FailField("privateKey", "请输入RSA私钥")
}
// 验证私钥
var err error
if len(params.Passphrase) > 0 {
_, err = ssh.ParsePrivateKeyWithPassphrase([]byte(params.PrivateKey), []byte(params.Passphrase))
} else {
_, err = ssh.ParsePrivateKey([]byte(params.PrivateKey))
}
if err != nil {
this.Fail("私钥验证失败,请检查格式:" + err.Error())
return
}
default:
this.Fail("请选择正确的认证方式")
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"golang.org/x/crypto/ssh"
)
type CreatePopupAction struct {
@@ -51,6 +52,18 @@ func (this *CreatePopupAction) RunPost(params struct {
if len(params.PrivateKey) == 0 {
this.FailField("privateKey", "请输入RSA私钥")
}
// 验证私钥
var err error
if len(params.Passphrase) > 0 {
_, err = ssh.ParsePrivateKeyWithPassphrase([]byte(params.PrivateKey), []byte(params.Passphrase))
} else {
_, err = ssh.ParsePrivateKey([]byte(params.PrivateKey))
}
if err != nil {
this.Fail("私钥验证失败,请检查格式:" + err.Error())
return
}
default:
this.Fail("请选择正确的认证方式")
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"golang.org/x/crypto/ssh"
)
type UpdateAction struct {
@@ -83,6 +84,18 @@ func (this *UpdateAction) RunPost(params struct {
if len(params.PrivateKey) == 0 {
this.FailField("privateKey", "请输入RSA私钥")
}
// 验证私钥
var err error
if len(params.Passphrase) > 0 {
_, err = ssh.ParsePrivateKeyWithPassphrase([]byte(params.PrivateKey), []byte(params.Passphrase))
} else {
_, err = ssh.ParsePrivateKey([]byte(params.PrivateKey))
}
if err != nil {
this.Fail("私钥验证失败,请检查格式:" + err.Error())
return
}
default:
this.Fail("请选择正确的认证方式")
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"golang.org/x/crypto/ssh"
)
type UpdatePopupAction struct {
@@ -83,6 +84,18 @@ func (this *UpdatePopupAction) RunPost(params struct {
if len(params.PrivateKey) == 0 {
this.FailField("privateKey", "请输入RSA私钥")
}
// 验证私钥
var err error
if len(params.Passphrase) > 0 {
_, err = ssh.ParsePrivateKeyWithPassphrase([]byte(params.PrivateKey), []byte(params.Passphrase))
} else {
_, err = ssh.ParsePrivateKey([]byte(params.PrivateKey))
}
if err != nil {
this.Fail("私钥验证失败,请检查格式:" + err.Error())
return
}
default:
this.Fail("请选择正确的认证方式")
}

View File

@@ -33,11 +33,12 @@ func (this *NodesAction) RunGet(params struct {
Keyword string
Level int32
CpuOrder string
MemoryOrder string
TrafficInOrder string
TrafficOutOrder string
LoadOrder string
CpuOrder string
MemoryOrder string
TrafficInOrder string
TrafficOutOrder string
LoadOrder string
ConnectionsOrder string
}) {
this.Data["groupId"] = params.GroupId
this.Data["regionId"] = params.RegionId
@@ -46,7 +47,7 @@ func (this *NodesAction) RunGet(params struct {
this.Data["keyword"] = params.Keyword
this.Data["level"] = params.Level
this.Data["clusterId"] = params.ClusterId
this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0
this.Data["hasOrder"] = len(params.CpuOrder) > 0 || len(params.MemoryOrder) > 0 || len(params.TrafficInOrder) > 0 || len(params.TrafficOutOrder) > 0 || len(params.LoadOrder) > 0 || len(params.ConnectionsOrder) > 0
// 集群是否已经设置了线路
clusterDNSResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
@@ -114,6 +115,10 @@ func (this *NodesAction) RunGet(params struct {
req.LoadAsc = true
} else if params.LoadOrder == "desc" {
req.LoadDesc = true
} else if params.ConnectionsOrder == "asc" {
req.ConnectionsAsc = true
} else if params.ConnectionsOrder == "desc" {
req.ConnectionsDesc = true
}
nodesResp, err := this.RPC().NodeRPC().ListEnabledNodesMatch(this.AdminContext(), req)
if err != nil {
@@ -123,8 +128,8 @@ func (this *NodesAction) RunGet(params struct {
var nodeMaps = []maps.Map{}
for _, node := range nodesResp.Nodes {
// 状态
isSynced := false
status := &nodeconfigs.NodeStatus{}
var isSynced = false
var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
@@ -213,16 +218,17 @@ func (this *NodesAction) RunGet(params struct {
"error": node.InstallStatus.Error,
},
"status": maps.Map{
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage * 100),
"memUsage": status.MemoryUsage,
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage * 100),
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.FormatFloat2(status.Load1m),
"isActive": status.IsActive,
"updatedAt": status.UpdatedAt,
"hostname": status.Hostname,
"cpuUsage": status.CPUUsage,
"cpuUsageText": numberutils.FormatFloat2(status.CPUUsage * 100) + "%",
"memUsage": status.MemoryUsage,
"memUsageText": numberutils.FormatFloat2(status.MemoryUsage * 100) + "%",
"trafficInBytes": status.TrafficInBytes,
"trafficOutBytes": status.TrafficOutBytes,
"load1m": numberutils.PadFloatZero(numberutils.FormatFloat2(status.Load1m), 2),
"countConnections": status.ConnectionCount,
},
"cluster": maps.Map{
"id": node.NodeCluster.Id,

View File

@@ -15,6 +15,9 @@ func (this *CheckAction) RunPost(params struct {
HasError bool
IsUpdated bool
}) {
var isStream = this.Request.ProtoMajor >= 2
this.Data["isStream"] = isStream
var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
@@ -26,7 +29,7 @@ func (this *CheckAction) RunPost(params struct {
}
// 如果没有数据变化,继续查询
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError && isStream {
time.Sleep(3 * time.Second)
continue
}

View File

@@ -30,7 +30,7 @@ func (this *IndexAction) RunGet(params struct{}) {
helpers.NotifyIPItemsCountChanges()
helpers.NotifyNodeLogsCountChange()
if teaconst.IsPlus {
if this.checkPlus() {
this.RedirectURL("/dashboard/boards")
return
}
@@ -276,5 +276,13 @@ func (this *IndexAction) RunPost(params struct{}) {
}
}
// 弱密码提示
countWeakAdminsResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{HasWeakPassword: true})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["countWeakAdmins"] = countWeakAdminsResp.Count
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package dashboard
func (this *IndexAction) checkPlus() bool {
return false
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/db/dbnodeutils"
"github.com/iwind/TeaGo/maps"
"strings"
)
type NodeAction struct {
@@ -33,7 +34,7 @@ func (this *NodeAction) RunGet(params struct {
"host": node.Host,
"port": node.Port,
"username": node.Username,
"password": node.Password,
"password": strings.Repeat("*", len(node.Password)),
"description": node.Description,
}

View File

@@ -25,7 +25,7 @@ func (this *ClusterAction) RunGet(params struct {
this.ErrorPage(err)
return
}
cluster := clusterResp.NodeCluster
var cluster = clusterResp.NodeCluster
if cluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
@@ -42,7 +42,7 @@ func (this *ClusterAction) RunGet(params struct {
return
}
var defaultRoute = dnsResp.DefaultRoute
domainName := ""
var domainName = ""
var dnsMap = maps.Map{
"dnsName": dnsResp.Name,
"domainId": 0,
@@ -70,19 +70,42 @@ func (this *ClusterAction) RunGet(params struct {
this.Data["dnsInfo"] = dnsMap
// 节点DNS解析记录
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
// 未安装的节点
notInstalledNodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: false,
})
if err != nil {
this.ErrorPage(err)
return
}
var nodeMaps = []maps.Map{}
var allNodes = notInstalledNodesResp.Nodes
// 节点DNS解析记录
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var installedNodeIdsMap = map[int64]bool{}
for _, node := range nodesResp.Nodes {
installedNodeIdsMap[node.Id] = true
}
allNodes = append(allNodes, nodesResp.Nodes...)
var nodeMaps = []maps.Map{}
for _, node := range allNodes {
var isInstalled = installedNodeIdsMap[node.Id]
if len(node.Routes) > 0 {
for _, route := range node.Routes {
// 检查是否已解析
var isResolved = false
if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
@@ -110,14 +133,15 @@ func (this *ClusterAction) RunGet(params struct {
"name": route.Name,
"code": route.Code,
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
})
}
} else {
// 默认线路
var isResolved = false
if len(defaultRoute) > 0 {
if isInstalled && len(defaultRoute) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
@@ -144,8 +168,9 @@ func (this *ClusterAction) RunGet(params struct {
"name": "",
"code": "",
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
})
}
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
"sort"
)
// DomainOptionsAction 域名列表选项
@@ -21,10 +22,18 @@ func (this *DomainOptionsAction) RunPost(params struct {
this.ErrorPage(err)
return
}
domainMaps := []maps.Map{}
// 排序
if len(domainsResp.DnsDomains) > 0 {
sort.Slice(domainsResp.DnsDomains, func(i, j int) bool {
return domainsResp.DnsDomains[i].Name < domainsResp.DnsDomains[j].Name
})
}
var domainMaps = []maps.Map{}
for _, domain := range domainsResp.DnsDomains {
// 未开启或者已删除的先跳过
if !domain.IsOn || domain.IsDeleted {
if !domain.IsOn || domain.IsDeleted || !domain.IsUp {
continue
}

View File

@@ -18,6 +18,8 @@ func (this *NodesPopupAction) Init() {
func (this *NodesPopupAction) RunGet(params struct {
DomainId int64
}) {
this.Data["domainId"] = params.DomainId
// 域名信息
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{
DnsDomainId: params.DomainId,
@@ -26,7 +28,7 @@ func (this *NodesPopupAction) RunGet(params struct {
this.ErrorPage(err)
return
}
domain := domainResp.DnsDomain
var domain = domainResp.DnsDomain
if domain == nil {
this.NotFound("dnsDomain", params.DomainId)
return
@@ -35,7 +37,7 @@ func (this *NodesPopupAction) RunGet(params struct {
this.Data["domain"] = domain.Name
// 集群
clusterMaps := []maps.Map{}
var clusterMaps = []maps.Map{}
clustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClustersWithDNSDomainId(this.AdminContext(), &pb.FindAllEnabledNodeClustersWithDNSDomainIdRequest{DnsDomainId: params.DomainId})
if err != nil {
this.ErrorPage(err)
@@ -43,18 +45,24 @@ func (this *NodesPopupAction) RunGet(params struct {
}
for _, cluster := range clustersResp.NodeClusters {
// 默认值
var defaultRoute = cluster.DnsDefaultRoute
// 节点DNS解析记录
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{NodeClusterId: cluster.Id})
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: cluster.Id,
IsInstalled: true,
})
if err != nil {
this.ErrorPage(err)
return
}
nodeMaps := []maps.Map{}
var nodeMaps = []maps.Map{}
for _, node := range nodesResp.Nodes {
if len(node.Routes) > 0 {
for _, route := range node.Routes {
// 检查是否有域名解析记录
isOk := false
var isResolved = false
if len(route.Name) > 0 && len(node.IpAddr) > 0 && len(cluster.DnsName) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
@@ -71,7 +79,7 @@ func (this *NodesPopupAction) RunGet(params struct {
this.ErrorPage(err)
return
}
isOk = checkResp.IsOk
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
@@ -83,10 +91,30 @@ func (this *NodesPopupAction) RunGet(params struct {
"code": route.Code,
},
"clusterId": node.NodeClusterId,
"isOk": isOk,
"isOk": isResolved,
})
}
} else {
// 默认线路
var isResolved = false
if len(defaultRoute) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: cluster.DnsName,
Type: recordType,
Route: defaultRoute,
Value: node.IpAddr,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
@@ -96,7 +124,7 @@ func (this *NodesPopupAction) RunGet(params struct {
"code": "",
},
"clusterId": node.NodeClusterId,
"isOk": false,
"isOk": isResolved,
})
}
}

View File

@@ -17,6 +17,8 @@ func (this *ServersPopupAction) Init() {
func (this *ServersPopupAction) RunGet(params struct {
DomainId int64
}) {
this.Data["domainId"] = params.DomainId
// 域名信息
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{
DnsDomainId: params.DomainId,
@@ -25,7 +27,7 @@ func (this *ServersPopupAction) RunGet(params struct {
this.ErrorPage(err)
return
}
domain := domainResp.DnsDomain
var domain = domainResp.DnsDomain
if domain == nil {
this.NotFound("dnsDomain", params.DomainId)
return
@@ -34,7 +36,7 @@ func (this *ServersPopupAction) RunGet(params struct {
this.Data["domain"] = domain.Name
// 服务信息
clusterMaps := []maps.Map{}
var clusterMaps = []maps.Map{}
clustersResp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClustersWithDNSDomainId(this.AdminContext(), &pb.FindAllEnabledNodeClustersWithDNSDomainIdRequest{DnsDomainId: params.DomainId})
if err != nil {
this.ErrorPage(err)
@@ -46,9 +48,9 @@ func (this *ServersPopupAction) RunGet(params struct {
this.ErrorPage(err)
return
}
serverMaps := []maps.Map{}
var serverMaps = []maps.Map{}
for _, server := range serversResp.Servers {
isOk := false
var isOk = false
if len(cluster.DnsName) > 0 && len(server.DnsName) > 0 {
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: params.DomainId,

View File

@@ -15,6 +15,9 @@ func (this *CheckAction) RunPost(params struct {
HasError bool
IsUpdated bool
}) {
var isStream = this.Request.ProtoMajor >= 2
this.Data["isStream"] = isStream
var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
@@ -24,7 +27,7 @@ func (this *CheckAction) RunPost(params struct {
}
// 如果没有数据变化,继续查询
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError && isStream {
time.Sleep(3 * time.Second)
continue
}

View File

@@ -9,12 +9,16 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
"time"
)
@@ -32,6 +36,25 @@ func (this *IndexAction) RunGet(params struct {
Auth *helpers.UserShouldAuth
}) {
// 是否自动从HTTP跳转到HTTPS
if this.Request.TLS == nil {
httpsPort, _ := adminserverutils.ReadServerHTTPS()
if httpsPort > 0 {
currentHost, _, err := net.SplitHostPort(this.Request.Host)
if err != nil {
currentHost = this.Request.Host
}
var newHost = configutils.QuoteIP(currentHost)
if httpsPort != 443 /** default https port **/ {
newHost += ":" + types.String(httpsPort)
}
this.RedirectURL("https://" + newHost + this.Request.RequestURI)
return
}
}
// DEMO模式
this.Data["isDemo"] = teaconst.IsDemoMode
@@ -81,6 +104,9 @@ func (this *IndexAction) RunGet(params struct {
this.Data["rememberLogin"] = securityConfig.AllowRememberLogin
}
// 删除Cookie
loginutils.UnsetCookie(this.Object())
this.Show()
}

View File

@@ -0,0 +1,86 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package loginutils
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/iwind/TeaGo/actions"
stringutil "github.com/iwind/TeaGo/utils/string"
"net"
"net/http"
)
// CalculateClientFingerprint 计算客户端指纹
func CalculateClientFingerprint(action *actions.ActionObject) string {
return stringutil.Md5(RemoteIP(action) + "@" + action.Request.UserAgent())
}
// RemoteIP 获取客户端IP
// TODO 将来增加是否使用代理设置即从X-Real-IP中获取IP
func RemoteIP(action *actions.ActionObject) string {
ip, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
return ip
}
// LookupIPRegion 查找登录区域
func LookupIPRegion(ip string) string {
if len(ip) == 0 {
return ""
}
var result = iplibrary.LookupIP(ip)
if result != nil && result.IsOk() {
// 这里不需要网络运营商信息
return result.CountryName() + "@" + result.ProvinceName() + "@" + result.CityName() + "@" + result.TownName()
}
return ""
}
// SetCookie 设置Cookie
func SetCookie(action *actions.ActionObject, remember bool) {
if remember {
var cookie = &http.Cookie{
Name: teaconst.CookieSID,
Value: action.Session().Sid,
Path: "/",
MaxAge: 14 * 86400,
HttpOnly: true,
}
if action.Request.TLS != nil {
cookie.SameSite = http.SameSiteStrictMode
cookie.Secure = true
}
action.AddCookie(cookie)
} else {
var cookie = &http.Cookie{
Name: teaconst.CookieSID,
Value: action.Session().Sid,
Path: "/",
MaxAge: 0,
HttpOnly: true,
}
if action.Request.TLS != nil {
cookie.SameSite = http.SameSiteStrictMode
cookie.Secure = true
}
action.AddCookie(cookie)
}
}
// UnsetCookie 重置Cookie
func UnsetCookie(action *actions.ActionObject) {
cookie := &http.Cookie{
Name: teaconst.CookieSID,
Value: action.Session().Sid,
Path: "/",
MaxAge: -1,
HttpOnly: true,
}
if action.Request.TLS != nil {
cookie.SameSite = http.SameSiteStrictMode
cookie.Secure = true
}
action.AddCookie(cookie)
}

View File

@@ -23,6 +23,7 @@ func (this *ExportExcelAction) RunGet(params struct {
DayTo string
Keyword string
UserType string
Level string
}) {
logsResp, err := this.RPC().LogRPC().ListLogs(this.AdminContext(), &pb.ListLogsRequest{
Offset: 0,
@@ -31,6 +32,7 @@ func (this *ExportExcelAction) RunGet(params struct {
DayTo: params.DayTo,
Keyword: params.Keyword,
UserType: params.UserType,
Level: params.Level,
})
if err != nil {
this.ErrorPage(err)
@@ -56,6 +58,7 @@ func (this *ExportExcelAction) RunGet(params struct {
row.AddCell().SetString("区域")
row.AddCell().SetString("运营商")
row.AddCell().SetString("页面地址")
row.AddCell().SetString("级别")
}
// 数据
@@ -95,6 +98,17 @@ func (this *ExportExcelAction) RunGet(params struct {
row.AddCell().SetString(regionName)
row.AddCell().SetString(ispName)
row.AddCell().SetString(log.Action)
var levelName = ""
switch log.Level {
case "info":
levelName = "信息"
case "warn", "warning":
levelName = "警告"
case "error":
levelName = "错误"
}
row.AddCell().SetString(levelName)
}
this.AddHeader("Content-Type", "application/vnd.ms-excel")

View File

@@ -21,6 +21,7 @@ func (this *IndexAction) RunGet(params struct {
DayTo string
Keyword string
UserType string
Level string
}) {
// 读取配置
config, err := configloaders.LoadLogConfig()
@@ -35,18 +36,36 @@ func (this *IndexAction) RunGet(params struct {
this.Data["keyword"] = params.Keyword
this.Data["userType"] = params.UserType
// 级别
this.Data["level"] = params.Level
this.Data["levelOptions"] = []maps.Map{
{
"code": "info",
"name": "信息",
},
{
"code": "warn",
"name": "警告",
},
{
"code": "error",
"name": "错误",
},
}
countResp, err := this.RPC().LogRPC().CountLogs(this.AdminContext(), &pb.CountLogRequest{
DayFrom: params.DayFrom,
DayTo: params.DayTo,
Keyword: params.Keyword,
UserType: params.UserType,
Level: params.Level,
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
var count = countResp.Count
var page = this.NewPage(count)
this.Data["page"] = page.AsHTML()
logsResp, err := this.RPC().LogRPC().ListLogs(this.AdminContext(), &pb.ListLogsRequest{
@@ -56,12 +75,13 @@ func (this *IndexAction) RunGet(params struct {
DayTo: params.DayTo,
Keyword: params.Keyword,
UserType: params.UserType,
Level: params.Level,
})
if err != nil {
this.ErrorPage(err)
return
}
logMaps := []maps.Map{}
var logMaps = []maps.Map{}
for _, log := range logsResp.Logs {
regionName := ""
regionResp, err := this.RPC().IPLibraryRPC().LookupIPRegion(this.AdminContext(), &pb.LookupIPRegionRequest{Ip: log.Ip})

View File

@@ -18,31 +18,6 @@ func (this *CreateAction) Init() {
}
func (this *CreateAction) RunGet(params struct{}) {
// 获取所有可用的用户
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
AdminId: this.AdminId(),
UserId: 0,
})
if err != nil {
this.ErrorPage(err)
return
}
userMaps := []maps.Map{}
for _, user := range usersResp.AcmeUsers {
description := user.Description
if len(description) > 0 {
description = "" + description + ""
}
userMaps = append(userMaps, maps.Map{
"id": user.Id,
"description": description,
"email": user.Email,
"providerCode": user.AcmeProviderCode,
})
}
this.Data["users"] = userMaps
// 证书服务商
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
if err != nil {
@@ -81,14 +56,15 @@ func (this *CreateAction) RunGet(params struct{}) {
}
func (this *CreateAction) RunPost(params struct {
TaskId int64
AuthType string
AcmeUserId int64
DnsProviderId int64
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
PlatformUserId int64
TaskId int64
AuthType string
AcmeUserId int64
DnsProviderId int64
DnsDomain string
Domains []string
AutoRenew bool
AuthURL string
Must *actions.Must
}) {
@@ -117,7 +93,7 @@ func (this *CreateAction) RunPost(params struct {
if len(params.Domains) == 0 {
this.Fail("请输入证书域名列表")
}
realDomains := []string{}
var realDomains = []string{}
for _, domain := range params.Domains {
domain = strings.ToLower(domain)
if params.AuthType == "dns" { // DNS认证
@@ -134,6 +110,7 @@ func (this *CreateAction) RunPost(params struct {
if params.TaskId == 0 {
createResp, err := this.RPC().ACMETaskRPC().CreateACMETask(this.AdminContext(), &pb.CreateACMETaskRequest{
UserId: params.PlatformUserId,
AuthType: params.AuthType,
AcmeUserId: params.AcmeUserId,
DnsProviderId: params.DnsProviderId,

View File

@@ -17,22 +17,48 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Type string
Keyword string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
countAll := int64(0)
countAvailable := int64(0)
countExpired := int64(0)
count7Days := int64(0)
count30Days := int64(0)
// 当前用户
this.Data["searchingUserId"] = params.UserId
var userMap = maps.Map{
"id": 0,
"username": "",
"fullname": "",
}
if params.UserId > 0 {
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
var user = userResp.User
if user != nil {
userMap = maps.Map{
"id": user.Id,
"username": user.Username,
"fullname": user.Fullname,
}
}
}
this.Data["user"] = userMap
var countAll int64
var countAvailable int64
var countExpired int64
var count7Days int64
var count30Days int64
// 计算数量
{
// all
resp, err := this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
Keyword: params.Keyword,
})
if err != nil {
@@ -43,6 +69,7 @@ func (this *IndexAction) RunGet(params struct {
// available
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
IsAvailable: true,
Keyword: params.Keyword,
})
@@ -54,6 +81,7 @@ func (this *IndexAction) RunGet(params struct {
// expired
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
IsExpired: true,
Keyword: params.Keyword,
})
@@ -65,6 +93,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 7 days
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 7,
Keyword: params.Keyword,
})
@@ -76,6 +105,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 30 days
resp, err = this.RPC().ACMETaskRPC().CountAllEnabledACMETasks(this.AdminContext(), &pb.CountAllEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 30,
Keyword: params.Keyword,
})
@@ -100,25 +130,51 @@ func (this *IndexAction) RunGet(params struct {
case "":
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "available":
page = this.NewPage(countAvailable)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
IsAvailable: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "expired":
page = this.NewPage(countExpired)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
IsExpired: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "7days":
page = this.NewPage(count7Days)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 7,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "30days":
page = this.NewPage(count30Days)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
ExpiringDays: 30,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
default:
page = this.NewPage(countAll)
tasksResp, err = this.RPC().ACMETaskRPC().ListEnabledACMETasks(this.AdminContext(), &pb.ListEnabledACMETasksRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
@@ -131,7 +187,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["page"] = page.AsHTML()
taskMaps := []maps.Map{}
var taskMaps = []maps.Map{}
for _, task := range tasksResp.AcmeTasks {
if task.AcmeUser == nil {
continue

View File

@@ -12,6 +12,8 @@ type RunAction struct {
func (this *RunAction) RunPost(params struct {
TaskId int64
}) {
defer this.CreateLogInfo("执行ACME任务 %d", params.TaskId)
runResp, err := this.RPC().ACMETaskRPC().RunACMETask(this.AdminContext(), &pb.RunACMETaskRequest{AcmeTaskId: params.TaskId})
if err != nil {
this.ErrorPage(err)

View File

@@ -0,0 +1,44 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package acme
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type UserOptionsAction struct {
actionutils.ParentAction
}
func (this *UserOptionsAction) RunPost(params struct {
PlatformUserId int64
}) {
// 获取所有可用的用户
usersResp, err := this.RPC().ACMEUserRPC().FindAllACMEUsers(this.AdminContext(), &pb.FindAllACMEUsersRequest{
AdminId: 0,
UserId: params.PlatformUserId,
})
if err != nil {
this.ErrorPage(err)
return
}
var userMaps = []maps.Map{}
for _, user := range usersResp.AcmeUsers {
description := user.Description
if len(description) > 0 {
description = "" + description + ""
}
userMaps = append(userMaps, maps.Map{
"id": user.Id,
"description": description,
"email": user.Email,
"providerCode": user.AcmeProviderCode,
})
}
this.Data["users"] = userMaps
this.Success()
}

View File

@@ -16,10 +16,30 @@ func (this *CreatePopupAction) Init() {
}
func (this *CreatePopupAction) RunGet(params struct {
ProviderCode string
PlatformUserId int64
ProviderCode string
}) {
this.Data["platformUserId"] = params.PlatformUserId
this.Data["providerCode"] = params.ProviderCode
// 平台用户信息
this.Data["platformUser"] = nil
if params.PlatformUserId > 0 {
platformUserResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.PlatformUserId})
if err != nil {
this.ErrorPage(err)
return
}
var platformUser = platformUserResp.User
if platformUser != nil {
this.Data["platformUser"] = maps.Map{
"id": platformUser.Id,
"username": platformUser.Username,
"fullname": platformUser.Fullname,
}
}
}
// 服务商
providersResp, err := this.RPC().ACMEProviderRPC().FindAllACMEProviders(this.AdminContext(), &pb.FindAllACMEProvidersRequest{})
if err != nil {
@@ -40,10 +60,11 @@ func (this *CreatePopupAction) RunGet(params struct {
}
func (this *CreatePopupAction) RunPost(params struct {
Email string
ProviderCode string
AccountId int64
Description string
PlatformUserId int64
Email string
ProviderCode string
AccountId int64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
@@ -85,6 +106,7 @@ func (this *CreatePopupAction) RunPost(params struct {
}
createResp, err := this.RPC().ACMEUserRPC().CreateACMEUser(this.AdminContext(), &pb.CreateACMEUserRequest{
UserId: params.PlatformUserId,
Email: params.Email,
Description: params.Description,
AcmeProviderCode: params.ProviderCode,

View File

@@ -19,12 +19,37 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct {
UserId int64
Type string
Keyword string
}) {
this.Data["type"] = params.Type
this.Data["keyword"] = params.Keyword
// 当前用户
this.Data["searchingUserId"] = params.UserId
var userMap = maps.Map{
"id": 0,
"username": "",
"fullname": "",
}
if params.UserId > 0 {
userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
if err != nil {
this.ErrorPage(err)
return
}
var user = userResp.User
if user != nil {
userMap = maps.Map{
"id": user.Id,
"username": user.Username,
"fullname": user.Fullname,
}
}
}
this.Data["user"] = userMap
var countAll = int64(0)
var countCA = int64(0)
var countAvailable = int64(0)
@@ -36,6 +61,7 @@ func (this *IndexAction) RunGet(params struct {
{
// all
resp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
Keyword: params.Keyword,
})
if err != nil {
@@ -46,6 +72,7 @@ func (this *IndexAction) RunGet(params struct {
// CA
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsCA: true,
Keyword: params.Keyword,
})
@@ -57,6 +84,7 @@ func (this *IndexAction) RunGet(params struct {
// available
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsAvailable: true,
Keyword: params.Keyword,
})
@@ -68,6 +96,7 @@ func (this *IndexAction) RunGet(params struct {
// expired
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
IsExpired: true,
Keyword: params.Keyword,
})
@@ -79,6 +108,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 7 days
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
ExpiringDays: 7,
Keyword: params.Keyword,
})
@@ -90,6 +120,7 @@ func (this *IndexAction) RunGet(params struct {
// expire in 30 days
resp, err = this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
ExpiringDays: 30,
Keyword: params.Keyword,
})
@@ -115,28 +146,60 @@ func (this *IndexAction) RunGet(params struct {
case "":
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "ca":
page = this.NewPage(countCA)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsCA: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsCA: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "available":
page = this.NewPage(countAvailable)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsAvailable: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsAvailable: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "expired":
page = this.NewPage(countExpired)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{IsExpired: true, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
IsExpired: true,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "7days":
page = this.NewPage(count7Days)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 7, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
ExpiringDays: 7,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
case "30days":
page = this.NewPage(count30Days)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{ExpiringDays: 30, Offset: page.Offset, Size: page.Size, Keyword: params.Keyword})
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
ExpiringDays: 30,
Offset: page.Offset,
Size: page.Size,
Keyword: params.Keyword,
})
default:
page = this.NewPage(countAll)
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
@@ -158,7 +221,9 @@ func (this *IndexAction) RunGet(params struct {
var certMaps = []maps.Map{}
var nowTime = time.Now().Unix()
for _, certConfig := range certConfigs {
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{SslCertId: certConfig.Id})
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{
SslCertId: certConfig.Id,
})
if err != nil {
this.ErrorPage(err)
return

View File

@@ -21,6 +21,7 @@ func init() {
Data("leftMenuItem", "cert").
Get("", new(IndexAction)).
GetPost("/uploadPopup", new(UploadPopupAction)).
GetPost("/uploadBatchPopup", new(UploadBatchPopupAction)).
Post("/delete", new(DeleteAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Get("/certPopup", new(CertPopupAction)).
@@ -40,6 +41,7 @@ func init() {
Post("/run", new(acme.RunAction)).
GetPost("/updateTaskPopup", new(acme.UpdateTaskPopupAction)).
Post("/deleteTask", new(acme.DeleteTaskAction)).
Post("/userOptions", new(acme.UserOptionsAction)).
// ACME用户
Prefix("/servers/certs/acme/users").

View File

@@ -2,9 +2,11 @@ package certs
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
@@ -23,15 +25,86 @@ func (this *SelectPopupAction) Init() {
}
func (this *SelectPopupAction) RunGet(params struct {
ServerId int64 // 搜索的服务
UserId int64 // 搜索的用户名
SearchingDomains string // 搜索的域名
SearchingType string // 搜索类型match|all
ViewSize string
SelectedCertIds string
Keyword string
}) {
// TODO 列出常用和最新的证书供用户选择
this.Data["searchingServerId"] = params.ServerId
// 服务相关
if params.ServerId > 0 {
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
var server = serverResp.Server
if server != nil {
if server.UserId > 0 {
params.UserId = server.UserId
}
// 读取所有ServerNames
serverNamesResp, err := this.RPC().ServerRPC().FindServerNames(this.AdminContext(), &pb.FindServerNamesRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
if len(serverNamesResp.ServerNamesJSON) > 0 {
var serverNames = []*serverconfigs.ServerNameConfig{}
err = json.Unmarshal(serverNamesResp.ServerNamesJSON, &serverNames)
if err != nil {
this.ErrorPage(err)
return
}
params.SearchingDomains = strings.Join(serverconfigs.PlainServerNames(serverNames), ",")
}
}
}
// 用户相关
this.Data["userId"] = params.UserId // 可变
this.Data["searchingUserId"] = params.UserId
// 域名搜索相关
var url = this.Request.URL.Path
var query = this.Request.URL.Query()
query.Del("searchingType")
this.Data["baseURL"] = url + "?" + query.Encode()
var searchingDomains = []string{}
if len(params.SearchingDomains) > 0 {
searchingDomains = strings.Split(params.SearchingDomains, ",")
}
const maxDomains = 2_000 // 限制搜索的域名数量
if len(searchingDomains) > maxDomains {
searchingDomains = searchingDomains[:maxDomains]
}
this.Data["allSearchingDomains"] = params.SearchingDomains
this.Data["searchingDomains"] = searchingDomains
this.Data["keyword"] = params.Keyword
this.Data["selectedCertIds"] = params.SelectedCertIds
var searchingType = params.SearchingType
if len(searchingType) == 0 {
if len(params.SearchingDomains) == 0 {
searchingType = "all"
} else {
searchingType = "match"
}
}
if searchingType != "all" && searchingType != "match" {
this.ErrorPage(errors.New("invalid searching type '" + searchingType + "'"))
return
}
this.Data["searchingType"] = searchingType
// 已经选择的证书
var selectedCertIds = []string{}
if len(params.SelectedCertIds) > 0 {
@@ -43,24 +116,68 @@ func (this *SelectPopupAction) RunGet(params struct {
}
this.Data["viewSize"] = params.ViewSize
countResp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
// 全部证书数量
countAllResp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
Keyword: params.Keyword,
})
if err != nil {
this.ErrorPage(err)
return
}
var totalAll = countAllResp.Count
this.Data["totalAll"] = totalAll
page := this.NewPage(countResp.Count)
// 已匹配证书数量
var totalMatch int64 = 0
if len(searchingDomains) > 0 {
countMatchResp, err := this.RPC().SSLCertRPC().CountSSLCerts(this.AdminContext(), &pb.CountSSLCertRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Domains: searchingDomains,
})
if err != nil {
this.ErrorPage(err)
return
}
totalMatch = countMatchResp.Count
}
this.Data["totalMatch"] = totalMatch
var totalCerts int64
if searchingType == "all" {
totalCerts = totalAll
} else if searchingType == "match" {
totalCerts = totalMatch
}
var page = this.NewPage(totalCerts)
this.Data["page"] = page.AsHTML()
listResp, err := this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
var listResp *pb.ListSSLCertsResponse
if searchingType == "all" {
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Offset: page.Offset,
Size: page.Size,
})
} else if searchingType == "match" {
listResp, err = this.RPC().SSLCertRPC().ListSSLCerts(this.AdminContext(), &pb.ListSSLCertsRequest{
UserId: params.UserId,
Keyword: params.Keyword,
Domains: searchingDomains,
Offset: page.Offset,
Size: page.Size,
})
}
certConfigs := []*sslconfigs.SSLCertConfig{}
if listResp == nil {
this.ErrorPage(errors.New("'listResp' should not be nil"))
return
}
var certConfigs = []*sslconfigs.SSLCertConfig{}
err = json.Unmarshal(listResp.SslCertsJSON, &certConfigs)
if err != nil {
this.ErrorPage(err)
@@ -68,8 +185,8 @@ func (this *SelectPopupAction) RunGet(params struct {
}
this.Data["certs"] = certConfigs
certMaps := []maps.Map{}
nowTime := time.Now().Unix()
var certMaps = []maps.Map{}
var nowTime = time.Now().Unix()
for _, certConfig := range certConfigs {
countServersResp, err := this.RPC().ServerRPC().CountAllEnabledServersWithSSLCertId(this.AdminContext(), &pb.CountAllEnabledServersWithSSLCertIdRequest{SslCertId: certConfig.Id})
if err != nil {

View File

@@ -26,18 +26,20 @@ func (this *UpdatePopupAction) RunGet(params struct {
this.ErrorPage(err)
return
}
certConfigJSON := certConfigResp.SslCertJSON
var certConfigJSON = certConfigResp.SslCertJSON
if len(certConfigJSON) == 0 {
this.NotFound("cert", params.CertId)
return
}
certConfig := &sslconfigs.SSLCertConfig{}
var certConfig = &sslconfigs.SSLCertConfig{}
err = json.Unmarshal(certConfigJSON, certConfig)
if err != nil {
this.ErrorPage(err)
return
}
certConfig.CertData = nil // cert & key 不需要在界面上显示
certConfig.KeyData = nil
this.Data["certConfig"] = certConfig
this.Show()
@@ -118,7 +120,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
// 校验
certConfig.IsCA = params.IsCA
err = certConfig.Init()
err = certConfig.Init(nil)
if err != nil {
if params.IsCA {
this.Fail("证书校验错误:" + err.Error())

View File

@@ -0,0 +1,225 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package certs
import (
"bytes"
"crypto/tls"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"io"
"mime/multipart"
"strings"
)
// UploadBatchPopupAction 批量上传证书
type UploadBatchPopupAction struct {
actionutils.ParentAction
}
func (this *UploadBatchPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UploadBatchPopupAction) RunGet(params struct {
ServerId int64
UserId int64
}) {
// 读取服务用户
if params.ServerId > 0 {
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
var server = serverResp.Server
if server != nil {
params.UserId = server.UserId
}
}
this.Data["userId"] = params.UserId
this.Data["maxFiles"] = this.maxFiles()
this.Show()
}
func (this *UploadBatchPopupAction) RunPost(params struct {
UserId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("批量上传证书")
var files = this.Request.MultipartForm.File["certFiles"]
if len(files) == 0 {
this.Fail("请选择要上传的证书和私钥文件")
return
}
// 限制每次上传的文件数量
var maxFiles = this.maxFiles()
if len(files) > maxFiles {
this.Fail("每次上传最多不能超过" + types.String(maxFiles) + "个文件")
return
}
type certInfo struct {
filename string
data []byte
}
var certDataList = []*certInfo{}
var keyDataList = [][]byte{}
var failMessages = []string{}
for _, file := range files {
func(file *multipart.FileHeader) {
fp, err := file.Open()
if err != nil {
failMessages = append(failMessages, "文件"+file.Filename+"读取失败:"+err.Error())
return
}
defer func() {
_ = fp.Close()
}()
data, err := io.ReadAll(fp)
if err != nil {
failMessages = append(failMessages, "文件"+file.Filename+"读取失败:"+err.Error())
return
}
if bytes.Contains(data, []byte("CERTIFICATE-")) {
certDataList = append(certDataList, &certInfo{
filename: file.Filename,
data: data,
})
} else if bytes.Contains(data, []byte("PRIVATE KEY-")) {
keyDataList = append(keyDataList, data)
} else {
failMessages = append(failMessages, "文件"+file.Filename+"读取失败:文件格式错误,无法识别是证书还是私钥")
return
}
}(file)
}
if len(failMessages) > 0 {
this.Fail("发生了错误:" + strings.Join(failMessages, ""))
return
}
// 对比证书和私钥数量是否一致
if len(certDataList) != len(keyDataList) {
this.Fail("证书文件数量(" + types.String(len(certDataList)) + ")和私钥文件数量(" + types.String(len(keyDataList)) + ")不一致")
return
}
// 自动匹配
var pairs = [][2][]byte{} // [] { cert, key }
var keyIndexMap = map[int]bool{} // 方便下面跳过已匹配的Key
for _, cert := range certDataList {
var found = false
for keyIndex, keyData := range keyDataList {
if keyIndexMap[keyIndex] {
continue
}
_, err := tls.X509KeyPair(cert.data, keyData)
if err == nil {
found = true
pairs = append(pairs, [2][]byte{cert.data, keyData})
keyIndexMap[keyIndex] = true
break
}
}
if !found {
this.Fail("找不到" + cert.filename + "对应的私钥")
return
}
}
// 组织 CertConfig
var pbCerts = []*pb.CreateSSLCertsRequestCert{}
var certConfigs = []*sslconfigs.SSLCertConfig{}
for _, pair := range pairs {
certData, keyData := pair[0], pair[1]
var certConfig = &sslconfigs.SSLCertConfig{
IsCA: false,
CertData: certData,
KeyData: keyData,
}
err := certConfig.Init(nil)
if err != nil {
this.Fail("证书验证失败:" + err.Error())
return
}
certConfigs = append(certConfigs, certConfig)
var certName = ""
if len(certConfig.DNSNames) > 0 {
certName = certConfig.DNSNames[0]
if len(certConfig.DNSNames) > 1 {
certName += "等" + types.String(len(certConfig.DNSNames)) + "个域名"
}
}
certConfig.Name = certName
pbCerts = append(pbCerts, &pb.CreateSSLCertsRequestCert{
IsOn: true,
Name: certName,
Description: "",
ServerName: "",
IsCA: false,
CertData: certData,
KeyData: keyData,
TimeBeginAt: certConfig.TimeBeginAt,
TimeEndAt: certConfig.TimeEndAt,
DnsNames: certConfig.DNSNames,
CommonNames: certConfig.CommonNames,
})
}
createResp, err := this.RPC().SSLCertRPC().CreateSSLCerts(this.AdminContext(), &pb.CreateSSLCertsRequest{
UserId: params.UserId,
SSLCerts: pbCerts,
})
if err != nil {
this.ErrorPage(err)
return
}
var certIds = createResp.SslCertIds
if len(certIds) != len(certConfigs) {
this.Fail("上传成功但API返回的证书ID数量错误请反馈给开发者")
return
}
// 返回数据
this.Data["count"] = len(pbCerts)
var certRefs = []*sslconfigs.SSLCertRef{}
for index, cert := range certConfigs {
// ID
cert.Id = certIds[index]
// 减少不必要的数据
cert.CertData = nil
cert.KeyData = nil
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
CertId: cert.Id,
})
}
this.Data["certs"] = certConfigs
this.Data["certRefs"] = certRefs
this.Success()
}

View File

@@ -0,0 +1,8 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package certs
func (this *UploadBatchPopupAction) maxFiles() int {
return 20
}

View File

@@ -18,11 +18,30 @@ func (this *UploadPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UploadPopupAction) RunGet(params struct{}) {
func (this *UploadPopupAction) RunGet(params struct {
ServerId int64
UserId int64
}) {
// 读取服务用户
if params.ServerId > 0 {
serverResp, err := this.RPC().ServerRPC().FindEnabledUserServerBasic(this.AdminContext(), &pb.FindEnabledUserServerBasicRequest{ServerId: params.ServerId})
if err != nil {
this.ErrorPage(err)
return
}
var server = serverResp.Server
if server != nil {
params.UserId = server.UserId
}
}
this.Data["userId"] = params.UserId
this.Show()
}
func (this *UploadPopupAction) RunPost(params struct {
UserId int64
TextMode bool
Name string
IsCA bool
@@ -85,7 +104,7 @@ func (this *UploadPopupAction) RunPost(params struct {
CertData: certData,
KeyData: keyData,
}
err := certConfig.Init()
err := certConfig.Init(nil)
if err != nil {
if params.IsCA {
this.Fail("证书校验错误:" + err.Error())
@@ -107,6 +126,7 @@ func (this *UploadPopupAction) RunPost(params struct {
// 保存
createResp, err := this.RPC().SSLCertRPC().CreateSSLCert(this.AdminContext(), &pb.CreateSSLCertRequest{
IsOn: params.IsOn,
UserId: params.UserId,
Name: params.Name,
Description: params.Description,
ServerName: "",

View File

@@ -45,7 +45,7 @@ func KeyFailReason(reasonCode string) string {
case "requireDomain":
return "找不到Key对应的域名"
case "requireServer":
return "找不到Key对应的网站服务"
return "找不到Key对应的网站"
case "requireUser":
return "该域名不属于当前用户"
case "requireClusterId":

View File

@@ -64,7 +64,7 @@ func (this *IndexAction) RunPost(params struct {
DefaultDomain string
}) {
// 创建日志
defer this.CreateLog(oplogs.LevelInfo, "保存网站服务全局配置")
defer this.CreateLog(oplogs.LevelInfo, "保存网站全局配置")
if len(params.GlobalConfigJSON) == 0 {
this.Fail("错误的配置信息,请刷新当前页面后重试")

View File

@@ -36,24 +36,6 @@ func (this *CreateAction) RunGet(params struct{}) {
}
this.Data["countAuditing"] = countAuditingResp.Count
// 所有集群
resp, err := this.RPC().NodeClusterRPC().FindAllEnabledNodeClusters(this.AdminContext(), &pb.FindAllEnabledNodeClustersRequest{})
if err != nil {
this.ErrorPage(err)
}
if err != nil {
this.ErrorPage(err)
return
}
clusterMaps := []maps.Map{}
for _, cluster := range resp.NodeClusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
})
}
this.Data["clusters"] = clusterMaps
// 服务类型
this.Data["serverTypes"] = serverconfigs.AllServerTypes()
@@ -95,10 +77,6 @@ func (this *CreateAction) RunPost(params struct {
Must *actions.Must
}) {
params.Must.
Field("name", params.Name).
Require("请输入服务名称")
var clusterId = params.ClusterId
// 用户
@@ -129,7 +107,7 @@ func (this *CreateAction) RunPost(params struct {
switch params.ServerType {
case serverconfigs.ServerTypeHTTPProxy, serverconfigs.ServerTypeHTTPWeb:
listen := []*serverconfigs.NetworkAddressConfig{}
var listen = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal([]byte(params.Addresses), &listen)
if err != nil {
this.Fail("端口地址解析失败:" + err.Error())
@@ -166,7 +144,7 @@ func (this *CreateAction) RunPost(params struct {
this.Fail("DEMO模式下不能创建TCP反向代理")
}
listen := []*serverconfigs.NetworkAddressConfig{}
var listen = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal([]byte(params.Addresses), &listen)
if err != nil {
this.Fail("端口地址解析失败:" + err.Error())
@@ -197,13 +175,17 @@ func (this *CreateAction) RunPost(params struct {
tlsConfig.AddListen(addr)
}
}
if len(params.Name) == 0 {
params.Name = "TCP负载均衡"
}
case serverconfigs.ServerTypeUDPProxy:
// 在DEMO模式下不能创建
if teaconst.IsDemoMode {
this.Fail("DEMO模式下不能创建UDP反向代理")
}
listen := []*serverconfigs.NetworkAddressConfig{}
var listen = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal([]byte(params.Addresses), &listen)
if err != nil {
this.Fail("端口地址解析失败:" + err.Error())
@@ -225,20 +207,24 @@ func (this *CreateAction) RunPost(params struct {
udpConfig.AddListen(addr)
}
}
if len(params.Name) == 0 {
params.Name = "UDP负载均衡"
}
default:
this.Fail("请选择正确的服务类型")
}
// 证书
if len(params.CertIdsJSON) > 0 {
certIds := []int64{}
var certIds = []int64{}
err := json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil {
this.ErrorPage(err)
return
}
if len(certIds) > 0 {
certRefs := []*sslconfigs.SSLCertRef{}
var certRefs = []*sslconfigs.SSLCertRef{}
for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true,
@@ -265,7 +251,7 @@ func (this *CreateAction) RunPost(params struct {
this.ErrorPage(err)
return
}
sslPolicyId := sslPolicyIdResp.SslPolicyId
var sslPolicyId = sslPolicyIdResp.SslPolicyId
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,
@@ -282,8 +268,13 @@ func (this *CreateAction) RunPost(params struct {
}
// 检查域名是否已经存在
allServerNames := serverconfigs.PlainServerNames(serverNames)
var allServerNames = serverconfigs.PlainServerNames(serverNames)
if len(allServerNames) > 0 {
// 指定默认名称
if len(params.Name) == 0 {
params.Name = allServerNames[0]
}
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
ServerNames: allServerNames,
NodeClusterId: clusterId,
@@ -396,7 +387,7 @@ func (this *CreateAction) RunPost(params struct {
AdminId: this.AdminId(),
Type: params.ServerType,
Name: params.Name,
ServerNamesJON: params.ServerNames,
ServerNamesJSON: params.ServerNames,
Description: params.Description,
NodeClusterId: clusterId,
IncludeNodesJSON: includeNodesJSON,

View File

@@ -65,14 +65,14 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
reverseProxyConfig := &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = reverseProxyConfig.Init()
err = reverseProxyConfig.Init(nil)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}

View File

@@ -65,14 +65,14 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
reverseProxyConfig := &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = reverseProxyConfig.Init()
err = reverseProxyConfig.Init(nil)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}

View File

@@ -72,7 +72,7 @@ func (this *SettingAction) RunPost(params struct {
return
}
err = reverseProxyConfig.Init()
err = reverseProxyConfig.Init(nil)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}

View File

@@ -41,6 +41,10 @@ func (this *UpdateRefsAction) RunPost(params struct {
// 校验配置
var cacheConfig = webConfig.Cache
if cacheConfig == nil {
this.Success()
return
}
var refs = []*serverconfigs.HTTPCacheRef{}
err = json.Unmarshal(params.RefsJSON, &refs)

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
@@ -30,7 +31,7 @@ func (this *IndexAction) RunGet(params struct {
if !isOk {
return
}
httpConfig := &serverconfigs.HTTPProtocolConfig{}
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
if len(server.HttpJSON) > 0 {
err := json.Unmarshal(server.HttpJSON, httpConfig)
if err != nil {
@@ -40,6 +41,26 @@ func (this *IndexAction) RunGet(params struct {
} else {
httpConfig.IsOn = true
}
_ = httpConfig.Init()
var httpPorts = httpConfig.AllPorts()
// 检查http和https端口冲突
var conflictingPorts = []int{}
if len(server.HttpsJSON) > 0 {
var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
err := json.Unmarshal(server.HttpsJSON, httpsConfig)
if err != nil {
this.ErrorPage(err)
return
}
_ = httpsConfig.Init(nil)
for _, port := range httpsConfig.AllPorts() {
if lists.ContainsInt(httpPorts, port) {
conflictingPorts = append(conflictingPorts, port)
}
}
}
this.Data["conflictingPorts"] = conflictingPorts
this.Data["serverType"] = server.Type
this.Data["httpConfig"] = maps.Map{

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"regexp"
@@ -42,6 +43,27 @@ func (this *IndexAction) RunGet(params struct {
httpsConfig.IsOn = true
}
_ = httpsConfig.Init(nil)
var httpsPorts = httpsConfig.AllPorts()
// 检查http和https端口冲突
var conflictingPorts = []int{}
if len(server.HttpJSON) > 0 {
var httpConfig = &serverconfigs.HTTPProtocolConfig{}
err := json.Unmarshal(server.HttpJSON, httpConfig)
if err != nil {
this.ErrorPage(err)
return
}
_ = httpConfig.Init()
for _, port := range httpConfig.AllPorts() {
if lists.ContainsInt(httpsPorts, port) {
conflictingPorts = append(conflictingPorts, port)
}
}
}
this.Data["conflictingPorts"] = conflictingPorts
var sslPolicy *sslconfigs.SSLPolicy
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
@@ -199,6 +221,7 @@ func (this *IndexAction) RunPost(params struct {
}
}
httpsConfig.SSLPolicy = nil
httpsConfig.SSLPolicyRef = &sslconfigs.SSLPolicyRef{
IsOn: true,
SSLPolicyId: sslPolicyId,

View File

@@ -22,7 +22,7 @@ func FindLocationConfig(parentAction *actionutils.ParentAction, locationId int64
return
}
err = locationConfig.Init()
err = locationConfig.Init(nil)
if err != nil {
parentAction.ErrorPage(err)
return

View File

@@ -56,14 +56,14 @@ func (this *SettingAction) RunPost(params struct {
// TODO 校验配置
reverseProxyConfig := &serverconfigs.ReverseProxyConfig{}
var reverseProxyConfig = &serverconfigs.ReverseProxyConfig{}
err := json.Unmarshal(params.ReverseProxyJSON, reverseProxyConfig)
if err != nil {
this.ErrorPage(err)
return
}
err = reverseProxyConfig.Init()
err = reverseProxyConfig.Init(nil)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}

View File

@@ -3,6 +3,7 @@ package redirects
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
@@ -134,6 +135,23 @@ func (this *CreatePopupAction) RunPost(params struct {
this.FailField("domainAfter", "请输入跳转后域名")
return
}
// 检查用户输入的是否为域名
if !domainutils.ValidateDomainFormat(params.DomainAfter) {
// 是否为URL
u, err := url.Parse(params.DomainAfter)
if err == nil {
if len(u.Host) == 0 {
this.FailField("domainAfter", "跳转后域名输入不正确")
return
}
params.DomainAfter = u.Host
} else {
this.FailField("domainAfter", "跳转后域名输入不正确")
return
}
}
config.DomainAfter = params.DomainAfter
config.DomainAfterScheme = params.DomainAfterScheme
case serverconfigs.HTTPHostRedirectTypePort:

View File

@@ -62,7 +62,7 @@ func (this *SettingAction) RunPost(params struct {
return
}
err = reverseProxyConfig.Init()
err = reverseProxyConfig.Init(nil)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}

View File

@@ -43,7 +43,10 @@ func (this *IndexAction) RunGet(params struct {
// SSL配置
var sslPolicy *sslconfigs.SSLPolicy
if tlsConfig.SSLPolicyRef != nil && tlsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: tlsConfig.SSLPolicyRef.SSLPolicyId})
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: tlsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil {
this.ErrorPage(err)
return

View File

@@ -107,7 +107,7 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
// TABBAR
selectedTabbar, _ := action.Data["mainTab"]
tabbar := actionutils.NewTabbar()
tabbar.Add("服务列表", "", "/servers", "", false)
tabbar.Add("网站列表", "", "/servers", "", false)
if teaconst.IsPlus {
tabbar.Add("看板", "", "/servers/server/boards?serverId="+serverIdString, "dashboard", selectedTabbar == "board")
}
@@ -181,7 +181,7 @@ func (this *ServerHelper) createLogMenu(secondMenuItem string, serverIdString st
// 统计菜单
func (this *ServerHelper) createStatMenu(secondMenuItem string, serverIdString string, serverConfig *serverconfigs.ServerConfig) []maps.Map {
menuItems := []maps.Map{}
var menuItems = []maps.Map{}
menuItems = append(menuItems, maps.Map{
"name": "流量统计",
"url": "/servers/server/stat?serverId=" + serverIdString,
@@ -212,7 +212,7 @@ func (this *ServerHelper) createStatMenu(secondMenuItem string, serverIdString s
// 设置菜单
func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdString string, serverConfig *serverconfigs.ServerConfig) (items []maps.Map) {
menuItems := []maps.Map{
var menuItems = []maps.Map{
{
"name": "基本信息",
"url": "/servers/server/settings?serverId=" + serverIdString,
@@ -249,10 +249,11 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOff": serverConfig.HTTPS != nil && !serverConfig.HTTPS.IsOn,
})
menuItems = append(menuItems, maps.Map{
"name": "源站",
"url": "/servers/server/settings/reverseProxy?serverId=" + serverIdString,
"isActive": secondMenuItem == "reverseProxy",
"isOn": serverConfig.ReverseProxyRef != nil && serverConfig.ReverseProxyRef.IsOn,
"name": "源站",
"url": "/servers/server/settings/reverseProxy?serverId=" + serverIdString,
"isActive": secondMenuItem == "reverseProxy",
"isOn": serverConfig.ReverseProxyRef != nil && serverConfig.ReverseProxyRef.IsOn,
"configCode": serverconfigs.ConfigCodeReverseProxy,
})
menuItems = filterMenuItems(serverConfig, menuItems, serverIdString, secondMenuItem)
@@ -263,10 +264,11 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isActive": false,
})
menuItems = append(menuItems, maps.Map{
"name": "URL跳转",
"url": "/servers/server/settings/redirects?serverId=" + serverIdString,
"isActive": secondMenuItem == "redirects",
"isOn": serverConfig.Web != nil && len(serverConfig.Web.HostRedirects) > 0,
"name": "URL跳转",
"url": "/servers/server/settings/redirects?serverId=" + serverIdString,
"isActive": secondMenuItem == "redirects",
"isOn": serverConfig.Web != nil && len(serverConfig.Web.HostRedirects) > 0,
"configCode": serverconfigs.ConfigCodeHostRedirects,
})
menuItems = append(menuItems, maps.Map{
"name": "路由规则",
@@ -287,58 +289,67 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOn": serverConfig.Web != nil && serverConfig.Web.FirewallRef != nil && serverConfig.Web.FirewallRef.IsOn,
})
menuItems = append(menuItems, maps.Map{
"name": "缓存",
"url": "/servers/server/settings/cache?serverId=" + serverIdString,
"isActive": secondMenuItem == "cache",
"isOn": serverConfig.Web != nil && serverConfig.Web.Cache != nil && serverConfig.Web.Cache.IsOn,
"name": "缓存",
"url": "/servers/server/settings/cache?serverId=" + serverIdString,
"isActive": secondMenuItem == "cache",
"isOn": serverConfig.Web != nil && serverConfig.Web.Cache != nil && serverConfig.Web.Cache.IsOn,
"configCode": serverconfigs.ConfigCodeCache,
})
menuItems = append(menuItems, maps.Map{
"name": "访问鉴权",
"url": "/servers/server/settings/access?serverId=" + serverIdString,
"isActive": secondMenuItem == "access",
"isOn": serverConfig.Web != nil && serverConfig.Web.Auth != nil && serverConfig.Web.Auth.IsOn,
"name": "访问鉴权",
"url": "/servers/server/settings/access?serverId=" + serverIdString,
"isActive": secondMenuItem == "access",
"isOn": serverConfig.Web != nil && serverConfig.Web.Auth != nil && serverConfig.Web.Auth.IsOn,
"configCode": serverconfigs.ConfigCodeAuth,
})
menuItems = append(menuItems, maps.Map{
"name": "防盗链",
"url": "/servers/server/settings/referers?serverId=" + serverIdString,
"isActive": secondMenuItem == "referer",
"isOn": serverConfig.Web != nil && serverConfig.Web.Referers != nil && serverConfig.Web.Referers.IsOn,
"name": "防盗链",
"url": "/servers/server/settings/referers?serverId=" + serverIdString,
"isActive": secondMenuItem == "referer",
"isOn": serverConfig.Web != nil && serverConfig.Web.Referers != nil && serverConfig.Web.Referers.IsOn,
"configCode": serverconfigs.ConfigCodeReferers,
})
menuItems = append(menuItems, maps.Map{
"name": "UA名单",
"url": "/servers/server/settings/userAgent?serverId=" + serverIdString,
"isActive": secondMenuItem == "userAgent",
"isOn": serverConfig.Web != nil && serverConfig.Web.UserAgent != nil && serverConfig.Web.UserAgent.IsOn,
"name": "UA名单",
"url": "/servers/server/settings/userAgent?serverId=" + serverIdString,
"isActive": secondMenuItem == "userAgent",
"isOn": serverConfig.Web != nil && serverConfig.Web.UserAgent != nil && serverConfig.Web.UserAgent.IsOn,
"configCode": serverconfigs.ConfigCodeUserAgent,
})
menuItems = append(menuItems, maps.Map{
"name": "字符编码",
"url": "/servers/server/settings/charset?serverId=" + serverIdString,
"isActive": secondMenuItem == "charset",
"isOn": serverConfig.Web != nil && serverConfig.Web.Charset != nil && serverConfig.Web.Charset.IsOn,
"name": "字符编码",
"url": "/servers/server/settings/charset?serverId=" + serverIdString,
"isActive": secondMenuItem == "charset",
"isOn": serverConfig.Web != nil && serverConfig.Web.Charset != nil && serverConfig.Web.Charset.IsOn,
"configCode": serverconfigs.ConfigCodeCharset,
})
menuItems = append(menuItems, maps.Map{
"name": "访问日志",
"url": "/servers/server/settings/accessLog?serverId=" + serverIdString,
"isActive": secondMenuItem == "accessLog",
"isOn": serverConfig.Web != nil && serverConfig.Web.AccessLogRef != nil && serverConfig.Web.AccessLogRef.IsOn,
"name": "访问日志",
"url": "/servers/server/settings/accessLog?serverId=" + serverIdString,
"isActive": secondMenuItem == "accessLog",
"isOn": serverConfig.Web != nil && serverConfig.Web.AccessLogRef != nil && serverConfig.Web.AccessLogRef.IsOn,
"configCode": serverconfigs.ConfigCodeAccessLog,
})
menuItems = append(menuItems, maps.Map{
"name": "统计",
"url": "/servers/server/settings/stat?serverId=" + serverIdString,
"isActive": secondMenuItem == "stat",
"isOn": serverConfig.Web != nil && serverConfig.Web.StatRef != nil && serverConfig.Web.StatRef.IsOn,
"name": "统计",
"url": "/servers/server/settings/stat?serverId=" + serverIdString,
"isActive": secondMenuItem == "stat",
"isOn": serverConfig.Web != nil && serverConfig.Web.StatRef != nil && serverConfig.Web.StatRef.IsOn,
"configCode": serverconfigs.ConfigCodeStat,
})
menuItems = append(menuItems, maps.Map{
"name": "内容压缩",
"url": "/servers/server/settings/compression?serverId=" + serverIdString,
"isActive": secondMenuItem == "compression",
"isOn": serverConfig.Web != nil && serverConfig.Web.Compression != nil && serverConfig.Web.Compression.IsOn,
"name": "内容压缩",
"url": "/servers/server/settings/compression?serverId=" + serverIdString,
"isActive": secondMenuItem == "compression",
"isOn": serverConfig.Web != nil && serverConfig.Web.Compression != nil && serverConfig.Web.Compression.IsOn,
"configCode": serverconfigs.ConfigCodeCompression,
})
menuItems = append(menuItems, maps.Map{
"name": "自定义页面",
"url": "/servers/server/settings/pages?serverId=" + serverIdString,
"isActive": secondMenuItem == "pages",
"isOn": serverConfig.Web != nil && (len(serverConfig.Web.Pages) > 0 || (serverConfig.Web.Shutdown != nil && serverConfig.Web.Shutdown.IsOn)),
"name": "自定义页面",
"url": "/servers/server/settings/pages?serverId=" + serverIdString,
"isActive": secondMenuItem == "pages",
"isOn": serverConfig.Web != nil && (len(serverConfig.Web.Pages) > 0 || (serverConfig.Web.Shutdown != nil && serverConfig.Web.Shutdown.IsOn)),
"configCode": serverconfigs.ConfigCodePages,
})
menuItems = append(menuItems, maps.Map{
"name": "HTTP Header",
@@ -347,23 +358,26 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOn": this.hasHTTPHeaders(serverConfig.Web),
})
menuItems = append(menuItems, maps.Map{
"name": "Websocket",
"url": "/servers/server/settings/websocket?serverId=" + serverIdString,
"isActive": secondMenuItem == "websocket",
"isOn": serverConfig.Web != nil && serverConfig.Web.WebsocketRef != nil && serverConfig.Web.WebsocketRef.IsOn,
"name": "Websocket",
"url": "/servers/server/settings/websocket?serverId=" + serverIdString,
"isActive": secondMenuItem == "websocket",
"isOn": serverConfig.Web != nil && serverConfig.Web.WebsocketRef != nil && serverConfig.Web.WebsocketRef.IsOn,
"configCode": serverconfigs.ConfigCodeWebsocket,
})
menuItems = append(menuItems, maps.Map{
"name": "WebP",
"url": "/servers/server/settings/webp?serverId=" + serverIdString,
"isActive": secondMenuItem == "webp",
"isOn": serverConfig.Web != nil && serverConfig.Web.WebP != nil && serverConfig.Web.WebP.IsOn,
"name": "WebP",
"url": "/servers/server/settings/webp?serverId=" + serverIdString,
"isActive": secondMenuItem == "webp",
"isOn": serverConfig.Web != nil && serverConfig.Web.WebP != nil && serverConfig.Web.WebP.IsOn,
"configCode": serverconfigs.ConfigCodeWebp,
})
menuItems = append(menuItems, maps.Map{
"name": "静态分发",
"url": "/servers/server/settings/web?serverId=" + serverIdString,
"isActive": secondMenuItem == "web",
"isOn": serverConfig.Web != nil && serverConfig.Web.Root != nil && serverConfig.Web.Root.IsOn,
"name": "静态分发",
"url": "/servers/server/settings/web?serverId=" + serverIdString,
"isActive": secondMenuItem == "web",
"isOn": serverConfig.Web != nil && serverConfig.Web.Root != nil && serverConfig.Web.Root.IsOn,
"configCode": serverconfigs.ConfigCodeRoot,
})
menuItems = append(menuItems, maps.Map{
"name": "Fastcgi",
@@ -379,17 +393,19 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
})
menuItems = append(menuItems, maps.Map{
"name": "访客IP地址",
"url": "/servers/server/settings/remoteAddr?serverId=" + serverIdString,
"isActive": secondMenuItem == "remoteAddr",
"isOn": serverConfig.Web != nil && serverConfig.Web.RemoteAddr != nil && serverConfig.Web.RemoteAddr.IsOn,
"name": "访客IP地址",
"url": "/servers/server/settings/remoteAddr?serverId=" + serverIdString,
"isActive": secondMenuItem == "remoteAddr",
"isOn": serverConfig.Web != nil && serverConfig.Web.RemoteAddr != nil && serverConfig.Web.RemoteAddr.IsOn,
"configCode": serverconfigs.ConfigCodeRemoteAddr,
})
menuItems = append(menuItems, maps.Map{
"name": "请求限制",
"url": "/servers/server/settings/requestLimit?serverId=" + serverIdString,
"isActive": secondMenuItem == "requestLimit",
"isOn": serverConfig.Web != nil && serverConfig.Web.RequestLimit != nil && serverConfig.Web.RequestLimit.IsOn,
"name": "请求限制",
"url": "/servers/server/settings/requestLimit?serverId=" + serverIdString,
"isActive": secondMenuItem == "requestLimit",
"isOn": serverConfig.Web != nil && serverConfig.Web.RequestLimit != nil && serverConfig.Web.RequestLimit.IsOn,
"configCode": serverconfigs.ConfigCodeRequestLimit,
})
menuItems = filterMenuItems2(serverConfig, menuItems, serverIdString, secondMenuItem)

View File

@@ -79,6 +79,9 @@ func (this *IndexAction) RunPost(params struct {
DenySearchEngines bool
DenySpiders bool
CheckClientFingerprint bool
CheckClientRegion bool
DomainsJSON []byte
Must *actions.Must
@@ -150,6 +153,10 @@ func (this *IndexAction) RunPost(params struct {
// 允许记住登录
config.AllowRememberLogin = params.AllowRememberLogin
// Cookie检查
config.CheckClientFingerprint = params.CheckClientFingerprint
config.CheckClientRegion = params.CheckClientRegion
err = configloaders.UpdateSecurityConfig(config)
if err != nil {
this.ErrorPage(err)

View File

@@ -0,0 +1,86 @@
package adminserverutils
import (
"errors"
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"gopkg.in/yaml.v3"
"net"
"os"
"time"
)
var ServerConfigIsChanged = false
const configFilename = "server.yaml"
// LoadServerConfig 读取当前服务配置
func LoadServerConfig() (*TeaGo.ServerConfig, error) {
configFile := Tea.ConfigFile(configFilename)
data, err := os.ReadFile(configFile)
if err != nil {
return nil, err
}
var serverConfig = &TeaGo.ServerConfig{}
err = yaml.Unmarshal(data, serverConfig)
if err != nil {
return nil, err
}
return serverConfig, nil
}
// WriteServerConfig 保存当前服务配置
func WriteServerConfig(serverConfig *TeaGo.ServerConfig) error {
data, err := yaml.Marshal(serverConfig)
if err != nil {
return err
}
err = os.WriteFile(Tea.ConfigFile(configFilename), data, 0666)
if err != nil {
return err
}
ServerConfigIsChanged = true
return nil
}
// ReadServerHTTPS 检查HTTPS地址
func ReadServerHTTPS() (port int, err error) {
config, err := LoadServerConfig()
if err != nil {
return 0, err
}
if config == nil {
return 0, errors.New("could not load server config")
}
if config.Https.On && len(config.Https.Listen) > 0 {
for _, listen := range config.Https.Listen {
_, portString, splitErr := net.SplitHostPort(listen)
if splitErr == nil {
var portInt = types.Int(portString)
if portInt > 0 {
// 是否已经启动
checkErr := func() error {
conn, connErr := net.DialTimeout("tcp", ":"+portString, 1*time.Second)
if connErr != nil {
return connErr
}
_ = conn.Close()
return nil
}()
if checkErr != nil {
continue
}
port = portInt
err = nil
break
}
}
}
}
return
}

View File

@@ -2,6 +2,7 @@ package server
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
)
type IndexAction struct {
@@ -13,9 +14,9 @@ func (this *IndexAction) Init() {
}
func (this *IndexAction) RunGet(params struct{}) {
this.Data["serverIsChanged"] = serverConfigIsChanged
this.Data["serverIsChanged"] = adminserverutils.ServerConfigIsChanged
serverConfig, err := loadServerConfig()
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.ErrorPage(err)
return

View File

@@ -3,6 +3,7 @@ package server
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
"github.com/iwind/TeaGo/actions"
"net"
)
@@ -16,7 +17,7 @@ func (this *UpdateHTTPPopupAction) Init() {
}
func (this *UpdateHTTPPopupAction) RunGet(params struct{}) {
serverConfig, err := loadServerConfig()
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.ErrorPage(err)
return
@@ -38,7 +39,7 @@ func (this *UpdateHTTPPopupAction) RunPost(params struct {
this.Fail("请输入绑定地址")
}
serverConfig, err := loadServerConfig()
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.Fail("保存失败:" + err.Error())
}
@@ -58,7 +59,7 @@ func (this *UpdateHTTPPopupAction) RunPost(params struct {
}
serverConfig.Http.Listen = listen
err = writeServerConfig(serverConfig)
err = adminserverutils.WriteServerConfig(serverConfig)
if err != nil {
this.Fail("保存失败:" + err.Error())
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
adminserverutils "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/server/admin-server-utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/Tea"
@@ -21,7 +22,7 @@ func (this *UpdateHTTPSPopupAction) Init() {
}
func (this *UpdateHTTPSPopupAction) RunGet(params struct{}) {
serverConfig, err := loadServerConfig()
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.ErrorPage(err)
return
@@ -47,7 +48,7 @@ func (this *UpdateHTTPSPopupAction) RunGet(params struct{}) {
CertData: certData,
KeyData: keyData,
}
_ = certConfig.Init()
_ = certConfig.Init(nil)
certConfig.CertData = nil
certConfig.KeyData = nil
certConfigs = append(certConfigs, certConfig)
@@ -70,7 +71,7 @@ func (this *UpdateHTTPSPopupAction) RunPost(params struct {
this.Fail("请输入绑定地址")
}
serverConfig, err := loadServerConfig()
serverConfig, err := adminserverutils.LoadServerConfig()
if err != nil {
this.Fail("保存失败:" + err.Error())
}
@@ -133,7 +134,7 @@ func (this *UpdateHTTPSPopupAction) RunPost(params struct {
serverConfig.Https.Cert = "configs/https.cert.pem"
}
err = writeServerConfig(serverConfig)
err = adminserverutils.WriteServerConfig(serverConfig)
if err != nil {
this.Fail("保存配置失败:" + err.Error())
}

View File

@@ -1,41 +0,0 @@
package server
import (
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
"gopkg.in/yaml.v3"
"os"
)
var serverConfigIsChanged = false
// 读取当前服务配置
func loadServerConfig() (*TeaGo.ServerConfig, error) {
configFile := Tea.ConfigFile("server.yaml")
data, err := os.ReadFile(configFile)
if err != nil {
return nil, err
}
serverConfig := &TeaGo.ServerConfig{}
err = yaml.Unmarshal(data, serverConfig)
if err != nil {
return nil, err
}
return serverConfig, nil
}
// 保存当前服务配置
func writeServerConfig(serverConfig *TeaGo.ServerConfig) error {
data, err := yaml.Marshal(serverConfig)
if err != nil {
return err
}
err = os.WriteFile(Tea.ConfigFile("server.yaml"), data, 0666)
if err != nil {
return err
}
serverConfigIsChanged = true
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql/mysqlinstallers/utils"
stringutil "github.com/iwind/TeaGo/utils/string"
timeutil "github.com/iwind/TeaGo/utils/time"
"io"
"net"
@@ -16,6 +17,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@@ -106,6 +108,31 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
time.Sleep(1 * time.Second)
}
}
// create symbolic links
{
var libFile = "/usr/lib64/libncurses.so.5"
_, err = os.Stat(libFile)
if err != nil && os.IsNotExist(err) {
var latestLibFile = this.findLatestVersionFile("/usr/lib64", "libncurses.so.")
if len(latestLibFile) > 0 {
this.log("link '" + latestLibFile + "' to '" + libFile + "'")
_ = os.Symlink(latestLibFile, libFile)
}
}
}
{
var libFile = "/usr/lib64/libtinfo.so.5"
_, err = os.Stat(libFile)
if err != nil && os.IsNotExist(err) {
var latestLibFile = this.findLatestVersionFile("/usr/lib64", "libtinfo.so.")
if len(latestLibFile) > 0 {
this.log("link '" + latestLibFile + "' to '" + libFile + "'")
_ = os.Symlink(latestLibFile, libFile)
}
}
}
}
// create 'mysql' user group
@@ -486,6 +513,15 @@ func (this *MySQLInstaller) Password() string {
// create my.cnf content
func (this *MySQLInstaller) createMyCnf(baseDir string, dataDir string) string {
var memoryTotalG = 1
if runtime.GOOS == "linux" {
memoryTotalG = this.sysMemoryGB() / 2
if memoryTotalG <= 0 {
memoryTotalG = 1
}
}
return `
[mysqld]
port=3306
@@ -499,7 +535,8 @@ binlog_cache_size=1M
binlog_stmt_cache_size=1M
thread_cache_size=32
binlog_expire_logs_seconds=604800
`
innodb_sort_buffer_size=8M
innodb_buffer_pool_size=` + strconv.Itoa(memoryTotalG) + "G"
}
// generate random password
@@ -544,8 +581,8 @@ After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=1s
Restart=on-failure
RestartSec=5s
ExecStart=${BASE_DIR}/support-files/mysql.server start
ExecStop=${BASE_DIR}/support-files/mysql.server stop
ExecRestart=${BASE_DIR}/support-files/mysql.server restart
@@ -610,3 +647,67 @@ func (this *MySQLInstaller) lookupUserAdd() (string, error) {
}
return "", errors.New("not found")
}
func (this *MySQLInstaller) findLatestVersionFile(dir string, prefix string) string {
files, err := filepath.Glob(filepath.Clean(dir + "/" + prefix + "*"))
if err != nil {
return ""
}
var resultFile = ""
var lastVersion = ""
var reg = regexp.MustCompile(`\.([\d.]+)`)
for _, file := range files {
var filename = filepath.Base(file)
var matches = reg.FindStringSubmatch(filename)
if len(matches) > 1 {
var version = matches[1]
if len(lastVersion) == 0 || stringutil.VersionCompare(lastVersion, version) < 0 {
lastVersion = version
resultFile = file
}
}
}
return resultFile
}
func (this *MySQLInstaller) sysMemoryGB() int {
if runtime.GOOS != "linux" {
return 0
}
meminfoData, err := os.ReadFile("/proc/meminfo")
if err != nil {
return 0
}
for _, line := range bytes.Split(meminfoData, []byte{'\n'}) {
line = bytes.TrimSpace(line)
if bytes.Contains(line, []byte{':'}) {
name, value, found := bytes.Cut(line, []byte{':'})
if found {
name = bytes.TrimSpace(name)
if bytes.Equal(name, []byte("MemTotal")) {
for _, unit := range []string{"gB", "mB", "kB"} {
if bytes.Contains(value, []byte(unit)) {
value = bytes.TrimSpace(bytes.ReplaceAll(value, []byte(unit), nil))
valueInt, err := strconv.Atoi(string(value))
if err != nil {
return 0
}
switch unit {
case "gB":
return valueInt
case "mB":
return valueInt / 1024
case "kB":
return valueInt / 1024 / 1024
}
return 0
}
}
}
}
}
}
return 0
}

View File

@@ -34,7 +34,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
if !Tea.IsTesting() && len(componentsData) > 0 {
this.AddHeader("Last-Modified", "Fri, 06 Sep 2019 08:29:50 GMT")
this.Write(componentsData)
_, _ = this.Write(componentsData)
return
}

View File

@@ -15,7 +15,7 @@ type ThemeAction struct {
func (this *ThemeAction) RunPost(params struct{}) {
theme := configloaders.FindAdminTheme(this.AdminId())
var themes = []string{"theme1", "theme2", "theme3", "theme4", "theme5"}
var themes = []string{"theme1", "theme2", "theme3", "theme4", "theme5", "theme6", "theme7"}
var nextTheme = "theme1"
if len(theme) == 0 {
nextTheme = "theme2"

View File

@@ -19,8 +19,8 @@ func FindAllMenuMaps(nodeLogsType string, countUnreadNodeLogs int64, countUnread
{
"code": "servers",
"module": configloaders.AdminModuleCodeServer,
"name": "网站服务",
"subtitle": "服务列表",
"name": "网站列表",
"subtitle": "",
"icon": "clone outsize",
"subItems": []maps.Map{
{

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
@@ -169,12 +170,47 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
var adminId = session.GetInt64("adminId")
if adminId <= 0 {
var errString = session.GetString("@error")
if len(errString) > 0 {
action.WriteString("read session failed: " + errString)
return false
}
this.login(action)
return false
}
// 检查指纹
if securityConfig != nil && securityConfig.CheckClientFingerprint {
var clientFingerprint = session.GetString("@fingerprint")
if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
}
}
// 检查区域
if securityConfig != nil && securityConfig.CheckClientRegion {
var oldClientIP = session.GetString("@ip")
var currentClientIP = loginutils.RemoteIP(action)
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
var newRegion = loginutils.LookupIPRegion(currentClientIP)
if newRegion != oldRegion {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
}
}
}
// 检查用户是否存在
if !configloaders.CheckAdmin(adminId) {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)

View File

@@ -4,6 +4,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/iwind/TeaGo/actions"
"net"
"net/http"
@@ -53,35 +54,11 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
// StoreAdmin 存储用户名到SESSION
func (this *UserShouldAuth) StoreAdmin(adminId int64, remember bool) {
// 修改sid的时间
if remember {
cookie := &http.Cookie{
Name: teaconst.CookieSID,
Value: this.action.Session().Sid,
Path: "/",
MaxAge: 14 * 86400,
HttpOnly: true,
}
if this.action.Request.TLS != nil {
cookie.SameSite = http.SameSiteStrictMode
cookie.Secure = true
}
this.action.AddCookie(cookie)
} else {
cookie := &http.Cookie{
Name: teaconst.CookieSID,
Value: this.action.Session().Sid,
Path: "/",
MaxAge: 0,
HttpOnly: true,
}
if this.action.Request.TLS != nil {
cookie.SameSite = http.SameSiteStrictMode
cookie.Secure = true
}
this.action.AddCookie(cookie)
}
this.action.Session().Write("adminId", numberutils.FormatInt64(adminId))
loginutils.SetCookie(this.action, remember)
var session = this.action.Session()
session.Write("adminId", numberutils.FormatInt64(adminId))
session.Write("@fingerprint", loginutils.CalculateClientFingerprint(this.action))
session.Write("@ip", loginutils.RemoteIP(this.action))
}
func (this *UserShouldAuth) IsUser() bool {
@@ -93,5 +70,6 @@ func (this *UserShouldAuth) AdminId() int {
}
func (this *UserShouldAuth) Logout() {
loginutils.UnsetCookie(this.action)
this.action.Session().Delete()
}

File diff suppressed because one or more lines are too long

View File

@@ -503,7 +503,7 @@ Vue.component("node-ddos-protection-config-box", {
<prior-checkbox :v-config="config.tcp" v-if="isNode"></prior-checkbox>
<tbody v-show="config.tcp.isPrior || !isNode">
<tr>
<td class="title">启用</td>
<td class="title">启用DDoS防护</td>
<td>
<checkbox v-model="config.tcp.isOn"></checkbox>
</td>
@@ -1139,7 +1139,7 @@ Vue.component("message-row", {
<!-- 证书即将过期 -->
<div v-if="message.type == 'SSLCertExpiring'" style="margin-top: 0.8em">
<a href="" @click.prevent="viewCert(params.certId)" target="_top">查看证书</a> &nbsp;|&nbsp; <a :href="'/servers/certs/acme'" v-if="params != null && params.acmeTaskId > 0" target="_top">查看任务&raquo;</a>
<a href="" @click.prevent="viewCert(params.certId)" target="_top">查看证书</a><span v-if="params != null && params.acmeTaskId > 0"> &nbsp;|&nbsp; <a :href="'/servers/certs/acme'" target="_top">查看任务&raquo;</a></span>
</div>
<!-- 证书续期成功 -->
@@ -3616,7 +3616,11 @@ Vue.component("http-request-conds-box", {
})
Vue.component("ssl-config-box", {
props: ["v-ssl-policy", "v-protocol", "v-server-id"],
props: [
"v-ssl-policy",
"v-protocol",
"v-server-id"
],
created: function () {
let that = this
setTimeout(function () {
@@ -3724,12 +3728,23 @@ Vue.component("ssl-config-box", {
selectedCertIds.push(cert.id.toString())
})
}
teaweb.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds, {
let serverId = this.vServerId
if (serverId == null) {
serverId = 0
}
teaweb.popup("/servers/certs/selectPopup?selectedCertIds=" + selectedCertIds + "&serverId=" + serverId, {
width: "50em",
height: "30em",
callback: function (resp) {
that.policy.certRefs.push(resp.data.certRef)
that.policy.certs.push(resp.data.cert)
if (resp.data.cert != null && resp.data.certRef != null) {
that.policy.certRefs.push(resp.data.certRef)
that.policy.certs.push(resp.data.cert)
}
if (resp.data.certs != null && resp.data.certRefs != null) {
that.policy.certRefs.$pushAll(resp.data.certRefs)
that.policy.certs.$pushAll(resp.data.certs)
}
that.$forceUpdate()
}
})
},
@@ -3737,8 +3752,12 @@ Vue.component("ssl-config-box", {
// 上传证书
uploadCert: function () {
let that = this
teaweb.popup("/servers/certs/uploadPopup", {
height: "28em",
let serverId = this.vServerId
if (typeof serverId != "number" && typeof serverId != "string") {
serverId = 0
}
teaweb.popup("/servers/certs/uploadPopup?serverId=" + serverId, {
height: "30em",
callback: function (resp) {
teaweb.success("上传成功", function () {
that.policy.certRefs.push(resp.data.certRef)
@@ -3748,6 +3767,28 @@ Vue.component("ssl-config-box", {
})
},
// 批量上传
uploadBatch: function () {
let that = this
let serverId = this.vServerId
if (typeof serverId != "number" && typeof serverId != "string") {
serverId = 0
}
teaweb.popup("/servers/certs/uploadBatchPopup?serverId=" + serverId, {
callback: function (resp) {
if (resp.data.cert != null) {
that.policy.certRefs.push(resp.data.certRef)
that.policy.certs.push(resp.data.cert)
}
if (resp.data.certs != null) {
that.policy.certRefs.$pushAll(resp.data.certRefs)
that.policy.certs.$pushAll(resp.data.certs)
}
that.$forceUpdate()
}
})
},
// 申请证书
requestCert: function () {
// 已经在证书中的域名
@@ -3929,8 +3970,15 @@ Vue.component("ssl-config-box", {
width: "50em",
height: "30em",
callback: function (resp) {
that.policy.clientCARefs.push(resp.data.certRef)
that.policy.clientCACerts.push(resp.data.cert)
if (resp.data.cert != null && resp.data.certRef != null) {
that.policy.clientCARefs.push(resp.data.certRef)
that.policy.clientCACerts.push(resp.data.cert)
}
if (resp.data.certs != null && resp.data.certRefs != null) {
that.policy.clientCARefs.$pushAll(resp.data.certRefs)
that.policy.clientCACerts.$pushAll(resp.data.certs)
}
that.$forceUpdate()
}
})
},
@@ -3986,7 +4034,10 @@ Vue.component("ssl-config-box", {
<div class="ui divider"></div>
</div>
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="requestCert()" v-if="vServerId != null && vServerId > 0">申请免费证书</button>
</td>
</tr>
@@ -4142,7 +4193,7 @@ Vue.component("ssl-config-box", {
<td>客户端认证CA证书</td>
<td>
<div v-if="policy.clientCACerts != null && policy.clientCACerts.length > 0">
<div class="ui label small" v-for="(cert, index) in policy.clientCACerts">
<div class="ui label small basic" v-for="(cert, index) in policy.clientCACerts">
{{cert.name}} / {{cert.dnsNames}} / 有效至{{formatTime(cert.timeEndAt)}} &nbsp; <a href="" title="删除" @click.prevent="removeClientCACert()"><i class="icon remove"></i></a>
</div>
<div class="ui divider"></div>
@@ -4352,7 +4403,9 @@ Vue.component("ssl-certs-box", {
"v-protocol", // 协议https|tls
"v-view-size", // 弹窗尺寸normal, mini
"v-single-mode", // 单证书模式
"v-description" // 描述文字
"v-description", // 描述文字
"v-domains", // 搜索的域名列表或者函数
"v-user-id" // 用户ID
],
data: function () {
let certs = this.vCerts
@@ -4390,8 +4443,8 @@ Vue.component("ssl-certs-box", {
// 选择证书
selectCert: function () {
let that = this
let width = "50em"
let height = "30em"
let width = "54em"
let height = "32em"
let viewSize = this.vViewSize
if (viewSize == null) {
viewSize = "normal"
@@ -4400,11 +4453,41 @@ Vue.component("ssl-certs-box", {
width = "35em"
height = "20em"
}
teaweb.popup("/servers/certs/selectPopup?viewSize=" + viewSize, {
let searchingDomains = []
if (this.vDomains != null) {
if (typeof this.vDomains == "function") {
let resultDomains = this.vDomains()
if (resultDomains != null && typeof resultDomains == "object" && (resultDomains instanceof Array)) {
searchingDomains = resultDomains
}
} else if (typeof this.vDomains == "object" && (this.vDomains instanceof Array)) {
searchingDomains = this.vDomains
}
if (searchingDomains.length > 10000) {
searchingDomains = searchingDomains.slice(0, 10000)
}
}
let selectedCertIds = this.certs.map(function (cert) {
return cert.id
})
let userId = this.vUserId
if (userId == null) {
userId = 0
}
teaweb.popup("/servers/certs/selectPopup?viewSize=" + viewSize + "&searchingDomains=" + window.encodeURIComponent(searchingDomains.join(",")) + "&selectedCertIds=" + selectedCertIds.join(",") + "&userId=" + userId, {
width: width,
height: height,
callback: function (resp) {
that.certs.push(resp.data.cert)
if (resp.data.cert != null) {
that.certs.push(resp.data.cert)
}
if (resp.data.certs != null) {
that.certs.$pushAll(resp.data.certs)
}
that.$forceUpdate()
}
})
},
@@ -4412,16 +4495,46 @@ Vue.component("ssl-certs-box", {
// 上传证书
uploadCert: function () {
let that = this
teaweb.popup("/servers/certs/uploadPopup", {
let userId = this.vUserId
if (typeof userId != "number" && typeof userId != "string") {
userId = 0
}
teaweb.popup("/servers/certs/uploadPopup?userId=" + userId, {
height: "28em",
callback: function (resp) {
teaweb.success("上传成功", function () {
that.certs.push(resp.data.cert)
if (resp.data.cert != null) {
that.certs.push(resp.data.cert)
}
if (resp.data.certs != null) {
that.certs.$pushAll(resp.data.certs)
}
that.$forceUpdate()
})
}
})
},
// 批量上传
uploadBatch: function () {
let that = this
let userId = this.vUserId
if (typeof userId != "number" && typeof userId != "string") {
userId = 0
}
teaweb.popup("/servers/certs/uploadBatchPopup?userId=" + userId, {
callback: function (resp) {
if (resp.data.cert != null) {
that.certs.push(resp.data.cert)
}
if (resp.data.certs != null) {
that.certs.$pushAll(resp.data.certs)
}
that.$forceUpdate()
}
})
},
// 格式化时间
formatTime: function (timestamp) {
return new Date(timestamp * 1000).format("Y-m-d")
@@ -4447,7 +4560,9 @@ Vue.component("ssl-certs-box", {
</div>
<div v-if="buttonsVisible()">
<button class="ui button tiny" type="button" @click.prevent="selectCert()">选择已有证书</button> &nbsp;
<span class="disabled">|</span> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadCert()">上传新证书</button> &nbsp;
<button class="ui button tiny" type="button" @click.prevent="uploadBatch()">批量上传证书</button> &nbsp;
</div>
</div>`
})
@@ -5275,7 +5390,7 @@ Vue.component("http-firewall-config-box", {
<td class="title">启用WAF</td>
<td>
<checkbox v-model="firewall.isOn"></checkbox>
<p class="comment">选中后,表示启用当前网站服务的WAF功能。</p>
<p class="comment">选中后表示启用当前网站的WAF功能。</p>
</td>
</tr>
</tbody>
@@ -6112,7 +6227,7 @@ Vue.component("http-firewall-checkpoint-cc", {
<td>检查请求来源指纹</td>
<td>
<checkbox v-model="enableFingerprint"></checkbox>
<p class="comment">在接收到HTTPS请求时尝试检查请求来源的指纹用来检测代理服务和爬虫攻击。</p>
<p class="comment">在接收到HTTPS请求时尝试检查请求来源的指纹用来检测代理服务和爬虫攻击;如果你在网站前面放置了别的反向代理服务,请取消此选项。</p>
</td>
</tr>
<tr>
@@ -7026,80 +7141,102 @@ Vue.component("http-rewrite-labels-label", {
})
Vue.component("server-name-box", {
props: ["v-server-names"],
data: function () {
let serverNames = this.vServerNames;
if (serverNames == null) {
serverNames = []
}
return {
serverNames: serverNames,
isSearching: false,
keyword: ""
}
},
methods: {
addServerName: function () {
window.UPDATING_SERVER_NAME = null
let that = this
teaweb.popup("/servers/addServerNamePopup", {
callback: function (resp) {
var serverName = resp.data.serverName
that.serverNames.push(serverName)
}
});
},
props: ["v-server-names"],
data: function () {
let serverNames = this.vServerNames;
if (serverNames == null) {
serverNames = []
}
return {
serverNames: serverNames,
isSearching: false,
keyword: ""
}
},
methods: {
addServerName: function () {
window.UPDATING_SERVER_NAME = null
let that = this
teaweb.popup("/servers/addServerNamePopup", {
callback: function (resp) {
var serverName = resp.data.serverName
that.serverNames.push(serverName)
}
});
},
removeServerName: function (index) {
this.serverNames.$remove(index)
},
removeServerName: function (index) {
this.serverNames.$remove(index)
},
updateServerName: function (index, serverName) {
window.UPDATING_SERVER_NAME = serverName
let that = this
teaweb.popup("/servers/addServerNamePopup", {
callback: function (resp) {
var serverName = resp.data.serverName
Vue.set(that.serverNames, index, serverName)
}
});
},
showSearchBox: function () {
this.isSearching = !this.isSearching
if (this.isSearching) {
let that = this
setTimeout(function () {
that.$refs.keywordRef.focus()
}, 200)
} else {
this.keyword = ""
}
},
},
watch: {
keyword: function (v) {
this.serverNames.forEach(function (serverName) {
if (v.length == 0) {
serverName.isShowing = true
return
}
if (serverName.subNames == null || serverName.subNames.length == 0) {
if (!teaweb.match(serverName.name, v)) {
serverName.isShowing = false
}
} else {
let found = false
serverName.subNames.forEach(function (subName) {
if (teaweb.match(subName, v)) {
found = true
}
})
serverName.isShowing = found
}
})
}
},
template: `<div>
updateServerName: function (index, serverName) {
window.UPDATING_SERVER_NAME = teaweb.clone(serverName)
let that = this
teaweb.popup("/servers/addServerNamePopup", {
callback: function (resp) {
var serverName = resp.data.serverName
Vue.set(that.serverNames, index, serverName)
}
});
},
showSearchBox: function () {
this.isSearching = !this.isSearching
if (this.isSearching) {
let that = this
setTimeout(function () {
that.$refs.keywordRef.focus()
}, 200)
} else {
this.keyword = ""
}
},
allServerNames: function () {
if (this.serverNames == null) {
return []
}
let result = []
this.serverNames.forEach(function (serverName) {
if (serverName.subNames != null && serverName.subNames.length > 0) {
serverName.subNames.forEach(function (subName) {
if (subName != null && subName.length > 0) {
if (!result.$contains(subName)) {
result.push(subName)
}
}
})
} else if (serverName.name != null && serverName.name.length > 0) {
if (!result.$contains(serverName.name)) {
result.push(serverName.name)
}
}
})
return result
}
},
watch: {
keyword: function (v) {
this.serverNames.forEach(function (serverName) {
if (v.length == 0) {
serverName.isShowing = true
return
}
if (serverName.subNames == null || serverName.subNames.length == 0) {
if (!teaweb.match(serverName.name, v)) {
serverName.isShowing = false
}
} else {
let found = false
serverName.subNames.forEach(function (subName) {
if (teaweb.match(subName, v)) {
found = true
}
})
serverName.isShowing = found
}
})
}
},
template: `<div>
<input type="hidden" name="serverNames" :value="JSON.stringify(serverNames)"/>
<div v-if="serverNames.length > 0">
<div v-for="(serverName, index) in serverNames" class="ui label small basic" :class="{hidden: serverName.isShowing === false}">
@@ -7133,6 +7270,7 @@ Vue.component("uam-config-box", {
config = {
isPrior: false,
isOn: false,
addToWhiteList: true,
onlyURLPatterns: [],
exceptURLPatterns: []
}
@@ -7172,6 +7310,13 @@ Vue.component("uam-config-box", {
</tr>
</tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>加入IP白名单</td>
<td>
<checkbox v-model="config.addToWhiteList"></checkbox>
<p class="comment">选中后表示验证通过后将访问者IP加入到临时白名单中此IP下次访问时不再校验5秒盾此白名单只对5秒盾有效不影响其他规则。此选项主要用于可能无法正常使用Cookie的网站。</p>
</td>
</tr>
<tr>
<td>例外URL</td>
<td>
@@ -8436,8 +8581,8 @@ Vue.component("http-firewall-actions-box", {
<option value="service">当前服务</option>
<option value="global">所有服务</option>
</select>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站服务的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站服务的访问。</p>
<p class="comment" v-if="blockScope == 'service'">只封禁用户对当前网站的访问,其他服务不受影响。</p>
<p class="comment" v-if="blockScope =='global'">封禁用户对所有网站的访问。</p>
</td>
</tr>
<tr v-if="actionCode == 'block'">
@@ -8812,10 +8957,13 @@ Vue.component("user-selector", {
} else {
this.$emit("change", 0)
}
},
clear: function () {
this.$refs.comboBox.clear()
}
},
template: `<div>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change"></combo-box>
<combo-box placeholder="选择用户" :data-url="dataURL" :data-key="'users'" data-search="on" name="userId" :v-value="userId" @change="change" ref="comboBox"></combo-box>
</div>`
})
@@ -9626,10 +9774,20 @@ Vue.component("http-cc-config-box", {
config = {
isPrior: false,
isOn: false,
enableFingerprint: true,
enableGET302: true,
onlyURLPatterns: [],
exceptURLPatterns: []
}
}
if (typeof config.enableFingerprint != "boolean") {
config.enableFingerprint = true
}
if (typeof config.enableGET302 != "boolean") {
config.enableGET302 = true
}
if (config.onlyURLPatterns == null) {
config.onlyURLPatterns = []
}
@@ -9655,7 +9813,7 @@ Vue.component("http-cc-config-box", {
<td class="title">启用CC无感防护</td>
<td>
<checkbox v-model="config.isOn"></checkbox>
<p class="comment"><plus-label></plus-label>启用后自动检测并拦截CC攻击此功能不需要开启WAF功能。</p>
<p class="comment"><plus-label></plus-label>启用后自动检测并拦截CC攻击。</p>
</td>
</tr>
</tbody>
@@ -9679,6 +9837,20 @@ Vue.component("http-cc-config-box", {
<p class="comment">如果填写了支持URL表示只对这些URL进行CC防护处理如果不填则表示支持所有的URL。</p>
</td>
</tr>
<tr>
<td>检查请求来源指纹</td>
<td>
<checkbox v-model="config.enableFingerprint"></checkbox>
<p class="comment">在接收到HTTPS请求时尝试检查请求来源的指纹用来检测代理服务和爬虫攻击如果你在网站前面放置了别的反向代理服务请取消此选项。</p>
</td>
</tr>
<tr>
<td>启用GET302校验</td>
<td>
<checkbox v-model="config.enableGET302"></checkbox>
<p class="comment">选中后表示自动通过GET302方法来校验客户端。</p>
</td>
</tr>
</tr>
</tbody>
</table>
@@ -9977,7 +10149,7 @@ Vue.component("http-access-log-box", {
this.$refs.box.parentNode.style.cssText = ""
},
mismatch: function () {
teaweb.warn("当前访问没有匹配到任何网站服务")
teaweb.warn("当前访问没有匹配到任何网站")
}
},
template: `<div style="word-break: break-all" :style="{'color': (accessLog.status >= 400) ? '#dc143c' : ''}" ref="box">
@@ -9985,7 +10157,7 @@ Vue.component("http-access-log-box", {
<a v-if="accessLog.node != null && accessLog.node.nodeCluster != null" :href="'/clusters/cluster/node?nodeId=' + accessLog.node.id + '&clusterId=' + accessLog.node.nodeCluster.id" title="点击查看节点详情" target="_top"><span class="grey">[{{accessLog.node.name}}<span v-if="!accessLog.node.name.endsWith('节点')">节点</span>]</span></a>
<!-- 服务 -->
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站服务" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<a :href="'/servers/server/log?serverId=' + accessLog.serverId" title="点击到网站" v-if="vShowServerLink && accessLog.serverId > 0"><span class="grey">[服务]</span></a>
<span v-if="vShowServerLink && (accessLog.serverId == null || accessLog.serverId == 0)" @click.prevent="mismatch()"><span class="disabled">[服务]</span></span>
<span v-if="accessLog.region != null && accessLog.region.length > 0" class="grey"><ip-box :v-ip="accessLog.remoteAddr">[{{accessLog.region}}]</ip-box></span>
@@ -11562,6 +11734,27 @@ Vue.component("http-access-log-search-box", {
</div>`
})
Vue.component("server-config-copy-link", {
props: ["v-server-id", "v-config-code"],
data: function () {
return {
serverId: this.vServerId,
configCode: this.vConfigCode
}
},
methods: {
copy: function () {
teaweb.popup("/servers/server/settings/copy?serverId=" + this.serverId + "&configCode=" + this.configCode, {
height: "25em",
callback: function () {
teaweb.success("复制成功")
}
})
}
},
template: `<a href=\"" class="item" @click.prevent="copy" style="padding-right:0"><span style="font-size: 0.8em">复制</span>&nbsp;<i class="icon copy small"></i></a>`
})
// 显示指标对象名
Vue.component("metric-key-label", {
props: ["v-key"],
@@ -13809,7 +14002,7 @@ Vue.component("http-firewall-captcha-options", {
<td>失败全局封禁</td>
<td>
<checkbox v-model="options.failBlockScopeAll"></checkbox>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站服务的访问。</p>
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
</td>
</tr>
<tr>
@@ -14691,7 +14884,8 @@ Vue.component("network-addresses-box", {
addresses: addresses,
protocol: protocol,
name: name,
from: from
from: from,
isEditing: false
}
},
watch: {
@@ -14706,6 +14900,8 @@ Vue.component("network-addresses-box", {
},
methods: {
addAddr: function () {
this.isEditing = true
let that = this
window.UPDATING_ADDR = null
@@ -14773,18 +14969,32 @@ Vue.component("network-addresses-box", {
},
supportRange: function () {
return this.vSupportRange || (this.vServerType == "tcpProxy" || this.vServerType == "udpProxy")
},
edit: function () {
this.isEditing = true
}
},
template: `<div>
<input type="hidden" :name="name" :value="JSON.stringify(addresses)"/>
<div v-if="addresses.length > 0">
<div class="ui label small basic" v-for="(addr, index) in addresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-if="addr.host.length == 0">*</span>:<span v-if="addr.portRange.indexOf('-')<0">{{addr.portRange}}</span><span v-else style="font-style: italic">{{addr.portRange}}</span>
<a href="" @click.prevent="updateAddr(index, addr)" title="修改"><i class="icon pencil small"></i></a>
<a href="" @click.prevent="removeAddr(index)" title="删除"><i class="icon remove"></i></a> </div>
<div class="ui divider"></div>
<div v-show="!isEditing">
<div v-if="addresses.length > 0">
<div class="ui label small basic" v-for="(addr, index) in addresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-if="addr.host.length == 0">*</span>:<span v-if="addr.portRange.indexOf('-')<0">{{addr.portRange}}</span><span v-else style="font-style: italic">{{addr.portRange}}</span>
</div>
&nbsp; &nbsp; <a href="" @click.prevent="edit" style="font-size: 0.9em">[修改]</a>
</div>
</div>
<div v-show="isEditing || addresses.length == 0">
<div v-if="addresses.length > 0">
<div class="ui label small basic" v-for="(addr, index) in addresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-if="addr.host.length == 0">*</span>:<span v-if="addr.portRange.indexOf('-')<0">{{addr.portRange}}</span><span v-else style="font-style: italic">{{addr.portRange}}</span>
<a href="" @click.prevent="updateAddr(index, addr)" title="修改"><i class="icon pencil small"></i></a>
<a href="" @click.prevent="removeAddr(index)" title="删除"><i class="icon remove"></i></a>
</div>
<div class="ui divider"></div>
</div>
<a href="" @click.prevent="addAddr()">[添加端口绑定]</a>
</div>
<a href="" @click.prevent="addAddr()">[添加端口绑定]</a>
</div>`
})
@@ -14876,7 +15086,7 @@ Vue.component("more-items-angle", {
return false
}
},
template: `<a href="" class="item" @click.prevent="show"><i class="icon angle" :class="{down: !visible, up: visible}"></i></a>`
template: `<a href="" class="item" @click.prevent="show" style="padding-right: 0"><span style="font-size: 0.8em">切换</span><i class="icon angle" :class="{down: !visible, up: visible}"></i></a>`
})
/**
@@ -14951,6 +15161,10 @@ Vue.component("link-red", {
methods: {
clickPrevent: function () {
emitClick(this, arguments)
if (this.vHref.length > 0) {
window.location = this.vHref
}
}
},
template: `<a :href="vHref" :title="title" style="border-bottom: 1px #db2828 dashed" @click.prevent="clickPrevent"><span class="red"><slot></slot></span></a>`
@@ -15615,6 +15829,37 @@ Vue.component("loading-message", {
</div>`
})
Vue.component("file-textarea", {
props: ["value"],
data: function () {
let value = this.value
if (typeof value != "string") {
value = ""
}
return {
realValue: value
}
},
mounted: function () {
},
methods: {
dragover: function () {},
drop: function (e) {
let that = this
e.dataTransfer.items[0].getAsFile().text().then(function (data) {
that.setValue(data)
})
},
setValue: function (value) {
this.realValue = value
},
focus: function () {
this.$refs.textarea.focus()
}
},
template: `<textarea @drop.prevent="drop" @dragover.prevent="dragover" ref="textarea" v-model="realValue"></textarea>`
})
Vue.component("more-options-angle", {
data: function () {
return {
@@ -15630,6 +15875,78 @@ Vue.component("more-options-angle", {
template: `<a href="" @click.prevent="show()"><span v-if="!isVisible">更多选项</span><span v-if="isVisible">收起选项</span><i class="icon angle" :class="{down:!isVisible, up:isVisible}"></i></a>`
})
Vue.component("columns-grid", {
props: [],
mounted: function () {
this.columns = this.calculateColumns()
let that = this
window.addEventListener("resize", function () {
that.columns = that.calculateColumns()
})
},
data: function () {
return {
columns: "four"
}
},
methods: {
calculateColumns: function () {
let w = window.innerWidth
let columns = Math.floor(w / 250)
if (columns == 0) {
columns = 1
}
let columnElements = this.$el.getElementsByClassName("column")
if (columnElements.length == 0) {
return
}
let maxColumns = columnElements.length
if (columns > maxColumns) {
columns = maxColumns
}
// 添加右侧边框
for (let index = 0; index < columnElements.length; index++) {
let el = columnElements[index]
el.className = el.className.replace("with-border", "")
if (index % columns == columns - 1 || index == columnElements.length - 1 /** 最后一个 **/) {
el.className += " with-border"
}
}
switch (columns) {
case 1:
return "one"
case 2:
return "two"
case 3:
return "three"
case 4:
return "four"
case 5:
return "five"
case 6:
return "six"
case 7:
return "seven"
case 8:
return "eight"
case 9:
return "nine"
case 10:
return "ten"
default:
return "ten"
}
}
},
template: `<div :class="'ui ' + columns + ' columns grid counter-chart'">
<slot></slot>
</div>`
})
/**
* 菜单项
*/
@@ -16264,15 +16581,15 @@ Vue.component("combo-box", {
template: `<div style="display: inline; z-index: 10; background: white" class="combo-box">
<!-- 搜索框 -->
<div v-if="selectedItem == null">
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" :style="{'width': styleWidth}" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keyup.down="downItem" @keyup.up="upItem"/>
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" :style="{'width': styleWidth}" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keydown.down.prevent="downItem" @keydown.up.prevent="upItem"/>
</div>
<!-- 当前选中 -->
<div v-if="selectedItem != null">
<input type="hidden" :name="name" :value="selectedItem.value"/>
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span><span v-if="title != null && title.length > 0">{{title}}</span>{{selectedItem.name}}</span>
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
</a>
<span class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel"><span><span v-if="title != null && title.length > 0">{{title}}</span>{{selectedItem.name}}</span>
<a href="" title="清除" @click.prevent="reset"><i class="icon remove small"></i></a>
</span>
</div>
<!-- 菜单 -->
@@ -18822,7 +19139,7 @@ Vue.component("ad-instance-objects-box", {
<td class="title">已选中防护对象</td>
<td>
<div v-for="(object, index) in objects" class="ui label basic small" style="margin-bottom: 0.5em">
<span v-if="object.type == 'server'">网站服务{{object.name}}</span>
<span v-if="object.type == 'server'">网站:{{object.name}}</span>
&nbsp; <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove small"></i></a>
</div>
</td>
@@ -18837,18 +19154,18 @@ Vue.component("ad-instance-objects-box", {
<table class="ui table celled">
<tr>
<td class="title">对象类型</td>
<td>网站服务</td>
<td>网站</td>
</tr>
<!-- 服务列表 -->
<tr>
<td>服务列表</td>
<td>网站列表</td>
<td>
<span v-if="serversIsLoading">加载中...</span>
<div v-if="!serversIsLoading && servers.length == 0">暂时还没有可选的网站服务。</div>
<div v-if="!serversIsLoading && servers.length == 0">暂时还没有可选的网站。</div>
<table class="ui table" v-show="!serversIsLoading && servers.length > 0">
<thead class="full-width">
<tr>
<th>网站服务名称</th>
<th>网站名称</th>
<th class="one op">操作</th>
</tr>
</thead>

View File

@@ -64,7 +64,7 @@ Vue.component("node-ddos-protection-config-box", {
<prior-checkbox :v-config="config.tcp" v-if="isNode"></prior-checkbox>
<tbody v-show="config.tcp.isPrior || !isNode">
<tr>
<td class="title">启用</td>
<td class="title">启用DDoS防护</td>
<td>
<checkbox v-model="config.tcp.isOn"></checkbox>
</td>

View File

@@ -0,0 +1,71 @@
Vue.component("columns-grid", {
props: [],
mounted: function () {
this.columns = this.calculateColumns()
let that = this
window.addEventListener("resize", function () {
that.columns = that.calculateColumns()
})
},
data: function () {
return {
columns: "four"
}
},
methods: {
calculateColumns: function () {
let w = window.innerWidth
let columns = Math.floor(w / 250)
if (columns == 0) {
columns = 1
}
let columnElements = this.$el.getElementsByClassName("column")
if (columnElements.length == 0) {
return
}
let maxColumns = columnElements.length
if (columns > maxColumns) {
columns = maxColumns
}
// 添加右侧边框
for (let index = 0; index < columnElements.length; index++) {
let el = columnElements[index]
el.className = el.className.replace("with-border", "")
if (index % columns == columns - 1 || index == columnElements.length - 1 /** 最后一个 **/) {
el.className += " with-border"
}
}
switch (columns) {
case 1:
return "one"
case 2:
return "two"
case 3:
return "three"
case 4:
return "four"
case 5:
return "five"
case 6:
return "six"
case 7:
return "seven"
case 8:
return "eight"
case 9:
return "nine"
case 10:
return "ten"
default:
return "ten"
}
}
},
template: `<div :class="'ui ' + columns + ' columns grid counter-chart'">
<slot></slot>
</div>`
})

View File

@@ -249,15 +249,15 @@ Vue.component("combo-box", {
template: `<div style="display: inline; z-index: 10; background: white" class="combo-box">
<!-- 搜索框 -->
<div v-if="selectedItem == null">
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" :style="{'width': styleWidth}" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keyup.down="downItem" @keyup.up="upItem"/>
<input type="text" v-model="keyword" :placeholder="placeholder" :size="size" :style="{'width': styleWidth}" @input="changeKeyword" @focus="show" @blur="hide" @keyup.enter="confirm()" @keypress.enter.prevent="1" ref="searchBox" @keydown.down.prevent="downItem" @keydown.up.prevent="upItem"/>
</div>
<!-- 当前选中 -->
<div v-if="selectedItem != null">
<input type="hidden" :name="name" :value="selectedItem.value"/>
<a href="" class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel" @click.prevent="submitForm"><span><span v-if="title != null && title.length > 0">{{title}}</span>{{selectedItem.name}}</span>
<span title="清除" @click.prevent="reset"><i class="icon remove small"></i></span>
</a>
<span class="ui label basic" style="line-height: 1.4; font-weight: normal; font-size: 1em" ref="selectedLabel"><span><span v-if="title != null && title.length > 0">{{title}}</span>{{selectedItem.name}}</span>
<a href="" title="清除" @click.prevent="reset"><i class="icon remove small"></i></a>
</span>
</div>
<!-- 菜单 -->

View File

@@ -0,0 +1,30 @@
Vue.component("file-textarea", {
props: ["value"],
data: function () {
let value = this.value
if (typeof value != "string") {
value = ""
}
return {
realValue: value
}
},
mounted: function () {
},
methods: {
dragover: function () {},
drop: function (e) {
let that = this
e.dataTransfer.items[0].getAsFile().text().then(function (data) {
that.setValue(data)
})
},
setValue: function (value) {
this.realValue = value
},
focus: function () {
this.$refs.textarea.focus()
}
},
template: `<textarea @drop.prevent="drop" @dragover.prevent="dragover" ref="textarea" v-model="realValue"></textarea>`
})

View File

@@ -24,6 +24,10 @@ Vue.component("link-red", {
methods: {
clickPrevent: function () {
emitClick(this, arguments)
if (this.vHref.length > 0) {
window.location = this.vHref
}
}
},
template: `<a :href="vHref" :title="title" style="border-bottom: 1px #db2828 dashed" @click.prevent="clickPrevent"><span class="red"><slot></slot></span></a>`

View File

@@ -79,5 +79,5 @@ Vue.component("more-items-angle", {
return false
}
},
template: `<a href="" class="item" @click.prevent="show"><i class="icon angle" :class="{down: !visible, up: visible}"></i></a>`
template: `<a href="" class="item" @click.prevent="show" style="padding-right: 0"><span style="font-size: 0.8em">切换</span><i class="icon angle" :class="{down: !visible, up: visible}"></i></a>`
})

View File

@@ -24,7 +24,8 @@ Vue.component("network-addresses-box", {
addresses: addresses,
protocol: protocol,
name: name,
from: from
from: from,
isEditing: false
}
},
watch: {
@@ -39,6 +40,8 @@ Vue.component("network-addresses-box", {
},
methods: {
addAddr: function () {
this.isEditing = true
let that = this
window.UPDATING_ADDR = null
@@ -106,17 +109,31 @@ Vue.component("network-addresses-box", {
},
supportRange: function () {
return this.vSupportRange || (this.vServerType == "tcpProxy" || this.vServerType == "udpProxy")
},
edit: function () {
this.isEditing = true
}
},
template: `<div>
<input type="hidden" :name="name" :value="JSON.stringify(addresses)"/>
<div v-if="addresses.length > 0">
<div class="ui label small basic" v-for="(addr, index) in addresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-if="addr.host.length == 0">*</span>:<span v-if="addr.portRange.indexOf('-')<0">{{addr.portRange}}</span><span v-else style="font-style: italic">{{addr.portRange}}</span>
<a href="" @click.prevent="updateAddr(index, addr)" title="修改"><i class="icon pencil small"></i></a>
<a href="" @click.prevent="removeAddr(index)" title="删除"><i class="icon remove"></i></a> </div>
<div class="ui divider"></div>
<div v-show="!isEditing">
<div v-if="addresses.length > 0">
<div class="ui label small basic" v-for="(addr, index) in addresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-if="addr.host.length == 0">*</span>:<span v-if="addr.portRange.indexOf('-')<0">{{addr.portRange}}</span><span v-else style="font-style: italic">{{addr.portRange}}</span>
</div>
&nbsp; &nbsp; <a href="" @click.prevent="edit" style="font-size: 0.9em">[修改]</a>
</div>
</div>
<div v-show="isEditing || addresses.length == 0">
<div v-if="addresses.length > 0">
<div class="ui label small basic" v-for="(addr, index) in addresses">
{{addr.protocol}}://<span v-if="addr.host.length > 0">{{addr.host.quoteIP()}}</span><span v-if="addr.host.length == 0">*</span>:<span v-if="addr.portRange.indexOf('-')<0">{{addr.portRange}}</span><span v-else style="font-style: italic">{{addr.portRange}}</span>
<a href="" @click.prevent="updateAddr(index, addr)" title="修改"><i class="icon pencil small"></i></a>
<a href="" @click.prevent="removeAddr(index)" title="删除"><i class="icon remove"></i></a>
</div>
<div class="ui divider"></div>
</div>
<a href="" @click.prevent="addAddr()">[添加端口绑定]</a>
</div>
<a href="" @click.prevent="addAddr()">[添加端口绑定]</a>
</div>`
})

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