Compare commits

..

130 Commits

Author SHA1 Message Date
刘祥超
7e5802dcd1 优化没有网站时的提示 2024-01-21 21:17:42 +08:00
刘祥超
2b6d1c70a8 集群节点列表页增加停用/启用操作 2024-01-21 17:42:56 +08:00
刘祥超
faaef3b353 Dockerfile版本号修改为1.3.3 2024-01-21 16:58:01 +08:00
刘祥超
6ffa1d4a1d 版本号修改为1.3.3 2024-01-21 16:55:55 +08:00
刘祥超
10f0e0dcca 提交components.js 2024-01-21 16:55:31 +08:00
刘祥超
0afad2e192 增加创建本地节点命令 2024-01-21 16:55:22 +08:00
刘祥超
6a1e7266b9 优化缓存文件句柄缓存的文字提示 2024-01-21 14:31:05 +08:00
刘祥超
6ffc552bd8 WAF允许ALLOW动作增加有效范围 2024-01-20 21:23:59 +08:00
刘祥超
8d8245971a WAF策略增加显示页面动作默认设置 2024-01-20 16:17:28 +08:00
刘祥超
a2f730d57e 修复部分内置页面没有<head>标签的问题 2024-01-20 10:15:05 +08:00
刘祥超
bf282c41fd 优化创建策略规则集提示 2024-01-19 11:24:02 +08:00
刘祥超
3e3f7b2cc6 WAF动作默认设置为“显示网页(page)”,减少因错误规则导致的IP封禁 2024-01-19 11:13:01 +08:00
刘祥超
0580e063be 优化网站列表批量操作按钮,防止误操作 2024-01-19 10:56:31 +08:00
刘祥超
2924aad25e 修改版本号为1.3.2.2 2024-01-16 20:59:07 +08:00
刘祥超
39907299cd 提交components.js 2024-01-16 20:58:58 +08:00
刘祥超
77705895d5 优化添加规则界面 2024-01-16 20:42:06 +08:00
刘祥超
3fb85edf0b 提交components.js 2024-01-15 08:51:21 +08:00
刘祥超
0e7a968cac 版本号修改为1.3.2.1 2024-01-15 08:40:14 +08:00
刘祥超
195a9dc771 优化网站设置和路由设置中的左侧菜单 2024-01-14 20:33:07 +08:00
刘祥超
ca227a846a 优化代码 2024-01-13 19:31:52 +08:00
刘祥超
e1057fc76e 字符编码设置增加“强制替换”选项 2024-01-13 16:28:37 +08:00
刘祥超
b5168c3174 优化文字 2024-01-13 15:46:26 +08:00
刘祥超
4ced00de50 修改API节点时提示用户需要重启edge-api进程 2024-01-12 16:44:02 +08:00
刘祥超
750c929506 实现批量删除网站功能 2024-01-11 18:40:29 +08:00
刘祥超
8b46a5a5af 自定义页面跳转支持使用变量 2024-01-11 15:37:31 +08:00
刘祥超
10d606a101 增加带宽相关组件 2024-01-11 15:17:52 +08:00
刘祥超
45d36dadcb 修复“迁移”功能中无法远程修改API节点访问地址的问题 2024-01-09 10:39:09 +08:00
刘祥超
61484d5cb8 增加用户系统文章相关管理 2024-01-09 10:21:05 +08:00
刘祥超
b7775a99ef 优化添加缓存条件和不缓存条件交互 2023-12-28 15:50:19 +08:00
刘祥超
c45ba38ffa 修复参数匹配不区分大小写选项无法保存的问题 2023-12-28 15:31:40 +08:00
刘祥超
e97d9fb8a0 优化可执行文件程序 2023-12-26 09:09:21 +08:00
刘祥超
bb56ec9ec5 修改Dockerfile版本 2023-12-24 17:35:30 +08:00
刘祥超
6d8f659558 版本号修改为1.3.2 2023-12-24 11:14:31 +08:00
刘祥超
7e9047c8fe 优化IP名单管理 2023-12-24 11:05:38 +08:00
刘祥超
b5af560b67 IP名单中可以批量删除最多10000个IP 2023-12-24 10:53:03 +08:00
刘祥超
5622636870 优化请求脚本相关代码 2023-12-23 20:54:37 +08:00
刘祥超
372c276afc 优化英文菜单样式 2023-12-23 09:41:33 +08:00
刘祥超
250b5fdd10 取消“通用设置”菜单 2023-12-22 16:58:16 +08:00
刘祥超
d1fb0bf8b7 优化数据库节点相关文字提示 2023-12-21 17:18:04 +08:00
刘祥超
490c273d36 改进部分文字 2023-12-20 15:06:19 +08:00
刘祥超
0773dbaa28 在网站设置中显示流量相关限制状态 2023-12-19 14:56:05 +08:00
刘祥超
4d04964740 修复缓存条件简单和复杂切换时可能导致复杂条件失效的问题 2023-12-19 09:26:18 +08:00
刘祥超
537e6c9a5e 修复一处编译问题 2023-12-18 10:12:20 +08:00
刘祥超
082a6721f3 版本号修改为1.3.1.2 2023-12-18 08:51:16 +08:00
刘祥超
d515a20129 提升Go模板安全性 2023-12-14 19:57:02 +08:00
刘祥超
a66d3ef61a 更新相关库 2023-12-14 19:53:43 +08:00
刘祥超
3b05b1a933 优化修改网站域名时套餐相关检查 2023-12-13 19:02:12 +08:00
刘祥超
105479ce4b 网站所属用户没有套餐时提示用户 2023-12-13 18:53:08 +08:00
刘祥超
1a950e3aad 非商业版隐藏网站配置复制图标 2023-12-13 18:46:16 +08:00
刘祥超
79fa76e039 缓存设置中可以设置缓存主域名,用来复用多域名下的缓存 2023-12-13 18:33:48 +08:00
刘祥超
9db9a70a68 优化文字 2023-12-13 11:08:35 +08:00
刘祥超
632862c742 优化多语言相关代码 2023-12-13 10:29:50 +08:00
刘祥超
76d0783f6f 切换语言时保存语言设置 2023-12-12 22:39:42 +08:00
刘祥超
fc6a0e9813 切换语言时保存更长时间 2023-12-12 22:28:17 +08:00
刘祥超
038e289057 更新components.js 2023-12-12 22:28:04 +08:00
刘祥超
4b2a4af1e7 优化极验相关代码 2023-12-12 15:00:38 +08:00
刘祥超
0fb2555bea Update en-us.js 2023-12-12 12:11:13 +08:00
刘祥超
bd8a809065 优化英文版样式 2023-12-12 11:54:20 +08:00
刘祥超
fc6fb7dce2 部分菜单实现中英文切换 2023-12-12 11:47:41 +08:00
刘祥超
bacb94abe6 优化WebP相关提示和代码 2023-12-11 11:07:02 +08:00
刘祥超
eb04421412 WebP转换质量转移到WebP策略配置 2023-12-11 10:00:22 +08:00
刘祥超
2fbd5a785e 改进部分表单文字提示 2023-12-10 10:46:50 +08:00
刘祥超
d8ae1525be 管理系统安全设置增加“自定义客户端IP报头” 2023-12-10 10:46:35 +08:00
刘祥超
daaa165d25 优化集群设置--WAF策略选择窗口尺寸 2023-12-10 08:55:12 +08:00
刘祥超
27ea02be5e 优化WAF动作“显示网页”显示 2023-12-09 15:55:17 +08:00
刘祥超
8eac1ef307 优化文字提示 2023-12-08 16:00:48 +08:00
刘祥超
1181974ea5 WAF规则集列表中参数和操作符增加悬停提示 2023-12-08 12:01:36 +08:00
刘祥超
5f57fa0470 WAF规则集列表中参数显示中文 2023-12-08 11:20:32 +08:00
刘祥超
c0ad74ab7b WAF操作符对应的对比值可以不填写 2023-12-07 20:24:26 +08:00
刘祥超
c27905e1cc 优化WAF多行对比值的文字提示 2023-12-07 11:44:37 +08:00
刘祥超
cadb65a85f 业务域名CNAME检查DNS主机地址增加Google公共DNS 2023-12-06 09:42:15 +08:00
刘祥超
b3c152685a 优化检查域名解析文字提示 2023-12-05 11:33:32 +08:00
刘祥超
16ce0726f8 将部分MB、GB...改成MiB、GiB... 2023-12-03 11:41:04 +08:00
刘祥超
5be306e087 将部分MB、GB...改成MiB、GiB... 2023-12-03 11:30:02 +08:00
刘祥超
0d21fc27ab 增加“极验-行为验”验证码集成支持 2023-11-29 16:57:58 +08:00
刘祥超
7deaa678bc 创建集群时默认生成子域名 2023-11-27 11:28:01 +08:00
刘祥超
0d230a9237 <domain-box>组件自动分割单个域名 2023-11-27 10:31:41 +08:00
刘祥超
8453d754f1 添加域名时自动分割用逗号、顿号连接的域名列表 2023-11-27 10:20:54 +08:00
刘祥超
05f77f7a0d 修复go.mod 2023-11-24 10:21:07 +08:00
刘祥超
4334b6f148 修复若干测试用例 2023-11-24 09:13:18 +08:00
刘祥超
72955759d7 更新go.sum 2023-11-24 08:53:40 +08:00
刘祥超
18dc52996d 更新Dockerfile中版本 2023-11-23 17:40:28 +08:00
刘祥超
0b16d09330 版本号修改为1.3.1 2023-11-23 17:26:24 +08:00
刘祥超
027d81a8cd 优化SSH认证sudo设置 2023-11-23 16:12:41 +08:00
刘祥超
d833158784 DNSPod改名为腾讯云DNSPod/DNSPod 支持腾讯云API密钥 2023-11-23 15:14:34 +08:00
刘祥超
75024c9364 优化X-Cache报头设置文字提示 2023-11-22 19:02:18 +08:00
刘祥超
1dd5db3c42 优化第三方域名解析azure dns提示文字 2023-11-22 10:30:29 +08:00
刘祥超
faba80e315 GRPC增加Keepalive参数 2023-11-20 09:56:35 +08:00
刘祥超
1b7dc8eb96 优化下载升级新版本文字提示 2023-11-20 08:25:34 +08:00
刘祥超
7c0af45dd0 更新Dockerfile中版本号 2023-11-19 17:34:15 +08:00
刘祥超
daea270132 更新components.js 2023-11-19 17:33:56 +08:00
刘祥超
a42e3ac4a6 优化CAPTCHA最大失败次数文字提示 2023-11-19 15:43:37 +08:00
刘祥超
c94c526b43 提交components.js 2023-11-19 09:09:50 +08:00
刘祥超
e531787773 集群WAF策略处于非防御模式时在地区封禁页面提示用户 2023-11-18 15:25:14 +08:00
刘祥超
0eda84a99a 修复修改单个WAF动作时名称变为阻止的问题 2023-11-18 08:51:50 +08:00
刘祥超
89b791448a 优化DNS.LA域名解析提示文字 2023-11-17 18:13:54 +08:00
刘祥超
2239328b2e 优化GoDaddy域名解析提示文字 2023-11-17 18:01:50 +08:00
刘祥超
e440a46080 源站支持404内容自动重试其他源站 2023-11-15 19:05:04 +08:00
刘祥超
4a0fec6e35 优化WAF设置中人机识别验证方式 2023-11-15 17:19:29 +08:00
刘祥超
a9b46987bc 更新components.js 2023-11-15 15:13:09 +08:00
刘祥超
6771afead5 WAF人机识别实现点击验证和滑动解锁验证/单个网站可以设置默认的人机识别方式 2023-11-15 15:12:58 +08:00
刘祥超
553fca2981 版本号修改为1.3.0 2023-11-14 14:47:52 +08:00
刘祥超
8ef1e36b8b 优化界面 2023-11-13 17:47:24 +08:00
刘祥超
9e6c49b67c 调教components.js 2023-11-13 17:00:22 +08:00
刘祥超
170632ddbc 增加用户列表菜单 2023-11-13 11:53:30 +08:00
刘祥超
4a74488cac 自定义页面增加例外URL和限制URL设置 2023-11-13 10:45:13 +08:00
刘祥超
5fd6a08988 第三方域名解析支持微软Azure DNS(商业版) 2023-11-12 20:31:16 +08:00
刘祥超
c86b91ead6 自定义页面增加“跳转URL”功能 2023-11-10 16:36:03 +08:00
刘祥超
aeebb763c1 优化自定义页面组件界面 2023-11-10 14:59:53 +08:00
刘祥超
b05630e3e0 优化集群设置 -- 网站设置界面 2023-11-10 11:46:02 +08:00
刘祥超
51905a0ee3 URL跳转中增加例外域名和仅限域名 2023-11-10 11:05:05 +08:00
刘祥超
869ebd59d1 优化Ln节点域名解析选项提示 2023-11-09 17:02:15 +08:00
刘祥超
8a6aedca16 集群设置--DNS设置中增加”解析记录“页面 2023-11-09 16:24:52 +08:00
刘祥超
cf98c3453a 域名解析支持Amazon Route 53(商业版) 2023-11-08 21:17:32 +08:00
刘祥超
1b6f234f39 第三方域名解析增加火山引擎TrafficRoute 2023-11-06 16:21:19 +08:00
刘祥超
26c7f0f566 优化多行消息显示 2023-11-03 16:39:20 +08:00
刘祥超
617ad9b16e 申请ACME证书时防止重复提交 2023-11-03 16:26:20 +08:00
刘祥超
04a6792dbf 删除不需要的文件 2023-11-03 11:00:26 +08:00
刘祥超
f2bf8f2dbf 删除不需要的文件 2023-11-03 10:48:34 +08:00
刘祥超
3aba0abd24 优化硬盘不足提示算法 2023-11-03 09:06:33 +08:00
刘祥超
b56ccc7cef 优化数据看板 2023-10-30 16:44:01 +08:00
刘祥超
aeebf55e60 优化数据看板 2023-10-30 11:33:00 +08:00
刘祥超
9a9e0d25f3 优化数据看板图表排列 2023-10-30 11:30:51 +08:00
刘祥超
6b3033e240 删除不需要的文件 2023-10-27 09:12:19 +08:00
刘祥超
2859a59340 优化界面 2023-10-26 15:13:35 +08:00
刘祥超
45367dd579 将看板中的“服务”文字改为“网站” 2023-10-26 11:35:31 +08:00
刘祥超
fee3fd743f MySQL安装程序支持8.2 2023-10-26 08:33:35 +08:00
刘祥超
ba4cfea47c 优化文字提示 2023-10-25 15:22:13 +08:00
刘祥超
5c11f07f8e 优化域名解析文字提示 2023-10-17 15:53:58 +08:00
刘祥超
ffac080611 将版本号修改为1.2.11 2023-10-17 13:50:03 +08:00
257 changed files with 28738 additions and 2470 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
_ "github.com/TeaOSLab/EdgeCommon/pkg/langs/messages"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
@@ -40,7 +41,8 @@ func main() {
Option("demo", "switch to demo mode").
Option("dev", "switch to 'dev' mode").
Option("prod", "switch to 'prod' mode").
Option("upgrade [--url=URL]", "upgrade from official site or an url")
Option("upgrade [--url=URL]", "upgrade from official site or an url").
Option("install-local-node", "install a local node")
app.On("daemon", func() {
nodes.NewAdminNode().Daemon()
@@ -76,7 +78,7 @@ func main() {
fmt.Println("done")
})
app.On("recover", func() {
sock := gosock.NewTmpSock(teaconst.ProcessName)
var sock = gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
@@ -89,7 +91,7 @@ func main() {
fmt.Println("enter recovery mode successfully")
})
app.On("demo", func() {
sock := gosock.NewTmpSock(teaconst.ProcessName)
var sock = gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
@@ -178,6 +180,14 @@ func main() {
log.Println("restarting ...")
app.RunRestart()
})
app.On("install-local-node", func() {
err := nodeutils.InstallLocalNode()
if err != nil {
fmt.Println("[ERROR]" + err.Error())
return
}
fmt.Println("success")
})
app.Run(func() {
var adminNode = nodes.NewAdminNode()
adminNode.Run()

View File

@@ -1,7 +1,7 @@
FROM --platform=linux/amd64 alpine:latest
LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.2.10
ENV VERSION 1.3.3
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip

29
go.mod
View File

@@ -8,16 +8,16 @@ require (
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/cespare/xxhash v1.1.0
github.com/go-sql-driver/mysql v1.5.0
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/miekg/dns v1.1.43
github.com/quic-go/quic-go v0.36.0
github.com/quic-go/quic-go v0.40.0
github.com/shirou/gopsutil/v3 v3.22.5
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/crypto v0.10.0
golang.org/x/sys v0.9.0
golang.org/x/crypto v0.16.0
golang.org/x/sys v0.15.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -26,29 +26,28 @@ require (
github.com/frankban/quicktest v1.11.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/tdewolff/minify/v2 v2.12.7 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.10.0 // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

69
go.sum
View File

@@ -41,7 +41,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
@@ -51,8 +51,6 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
@@ -79,17 +77,19 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d h1:bM3Cipp7U7Sdn0DpYngVaLWFV9yonxQ1IB7ZPQ41OLw=
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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-20230623080147-cd1e53b4915f h1:xo6XmXLtveKcwcZAXV6VMxkWNzy/2dStfHEnyowsGAE=
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6 h1:l8MW552d+gLzRd+MAtM/2X+80n/uS5nJAlBTYWHroRI=
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6/go.mod h1:Ng3xWekHSVy0E/6/jYqJ7Htydm/H+mWIl0AS+Eg3H2M=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -112,12 +112,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -130,12 +130,10 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.36.0 h1:JIrO7p7Ug6hssFcARjWDiqS2RAKJHCiwPxBAA989rbI=
github.com/quic-go/quic-go v0.36.0/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
@@ -167,21 +165,22 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
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.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -190,9 +189,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -206,8 +204,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.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
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=
@@ -215,7 +213,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -237,27 +235,24 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
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.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -174,6 +174,17 @@ func FindAdminLang(adminId int64) string {
return ""
}
// UpdateAdminLang 修改某个管理员选择的语言
func UpdateAdminLang(adminId int64, langCode string) {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
list.Lang = langCode
}
}
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
locker.Lock()
defer locker.Unlock()

View File

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

View File

@@ -14,6 +14,7 @@ import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"io"
"os"
"path/filepath"
@@ -63,7 +64,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
buffer.Write(typesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 条件操作符
@@ -73,7 +74,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
buffer.Write(requestOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 请求变量
@@ -83,7 +84,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_VARIABLES = ")
buffer.Write(requestVariablesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 指标
@@ -93,7 +94,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
buffer.Write(metricHTTPKeysJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值项目
@@ -103,7 +104,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
buffer.Write(ipAddrThresholdItemsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值动作
@@ -113,7 +114,25 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
buffer.Write(ipAddrThresholdActionsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF checkpoints
var wafCheckpointsMaps = []maps.Map{}
for _, checkpoint := range firewallconfigs.AllCheckpoints {
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
"name": checkpoint.Name,
"prefix": checkpoint.Prefix,
"description": checkpoint.Description,
})
}
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
if err != nil {
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
buffer.Write(wafCheckpointsJSON)
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF操作符
@@ -123,7 +142,17 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF验证码类型
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
if err != nil {
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
buffer.Write(captchaTypesJSON)
buffer.Write([]byte{';', '\n', '\n'})
}
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)

View File

@@ -20,6 +20,7 @@ import (
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"net"
"net/url"
@@ -514,12 +515,15 @@ func (this *RPCClient) init() error {
grpc.MaxCallSendMsgSize(128<<20),
grpc.UseCompressor(gzip.Name),
)
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
})
if u.Scheme == "http" {
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
} else if u.Scheme == "https" {
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})), callOptions)
})), callOptions, keepaliveParams)
} else {
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
}

View File

@@ -34,14 +34,9 @@ func TestRPCClient_NodeRPC(t *testing.T) {
func TestRPC_Dial_HTTP(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"http://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8003"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -56,14 +51,9 @@ func TestRPC_Dial_HTTP(t *testing.T) {
func TestRPC_Dial_HTTP_2(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"https://127.0.0.1:8003"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8003"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -78,14 +68,9 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
func TestRPC_Dial_HTTPS(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"https://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8004"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)

162
internal/utils/exec/cmd.go Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package executils
import (
"bytes"
"context"
"os"
"os/exec"
"strings"
"time"
)
type Cmd struct {
name string
args []string
env []string
dir string
ctx context.Context
timeout time.Duration
cancelFunc func()
captureStdout bool
captureStderr bool
stdout *bytes.Buffer
stderr *bytes.Buffer
rawCmd *exec.Cmd
}
func NewCmd(name string, args ...string) *Cmd {
return &Cmd{
name: name,
args: args,
}
}
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
return (&Cmd{
name: name,
args: args,
}).WithTimeout(timeout)
}
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
this.timeout = timeout
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
this.ctx = ctx
this.cancelFunc = cancelFunc
return this
}
func (this *Cmd) WithStdout() *Cmd {
this.captureStdout = true
return this
}
func (this *Cmd) WithStderr() *Cmd {
this.captureStderr = true
return this
}
func (this *Cmd) WithEnv(env []string) *Cmd {
this.env = env
return this
}
func (this *Cmd) WithDir(dir string) *Cmd {
this.dir = dir
return this
}
func (this *Cmd) Start() error {
var cmd = this.compose()
return cmd.Start()
}
func (this *Cmd) Wait() error {
var cmd = this.compose()
return cmd.Wait()
}
func (this *Cmd) Run() error {
if this.cancelFunc != nil {
defer this.cancelFunc()
}
var cmd = this.compose()
return cmd.Run()
}
func (this *Cmd) RawStdout() string {
if this.stdout != nil {
return this.stdout.String()
}
return ""
}
func (this *Cmd) Stdout() string {
return strings.TrimSpace(this.RawStdout())
}
func (this *Cmd) RawStderr() string {
if this.stderr != nil {
return this.stderr.String()
}
return ""
}
func (this *Cmd) Stderr() string {
return strings.TrimSpace(this.RawStderr())
}
func (this *Cmd) String() string {
if this.rawCmd != nil {
return this.rawCmd.String()
}
var newCmd = exec.Command(this.name, this.args...)
return newCmd.String()
}
func (this *Cmd) Process() *os.Process {
if this.rawCmd != nil {
return this.rawCmd.Process
}
return nil
}
func (this *Cmd) compose() *exec.Cmd {
if this.rawCmd != nil {
return this.rawCmd
}
if this.ctx != nil {
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
} else {
this.rawCmd = exec.Command(this.name, this.args...)
}
if this.env != nil {
this.rawCmd.Env = this.env
}
if len(this.dir) > 0 {
this.rawCmd.Dir = this.dir
}
if this.captureStdout {
this.stdout = &bytes.Buffer{}
this.rawCmd.Stdout = this.stdout
}
if this.captureStderr {
this.stderr = &bytes.Buffer{}
this.rawCmd.Stderr = this.stderr
}
return this.rawCmd
}

View File

@@ -0,0 +1,61 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package executils_test
import (
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"testing"
"time"
)
func TestNewTimeoutCmd_Sleep(t *testing.T) {
var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestNewTimeoutCmd_Echo(t *testing.T) {
var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestNewTimeoutCmd_Echo2(t *testing.T) {
var cmd = executils.NewCmd("echo", "hello")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("raw stdout:", cmd.RawStdout())
t.Log("stderr:", cmd.Stderr())
t.Log("raw stderr:", cmd.RawStderr())
}
func TestNewTimeoutCmd_Echo3(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestCmd_Process(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
err := cmd.Run()
t.Log("error:", err)
t.Log(cmd.Process())
}
func TestNewTimeoutCmd_String(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
t.Log("stdout:", cmd.String())
}

View File

@@ -0,0 +1,58 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux
package executils
import (
"golang.org/x/sys/unix"
"io/fs"
"os"
"os/exec"
"syscall"
)
// LookPath customize our LookPath() function, to work in broken $PATH environment variable
func LookPath(file string) (string, error) {
result, err := exec.LookPath(file)
if err == nil && len(result) > 0 {
return result, nil
}
// add common dirs contains executable files these may be excluded in $PATH environment variable
var binPaths = []string{
"/usr/sbin",
"/usr/bin",
"/usr/local/sbin",
"/usr/local/bin",
}
for _, binPath := range binPaths {
var fullPath = binPath + string(os.PathSeparator) + file
stat, err := os.Stat(fullPath)
if err != nil {
continue
}
if stat.IsDir() {
return "", syscall.EISDIR
}
var mode = stat.Mode()
if mode.IsDir() {
return "", syscall.EISDIR
}
err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS)
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
return fullPath, err
}
if mode&0111 != 0 {
return fullPath, nil
}
return "", fs.ErrPermission
}
return "", &exec.Error{
Name: file,
Err: exec.ErrNotFound,
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !linux
package executils
import "os/exec"
func LookPath(file string) (string, error) {
return exec.LookPath(file)
}

View File

@@ -3,6 +3,7 @@
package utils
import (
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types"
"os/exec"
@@ -14,7 +15,7 @@ func AddPortsToFirewall(ports []int) {
// Linux
if runtime.GOOS == "linux" {
// firewalld
firewallCmd, _ := exec.LookPath("firewall-cmd")
firewallCmd, _ := executils.LookPath("firewall-cmd")
if len(firewallCmd) > 0 {
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
if err == nil {

View File

@@ -3,8 +3,10 @@ package utils
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/miekg/dns"
"sync"
)
var sharedDNSClient *dns.Client
@@ -33,17 +35,47 @@ func LookupCNAME(host string) (string, error) {
m.RecursionDesired = true
var lastErr error
for _, serverAddr := range sharedDNSConfig.Servers {
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
if err != nil {
lastErr = err
continue
}
if len(r.Answer) == 0 {
continue
}
var success = false
var result = ""
return r.Answer[0].(*dns.CNAME).Target, nil
var serverAddrs = sharedDNSConfig.Servers
{
var publicDNSHosts = []string{"8.8.8.8" /** Google **/, "8.8.4.4" /** Google **/}
for _, publicDNSHost := range publicDNSHosts {
if !lists.ContainsString(serverAddrs, publicDNSHost) {
serverAddrs = append(serverAddrs, publicDNSHost)
}
}
}
var wg = &sync.WaitGroup{}
for _, serverAddr := range serverAddrs {
wg.Add(1)
go func(serverAddr string) {
defer wg.Done()
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
if err != nil {
lastErr = err
return
}
success = true
if len(r.Answer) == 0 {
return
}
result = r.Answer[0].(*dns.CNAME).Target
}(serverAddr)
}
wg.Wait()
if success {
return result, nil
}
return "", lastErr
}

View File

@@ -8,5 +8,8 @@ import (
)
func TestLookupCNAME(t *testing.T) {
t.Log(utils.LookupCNAME("www.yun4s.cn"))
for _, domain := range []string{"www.yun4s.cn", "example.com"} {
result, err := utils.LookupCNAME(domain)
t.Log(domain, "=>", result, err)
}
}

View File

@@ -30,17 +30,17 @@ func FormatBytes(bytes int64) string {
if bytes < Pow1024(1) {
return FormatInt64(bytes) + "B"
} else if bytes < Pow1024(2) {
return TrimZeroSuffix(fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1))))
return TrimZeroSuffix(fmt.Sprintf("%.2fKiB", float64(bytes)/float64(Pow1024(1))))
} else if bytes < Pow1024(3) {
return TrimZeroSuffix(fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2))))
return TrimZeroSuffix(fmt.Sprintf("%.2fMiB", float64(bytes)/float64(Pow1024(2))))
} else if bytes < Pow1024(4) {
return TrimZeroSuffix(fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3))))
return TrimZeroSuffix(fmt.Sprintf("%.2fGiB", float64(bytes)/float64(Pow1024(3))))
} else if bytes < Pow1024(5) {
return TrimZeroSuffix(fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4))))
return TrimZeroSuffix(fmt.Sprintf("%.2fTiB", float64(bytes)/float64(Pow1024(4))))
} else if bytes < Pow1024(6) {
return TrimZeroSuffix(fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5))))
return TrimZeroSuffix(fmt.Sprintf("%.2fPiB", float64(bytes)/float64(Pow1024(5))))
} else {
return TrimZeroSuffix(fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))))
return TrimZeroSuffix(fmt.Sprintf("%.2fEiB", float64(bytes)/float64(Pow1024(6))))
}
}

View File

@@ -5,6 +5,7 @@ package utils
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"os"
@@ -21,7 +22,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
return errors.New("only root users can install the service")
}
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return this.installInitService(exePath, args)
}
@@ -36,7 +37,7 @@ func (this *ServiceManager) Start() error {
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return err
}
@@ -53,7 +54,7 @@ func (this *ServiceManager) Uninstall() error {
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return err
}
@@ -93,7 +94,7 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
return err
}
chkCmd, err := exec.LookPath("chkconfig")
chkCmd, err := executils.LookPath("chkconfig")
if err != nil {
return err
}

View File

@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
@@ -87,10 +88,10 @@ func (this *UpgradeManager) Start() error {
}()
// 检查unzip
unzipExe, _ := exec.LookPath("unzip")
unzipExe, _ := executils.LookPath("unzip")
// 检查cp
cpExe, _ := exec.LookPath("cp")
cpExe, _ := executils.LookPath("cp")
if len(cpExe) == 0 {
return errors.New("can not find 'cp' command")
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
@@ -106,7 +107,7 @@ func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode,
}
}
}
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, this.RequestRemoteIP(), messageCode, args)
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject), messageCode, args)
if err != nil {
utils.PrintError(err)
}

View File

@@ -49,6 +49,7 @@ func init() {
Post("/start", new(node.StartAction)).
Post("/stop", new(node.StopAction)).
Post("/up", new(node.UpAction)).
Post("/updateIsOn", new(node.UpdateIsOnAction)).
Get("/detail", new(node.DetailAction)).
GetPost("/updateDNSPopup", new(node.UpdateDNSPopupAction)).
Post("/syncDomain", new(node.SyncDomainAction)).

View File

@@ -4,12 +4,23 @@ package nodeutils
import (
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"gopkg.in/yaml.v3"
"os"
"runtime"
"strconv"
"time"
)
// InitNodeInfo 初始化节点信息
@@ -104,3 +115,124 @@ func InitNodeInfo(parentAction *actionutils.ParentAction, nodeId int64) (*pb.Nod
return nodeResp.Node, nil
}
// InstallLocalNode 安装本地节点
func InstallLocalNode() error {
var targetDir = Tea.Root
var nodeDir = targetDir + "/edge-node"
var apiAddr = "http://127.0.0.1:8001" // 先固定
// 查找节点安装文件
var zipFile = Tea.Root + "/edge-api/deploy/edge-node-linux-" + runtime.GOARCH + "-v" + teaconst.Version /** 默认和管理系统一致 **/ + ".zip"
{
stat, err := os.Stat(zipFile)
if err != nil {
if os.IsNotExist(err) {
return errors.New("installer file not found in '" + zipFile + "'")
}
return fmt.Errorf("open installer file failed: %w", err)
}
if stat.IsDir() {
return errors.New("invalid installer file '" + zipFile + "'")
}
}
// 解压节点
var unzip = utils.NewUnzip(zipFile, targetDir)
err := unzip.Run()
if err != nil {
return fmt.Errorf("unzip installer file failed: %w", err)
}
// 创建节点
rpcClient, err := rpc.SharedRPC()
if err != nil {
return fmt.Errorf("create rpc client failed: %w", err)
}
var ctx = rpcClient.Context(0)
nodeClustersResp, err := rpcClient.NodeClusterRPC().ListEnabledNodeClusters(ctx, &pb.ListEnabledNodeClustersRequest{
IdDesc: true,
Offset: 0,
Size: 1,
})
if err != nil {
return err
}
if len(nodeClustersResp.NodeClusters) == 0 {
return errors.New("no clusters yet, please create a cluster at least")
}
var clusterId = nodeClustersResp.NodeClusters[0].Id
// 检查节点是否已生成
countNodesResp, err := rpcClient.NodeRPC().CountAllEnabledNodesMatch(ctx, &pb.CountAllEnabledNodesMatchRequest{
NodeClusterId: clusterId,
})
if err != nil {
return err
}
if countNodesResp.Count > 0 {
// 这里先不判断是否有本地节点,只要有节点,就不允许再次执行
return errors.New("there are already nodes in the cluster")
}
createNodeResp, err := rpcClient.NodeRPC().CreateNode(ctx, &pb.CreateNodeRequest{
Name: "本地节点",
NodeClusterId: clusterId,
NodeLogin: nil,
NodeGroupId: 0,
DnsRoutes: nil,
NodeRegionId: 0,
})
if err != nil {
return err
}
var nodeId = createNodeResp.NodeId
nodeResp, err := rpcClient.NodeRPC().FindEnabledNode(ctx, &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
return err
}
if nodeResp.Node == nil {
return errors.New("could not find local node with created id '" + types.String(nodeId) + "'")
}
var node = nodeResp.Node
// 生成节点配置
var apiConfig = &configs.APIConfig{
RPCEndpoints: []string{apiAddr},
RPCDisableUpdate: true,
NodeId: node.UniqueId,
Secret: node.Secret,
}
apiConfigYAML, err := yaml.Marshal(apiConfig)
if err != nil {
return fmt.Errorf("encode config failed: %w", err)
}
err = os.WriteFile(nodeDir+"/configs/api_node.yaml", apiConfigYAML, 0666)
if err != nil {
return fmt.Errorf("write config file failed: %w", err)
}
// 测试节点
{
var cmd = executils.NewTimeoutCmd(5*time.Second, nodeDir+"/bin/edge-node", "test")
cmd.WithStdout()
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return fmt.Errorf("node test failed: %w", err)
}
}
// 启动节点
{
var cmd = executils.NewTimeoutCmd(5*time.Second, nodeDir+"/bin/edge-node", "start")
cmd.WithStdout()
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return fmt.Errorf("node start failed: %w", err)
}
}
return nil
}

View File

@@ -0,0 +1,16 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nodeutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestInstallLocalNode(t *testing.T) {
err := nodeutils.InstallLocalNode()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -1,62 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package thresholds
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("threshold")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
// 列出所有阈值
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
Role: "node",
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
thresholdMaps := []maps.Map{}
for _, threshold := range thresholdsResp.NodeThresholds {
thresholdMaps = append(thresholdMaps, maps.Map{
"id": threshold.Id,
"itemName": nodeconfigs.FindNodeValueItemName(threshold.Item),
"paramName": nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param),
"operatorName": nodeconfigs.FindNodeValueOperatorName(threshold.Operator),
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
"sumMethodName": nodeconfigs.FindNodeValueSumMethodName(threshold.SumMethod),
"duration": threshold.Duration,
"durationUnitName": nodeconfigs.FindNodeValueDurationUnitName(threshold.DurationUnit),
"isOn": threshold.IsOn,
})
}
this.Data["thresholds"] = thresholdMaps
this.Show()
}

View File

@@ -0,0 +1,35 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateIsOnAction struct {
actionutils.ParentAction
}
func (this *UpdateIsOnAction) RunPost(params struct {
NodeId int64
IsOn bool
}) {
if params.IsOn {
defer this.CreateLogInfo(codes.Node_LogUpdateNodeOn, params.NodeId)
} else {
defer this.CreateLogInfo(codes.Node_LogUpdateNodeOff, params.NodeId)
}
_, err := this.RPC().NodeRPC().UpdateNodeIsOn(this.AdminContext(), &pb.UpdateNodeIsOnRequest{
NodeId: params.NodeId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -14,7 +14,7 @@ type IndexAction struct {
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.Nav("", "setting", "index")
this.SecondMenu("dns")
}

View File

@@ -0,0 +1,303 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type RecordsAction struct {
actionutils.ParentAction
}
func (this *RecordsAction) Init() {
this.Nav("", "setting", "records")
this.SecondMenu("dns")
}
func (this *RecordsAction) RunGet(params struct {
ClusterId int64
}) {
// 集群信息
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
this.Data["cluster"] = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
}
// DNS信息
dnsResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var defaultRoute = dnsResp.DefaultRoute
var domainName = ""
var dnsMap = maps.Map{
"dnsName": dnsResp.Name,
"domainId": 0,
"domainName": "",
"providerId": 0,
"providerName": "",
"providerTypeName": "",
}
if dnsResp.Domain != nil {
domainName = dnsResp.Domain.Name
dnsMap["domainId"] = dnsResp.Domain.Id
dnsMap["domainName"] = dnsResp.Domain.Name
}
if dnsResp.Provider != nil {
dnsMap["providerId"] = dnsResp.Provider.Id
dnsMap["providerName"] = dnsResp.Provider.Name
dnsMap["providerTypeName"] = dnsResp.Provider.TypeName
}
if len(dnsResp.CnameRecords) > 0 {
dnsMap["cnameRecords"] = dnsResp.CnameRecords
} else {
dnsMap["cnameRecords"] = []string{}
}
this.Data["dnsInfo"] = dnsMap
// 未安装的节点
notInstalledNodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: false,
})
if err != nil {
this.ErrorPage(err)
return
}
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 isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 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: route.Code,
Value: node.IpAddr,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{
"name": route.Name,
"code": route.Code,
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
} else {
// 默认线路
var isResolved = false
if isInstalled && 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,
"ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{
"name": "",
"code": "",
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
}
this.Data["nodes"] = nodeMaps
// 代理服务解析记录
serversResp, err := this.RPC().ServerRPC().FindAllEnabledServersDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledServersDNSWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var serverMaps = []maps.Map{}
for _, server := range serversResp.Servers {
// 检查是否已解析
isResolved := false
if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(server.DnsName) > 0 && len(domainName) > 0 {
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: server.DnsName,
Type: "CNAME",
Value: cluster.DnsName + "." + domainName,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
serverMaps = append(serverMaps, maps.Map{
"id": server.Id,
"name": server.Name,
"dnsName": server.DnsName,
"isResolved": isResolved,
})
}
this.Data["servers"] = serverMaps
// 检查解析记录是否有变化
checkChangesResp, err := this.RPC().NodeClusterRPC().CheckNodeClusterDNSChanges(this.AdminContext(), &pb.CheckNodeClusterDNSChangesRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsHasChanges"] = checkChangesResp.IsChanged
// 需要解决的问题
issuesResp, err := this.RPC().DNSRPC().FindAllDNSIssues(this.AdminContext(), &pb.FindAllDNSIssuesRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var issueMaps = []maps.Map{}
for _, issue := range issuesResp.Issues {
issueMaps = append(issueMaps, maps.Map{
"target": issue.Target,
"targetId": issue.TargetId,
"type": issue.Type,
"description": issue.Description,
"params": issue.Params,
})
}
this.Data["issues"] = issueMaps
// 当前正在执行的任务
resp, err := this.RPC().DNSTaskRPC().FindAllDoingDNSTasks(this.AdminContext(), &pb.FindAllDoingDNSTasksRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var taskMaps = []maps.Map{}
for _, task := range resp.DnsTasks {
var clusterMap maps.Map = nil
var nodeMap maps.Map = nil
var serverMap maps.Map = nil
var domainMap maps.Map = nil
if task.NodeCluster != nil {
clusterMap = maps.Map{
"id": task.NodeCluster.Id,
"name": task.NodeCluster.Name,
}
}
if task.Node != nil {
nodeMap = maps.Map{
"id": task.Node.Id,
"name": task.Node.Name,
}
}
if task.Server != nil {
serverMap = maps.Map{
"id": task.Server.Id,
"name": task.Server.Name,
}
}
if task.DnsDomain != nil {
domainMap = maps.Map{
"id": task.DnsDomain.Id,
"name": task.DnsDomain.Name,
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"type": task.Type,
"isDone": task.IsDone,
"isOk": task.IsOk,
"error": task.Error,
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", task.UpdatedAt),
"cluster": clusterMap,
"node": nodeMap,
"server": serverMap,
"domain": domainMap,
})
}
this.Data["tasks"] = taskMaps
this.Show()
}

View File

@@ -40,6 +40,7 @@ func init() {
// DNS
Prefix("/clusters/cluster/settings/dns").
GetPost("", new(dns.IndexAction)).
Get("/records", new(dns.RecordsAction)).
Post("/randomName", new(dns.RandomNameAction)).
// 系统服务设置

View File

@@ -45,6 +45,7 @@ func (this *IndexAction) RunGet(params struct {
func (this *IndexAction) RunPost(params struct {
ClusterId int64
IsOn bool
Quality int
RequireCache bool
MinLengthJSON []byte
MaxLengthJSON []byte
@@ -59,6 +60,13 @@ func (this *IndexAction) RunPost(params struct {
RequireCache: params.RequireCache,
}
if params.Quality < 0 {
params.Quality = 0
} else if params.Quality > 100 {
params.Quality = 100
}
config.Quality = params.Quality
if len(params.MinLengthJSON) > 0 {
var minLength = &shared.SizeCapacity{}
err := json.Unmarshal(params.MinLengthJSON, minLength)
@@ -79,6 +87,11 @@ func (this *IndexAction) RunPost(params struct {
config.MaxLength = maxLength
}
err := config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
}
configJSON, err := json.Marshal(config)
if err != nil {
this.ErrorPage(err)
@@ -87,7 +100,7 @@ func (this *IndexAction) RunPost(params struct {
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterWebPPolicy(this.AdminContext(), &pb.UpdateNodeClusterWebPPolicyRequest{
NodeClusterId: params.ClusterId,
WebpPolicyJSON: configJSON,
WebPPolicyJSON: configJSON,
})
if err != nil {

View File

@@ -180,7 +180,7 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
"name": this.Lang(actionPtr, codes.NodeClusterMenu_SettingWebP),
"url": "/clusters/cluster/settings/webp?clusterId=" + clusterId,
"isActive": selectedItem == "webp",
"isOn": info != nil && info.WebpIsOn,
"isOn": info != nil && info.WebPIsOn,
})
items = this.filterMenuItems1(items, info, clusterId, selectedItem, actionPtr)

View File

@@ -1,7 +1,9 @@
package clusters
import (
"crypto/rand"
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
@@ -9,6 +11,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/rands"
)
type CreateAction struct {
@@ -44,6 +47,18 @@ func (this *CreateAction) RunGet(params struct{}) {
}
this.Data["totalNodes"] = totalNodesResp.Count
// 随机子域名
var defaultDNSName = "g" + rands.HexString(6) + ".cdn"
{
var b = make([]byte, 3)
_, err = rand.Read(b)
if err == nil {
defaultDNSName = fmt.Sprintf("g%x.cdn", b)
}
}
this.Data["defaultDNSName"] = defaultDNSName
this.Data["dnsName"] = defaultDNSName
this.Show()
}

View File

@@ -8,7 +8,6 @@ import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
@@ -58,7 +57,7 @@ func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, u
stat, _ := disk.Usage(p.Mountpoint)
if stat != nil {
if stat.Used < 2*uint64(sizes.G) {
if stat.Used < (5<<30) || stat.Free > (100<<30) {
continue
}
if stat.UsedPercent > thresholdPercent {

View File

@@ -52,9 +52,13 @@ func (this *CreatePopupAction) RunPost(params struct {
Type string
// DNSPod
ParamId string
ParamToken string
ParamRegion string
ParamDNSPodId string
ParamDNSPodToken string
ParamDNSPodRegion string
ParamDNSPodAPIType string
ParamDNSPodAccessKeyId string
ParamDNSPodAccessKeySecret string
// AliDNS
ParamAliDNSAccessKeyId string
@@ -89,18 +93,31 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("type", params.Type).
Require("请选择服务商厂家")
apiParams := maps.Map{}
var apiParams = maps.Map{}
switch params.Type {
case "dnspod":
params.Must.
Field("paramId", params.ParamId).
Require("请输入密钥ID").
Field("paramToken", params.ParamToken).
Require("请输入密钥Token")
apiParams["apiType"] = params.ParamDNSPodAPIType
switch params.ParamDNSPodAPIType {
case "tencentDNS":
params.Must.
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
Require("请输入SecretId").
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
Require("请输入SecretKey")
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
apiParams["region"] = params.ParamDNSPodRegion
default:
params.Must.
Field("paramId", params.ParamDNSPodId).
Require("请输入密钥ID").
Field("paramToken", params.ParamDNSPodToken).
Require("请输入密钥Token")
apiParams["id"] = params.ParamId
apiParams["token"] = params.ParamToken
apiParams["region"] = params.ParamRegion
apiParams["id"] = params.ParamDNSPodId
apiParams["token"] = params.ParamDNSPodToken
apiParams["region"] = params.ParamDNSPodRegion
}
case "alidns":
params.Must.
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).

View File

@@ -79,9 +79,13 @@ func (this *UpdatePopupAction) RunPost(params struct {
Type string
// DNSPod
ParamId string
ParamToken string
ParamRegion string
ParamDNSPodId string
ParamDNSPodToken string
ParamDNSPodRegion string
ParamDNSPodAPIType string
ParamDNSPodAccessKeyId string
ParamDNSPodAccessKeySecret string
// AliDNS
ParamAliDNSAccessKeyId string
@@ -118,18 +122,31 @@ func (this *UpdatePopupAction) RunPost(params struct {
Field("type", params.Type).
Require("请选择服务商厂家")
apiParams := maps.Map{}
var apiParams = maps.Map{}
switch params.Type {
case "dnspod":
params.Must.
Field("paramId", params.ParamId).
Require("请输入密钥ID").
Field("paramToken", params.ParamToken).
Require("请输入密钥Token")
apiParams["apiType"] = params.ParamDNSPodAPIType
switch params.ParamDNSPodAPIType {
case "tencentDNS":
params.Must.
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
Require("请输入SecretId").
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
Require("请输入SecretKey")
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
apiParams["region"] = params.ParamDNSPodRegion
default:
params.Must.
Field("paramId", params.ParamDNSPodId).
Require("请输入密钥ID").
Field("paramToken", params.ParamDNSPodToken).
Require("请输入密钥Token")
apiParams["id"] = params.ParamId
apiParams["token"] = params.ParamToken
apiParams["region"] = params.ParamRegion
apiParams["id"] = params.ParamDNSPodId
apiParams["token"] = params.ParamDNSPodToken
apiParams["region"] = params.ParamDNSPodRegion
}
case "alidns":
params.Must.
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).

View File

@@ -175,7 +175,7 @@ func (this *IndexAction) RunPost(params struct {
})
if err != nil {
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelError, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSystemError, err.Error()), this.RequestRemoteIP(), codes.AdminLogin_LogSystemError, []any{err.Error()})
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelError, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSystemError, err.Error()), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogSystemError, []any{err.Error()})
if err != nil {
utils.PrintError(err)
}
@@ -185,7 +185,7 @@ func (this *IndexAction) RunPost(params struct {
}
if !resp.IsOk {
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelWarn, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogFailed, params.Username), this.RequestRemoteIP(), codes.AdminLogin_LogFailed, []any{params.Username})
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelWarn, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogFailed, params.Username), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogFailed, []any{params.Username})
if err != nil {
utils.PrintError(err)
}
@@ -225,7 +225,7 @@ func (this *IndexAction) RunPost(params struct {
params.Auth.StoreAdmin(adminId, params.Remember)
// 记录日志
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSuccess, params.Username), this.RequestRemoteIP(), codes.AdminLogin_LogSuccess, []any{params.Username})
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSuccess, params.Username), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogSuccess, []any{params.Username})
if err != nil {
utils.PrintError(err)
}
@@ -235,7 +235,7 @@ func (this *IndexAction) RunPost(params struct {
// 检查登录区域
func (this *IndexAction) checkRegion() bool {
var ip = this.RequestRemoteIP()
var ip = loginutils.RemoteIP(&this.ActionObject)
var result = iplibrary.LookupIP(ip)
if result != nil && result.IsOk() && result.CountryId() > 0 && lists.ContainsInt64([]int64{9, 10}, result.CountryId()) {
return false

View File

@@ -3,12 +3,15 @@
package loginutils
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
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"
"regexp"
"strings"
)
// CalculateClientFingerprint 计算客户端指纹
@@ -17,8 +20,26 @@ func CalculateClientFingerprint(action *actions.ActionObject) string {
}
// RemoteIP 获取客户端IP
// TODO 将来增加是否使用代理设置即从X-Real-IP中获取IP
func RemoteIP(action *actions.ActionObject) string {
securityConfig, _ := configloaders.LoadSecurityConfig()
if securityConfig != nil {
if len(securityConfig.ClientIPHeaderNames) > 0 {
var headerNames = regexp.MustCompile(`[,;\s]`).Split(securityConfig.ClientIPHeaderNames, -1)
for _, headerName := range headerNames {
headerName = http.CanonicalHeaderKey(strings.TrimSpace(headerName))
if len(headerName) == 0 {
continue
}
var ipValue = action.Request.Header.Get(headerName)
if net.ParseIP(ipValue) != nil {
return ipValue
}
}
}
}
ip, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
return ip
}

View File

@@ -12,6 +12,7 @@ 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"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
@@ -146,7 +147,7 @@ func (this *OtpAction) RunPost(params struct {
this.ErrorPage(err)
return
}
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, this.Lang(codes.AdminLogin_LogOtpVerifiedSuccess), this.RequestRemoteIP(), codes.AdminLogin_LogOtpVerifiedSuccess, nil)
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, this.Lang(codes.AdminLogin_LogOtpVerifiedSuccess), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogOtpVerifiedSuccess, nil)
if err != nil {
utils.PrintError(err)
}

View File

@@ -36,31 +36,43 @@ func (this *AddServerNamePopupAction) RunPost(params struct {
// 去除空格
serverName = regexp.MustCompile(`\s+`).ReplaceAllString(serverName, "")
// 处理URL
if regexp.MustCompile(`^(?i)(http|https|ftp)://`).MatchString(serverName) {
u, err := url.Parse(serverName)
if err == nil && len(u.Host) > 0 {
serverName = u.Host
// 是否包含了多个域名
var splitReg = regexp.MustCompile(`([,、|,;|])`)
if splitReg.MatchString(serverName) {
params.ServerNames = strings.Join(splitReg.Split(serverName, -1), "\n")
params.Mode = "multiple"
} else {
// 处理URL
if regexp.MustCompile(`^(?i)(http|https|ftp)://`).MatchString(serverName) {
u, err := url.Parse(serverName)
if err == nil && len(u.Host) > 0 {
serverName = u.Host
}
}
}
// 去除端口
if regexp.MustCompile(`:\d+$`).MatchString(serverName) {
host, _, err := net.SplitHostPort(serverName)
if err == nil && len(host) > 0 {
serverName = host
// 去除端口
if regexp.MustCompile(`:\d+$`).MatchString(serverName) {
host, _, err := net.SplitHostPort(serverName)
if err == nil && len(host) > 0 {
serverName = host
}
}
}
params.Must.
Field("serverName", serverName).
Require("请输入域名")
params.Must.
Field("serverName", serverName).
Require("请输入域名")
this.Data["serverName"] = maps.Map{
"name": serverName,
"type": "full",
this.Data["serverName"] = maps.Map{
"name": serverName,
"type": "full",
}
this.Success()
return
}
} else if params.Mode == "multiple" {
}
if params.Mode == "multiple" {
if len(params.ServerNames) == 0 {
this.FailField("serverNames", "请输入至少域名")
}
@@ -99,8 +111,6 @@ func (this *AddServerNamePopupAction) RunPost(params struct {
"type": "full",
"subNames": serverNames,
}
} else {
this.Fail("错误的mode参数")
}
this.Success()

View File

@@ -54,7 +54,7 @@ func (this *CreateSetPopupAction) RunGet(params struct {
}
// 所有可选的动作
actionMaps := []maps.Map{}
var actionMaps = []maps.Map{}
for _, action := range firewallconfigs.AllActions {
actionMaps = append(actionMaps, maps.Map{
"name": action.Name,
@@ -64,6 +64,9 @@ func (this *CreateSetPopupAction) RunGet(params struct {
}
this.Data["actions"] = actionMaps
// 是否为全局
this.Data["isGlobalPolicy"] = firewallPolicy.ServerId == 0
this.Show()
}

View File

@@ -31,8 +31,8 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
var count = countResp.Count
var page = this.NewPage(count)
listResp, err := this.RPC().HTTPFirewallPolicyRPC().ListEnabledHTTPFirewallPolicies(this.AdminContext(), &pb.ListEnabledHTTPFirewallPoliciesRequest{
NodeClusterId: params.ClusterId,
@@ -44,10 +44,10 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
policyMaps := []maps.Map{}
var policyMaps = []maps.Map{}
for _, policy := range listResp.HttpFirewallPolicies {
countInbound := 0
countOutbound := 0
var countInbound = 0
var countOutbound = 0
if len(policy.InboundJSON) > 0 {
inboundConfig := &firewallconfigs.HTTPFirewallInboundConfig{}
err = json.Unmarshal(policy.InboundJSON, inboundConfig)
@@ -72,7 +72,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
countClusters := countClustersResp.Count
var countClusters = countClustersResp.Count
// mode
if len(policy.Mode) == 0 {

View File

@@ -95,6 +95,7 @@ func (this *PolicyAction) RunGet(params struct {
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"groups": internalGroups,
"blockOptions": firewallPolicy.BlockOptions,
"pageOptions": firewallPolicy.PageOptions,
"captchaOptions": firewallPolicy.CaptchaOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFlood": firewallPolicy.SYNFlood,

View File

@@ -34,6 +34,7 @@ func (this *UpdateAction) RunGet(params struct {
return
}
// block options
if firewallPolicy.BlockOptions == nil {
firewallPolicy.BlockOptions = &firewallconfigs.HTTPFirewallBlockAction{
StatusCode: http.StatusForbidden,
@@ -43,6 +44,11 @@ func (this *UpdateAction) RunGet(params struct {
}
}
// page options
if firewallPolicy.PageOptions == nil {
firewallPolicy.PageOptions = firewallconfigs.DefaultHTTPFirewallPageAction()
}
// mode
if len(firewallPolicy.Mode) == 0 {
firewallPolicy.Mode = firewallconfigs.FirewallModeDefend
@@ -71,6 +77,7 @@ func (this *UpdateAction) RunGet(params struct {
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"blockOptions": firewallPolicy.BlockOptions,
"pageOptions": firewallPolicy.PageOptions,
"captchaOptions": firewallPolicy.CaptchaOptions,
"useLocalFirewall": firewallPolicy.UseLocalFirewall,
"synFloodConfig": firewallPolicy.SYNFlood,
@@ -107,6 +114,7 @@ func (this *UpdateAction) RunPost(params struct {
Name string
GroupCodes []string
BlockOptionsJSON []byte
PageOptionsJSON []byte
CaptchaOptionsJSON []byte
Description string
IsOn bool
@@ -132,6 +140,19 @@ func (this *UpdateAction) RunPost(params struct {
err := json.Unmarshal(params.BlockOptionsJSON, blockOptions)
if err != nil {
this.Fail("拦截动作参数校验失败:" + err.Error())
return
}
// 校验显示页面选项JSON
var pageOptions = &firewallconfigs.HTTPFirewallPageAction{}
err = json.Unmarshal(params.PageOptionsJSON, pageOptions)
if err != nil {
this.Fail("校验显示页面动作配置失败:" + err.Error())
return
}
if pageOptions.Status < 100 && pageOptions.Status > 999 {
this.Fail("显示页面动作的状态码配置错误:" + types.String(pageOptions.Status))
return
}
// 校验验证码选项JSON
@@ -139,6 +160,24 @@ func (this *UpdateAction) RunPost(params struct {
err = json.Unmarshal(params.CaptchaOptionsJSON, captchaOptions)
if err != nil {
this.Fail("验证码动作参数校验失败:" + err.Error())
return
}
// 检查极验配置
if captchaOptions.CaptchaType == firewallconfigs.CaptchaTypeGeeTest || captchaOptions.GeeTestConfig.IsOn {
if captchaOptions.CaptchaType == firewallconfigs.CaptchaTypeGeeTest && !captchaOptions.GeeTestConfig.IsOn {
this.Fail("人机识别动作配置的默认验证方式为极验-行为验,所以需要选择允许用户使用极验")
return
}
if len(captchaOptions.GeeTestConfig.CaptchaId) == 0 {
this.FailField("geetestCaptchaId", "请输入极验-验证ID")
return
}
if len(captchaOptions.GeeTestConfig.CaptchaKey) == 0 {
this.FailField("geetestCaptchaKey", "请输入极验-验证Key")
return
}
}
// 最大内容尺寸
@@ -153,6 +192,7 @@ func (this *UpdateAction) RunPost(params struct {
Description: params.Description,
FirewallGroupCodes: params.GroupCodes,
BlockOptionsJSON: params.BlockOptionsJSON,
PageOptionsJSON: params.PageOptionsJSON,
CaptchaOptionsJSON: params.CaptchaOptionsJSON,
Mode: params.Mode,
UseLocalFirewall: params.UseLocalFirewall,

View File

@@ -0,0 +1,28 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package servers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// DeleteServersAction 删除一组网站
type DeleteServersAction struct {
actionutils.ParentAction
}
func (this *DeleteServersAction) RunPost(params struct {
ServerIds []int64
}) {
defer this.CreateLogInfo(codes.Server_LogDeleteServers)
_, err := this.RPC().ServerRPC().DeleteServers(this.AdminContext(), &pb.DeleteServersRequest{ServerIds: params.ServerIds})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -53,12 +53,16 @@ func (this *IndexAction) RunPost(params struct {
defer this.CreateLogInfo(codes.ServerCache_LogUpdateCacheSettings, params.WebId)
// 校验配置
cacheConfig := &serverconfigs.HTTPCacheConfig{}
var cacheConfig = &serverconfigs.HTTPCacheConfig{}
err := json.Unmarshal(params.CacheJSON, cacheConfig)
if err != nil {
this.ErrorPage(err)
return
}
// 分组不支持主域名
cacheConfig.Key = nil
err = cacheConfig.Init()
if err != nil {
this.Fail("检查配置失败:" + err.Error())

View File

@@ -111,6 +111,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -111,6 +111,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -19,6 +19,7 @@ func init() {
GetPost("/create", new(CreateAction)).
GetPost("/update", new(UpdateAction)).
Post("/nearby", new(NearbyAction)).
Post("/deleteServers", new(DeleteServersAction)).
//
GetPost("/addPortPopup", new(AddPortPopupAction)).

View File

@@ -0,0 +1,82 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplists
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
"strings"
)
type DeleteCountAction struct {
actionutils.ParentAction
}
func (this *DeleteCountAction) RunPost(params struct {
Ip string
Keyword string
GlobalOnly bool
Unread bool
EventLevel string
ListType string
Count int64
}) {
var count = params.Count
if count <= 0 || count >= 100_000 {
this.Fail("'count' 参数错误")
return
}
itemIdsResp, err := this.RPC().IPItemRPC().ListAllIPItemIds(this.AdminContext(), &pb.ListAllIPItemIdsRequest{
Keyword: params.Keyword,
GlobalOnly: params.GlobalOnly,
Unread: params.Unread,
EventLevel: params.EventLevel,
ListType: params.ListType,
Ip: params.Ip,
Offset: 0,
Size: count,
})
if err != nil {
this.ErrorPage(err)
return
}
var itemIds = itemIdsResp.IpItemIds
if len(itemIds) == 0 {
this.Success()
}
// 记录日志
defer func() {
var itemIdStrings = []string{}
for _, itemId := range itemIds {
itemIdStrings = append(itemIdStrings, types.String(itemId))
}
var itemIdsDescription = ""
if len(itemIdStrings) > 10 {
itemIdsDescription = strings.Join(itemIdStrings[:10], ", ") + " ..."
} else {
itemIdsDescription = strings.Join(itemIdStrings, ", ")
}
this.CreateLogInfo(codes.IPList_LogDeleteIPBatch, itemIdsDescription)
}()
_, err = this.RPC().IPItemRPC().DeleteIPItems(this.AdminContext(), &pb.DeleteIPItemsRequest{IpItemIds: itemIds})
if err != nil {
this.ErrorPage(err)
return
}
// 通知左侧菜单Badge更新
helpers.NotifyIPItemsCountChanges()
this.Success()
}

View File

@@ -63,6 +63,7 @@ func (this *IndexAction) RunGet(params struct {
}
var count = countResp.Count
var page = this.NewPage(count)
this.Data["totalItems"] = count
this.Data["page"] = page.AsHTML()
itemsResp, err := this.RPC().IPItemRPC().ListAllEnabledIPItems(this.AdminContext(), &pb.ListAllEnabledIPItemsRequest{

View File

@@ -22,6 +22,7 @@ func init() {
Get("/exportData", new(ExportDataAction)).
Post("/delete", new(DeleteAction)).
Post("/deleteItems", new(DeleteItemsAction)).
Post("/deleteCount", new(DeleteCountAction)).
GetPost("/test", new(TestAction)).
GetPost("/update", new(UpdateAction)).
Get("/items", new(ItemsAction)).

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -12,6 +13,7 @@ import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strings"
)
type IndexAction struct {
@@ -92,6 +94,48 @@ func (this *IndexAction) RunPost(params struct {
return
}
// 检查Key
if cacheConfig.Key != nil && cacheConfig.Key.IsOn {
if cacheConfig.Key.Scheme != "http" && cacheConfig.Key.Scheme != "https" {
this.Fail("缓存主域名协议只能是http或者https")
return
}
if len(cacheConfig.Key.Host) == 0 {
this.Fail("请输入缓存主域名")
return
}
cacheConfig.Key.Host = strings.ToLower(strings.TrimSuffix(cacheConfig.Key.Host, "/"))
if !domainutils.ValidateDomainFormat(cacheConfig.Key.Host) {
this.Fail("请输入正确的缓存主域名")
return
}
// 检查域名所属
serverIdResp, err := this.RPC().HTTPWebRPC().FindServerIdWithHTTPWebId(this.AdminContext(), &pb.FindServerIdWithHTTPWebIdRequest{HttpWebId: params.WebId})
if err != nil {
this.ErrorPage(err)
return
}
var serverId = serverIdResp.ServerId
if serverId <= 0 {
this.Fail("找不到要操作的网站")
return
}
existServerNameResp, err := this.RPC().ServerRPC().CheckServerNameInServer(this.AdminContext(), &pb.CheckServerNameInServerRequest{
ServerId: serverId,
ServerName: cacheConfig.Key.Host,
})
if err != nil {
this.ErrorPage(err)
return
}
if !existServerNameResp.Exists {
this.Fail("域名 '" + cacheConfig.Key.Host + "' 在当前网站中并未绑定,不能作为缓存主域名")
return
}
}
err = cacheConfig.Init()
if err != nil {
this.Fail("检查配置失败:" + err.Error())

View File

@@ -1,6 +1,7 @@
package settings
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
@@ -32,7 +33,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
clusterMaps := []maps.Map{}
var clusterMaps = []maps.Map{}
for _, cluster := range resp.NodeClusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
@@ -50,7 +51,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err)
return
}
server := serverResp.Server
var server = serverResp.Server
if server == nil {
this.NotFound("server", params.ServerId)
return
@@ -71,7 +72,7 @@ func (this *IndexAction) RunGet(params struct {
this.initUserPlan(server)
// 集群
clusterId := int64(0)
var clusterId = int64(0)
this.Data["clusterName"] = ""
if server.NodeCluster != nil {
clusterId = server.NodeCluster.Id
@@ -79,7 +80,7 @@ func (this *IndexAction) RunGet(params struct {
}
// 分组
groupMaps := []maps.Map{}
var groupMaps = []maps.Map{}
if len(server.ServerGroups) > 0 {
for _, group := range server.ServerGroups {
groupMaps = append(groupMaps, maps.Map{
@@ -89,23 +90,36 @@ func (this *IndexAction) RunGet(params struct {
}
}
this.Data["server"] = maps.Map{
"id": server.Id,
"clusterId": clusterId,
"type": server.Type,
"name": server.Name,
"description": server.Description,
"isOn": server.IsOn,
"groups": groupMaps,
// 域名和限流状态
var trafficLimitStatus *serverconfigs.TrafficLimitStatus
if len(server.Config) > 0 {
var serverConfig = &serverconfigs.ServerConfig{}
err = json.Unmarshal(server.Config, serverConfig)
if err == nil {
if serverConfig.TrafficLimitStatus != nil && serverConfig.TrafficLimitStatus.IsValid() {
trafficLimitStatus = serverConfig.TrafficLimitStatus
}
}
}
serverType := serverconfigs.FindServerType(server.Type)
this.Data["server"] = maps.Map{
"id": server.Id,
"clusterId": clusterId,
"type": server.Type,
"name": server.Name,
"description": server.Description,
"isOn": server.IsOn,
"groups": groupMaps,
"trafficLimitStatus": trafficLimitStatus,
}
var serverType = serverconfigs.FindServerType(server.Type)
if serverType == nil {
this.ErrorPage(errors.New("invalid server type '" + server.Type + "'"))
return
}
typeName := serverType.GetString("name")
var typeName = serverType.GetString("name")
this.Data["typeName"] = typeName
// 记录最近使用

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -11,6 +12,7 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"strings"
)
type IndexAction struct {
@@ -71,12 +73,55 @@ func (this *IndexAction) RunPost(params struct {
defer this.CreateLogInfo(codes.ServerCache_LogUpdateCacheSettings, params.WebId)
// 校验配置
cacheConfig := &serverconfigs.HTTPCacheConfig{}
var cacheConfig = &serverconfigs.HTTPCacheConfig{}
err := json.Unmarshal(params.CacheJSON, cacheConfig)
if err != nil {
this.ErrorPage(err)
return
}
// 检查Key
if cacheConfig.Key != nil && cacheConfig.Key.IsOn {
if cacheConfig.Key.Scheme != "http" && cacheConfig.Key.Scheme != "https" {
this.Fail("缓存主域名协议只能是http或者https")
return
}
if len(cacheConfig.Key.Host) == 0 {
this.Fail("请输入缓存主域名")
return
}
cacheConfig.Key.Host = strings.ToLower(strings.TrimSuffix(cacheConfig.Key.Host, "/"))
if !domainutils.ValidateDomainFormat(cacheConfig.Key.Host) {
this.Fail("请输入正确的缓存主域名")
return
}
// 检查域名所属
serverIdResp, err := this.RPC().HTTPWebRPC().FindServerIdWithHTTPWebId(this.AdminContext(), &pb.FindServerIdWithHTTPWebIdRequest{HttpWebId: params.WebId})
if err != nil {
this.ErrorPage(err)
return
}
var serverId = serverIdResp.ServerId
if serverId <= 0 {
this.Fail("找不到要操作的网站")
return
}
existServerNameResp, err := this.RPC().ServerRPC().CheckServerNameInServer(this.AdminContext(), &pb.CheckServerNameInServerRequest{
ServerId: serverId,
ServerName: cacheConfig.Key.Host,
})
if err != nil {
this.ErrorPage(err)
return
}
if !existServerNameResp.Exists {
this.Fail("域名 '" + cacheConfig.Key.Host + "' 在当前网站中并未绑定,不能作为缓存主域名")
return
}
}
err = cacheConfig.Init()
if err != nil {
this.Fail("检查配置失败:" + err.Error())

View File

@@ -130,8 +130,6 @@ func (this *LocationHelper) createMenus(serverIdString string, locationIdString
"isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.Compression != nil && locationConfig.Web.Compression.IsPrior,
})
menuItems = this.filterMenuItems3(locationConfig, menuItems, serverIdString, locationIdString, secondMenuItem, actionPtr)
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingPages),
"url": "/servers/server/settings/locations/pages?serverId=" + serverIdString + "&locationId=" + locationIdString,
@@ -156,6 +154,9 @@ func (this *LocationHelper) createMenus(serverIdString string, locationIdString
"isActive": secondMenuItem == "webp",
"isOn": locationConfig != nil && locationConfig.Web != nil && locationConfig.Web.WebP != nil && locationConfig.Web.WebP.IsPrior,
})
menuItems = this.filterMenuItems3(locationConfig, menuItems, serverIdString, locationIdString, secondMenuItem, actionPtr)
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingStat),
"url": "/servers/server/settings/locations/stat?serverId=" + serverIdString + "&locationId=" + locationIdString,

View File

@@ -102,6 +102,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -1,6 +1,7 @@
package waf
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
@@ -37,12 +38,23 @@ func (this *IndexAction) RunGet(params struct {
return
}
if firewallPolicy != nil {
// captcha action
var captchaOptions = firewallconfigs.DefaultHTTPFirewallCaptchaAction()
if len(firewallPolicy.CaptchaOptionsJSON) > 0 {
err = json.Unmarshal(firewallPolicy.CaptchaOptionsJSON, captchaOptions)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["firewallPolicy"] = maps.Map{
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"captchaOptions": captchaOptions,
}
} else {
this.Data["firewallPolicy"] = nil

View File

@@ -20,7 +20,7 @@ func (this *CreatePopupAction) Init() {
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Data["bodyTypes"] = shared.FindAllBodyTypes()
this.Data["bodyTypes"] = serverconfigs.FindAllHTTPPageBodyTypes()
this.Show()
}
@@ -32,22 +32,33 @@ func (this *CreatePopupAction) RunPost(params struct {
URL string `alias:"url"`
Body string
ExceptURLPatternsJSON []byte
OnlyURLPatternsJSON []byte
NewStatus int
Must *actions.Must
}) {
// TODO 对状态码进行更多校验
params.Must.
Field("status", params.Status).
Require("请输入响应状态码")
if len(params.Status) != 3 {
this.FailField("status", "状态码长度必须为3位")
return
}
switch params.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
params.Must.
Field("url", params.URL).
Require("请输入要显示的URL").
Match( `^(?i)(http|https)://`, "请输入正确的URL")
case shared.BodyTypeHTML:
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeRedirectURL:
params.Must.
Field("url", params.URL).
Require("请输入要跳转的URL").
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeHTML:
params.Must.
Field("body", params.Body).
Require("请输入要显示的HTML内容")
@@ -58,12 +69,32 @@ func (this *CreatePopupAction) RunPost(params struct {
}
}
var exceptURLPatterns = []*shared.URLPattern{}
if len(params.ExceptURLPatternsJSON) > 0 {
err := json.Unmarshal(params.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
var onlyURLPatterns = []*shared.URLPattern{}
if len(params.OnlyURLPatternsJSON) > 0 {
err := json.Unmarshal(params.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
createResp, err := this.RPC().HTTPPageRPC().CreateHTTPPage(this.AdminContext(), &pb.CreateHTTPPageRequest{
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
ExceptURLPatternsJSON: params.ExceptURLPatternsJSON,
OnlyURLPatternsJSON: params.OnlyURLPatternsJSON,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -7,7 +7,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"regexp"
@@ -77,7 +76,11 @@ func (this *IndexAction) RunPost(params struct {
}
// check url
if page.BodyType == shared.BodyTypeURL && !urlReg.MatchString(page.URL) {
if page.BodyType == serverconfigs.HTTPPageBodyTypeURL && !urlReg.MatchString(page.URL) {
this.Fail("自定义页面中 '" + page.URL + "' 不是一个正确的URL请进行修改")
return
}
if page.BodyType == serverconfigs.HTTPPageBodyTypeRedirectURL && !urlReg.MatchString(page.URL) {
this.Fail("自定义页面中 '" + page.URL + "' 不是一个正确的URL请进行修改")
return
}
@@ -99,7 +102,7 @@ func (this *IndexAction) RunPost(params struct {
return
}
if shutdownConfig.BodyType == shared.BodyTypeURL {
if shutdownConfig.BodyType == serverconfigs.HTTPPageBodyTypeURL {
if len(shutdownConfig.URL) > 512 {
this.Fail("临时关闭页面中URL过长不能超过512字节")
return
@@ -109,7 +112,17 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("临时关闭页面中 '" + shutdownConfig.URL + "' 不是一个正确的URL请进行修改")
return
}
} else if shutdownConfig.Body == shared.BodyTypeHTML {
} else if shutdownConfig.BodyType == serverconfigs.HTTPPageBodyTypeRedirectURL {
if len(shutdownConfig.URL) > 512 {
this.Fail("临时关闭页面中URL过长不能超过512字节")
return
}
if shutdownConfig.IsOn /** 只有启用的时候才校验 **/ && !urlReg.MatchString(shutdownConfig.URL) {
this.Fail("临时关闭页面中 '" + shutdownConfig.URL + "' 不是一个正确的URL请进行修改")
return
}
} else if shutdownConfig.Body == serverconfigs.HTTPPageBodyTypeHTML {
if len(shutdownConfig.Body) > 32*1024 {
this.Fail("临时关闭页面中HTML内容长度不能超过32K")
return

View File

@@ -22,7 +22,7 @@ func (this *UpdatePopupAction) Init() {
func (this *UpdatePopupAction) RunGet(params struct {
PageId int64
}) {
this.Data["bodyTypes"] = shared.FindAllBodyTypes()
this.Data["bodyTypes"] = serverconfigs.FindAllHTTPPageBodyTypes()
configResp, err := this.RPC().HTTPPageRPC().FindEnabledHTTPPageConfig(this.AdminContext(), &pb.FindEnabledHTTPPageConfigRequest{HttpPageId: params.PageId})
if err != nil {
@@ -30,12 +30,18 @@ func (this *UpdatePopupAction) RunGet(params struct {
return
}
pageConfig := &serverconfigs.HTTPPageConfig{}
var pageConfig = &serverconfigs.HTTPPageConfig{}
err = json.Unmarshal(configResp.PageJSON, pageConfig)
if err != nil {
this.ErrorPage(err)
return
}
if pageConfig.ExceptURLPatterns == nil {
pageConfig.ExceptURLPatterns = []*shared.URLPattern{}
}
if pageConfig.OnlyURLPatterns == nil {
pageConfig.OnlyURLPatterns = []*shared.URLPattern{}
}
this.Data["pageConfig"] = pageConfig
this.Show()
@@ -50,6 +56,9 @@ func (this *UpdatePopupAction) RunPost(params struct {
URL string `alias:"url"`
Body string
ExceptURLPatternsJSON []byte
OnlyURLPatternsJSON []byte
NewStatus int
Must *actions.Must
@@ -61,13 +70,23 @@ func (this *UpdatePopupAction) RunPost(params struct {
Field("status", params.Status).
Require("请输入响应状态码")
if len(params.Status) != 3 {
this.FailField("status", "状态码长度必须为3位")
return
}
switch params.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
params.Must.
Field("url", params.URL).
Require("请输入要显示的URL").
Match( `^(?i)(http|https)://`, "请输入正确的URL")
case shared.BodyTypeHTML:
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeRedirectURL:
params.Must.
Field("url", params.URL).
Require("请输入要跳转的URL").
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeHTML:
params.Must.
Field("body", params.Body).
Require("请输入要显示的HTML内容")
@@ -78,13 +97,33 @@ func (this *UpdatePopupAction) RunPost(params struct {
}
}
var exceptURLPatterns = []*shared.URLPattern{}
if len(params.ExceptURLPatternsJSON) > 0 {
err := json.Unmarshal(params.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
var onlyURLPatterns = []*shared.URLPattern{}
if len(params.OnlyURLPatternsJSON) > 0 {
err := json.Unmarshal(params.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
_, err := this.RPC().HTTPPageRPC().UpdateHTTPPage(this.AdminContext(), &pb.UpdateHTTPPageRequest{
HttpPageId: params.PageId,
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
HttpPageId: params.PageId,
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
ExceptURLPatternsJSON: params.ExceptURLPatternsJSON,
OnlyURLPatternsJSON: params.OnlyURLPatternsJSON,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -52,7 +52,11 @@ func (this *CreatePopupAction) RunPost(params struct {
PortAfter int
PortAfterScheme string
Status int
Status int
ExceptDomainsJSON []byte
OnlyDomainsJSON []byte
CondsJSON []byte
IsOn bool
@@ -186,6 +190,27 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("status", params.Status).
Gte(0, "请选择正确的跳转状态码")
// 域名
if len(params.ExceptDomainsJSON) > 0 {
var exceptDomains = []string{}
err := json.Unmarshal(params.ExceptDomainsJSON, &exceptDomains)
if err != nil {
this.ErrorPage(err)
return
}
config.ExceptDomains = exceptDomains
}
if len(params.OnlyDomainsJSON) > 0 {
var onlyDomains = []string{}
err := json.Unmarshal(params.OnlyDomainsJSON, &onlyDomains)
if err != nil {
this.ErrorPage(err)
return
}
config.OnlyDomains = onlyDomains
}
// 校验匹配条件
var conds *shared.HTTPRequestCondsConfig
if len(params.CondsJSON) > 0 {

View File

@@ -134,6 +134,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
RequestHostExcludingPort: reverseProxyConfig.RequestHostExcludingPort,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -90,7 +90,7 @@ func (this *IndexAction) RunPost(params struct {
// 记录日志
defer this.CreateLogInfo(codes.Server_ServerNamesLogUpdateServerNames, params.ServerId)
serverNames := []*serverconfigs.ServerNameConfig{}
var serverNames = []*serverconfigs.ServerNameConfig{}
err := json.Unmarshal([]byte(params.ServerNames), &serverNames)
if err != nil {
this.Fail("域名解析失败:" + err.Error())
@@ -105,10 +105,13 @@ func (this *IndexAction) RunPost(params struct {
this.NotFound("server", params.ServerId)
return
}
clusterId := serverResp.Server.NodeCluster.Id
var clusterId = serverResp.Server.NodeCluster.Id
// 检查套餐
this.checkPlan(params.ServerId, serverNames)
// 检查域名是否已经存在
allServerNames := serverconfigs.PlainServerNames(serverNames)
var allServerNames = serverconfigs.PlainServerNames(serverNames)
if len(allServerNames) > 0 {
dupResp, err := this.RPC().ServerRPC().CheckServerNameDuplicationInNodeCluster(this.AdminContext(), &pb.CheckServerNameDuplicationInNodeClusterRequest{
ServerNames: allServerNames,

View File

@@ -0,0 +1,10 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package serverNames
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
func (this *IndexAction) checkPlan(serverId int64, serverNames []*serverconfigs.ServerNameConfig) {
// stub
}

View File

@@ -1,6 +1,7 @@
package waf
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
@@ -50,12 +51,23 @@ func (this *IndexAction) RunGet(params struct {
return
}
if firewallPolicy != nil {
// captcha action
var captchaOptions = firewallconfigs.DefaultHTTPFirewallCaptchaAction()
if len(firewallPolicy.CaptchaOptionsJSON) > 0 {
err = json.Unmarshal(firewallPolicy.CaptchaOptionsJSON, captchaOptions)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["firewallPolicy"] = maps.Map{
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"id": firewallPolicy.Id,
"name": firewallPolicy.Name,
"isOn": firewallPolicy.IsOn,
"mode": firewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(firewallPolicy.Mode),
"captchaAction": captchaOptions,
}
} else {
this.Data["firewallPolicy"] = nil

View File

@@ -42,6 +42,7 @@ func (this *CountriesAction) RunGet(params struct {
this.NotFound("firewallPolicy", params.FirewallPolicyId)
return
}
var deniedCountryIds = []int64{}
var allowedCountryIds = []int64{}
var countryHTML = ""
@@ -95,6 +96,24 @@ func (this *CountriesAction) RunGet(params struct {
}
this.Data["wafIsOn"] = webConfig.FirewallRef != nil && webConfig.FirewallRef.IsOn
// 获取当前服务所在集群的WAF设置
clusterFirewallPolicy, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if clusterFirewallPolicy != nil {
this.Data["clusterFirewallPolicy"] = maps.Map{
"id": clusterFirewallPolicy.Id,
"name": clusterFirewallPolicy.Name,
"isOn": clusterFirewallPolicy.IsOn,
"mode": clusterFirewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(clusterFirewallPolicy.Mode),
}
} else {
this.Data["clusterFirewallPolicy"] = nil
}
this.Show()
}

View File

@@ -96,6 +96,24 @@ func (this *ProvincesAction) RunGet(params struct {
}
this.Data["wafIsOn"] = webConfig.FirewallRef != nil && webConfig.FirewallRef.IsOn
// 获取当前服务所在集群的WAF设置
clusterFirewallPolicy, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if clusterFirewallPolicy != nil {
this.Data["clusterFirewallPolicy"] = maps.Map{
"id": clusterFirewallPolicy.Id,
"name": clusterFirewallPolicy.Name,
"isOn": clusterFirewallPolicy.IsOn,
"mode": clusterFirewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(clusterFirewallPolicy.Mode),
}
} else {
this.Data["clusterFirewallPolicy"] = nil
}
this.Show()
}

View File

@@ -344,8 +344,6 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"configCode": serverconfigs.ConfigCodeCompression,
})
menuItems = this.filterMenuItems3(serverConfig, menuItems, serverIdString, secondMenuItem, actionPtr)
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingPages),
"url": "/servers/server/settings/pages?serverId=" + serverIdString,
@@ -374,6 +372,8 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"configCode": serverconfigs.ConfigCodeWebp,
})
menuItems = this.filterMenuItems3(serverConfig, menuItems, serverIdString, secondMenuItem, actionPtr)
menuItems = append(menuItems, maps.Map{
"name": this.Lang(actionPtr, codes.Server_MenuSettingStat),
"url": "/servers/server/settings/stat?serverId=" + serverIdString,

View File

@@ -132,25 +132,25 @@ func (this *StatusAction) RunPost(params struct {
if err != nil {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:解析域名'" + serverName.Name + "' CNAME记录时出错" + err.Error() + ",请修复此问题。如果已经修改,请等待一个小时后再试。"
m["todo"] = "错误信息:解析域名'" + serverName.Name + "' CNAME记录时出错" + err.Error() + ",请修复此问题。如果已经修改,请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
if len(result) == 0 {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:找不到域名'" + serverName.Name + "'的CNAME记录请修复此问题。如果已经修改请等待一个小时后再试。"
m["todo"] = "错误信息:找不到域名'" + serverName.Name + "'的CNAME记录请修复此问题。如果已经修改请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
if result == serverName.Name+"." {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:找不到域名'" + serverName.Name + "'的CNAME记录请设置为'" + cname + "'。如果已经设置,请等待一个小时后再试。"
m["todo"] = "错误信息:找不到域名'" + serverName.Name + "'的CNAME记录请设置为'" + cname + "'。如果已经设置,请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
if result != cname {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:解析域名'" + serverName.Name + "' CNAME记录时出错当前的CNAME值为" + result + ",请修改为" + cname + "。如果已经修改,请等待一个小时后再试。"
m["todo"] = "错误信息:解析域名'" + serverName.Name + "' CNAME记录时出错当前的CNAME值为" + result + ",请修改为" + cname + "。如果已经修改,请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
} else {
@@ -160,25 +160,25 @@ func (this *StatusAction) RunPost(params struct {
if err != nil {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:解析域名'" + subName + "' CNAME记录时出错" + err.Error() + ",请修复此问题。如果已经修改,请等待一个小时后再试。"
m["todo"] = "错误信息:解析域名'" + subName + "' CNAME记录时出错" + err.Error() + ",请修复此问题。如果已经修改,请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
if len(result) == 0 {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:找不到域名'" + subName + "'的CNAME记录请修复此问题。如果已经修改请等待一个小时后再试。"
m["todo"] = "错误信息:找不到域名'" + subName + "'的CNAME记录请修复此问题。如果已经修改请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
if result == cname+"." {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:找不到域名'" + serverName.Name + "'的CNAME记录请设置为'" + cname + "'。如果已经设置,请等待一个小时后再试。"
m["todo"] = "错误信息:找不到域名'" + serverName.Name + "'的CNAME记录请设置为'" + cname + "'。如果已经设置,请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
if result != cname {
m["type"] = "dnsResolveErr"
m["message"] = "域名解析错误"
m["todo"] = "错误信息:解析域名'" + subName + "' CNAME记录时出错当前的CNAME值为" + result + ",请修改为" + cname + "。如果已经修改,请等待一个小时后再试。"
m["todo"] = "错误信息:解析域名'" + subName + "' CNAME记录时出错当前的CNAME值为" + result + ",请修改为" + cname + "。如果已经修改,请等待一个小时后再试。如果长时间无法生效请咨询你的域名DNS服务商。"
return
}
}

View File

@@ -0,0 +1,17 @@
package lang
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Prefix("/settings/lang").
Post("/switch", new(SwitchAction)).
EndAll()
})
}

View File

@@ -0,0 +1,36 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package lang
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type SwitchAction struct {
actionutils.ParentAction
}
func (this *SwitchAction) Init() {
this.Nav("", "", "")
}
func (this *SwitchAction) RunPost(params struct{}) {
var langCode = this.LangCode()
if len(langCode) == 0 || langCode == "zh-cn" {
langCode = "en-us"
} else {
langCode = "zh-cn"
}
configloaders.UpdateAdminLang(this.AdminId(), langCode)
_, err := this.RPC().AdminRPC().UpdateAdminLang(this.AdminContext(), &pb.UpdateAdminLangRequest{LangCode: langCode})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -77,6 +77,8 @@ func (this *IndexAction) RunPost(params struct {
AllowIPs []string
AllowRememberLogin bool
ClientIPHeaderNames string
DenySearchEngines bool
DenySpiders bool
@@ -137,6 +139,9 @@ func (this *IndexAction) RunPost(params struct {
// 允许本地
config.AllowLocal = params.AllowLocal
// 客户端IP获取方式
config.ClientIPHeaderNames = params.ClientIPHeaderNames
// 禁止搜索引擎和爬虫
config.DenySearchEngines = params.DenySearchEngines
config.DenySpiders = params.DenySpiders

View File

@@ -48,7 +48,7 @@ func (this *UpdateHostsAction) RunPost(params struct {
if err != nil {
this.FailField("host", "测试API节点时出错请检查配置错误信息"+err.Error())
}
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.APIContext(0), &pb.FindCurrentAPINodeVersionRequest{})
_, err = client.APINodeRPC().FindCurrentAPINodeVersion(client.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.FailField("host", "无法连接此API节点错误信息"+err.Error())
}

View File

@@ -7,6 +7,7 @@ import (
"crypto/rand"
"errors"
"fmt"
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"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"
@@ -14,7 +15,6 @@ import (
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
@@ -57,7 +57,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
// check 'tar' command
this.log("checking 'tar' command ...")
var tarExe, _ = exec.LookPath("tar")
var tarExe, _ = executils.LookPath("tar")
if len(tarExe) == 0 {
this.log("installing 'tar' command ...")
err = this.installTarCommand()
@@ -70,7 +70,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
this.log("checking system commands ...")
var cmdList = []string{"tar" /** again **/, "chown", "sh"}
for _, cmd := range cmdList {
cmdPath, err := exec.LookPath(cmd)
cmdPath, err := executils.LookPath(cmd)
if err != nil || len(cmdPath) == 0 {
return errors.New("could not find '" + cmd + "' command in this system")
}
@@ -87,7 +87,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
}
// ubuntu apt
aptExe, err := exec.LookPath("apt")
aptExe, err := executils.LookPath("apt")
if err == nil && len(aptExe) > 0 {
for _, lib := range []string{"libaio1", "libncurses5"} {
this.log("checking " + lib + " ...")
@@ -100,7 +100,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string)
time.Sleep(1 * time.Second)
}
} else { // yum
yumExe, err := exec.LookPath("yum")
yumExe, err := executils.LookPath("yum")
if err == nil && len(yumExe) > 0 {
for _, lib := range []string{"libaio", "ncurses-libs", "ncurses-compat-libs", "numactl-libs"} {
var cmd = utils.NewCmd("yum", "-y", "install", lib)
@@ -408,7 +408,8 @@ func (this *MySQLInstaller) Download() (path string, err error) {
// check latest version
this.log("checking mysql latest version ...")
var latestVersion = "8.1.0" // default version
var latestVersion = "8.2.0" // default version
var majorVersion = "8.2"
{
req, err := http.NewRequest(http.MethodGet, "https://dev.mysql.com/downloads/mysql/", nil)
if err != nil {
@@ -434,6 +435,10 @@ func (this *MySQLInstaller) Download() (path string, err error) {
var matches = reg.FindSubmatch(data)
if len(matches) > 0 {
latestVersion = string(matches[1])
var matchPieces = strings.Split(latestVersion, ".")
if len(matchPieces) >= 2 {
majorVersion = strings.Join(matchPieces[:2], ".")
}
}
}
}
@@ -441,7 +446,7 @@ func (this *MySQLInstaller) Download() (path string, err error) {
// download
this.log("start downloading ...")
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-8.1/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-" + majorVersion + "/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
{
this.log("downloading from url '" + downloadURL + "' ...")
@@ -557,7 +562,7 @@ func (this *MySQLInstaller) log(message string) {
// copy file
func (this *MySQLInstaller) installService(baseDir string) error {
_, err := exec.LookPath("systemctl")
_, err := executils.LookPath("systemctl")
if err != nil {
return err
}
@@ -613,14 +618,14 @@ WantedBy=multi-user.target`
// install 'tar' command automatically
func (this *MySQLInstaller) installTarCommand() error {
// yum
yumExe, err := exec.LookPath("yum")
yumExe, err := executils.LookPath("yum")
if err == nil && len(yumExe) > 0 {
var cmd = utils.NewTimeoutCmd(10*time.Second, yumExe, "-y", "install", "tar")
return cmd.Run()
}
// apt
aptExe, err := exec.LookPath("apt")
aptExe, err := executils.LookPath("apt")
if err == nil && len(aptExe) > 0 {
var cmd = utils.NewTimeoutCmd(10*time.Second, aptExe, "-y", "install", "tar")
return cmd.Run()
@@ -631,7 +636,7 @@ func (this *MySQLInstaller) installTarCommand() error {
func (this *MySQLInstaller) lookupGroupAdd() (string, error) {
for _, cmd := range []string{"groupadd", "addgroup"} {
path, err := exec.LookPath(cmd)
path, err := executils.LookPath(cmd)
if err == nil && len(path) > 0 {
return path, nil
}
@@ -641,7 +646,7 @@ func (this *MySQLInstaller) lookupGroupAdd() (string, error) {
func (this *MySQLInstaller) lookupUserAdd() (string, error) {
for _, cmd := range []string{"useradd", "adduser"} {
path, err := exec.LookPath(cmd)
path, err := executils.LookPath(cmd)
if err == nil && len(path) > 0 {
return path, nil
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"net/http"
)
@@ -71,7 +72,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
buffer.Write(typesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 条件操作符
@@ -81,7 +82,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
buffer.Write(requestOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 请求变量
@@ -91,7 +92,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.REQUEST_VARIABLES = ")
buffer.Write(requestVariablesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 指标
@@ -101,7 +102,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
buffer.Write(metricHTTPKeysJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值项目
@@ -111,7 +112,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
buffer.Write(ipAddrThresholdItemsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值动作
@@ -121,7 +122,25 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
buffer.Write(ipAddrThresholdActionsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF checkpoints
var wafCheckpointsMaps = []maps.Map{}
for _, checkpoint := range firewallconfigs.AllCheckpoints {
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
"name": checkpoint.Name,
"prefix": checkpoint.Prefix,
"description": checkpoint.Description,
})
}
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
if err != nil {
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
buffer.Write(wafCheckpointsJSON)
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF操作符
@@ -131,7 +150,17 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF验证码类型
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
if err != nil {
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
buffer.Write(captchaTypesJSON)
buffer.Write([]byte{';', '\n', '\n'})
}
componentsData = buffer.Bytes()

View File

@@ -13,6 +13,7 @@ func init() {
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeUser)).
Data("teaMenu", "users").
Prefix("/users").
Data("teaSubMenu", "users").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).

View File

@@ -81,11 +81,6 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i
"url": "/servers/metrics",
"code": "metric",
},
{
"name": langs.Message(langCode, codes.AdminMenu_ServerGlobalSettings),
"url": "/servers/components",
"code": "global",
},
},
},
{
@@ -147,6 +142,13 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i
"module": configloaders.AdminModuleCodeUser,
"name": langs.Message(langCode, codes.AdminMenu_Users),
"icon": "users",
"subItems": []maps.Map{
{
"name": langs.Message(langCode, codes.AdminMenu_UserList),
"url": "/users",
"code": "users",
},
},
},
{
"code": "admins",

View File

@@ -17,7 +17,6 @@ import (
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"net"
"net/http"
"net/url"
"reflect"
@@ -145,12 +144,7 @@ func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
// 检查IP
if !checkIP(securityConfig, action.RequestRemoteIP()) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
remoteAddr, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
if len(remoteAddr) > 0 && remoteAddr != action.RequestRemoteIP() && !checkIP(securityConfig, remoteAddr) {
if !checkIP(securityConfig, loginutils.RemoteIP(action)) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}

View File

@@ -6,7 +6,6 @@ import (
"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"
)
@@ -33,12 +32,7 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
// 检查IP
if !checkIP(securityConfig, action.RequestRemoteIP()) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
remoteAddr, _, _ := net.SplitHostPort(action.Request.RemoteAddr)
if len(remoteAddr) > 0 && remoteAddr != action.RequestRemoteIP() && !checkIP(securityConfig, remoteAddr) {
if !checkIP(securityConfig, loginutils.RemoteIP(action)) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}

View File

@@ -117,6 +117,7 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/backup"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/database"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/lang"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/login"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/profile"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/security"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,79 @@
Vue.component("bandwidth-size-capacity-box", {
props: ["v-name", "v-value", "v-count", "v-unit", "size", "maxlength", "v-supported-units"],
data: function () {
let v = this.vValue
if (v == null) {
v = {
count: this.vCount,
unit: this.vUnit
}
}
if (v.unit == null || v.unit.length == 0) {
v.unit = "mb"
}
if (typeof (v["count"]) != "number") {
v["count"] = -1
}
let vSize = this.size
if (vSize == null) {
vSize = 6
}
let vMaxlength = this.maxlength
if (vMaxlength == null) {
vMaxlength = 10
}
let supportedUnits = this.vSupportedUnits
if (supportedUnits == null) {
supportedUnits = []
}
return {
capacity: v,
countString: (v.count >= 0) ? v.count.toString() : "",
vSize: vSize,
vMaxlength: vMaxlength,
supportedUnits: supportedUnits
}
},
watch: {
"countString": function (newValue) {
let value = newValue.trim()
if (value.length == 0) {
this.capacity.count = -1
this.change()
return
}
let count = parseInt(value)
if (!isNaN(count)) {
this.capacity.count = count
}
this.change()
}
},
methods: {
change: function () {
this.$emit("change", this.capacity)
}
},
template: `<div class="ui fields inline">
<input type="hidden" :name="vName" :value="JSON.stringify(capacity)"/>
<div class="ui field">
<input type="text" v-model="countString" :maxlength="vMaxlength" :size="vSize"/>
</div>
<div class="ui field">
<select class="ui dropdown" v-model="capacity.unit" @change="change">
<option value="b" v-if="supportedUnits.length == 0 || supportedUnits.$contains('b')">Bps</option>
<option value="kb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('kb')">Kbps</option>
<option value="mb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('mb')">Mbps</option>
<option value="gb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('gb')">Gbps</option>
<option value="tb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('tb')">Tbps</option>
<option value="pb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('pb')">Pbps</option>
<option value="eb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('eb')">Ebps</option>
</select>
</div>
</div>`
})

View File

@@ -0,0 +1,15 @@
Vue.component("bandwidth-size-capacity-view", {
props: ["v-value"],
data: function () {
let capacity = this.vValue
if (capacity != null && capacity.count > 0 && typeof capacity.unit === "string") {
capacity.unit = capacity.unit[0].toUpperCase() + capacity.unit.substring(1) + "ps"
}
return {
capacity: capacity
}
},
template: `<span>
<span v-if="capacity != null && capacity.count > 0">{{capacity.count}}{{capacity.unit}}</span>
</span>`
})

View File

@@ -0,0 +1,80 @@
Vue.component("chart-columns-grid", {
props: [],
mounted: function () {
this.columns = this.calculateColumns()
let that = this
window.addEventListener("resize", function () {
that.columns = that.calculateColumns()
})
},
updated: function () {
let totalElements = this.$el.getElementsByClassName("column").length
if (totalElements == this.totalElements) {
return
}
this.totalElements = totalElements
this.calculateColumns()
},
data: function () {
return {
columns: "four",
totalElements: 0
}
},
methods: {
calculateColumns: function () {
let w = window.innerWidth
let columns = Math.floor(w / 500)
if (columns == 0) {
columns = 1
}
let columnElements = this.$el.getElementsByClassName("column")
if (columnElements.length == 0) {
return "one"
}
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 chart-grid'">
<slot></slot>
</div>`
})

View File

@@ -63,12 +63,12 @@ Vue.component("size-capacity-box", {
<div class="ui field">
<select class="ui dropdown" v-model="capacity.unit" @change="change">
<option value="byte" v-if="supportedUnits.length == 0 || supportedUnits.$contains('byte')">字节</option>
<option value="kb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('kb')">KB</option>
<option value="mb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('mb')">MB</option>
<option value="gb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('gb')">GB</option>
<option value="tb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('tb')">TB</option>
<option value="pb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('pb')">PB</option>
<option value="eb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('eb')">EB</option>
<option value="kb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('kb')">KiB</option>
<option value="mb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('mb')">MiB</option>
<option value="gb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('gb')">GiB</option>
<option value="tb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('tb')">TiB</option>
<option value="pb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('pb')">PiB</option>
<option value="eb" v-if="supportedUnits.length == 0 || supportedUnits.$contains('eb')">EiB</option>
</select>
</div>
</div>`

View File

@@ -1,7 +1,12 @@
Vue.component("size-capacity-view", {
props:["v-default-text", "v-value"],
methods: {
composeCapacity: function (capacity) {
return teaweb.convertSizeCapacityToString(capacity)
}
},
template: `<div>
<span v-if="vValue != null && vValue.count > 0">{{vValue.count}}{{vValue.unit.toUpperCase()}}</span>
<span v-if="vValue != null && vValue.count > 0">{{composeCapacity(vValue)}}</span>
<span v-else>{{vDefaultText}}</span>
</div>`
})

View File

@@ -5,6 +5,7 @@ Vue.component("url-patterns-box", {
if (this.value != null) {
patterns = this.value
}
return {
patterns: patterns,
isAdding: false,
@@ -12,7 +13,9 @@ Vue.component("url-patterns-box", {
addingPattern: {"type": "wildcard", "pattern": ""},
editingIndex: -1,
patternIsInvalid: false
patternIsInvalid: false,
windowIsSmall: window.innerWidth < 600
}
},
methods: {
@@ -105,7 +108,7 @@ Vue.component("url-patterns-box", {
</div>
</div>
<div v-show="isAdding" style="margin-top: 0.5em">
<div class="ui fields inline">
<div :class="{'ui fields inline': !windowIsSmall}">
<div class="ui field">
<select class="ui dropdown auto-width" v-model="addingPattern.type">
<option value="wildcard">通配符</option>

View File

@@ -1,11 +1,18 @@
Vue.component("ip-list-table", {
props: ["v-items", "v-keyword", "v-show-search-button"],
props: ["v-items", "v-keyword", "v-show-search-button", "v-total"/** total items >= items length **/],
data: function () {
let maxDeletes = 10000
if (this.vTotal != null && this.vTotal > 0 && this.vTotal < maxDeletes) {
maxDeletes = this.vTotal
}
return {
items: this.vItems,
keyword: (this.vKeyword != null) ? this.vKeyword : "",
selectedAll: false,
hasSelectedItems: false
hasSelectedItems: false,
MaxDeletes: maxDeletes
}
},
methods: {
@@ -71,6 +78,21 @@ Vue.component("ip-list-table", {
teaweb.successToast("批量删除成功", 1200, teaweb.reload)
})
},
deleteCount: function () {
let that = this
teaweb.confirm("确定要批量删除当前列表中的" + this.MaxDeletes + "个IP吗", function () {
let query = window.location.search
if (query.startsWith("?")) {
query = query.substring(1)
}
Tea.action("/servers/iplists/deleteCount?" + query)
.post()
.params({count: that.MaxDeletes})
.success(function () {
teaweb.successToast("批量删除成功", 1200, teaweb.reload)
})
})
},
formatSeconds: function (seconds) {
if (seconds < 60) {
return seconds + "秒"
@@ -82,11 +104,29 @@ Vue.component("ip-list-table", {
return Math.ceil(seconds / 3600) + "小时"
}
return Math.ceil(seconds / 86400) + "天"
},
cancelChecked: function () {
this.hasSelectedItems = false
this.selectedAll = false
let boxes = this.$refs.itemCheckBox
if (boxes == null) {
return
}
boxes.forEach(function (box) {
box.checked = false
})
}
},
template: `<div>
<div v-show="hasSelectedItems">
<a href="" @click.prevent="deleteAll">[批量删除]</a>
<div class="ui divider"></div>
<button class="ui button basic" type="button" @click.prevent="deleteAll">批量删除所选</button>
&nbsp; &nbsp;
<button class="ui button basic" type="button" @click.prevent="deleteCount" v-if="vTotal != null && vTotal >= MaxDeletes">批量删除{{MaxDeletes}}个</button>
&nbsp; &nbsp;
<button class="ui button basic" type="button" @click.prevent="cancelChecked">取消选中</button>
</div>
<table class="ui table selectable celled" v-if="items.length > 0">
<thead>

View File

@@ -34,6 +34,14 @@ Vue.component("traffic-map-box", {
},
methods: {
render: function () {
if (this.$el.offsetWidth < 300) {
let that = this
setTimeout(function () {
that.render()
}, 100)
return
}
this.chart = teaweb.initChart(document.getElementById("traffic-map-box"));
let that = this
this.chart.setOption({

View File

@@ -64,7 +64,7 @@ Vue.component("message-row", {
</tr>
<tr :class="{error: message.level == 'error', positive: message.level == 'success', warning: message.level == 'warning'}">
<td>
{{message.body}}
<pre style="padding: 0; margin:0; word-break: break-all;">{{message.body}}</pre>
<!-- 健康检查 -->
<div v-if="message.type == 'HealthCheckFailed'" style="margin-top: 0.8em">

View File

@@ -19,14 +19,14 @@ Vue.component("plan-price-view", {
<span v-if="plan.priceType == 'traffic'">
按流量计费
<div>
<span class="grey small">基础价格:¥{{plan.trafficPrice.base}}元/GB</span>
<span class="grey small">基础价格:¥{{plan.trafficPrice.base}}元/GiB</span>
</div>
</span>
<div v-if="plan.priceType == 'bandwidth' && plan.bandwidthPrice != null && plan.bandwidthPrice.percentile > 0">
按{{plan.bandwidthPrice.percentile}}th带宽计费
<div>
<div v-for="range in plan.bandwidthPrice.ranges">
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MB</span><span v-else>&infin;</span> <span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MB</span></span>
<span class="small grey">{{range.minMB}} - <span v-if="range.maxMB > 0">{{range.maxMB}}MiB</span><span v-else>&infin;</span> <span v-if="range.totalPrice > 0">{{range.totalPrice}}元</span><span v-else="">{{range.pricePerMB}}元/MiB</span></span>
</div>
</div>
</div>

View File

@@ -105,7 +105,17 @@ Vue.component("domains-box", {
if (this.isEditing && this.editingIndex >= 0) {
this.domains[this.editingIndex] = this.addingDomain
} else {
this.domains.push(this.addingDomain)
// 分割逗号(,)、顿号(、)
if (this.addingDomain.match("[,、,;]")) {
let domainList = this.addingDomain.split(new RegExp("[,、,;]"))
domainList.forEach(function (v) {
if (v.length > 0) {
that.domains.push(v)
}
})
} else {
this.domains.push(this.addingDomain)
}
}
this.cancel()
this.change()

View File

@@ -85,7 +85,7 @@ Vue.component("http-access-log-config-box", {
<label :for="'access-log-field-' + index">{{field.name}}</label>
</div>
<p class="comment">在基础信息之外要存储的信息。
<span class="red" v-if="hasRequestBodyField">记录"请求Body"将会显著消耗更多的系统资源建议仅在调试时启用最大记录尺寸为2MB。</span>
<span class="red" v-if="hasRequestBodyField">记录"请求Body"将会显著消耗更多的系统资源建议仅在调试时启用最大记录尺寸为2MiB。</span>
</p>
</td>
</tr>

View File

@@ -19,11 +19,21 @@ Vue.component("http-cache-config-box", {
cacheConfig.cacheRefs = []
}
var maxBytes = null
let maxBytes = null
if (this.vCachePolicy != null && this.vCachePolicy.maxBytes != null) {
maxBytes = this.vCachePolicy.maxBytes
}
// key
if (cacheConfig.key == null) {
// use Vue.set to activate vue events
Vue.set(cacheConfig, "key", {
isOn: false,
scheme: "https",
host: ""
})
}
return {
cacheConfig: cacheConfig,
moreOptionsVisible: false,
@@ -31,7 +41,9 @@ Vue.component("http-cache-config-box", {
maxBytes: maxBytes,
searchBoxVisible: false,
searchKeyword: ""
searchKeyword: "",
keyOptionsVisible: false
}
},
watch: {
@@ -106,6 +118,44 @@ Vue.component("http-cache-config-box", {
</td>
</tr>
</tbody>
<tbody v-show="isOn() && !vIsGroup">
<tr>
<td>缓存主域名</td>
<td>
<div v-show="!cacheConfig.key.isOn">默认 &nbsp; <a href="" @click.prevent="keyOptionsVisible = !keyOptionsVisible"><span class="small">[修改]</span></a></div>
<div v-show="cacheConfig.key.isOn">使用主域名:{{cacheConfig.key.scheme}}://{{cacheConfig.key.host}} &nbsp; <a href="" @click.prevent="keyOptionsVisible = !keyOptionsVisible"><span class="small">[修改]</span></a></div>
<div v-show="keyOptionsVisible" style="margin-top: 1em">
<div class="ui divider"></div>
<table class="ui table definition">
<tr>
<td class="title">启用主域名</td>
<td><checkbox v-model="cacheConfig.key.isOn"></checkbox>
<p class="comment">启用主域名后,所有缓存键值中的协议和域名部分都会修改为主域名,用来实现缓存不区分域名。</p>
</td>
</tr>
<tr v-show="cacheConfig.key.isOn">
<td>主域名 *</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" v-model="cacheConfig.key.scheme">
<option value="https">https://</option>
<option value="http">http://</option>
</select>
</div>
<div class="ui field">
<input type="text" v-model="cacheConfig.key.host" placeholder="example.com" @keyup.enter="keyOptionsVisible = false" @keypress.enter.prevent="1"/>
</div>
</div>
<p class="comment">此域名<strong>必须</strong>是当前网站已绑定域名,在刷新缓存时也需要使用此域名。</p>
</td>
</tr>
</table>
<button class="ui button tiny" type="button" @click.prevent="keyOptionsVisible = false">完成</button>
</div>
</td>
</tr>
</tbody>
<tbody v-show="isOn()">
<tr>
<td colspan="2">
@@ -122,10 +172,10 @@ Vue.component("http-cache-config-box", {
</td>
</tr>
<tr>
<td>添加X-Cache Header</td>
<td>添加X-Cache报头</td>
<td>
<checkbox v-model="cacheConfig.addStatusHeader"></checkbox>
<p class="comment">选中后自动在响应Header中增加<code-label>X-Cache: BYPASS|MISS|HIT|PURGE</code-label>。</p>
<p class="comment">选中后自动在响应Header中增加<code-label>X-Cache: BYPASS|MISS|HIT|PURGE</code-label>在浏览器端查看X-Cache值时请先禁用浏览器缓存避免影响观察。</p>
</td>
</tr>
<tr>
@@ -164,6 +214,11 @@ Vue.component("http-cache-config-box", {
<http-cache-stale-config :v-cache-stale-config="cacheConfig.stale" @change="changeStale"></http-cache-stale-config>
</div>
<div v-show="isOn()">
<submit-btn></submit-btn>
<div class="ui divider"></div>
</div>
<div v-show="isOn()" style="margin-top: 1em">
<h4 style="position: relative">缓存条件 &nbsp; <a href="" style="font-size: 0.8em" @click.prevent="$refs.cacheRefsConfigBoxRef.addRef(false)">[添加]</a> &nbsp; <a href="" style="font-size: 0.8em" @click.prevent="showSearchBox" v-show="!searchBoxVisible">[搜索]</a>
<div class="ui input small right labeled" style="position: absolute; top: -0.4em; margin-left: 0.5em; zoom: 0.9" v-show="searchBoxVisible">

View File

@@ -159,6 +159,9 @@ Vue.component("http-cache-ref-box", {
}
dialog.style.width = width + "px"
if (this.ref.conds != null) {
this.ref.conds.isOn = true
}
break
}
},

View File

@@ -79,7 +79,17 @@ Vue.component("http-cache-refs-config-box", {
that.refs.push(newRef)
}
that.change()
// move to bottom
var afterChangeCallback = function () {
setTimeout(function () {
let rightBox = document.querySelector(".right-box")
if (rightBox != null) {
rightBox.scrollTo(0, isReverse ? 0 : 100000)
}
}, 100)
}
that.change(afterChangeCallback)
}
})
},
@@ -140,7 +150,7 @@ Vue.component("http-cache-refs-config-box", {
}
return unit
},
change: function () {
change: function (callback) {
this.$forceUpdate()
// 自动保存
@@ -159,7 +169,11 @@ Vue.component("http-cache-refs-config-box", {
})
.success(function (resp) {
if (resp.data.isUpdated) {
teaweb.successToast("保存成功")
teaweb.successToast("保存成功", null, function () {
if (typeof callback == "function") {
callback()
}
})
}
})
.post()

View File

@@ -7,7 +7,8 @@ Vue.component("http-charsets-box", {
isPrior: false,
isOn: false,
charset: "",
isUpper: false
isUpper: false,
force: false
}
}
return {
@@ -51,13 +52,20 @@ Vue.component("http-charsets-box", {
<more-options-tbody @change="changeAdvancedVisible" v-if="((!vIsLocation && !vIsGroup) || charsetConfig.isPrior) && charsetConfig.isOn"></more-options-tbody>
<tbody v-show="((!vIsLocation && !vIsGroup) || charsetConfig.isPrior) && charsetConfig.isOn && advancedVisible">
<tr>
<td>字符编码是否大写</td>
<td>强制替换</td>
<td>
<checkbox v-model="charsetConfig.force"></checkbox>
<p class="comment">选中后,表示强制覆盖已经设置的字符集;不选中,表示如果源站已经设置了字符集,则保留不修改。</p>
</td>
</tr>
<tr>
<td>字符编码大写</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="charsetConfig.isUpper"/>
<label></label>
</div>
<p class="comment">选中后将指定的字符编码转换为大写,比如默认为<span class="ui label tiny">utf-8</span>,选中后将改为<span class="ui label tiny">UTF-8</span>。</p>
<p class="comment">选中后将指定的字符编码转换为大写,比如默认为<code-label>utf-8</code-label>,选中后将改为<code-label>UTF-8</code-label>。</p>
</td>
</tr>
</tbody>

View File

@@ -896,7 +896,7 @@ Vue.component("http-cond-params", {
<td>不区分大小写</td>
<td>
<div class="ui checkbox">
<input type="checkbox" v-model="cond.isCaseInsensitive"/>
<input type="checkbox" name="condIsCaseInsensitive" v-model="cond.isCaseInsensitive"/>
<label></label>
</div>
<p class="comment">选中后表示对比时忽略参数值的大小写。</p>

View File

@@ -55,10 +55,12 @@ Vue.component("http-firewall-actions-box", {
var defaultPageBody = `<!DOCTYPE html>
<html lang="en">
<title>403 Forbidden</title>
<head>
\t<title>403 Forbidden</title>
\t<style>
\t\taddress { line-height: 1.8; }
\t</style>
</head>
<body>
<h1>403 Forbidden</h1>
<address>Connection: \${remoteAddr} (Client) -&gt; \${serverAddr} (Server)</address>
@@ -83,6 +85,8 @@ Vue.component("http-firewall-actions-box", {
ipListLevels: [],
// 动作参数
allowScope: "global",
blockTimeout: "",
blockTimeoutMax: "",
blockScope: "global",
@@ -103,6 +107,7 @@ Vue.component("http-firewall-actions-box", {
tagTags: [],
pageUseDefault: true,
pageStatus: 403,
pageBody: defaultPageBody,
defaultPageBody: defaultPageBody,
@@ -137,6 +142,9 @@ Vue.component("http-firewall-actions-box", {
})
this.actionOptions = {}
},
allowScope: function (v) {
this.actionOptions["scope"] = v
},
blockTimeout: function (v) {
v = parseInt(v)
if (isNaN(v)) {
@@ -271,11 +279,13 @@ Vue.component("http-firewall-actions-box", {
methods: {
add: function () {
this.action = null
this.actionCode = "block"
this.actionCode = "page"
this.isAdding = true
this.actionOptions = {}
// 动作参数
this.allowScope = "global"
this.blockTimeout = ""
this.blockTimeoutMax = ""
this.blockScope = "global"
@@ -300,6 +310,7 @@ Vue.component("http-firewall-actions-box", {
this.tagTags = []
this.pageUseDefault = true
this.pageStatus = 403
this.pageBody = this.defaultPageBody
@@ -338,6 +349,9 @@ Vue.component("http-firewall-actions-box", {
this.editingIndex = index
this.actionCode = config.code
this.action = this.actions.$find(function (k, v) {
return v.code == config.code
})
switch (config.code) {
case "block":
@@ -356,6 +370,11 @@ Vue.component("http-firewall-actions-box", {
}
break
case "allow":
if (config.options != null && config.options.scope != null && config.options.scope.length > 0) {
this.allowScope = config.options.scope
} else {
this.allowScope = "global"
}
break
case "log":
break
@@ -424,8 +443,14 @@ Vue.component("http-firewall-actions-box", {
}
break
case "page":
this.pageUseDefault = true
this.pageStatus = 403
this.pageBody = this.defaultPageBody
if (typeof config.options.useDefault === "boolean") {
this.pageUseDefault = config.options.useDefault
} else {
this.pageUseDefault = false
}
if (config.options.status != null) {
this.pageStatus = config.options.status
}
@@ -528,6 +553,7 @@ Vue.component("http-firewall-actions-box", {
}
this.actionOptions = {
useDefault: this.pageUseDefault,
status: pageStatus,
body: this.pageBody
}
@@ -660,6 +686,13 @@ Vue.component("http-firewall-actions-box", {
<div v-for="(config, index) in configs" :data-index="index" :key="config.id" class="ui label small basic" :class="{blue: index == editingIndex}" style="margin-bottom: 0.4em">
{{config.name}} <span class="small">({{config.code.toUpperCase()}})</span>
<!-- allow -->
<span class="small" v-if="config.code == 'allow' && config.options != null && config.options.scope != null && config.options.scope.length > 0">
<span v-if="config.options.scope == 'group'">[分组]</span>
<span v-if="config.options.scope == 'server'">[网站]</span>
<span v-if="config.options.scope == 'global'">[网站和策略]</span>
</span>
<!-- block -->
<span v-if="config.code == 'block' && config.options.timeout > 0">:封禁时长{{config.options.timeout}}<span v-if="config.options.timeoutMax > config.options.timeout">-{{config.options.timeoutMax}}</span>秒</span>
@@ -686,7 +719,7 @@ Vue.component("http-firewall-actions-box", {
<span v-if="config.code == 'tag'">{{config.options.tags.join(", ")}}</span>
<!-- page -->
<span v-if="config.code == 'page'">[{{config.options.status}}]</span>
<span v-if="config.code == 'page'">[{{config.options.status}}]<span v-if="config.options.useDefault">&nbsp; [默认页面]</span></span>
<!-- redirect -->
<span v-if="config.code == 'redirect'">{{config.options.url}}</span>
@@ -698,7 +731,7 @@ Vue.component("http-firewall-actions-box", {
<span v-if="config.code == 'go_set'">{{config.options.groupName}} / {{config.options.setName}}</span>
<!-- 范围 -->
<span v-if="config.options.scope != null && config.options.scope.length > 0" class="small grey">
<span v-if="config.code != 'allow' && config.options.scope != null && config.options.scope.length > 0" class="small grey">
&nbsp;
<span v-if="config.options.scope == 'global'">[所有网站]</span>
<span v-if="config.options.scope == 'service'">[当前网站]</span>
@@ -721,6 +754,21 @@ Vue.component("http-firewall-actions-box", {
</td>
</tr>
<!-- allow -->
<tr v-if="actionCode == 'allow'">
<td>有效范围</td>
<td>
<select class="ui dropdown auto-width" v-model="allowScope">
<option value="group">分组</option>
<option value="server">网站</option>
<option value="global">网站和策略</option>
</select>
<p class="comment" v-if="allowScope == 'group'">跳过当前分组其他规则集,继续执行其他分组的规则集。</p>
<p class="comment" v-if="allowScope == 'server'">跳过当前网站所有的规则集。</p>
<p class="comment" v-if="allowScope =='global'">跳过当前网站和网站对应WAF策略所有的规则集。</p>
</td>
</tr>
<!-- block -->
<tr v-if="actionCode == 'block'">
<td>封禁范围</td>
@@ -771,7 +819,7 @@ Vue.component("http-firewall-actions-box", {
<input type="text" style="width: 5em" maxlength="9" v-model="captchaMaxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
<p class="comment"><span v-if="captchaMaxFails > 0 && captchaMaxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
</td>
</tr>
<tr v-if="actionCode == 'captcha'">
@@ -888,11 +936,17 @@ Vue.component("http-firewall-actions-box", {
<!-- page -->
<tr v-if="actionCode == 'page'">
<td>状态码 *</td>
<td>使用默认提示</td>
<td>
<checkbox v-model="pageUseDefault"></checkbox>
</td>
</tr>
<tr v-if="actionCode == 'page' && !pageUseDefault">
<td class="color-border">状态码 *</td>
<td><input type="text" style="width: 4em" maxlength="3" v-model="pageStatus"/></td>
</tr>
<tr v-if="actionCode == 'page'">
<td>网页内容</td>
<tr v-if="actionCode == 'page' && !pageUseDefault">
<td class="color-border">网页内容</td>
<td>
<textarea v-model="pageBody"></textarea>
</td>

View File

@@ -3,7 +3,16 @@ Vue.component("http-firewall-actions-view", {
props: ["v-actions"],
template: `<div>
<div v-for="action in vActions" style="margin-bottom: 0.3em">
<span :class="{red: action.category == 'block', orange: action.category == 'verify', green: action.category == 'allow'}">{{action.name}} ({{action.code.toUpperCase()}})</span>
<span :class="{red: action.category == 'block', orange: action.category == 'verify', green: action.category == 'allow'}">{{action.name}} ({{action.code.toUpperCase()}})
<div v-if="action.options != null">
<span class="grey small" v-if="action.code.toLowerCase() == 'page'">[{{action.options.status}}]</span>
<span class="grey small" v-if="action.code.toLowerCase() == 'allow' && action.options != null && action.options.scope != null && action.options.scope.length > 0">
<span v-if="action.options.scope == 'group'">[分组]</span>
<span v-if="action.options.scope == 'server'">[网站]</span>
<span v-if="action.options.scope == 'global'">[网站和策略]</span>
</span>
</div>
</span>
</div>
</div>`
})

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